@burdenoff/website-sdk 2026.1105.3 → 2026.1106.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,5 +48,5 @@ function isAvatarLiveLocaleReviewed(locale) {
48
48
  }
49
49
 
50
50
  export { avatarApiBase, isAvatarLiveLocaleReviewed, isAvatarSite, isAvatarVersion, isRealTurnstileSiteKey, liveConfigComplete, turnstileSiteKeyOf };
51
- //# sourceMappingURL=chunk-JGU6JW4N.mjs.map
52
- //# sourceMappingURL=chunk-JGU6JW4N.mjs.map
51
+ //# sourceMappingURL=chunk-BHEHF3Y5.mjs.map
52
+ //# sourceMappingURL=chunk-BHEHF3Y5.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/explore-avatar-live-config.ts"],"names":[],"mappings":";AA2FO,IAAM,mBAAA,GAAsB,oBAAA;AAEnC,IAAM,IAAA,GAAO,wBAAA;AACb,IAAM,OAAA,GAAU,wBAAA;AAChB,IAAM,QAAA,GAAW,wBAAA;AAKjB,IAAM,aAAA,GAAgB,wBAAA;AAMtB,IAAM,aAAA,GAAgB,0BAAA;AACtB,IAAM,iCAAiB,IAAI,GAAA,CAAI,CAAC,WAAA,EAAa,WAAA,EAAa,OAAO,CAAC,CAAA;AAG3D,SAAS,aAAa,KAAA,EAAwB;AACnD,EAAA,OAAO,IAAA,CAAK,KAAK,KAAK,CAAA;AACxB;AAGO,SAAS,gBAAgB,KAAA,EAAwB;AACtD,EAAA,OAAO,OAAA,CAAQ,KAAK,KAAK,CAAA;AAC3B;AAOO,SAAS,cAAc,UAAA,EAA+C;AAC3E,EAAA,IAAI,CAAC,YAAY,OAAO,IAAA;AACxB,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,IAAI,IAAI,UAAU,CAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,KAAA,GACJ,IAAI,QAAA,KAAa,OAAA,IAAW,eAAe,GAAA,CAAI,GAAA,CAAI,QAAA,CAAS,WAAA,EAAa,CAAA;AAC3E,EAAA,IAAI,GAAA,CAAI,QAAA,KAAa,QAAA,IAAY,CAAC,OAAO,OAAO,IAAA;AAChD,EAAA,OAAO,CAAA,EAAG,GAAA,CAAI,MAAM,CAAA,EAAG,mBAAmB,CAAA,CAAA;AAC5C;AAGO,SAAS,uBAAuB,GAAA,EAAsB;AAC3D,EAAA,OAAO,cAAc,IAAA,CAAK,GAAG,KAAK,CAAC,aAAA,CAAc,KAAK,GAAG,CAAA;AAC3D;AAMO,SAAS,mBAAmB,IAAA,EAAuC;AACxE,EAAA,MAAM,MAAe,IAAA,CAAK,gBAAA;AAC1B,EAAA,MAAM,YAAqB,IAAA,CAAK,gBAAA;AAChC,EAAA,MAAM,gBAAyB,IAAA,CAAK,aAAA;AACpC,EAAA,MAAM,MAAe,IAAA,CAAK,GAAA;AAC1B,EAAA,OACE,OAAO,KAAK,IAAA,KAAS,UAAA,IACrB,OAAO,IAAA,CAAK,IAAA,KAAS,YACrB,YAAA,CAAa,IAAA,CAAK,IAAI,CAAA,IACtB,OAAO,KAAK,eAAA,KAAoB,QAAA,IAChC,gBAAgB,IAAA,CAAK,eAAe,MACnC,GAAA,KAAQ,MAAA,IACP,QAAQ,EAAA,IACP,OAAO,QAAQ,QAAA,IAAY,QAAA,CAAS,KAAK,GAAG,CAAA,CAAA,KAC9C,cAAc,MAAA,IAAa,OAAO,cAAc,SAAA,CAAA,KAChD,aAAA,KAAkB,UAAa,OAAO,aAAA,KAAkB,eACxD,GAAA,KAAQ,MAAA,IAAa,OAAO,GAAA,KAAQ,SAAA,CAAA;AAEzC;AAMO,SAAS,mBACd,IAAA,EACe;AACf,EAAA,MAAM,MAAM,IAAA,CAAK,gBAAA;AACjB,EAAA,IAAI,OAAO,QAAQ,QAAA,IAAY,CAAC,SAAS,IAAA,CAAK,GAAG,GAAG,OAAO,IAAA;AAC3D,EAAA,IAAI,KAAK,gBAAA,KAAqB,KAAA,IAAS,CAAC,sBAAA,CAAuB,GAAG,CAAA,EAAG;AACnE,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,GAAA;AACT;AAMA,IAAM,QAAA,mBAAuC,IAAI,GAAA,CAAmB,CAAC,IAAI,CAAC,CAAA;AAGnE,SAAS,2BAA2B,MAAA,EAAgC;AACzE,EAAA,OAAO,QAAA,CAAS,IAAI,MAAM,CAAA;AAC5B","file":"chunk-JGU6JW4N.mjs","sourcesContent":["/**\n * BOFF-9797. The small part of the live avatar that the panel itself needs, whether or not it is ever opened: what a\n * host's `avatar.live` is, whether the live avatar can run on this page at all, and where its requests go.\n *\n * Everything else (the view, its state machine, the requests, the words, the bot check) is a separate chunk that a\n * site only downloads when its visitor first opens the Avatar view (see `explore-avatar-live.tsx`). That is why this\n * file is separate and kept to a few lines: it is the only part of the live avatar in the package root for a site that\n * does not pass `avatar.live`, and the part of it that decides, without loading anything, whether the chunk is wanted.\n * Types only, plus plain functions: nothing here imports a component, a context or a string table.\n */\nimport type { ExploreLocale } from \"./explore-i18n\";\nimport type { AvatarLiveModule } from \"../avatar-live/types\";\nimport type { UseExploreChatResult } from \"../hooks/use-explore-chat\";\n\n/**\n * BOFF-9797. A host's opt-in to the live avatar on its panel's Avatar tab. `site`, `load` and `manifestVersion` are\n * required; a config that is incomplete, or has an optional field of the wrong shape, leaves the tab as it was (and a\n * warning in the console).\n */\nexport interface ExplorePanelAvatarLive {\n /** The site, as the live avatar service knows it: `healthybowl`. */\n site: string;\n /**\n * Loads the live avatar's room module: `() => import('@burdenoff/website-sdk/avatar-live')`. Called only after the\n * visitor presses Start on the consent card, never on page load or on opening the tab. That entry needs the\n * package's optional peer dependency for media rooms (see the README).\n */\n load: () => Promise<AvatarLiveModule>;\n /**\n * The site's Cloudflare Turnstile site key, for the bot check that runs when the visitor presses Start. Used only when\n * the server says it checks for bots (its availability's `botCheck`, contract v1.2), or says nothing of it (a server\n * older than v1.2, which always checks): such a server's live avatar then needs a key, and without one the tab stays\n * as it was. A server that runs no bot check needs none, and no Cloudflare script is loaded for it, key or not.\n */\n turnstileSiteKey?: string;\n /**\n * Whether `turnstileSiteKey` may be one of Cloudflare's test keys (`1x00000000000000000000AA` and keys of their\n * shape). `websiteAvatarLive` sets it: true on alpha only, false everywhere else, so a production page never runs a\n * test widget. Unset (a config made by hand): any key of a valid shape, as before.\n */\n allowTestSiteKey?: boolean;\n /** The version of the suggested-question list the panel shows (its `examplePrompts`), such as `hb-en-1`. */\n manifestVersion: string;\n /**\n * End a session nobody is attending to (BOFF-9797 stage A). A session keeps playing behind the Chat tab and on a\n * hidden page, so the answer under way is heard to its end; with this set, out of sight it also ends:\n *\n * - **once its answer has played out**, at once: the idle tail after an answer runs only while the Avatar view is in\n * sight (the shared lifecycle rule; see `AvatarLiveView`'s `attend`);\n * - **when the browser holds its sound back** (autoplay refused, or the audio context suspended), after a short grace:\n * the \"Tap to turn on sound\" button that would let it play is out of sight, so nobody hears it.\n *\n * Both end it with reason `user`. Unset or false: such a session plays on to the end the server planned for it, as\n * before.\n */\n endUnattended?: boolean;\n /**\n * Offer a mic in the Avatar tab's own question box (BOFF-9797 Stage C): tap to start, tap to stop, up to thirty\n * seconds, the text into the box for the visitor to correct and Send (nothing is sent by itself). Offered only where\n * the server's availability answer also offers one, and the server's offer decides how: the browser's own speech\n * recognition under `mic: {mode: \"browser\"}` (contract v1.6, the avatar service's `AVATAR_WEB_MIC`; not in Firefox\n * or Brave, and only under consent words that describe it), or the gateway's speech-to-text stream under `stt` (while\n * the gateway's website speech-to-text is on). Only where the box takes a question, after a mic notice of its own on\n * the first tap. The mic's code is a chunk of its own, loaded the first time it is used. Unset or false: no mic, as\n * before; set, a server that offers no mic still shows none.\n */\n mic?: boolean;\n}\n\n/**\n * What the live avatar reads of the panel's conversation: the transcript, whether it is read-only and the token, and\n * `reset` (the chat's own New chat) for its New chat button where the conversation can never be read. Where the server\n * reads typed questions too (contract v1.3), the Avatar tab's own question box asks through the same conversation:\n * `send` (with `surface: \"AVATAR\"`), and `canSend`, `phase`, `activity` and `catalog` to say when it can and what the\n * answer is doing, and `resent` to follow a question asked there that Chat's Retry sent again.\n */\nexport type AvatarConversation = Pick<\n UseExploreChatResult,\n | \"messages\"\n | \"isSharedView\"\n | \"getConversationToken\"\n | \"reset\"\n | \"send\"\n | \"canSend\"\n | \"phase\"\n | \"activity\"\n | \"catalog\"\n | \"resent\"\n>;\n\n/** The web branch's routes, under the gateway's origin. */\nexport const AVATAR_WEB_API_PATH = \"/api/web-avatar/v1\";\n\nconst SITE = /^[a-z][a-z0-9-]{1,31}$/;\nconst VERSION = /^[A-Za-z0-9._-]{1,32}$/;\nconst SITE_KEY = /^[A-Za-z0-9_-]{1,128}$/;\n/**\n * The shape of Cloudflare's dummy site keys: a digit, `x`, a run of zeros and two letters (`1x00000000000000000000AA`,\n * `2x00000000000000000000AB`, `3x00000000000000000000FF`, …). Also catches an all-zero `0x…` placeholder.\n */\nconst TEST_SITE_KEY = /^\\dx0{16,}[A-Za-z]{2}$/;\n/**\n * A real-looking site key. Cloudflare documents no format for real keys; every key it issues starts `0x` (in practice\n * `0x4AAAAAAA…`, about 24 characters), in letters, digits, `_` and `-`. The length is kept loose (18 to 64 in all) so a\n * longer key Cloudflare might issue later still passes, and the charset matches what the panel accepts.\n */\nconst REAL_SITE_KEY = /^0x[A-Za-z0-9_-]{16,62}$/;\nconst LOOPBACK_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n\n/** A site slug the contract accepts (`healthybowl`). */\nexport function isAvatarSite(value: string): boolean {\n return SITE.test(value);\n}\n\n/** A consent or manifest version the contract accepts (`hb-en-1`). */\nexport function isAvatarVersion(value: string): boolean {\n return VERSION.test(value);\n}\n\n/**\n * The web branch's base URL on the SDK's configured gateway: the gateway URL's origin, plus\n * {@link AVATAR_WEB_API_PATH}. Null for anything that is not an `https:` URL (or `http:` on this machine, for local\n * development).\n */\nexport function avatarApiBase(gatewayUrl: string | undefined): string | null {\n if (!gatewayUrl) return null;\n let url: URL;\n try {\n url = new URL(gatewayUrl);\n } catch {\n return null;\n }\n const local =\n url.protocol === \"http:\" && LOOPBACK_HOSTS.has(url.hostname.toLowerCase());\n if (url.protocol !== \"https:\" && !local) return null;\n return `${url.origin}${AVATAR_WEB_API_PATH}`;\n}\n\n/** A real-looking Turnstile site key: `0x…`, and not of the shape of Cloudflare's test keys. */\nexport function isRealTurnstileSiteKey(key: string): boolean {\n return REAL_SITE_KEY.test(key) && !TEST_SITE_KEY.test(key);\n}\n\n/**\n * Whether a host's `avatar.live` has everything the live avatar needs, in the shapes the contract accepts. The site\n * key is optional (an empty one is none), and checked only when it is there.\n */\nexport function liveConfigComplete(live: ExplorePanelAvatarLive): boolean {\n const key: unknown = live.turnstileSiteKey;\n const allowTest: unknown = live.allowTestSiteKey;\n const endUnattended: unknown = live.endUnattended;\n const mic: unknown = live.mic;\n return (\n typeof live.load === \"function\" &&\n typeof live.site === \"string\" &&\n isAvatarSite(live.site) &&\n typeof live.manifestVersion === \"string\" &&\n isAvatarVersion(live.manifestVersion) &&\n (key === undefined ||\n key === \"\" ||\n (typeof key === \"string\" && SITE_KEY.test(key))) &&\n (allowTest === undefined || typeof allowTest === \"boolean\") &&\n (endUnattended === undefined || typeof endUnattended === \"boolean\") &&\n (mic === undefined || typeof mic === \"boolean\")\n );\n}\n\n/**\n * The site key a bot check at Start may use, or null when there is none: no key, one of no valid shape, or one of\n * Cloudflare's test keys where `allowTestSiteKey` is false.\n */\nexport function turnstileSiteKeyOf(\n live: ExplorePanelAvatarLive,\n): string | null {\n const key = live.turnstileSiteKey;\n if (typeof key !== \"string\" || !SITE_KEY.test(key)) return null;\n if (live.allowTestSiteKey === false && !isRealTurnstileSiteKey(key)) {\n return null;\n }\n return key;\n}\n\n/**\n * The locales whose live-avatar strings have been reviewed. The live avatar runs only in these; see\n * `explore-avatar-i18n.ts`. English only, until a named reviewer signs off on another.\n */\nconst REVIEWED: ReadonlySet<ExploreLocale> = new Set<ExploreLocale>([\"en\"]);\n\n/** Whether the live avatar may run in `locale`: its strings have been reviewed. */\nexport function isAvatarLiveLocaleReviewed(locale: ExploreLocale): boolean {\n return REVIEWED.has(locale);\n}\n"]}
1
+ {"version":3,"sources":["../src/components/explore-avatar-live-config.ts"],"names":[],"mappings":";AA+FO,IAAM,mBAAA,GAAsB,oBAAA;AAEnC,IAAM,IAAA,GAAO,wBAAA;AACb,IAAM,OAAA,GAAU,wBAAA;AAChB,IAAM,QAAA,GAAW,wBAAA;AAKjB,IAAM,aAAA,GAAgB,wBAAA;AAMtB,IAAM,aAAA,GAAgB,0BAAA;AACtB,IAAM,iCAAiB,IAAI,GAAA,CAAI,CAAC,WAAA,EAAa,WAAA,EAAa,OAAO,CAAC,CAAA;AAG3D,SAAS,aAAa,KAAA,EAAwB;AACnD,EAAA,OAAO,IAAA,CAAK,KAAK,KAAK,CAAA;AACxB;AAGO,SAAS,gBAAgB,KAAA,EAAwB;AACtD,EAAA,OAAO,OAAA,CAAQ,KAAK,KAAK,CAAA;AAC3B;AAOO,SAAS,cAAc,UAAA,EAA+C;AAC3E,EAAA,IAAI,CAAC,YAAY,OAAO,IAAA;AACxB,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,IAAI,IAAI,UAAU,CAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,KAAA,GACJ,IAAI,QAAA,KAAa,OAAA,IAAW,eAAe,GAAA,CAAI,GAAA,CAAI,QAAA,CAAS,WAAA,EAAa,CAAA;AAC3E,EAAA,IAAI,GAAA,CAAI,QAAA,KAAa,QAAA,IAAY,CAAC,OAAO,OAAO,IAAA;AAChD,EAAA,OAAO,CAAA,EAAG,GAAA,CAAI,MAAM,CAAA,EAAG,mBAAmB,CAAA,CAAA;AAC5C;AAGO,SAAS,uBAAuB,GAAA,EAAsB;AAC3D,EAAA,OAAO,cAAc,IAAA,CAAK,GAAG,KAAK,CAAC,aAAA,CAAc,KAAK,GAAG,CAAA;AAC3D;AAMO,SAAS,mBAAmB,IAAA,EAAuC;AACxE,EAAA,MAAM,MAAe,IAAA,CAAK,gBAAA;AAC1B,EAAA,MAAM,YAAqB,IAAA,CAAK,gBAAA;AAChC,EAAA,MAAM,gBAAyB,IAAA,CAAK,aAAA;AACpC,EAAA,MAAM,MAAe,IAAA,CAAK,GAAA;AAC1B,EAAA,OACE,OAAO,KAAK,IAAA,KAAS,UAAA,IACrB,OAAO,IAAA,CAAK,IAAA,KAAS,YACrB,YAAA,CAAa,IAAA,CAAK,IAAI,CAAA,IACtB,OAAO,KAAK,eAAA,KAAoB,QAAA,IAChC,gBAAgB,IAAA,CAAK,eAAe,MACnC,GAAA,KAAQ,MAAA,IACP,QAAQ,EAAA,IACP,OAAO,QAAQ,QAAA,IAAY,QAAA,CAAS,KAAK,GAAG,CAAA,CAAA,KAC9C,cAAc,MAAA,IAAa,OAAO,cAAc,SAAA,CAAA,KAChD,aAAA,KAAkB,UAAa,OAAO,aAAA,KAAkB,eACxD,GAAA,KAAQ,MAAA,IAAa,OAAO,GAAA,KAAQ,SAAA,CAAA;AAEzC;AAMO,SAAS,mBACd,IAAA,EACe;AACf,EAAA,MAAM,MAAM,IAAA,CAAK,gBAAA;AACjB,EAAA,IAAI,OAAO,QAAQ,QAAA,IAAY,CAAC,SAAS,IAAA,CAAK,GAAG,GAAG,OAAO,IAAA;AAC3D,EAAA,IAAI,KAAK,gBAAA,KAAqB,KAAA,IAAS,CAAC,sBAAA,CAAuB,GAAG,CAAA,EAAG;AACnE,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,GAAA;AACT;AAMA,IAAM,QAAA,mBAAuC,IAAI,GAAA,CAAmB,CAAC,IAAI,CAAC,CAAA;AAGnE,SAAS,2BAA2B,MAAA,EAAgC;AACzE,EAAA,OAAO,QAAA,CAAS,IAAI,MAAM,CAAA;AAC5B","file":"chunk-BHEHF3Y5.mjs","sourcesContent":["/**\n * BOFF-9797. The small part of the live avatar that the panel itself needs, whether or not it is ever opened: what a\n * host's `avatar.live` is, whether the live avatar can run on this page at all, and where its requests go.\n *\n * Everything else (the view, its state machine, the requests, the words, the bot check) is a separate chunk that a\n * site only downloads when its visitor first opens the Avatar view (see `explore-avatar-live.tsx`). That is why this\n * file is separate and kept to a few lines: it is the only part of the live avatar in the package root for a site that\n * does not pass `avatar.live`, and the part of it that decides, without loading anything, whether the chunk is wanted.\n * Types only, plus plain functions: nothing here imports a component, a context or a string table.\n */\nimport type { ExploreLocale } from \"./explore-i18n\";\nimport type { AvatarLiveModule } from \"../avatar-live/types\";\nimport type { UseExploreChatResult } from \"../hooks/use-explore-chat\";\n\n/**\n * BOFF-9797. A host's opt-in to the live avatar on its panel's Avatar tab. `site`, `load` and `manifestVersion` are\n * required; a config that is incomplete, or has an optional field of the wrong shape, leaves the tab as it was (and a\n * warning in the console).\n */\nexport interface ExplorePanelAvatarLive {\n /** The site, as the live avatar service knows it: `healthybowl`. */\n site: string;\n /**\n * Loads the live avatar's room module: `() => import('@burdenoff/website-sdk/avatar-live')`. Called only after the\n * visitor presses Start on the consent card, never on page load or on opening the tab. That entry needs the\n * package's optional peer dependency for media rooms (see the README).\n */\n load: () => Promise<AvatarLiveModule>;\n /**\n * The site's Cloudflare Turnstile site key, for the bot check that runs when the visitor presses Start. Used only when\n * the server says it checks for bots (its availability's `botCheck`, contract v1.2), or says nothing of it (a server\n * older than v1.2, which always checks): such a server's live avatar then needs a key, and without one the tab stays\n * as it was. A server that runs no bot check needs none, and no Cloudflare script is loaded for it, key or not.\n */\n turnstileSiteKey?: string;\n /**\n * Whether `turnstileSiteKey` may be one of Cloudflare's test keys (`1x00000000000000000000AA` and keys of their\n * shape). `websiteAvatarLive` sets it: true on alpha only, false everywhere else, so a production page never runs a\n * test widget. Unset (a config made by hand): any key of a valid shape, as before.\n */\n allowTestSiteKey?: boolean;\n /** The version of the suggested-question list the panel shows (its `examplePrompts`), such as `hb-en-1`. */\n manifestVersion: string;\n /**\n * End a session nobody is attending to (BOFF-9797 stage A). A session keeps playing behind the Chat tab and on a\n * hidden page, so the answer under way is heard to its end; with this set, out of sight it also ends:\n *\n * - **once its answer has played out**, at once: the idle tail after an answer runs only while the Avatar view is in\n * sight (the shared lifecycle rule; see `AvatarLiveView`'s `attend`);\n * - **when the browser holds its sound back** (autoplay refused, or the audio context suspended), after a short grace:\n * the \"Tap to turn on sound\" button that would let it play is out of sight, so nobody hears it.\n *\n * Both end it with reason `user`. Unset or false: such a session plays on to the end the server planned for it, as\n * before.\n */\n endUnattended?: boolean;\n /**\n * Offer a mic in the Avatar tab's own question box (BOFF-9797 Stage C): tap to start, tap to stop, up to thirty\n * seconds, the text into the box for the visitor to correct and Send (nothing is sent by itself). Offered only where\n * the server's availability answer also offers one, and the server's offer decides how: the browser's own speech\n * recognition under `mic: {mode: \"browser\"}` (contract v1.6, the avatar service's `AVATAR_WEB_MIC`; not in Firefox\n * or Brave, and only under consent words that describe it), or the gateway's speech-to-text stream under `stt` (while\n * the gateway's website speech-to-text is on). Only where the box takes a question, after a mic notice of its own on\n * the first tap. The mic's code is a chunk of its own, loaded the first time it is used. Unset or false: no mic, as\n * before; set, a server that offers no mic still shows none. Where the server's `mic` also carries a pause\n * (`pauseMs`, contract v1.7) under a version whose entry declares no notice (Stage C3), the mic is a voice\n * conversation instead (Stage C2: Talk listening inside its own tap, with no mic notice and nothing else shown first,\n * the question sent by itself after the pause, the mic listening again after each answer; its code is made ready as\n * soon as Talk is offered); no other setting.\n */\n mic?: boolean;\n}\n\n/**\n * What the live avatar reads of the panel's conversation: the transcript, whether it is read-only and the token, and\n * `reset` (the chat's own New chat) for its New chat button where the conversation can never be read. Where the server\n * reads typed questions too (contract v1.3), the Avatar tab's own question box asks through the same conversation:\n * `send` (with `surface: \"AVATAR\"`), and `canSend`, `phase`, `activity` and `catalog` to say when it can and what the\n * answer is doing, and `resent` to follow a question asked there that Chat's Retry sent again.\n */\nexport type AvatarConversation = Pick<\n UseExploreChatResult,\n | \"messages\"\n | \"isSharedView\"\n | \"getConversationToken\"\n | \"reset\"\n | \"send\"\n | \"canSend\"\n | \"phase\"\n | \"activity\"\n | \"catalog\"\n | \"resent\"\n>;\n\n/** The web branch's routes, under the gateway's origin. */\nexport const AVATAR_WEB_API_PATH = \"/api/web-avatar/v1\";\n\nconst SITE = /^[a-z][a-z0-9-]{1,31}$/;\nconst VERSION = /^[A-Za-z0-9._-]{1,32}$/;\nconst SITE_KEY = /^[A-Za-z0-9_-]{1,128}$/;\n/**\n * The shape of Cloudflare's dummy site keys: a digit, `x`, a run of zeros and two letters (`1x00000000000000000000AA`,\n * `2x00000000000000000000AB`, `3x00000000000000000000FF`, …). Also catches an all-zero `0x…` placeholder.\n */\nconst TEST_SITE_KEY = /^\\dx0{16,}[A-Za-z]{2}$/;\n/**\n * A real-looking site key. Cloudflare documents no format for real keys; every key it issues starts `0x` (in practice\n * `0x4AAAAAAA…`, about 24 characters), in letters, digits, `_` and `-`. The length is kept loose (18 to 64 in all) so a\n * longer key Cloudflare might issue later still passes, and the charset matches what the panel accepts.\n */\nconst REAL_SITE_KEY = /^0x[A-Za-z0-9_-]{16,62}$/;\nconst LOOPBACK_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n\n/** A site slug the contract accepts (`healthybowl`). */\nexport function isAvatarSite(value: string): boolean {\n return SITE.test(value);\n}\n\n/** A consent or manifest version the contract accepts (`hb-en-1`). */\nexport function isAvatarVersion(value: string): boolean {\n return VERSION.test(value);\n}\n\n/**\n * The web branch's base URL on the SDK's configured gateway: the gateway URL's origin, plus\n * {@link AVATAR_WEB_API_PATH}. Null for anything that is not an `https:` URL (or `http:` on this machine, for local\n * development).\n */\nexport function avatarApiBase(gatewayUrl: string | undefined): string | null {\n if (!gatewayUrl) return null;\n let url: URL;\n try {\n url = new URL(gatewayUrl);\n } catch {\n return null;\n }\n const local =\n url.protocol === \"http:\" && LOOPBACK_HOSTS.has(url.hostname.toLowerCase());\n if (url.protocol !== \"https:\" && !local) return null;\n return `${url.origin}${AVATAR_WEB_API_PATH}`;\n}\n\n/** A real-looking Turnstile site key: `0x…`, and not of the shape of Cloudflare's test keys. */\nexport function isRealTurnstileSiteKey(key: string): boolean {\n return REAL_SITE_KEY.test(key) && !TEST_SITE_KEY.test(key);\n}\n\n/**\n * Whether a host's `avatar.live` has everything the live avatar needs, in the shapes the contract accepts. The site\n * key is optional (an empty one is none), and checked only when it is there.\n */\nexport function liveConfigComplete(live: ExplorePanelAvatarLive): boolean {\n const key: unknown = live.turnstileSiteKey;\n const allowTest: unknown = live.allowTestSiteKey;\n const endUnattended: unknown = live.endUnattended;\n const mic: unknown = live.mic;\n return (\n typeof live.load === \"function\" &&\n typeof live.site === \"string\" &&\n isAvatarSite(live.site) &&\n typeof live.manifestVersion === \"string\" &&\n isAvatarVersion(live.manifestVersion) &&\n (key === undefined ||\n key === \"\" ||\n (typeof key === \"string\" && SITE_KEY.test(key))) &&\n (allowTest === undefined || typeof allowTest === \"boolean\") &&\n (endUnattended === undefined || typeof endUnattended === \"boolean\") &&\n (mic === undefined || typeof mic === \"boolean\")\n );\n}\n\n/**\n * The site key a bot check at Start may use, or null when there is none: no key, one of no valid shape, or one of\n * Cloudflare's test keys where `allowTestSiteKey` is false.\n */\nexport function turnstileSiteKeyOf(\n live: ExplorePanelAvatarLive,\n): string | null {\n const key = live.turnstileSiteKey;\n if (typeof key !== \"string\" || !SITE_KEY.test(key)) return null;\n if (live.allowTestSiteKey === false && !isRealTurnstileSiteKey(key)) {\n return null;\n }\n return key;\n}\n\n/**\n * The locales whose live-avatar strings have been reviewed. The live avatar runs only in these; see\n * `explore-avatar-i18n.ts`. English only, until a named reviewer signs off on another.\n */\nconst REVIEWED: ReadonlySet<ExploreLocale> = new Set<ExploreLocale>([\"en\"]);\n\n/** Whether the live avatar may run in `locale`: its strings have been reviewed. */\nexport function isAvatarLiveLocaleReviewed(locale: ExploreLocale): boolean {\n return REVIEWED.has(locale);\n}\n"]}
@@ -56,5 +56,5 @@ exports.isAvatarVersion = isAvatarVersion;
56
56
  exports.isRealTurnstileSiteKey = isRealTurnstileSiteKey;
