/** A profile popup stays in its existing managed view only after native input. */ export interface ProfilePopupDetails { url: string; postBody?: unknown; } export interface ProfilePopupDiagnostic { taskId: string; category: 'xhs_profile' | 'other'; action: 'deny' | 'deny_no_native_input' | 'deny_context' | 'deny_busy' | 'open_same_view' | 'opened_same_view' | 'cancel_stale' | 'open_failed'; } interface PopupPage { getURL(): string; isDestroyed(): boolean; loadURL(url: string): Promise; } interface ProfilePopupOptions { taskId: string; isCurrent(): boolean; allowed(url: string): boolean; diagnose(value: ProfilePopupDiagnostic): void; now?: () => number; defer?: (callback: () => void) => void; } const NATIVE_INPUT_WINDOW_MS = 750; function siteURL(value: string): URL | null { try { const url = new URL(value); return url.protocol === 'https:' && url.hostname === 'www.xiaohongshu.com' && !url.username && !url.password && !url.port ? url : null; } catch { return null; } } function isNote(value: string): boolean { const url = siteURL(value); return Boolean(url && /^\/(?:explore|discovery\/item)\/[a-f0-9]{24}\/?$/i.test(url.pathname)); } function isProfile(value: string): boolean { const url = siteURL(value); return Boolean(url && /^\/user\/profile\/[a-f0-9]{24}\/?$/i.test(url.pathname)); } export function createProfilePopupHandler(page: PopupPage, options: ProfilePopupOptions): { input(type: string): void; open(details: ProfilePopupDetails): { action: 'deny' }; } { const now = options.now ?? (() => performance.now()); const defer = options.defer ?? (callback => { setImmediate(callback); }); let nativeInput: { url: string; at: number } | null = null; let pressed: { url: string; at: number } | null = null; let pending = false; return { input(type): void { // An earlier click cannot authorize a later mouse press or popup. The // host event is independent of scripts in the untrusted page. if (type === 'mouseDown') { nativeInput = null; pressed = null; if (!page.isDestroyed() && isNote(page.getURL())) pressed = { url: page.getURL(), at: now() }; return; } if (type !== 'mouseUp') return; const down = pressed; pressed = null; if (page.isDestroyed()) { nativeInput = null; return; } const url = page.getURL(); const elapsed = down ? now() - down.at : -1; nativeInput = down && down.url === url && isNote(url) && elapsed >= 0 && elapsed <= NATIVE_INPUT_WINDOW_MS ? { url, at: now() } : null; }, open(details): { action: 'deny' } { const category = isProfile(details.url) ? 'xhs_profile' : 'other'; const log = (action: ProfilePopupDiagnostic['action']): void => { // Never spread Electron's details or exceptions into diagnostics. options.diagnose({ taskId: options.taskId, category, action }); }; const clicked = nativeInput; nativeInput = null; // One actual input can route at most one popup. if (category !== 'xhs_profile') { log('deny'); return { action: 'deny' }; } if (pending) { log('deny_busy'); return { action: 'deny' }; } const elapsed = clicked ? now() - clicked.at : -1; if (!clicked || elapsed < 0 || elapsed > NATIVE_INPUT_WINDOW_MS) { log('deny_no_native_input'); return { action: 'deny' }; } if (page.isDestroyed() || !options.isCurrent() || !isNote(page.getURL()) || page.getURL() !== clicked.url || Boolean(details.postBody) || !options.allowed(details.url)) { log('deny_context'); return { action: 'deny' }; } pending = true; const target = details.url; const source = clicked.url; log('open_same_view'); defer(() => { if (page.isDestroyed() || !options.isCurrent() || page.getURL() !== source || !options.allowed(target)) { pending = false; log('cancel_stale'); return; } // Use the actual resolved popup URL unchanged. loadURL adds a normal // history entry in this view; no new window or history reset is used. void page.loadURL(target).then(() => { pending = false; log('opened_same_view'); }).catch(() => { pending = false; log('open_failed'); }); }); // Electron must never create a separate BrowserWindow, including for an // accepted profile. Routing happens only on the next main-process turn. return { action: 'deny' }; }, }; }