Skip to content

Commit d66e027

Browse files
authored
Merge pull request #13 from legiz-ru/codex/migrate-deep-link-import-functionality-to-pandora-deeplink
feat: improve pandora deeplink import handling
2 parents 9712bb5 + 2a43afc commit d66e027

File tree

5 files changed

+339
-110
lines changed

5 files changed

+339
-110
lines changed

src-electron/main.ts

Lines changed: 118 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {app, BrowserWindow, BrowserWindowConstructorOptions, session} from 'electron';
1+
import {app, BrowserWindow, BrowserWindowConstructorOptions, ipcMain, session} from 'electron';
22
import path from 'node:path';
33
import {startServer, storeInfo} from "./server";
44
import {doQuit, initTray, showWindow} from "./tray";
@@ -11,7 +11,15 @@ import {isBootAutoLaunch, updateAutoLaunchRegistration, waitForNetworkReady} fro
1111
const isDev = !app.isPackaged;
1212

1313
// 主窗口
14-
let mainWindow: BrowserWindow;
14+
let mainWindow: BrowserWindow | null = null;
15+
16+
// 深度链接相关
17+
const DEEP_LINK_SCHEME = 'pandora-box';
18+
const DEEP_LINK_HOST_INSTALL = 'install-config';
19+
const DEEP_LINK_EVENT = 'import-profile-from-deeplink';
20+
const DEEP_LINK_READY_EVENT = 'deeplink-handler-ready';
21+
const pendingDeepLinks: string[] = [];
22+
let deepLinkHandlerReady = false;
1523
// 屏蔽安全警告
1624
process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";
1725
const createWindow = (isBoot: boolean) => {
@@ -44,6 +52,7 @@ const createWindow = (isBoot: boolean) => {
4452
};
4553
}
4654

55+
deepLinkHandlerReady = false;
4756
mainWindow = new BrowserWindow(windowOptions);
4857

4958
// 隐藏菜单栏
@@ -62,6 +71,14 @@ const createWindow = (isBoot: boolean) => {
6271
log.error('页面加载失败:', err);
6372
});
6473

74+
mainWindow.webContents.on('did-start-loading', () => {
75+
deepLinkHandlerReady = false;
76+
});
77+
78+
mainWindow.webContents.on('did-finish-load', () => {
79+
processPendingDeepLinks();
80+
});
81+
6582
// 页面加载完成再显示,避免白屏
6683
mainWindow.webContents.once('did-finish-load', () => {
6784
if (isBoot) {
@@ -72,8 +89,77 @@ const createWindow = (isBoot: boolean) => {
7289
log.info('页面加载成功');
7390
}
7491
});
92+
93+
mainWindow.on('closed', () => {
94+
deepLinkHandlerReady = false;
95+
mainWindow = null;
96+
});
97+
};
98+
99+
const isDeepLinkUrl = (arg: string | undefined): arg is string => {
100+
return typeof arg === 'string' && arg.startsWith(`${DEEP_LINK_SCHEME}://`);
101+
};
102+
103+
const processPendingDeepLinks = () => {
104+
if (!mainWindow || mainWindow.isDestroyed() || !deepLinkHandlerReady) {
105+
return;
106+
}
107+
108+
if (pendingDeepLinks.length === 0) {
109+
return;
110+
}
111+
112+
const queue = pendingDeepLinks.splice(0, pendingDeepLinks.length);
113+
showWindow();
114+
115+
for (const url of queue) {
116+
if (!url) {
117+
continue;
118+
}
119+
120+
log.info('处理深度链接队列:', url);
121+
mainWindow.webContents.send(DEEP_LINK_EVENT, {rawUrl: url});
122+
}
123+
};
124+
125+
const enqueueDeepLink = (url: string) => {
126+
pendingDeepLinks.push(url);
127+
processPendingDeepLinks();
75128
};
76129

