fix: deep link handling at electron startup

This commit is contained in:
Elysia
2026-01-31 16:07:15 +08:00
parent 3fe37f2773
commit 0ed7f64a21
6 changed files with 100 additions and 31 deletions

2
electron/global.ts Normal file
View File

@@ -0,0 +1,2 @@
import { ref } from 'vue';
export let isLoaded = ref(false);

View File

@@ -4,7 +4,7 @@ import readline from 'node:readline';
import { promisify } from 'node:util'; import { promisify } from 'node:util';
import pino from 'pino'; import pino from 'pino';
const logger = pino({ 'name': 'download-manager' }); const logger = pino({ 'name': 'install-manager' });
type InstallTask = { type InstallTask = {
id: number; id: number;

View File

@@ -3,6 +3,9 @@
* Author: juxnpxblo@github * Author: juxnpxblo@github
*/ */
import { app } from "electron"; import { app } from "electron";
import pino from "pino";
const logger = pino({ 'name': 'deeplink.ts' });
type Query = Record<string, string>; type Query = Record<string, string>;
export type Listener = (query: Query) => any; export type Listener = (query: Query) => any;
@@ -48,13 +51,13 @@ const listeners = new ListenersMap();
export const deepLink = { export const deepLink = {
on: (event: string, listener: Listener) => { on: (event: string, listener: Listener) => {
const count = listeners.add(event, listener); const count = listeners.add(event, listener);
console.log( logger.info(
`Deep link: listener added for event ${event}. Total event listeners: ${count}` `Deep link: listener added for event ${event}. Total event listeners: ${count}`
); );
}, },
off: (event: string, listener: Listener) => { off: (event: string, listener: Listener) => {
const count = listeners.remove(event, listener); const count = listeners.remove(event, listener);
console.log( logger.info(
`Deep link: listener removed for event ${event}. Total event listeners: ${count}` `Deep link: listener removed for event ${event}. Total event listeners: ${count}`
); );
}, },
@@ -67,29 +70,33 @@ export const deepLink = {
}, },
}; };
app.on("second-instance", (_e, commandLine) => { export function handleCommandLine(commandLine: string[]) {
const target = commandLine.find((arg) => const target = commandLine.find((arg) =>
protocols.some((protocol) => arg.startsWith(protocol + "://")) protocols.some((protocol) => arg.startsWith(protocol + "://"))
); );
if (!target) return; if (!target) return;
console.log(`Deep link: protocol link got: ${target}`); logger.info(`Deep link: protocol link got: ${target}`);
try { try {
const url = new URL(target); const url = new URL(target);
const action = url.hostname; const action = url.hostname;
console.log(`Deep link: action found: ${action}`); logger.info(`Deep link: action found: ${action}`);
const query: Query = {}; const query: Query = {};
url.searchParams.forEach((value, key) => { url.searchParams.forEach((value, key) => {
query[key] = value; query[key] = value;
}); });
console.log(`Deep link: query found: ${JSON.stringify(query)}`); logger.info(`Deep link: query found: ${JSON.stringify(query)}`);
const emitCount = listeners.emit(action, query); const emitCount = listeners.emit(action, query);
console.log(`Deep link: emitted for ${emitCount} listeners`); logger.info(`Deep link: emitted for ${emitCount} listeners`);
} catch (error) { } catch (error) {
console.error(`Deep link: error parsing URL: ${error}`); logger.error(`Deep link: error parsing URL: ${error}`);
} }
}
app.on("second-instance", (_e, commandLine) => {
handleCommandLine(commandLine);
}); });

View File

@@ -1,34 +1,76 @@
import { BrowserWindow } from 'electron'; import { BrowserWindow } from 'electron';
import { deepLink } from './deeplink'; import { deepLink } from './deeplink';
import { isLoaded } from '../global';
import pino from 'pino';
const logger = pino({ 'name': 'handle-url-scheme.ts' });
const pendingActions: Array<() => void> = [];
new Promise<void>((resolve) => {
const checkLoaded = () => {
if (isLoaded.value) {
resolve();
} else {
setTimeout(checkLoaded, 100);
}
};
checkLoaded();
}).then(() => {
while (pendingActions.length > 0) {
const action = pendingActions.shift();
if (action) action();
}
});
deepLink.on("event", (query) => { deepLink.on("event", (query) => {
console.log(`Deep link: event "event" fired with query: ${JSON.stringify(query)}`); logger.info(`Deep link: event "event" fired with query: ${JSON.stringify(query)}`);
}); });
deepLink.on("action", (query) => { deepLink.on("action", (query) => {
console.log(`Deep link: event "action" fired with query: ${JSON.stringify(query)}`); logger.info(`Deep link: event "action" fired with query: ${JSON.stringify(query)}`);
const win = BrowserWindow.getAllWindows()[0];
if (!win) return; const action = () => {
const win = BrowserWindow.getAllWindows()[0];
if (!win) return;
if (query.cmd === 'update') { if (query.cmd === 'update') {
win.webContents.send('deep-link-update'); win.webContents.send('deep-link-update');
if (win.isMinimized()) win.restore(); if (win.isMinimized()) win.restore();
win.focus(); win.focus();
} else if (query.cmd === 'list') { } else if (query.cmd === 'list') {
win.webContents.send('deep-link-installed'); win.webContents.send('deep-link-installed');
if (win.isMinimized()) win.restore(); if (win.isMinimized()) win.restore();
win.focus(); win.focus();
}
};
logger.info(`isLoaded: ${isLoaded.value}`);
if (isLoaded.value) {
action();
} else {
pendingActions.push(action);
} }
}); });
deepLink.on("install", (query) => { deepLink.on("install", (query) => {
console.log(`Deep link: event "install" fired with query: ${JSON.stringify(query)}`); logger.info(`Deep link: event "install" fired with query: ${JSON.stringify(query)}`);
const win = BrowserWindow.getAllWindows()[0];
if (!win) return; const action = () => {
const win = BrowserWindow.getAllWindows()[0];
if (!win) return;
if (query.pkg) { if (query.pkg) {
win.webContents.send('deep-link-install', query.pkg); win.webContents.send('deep-link-install', query.pkg);
if (win.isMinimized()) win.restore(); if (win.isMinimized()) win.restore();
win.focus(); win.focus();
}
};
if (isLoaded.value) {
action();
} else {
pendingActions.push(action);
} }
}); });

View File

@@ -1,17 +1,22 @@
import { app, BrowserWindow, shell } from 'electron' import { app, BrowserWindow, ipcMain, shell } from 'electron'
import { createRequire } from 'node:module' import { createRequire } from 'node:module'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import path from 'node:path' import path from 'node:path'
import os from 'node:os' import os from 'node:os'
import pino from 'pino'
import { handleCommandLine } from './deeplink.js'
import { isLoaded } from '../global.js'
// Assure single instance application // Assure single instance application
if (!app.requestSingleInstanceLock()) { if (!app.requestSingleInstanceLock()) {
app.exit(0); app.exit(0);
} }
import './handle-url-scheme.js'
import './backend/install-manager.js' import './backend/install-manager.js'
import './handle-url-scheme.js'
const logger = pino({ 'name': 'index.ts' });
const require = createRequire(import.meta.url) const require = createRequire(import.meta.url)
const __dirname = path.dirname(fileURLToPath(import.meta.url)) const __dirname = path.dirname(fileURLToPath(import.meta.url))
@@ -79,6 +84,7 @@ async function createWindow() {
// Test actively push message to the Electron-Renderer // Test actively push message to the Electron-Renderer
win.webContents.on('did-finish-load', () => { win.webContents.on('did-finish-load', () => {
win?.webContents.send('main-process-message', new Date().toLocaleString()) win?.webContents.send('main-process-message', new Date().toLocaleString())
logger.info('Renderer process is ready.');
}) })
// Make all links open with the browser, not with the application // Make all links open with the browser, not with the application
@@ -89,7 +95,16 @@ async function createWindow() {
// win.webContents.on('will-navigate', (event, url) => { }) #344 // win.webContents.on('will-navigate', (event, url) => { }) #344
} }
app.whenReady().then(createWindow) ipcMain.on('renderer-ready', (event, args) => {
logger.info('Received renderer-ready event with args: ' + JSON.stringify(args));
isLoaded.value = args.status;
logger.info(`isLoaded set to: ${isLoaded.value}`);
})
app.whenReady().then(() => {
createWindow()
handleCommandLine(process.argv)
})
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
win = null win = null

View File

@@ -588,6 +588,9 @@ onMounted(async () => {
tryOpen(); tryOpen();
} }
}); });
window.ipcRenderer.send('renderer-ready', { status: true });
logger.info('Renderer process is ready!');
}); });
// 观察器 // 观察器