hardware-example 1.2.0-alpha.66 → 1.2.0-alpha.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,26 +1,5 @@
1
1
 
2
- # Electron Connect Example
3
-
4
- 此示例使用 `@onekeyfe/hd-common-connect-sdk` 的 `desktop-web-ble` 环境,并通过 Electron
5
- Noble BLE Transport 连接设备。示例不再启动 OneKey Bridge,也不再注入 `hd-web-sdk` iframe。
6
-
7
- Pro2 会在真实连接响应中完成 Protocol V2 探测;应用层不要根据 BLE 名称或设备型号猜测协议。
8
- 连接后的 SDK 调用和自动化测试沿用 `expo-example` 的 Core API,因此 V1 与 V2 共用同一套生命周期。
9
-
10
- 开发运行:
11
-
12
- ```bash
13
- yarn example:desktop
14
- ```
15
-
16
- 打包前会先构建 Electron 主进程和 Expo Web 资源:
17
-
18
- ```bash
19
- yarn build:example:desktop:mac
20
- ```
21
-
22
- ## Q & A
23
-
2
+ # Q & A
24
3
  ## The mac running message is damaged and cannot be opened. You should move it to the trash.
25
4
  ### Intel Mac
26
5
  1. Open settings
@@ -40,3 +19,4 @@ sudo /usr/bin/xattr -c /Applications/YourAppName.app
40
19
  ```bash
41
20
  sudo xattr -r -d com.apple.quarantine /Applications/YourAppName.app
42
21
  ```
