From b6d76262395d658fc160cf152a7f797a5ee73433 Mon Sep 17 00:00:00 2001 From: Naiyuan Qing <145280634+NevilleQingNY@users.noreply.github.com> Date: Tue, 3 Feb 2026 20:13:51 +0800 Subject: [PATCH] fix(store): use string prefix + URLSearchParams for connection URL parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The URL constructor parses non-standard protocols (multica://) differently across engines — Chromium may return empty hostname while Node.js returns "connect". Replace new URL() with simple startsWith check and URLSearchParams to ensure consistent behavior in both web and Electron environments. Co-Authored-By: Claude Opus 4.5 --- packages/store/src/connection.ts | 46 +++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/store/src/connection.ts b/packages/store/src/connection.ts index 38b628a1..42e4f067 100644 --- a/packages/store/src/connection.ts +++ b/packages/store/src/connection.ts @@ -22,10 +22,50 @@ function isConnectionInfo(obj: unknown): obj is ConnectionInfo { ) } +// Parse multica://connect?gateway=...&hub=...&agent=...&token=...&exp=... URL format +// Uses string prefix + URLSearchParams to avoid cross-engine URL hostname differences +function parseConnectionUrl(input: string): ConnectionInfo | null { + const prefix = "multica://connect?" + if (!input.startsWith(prefix)) return null + try { + const params = new URLSearchParams(input.slice(prefix.length)) + const gateway = params.get("gateway") + const hubId = params.get("hub") + const agentId = params.get("agent") + const token = params.get("token") + const exp = params.get("exp") + if (!gateway || !hubId || !agentId || !token || !exp) return null + return { + type: "multica-connect", + gateway, + hubId, + agentId, + token, + expires: Number(exp), + } + } catch { + return null + } +} + +function isExpired(expires: number): boolean { + // Desktop generates expires as millisecond timestamp (Date.now() + seconds * 1000) + return Date.now() > expires +} + export function parseConnectionCode(input: string): ConnectionInfo { const trimmed = input.trim() - // Try JSON first + // Try multica:// URL format first (desktop "Copy Link" output) + const fromUrl = parseConnectionUrl(trimmed) + if (fromUrl) { + if (isExpired(fromUrl.expires)) { + throw new Error("Connection code has expired") + } + return fromUrl + } + + // Try JSON (QR code scan output) let parsed: unknown try { parsed = JSON.parse(trimmed) @@ -42,7 +82,7 @@ export function parseConnectionCode(input: string): ConnectionInfo { throw new Error("Invalid connection code format") } - if (Date.now() > parsed.expires * 1000) { + if (isExpired(parsed.expires)) { throw new Error("Connection code has expired") } @@ -60,7 +100,7 @@ export function loadConnection(): ConnectionInfo | null { try { const info = JSON.parse(raw) if (!isConnectionInfo(info)) return null - if (Date.now() > info.expires * 1000) { + if (isExpired(info.expires)) { localStorage.removeItem(STORAGE_KEY) return null }