- Add electron-updater dependency and implement native desktop integration utilities for dialogs, notifications, auto-update wiring, and window state persistence - Add fusion:// deep-link protocol registration, URL parsing, single-instance handlers, and renderer event forwarding for task/project routes - Add comprehensive Vitest coverage for native integrations and deep-link handling across success, edge, and failure cases - Document native and deep-link API contracts, behavior, and usage details in the desktop package README
100 lines
2.2 KiB
TypeScript
100 lines
2.2 KiB
TypeScript
import { app, BrowserWindow } from "electron";
|
|
|
|
export interface DeepLinkResult {
|
|
type: "task" | "project" | "unknown";
|
|
id: string;
|
|
raw: string;
|
|
}
|
|
|
|
const DEEP_LINK_EVENT = "deep-link";
|
|
const FUSION_SCHEME = "fusion:";
|
|
|
|
export function registerDeepLinkProtocol(): void {
|
|
try {
|
|
const isRegistered = app.setAsDefaultProtocolClient("fusion");
|
|
if (!isRegistered) {
|
|
console.warn("[desktop/deep-link] Failed to register fusion:// protocol");
|
|
return;
|
|
}
|
|
|
|
console.log("[desktop/deep-link] Registered fusion:// protocol handler");
|
|
} catch (error) {
|
|
console.error("[desktop/deep-link] Error while registering fusion:// protocol", error);
|
|
}
|
|
}
|
|
|
|
export function parseDeepLink(rawUrl: string): DeepLinkResult | null {
|
|
if (!rawUrl) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const parsedUrl = new URL(rawUrl);
|
|
|
|
if (parsedUrl.protocol !== FUSION_SCHEME) {
|
|
return null;
|
|
}
|
|
|
|
const type = parsedUrl.hostname;
|
|
if (type !== "task" && type !== "project") {
|
|
return null;
|
|
}
|
|
|
|
const pathSegments = parsedUrl.pathname
|
|
.split("/")
|
|
.filter((segment) => segment.length > 0);
|
|
|
|
const decodedId = pathSegments.length > 0 ? decodeURIComponent(pathSegments[0]) : "";
|
|
|
|
return {
|
|
type,
|
|
id: decodedId,
|
|
raw: rawUrl,
|
|
};
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function handleDeepLink(mainWindow: BrowserWindow, url: string): void {
|
|
const parsed = parseDeepLink(url);
|
|
|
|
if (!parsed || parsed.type === "unknown") {
|
|
console.warn(`[desktop/deep-link] Ignoring unsupported deep link: ${url}`);
|
|
return;
|
|
}
|
|
|
|
if (!mainWindow.isVisible()) {
|
|
mainWindow.show();
|
|
}
|
|
|
|
if (mainWindow.isMinimized()) {
|
|
mainWindow.restore();
|
|
}
|
|
|
|
mainWindow.focus();
|
|
mainWindow.webContents.send(DEEP_LINK_EVENT, parsed);
|
|
}
|
|
|
|
export function setupDeepLinkHandler(mainWindow: BrowserWindow): void {
|
|
const hasLock = app.requestSingleInstanceLock();
|
|
if (!hasLock) {
|
|
app.quit();
|
|
return;
|
|
}
|
|
|
|
app.on("open-url", (event, url) => {
|
|
event.preventDefault();
|
|
handleDeepLink(mainWindow, url);
|
|
});
|
|
|
|
app.on("second-instance", (_event, argv) => {
|
|
const deepLink = argv.find((arg) => arg.startsWith("fusion://"));
|
|
if (!deepLink) {
|
|
return;
|
|
}
|
|
|
|
handleDeepLink(mainWindow, deepLink);
|
|
});
|
|
}
|