22
+
@@ -20,7 +20,16 @@ module.exports = {
20
20
  directories: {
21
21
  output: 'out',
22
22
  },
23
- files: ['web-build', 'public', 'dist/**/*.js', '!dist/__**', 'package.json', '!scripts/**'],
23
+ files: [
24
+ 'web-build',
25
+ 'public',
26
+ 'bin',
27
+ '!public/bin/**/*',
28
+ 'dist/**/*.js',
29
+ '!dist/__**',
30
+ 'package.json',
31
+ '!scripts/**',
32
+ ],
24
33
  extraResources: [
25
34
  {
26
35
  from: 'public/icons/512x512.png',
@@ -51,6 +60,12 @@ module.exports = {
51
60
  mac: {
52
61
  // skip code signing
53
62
  identity: null,
63
+ extraResources: [
64
+ {
65
+ from: 'public/bin/bridge/mac-${arch}',
66
+ to: 'bin/bridge',
67
+ },
68
+ ],
54
69
  icon: 'public/icons/512x512.png',
55
70
  artifactName: 'Hardware-Example-mac-${arch}.${ext}',
56
71
  hardenedRuntime: true,
@@ -67,6 +82,12 @@ module.exports = {
67
82
  },
68
83
  },
69
84
  win: {
85
+ extraResources: [
86
+ {
87
+ from: 'public/bin/bridge/win-${arch}',
88
+ to: 'bin/bridge',
89
+ },
90
+ ],
70
91
  icon: 'public/icons/512x512.png',
71
92
  artifactName: 'Hardware-Example-win-${arch}.${ext}',
72
93
  verifyUpdateCodeSignature: false,
@@ -78,6 +99,12 @@ module.exports = {
78
99
  ],
79
100
  },
80
101
  linux: {
102
+ extraResources: [
103
+ {
104
+ from: 'public/bin/bridge/linux-${arch}',
105
+ to: 'bin/bridge',
106
+ },
107
+ ],
81
108
  icon: 'public/icons/512x512.png',
82
109
  artifactName: 'Hardware-Example-linux-${arch}.${ext}',
83
110
  executableName: 'onekey-hardware-example',
package/package.json CHANGED
@@ -2,15 +2,16 @@
2
2
  "name": "hardware-example",
3
3
  "productName": "HardwareExample",
4
4
  "executableName": "onekey-hardware-example",
5
- "version": "1.2.0-alpha.66",
5
+ "version": "1.2.0-alpha.67",
6
6
  "author": "OneKey",
7
- "description": "OneKey Hardware SDK Electron BLE example",
7
+ "description": "End-to-end encrypted workspaces for teams",
8
8
  "main": "dist/index.js",
9
9
  "license": "GPL-3.0-only",
10
10
  "scripts": {
11
+ "copy:inject": "node scripts/copy-injected.js",
11
12
  "clean:build": "rimraf out",
12
- "dev-electron-web": "cross-env NODE_ENV=development yarn build:main && cd ../expo-example && yarn dev:electron-web",
13
- "build-electron-web": "yarn build:main && cd ../expo-example && yarn build:electron-web",
13
+ "dev-electron-web": "cross-env NODE_ENV=development yarn copy:inject && yarn build:main && cd ../expo-example && yarn dev:electron-web",
14
+ "build-electron-web": "yarn copy:inject && yarn build:main && cd ../expo-example && yarn build:electron-web",
14
15
  "dev": "npx concurrently \"yarn dev:electron\" \"cross-env TRANSFORM_REGENERATOR_DISABLED=true BROWSER=none yarn dev-electron-web\"",
15
16
  "dev:electron": "electron --inspect=5858 dist/index.js",
16
17
  "build:main": "webpack --config webpack.config.ts",
@@ -21,12 +22,14 @@
21
22
  "ts:check": "yarn tsc --noEmit"
22
23
  },
23
24
  "dependencies": {
24
- "@onekeyfe/hd-transport-electron": "1.2.0-alpha.66",
25
+ "@onekeyfe/hd-transport-electron": "1.1.34-alpha.1",
25
26
  "@stoprocent/noble": "2.3.16",
26
27
  "debug": "4.3.4",
27
28
  "electron-is-dev": "^3.0.1",
28
29
  "electron-log": "^5.1.5",
29
- "electron-updater": "^6.2.1"
30
+ "electron-updater": "^6.2.1",
31
+ "fs-extra": "^11.2.0",
32
+ "node-fetch": "^2.6.7"
30
33
  },
31
34
  "devDependencies": {
32
35
  "@types/webpack": "^5.28.5",
@@ -42,5 +45,5 @@
42
45
  "**/node-gyp": "^10.0.1",
43
46
  "tmp": "0.2.4"
44
47
  },
45
- "gitHead": "81629c086665bb92ce4d52087ae47f084976da79"
48
+ "gitHead": "5fea66e8c65a1fab2ccc099b2811c8749b2487e0"
46
49
  }
@@ -0,0 +1,38 @@
1
+ /* eslint-disable @typescript-eslint/no-var-requires */
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+
5
+ const rootDir = path.join(__dirname, '../../../../');
6
+
7
+ const sourceDir = path.join(rootDir, 'node_modules/@onekeyfe/hd-web-sdk/build/');
8
+ const targetDir = path.join(rootDir, 'packages/connect-examples/electron-example/public/js-sdk/');
9
+
10
+ async function copyFiles() {
11
+ try {
12
+ await fs.remove(targetDir);
13
+ await fs.ensureDir(targetDir);
14
+ console.log(`Target directory ${targetDir} is ensured`);
15
+
16
+ await fs.copy(sourceDir, targetDir, {
17
+ recursive: true,
18
+ overwrite: true,
19
+ filter: (src, dest) => {
20
+ // Don't copy onekey-js-sdk
21
+ if (src.endsWith('onekey-js-sdk.min.js') || src.endsWith('onekey-js-sdk.js')) {
22
+ return false;
23
+ }
24
+
25
+ // Don't copy source maps
26
+ if (src.endsWith('.map')) {
27
+ return false;
28
+ }
29
+ return true;
30
+ },
31
+ });
32
+ console.log(`Files copied from ${sourceDir} to ${targetDir}`);
33
+ } catch (error) {
34
+ console.error('Error copying files:', error);
35
+ }
36
+ }
37
+
38
+ copyFiles();
package/src/config.ts CHANGED
@@ -4,5 +4,9 @@ export const ipcMessageKeys = {
4
4
  UPDATE_DOWNLOADED: 'update/downloaded',
5
5
  UPDATE_RESTART: 'update/restartApp',
6
6
 
7
+ APP_RELOAD_BRIDGE_PROCESS: 'app/reloadBridgeProcess',
8
+
9
+ INJECT_ONEKEY_DESKTOP_GLOBALS: 'inject/onekeyDesktop',
10
+
7
11
  APP_RESTART: 'app/restart',
8
12
  };
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ import { autoUpdater } from 'electron-updater';
7
7
  import { exec } from 'child_process';
8
8
  import { initNobleBleSupport } from '@onekeyfe/hd-transport-electron';
9
9
 
10
+ import initProcess, { restartBridge } from './process';
10
11
  import { ipcMessageKeys } from './config';
11
12
 
12
13
  // Set log level
@@ -23,6 +24,21 @@ let mainWindow: BrowserWindow | null;
23
24
 
24
25
  let isAppReady = false;
25
26
 
27
+ (global as any).resourcesPath = isDevelopment
28
+ ? path.join(__dirname, '../public')
29
+ : path.join(process.resourcesPath, 'app');
30
+ const staticPath = isDevelopment
31
+ ? path.join(__dirname, '../public')
32
+ : path.join((global as any).resourcesPath, 'public');
33
+
34
+ const sdkConnectSrc = isDevelopment
35
+ ? `file://${path.join(staticPath, 'js-sdk/')}`
36
+ : path.join('public', 'js-sdk/');
37
+
38
+ function initChildProcess() {
39
+ return initProcess({ isDevelopment });
40
+ }
41
+
26
42
  function showMainWindow() {
27
43
  if (!mainWindow) {
28
44
  return;
@@ -64,11 +80,13 @@ function createMainWindow() {
64
80
  height: Math.min(1920 / ratio, dimensions.height),
65
81
  webPreferences: {
66
82
  spellcheck: false,
83
+ webviewTag: true,
67
84
  webSecurity: !isDevelopment,
68
85
  // @ts-expect-error
69
86
  nativeWindowOpen: true,
70
87
  allowRunningInsecureContent: isDevelopment,
71
- contextIsolation: true,
88
+ // webview injected js needs isolation=false, because property can not be exposeInMainWorld() when isolation enabled.
89
+ contextIsolation: false,
72
90
  preload: path.join(__dirname, 'preload.js'),
73
91
  sandbox: false,
74
92
  },
@@ -78,6 +96,15 @@ function createMainWindow() {
78
96
  browserWindow.webContents.openDevTools();
79
97
  }
80
98
 
99
+ browserWindow.webContents.on('did-finish-load', () => {
100
+ console.log('browserWindow >>>> did-finish-load');
101
+ browserWindow.webContents.send(ipcMessageKeys.INJECT_ONEKEY_DESKTOP_GLOBALS, {
102
+ resourcesPath: (global as any).resourcesPath,
103
+ staticPath: `file://${staticPath}`,
104
+ sdkConnectSrc,
105
+ });
106
+ });
107
+
81
108
  const src = isDevelopment
82
109
  ? 'http://localhost:19006/'
83
110
  : formatUrl({
@@ -107,9 +134,25 @@ function createMainWindow() {
107
134
  console.log('set isAppReady on browserWindow dom-ready', isAppReady);
108
135
  });
109
136
 
137
+ const filter = {
138
+ urls: ['http://127.0.0.1:21320/*', 'http://localhost:21320/*'],
139
+ };
140
+
141
+ session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => {
142
+ const { url } = details;
143
+ if (url.startsWith('http://127.0.0.1:21320/') || url.startsWith('http://localhost:21320/')) {
144
+ // resolve onekey bridge CORS error
145
+ details.requestHeaders.Origin = 'https://jssdk.onekey.so';
146
+ }
147
+
148
+ callback({ cancel: false, requestHeaders: details.requestHeaders });
149
+ });
150
+
110
151
  // 记录已授权的设备
152
+ let grantedDeviceThroughPermHandler = null;
153
+
111
154
  browserWindow.webContents.session.setPermissionCheckHandler(
112
- (_webContents, permission, requestingOrigin, details) => {
155
+ (webContents, permission, requestingOrigin, details) => {
113
156
  log.debug('WebUSB: 权限检查被调用:', {
114
157
  permission,
115
158
  requestingOrigin,
@@ -134,6 +177,7 @@ function createMainWindow() {
134
177
  // 允许所有 USB 设备请求
135
178
  if (details.deviceType === 'usb') {
136
179
  log.debug('WebUSB: 记录已授权的设备');
180
+ grantedDeviceThroughPermHandler = details.device;
137
181
  return true;
138
182
  }
139
183
  return false;
@@ -169,6 +213,24 @@ function createMainWindow() {
169
213
  if (!isDevelopment) {
170
214
  const PROTOCOL = 'file';
171
215
  session.defaultSession.protocol.interceptFileProtocol(PROTOCOL, (request, callback) => {
216
+ const isJsSdkFile = request.url.indexOf('/public/js-sdk') > -1;
217
+ const isIFrameHtml = request.url.indexOf('/public/js-sdk/iframe.html') > -1;
218
+
219
+ // resolve iframe path
220
+ if (isJsSdkFile && isIFrameHtml) {
221
+ callback({
222
+ path: path.join(__dirname, '..', 'public', 'js-sdk', 'iframe.html'),
223
+ });
224
+ return;
225
+ }
226
+
227
+ // resolve jssdk path
228
+ if (isJsSdkFile) {
229
+ const url = request.url.substr(PROTOCOL.length + 1);
230
+ callback(path.join(__dirname, '..', url));
231
+ return;
232
+ }
233
+
172
234
  // resolve main app path
173
235
  let url = request.url.substr(PROTOCOL.length + 1);
174
236
  url = path.join(__dirname, '..', 'web-build', url);
@@ -209,7 +271,7 @@ const singleInstance = app.requestSingleInstanceLock();
209
271
  if (!singleInstance && !process.mas) {
210
272
  quitOrMinimizeApp();
211
273
  } else {
212
- app.on('second-instance', (_event, _argv) => {
274
+ app.on('second-instance', (e, argv) => {
213
275
  if (mainWindow) {
214
276
  if (mainWindow.isMinimized()) mainWindow.restore();
215
277
  showMainWindow();
@@ -230,6 +292,7 @@ if (!singleInstance && !process.mas) {
230
292
  log.error('Failed to initialize Noble BLE support:', e);
231
293
  }
232
294
 
295
+ initChildProcess();
233
296
  showMainWindow();
234
297
  console.log('日志文件位置:', log.transports.file.getFile().path);
235
298
  });
@@ -240,6 +303,10 @@ ipcMain.on(ipcMessageKeys.UPDATE_RESTART, () => {
240
303
  autoUpdater.quitAndInstall();
241
304
  });
242
305
 
306
+ ipcMain.on(ipcMessageKeys.APP_RELOAD_BRIDGE_PROCESS, () => {
307
+ restartBridge();
308
+ });
309
+
243
310
  // Simplified Bluetooth System API Implementation
244
311
  class BluetoothSystemManager {
245
312
  openBluetoothSettings(): void {
package/src/preload.ts CHANGED
@@ -17,6 +17,7 @@ export interface BluetoothSystemAPI {
17
17
  // Extend the base DesktopAPI with this specific application's needs
18
18
  export interface DesktopAPI extends BaseDesktopAPI {
19
19
  restart: () => void;
20
+ reloadBridgeProcess: () => void;
20
21
 
21
22
  // Generic IPC methods
22
23
  invoke: (channel: string, ...args: any[]) => Promise<any>;
@@ -43,6 +44,17 @@ const validChannels = [
43
44
  ipcMessageKeys.UPDATE_DOWNLOADED,
44
45
  ];
45
46
 
47
+ ipcRenderer.on(ipcMessageKeys.INJECT_ONEKEY_DESKTOP_GLOBALS, (_, globals) => {
48
+ try {
49
+ contextBridge.exposeInMainWorld('ONEKEY_DESKTOP_GLOBALS', globals);
50
+ } catch (error) {
51
+ // @ts-expect-error
52
+ window.ONEKEY_DESKTOP_GLOBALS = globals;
53
+ // Fallback for development or when contextBridge is not available
54
+ console.warn('Failed to expose ONEKEY_DESKTOP_GLOBALS via contextBridge:', error);
55
+ }
56
+ });
57
+
46
58
  const desktopApi = {
47
59
  // Generic IPC methods
48
60
  invoke: (channel: string, ...args: any[]) => ipcRenderer.invoke(channel, ...args),
@@ -63,6 +75,10 @@ const desktopApi = {
63
75
  updateReload: () => {
64
76
  ipcRenderer.send(ipcMessageKeys.UPDATE_RESTART);
65
77
  },
78
+ reloadBridgeProcess: () => {
79
+ ipcRenderer.send(ipcMessageKeys.APP_RELOAD_BRIDGE_PROCESS);
80
+ },
81
+
66
82
  // Noble BLE specific methods
67
83
  nobleBle: {
68
84
  enumerate: () => ipcRenderer.invoke(EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE),
@@ -0,0 +1,212 @@
1
+ import { spawn } from 'child_process';
2
+ import electron from 'electron';
3
+ import path from 'path';
4
+
5
+ import type { ChildProcess } from 'child_process';
6
+
7
+ export type Status = {
8
+ service: boolean;
9
+ process: boolean;
10
+ };
11
+
12
+ export type Options = {
13
+ startupThrottleTime?: number;
14
+ stopWaitTimes?: number;
15
+ autoRestart?: number;
16
+ };
17
+
18
+ const defaultOptions: Options = {
19
+ startupThrottleTime: 0,
20
+ stopWaitTimes: 10,
21
+ autoRestart: 2,
22
+ } as const;
23
+
24
+ export default abstract class BaseProcess {
25
+ process: ChildProcess | null;
26
+
27
+ resource: string;
28
+
29
+ processName: string;
30
+
31
+ options: Options;
32
+
33
+ launchThrottle: ReturnType<typeof setTimeout> | null;
34
+
35
+ supportedSystems = ['mac-x64', 'win-x64', 'linux-arm64', 'linux-x64'];
36
+
37
+ stopped = false;
38
+
39
+ constructor(resource: string, processName: string, options: Options = defaultOptions) {
40
+ this.process = null;
41
+ this.launchThrottle = null;
42
+ this.resource = resource;
43
+ this.processName = processName;
44
+ this.options = {
45
+ ...defaultOptions,
46
+ ...options,
47
+ };
48
+
49
+ const { system } = this.getPlatformInfo();
50
+ if (!this.isSystemSupported(system)) {
51
+ console.error('Unsupported system:', system);
52
+ }
53
+ }
54
+
55
+ abstract getStatus(): Promise<{ service: boolean; process: boolean }>;
56
+
57
+ async start(params: string[] = [], isDev = false) {
58
+ const { system, ext } = this.getPlatformInfo();
59
+ if (this.launchThrottle) {
60
+ console.debug('Throttling launch, cancel process');
61
+ return;
62
+ }
63
+
64
+ const status = await this.getStatus();
65
+
66
+ if (status.service) {
67
+ console.debug('Service already running');
68
+ return;
69
+ }
70
+
71
+ if (status.process) {
72
+ console.debug('Process already running, service not');
73
+ return;
74
+ }
75
+
76
+ if (this.options.startupThrottleTime && this.options.startupThrottleTime > 0) {
77
+ console.debug('Throttling startup');
78
+
79
+ this.launchThrottle = setTimeout(() => {
80
+ console.debug('Cleaning up launch throttle');
81
+ this.launchThrottle = null;
82
+ }, this.options.startupThrottleTime * 1000);
83
+ }
84
+
85
+ this.stopped = false;
86
+
87
+ const appPath = electron.app.getAppPath();
88
+ let processDir;
89
+ if (isDev) {
90
+ processDir = path.resolve(appPath, '../', 'public', 'bin', this.resource, system);
91
+ } else {
92
+ processDir = path.resolve(appPath, '../', 'bin', this.resource);
93
+ }
94
+
95
+ const processPath = path.join(processDir, `${this.processName}${ext}`);
96
+ const processEnv = { ...process.env };
97
+ // library search path for macOS
98
+ processEnv.DYLD_LIBRARY_PATH = processEnv.DYLD_LIBRARY_PATH
99
+ ? `${processEnv.DYLD_LIBRARY_PATH}:${processDir}`
100
+ : `${processDir}`;
101
+ // library search path for Linux
102
+ processEnv.LD_LIBRARY_PATH = processEnv.LD_LIBRARY_PATH
103
+ ? `${processEnv.LD_LIBRARY_PATH}:${processDir}`
104
+ : `${processDir}`;
105
+
106
+ console.info([
107
+ 'Starting process:',
108
+ `- Path: ${processPath}`,
109
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
110
+ `- Params: ${params}`,
111
+ `- CWD: ${processDir}`,
112
+ ]);
113
+ this.process = spawn(processPath, params, {
114
+ cwd: processDir,
115
+ env: processEnv,
116
+ stdio: ['ignore', 'ignore', 'ignore'],
117
+ });
118
+ this.process.on('error', err => this.onError(err));
119
+ this.process.on('exit', code => this.onExit(code));
120
+ }
121
+
122
+ /**
123
+ * Stops the process
124
+ */
125
+ stop() {
126
+ return new Promise<void>(resolve => {
127
+ this.stopped = true;
128
+
129
+ if (!this.process) {
130
+ console.warn('process already stopped');
131
+ resolve();
132
+ return;
133
+ }
134
+
135
+ console.debug('Stopping process');
136
+ this.process.kill();
137
+
138
+ let timeout = 0;
139
+ const interval = setInterval(() => {
140
+ if (!this.process || this.process.killed) {
141
+ console.info('Process killed successfully');
142
+ clearInterval(interval);
143
+ this.process = null;
144
+ resolve();
145
+ return;
146
+ }
147
+
148
+ if (this.options.stopWaitTimes && timeout < this.options.stopWaitTimes) {
149
+ console.info('Process Still alive, checking again...');
150
+ timeout += 1;
151
+ } else {
152
+ console.info('Process Still alive, going for the SIGKILL');
153
+ this.process.kill('SIGKILL');
154
+ }
155
+ }, 1000);
156
+ });
157
+ }
158
+
159
+ async restart() {
160
+ console.info('Restarting');
161
+ await this.stop();
162
+ await this.start();
163
+ }
164
+
165
+ onError(err: Error) {
166
+ console.error('Process exit error: ', err.message);
167
+ }
168
+
169
+ onExit(code: number | null) {
170
+ console.info(
171
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
172
+ `Exited, code: ${code ?? 'unknown'} (Stopped: ${this.stopped})`
173
+ );
174
+ this.process = null;
175
+
176
+ if (this.options.autoRestart && this.options.autoRestart > 0 && !this.stopped) {
177
+ console.debug('restarting...');
178
+ let restartDelay = this.options.autoRestart;
179
+
180
+ // Add throttle delay to prevent the process from never restarting if the throttle is hit
181
+ if (this.launchThrottle && this.options.startupThrottleTime) {
182
+ restartDelay += this.options.startupThrottleTime;
183
+ }
184
+
185
+ setTimeout(() => this.start(), restartDelay * 1000);
186
+ }
187
+ }
188
+
189
+ isSystemSupported(system: string) {
190
+ return this.supportedSystems.includes(system);
191
+ }
192
+
193
+ getPlatformInfo() {
194
+ const { arch } = process;
195
+ const platform = this.getPlatform();
196
+ const ext = platform === 'win' ? '.exe' : '';
197
+ const system = `${platform}-${arch}`;
198
+ console.debug('arch: ', arch);
199
+ return { system, platform, arch, ext };
200
+ }
201
+
202
+ getPlatform() {
203
+ switch (process.platform) {
204
+ case 'darwin':
205
+ return 'mac';
206
+ case 'win32':
207
+ return 'win';
208
+ default:
209
+ return process.platform;
210
+ }
211
+ }
212
+ }
@@ -0,0 +1,93 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
2
+ import fetch from 'node-fetch';
3
+
4
+ import BaseProcess from './BaseProcess';
5
+
6
+ import type { Status } from './BaseProcess';
7
+
8
+ class BridgeProcess extends BaseProcess {
9
+ constructor() {
10
+ super('bridge', 'onekeyd', {
11
+ startupThrottleTime: 3,
12
+ });
13
+ console.info('logger file name =====> :');
14
+ }
15
+
16
+ async getStatus(): Promise<Status> {
17
+ try {
18
+ const resp = await fetch(`http://127.0.0.1:21320/`, {
19
+ method: 'POST',
20
+ headers: {
21
+ Origin: 'https://electron.onekey.so',
22
+ },
23
+ });
24
+ console.debug(`Checking status (${resp.status})`);
25
+ if (resp.status === 200) {
26
+ const data = await resp.json();
27
+ if (data?.version) {
28
+ return {
29
+ service: true,
30
+ process: true,
31
+ };
32
+ }
33
+ }
34
+ } catch (err: any) {
35
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
36
+ console.error(`Status error: ${err.message}`);
37
+ }
38
+
39
+ // process
40
+ return {
41
+ service: false,
42
+ process: Boolean(this.process),
43
+ };
44
+ }
45
+ }
46
+
47
+ async function fetchWithTimeout(url: string, options: RequestInit & { timeout: number }) {
48
+ const { timeout = 3000 } = options;
49
+
50
+ const controller = new AbortController();
51
+ const id = setTimeout(() => controller.abort(), timeout);
52
+ // @ts-expect-error
53
+ const response = await fetch(url, {
54
+ ...options,
55
+ signal: controller.signal as any,
56
+ });
57
+ clearTimeout(id);
58
+ return response;
59
+ }
60
+
61
+ export const BridgeHeart = {
62
+ timer: null as ReturnType<typeof setInterval> | null,
63
+ start: (callback: () => void) => {
64
+ const checkBridge = async () => {
65
+ try {
66
+ const localBridgeUrl = 'http://127.0.0.1:21320/';
67
+ const resp = await fetchWithTimeout(localBridgeUrl, {
68
+ method: 'POST',
69
+ headers: {
70
+ Origin: 'https://electron.onekey.so',
71
+ },
72
+ timeout: 3000,
73
+ });
74
+
75
+ if (resp.status !== 200) {
76
+ console.debug(`Bridge Heart Checking ${localBridgeUrl} (${resp.status})`);
77
+ // check bridge failed, restart it
78
+ callback?.();
79
+ }
80
+ } catch (err: any) {
81
+ console.error(
82
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
83
+ `Bridge heart check error, will restart bridge process: ${err.message}`
84
+ );
85
+ callback?.();
86
+ }
87
+ };
88
+
89
+ BridgeHeart.timer = setInterval(checkBridge, 10000);
90
+ },
91
+ };
92
+
93
+ export default BridgeProcess;
@@ -0,0 +1,36 @@
1
+ import { app } from 'electron';
2
+
3
+ import BridgeProcess, { BridgeHeart } from './Bridge';
4
+
5
+ let bridgeInstance: BridgeProcess;
6
+ export const launchBridge = async (isDevelopment: boolean) => {
7
+ const bridge = new BridgeProcess();
8
+
9
+ try {
10
+ console.info('bridge: Staring');
11
+ await bridge.start([], isDevelopment);
12
+ bridgeInstance = bridge;
13
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
14
+ BridgeHeart.start(() => restartBridge());
15
+ } catch (err) {
16
+ console.error(`bridge: Start failed: ${(err as Error).message}`);
17
+ console.error(err);
18
+ }
19
+
20
+ app.on('before-quit', () => {
21
+ console.info('bridge', 'Stopping when app quit');
22
+ bridge.stop();
23
+ });
24
+ };
25
+
26
+ export const restartBridge = async () => {
27
+ console.debug('bridge: ', 'Restarting');
28
+ await bridgeInstance?.restart();
29
+ };
30
+
31
+ const init = async ({ isDevelopment }: { isDevelopment: boolean }) => {
32
+ console.info('Electron main process log path: ');
33
+ await launchBridge(isDevelopment);
34
+ };
35
+
36
+ export default init;