4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React+Electron+Vite+SymbolSDKv3

Last updated at Posted at 2024-09-16

React+Electron+Vite

yarn create @quick-start/electron

npm の場合は、以下。

npm create @quick-start/electron

以下のことを聴かれるので、適当な回答をする。

√ Project name: ... electron-app
√ Select a framework: » react
√ Add TypeScript? ... No / [Yes]
√ Add Electron updater plugin? ... No / [Yes]
√ Enable Electron download mirror proxy? ... No / [Yes]

yarn の場合、.yarnrc.ymlがないので、追加してyarnする。
npm の場合は、そのままnpm iで良いかと。

.yarnrc.yml
nodeLinker: node-modules

デバッグ起動やビルドを一通り試しておくと良いでしょう。

Symbol-SDK v3

polyfill など

yarn add -D vite-plugin-node-polyfills vite-plugin-wasm

rendererpluginswasm, nodePolyfillsを追加。

electron.vite.config.ts
import { resolve } from 'path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import react from '@vitejs/plugin-react'
import wasm from 'vite-plugin-wasm'
import { nodePolyfills } from 'vite-plugin-node-polyfills'

export default defineConfig({
  main: {
    plugins: [externalizeDepsPlugin()]
  },
  preload: {
    plugins: [externalizeDepsPlugin()]
  },
  renderer: {
    resolve: {
      alias: {
        '@renderer': resolve('src/renderer/src')
      }
    },
    plugins: [
      react(),
      wasm(),
      nodePolyfills({
        globals: { Buffer: true },
        include: ['buffer', 'crypto', 'stream', 'util']
      })
    ]
  }
})

Symbol-SDK

そのままでは React で動かないのでパッチを当てる。
npm の場合は、postinstall-postinstall不要です。

yarn add -D patch-package postinstall-postinstall

すでにpackage.jsonpostinstallがあるので、先頭にpatch-package &&を追加。

package.json
    "postinstall": "patch-package && electron-builder install-app-deps",

patchesディレクトリを作って、パッチファイルsymbol-sdk+3.2.2.patchを作成。

patches/symbol-sdk+3.2.2.patch
diff --git a/node_modules/symbol-sdk/src/impl/ed25519_wasm.js b/node_modules/symbol-sdk/src/impl/ed25519_wasm.js
index e0cb407..459f74e 100644
--- a/node_modules/symbol-sdk/src/impl/ed25519_wasm.js
+++ b/node_modules/symbol-sdk/src/impl/ed25519_wasm.js
@@ -3,7 +3,7 @@
 /* eslint-disable import/no-extraneous-dependencies */
 import {
 	HashMode, crypto_private_sign, crypto_private_verify, crypto_sign_keypair
-} from 'symbol-crypto-wasm-node';
+} from '../../../symbol-crypto-wasm-web/symbol_crypto_wasm.js';
 /* eslint-enable import/no-extraneous-dependencies */
 
 const CRYPTO_SIGN_BYTES = 64;

Symbol-SDK インストール。

yarn add symbol-sdk symbol-crypto-wasm-web

package.json"type": "module"を追加。

package.json
  "type": "module",

tsconfigcompilerOptions"module": "ESNext", "moduleResolution": "bundler"を追加。

tsconfig.node.json
{
  "extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
  "include": [
    "electron.vite.config.*",
    "src/main/**/*",
    "src/preload/**/*"
  ],
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "bundler",
    "composite": true,
    "types": [
      "electron-vite/node"
    ]
  }
}
tsconfig.web.json
{
  "extends": "@electron-toolkit/tsconfig/tsconfig.web.json",
  "include": [
    "src/renderer/src/env.d.ts",
    "src/renderer/src/**/*",
    "src/renderer/src/**/*.tsx",
    "src/preload/*.d.ts"
  ],
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "bundler",
    "composite": true,
    "jsx": "react-jsx",
    "baseUrl": ".",
    "paths": {
      "@renderer/*": [
        "src/renderer/src/*"
      ]
    }
  }
}

preloadのファイル名をindex.jsからindex.mjsに変更。

src/main/index.ts
  const mainWindow = new BrowserWindow({
    width: 900,
    height: 670,
    show: false,
    autoHideMenuBar: true,
    ...(process.platform === 'linux' ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, '../preload/index.mjs'),
      sandbox: false
    }
  })

動作確認

evalを使用しているので、Content-Security-Policyscript-src'unsafe-eval'を追加する。

src/renderer/index.html
  <meta http-equiv="Content-Security-Policy"
    content="default-src 'self'; script-src 'unsafe-eval' 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:" />

App.tsxに Symbol-SDK を使用して、ニーモニックとアカウントを生成するコードを追記する。

src/renderer/App.tsx
import Versions from './components/Versions'
import electronLogo from './assets/electron.svg'
import { useEffect, useState } from 'react'
import { Bip32 } from 'symbol-sdk'
import { Network, SymbolAccount, SymbolFacade } from 'symbol-sdk/symbol'

