{"version":3,"sources":["../src/pushChannel.ts"],"sourcesContent":["// Generic host→sandbox state channel read over the TRANSPORT\n// (SDK_PACKAGING_SPEC §4, expose-transport). The host pushes a `pushType`\n// message whenever the value changes and answers a `requestType` poll with the\n// current value — gated per-frame by the read ACL (site-main channelBridge.ts /\n// channelRouter). Historically the SDK read these values off injected sandbox\n// service objects (`module.evaluation.module.bundler.auth` etc.); this caches\n// the latest value SDK-side instead, so the SDK is self-contained and the\n// bundler services can eventually be retired.\n//\n// Every state helper (formFactor, auth, editorContext, theme, catalog) is a thin\n// wrapper over one of these — the get / onChange / use trio is identical.\nimport { useEffect, useState } from 'react';\nimport { sendMessage as defaultSend, addListener as defaultAddListener } from './hostTransport';\n\nexport interface PushChannel<T> {\n  /** Pollable snapshot of the current value. */\n  get(): T;\n  /** Subscribe; invoked immediately with the current value, then on every change. Returns unsubscribe. */\n  onChange(listener: (value: T) => void): () => void;\n  /** React hook returning the current value, re-rendering on change. */\n  use(): T;\n}\n\n/** Injectable transport — defaults to the real one; overridden in tests. */\nexport interface ChannelTransport {\n  sendMessage: (type: string, data?: Record<string, unknown>) => void;\n  addListener: (type: string, handler: (msg: Record<string, unknown>) => void) => () => void;\n}\n\nexport function createPushChannel<T>(\n  opts: {\n    /** Host→sandbox push message type (e.g. `form-factor`). */\n    pushType: string;\n    /** Poll message type the SDK sends to pull the current value (e.g. `request-form-factor`). */\n    requestType?: string;\n    /** Value assumed before the host answers — also the value when the app may not read the channel. */\n    initial: T;\n    /** Extract + validate the value from a push message; return `undefined` to ignore the message. */\n    parse: (msg: Record<string, unknown>) => T | undefined;\n  },\n  transport: ChannelTransport = { sendMessage: defaultSend, addListener: defaultAddListener },\n): PushChannel<T> {\n  let current = opts.initial;\n  const listeners = new Set<(value: T) => void>();\n  let started = false;\n\n  // Lazily start on first read: register the push listener + send one poll, so a\n  // late-mounting app still gets the current value. A channel the app may not\n  // read is simply never answered → `initial` stands.\n  const start = () => {\n    if (started) return;\n    try {\n      transport.addListener(opts.pushType, (msg) => {\n        const next = opts.parse(msg);\n        if (next !== undefined) {\n          current = next;\n          listeners.forEach((l) => l(current));\n        }\n      });\n    } catch {\n      // No host transport in this realm yet (the resolver throws when neither the injected\n      // bundler bus nor the §4 discovery global is present). Leave `started` false so the\n      // NEXT read retries, and let `initial` stand meanwhile — which is exactly the\n      // documented behaviour for a channel the host never answers. Throwing here instead\n      // would make a plain `getHostTheme()` fatal outside a sandbox, and since R3-307 the\n      // deadline on every host call reads a channel, so one throw would be every call.\n      return;\n    }\n    started = true;\n    if (opts.requestType) {\n      try {\n        transport.sendMessage(opts.requestType);\n      } catch {\n        /* transport not ready — a proactive push will still arrive */\n      }\n    }\n  };\n\n  const get = (): T => {\n    start();\n    return current;\n  };\n\n  const onChange = (listener: (value: T) => void): (() => void) => {\n    start();\n    listeners.add(listener);\n    listener(current);\n    return () => {\n      listeners.delete(listener);\n    };\n  };\n\n  const use = (): T => {\n    const [value, setValue] = useState<T>(get);\n    useEffect(() => onChange(setValue), []);\n    return value;\n  };\n\n  return { get, onChange, use };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA,mBAAoC;AACpC,2BAA8E;AAiBvE,SAAS,kBACd,MAUA,YAA8B,EAAE,aAAa,qBAAAA,aAAa,aAAa,qBAAAC,YAAmB,GAC1E;AAChB,MAAI,UAAU,KAAK;AACnB,QAAM,YAAY,oBAAI,IAAwB;AAC9C,MAAI,UAAU;AAKd,QAAM,QAAQ,MAAM;AAClB,QAAI,QAAS;AACb,QAAI;AACF,gBAAU,YAAY,KAAK,UAAU,CAAC,QAAQ;AAC5C,cAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,YAAI,SAAS,QAAW;AACtB,oBAAU;AACV,oBAAU,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;AAAA,QACrC;AAAA,MACF,CAAC;AAAA,IACH,QAAQ;AAON;AAAA,IACF;AACA,cAAU;AACV,QAAI,KAAK,aAAa;AACpB,UAAI;AACF,kBAAU,YAAY,KAAK,WAAW;AAAA,MACxC,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM,MAAS;AACnB,UAAM;AACN,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,CAAC,aAA+C;AAC/D,UAAM;AACN,cAAU,IAAI,QAAQ;AACtB,aAAS,OAAO;AAChB,WAAO,MAAM;AACX,gBAAU,OAAO,QAAQ;AAAA,IAC3B;AAAA,EACF;AAEA,QAAM,MAAM,MAAS;AACnB,UAAM,CAAC,OAAO,QAAQ,QAAI,uBAAY,GAAG;AACzC,gCAAU,MAAM,SAAS,QAAQ,GAAG,CAAC,CAAC;AACtC,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,KAAK,UAAU,IAAI;AAC9B;","names":["defaultSend","defaultAddListener"]}