mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-04-26 09:20:18 +08:00
添加对 legacy store 协议格式的支持,当收到 spk://store/category/pkgname 格式的 deep link 时,忽略 category 直接使用 pkgname 查找并打开应用详情。如果应用未找到,则回退到搜索模式。
132 lines
3.7 KiB
TypeScript
132 lines
3.7 KiB
TypeScript
/**
|
||
* Deep link handler for Electron app.
|
||
* Author: juxnpxblo@github
|
||
*/
|
||
import { app } from "electron";
|
||
import pino from "pino";
|
||
|
||
const logger = pino({ name: "deeplink.ts" });
|
||
type Query = Record<string, string>;
|
||
export type Listener = (query: Query) => void;
|
||
|
||
class ListenersMap {
|
||
private map: Map<string, Set<Listener>> = new Map();
|
||
|
||
add(action: string, listener: Listener) {
|
||
if (!this.map.has(action)) {
|
||
this.map.set(action, new Set());
|
||
}
|
||
this.map.get(action)!.add(listener);
|
||
|
||
return this.map.get(action)!.size;
|
||
}
|
||
|
||
remove(action: string, listener: Listener) {
|
||
const listeners = this.map.get(action);
|
||
if (!listeners) return 0;
|
||
|
||
listeners.delete(listener);
|
||
|
||
if (listeners.size === 0) {
|
||
this.map.delete(action);
|
||
return 0;
|
||
}
|
||
|
||
return listeners.size;
|
||
}
|
||
|
||
emit(action: string, query: Query) {
|
||
const actionListeners = this.map.get(action);
|
||
if (!actionListeners) return 0;
|
||
|
||
actionListeners.forEach((listener) => listener(query));
|
||
|
||
return actionListeners.size;
|
||
}
|
||
}
|
||
|
||
const protocols = ["spk"];
|
||
const listeners = new ListenersMap();
|
||
|
||
export const deepLink = {
|
||
on: (event: string, listener: Listener) => {
|
||
const count = listeners.add(event, listener);
|
||
logger.info(
|
||
`Deep link: listener added for event ${event}. Total event listeners: ${count}`,
|
||
);
|
||
},
|
||
off: (event: string, listener: Listener) => {
|
||
const count = listeners.remove(event, listener);
|
||
logger.info(
|
||
`Deep link: listener removed for event ${event}. Total event listeners: ${count}`,
|
||
);
|
||
},
|
||
once: (event: string, listener: Listener) => {
|
||
const onceListener: Listener = (query) => {
|
||
deepLink.off(event, onceListener);
|
||
listener(query);
|
||
};
|
||
deepLink.on(event, onceListener);
|
||
},
|
||
};
|
||
|
||
export function handleCommandLine(commandLine: string[]) {
|
||
const target = commandLine.find((arg) =>
|
||
protocols.some((protocol) => arg.startsWith(protocol + "://")),
|
||
);
|
||
if (!target) return;
|
||
|
||
logger.info(`Deep link: protocol link got: ${target}`);
|
||
|
||
try {
|
||
const url = new URL(target);
|
||
|
||
const action = url.hostname; // 'search'
|
||
logger.info(`Deep link: action found: ${action}`);
|
||
|
||
const query: Query = {};
|
||
|
||
if (action === "search") {
|
||
// Format: spk://search/pkgname
|
||
// url.pathname will be '/pkgname'
|
||
const pkgname = url.pathname.split("/").filter(Boolean)[0];
|
||
if (pkgname) {
|
||
query.pkgname = pkgname;
|
||
logger.info(`Deep link: search query found: ${JSON.stringify(query)}`);
|
||
listeners.emit(action, query);
|
||
} else {
|
||
logger.warn(
|
||
`Deep link: invalid search format, expected /pkgname, got ${url.pathname}`,
|
||
);
|
||
}
|
||
} else if (action === "store") {
|
||
// Format: spk://store/category/pkgname (legacy format)
|
||
// url.pathname will be '/category/pkgname'
|
||
const pathParts = url.pathname.split("/").filter(Boolean);
|
||
// 老协议格式: spk://store/category/pkgname
|
||
// 现在忽略 category,直接使用 pkgname 查找应用
|
||
const pkgname = pathParts.length >= 2 ? pathParts[1] : pathParts[0];
|
||
if (pkgname) {
|
||
query.pkgname = pkgname;
|
||
logger.info(
|
||
`Deep link: store legacy format query found: ${JSON.stringify(query)}`,
|
||
);
|
||
// 使用 search 事件来处理,前端会根据 pkgname 直接打开应用详情
|
||
listeners.emit("search", query);
|
||
} else {
|
||
logger.warn(
|
||
`Deep link: invalid store format, expected /category/pkgname, got ${url.pathname}`,
|
||
);
|
||
}
|
||
} else {
|
||
logger.warn(`Deep link: unknown action ${action}`);
|
||
}
|
||
} catch (error) {
|
||
logger.error(`Deep link: error parsing URL: ${error}`);
|
||
}
|
||
}
|
||
|
||
app.on("second-instance", (_e, commandLine) => {
|
||
handleCommandLine(commandLine);
|
||
});
|