function App(): JSX.Element {
  const ipcHandle = (): void => window.electron.ipcRenderer.send('ping')

  const [address, setAddress] = useState('')
  const [publicKey, setPublicKey] = useState('')

  useEffect(() => {
    // ニーモニック生成
    const bip32 = new Bip32()
    const mnemonic = bip32.random()
    // Bip32Node生成
    const passwd = ''
    const bip32Node = bip32.fromMnemonic(mnemonic, passwd)
    // faced生成
    const facade = new SymbolFacade(Network.TESTNET)
    // アカウントを生成する
    // Bip32Path生成
    const bip32Path = facade.bip32Path(0)
    // Bip32Pathから子孫Bip32Path生成
    const childBip32Node = bip32Node.derivePath(bip32Path)
    // 子孫Bip32Pathからキーペア生成
    const keyPair = SymbolFacade.bip32NodeToKeyPair(childBip32Node)
    // 公開鍵からアドレス生成
    const account = new SymbolAccount(facade, keyPair)
    setAddress(account.address.toString())
    setPublicKey(account.publicKey.toString())
  }, [])

  return (
    <>
      <img alt="logo" className="logo" src={electronLogo} />
      <div className="creator">Powered by electron-vite</div>
      <div className="text">
        Build an Electron app with <span className="react">React</span>
        &nbsp;and <span className="ts">TypeScript</span>
      </div>
      <p className="tip">
        Please try pressing <code>F12</code> to open the devTool
      </p>
      <div className="actions">
        <div className="action">
          <a href="https://electron-vite.org/" target="_blank" rel="noreferrer">
            Documentation
          </a>
        </div>
        <div className="action">
          <a target="_blank" rel="noreferrer" onClick={ipcHandle}>
            Send IPC
          </a>
        </div>
      </div>
      <div className="text" style={{ fontSize: 16 }}>
        Address: <span className="react">{address}</span>
        <br />
        PublicKey: <span className="ts">{publicKey}</span>
        <br />
      </div>
      <Versions></Versions>
    </>
  )
}

export default App
yarn dev

スクリーンショット 2024-09-16 125338.png

Node.js のモジュールを使用する

少し面倒だが、Electron なので Node.js のモジュールを使用できる。
child_process.execSyncを使用する例。

windowchild_processを生やす。

src/preload/index.ts
import { contextBridge } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
import { execSync } from 'child_process'

// Custom APIs for renderer
const api = {}

// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
  try {
    contextBridge.exposeInMainWorld('electron', electronAPI)
    contextBridge.exposeInMainWorld('api', api)
    contextBridge.exposeInMainWorld('child_process', { execSync: execSync })
  } catch (error) {
    console.error(error)
  }
} else {
  // @ts-ignore (define in dts)
  window.electron = electronAPI
  // @ts-ignore (define in dts)
  window.api = api
}

そのままだとタイプエラーが出るので、インターフェースを設定。

src/preload/index.d.ts
import { ElectronAPI } from '@electron-toolkit/preload'
import { execSync } from 'child_process'

declare global {
  interface Window {
    electron: ElectronAPI
    api: unknown
    child_process: {
      execSync: typeof execSync
    }
  }
}

windowから取り出して使用する。

src/renderer/src/App.tsx
import Versions from './components/Versions'
import electronLogo from './assets/electron.svg'
import { useEffect, useState } from 'react'
import { Bip32 } from 'symbol-sdk'
import { Network, SymbolAccount, SymbolFacade } from 'symbol-sdk/symbol'
const child_process = window.child_process

function App(): JSX.Element {
  const ipcHandle = (): void => window.electron.ipcRenderer.send('ping')

  const [address, setAddress] = useState('')
  const [publicKey, setPublicKey] = useState('')
  const [nodeVer, setNodeVer] = useState('')

  useEffect(() => {
    // ニーモニック生成
    const bip32 = new Bip32()
    const mnemonic = bip32.random()
    // Bip32Node生成
    const passwd = ''
    const bip32Node = bip32.fromMnemonic(mnemonic, passwd)
    // faced生成
    const facade = new SymbolFacade(Network.TESTNET)
    // アカウントを生成する
    // Bip32Path生成
    const bip32Path = facade.bip32Path(0)
    // Bip32Pathから子孫Bip32Path生成
    const childBip32Node = bip32Node.derivePath(bip32Path)
    // 子孫Bip32Pathからキーペア生成
    const keyPair = SymbolFacade.bip32NodeToKeyPair(childBip32Node)
    // 公開鍵からアドレス生成
    const account = new SymbolAccount(facade, keyPair)
    setAddress(account.address.toString())
    setPublicKey(account.publicKey.toString())

    const nodeVerExec = child_process.execSync('node -v')
    console.log(nodeVerExec)
    console.log(nodeVerExec.toString())
    setNodeVer(new TextDecoder().decode(nodeVerExec))
  }, [])

  return (
    <>
      <img alt="logo" className="logo" src={electronLogo} />
      <div className="creator">Powered by electron-vite</div>
      <div className="text">
        Build an Electron app with <span className="react">React</span>
        &nbsp;and <span className="ts">TypeScript</span>
      </div>
      <p className="tip">
        Please try pressing <code>F12</code> to open the devTool
      </p>
      <div className="actions">
        <div className="action">
          <a href="https://electron-vite.org/" target="_blank" rel="noreferrer">
            Documentation
          </a>
        </div>
        <div className="action">
          <a target="_blank" rel="noreferrer" onClick={ipcHandle}>
            Send IPC
          </a>
        </div>
      </div>
      <div className="text" style={{ fontSize: 16 }}>
        Address: <span className="react">{address}</span>
        <br />
        PublicKey: <span className="ts">{publicKey}</span>
        <br />
        <span className="ts">{nodeVer}</span>
      </div>
      <Versions></Versions>
    </>
  )
}

export default App
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?