57
57
  exports.liveConfigComplete = liveConfigComplete;
58
58
  exports.turnstileSiteKeyOf = turnstileSiteKeyOf;
59
- //# sourceMappingURL=chunk-3AIAJ2L4.js.map
60
- //# sourceMappingURL=chunk-3AIAJ2L4.js.map
59
+ //# sourceMappingURL=chunk-Z2UP22GV.js.map
60
+ //# sourceMappingURL=chunk-Z2UP22GV.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/explore-avatar-live-config.ts"],"names":[],"mappings":";;;AA2FO,IAAM,mBAAA,GAAsB,oBAAA;AAEnC,IAAM,IAAA,GAAO,wBAAA;AACb,IAAM,OAAA,GAAU,wBAAA;AAChB,IAAM,QAAA,GAAW,wBAAA;AAKjB,IAAM,aAAA,GAAgB,wBAAA;AAMtB,IAAM,aAAA,GAAgB,0BAAA;AACtB,IAAM,iCAAiB,IAAI,GAAA,CAAI,CAAC,WAAA,EAAa,WAAA,EAAa,OAAO,CAAC,CAAA;AAG3D,SAAS,aAAa,KAAA,EAAwB;AACnD,EAAA,OAAO,IAAA,CAAK,KAAK,KAAK,CAAA;AACxB;AAGO,SAAS,gBAAgB,KAAA,EAAwB;AACtD,EAAA,OAAO,OAAA,CAAQ,KAAK,KAAK,CAAA;AAC3B;AAOO,SAAS,cAAc,UAAA,EAA+C;AAC3E,EAAA,IAAI,CAAC,YAAY,OAAO,IAAA;AACxB,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,IAAI,IAAI,UAAU,CAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,KAAA,GACJ,IAAI,QAAA,KAAa,OAAA,IAAW,eAAe,GAAA,CAAI,GAAA,CAAI,QAAA,CAAS,WAAA,EAAa,CAAA;AAC3E,EAAA,IAAI,GAAA,CAAI,QAAA,KAAa,QAAA,IAAY,CAAC,OAAO,OAAO,IAAA;AAChD,EAAA,OAAO,CAAA,EAAG,GAAA,CAAI,MAAM,CAAA,EAAG,mBAAmB,CAAA,CAAA;AAC5C;AAGO,SAAS,uBAAuB,GAAA,EAAsB;AAC3D,EAAA,OAAO,cAAc,IAAA,CAAK,GAAG,KAAK,CAAC,aAAA,CAAc,KAAK,GAAG,CAAA;AAC3D;AAMO,SAAS,mBAAmB,IAAA,EAAuC;AACxE,EAAA,MAAM,MAAe,IAAA,CAAK,gBAAA;AAC1B,EAAA,MAAM,YAAqB,IAAA,CAAK,gBAAA;AAChC,EAAA,MAAM,gBAAyB,IAAA,CAAK,aAAA;AACpC,EAAA,MAAM,MAAe,IAAA,CAAK,GAAA;AAC1B,EAAA,OACE,OAAO,KAAK,IAAA,KAAS,UAAA,IACrB,OAAO,IAAA,CAAK,IAAA,KAAS,YACrB,YAAA,CAAa,IAAA,CAAK,IAAI,CAAA,IACtB,OAAO,KAAK,eAAA,KAAoB,QAAA,IAChC,gBAAgB,IAAA,CAAK,eAAe,MACnC,GAAA,KAAQ,MAAA,IACP,QAAQ,EAAA,IACP,OAAO,QAAQ,QAAA,IAAY,QAAA,CAAS,KAAK,GAAG,CAAA,CAAA,KAC9C,cAAc,MAAA,IAAa,OAAO,cAAc,SAAA,CAAA,KAChD,aAAA,KAAkB,UAAa,OAAO,aAAA,KAAkB,eACxD,GAAA,KAAQ,MAAA,IAAa,OAAO,GAAA,KAAQ,SAAA,CAAA;AAEzC;AAMO,SAAS,mBACd,IAAA,EACe;AACf,EAAA,MAAM,MAAM,IAAA,CAAK,gBAAA;AACjB,EAAA,IAAI,OAAO,QAAQ,QAAA,IAAY,CAAC,SAAS,IAAA,CAAK,GAAG,GAAG,OAAO,IAAA;AAC3D,EAAA,IAAI,KAAK,gBAAA,KAAqB,KAAA,IAAS,CAAC,sBAAA,CAAuB,GAAG,CAAA,EAAG;AACnE,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,GAAA;AACT;AAMA,IAAM,QAAA,mBAAuC,IAAI,GAAA,CAAmB,CAAC,IAAI,CAAC,CAAA;AAGnE,SAAS,2BAA2B,MAAA,EAAgC;AACzE,EAAA,OAAO,QAAA,CAAS,IAAI,MAAM,CAAA;AAC5B","file":"chunk-3AIAJ2L4.js","sourcesContent":["/**\n * BOFF-9797. The small part of the live avatar that the panel itself needs, whether or not it is ever opened: what a\n * host's `avatar.live` is, whether the live avatar can run on this page at all, and where its requests go.\n *\n * Everything else (the view, its state machine, the requests, the words, the bot check) is a separate chunk that a\n * site only downloads when its visitor first opens the Avatar view (see `explore-avatar-live.tsx`). That is why this\n * file is separate and kept to a few lines: it is the only part of the live avatar in the package root for a site that\n * does not pass `avatar.live`, and the part of it that decides, without loading anything, whether the chunk is wanted.\n * Types only, plus plain functions: nothing here imports a component, a context or a string table.\n */\nimport type { ExploreLocale } from \"./explore-i18n\";\nimport type { AvatarLiveModule } from \"../avatar-live/types\";\nimport type { UseExploreChatResult } from \"../hooks/use-explore-chat\";\n\n/**\n * BOFF-9797. A host's opt-in to the live avatar on its panel's Avatar tab. `site`, `load` and `manifestVersion` are\n * required; a config that is incomplete, or has an optional field of the wrong shape, leaves the tab as it was (and a\n * warning in the console).\n */\nexport interface ExplorePanelAvatarLive {\n /** The site, as the live avatar service knows it: `healthybowl`. */\n site: string;\n /**\n * Loads the live avatar's room module: `() => import('@burdenoff/website-sdk/avatar-live')`. Called only after the\n * visitor presses Start on the consent card, never on page load or on opening the tab. That entry needs the\n * package's optional peer dependency for media rooms (see the README).\n */\n load: () => Promise<AvatarLiveModule>;\n /**\n * The site's Cloudflare Turnstile site key, for the bot check that runs when the visitor presses Start. Used only when\n * the server says it checks for bots (its availability's `botCheck`, contract v1.2), or says nothing of it (a server\n * older than v1.2, which always checks): such a server's live avatar then needs a key, and without one the tab stays\n * as it was. A server that runs no bot check needs none, and no Cloudflare script is loaded for it, key or not.\n */\n turnstileSiteKey?: string;\n /**\n * Whether `turnstileSiteKey` may be one of Cloudflare's test keys (`1x00000000000000000000AA` and keys of their\n * shape). `websiteAvatarLive` sets it: true on alpha only, false everywhere else, so a production page never runs a\n * test widget. Unset (a config made by hand): any key of a valid shape, as before.\n */\n allowTestSiteKey?: boolean;\n /** The version of the suggested-question list the panel shows (its `examplePrompts`), such as `hb-en-1`. */\n manifestVersion: string;\n /**\n * End a session nobody is attending to (BOFF-9797 stage A). A session keeps playing behind the Chat tab and on a\n * hidden page, so the answer under way is heard to its end; with this set, out of sight it also ends:\n *\n * - **once its answer has played out**, at once: the idle tail after an answer runs only while the Avatar view is in\n * sight (the shared lifecycle rule; see `AvatarLiveView`'s `attend`);\n * - **when the browser holds its sound back** (autoplay refused, or the audio context suspended), after a short grace:\n * the \"Tap to turn on sound\" button that would let it play is out of sight, so nobody hears it.\n *\n * Both end it with reason `user`. Unset or false: such a session plays on to the end the server planned for it, as\n * before.\n */\n endUnattended?: boolean;\n /**\n * Offer a mic in the Avatar tab's own question box (BOFF-9797 Stage C): tap to start, tap to stop, up to thirty\n * seconds, the text into the box for the visitor to correct and Send (nothing is sent by itself). Offered only where\n * the server's availability answer also offers one, and the server's offer decides how: the browser's own speech\n * recognition under `mic: {mode: \"browser\"}` (contract v1.6, the avatar service's `AVATAR_WEB_MIC`; not in Firefox\n * or Brave, and only under consent words that describe it), or the gateway's speech-to-text stream under `stt` (while\n * the gateway's website speech-to-text is on). Only where the box takes a question, after a mic notice of its own on\n * the first tap. The mic's code is a chunk of its own, loaded the first time it is used. Unset or false: no mic, as\n * before; set, a server that offers no mic still shows none.\n */\n mic?: boolean;\n}\n\n/**\n * What the live avatar reads of the panel's conversation: the transcript, whether it is read-only and the token, and\n * `reset` (the chat's own New chat) for its New chat button where the conversation can never be read. Where the server\n * reads typed questions too (contract v1.3), the Avatar tab's own question box asks through the same conversation:\n * `send` (with `surface: \"AVATAR\"`), and `canSend`, `phase`, `activity` and `catalog` to say when it can and what the\n * answer is doing, and `resent` to follow a question asked there that Chat's Retry sent again.\n */\nexport type AvatarConversation = Pick<\n UseExploreChatResult,\n | \"messages\"\n | \"isSharedView\"\n | \"getConversationToken\"\n | \"reset\"\n | \"send\"\n | \"canSend\"\n | \"phase\"\n | \"activity\"\n | \"catalog\"\n | \"resent\"\n>;\n\n/** The web branch's routes, under the gateway's origin. */\nexport const AVATAR_WEB_API_PATH = \"/api/web-avatar/v1\";\n\nconst SITE = /^[a-z][a-z0-9-]{1,31}$/;\nconst VERSION = /^[A-Za-z0-9._-]{1,32}$/;\nconst SITE_KEY = /^[A-Za-z0-9_-]{1,128}$/;\n/**\n * The shape of Cloudflare's dummy site keys: a digit, `x`, a run of zeros and two letters (`1x00000000000000000000AA`,\n * `2x00000000000000000000AB`, `3x00000000000000000000FF`, …). Also catches an all-zero `0x…` placeholder.\n */\nconst TEST_SITE_KEY = /^\\dx0{16,}[A-Za-z]{2}$/;\n/**\n * A real-looking site key. Cloudflare documents no format for real keys; every key it issues starts `0x` (in practice\n * `0x4AAAAAAA…`, about 24 characters), in letters, digits, `_` and `-`. The length is kept loose (18 to 64 in all) so a\n * longer key Cloudflare might issue later still passes, and the charset matches what the panel accepts.\n */\nconst REAL_SITE_KEY = /^0x[A-Za-z0-9_-]{16,62}$/;\nconst LOOPBACK_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n\n/** A site slug the contract accepts (`healthybowl`). */\nexport function isAvatarSite(value: string): boolean {\n return SITE.test(value);\n}\n\n/** A consent or manifest version the contract accepts (`hb-en-1`). */\nexport function isAvatarVersion(value: string): boolean {\n return VERSION.test(value);\n}\n\n/**\n * The web branch's base URL on the SDK's configured gateway: the gateway URL's origin, plus\n * {@link AVATAR_WEB_API_PATH}. Null for anything that is not an `https:` URL (or `http:` on this machine, for local\n * development).\n */\nexport function avatarApiBase(gatewayUrl: string | undefined): string | null {\n if (!gatewayUrl) return null;\n let url: URL;\n try {\n url = new URL(gatewayUrl);\n } catch {\n return null;\n }\n const local =\n url.protocol === \"http:\" && LOOPBACK_HOSTS.has(url.hostname.toLowerCase());\n if (url.protocol !== \"https:\" && !local) return null;\n return `${url.origin}${AVATAR_WEB_API_PATH}`;\n}\n\n/** A real-looking Turnstile site key: `0x…`, and not of the shape of Cloudflare's test keys. */\nexport function isRealTurnstileSiteKey(key: string): boolean {\n return REAL_SITE_KEY.test(key) && !TEST_SITE_KEY.test(key);\n}\n\n/**\n * Whether a host's `avatar.live` has everything the live avatar needs, in the shapes the contract accepts. The site\n * key is optional (an empty one is none), and checked only when it is there.\n */\nexport function liveConfigComplete(live: ExplorePanelAvatarLive): boolean {\n const key: unknown = live.turnstileSiteKey;\n const allowTest: unknown = live.allowTestSiteKey;\n const endUnattended: unknown = live.endUnattended;\n const mic: unknown = live.mic;\n return (\n typeof live.load === \"function\" &&\n typeof live.site === \"string\" &&\n isAvatarSite(live.site) &&\n typeof live.manifestVersion === \"string\" &&\n isAvatarVersion(live.manifestVersion) &&\n (key === undefined ||\n key === \"\" ||\n (typeof key === \"string\" && SITE_KEY.test(key))) &&\n (allowTest === undefined || typeof allowTest === \"boolean\") &&\n (endUnattended === undefined || typeof endUnattended === \"boolean\") &&\n (mic === undefined || typeof mic === \"boolean\")\n );\n}\n\n/**\n * The site key a bot check at Start may use, or null when there is none: no key, one of no valid shape, or one of\n * Cloudflare's test keys where `allowTestSiteKey` is false.\n */\nexport function turnstileSiteKeyOf(\n live: ExplorePanelAvatarLive,\n): string | null {\n const key = live.turnstileSiteKey;\n if (typeof key !== \"string\" || !SITE_KEY.test(key)) return null;\n if (live.allowTestSiteKey === false && !isRealTurnstileSiteKey(key)) {\n return null;\n }\n return key;\n}\n\n/**\n * The locales whose live-avatar strings have been reviewed. The live avatar runs only in these; see\n * `explore-avatar-i18n.ts`. English only, until a named reviewer signs off on another.\n */\nconst REVIEWED: ReadonlySet<ExploreLocale> = new Set<ExploreLocale>([\"en\"]);\n\n/** Whether the live avatar may run in `locale`: its strings have been reviewed. */\nexport function isAvatarLiveLocaleReviewed(locale: ExploreLocale): boolean {\n return REVIEWED.has(locale);\n}\n"]}
1
+ {"version":3,"sources":["../src/components/explore-avatar-live-config.ts"],"names":[],"mappings":";;;AA+FO,IAAM,mBAAA,GAAsB,oBAAA;AAEnC,IAAM,IAAA,GAAO,wBAAA;AACb,IAAM,OAAA,GAAU,wBAAA;AAChB,IAAM,QAAA,GAAW,wBAAA;AAKjB,IAAM,aAAA,GAAgB,wBAAA;AAMtB,IAAM,aAAA,GAAgB,0BAAA;AACtB,IAAM,iCAAiB,IAAI,GAAA,CAAI,CAAC,WAAA,EAAa,WAAA,EAAa,OAAO,CAAC,CAAA;AAG3D,SAAS,aAAa,KAAA,EAAwB;AACnD,EAAA,OAAO,IAAA,CAAK,KAAK,KAAK,CAAA;AACxB;AAGO,SAAS,gBAAgB,KAAA,EAAwB;AACtD,EAAA,OAAO,OAAA,CAAQ,KAAK,KAAK,CAAA;AAC3B;AAOO,SAAS,cAAc,UAAA,EAA+C;AAC3E,EAAA,IAAI,CAAC,YAAY,OAAO,IAAA;AACxB,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,IAAI,IAAI,UAAU,CAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,KAAA,GACJ,IAAI,QAAA,KAAa,OAAA,IAAW,eAAe,GAAA,CAAI,GAAA,CAAI,QAAA,CAAS,WAAA,EAAa,CAAA;AAC3E,EAAA,IAAI,GAAA,CAAI,QAAA,KAAa,QAAA,IAAY,CAAC,OAAO,OAAO,IAAA;AAChD,EAAA,OAAO,CAAA,EAAG,GAAA,CAAI,MAAM,CAAA,EAAG,mBAAmB,CAAA,CAAA;AAC5C;AAGO,SAAS,uBAAuB,GAAA,EAAsB;AAC3D,EAAA,OAAO,cAAc,IAAA,CAAK,GAAG,KAAK,CAAC,aAAA,CAAc,KAAK,GAAG,CAAA;AAC3D;AAMO,SAAS,mBAAmB,IAAA,EAAuC;AACxE,EAAA,MAAM,MAAe,IAAA,CAAK,gBAAA;AAC1B,EAAA,MAAM,YAAqB,IAAA,CAAK,gBAAA;AAChC,EAAA,MAAM,gBAAyB,IAAA,CAAK,aAAA;AACpC,EAAA,MAAM,MAAe,IAAA,CAAK,GAAA;AAC1B,EAAA,OACE,OAAO,KAAK,IAAA,KAAS,UAAA,IACrB,OAAO,IAAA,CAAK,IAAA,KAAS,YACrB,YAAA,CAAa,IAAA,CAAK,IAAI,CAAA,IACtB,OAAO,KAAK,eAAA,KAAoB,QAAA,IAChC,gBAAgB,IAAA,CAAK,eAAe,MACnC,GAAA,KAAQ,MAAA,IACP,QAAQ,EAAA,IACP,OAAO,QAAQ,QAAA,IAAY,QAAA,CAAS,KAAK,GAAG,CAAA,CAAA,KAC9C,cAAc,MAAA,IAAa,OAAO,cAAc,SAAA,CAAA,KAChD,aAAA,KAAkB,UAAa,OAAO,aAAA,KAAkB,eACxD,GAAA,KAAQ,MAAA,IAAa,OAAO,GAAA,KAAQ,SAAA,CAAA;AAEzC;AAMO,SAAS,mBACd,IAAA,EACe;AACf,EAAA,MAAM,MAAM,IAAA,CAAK,gBAAA;AACjB,EAAA,IAAI,OAAO,QAAQ,QAAA,IAAY,CAAC,SAAS,IAAA,CAAK,GAAG,GAAG,OAAO,IAAA;AAC3D,EAAA,IAAI,KAAK,gBAAA,KAAqB,KAAA,IAAS,CAAC,sBAAA,CAAuB,GAAG,CAAA,EAAG;AACnE,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,GAAA;AACT;AAMA,IAAM,QAAA,mBAAuC,IAAI,GAAA,CAAmB,CAAC,IAAI,CAAC,CAAA;AAGnE,SAAS,2BAA2B,MAAA,EAAgC;AACzE,EAAA,OAAO,QAAA,CAAS,IAAI,MAAM,CAAA;AAC5B","file":"chunk-Z2UP22GV.js","sourcesContent":["/**\n * BOFF-9797. The small part of the live avatar that the panel itself needs, whether or not it is ever opened: what a\n * host's `avatar.live` is, whether the live avatar can run on this page at all, and where its requests go.\n *\n * Everything else (the view, its state machine, the requests, the words, the bot check) is a separate chunk that a\n * site only downloads when its visitor first opens the Avatar view (see `explore-avatar-live.tsx`). That is why this\n * file is separate and kept to a few lines: it is the only part of the live avatar in the package root for a site that\n * does not pass `avatar.live`, and the part of it that decides, without loading anything, whether the chunk is wanted.\n * Types only, plus plain functions: nothing here imports a component, a context or a string table.\n */\nimport type { ExploreLocale } from \"./explore-i18n\";\nimport type { AvatarLiveModule } from \"../avatar-live/types\";\nimport type { UseExploreChatResult } from \"../hooks/use-explore-chat\";\n\n/**\n * BOFF-9797. A host's opt-in to the live avatar on its panel's Avatar tab. `site`, `load` and `manifestVersion` are\n * required; a config that is incomplete, or has an optional field of the wrong shape, leaves the tab as it was (and a\n * warning in the console).\n */\nexport interface ExplorePanelAvatarLive {\n /** The site, as the live avatar service knows it: `healthybowl`. */\n site: string;\n /**\n * Loads the live avatar's room module: `() => import('@burdenoff/website-sdk/avatar-live')`. Called only after the\n * visitor presses Start on the consent card, never on page load or on opening the tab. That entry needs the\n * package's optional peer dependency for media rooms (see the README).\n */\n load: () => Promise<AvatarLiveModule>;\n /**\n * The site's Cloudflare Turnstile site key, for the bot check that runs when the visitor presses Start. Used only when\n * the server says it checks for bots (its availability's `botCheck`, contract v1.2), or says nothing of it (a server\n * older than v1.2, which always checks): such a server's live avatar then needs a key, and without one the tab stays\n * as it was. A server that runs no bot check needs none, and no Cloudflare script is loaded for it, key or not.\n */\n turnstileSiteKey?: string;\n /**\n * Whether `turnstileSiteKey` may be one of Cloudflare's test keys (`1x00000000000000000000AA` and keys of their\n * shape). `websiteAvatarLive` sets it: true on alpha only, false everywhere else, so a production page never runs a\n * test widget. Unset (a config made by hand): any key of a valid shape, as before.\n */\n allowTestSiteKey?: boolean;\n /** The version of the suggested-question list the panel shows (its `examplePrompts`), such as `hb-en-1`. */\n manifestVersion: string;\n /**\n * End a session nobody is attending to (BOFF-9797 stage A). A session keeps playing behind the Chat tab and on a\n * hidden page, so the answer under way is heard to its end; with this set, out of sight it also ends:\n *\n * - **once its answer has played out**, at once: the idle tail after an answer runs only while the Avatar view is in\n * sight (the shared lifecycle rule; see `AvatarLiveView`'s `attend`);\n * - **when the browser holds its sound back** (autoplay refused, or the audio context suspended), after a short grace:\n * the \"Tap to turn on sound\" button that would let it play is out of sight, so nobody hears it.\n *\n * Both end it with reason `user`. Unset or false: such a session plays on to the end the server planned for it, as\n * before.\n */\n endUnattended?: boolean;\n /**\n * Offer a mic in the Avatar tab's own question box (BOFF-9797 Stage C): tap to start, tap to stop, up to thirty\n * seconds, the text into the box for the visitor to correct and Send (nothing is sent by itself). Offered only where\n * the server's availability answer also offers one, and the server's offer decides how: the browser's own speech\n * recognition under `mic: {mode: \"browser\"}` (contract v1.6, the avatar service's `AVATAR_WEB_MIC`; not in Firefox\n * or Brave, and only under consent words that describe it), or the gateway's speech-to-text stream under `stt` (while\n * the gateway's website speech-to-text is on). Only where the box takes a question, after a mic notice of its own on\n * the first tap. The mic's code is a chunk of its own, loaded the first time it is used. Unset or false: no mic, as\n * before; set, a server that offers no mic still shows none. Where the server's `mic` also carries a pause\n * (`pauseMs`, contract v1.7) under a version whose entry declares no notice (Stage C3), the mic is a voice\n * conversation instead (Stage C2: Talk listening inside its own tap, with no mic notice and nothing else shown first,\n * the question sent by itself after the pause, the mic listening again after each answer; its code is made ready as\n * soon as Talk is offered); no other setting.\n */\n mic?: boolean;\n}\n\n/**\n * What the live avatar reads of the panel's conversation: the transcript, whether it is read-only and the token, and\n * `reset` (the chat's own New chat) for its New chat button where the conversation can never be read. Where the server\n * reads typed questions too (contract v1.3), the Avatar tab's own question box asks through the same conversation:\n * `send` (with `surface: \"AVATAR\"`), and `canSend`, `phase`, `activity` and `catalog` to say when it can and what the\n * answer is doing, and `resent` to follow a question asked there that Chat's Retry sent again.\n */\nexport type AvatarConversation = Pick<\n UseExploreChatResult,\n | \"messages\"\n | \"isSharedView\"\n | \"getConversationToken\"\n | \"reset\"\n | \"send\"\n | \"canSend\"\n | \"phase\"\n | \"activity\"\n | \"catalog\"\n | \"resent\"\n>;\n\n/** The web branch's routes, under the gateway's origin. */\nexport const AVATAR_WEB_API_PATH = \"/api/web-avatar/v1\";\n\nconst SITE = /^[a-z][a-z0-9-]{1,31}$/;\nconst VERSION = /^[A-Za-z0-9._-]{1,32}$/;\nconst SITE_KEY = /^[A-Za-z0-9_-]{1,128}$/;\n/**\n * The shape of Cloudflare's dummy site keys: a digit, `x`, a run of zeros and two letters (`1x00000000000000000000AA`,\n * `2x00000000000000000000AB`, `3x00000000000000000000FF`, …). Also catches an all-zero `0x…` placeholder.\n */\nconst TEST_SITE_KEY = /^\\dx0{16,}[A-Za-z]{2}$/;\n/**\n * A real-looking site key. Cloudflare documents no format for real keys; every key it issues starts `0x` (in practice\n * `0x4AAAAAAA…`, about 24 characters), in letters, digits, `_` and `-`. The length is kept loose (18 to 64 in all) so a\n * longer key Cloudflare might issue later still passes, and the charset matches what the panel accepts.\n */\nconst REAL_SITE_KEY = /^0x[A-Za-z0-9_-]{16,62}$/;\nconst LOOPBACK_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n\n/** A site slug the contract accepts (`healthybowl`). */\nexport function isAvatarSite(value: string): boolean {\n return SITE.test(value);\n}\n\n/** A consent or manifest version the contract accepts (`hb-en-1`). */\nexport function isAvatarVersion(value: string): boolean {\n return VERSION.test(value);\n}\n\n/**\n * The web branch's base URL on the SDK's configured gateway: the gateway URL's origin, plus\n * {@link AVATAR_WEB_API_PATH}. Null for anything that is not an `https:` URL (or `http:` on this machine, for local\n * development).\n */\nexport function avatarApiBase(gatewayUrl: string | undefined): string | null {\n if (!gatewayUrl) return null;\n let url: URL;\n try {\n url = new URL(gatewayUrl);\n } catch {\n return null;\n }\n const local =\n url.protocol === \"http:\" && LOOPBACK_HOSTS.has(url.hostname.toLowerCase());\n if (url.protocol !== \"https:\" && !local) return null;\n return `${url.origin}${AVATAR_WEB_API_PATH}`;\n}\n\n/** A real-looking Turnstile site key: `0x…`, and not of the shape of Cloudflare's test keys. */\nexport function isRealTurnstileSiteKey(key: string): boolean {\n return REAL_SITE_KEY.test(key) && !TEST_SITE_KEY.test(key);\n}\n\n/**\n * Whether a host's `avatar.live` has everything the live avatar needs, in the shapes the contract accepts. The site\n * key is optional (an empty one is none), and checked only when it is there.\n */\nexport function liveConfigComplete(live: ExplorePanelAvatarLive): boolean {\n const key: unknown = live.turnstileSiteKey;\n const allowTest: unknown = live.allowTestSiteKey;\n const endUnattended: unknown = live.endUnattended;\n const mic: unknown = live.mic;\n return (\n typeof live.load === \"function\" &&\n typeof live.site === \"string\" &&\n isAvatarSite(live.site) &&\n typeof live.manifestVersion === \"string\" &&\n isAvatarVersion(live.manifestVersion) &&\n (key === undefined ||\n key === \"\" ||\n (typeof key === \"string\" && SITE_KEY.test(key))) &&\n (allowTest === undefined || typeof allowTest === \"boolean\") &&\n (endUnattended === undefined || typeof endUnattended === \"boolean\") &&\n (mic === undefined || typeof mic === \"boolean\")\n );\n}\n\n/**\n * The site key a bot check at Start may use, or null when there is none: no key, one of no valid shape, or one of\n * Cloudflare's test keys where `allowTestSiteKey` is false.\n */\nexport function turnstileSiteKeyOf(\n live: ExplorePanelAvatarLive,\n): string | null {\n const key = live.turnstileSiteKey;\n if (typeof key !== \"string\" || !SITE_KEY.test(key)) return null;\n if (live.allowTestSiteKey === false && !isRealTurnstileSiteKey(key)) {\n return null;\n }\n return key;\n}\n\n/**\n * The locales whose live-avatar strings have been reviewed. The live avatar runs only in these; see\n * `explore-avatar-i18n.ts`. English only, until a named reviewer signs off on another.\n */\nconst REVIEWED: ReadonlySet<ExploreLocale> = new Set<ExploreLocale>([\"en\"]);\n\n/** Whether the live avatar may run in `locale`: its strings have been reviewed. */\nexport function isAvatarLiveLocaleReviewed(locale: ExploreLocale): boolean {\n return REVIEWED.has(locale);\n}\n"]}
@@ -309,7 +309,11 @@ interface ExplorePanelAvatarLive {
309
309
  * or Brave, and only under consent words that describe it), or the gateway's speech-to-text stream under `stt` (while