130+
function handleDeepLink(url: string) {
131+
const trimmed = url?.trim();
132+
if (!trimmed) {
133+
return;
134+
}
135+
136+
try {
137+
const parsedUrl = new URL(trimmed);
138+
if (parsedUrl.protocol !== `${DEEP_LINK_SCHEME}:`) {
139+
return;
140+
}
141+
142+
const host = parsedUrl.hostname || parsedUrl.host;
143+
if (host && host.toLowerCase() === DEEP_LINK_HOST_INSTALL) {
144+
log.info('收到深度链接:', trimmed);
145+
enqueueDeepLink(trimmed);
146+
} else {
147+
log.warn('未知深度链接:', trimmed);
148+
}
149+
} catch (error) {
150+
log.error('解析深度链接失败:', error);
151+
}
152+
}
153+
154+
ipcMain.on(DEEP_LINK_READY_EVENT, (event) => {
155+
if (!mainWindow || event.sender !== mainWindow.webContents) {
156+
return;
157+
}
158+
159+
deepLinkHandlerReady = true;
160+
processPendingDeepLinks();
161+
});
162+
77163
// 等待 backend 传来的 port 和 secret
78164
let resolveReady: () => void;
79165
const waitForReady = new Promise<void>((resolve) => {
@@ -100,37 +186,23 @@ const agents = [
100186
}
101187
];
102188

103-
// 注册自定义协议
104-
if (!app.isDefaultProtocolClient('pandora-box')) {
105-
app.setAsDefaultProtocolClient('pandora-box');
106-
}
107-
108-
// 处理深度链接的函数
109-
function handleDeepLink(url: string) {
110-
log.info('收到深度链接:', url);
111-
112-
// 解析URL
189+
const registerDeepLinkProtocol = () => {
113190
try {
114-
const parsedUrl = new URL(url);
115-
if (parsedUrl.protocol === 'pandora-box:' && parsedUrl.hostname === 'install-config') {
116-
const subUrl = parsedUrl.searchParams.get('url');
117-
118-
if (subUrl) {
119-
log.info('准备导入配置, URL:', subUrl);
120-
121-
// 显示窗口
122-
showWindow();
123-
124-
// 向渲染进程发送导入配置的消息
125-
if (mainWindow && mainWindow.webContents) {
126-
mainWindow.webContents.send('import-profile-from-deeplink', {
127-
url: subUrl
128-
});
129-
}
130-
}
191+
if (process.defaultApp && process.argv.length >= 2) {
192+
const exePath = process.execPath;
193+
const resolvedPath = path.resolve(process.argv[1]);
194+
app.setAsDefaultProtocolClient(DEEP_LINK_SCHEME, exePath, [resolvedPath]);
195+
} else if (!app.isDefaultProtocolClient(DEEP_LINK_SCHEME)) {
196+
app.setAsDefaultProtocolClient(DEEP_LINK_SCHEME);
131197
}
132198
} catch (error) {
133-
log.error('解析深度链接失败:', error);
199+
log.error('注册深度链接协议失败:', error);
200+
}
201+
};
202+
203+
for (const arg of process.argv) {
204+
if (isDeepLinkUrl(arg)) {
205+
pendingDeepLinks.push(arg);
134206
}
135207
}
136208

@@ -139,20 +211,25 @@ const gotTheLock = app.requestSingleInstanceLock();
139211
if (!gotTheLock) {
140212
doQuit()
141213
} else {
142-
// 试图启动第二个应用实例,也需要处理深度链接
143-
app.on('second-instance', (event, commandLine) => {
144-
// Windows/Linux 下从命令行参数获取深度链接
145-
const url = commandLine.find(arg => arg.startsWith('pandora-box://'));
146-
if (url) {
147-
handleDeepLink(url);
148-
} else {
149-
showWindow();
214+
// 试图启动第二个应用实例
215+
app.on('second-instance', (_event, commandLine) => {
216+
const urls = commandLine.filter(isDeepLinkUrl);
217+
if (urls.length > 0) {
218+
urls.forEach(handleDeepLink);
150219
}
220+
showWindow();
151221
});
152222

153223
// 监听应用被激活
154224
app.on('activate', showWindow);
155225

226+
if (process.platform === 'darwin') {
227+
app.on('open-url', (event, url) => {
228+
event.preventDefault();
229+
handleDeepLink(url);
230+
});
231+
}
232+
156233
app.whenReady().then(async () => {
157234
// 判断是否开机启动
158235
const isBoot = await isBootAutoLaunch();
@@ -181,6 +258,8 @@ if (!gotTheLock) {
181258
// 等待后端启动
182259
await waitForReady;
183260

261+
registerDeepLinkProtocol();
262+
184263
// 设置请求头 Referer
185264
const agent = agents[Math.floor(Math.random() * agents.length)];
186265
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
@@ -197,21 +276,5 @@ if (!gotTheLock) {
197276

198277
// 更新开机自启路径
199278
await updateAutoLaunchRegistration()
200-
201-
// 处理 macOS 下的深度链接
202-
if (process.platform === 'darwin') {
203-
app.on('open-url', (event, url) => {
204-
event.preventDefault();
205-
handleDeepLink(url);
206-
});
207-
}
208-
209-
// 处理 Windows/Linux 下启动时的深度链接
210-
if (process.platform !== 'darwin') {
211-
const url = process.argv.find(arg => arg.startsWith('pandora-box://'));
212-
if (url) {
213-
handleDeepLink(url);
214-
}
215-
}
216279
});
217-
}
280+
}

src-electron/preload.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ contextBridge.exposeInMainWorld('pxDeepLink', {
2020
// 移除旧监听器,确保只注册一次
2121
ipcRenderer.removeAllListeners('import-profile-from-deeplink');
2222
ipcRenderer.on('import-profile-from-deeplink', (_event, data) => callback(data));
23+
},
24+
notifyReady: () => {
25+
ipcRenderer.send('deeplink-handler-ready');
2326
}
2427
});
2528

0 commit comments

Comments
 (0)