import type { PublisherBindRequest, PublisherBindResponse, PublisherLocalPlatformState, PublisherPluginPingResponse, PublisherRegisterInstallationPayload, PublisherRegisterInstallationResult, PublisherPublishArticleRequest, PublisherPublishResponse, } from "@geo/shared-types"; type PluginAction = "ping" | "detectPlatforms" | "registerInstallation" | "bindAccount" | "publishArticle"; type PluginSuccessPayload = { source: "geo-extension"; type: "geo.publisher.response"; requestId: string; success: true; data: T; }; type PluginErrorPayload = { source: "geo-extension"; type: "geo.publisher.response"; requestId: string; success: false; error: string; }; type PluginPayloadMap = { ping: PublisherPluginPingResponse; detectPlatforms: PublisherLocalPlatformState[]; registerInstallation: PublisherRegisterInstallationResult; bindAccount: PublisherBindResponse; publishArticle: PublisherPublishResponse; }; function isPlainObject(value: unknown): value is Record { return typeof value === "object" && value !== null; } function isPingPayload(value: unknown): value is PublisherPluginPingResponse { return isPlainObject(value) && typeof value.installed === "boolean"; } function isDetectPlatformsPayload(value: unknown): value is PublisherLocalPlatformState[] { return Array.isArray(value); } function isRegisterInstallationPayload(value: unknown): value is PublisherRegisterInstallationResult { return isPlainObject(value) && typeof value.success === "boolean" && typeof value.plugin_installation_id === "number"; } function isBindPayload(value: unknown): value is PublisherBindResponse { return isPlainObject(value) && typeof value.success === "boolean" && typeof value.platform_id === "string"; } function isPublishPayload(value: unknown): value is PublisherPublishResponse { return isPlainObject(value) && Array.isArray(value.results); } function isPayloadValid(action: T, value: unknown): value is PluginPayloadMap[T] { switch (action) { case "ping": return isPingPayload(value); case "detectPlatforms": return isDetectPlatformsPayload(value); case "registerInstallation": return isRegisterInstallationPayload(value); case "bindAccount": return isBindPayload(value); case "publishArticle": return isPublishPayload(value); default: return false; } } function nextRequestId(): string { if (typeof crypto !== "undefined" && "randomUUID" in crypto) { return crypto.randomUUID(); } return `geo-${Date.now()}-${Math.random().toString(36).slice(2)}`; } function sleep(ms: number): Promise { return new Promise((resolve) => { window.setTimeout(resolve, ms); }); } async function requestPlugin( action: T, payload?: T extends "bindAccount" ? PublisherBindRequest : T extends "registerInstallation" ? PublisherRegisterInstallationPayload : T extends "publishArticle" ? PublisherPublishArticleRequest : undefined, timeoutMs = 4000, ): Promise { if (typeof window === "undefined") { throw new Error("plugin bridge is only available in browser contexts"); } const requestId = nextRequestId(); return new Promise((resolve, reject) => { const timer = window.setTimeout(() => { cleanup(); reject(new Error("publisher_plugin_timeout")); }, timeoutMs); const handleMessage = (event: MessageEvent | PluginErrorPayload>) => { if (event.source !== window || !event.data || event.data.type !== "geo.publisher.response") { return; } if (event.data.requestId !== requestId || event.data.source !== "geo-extension") { return; } cleanup(); if (event.data.success) { if (!isPayloadValid(action, event.data.data)) { reject(new Error("publisher_plugin_invalid_response")); return; } resolve(event.data.data); return; } reject(new Error(event.data.error || "publisher_plugin_unknown_error")); }; const cleanup = () => { window.clearTimeout(timer); window.removeEventListener("message", handleMessage); }; window.addEventListener("message", handleMessage); window.postMessage( { source: "geo-admin", type: "geo.publisher.request", requestId, action, payload, }, window.location.origin, ); }); } export async function pingPublisherPlugin(): Promise { try { return await requestPlugin("ping", undefined, 1500); } catch { try { await sleep(300); return await requestPlugin("ping", undefined, 2000); } catch { return { installed: false, capabilities: [], }; } } } export async function detectPublisherPlatforms(): Promise { return requestPlugin("detectPlatforms", undefined, 2500); } export async function registerPublisherInstallation( payload: PublisherRegisterInstallationPayload, ): Promise { return requestPlugin("registerInstallation", payload, 5000); } export async function bindPublisherPlatform(payload: PublisherBindRequest): Promise { return requestPlugin("bindAccount", payload, 10000); } export async function publishWithPublisherPlugin( payload: PublisherPublishArticleRequest, ): Promise { return requestPlugin("publishArticle", payload, 25000); }