310
310
  * the gateway's website speech-to-text is on). Only where the box takes a question, after a mic notice of its own on
311
311
  * the first tap. The mic's code is a chunk of its own, loaded the first time it is used. Unset or false: no mic, as
312
- * before; set, a server that offers no mic still shows none.
312
+ * before; set, a server that offers no mic still shows none. Where the server's `mic` also carries a pause
313
+ * (`pauseMs`, contract v1.7) under a version whose entry declares no notice (Stage C3), the mic is a voice
314
+ * conversation instead (Stage C2: Talk listening inside its own tap, with no mic notice and nothing else shown first,
315
+ * the question sent by itself after the pause, the mic listening again after each answer; its code is made ready as
316
+ * soon as Talk is offered); no other setting.
313
317
  */
314
318
  mic?: boolean;
315
319
  }
@@ -309,7 +309,11 @@ interface ExplorePanelAvatarLive {
309
309
  * or Brave, and only under consent words that describe it), or the gateway's speech-to-text stream under `stt` (while
310
310
  * the gateway's website speech-to-text is on). Only where the box takes a question, after a mic notice of its own on
311
311
  * the first tap. The mic's code is a chunk of its own, loaded the first time it is used. Unset or false: no mic, as
312
- * before; set, a server that offers no mic still shows none.
312
+ * before; set, a server that offers no mic still shows none. Where the server's `mic` also carries a pause
313
+ * (`pauseMs`, contract v1.7) under a version whose entry declares no notice (Stage C3), the mic is a voice
314
+ * conversation instead (Stage C2: Talk listening inside its own tap, with no mic notice and nothing else shown first,
315
+ * the question sent by itself after the pause, the mic listening again after each answer; its code is made ready as
316
+ * soon as Talk is offered); no other setting.
313
317
  */
314
318
  mic?: boolean;
315
319
  }
package/dist/index.d.mts CHANGED
@@ -18,8 +18,8 @@ export { LaunchCountdown, LaunchCountdownProps } from './components/launch-count
18
18
  export { ProblemSolved, ProblemsSolvedSection, ProblemsSolvedSectionProps } from './components/problems-solved.mjs';
19
19
  export { DetectedPlatform, ElectronDownloadLink, ElectronDownloadLinkProps, detectElectronPlatform } from './components/electron-download.mjs';
20
20
  import { Components } from 'react-markdown';
21
- import { a as ExploreLocale, E as ExplorePanelAvatarLive } from './explore-avatar-live-config-BxiE66Im.mjs';
22
- export { b as EXPLORE_LOCALES, c as ExploreStrings, s as speechLocaleOf } from './explore-avatar-live-config-BxiE66Im.mjs';
21
+ import { a as ExploreLocale, E as ExplorePanelAvatarLive } from './explore-avatar-live-config-D2PGF8fG.mjs';
22
+ export { b as EXPLORE_LOCALES, c as ExploreStrings, s as speechLocaleOf } from './explore-avatar-live-config-D2PGF8fG.mjs';
23
23
  import { A as AnswerRendererStrings, E as ExploreScope } from './explore-ei8uoko9.mjs';
24
24
  export { a as EXPLORE_PREFILL_MAX_LENGTH, b as ExploreAnswerBody, c as ExploreAnswerBodyProps, d as ExploreAnswerStyles, e as ExplorePage, f as ExplorePageProps, g as answerPlainText, h as answerSpeechText, i as exploreQuestionHref, j as inlineImageOrigins, k as isInlineImageAllowed, l as isInlineVideoAllowed, p as productSlugsByOrigin } from './explore-ei8uoko9.mjs';
25
25
  export { ExploreCta, ExploreCtaProps } from './components/explore-cta.mjs';
@@ -866,7 +866,7 @@ declare function websiteAvatarLive({ deployEnv, siteKey, site, manifestVersion,
866
866
 
867
867
  declare const buttonVariants: (props?: ({
868
868
  variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
869
- size?: "default" | "sm" | "lg" | "icon" | null | undefined;
869
+ size?: "icon" | "default" | "sm" | "lg" | null | undefined;
870
870
  } & class_variance_authority_types.ClassProp) | undefined) => string;
871
871
  interface ButtonProps extends react.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
872
872
  asChild?: boolean;
package/dist/index.d.ts CHANGED
@@ -18,8 +18,8 @@ export { LaunchCountdown, LaunchCountdownProps } from './components/launch-count
18
18
  export { ProblemSolved, ProblemsSolvedSection, ProblemsSolvedSectionProps } from './components/problems-solved.js';
19
19
  export { DetectedPlatform, ElectronDownloadLink, ElectronDownloadLinkProps, detectElectronPlatform } from './components/electron-download.js';
20
20
  import { Components } from 'react-markdown';
21
- import { a as ExploreLocale, E as ExplorePanelAvatarLive } from './explore-avatar-live-config-1zeXdMer.js';
22
- export { b as EXPLORE_LOCALES, c as ExploreStrings, s as speechLocaleOf } from './explore-avatar-live-config-1zeXdMer.js';
21
+ import { a as ExploreLocale, E as ExplorePanelAvatarLive } from './explore-avatar-live-config-CJstiSAN.js';
22
+ export { b as EXPLORE_LOCALES, c as ExploreStrings, s as speechLocaleOf } from './explore-avatar-live-config-CJstiSAN.js';
23
23
  import { A as AnswerRendererStrings, E as ExploreScope } from './explore-Dfru4j4m.js';
24
24
  export { a as EXPLORE_PREFILL_MAX_LENGTH, b as ExploreAnswerBody, c as ExploreAnswerBodyProps, d as ExploreAnswerStyles, e as ExplorePage, f as ExplorePageProps, g as answerPlainText, h as answerSpeechText, i as exploreQuestionHref, j as inlineImageOrigins, k as isInlineImageAllowed, l as isInlineVideoAllowed, p as productSlugsByOrigin } from './explore-Dfru4j4m.js';
25
25
  export { ExploreCta, ExploreCtaProps } from './components/explore-cta.js';
@@ -866,7 +866,7 @@ declare function websiteAvatarLive({ deployEnv, siteKey, site, manifestVersion,
866
866
 
867
867
  declare const buttonVariants: (props?: ({
868
868
  variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
869
- size?: "default" | "sm" | "lg" | "icon" | null | undefined;
869
+ size?: "icon" | "default" | "sm" | "lg" | null | undefined;
870
870
  } & class_variance_authority_types.ClassProp) | undefined) => string;
871
871
  interface ButtonProps extends react.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
872
872
  asChild?: boolean;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunk3AIAJ2L4_js = require('./chunk-3AIAJ2L4.js');
3
+ var chunkZ2UP22GV_js = require('./chunk-Z2UP22GV.js');
4
4
  var chunk6KY2ZRVI_js = require('./chunk-6KY2ZRVI.js');
5
5
  require('./chunk-VRWZLGR7.js');
6
6
  var chunkQMA6U6AU_js = require('./chunk-QMA6U6AU.js');
@@ -3365,13 +3365,13 @@ function ExploreAvatarLive({
3365
3365
  () => suggestedQuestions(catalog, examplePrompts, pinnedSlug),
3366
3366
  [catalog, examplePrompts, pinnedSlug]
3367
3367
  );
3368
- const base = chunk3AIAJ2L4_js.avatarApiBase(config.gatewayUrl);
3368
+ const base = chunkZ2UP22GV_js.avatarApiBase(config.gatewayUrl);
3369
3369
  const headers = react.useMemo(
3370
3370
  () => chunkJXLSTVBK_js.cfAccessHeaders(config.gatewayUrl, config.cfAccess),
3371
3371
  [config.gatewayUrl, config.cfAccess]
3372
3372
  );
3373
- const complete = chunk3AIAJ2L4_js.liveConfigComplete(live);
3374
- const off = !complete || base === null || conversation === null || conversation.isSharedView || !chunk3AIAJ2L4_js.isAvatarLiveLocaleReviewed(locale);
3373
+ const complete = chunkZ2UP22GV_js.liveConfigComplete(live);
3374
+ const off = !complete || base === null || conversation === null || conversation.isSharedView || !chunkZ2UP22GV_js.isAvatarLiveLocaleReviewed(locale);
3375
3375
  react.useEffect(() => {
3376
3376
  if (!complete) {
3377
3377
  console.warn(
@@ -4360,7 +4360,7 @@ function websiteAvatarLive({
4360
4360
  };
4361
4361
  const key = typeof siteKey === "string" ? siteKey.trim() : "";
4362
4362
  if (key === "") return live;
4363
- const accepted = alpha ? key === ALPHA_TEST_SITE_KEY || chunk3AIAJ2L4_js.isRealTurnstileSiteKey(key) : chunk3AIAJ2L4_js.isRealTurnstileSiteKey(key);
4363
+ const accepted = alpha ? key === ALPHA_TEST_SITE_KEY || chunkZ2UP22GV_js.isRealTurnstileSiteKey(key) : chunkZ2UP22GV_js.isRealTurnstileSiteKey(key);
4364
4364
  if (!accepted) return void 0;
4365
4365
  return { ...live, turnstileSiteKey: key };
4366
4366
  }
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { isRealTurnstileSiteKey, avatarApiBase, liveConfigComplete, isAvatarLiveLocaleReviewed } from './chunk-JGU6JW4N.mjs';
1
+ import { isRealTurnstileSiteKey, avatarApiBase, liveConfigComplete, isAvatarLiveLocaleReviewed } from './chunk-BHEHF3Y5.mjs';
2
2
  export { DEFAULT_COMPARISON_MAX_AGE_DAYS, checkComparisonFreshness, formatFreshnessReport, latestVerification, oldestVerification, parseVerifiedAt } from './chunk-B63XLDZH.mjs';
3
3
  import './chunk-GTRAXQGC.mjs';
4
4
  export { RybbitAnalytics } from './chunk-NZANQGDM.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/website-sdk",
3
- "version": "2026.1105.3",
3
+ "version": "2026.1106.1",
4
4
  "description": "Shared SDK for Burdenoff product websites - reusable React components, utilities, and configurations",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",