@burdenoff/website-sdk 2026.1101.1 → 2026.1101.2

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.
Files changed (276) hide show
  1. package/README.md +42 -8
  2. package/dist/analytics/index.js +19 -69
  3. package/dist/analytics/index.js.map +1 -1
  4. package/dist/analytics/index.mjs +3 -69
  5. package/dist/analytics/index.mjs.map +1 -1
  6. package/dist/avatar-live/index.js +1 -0
  7. package/dist/avatar-live/index.js.map +1 -1
  8. package/dist/avatar-live/index.mjs +1 -0
  9. package/dist/avatar-live/index.mjs.map +1 -1
  10. package/dist/avatar-live-view/index.js +10 -30
  11. package/dist/avatar-live-view/index.js.map +1 -1
  12. package/dist/avatar-live-view/index.mjs +4 -24
  13. package/dist/avatar-live-view/index.mjs.map +1 -1
  14. package/dist/chunk-23H4JUL4.js +41 -0
  15. package/dist/chunk-23H4JUL4.js.map +1 -0
  16. package/dist/chunk-34RX5P75.mjs +25 -0
  17. package/dist/chunk-34RX5P75.mjs.map +1 -0
  18. package/dist/chunk-3LFFY7AZ.js +184 -0
  19. package/dist/chunk-3LFFY7AZ.js.map +1 -0
  20. package/dist/chunk-3X26GE4S.js +2794 -0
  21. package/dist/chunk-3X26GE4S.js.map +1 -0
  22. package/dist/chunk-45GCA4GG.js +85 -0
  23. package/dist/chunk-45GCA4GG.js.map +1 -0
  24. package/dist/chunk-4SAGVLHN.js +876 -0
  25. package/dist/chunk-4SAGVLHN.js.map +1 -0
  26. package/dist/chunk-4U6YAII5.js +218 -0
  27. package/dist/chunk-4U6YAII5.js.map +1 -0
  28. package/dist/chunk-5GMTBOO6.js +21 -0
  29. package/dist/chunk-5GMTBOO6.js.map +1 -0
  30. package/dist/chunk-5JRJ57CL.mjs +329 -0
  31. package/dist/chunk-5JRJ57CL.mjs.map +1 -0
  32. package/dist/chunk-6C2DKIC7.js +4 -0
  33. package/dist/chunk-6C2DKIC7.js.map +1 -0
  34. package/dist/chunk-6IBFBWC5.mjs +17 -0
  35. package/dist/chunk-6IBFBWC5.mjs.map +1 -0
  36. package/dist/chunk-6KY2ZRVI.js +116 -0
  37. package/dist/chunk-6KY2ZRVI.js.map +1 -0
  38. package/dist/chunk-6VJT3SM4.mjs +182 -0
  39. package/dist/chunk-6VJT3SM4.mjs.map +1 -0
  40. package/dist/chunk-6YZUFMK2.mjs +40 -0
  41. package/dist/chunk-6YZUFMK2.mjs.map +1 -0
  42. package/dist/chunk-7A6R7CGG.js +47 -0
  43. package/dist/chunk-7A6R7CGG.js.map +1 -0
  44. package/dist/chunk-7PWL4V2R.js +4 -0
  45. package/dist/chunk-7PWL4V2R.js.map +1 -0
  46. package/dist/chunk-AA33WDYS.js +161 -0
  47. package/dist/chunk-AA33WDYS.js.map +1 -0
  48. package/dist/chunk-AHRGFXV2.js +525 -0
  49. package/dist/chunk-AHRGFXV2.js.map +1 -0
  50. package/dist/chunk-B63XLDZH.mjs +109 -0
  51. package/dist/chunk-B63XLDZH.mjs.map +1 -0
  52. package/dist/chunk-BX2USEWF.mjs +35 -0
  53. package/dist/chunk-BX2USEWF.mjs.map +1 -0
  54. package/dist/chunk-BYSZT35R.js +150 -0
  55. package/dist/chunk-BYSZT35R.js.map +1 -0
  56. package/dist/chunk-C6OJIHNO.js +12 -0
  57. package/dist/chunk-C6OJIHNO.js.map +1 -0
  58. package/dist/chunk-COOELBX7.mjs +63 -0
  59. package/dist/chunk-COOELBX7.mjs.map +1 -0
  60. package/dist/chunk-D2TW7MNC.mjs +2771 -0
  61. package/dist/chunk-D2TW7MNC.mjs.map +1 -0
  62. package/dist/chunk-D5KEZ6V6.mjs +3 -0
  63. package/dist/chunk-D5KEZ6V6.mjs.map +1 -0
  64. package/dist/chunk-E3GUGJMY.js +46 -0
  65. package/dist/chunk-E3GUGJMY.js.map +1 -0
  66. package/dist/chunk-EBZ2HGJ2.mjs +52 -0
  67. package/dist/chunk-EBZ2HGJ2.mjs.map +1 -0
  68. package/dist/chunk-EDLTUOIA.mjs +210 -0
  69. package/dist/chunk-EDLTUOIA.mjs.map +1 -0
  70. package/dist/chunk-EJQSOT4X.js +4 -0
  71. package/dist/chunk-EJQSOT4X.js.map +1 -0
  72. package/dist/chunk-EUPFS6MZ.mjs +2019 -0
  73. package/dist/chunk-EUPFS6MZ.mjs.map +1 -0
  74. package/dist/chunk-F3XWDC3B.js +77 -0
  75. package/dist/chunk-F3XWDC3B.js.map +1 -0
  76. package/dist/chunk-FFADMVIK.mjs +5127 -0
  77. package/dist/chunk-FFADMVIK.mjs.map +1 -0
  78. package/dist/chunk-FJWS7BKT.mjs +3 -0
  79. package/dist/chunk-FJWS7BKT.mjs.map +1 -0
  80. package/dist/chunk-FJZJDUOR.mjs +3 -0
  81. package/dist/chunk-FJZJDUOR.mjs.map +1 -0
  82. package/dist/chunk-GCFLCO2Z.mjs +145 -0
  83. package/dist/chunk-GCFLCO2Z.mjs.map +1 -0
  84. package/dist/chunk-GEXC4LRP.js +61 -0
  85. package/dist/chunk-GEXC4LRP.js.map +1 -0
  86. package/dist/chunk-GTRAXQGC.mjs +3 -0
  87. package/dist/chunk-GTRAXQGC.mjs.map +1 -0
  88. package/dist/chunk-IKOIRUKW.js +270 -0
  89. package/dist/chunk-IKOIRUKW.js.map +1 -0
  90. package/dist/chunk-IXTWOPRN.js +45 -0
  91. package/dist/chunk-IXTWOPRN.js.map +1 -0
  92. package/dist/chunk-JDXOXGOK.js +547 -0
  93. package/dist/chunk-JDXOXGOK.js.map +1 -0
  94. package/dist/chunk-JSLDWYV7.mjs +558 -0
  95. package/dist/chunk-JSLDWYV7.mjs.map +1 -0
  96. package/dist/chunk-JXLSTVBK.js +336 -0
  97. package/dist/chunk-JXLSTVBK.js.map +1 -0
  98. package/dist/chunk-KHYXK7FG.mjs +49 -0
  99. package/dist/chunk-KHYXK7FG.mjs.map +1 -0
  100. package/dist/chunk-KSMBOWQQ.js +2034 -0
  101. package/dist/chunk-KSMBOWQQ.js.map +1 -0
  102. package/dist/chunk-KU2PXHGB.mjs +83 -0
  103. package/dist/chunk-KU2PXHGB.mjs.map +1 -0
  104. package/dist/chunk-KZANIV3S.mjs +37 -0
  105. package/dist/chunk-KZANIV3S.mjs.map +1 -0
  106. package/dist/chunk-L4BP6YOB.js +20 -0
  107. package/dist/chunk-L4BP6YOB.js.map +1 -0
  108. package/dist/chunk-MRKA2OAF.mjs +75 -0
  109. package/dist/chunk-MRKA2OAF.mjs.map +1 -0
  110. package/dist/chunk-N5EC53MH.mjs +80 -0
  111. package/dist/chunk-N5EC53MH.mjs.map +1 -0
  112. package/dist/chunk-NZANQGDM.mjs +34 -0
  113. package/dist/chunk-NZANQGDM.mjs.map +1 -0
  114. package/dist/chunk-O4GS3XPZ.js +10 -0
  115. package/dist/chunk-O4GS3XPZ.js.map +1 -0
  116. package/dist/chunk-O76QDGRM.js +5154 -0
  117. package/dist/chunk-O76QDGRM.js.map +1 -0
  118. package/dist/chunk-OYEUNZJY.js +114 -0
  119. package/dist/chunk-OYEUNZJY.js.map +1 -0
  120. package/dist/chunk-PHXAGQNB.js +74 -0
  121. package/dist/chunk-PHXAGQNB.js.map +1 -0
  122. package/dist/chunk-PIHHPP7J.mjs +16 -0
  123. package/dist/chunk-PIHHPP7J.mjs.map +1 -0
  124. package/dist/chunk-PXRHVVOU.mjs +499 -0
  125. package/dist/chunk-PXRHVVOU.mjs.map +1 -0
  126. package/dist/chunk-QMA6U6AU.js +36 -0
  127. package/dist/chunk-QMA6U6AU.js.map +1 -0
  128. package/dist/chunk-R6J7IULF.js +44 -0
  129. package/dist/chunk-R6J7IULF.js.map +1 -0
  130. package/dist/chunk-R7G6TWIS.mjs +10 -0
  131. package/dist/chunk-R7G6TWIS.mjs.map +1 -0
  132. package/dist/chunk-RUMGFI6L.mjs +544 -0
  133. package/dist/chunk-RUMGFI6L.mjs.map +1 -0
  134. package/dist/chunk-S54JWSJF.js +565 -0
  135. package/dist/chunk-S54JWSJF.js.map +1 -0
  136. package/dist/chunk-SK5UGG6A.js +2041 -0
  137. package/dist/chunk-SK5UGG6A.js.map +1 -0
  138. package/dist/chunk-SKIYDKIS.mjs +70 -0
  139. package/dist/chunk-SKIYDKIS.mjs.map +1 -0
  140. package/dist/chunk-SZNS5IK6.mjs +322 -0
  141. package/dist/chunk-SZNS5IK6.mjs.map +1 -0
  142. package/dist/chunk-T7JIPZAC.js +66 -0
  143. package/dist/chunk-T7JIPZAC.js.map +1 -0
  144. package/dist/chunk-TGYQHO2E.mjs +864 -0
  145. package/dist/chunk-TGYQHO2E.mjs.map +1 -0
  146. package/dist/chunk-TT3MHN42.mjs +8 -0
  147. package/dist/chunk-TT3MHN42.mjs.map +1 -0
  148. package/dist/chunk-UM55U65W.mjs +268 -0
  149. package/dist/chunk-UM55U65W.mjs.map +1 -0
  150. package/dist/chunk-UQ2HALUD.js +72 -0
  151. package/dist/chunk-UQ2HALUD.js.map +1 -0
  152. package/dist/chunk-UXWT7G77.js +82 -0
  153. package/dist/chunk-UXWT7G77.js.map +1 -0
  154. package/dist/chunk-VNS5WDWO.mjs +7 -0
  155. package/dist/chunk-VNS5WDWO.mjs.map +1 -0
  156. package/dist/chunk-VRWZLGR7.js +4 -0
  157. package/dist/chunk-VRWZLGR7.js.map +1 -0
  158. package/dist/chunk-VZLDSNH4.mjs +22 -0
  159. package/dist/chunk-VZLDSNH4.mjs.map +1 -0
  160. package/dist/chunk-W6P3YETJ.js +92 -0
  161. package/dist/chunk-W6P3YETJ.js.map +1 -0
  162. package/dist/chunk-WPKFFQCR.mjs +90 -0
  163. package/dist/chunk-WPKFFQCR.mjs.map +1 -0
  164. package/dist/chunk-WTVTI5TO.mjs +22 -0
  165. package/dist/chunk-WTVTI5TO.mjs.map +1 -0
  166. package/dist/chunk-XJOBZ4RJ.js +44 -0
  167. package/dist/chunk-XJOBZ4RJ.js.map +1 -0
  168. package/dist/chunk-XP2JDNDU.js +325 -0
  169. package/dist/chunk-XP2JDNDU.js.map +1 -0
  170. package/dist/chunk-Y4V5CEWD.mjs +112 -0
  171. package/dist/chunk-Y4V5CEWD.mjs.map +1 -0
  172. package/dist/chunk-YERN2UWM.js +116 -0
  173. package/dist/chunk-YERN2UWM.js.map +1 -0
  174. package/dist/chunk-ZKJQWFAA.mjs +114 -0
  175. package/dist/chunk-ZKJQWFAA.mjs.map +1 -0
  176. package/dist/chunk-ZOON3S3Y.mjs +138 -0
  177. package/dist/chunk-ZOON3S3Y.mjs.map +1 -0
  178. package/dist/chunk-ZP6QRAYC.mjs +2024 -0
  179. package/dist/chunk-ZP6QRAYC.mjs.map +1 -0
  180. package/dist/chunk-ZSTFX7FV.js +9 -0
  181. package/dist/chunk-ZSTFX7FV.js.map +1 -0
  182. package/dist/client/index.js +23 -331
  183. package/dist/client/index.js.map +1 -1
  184. package/dist/client/index.mjs +3 -330
  185. package/dist/client/index.mjs.map +1 -1
  186. package/dist/comparison/index.js +28 -111
  187. package/dist/comparison/index.js.map +1 -1
  188. package/dist/comparison/index.mjs +2 -107
  189. package/dist/comparison/index.mjs.map +1 -1
  190. package/dist/components/contact.js +21 -931
  191. package/dist/components/contact.js.map +1 -1
  192. package/dist/components/contact.mjs +12 -909
  193. package/dist/components/contact.mjs.map +1 -1
  194. package/dist/components/electron-download.js +12 -124
  195. package/dist/components/electron-download.js.map +1 -1
  196. package/dist/components/electron-download.mjs +4 -106
  197. package/dist/components/electron-download.mjs.map +1 -1
  198. package/dist/components/explore-cta.js +8 -112
  199. package/dist/components/explore-cta.js.map +1 -1
  200. package/dist/components/explore-cta.mjs +3 -114
  201. package/dist/components/explore-cta.mjs.map +1 -1
  202. package/dist/components/explore.js +62 -7833
  203. package/dist/components/explore.js.map +1 -1
  204. package/dist/components/explore.mjs +13 -7800
  205. package/dist/components/explore.mjs.map +1 -1
  206. package/dist/components/launch-countdown.js +8 -83
  207. package/dist/components/launch-countdown.js.map +1 -1
  208. package/dist/components/launch-countdown.mjs +3 -85
  209. package/dist/components/launch-countdown.mjs.map +1 -1
  210. package/dist/components/newsletter.js +20 -993
  211. package/dist/components/newsletter.js.map +1 -1
  212. package/dist/components/newsletter.mjs +11 -971
  213. package/dist/components/newsletter.mjs.map +1 -1
  214. package/dist/components/partners.js +24 -1302
  215. package/dist/components/partners.js.map +1 -1
  216. package/dist/components/partners.mjs +15 -1276
  217. package/dist/components/partners.mjs.map +1 -1
  218. package/dist/components/pricing.js +49 -1249
  219. package/dist/components/pricing.js.map +1 -1
  220. package/dist/components/pricing.mjs +8 -1226
  221. package/dist/components/pricing.mjs.map +1 -1
  222. package/dist/components/problems-solved.js +8 -150
  223. package/dist/components/problems-solved.js.map +1 -1
  224. package/dist/components/problems-solved.mjs +4 -133
  225. package/dist/components/problems-solved.mjs.map +1 -1
  226. package/dist/components/resource-links.js +8 -304
  227. package/dist/components/resource-links.js.map +1 -1
  228. package/dist/components/resource-links.mjs +5 -308
  229. package/dist/components/resource-links.mjs.map +1 -1
  230. package/dist/components/social-links.js +8 -343
  231. package/dist/components/social-links.js.map +1 -1
  232. package/dist/components/social-links.mjs +5 -347
  233. package/dist/components/social-links.mjs.map +1 -1
  234. package/dist/components/unsubscribe.js +10 -627
  235. package/dist/components/unsubscribe.js.map +1 -1
  236. package/dist/components/unsubscribe.mjs +6 -611
  237. package/dist/components/unsubscribe.mjs.map +1 -1
  238. package/dist/components/waitlist.js +21 -1001
  239. package/dist/components/waitlist.js.map +1 -1
  240. package/dist/components/waitlist.mjs +12 -982
  241. package/dist/components/waitlist.mjs.map +1 -1
  242. package/dist/components/website-switcher.js +12 -175
  243. package/dist/components/website-switcher.js.map +1 -1
  244. package/dist/components/website-switcher.mjs +4 -157
  245. package/dist/components/website-switcher.mjs.map +1 -1
  246. package/dist/content/index.js +118 -3616
  247. package/dist/content/index.js.map +1 -1
  248. package/dist/content/index.mjs +12 -3567
  249. package/dist/content/index.mjs.map +1 -1
  250. package/dist/hooks/index.js +37 -2321
  251. package/dist/hooks/index.js.map +1 -1
  252. package/dist/hooks/index.mjs +6 -2318
  253. package/dist/hooks/index.mjs.map +1 -1
  254. package/dist/index.d.mts +1 -1
  255. package/dist/index.d.ts +1 -1
  256. package/dist/index.js +4145 -20599
  257. package/dist/index.js.map +1 -1
  258. package/dist/index.mjs +3766 -20567
  259. package/dist/index.mjs.map +1 -1
  260. package/dist/product/index.js +20 -393
  261. package/dist/product/index.js.map +1 -1
  262. package/dist/product/index.mjs +5 -394
  263. package/dist/product/index.mjs.map +1 -1
  264. package/dist/seo/index.js +8 -76
  265. package/dist/seo/index.js.map +1 -1
  266. package/dist/seo/index.mjs +3 -78
  267. package/dist/seo/index.mjs.map +1 -1
  268. package/dist/store/index.js +51 -2650
  269. package/dist/store/index.js.map +1 -1
  270. package/dist/store/index.mjs +13 -2619
  271. package/dist/store/index.mjs.map +1 -1
  272. package/dist/utils/index.js +69 -134
  273. package/dist/utils/index.js.map +1 -1
  274. package/dist/utils/index.mjs +5 -122
  275. package/dist/utils/index.mjs.map +1 -1
  276. package/package.json +2 -1
package/README.md CHANGED
@@ -34,6 +34,38 @@ For UI components (if using separately):
34
34
  npm install @radix-ui/react-label @radix-ui/react-slot class-variance-authority clsx tailwind-merge
35
35
  ```
36
36
 
37
+ ## Bundle, sub-paths and context identity
38
+
39
+ Since **2026.1101.2** (BOFF-8508) the ESM build is code-split (`splitting: true`
40
+ in `tsup.config.ts`) and the package declares `"sideEffects": false`:
41
+
42
+ - **One context identity.** Shared modules — the React contexts above all
43
+ (`WebSDKProvider`, `ProductProvider`, the explore conversation contexts) —
44
+ are built once into common chunks that every entry imports. A provider
45
+ installed from the package root is seen by components imported from a
46
+ sub-path (`@burdenoff/website-sdk/components/explore`, `/content`, …) and
47
+ the other way round. There is no duplicate-context failure mode any more.
48
+ - **Smaller startup bundles.** Because the package is side-effect-free and
49
+ every entry is a thin graph over shared chunks, a bundler tree-shakes an
50
+ `import { PageHead } from '@burdenoff/website-sdk'` down to the SEO subtree:
51
+ the optional content/markdown, store, comparison, forms and reCAPTCHA code
52
+ stays out of the startup graph unless it is actually used. A site that
53
+ lazy-routes heavy SDK pages (`const sdk = () => import('@burdenoff/website-sdk')`
54
+ inside `React.lazy`) gets them as separate on-demand chunks — provided its
55
+ own build config does not force every SDK module into one eager manual
56
+ chunk.
57
+ - **Exact-version adoption interface.** Root/sub-path context identity holds
58
+ when *all* of a site's imports resolve to the **same installed version**
59
+ `>= 2026.1101.2`. Duplicate installed copies of the package (for example a
60
+ second copy under a nested `node_modules`) still mean duplicate contexts —
61
+ dedupe the package in your lockfile. Versions `< 2026.1101.2` are built
62
+ flat: there, import providers **and** consumers from the package root only.
63
+
64
+ The live-avatar guarantees are unchanged: `livekit-client` stays external to
65
+ the `./avatar-live` entry alone, and the avatar's view remains a chunk of its
66
+ own that only the root loads on demand (`scripts/validate-avatar-live.mjs`
67
+ fails the build otherwise).
68
+
37
69
  ## Quick Start
38
70
 
39
71
  ### Contact Page
@@ -114,14 +146,16 @@ function Pricing() {
114
146
  English and gets an answer drawn from the product's own published documentation,
115
147
  with the source pages and next-step CTAs attached.
116
148
 
117
- > **Import from the package ROOT.** `ExplorePage`, `ExploreCta` and
118
- > `useExploreChat` **must** be imported from `@burdenoff/website-sdk`, never
119
- > from `@burdenoff/website-sdk/components/explore`. This package builds with
120
- > tsup `splitting: false`, so every sub-path entry bundles **its own copy of the
121
- > React context** — a sub-path `ExplorePage` cannot see your `WebSDKProvider`
122
- > and throws `useWebSDK must be used within a WebSDKProvider` even when it is
123
- > rendered inside one. The sub-path entries exist only for build parity with the
124
- > other components.
149
+ > **Root and sub-path imports are interchangeable since `2026.1101.2`** (the
150
+ > first release built with tsup `splitting: true` — see
151
+ > [Bundle, sub-paths and context identity](#bundle-sub-paths-and-context-identity)).
152
+ > On **older versions** `ExplorePage`, `ExploreCta` and `useExploreChat`
153
+ > **must** be imported from the package ROOT, never from
154
+ > `@burdenoff/website-sdk/components/explore`: those builds gave every
155
+ > sub-path entry **its own copy of the React context**, so a sub-path
156
+ > `ExplorePage` could not see your `WebSDKProvider` and threw
157
+ > `useWebSDK must be used within a WebSDKProvider` even when it was rendered
158
+ > inside one.
125
159
 
126
160
  ```tsx
127
161
  import { ExplorePage, ExploreCta } from '@burdenoff/website-sdk'
@@ -1,76 +1,26 @@
1
1
  'use strict';
2
2
 
3
- var react = require('react');
3
+ var chunkQMA6U6AU_js = require('../chunk-QMA6U6AU.js');
4
+ var chunkXJOBZ4RJ_js = require('../chunk-XJOBZ4RJ.js');
5
+ require('../chunk-ZSTFX7FV.js');
4
6
 
5
- function RybbitAnalytics({
6
- siteId,
7
- scriptUrl = "https://analytics.burdenoff.com/api/script.js",
8
- enabled = true
9
- }) {
10
- react.useEffect(() => {
11
- if (!enabled || !siteId) return;
12
- const existingScript = document.querySelector(
13
- `script[data-site-id="${siteId}"]`
14
- );
15
- if (existingScript) return;
16
- const script = document.createElement("script");
17
- script.src = scriptUrl;
18
- script.defer = true;
19
- script.crossOrigin = "anonymous";
20
- script.setAttribute("data-site-id", siteId);
21
- document.head.appendChild(script);
22
- return () => {
23
- const scriptToRemove = document.querySelector(
24
- `script[data-site-id="${siteId}"]`
25
- );
26
- if (scriptToRemove) {
27
- document.head.removeChild(scriptToRemove);
28
- }
29
- };
30
- }, [siteId, scriptUrl, enabled]);
31
- return null;
32
- }
33
7
 
34
- // src/analytics/events.ts
35
- var MAX_PROPERTY_LENGTH = 64;
36
- var EMAIL_LIKE = /[^\s@]+@[^\s@]+/;
37
- function sanitizeWebsiteEventProperties(properties) {
38
- const safe = {};
39
- if (!properties) return safe;
40
- for (const [key, value] of Object.entries(properties)) {
41
- if (typeof value === "boolean") {
42
- safe[key] = value;
43
- } else if (typeof value === "number") {
44
- if (Number.isFinite(value)) safe[key] = value;
45
- } else if (typeof value === "string") {
46
- const trimmed = value.trim();
47
- if (trimmed.length > 0 && trimmed.length <= MAX_PROPERTY_LENGTH && !EMAIL_LIKE.test(trimmed)) {
48
- safe[key] = trimmed;
49
- }
50
- }
51
- }
52
- return safe;
53
- }
54
- function trackWebsiteEvent(name, properties) {
55
- if (typeof window === "undefined") return false;
56
- try {
57
- const rybbit = window.rybbit;
58
- if (typeof rybbit?.event !== "function") return false;
59
- const safe = sanitizeWebsiteEventProperties(properties);
60
- if (Object.keys(safe).length > 0) {
61
- rybbit.event(name, safe);
62
- } else {
63
- rybbit.event(name);
64
- }
65
- return true;
66
- } catch {
67
- return false;
68
- }
69
- }
70
8
 
71
- exports.MAX_PROPERTY_LENGTH = MAX_PROPERTY_LENGTH;
72
- exports.RybbitAnalytics = RybbitAnalytics;
73
- exports.sanitizeWebsiteEventProperties = sanitizeWebsiteEventProperties;
74
- exports.trackWebsiteEvent = trackWebsiteEvent;
9
+ Object.defineProperty(exports, "RybbitAnalytics", {
10
+ enumerable: true,
11
+ get: function () { return chunkQMA6U6AU_js.RybbitAnalytics; }
12
+ });
13
+ Object.defineProperty(exports, "MAX_PROPERTY_LENGTH", {
14
+ enumerable: true,
15
+ get: function () { return chunkXJOBZ4RJ_js.MAX_PROPERTY_LENGTH; }
16
+ });
17
+ Object.defineProperty(exports, "sanitizeWebsiteEventProperties", {
18
+ enumerable: true,
19
+ get: function () { return chunkXJOBZ4RJ_js.sanitizeWebsiteEventProperties; }
20
+ });
21
+ Object.defineProperty(exports, "trackWebsiteEvent", {
22
+ enumerable: true,
23
+ get: function () { return chunkXJOBZ4RJ_js.trackWebsiteEvent; }
24
+ });
75
25
  //# sourceMappingURL=index.js.map
76
26
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/analytics/rybbit.tsx","../../src/analytics/events.ts"],"names":["useEffect"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Rybbit Analytics Component\n *\n * Injects the Rybbit tracking script into the page head.\n * Works with both Vite (CSR) and Next.js (SSR/CSR) applications.\n *\n * @example\n * ```tsx\n * // In your app layout or root component\n * import { RybbitAnalytics } from '@burdenoff/website-sdk/analytics'\n *\n * function App() {\n * return (\n * <>\n * <RybbitAnalytics siteId=\"1\" />\n * <Router>...</Router>\n * </>\n * )\n * }\n * ```\n */\n\"use client\";\n\nimport { useEffect } from \"react\";\n\nexport interface RybbitAnalyticsProps {\n /**\n * Rybbit site ID (obtained from the Rybbit dashboard)\n */\n siteId: string;\n\n /**\n * Rybbit script URL (defaults to the self-hosted instance)\n * @default \"https://analytics.burdenoff.com/api/script.js\"\n */\n scriptUrl?: string;\n\n /**\n * Whether to enable tracking (useful for disabling in development)\n * @default true\n */\n enabled?: boolean;\n}\n\n/**\n * RybbitAnalytics - Embeds the Rybbit analytics tracking script.\n *\n * The script is loaded asynchronously with `defer` to avoid blocking page load.\n * It automatically tracks page views and user interactions.\n *\n * In development mode (when `enabled` is false), no script is loaded.\n */\nexport function RybbitAnalytics({\n siteId,\n scriptUrl = \"https://analytics.burdenoff.com/api/script.js\",\n enabled = true,\n}: RybbitAnalyticsProps) {\n useEffect(() => {\n if (!enabled || !siteId) return;\n\n // Check if script already exists\n const existingScript = document.querySelector(\n `script[data-site-id=\"${siteId}\"]`,\n );\n if (existingScript) return;\n\n // Create and inject the script. `crossOrigin=\"anonymous\"` puts the\n // request in CORS-no-credentials mode so the browser does not include\n // any third-party cookies the visitor may have on the analytics origin\n // (e.g. `app.rybbit.io`'s own dashboard session cookie). Without this,\n // Lighthouse `third-party-cookies` audit flags the page even though we\n // never read those cookies ourselves.\n const script = document.createElement(\"script\");\n script.src = scriptUrl;\n script.defer = true;\n script.crossOrigin = \"anonymous\";\n script.setAttribute(\"data-site-id\", siteId);\n document.head.appendChild(script);\n\n // Cleanup on unmount\n return () => {\n const scriptToRemove = document.querySelector(\n `script[data-site-id=\"${siteId}\"]`,\n );\n if (scriptToRemove) {\n document.head.removeChild(scriptToRemove);\n }\n };\n }, [siteId, scriptUrl, enabled]);\n\n // This component renders nothing - it only manages the script tag\n return null;\n}\n","/**\n * Website analytics events (Rybbit custom events).\n *\n * Product websites load the Rybbit script, which records pageviews on its own.\n * Funnel facts that are not a pageview (for example a successful waitlist\n * signup) are sent through `trackWebsiteEvent`, which calls `window.rybbit.event`\n * when the script is present and does nothing otherwise.\n *\n * Privacy rules (BOFF-3167):\n * - Event names come from the closed `WebsiteEventName` union.\n * - Properties are short, low-cardinality strings, numbers or booleans. Strings\n * longer than `MAX_PROPERTY_LENGTH`, values that look like an email address,\n * and any other value type are dropped, so a caller cannot leak an email,\n * name or free text into the analytics provider by mistake.\n * - Tracking never throws: analytics must not break a signup.\n */\n\n/** Custom events product websites emit. */\nexport type WebsiteEventName = \"waitlist.signed_up\";\n\nexport type WebsiteEventPropertyValue = string | number | boolean;\n\nexport type WebsiteEventProperties = Record<string, WebsiteEventPropertyValue>;\n\ndeclare global {\n interface Window {\n /** Set by the Rybbit tracking script (https://app.rybbit.io/api/script.js). */\n rybbit?: {\n event?: (\n name: string,\n properties?: Record<string, WebsiteEventPropertyValue>,\n ) => void;\n };\n }\n}\n\n/** Longest string property value that is forwarded. */\nexport const MAX_PROPERTY_LENGTH = 64;\n\nconst EMAIL_LIKE = /[^\\s@]+@[^\\s@]+/;\n\n/**\n * Keeps only short, non-identifying primitive values. Exported for tests.\n */\nexport function sanitizeWebsiteEventProperties(\n properties: WebsiteEventProperties | undefined,\n): WebsiteEventProperties {\n const safe: WebsiteEventProperties = {};\n if (!properties) return safe;\n for (const [key, value] of Object.entries(properties)) {\n if (typeof value === \"boolean\") {\n safe[key] = value;\n } else if (typeof value === \"number\") {\n if (Number.isFinite(value)) safe[key] = value;\n } else if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (\n trimmed.length > 0 &&\n trimmed.length <= MAX_PROPERTY_LENGTH &&\n !EMAIL_LIKE.test(trimmed)\n ) {\n safe[key] = trimmed;\n }\n }\n }\n return safe;\n}\n\n/**\n * Sends a custom event to Rybbit when the tracking script is loaded.\n * Returns true when the event was handed to Rybbit.\n */\nexport function trackWebsiteEvent(\n name: WebsiteEventName,\n properties?: WebsiteEventProperties,\n): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n const rybbit = window.rybbit;\n if (typeof rybbit?.event !== \"function\") return false;\n const safe = sanitizeWebsiteEventProperties(properties);\n if (Object.keys(safe).length > 0) {\n rybbit.event(name, safe);\n } else {\n rybbit.event(name);\n }\n return true;\n } catch {\n // Analytics is best-effort and must never break the page.\n return false;\n }\n}\n"]}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -1,71 +1,5 @@
1
- import { useEffect } from 'react';
2
-
3
- function RybbitAnalytics({
4
- siteId,
5
- scriptUrl = "https://analytics.burdenoff.com/api/script.js",
6
- enabled = true
7
- }) {
8
- useEffect(() => {
9
- if (!enabled || !siteId) return;
10
- const existingScript = document.querySelector(
11
- `script[data-site-id="${siteId}"]`
12
- );
13
- if (existingScript) return;
14
- const script = document.createElement("script");
15
- script.src = scriptUrl;
16
- script.defer = true;
17
- script.crossOrigin = "anonymous";
18
- script.setAttribute("data-site-id", siteId);
19
- document.head.appendChild(script);
20
- return () => {
21
- const scriptToRemove = document.querySelector(
22
- `script[data-site-id="${siteId}"]`
23
- );
24
- if (scriptToRemove) {
25
- document.head.removeChild(scriptToRemove);
26
- }
27
- };
28
- }, [siteId, scriptUrl, enabled]);
29
- return null;
30
- }
31
-
32
- // src/analytics/events.ts
33
- var MAX_PROPERTY_LENGTH = 64;
34
- var EMAIL_LIKE = /[^\s@]+@[^\s@]+/;
35
- function sanitizeWebsiteEventProperties(properties) {
36
- const safe = {};
37
- if (!properties) return safe;
38
- for (const [key, value] of Object.entries(properties)) {
39
- if (typeof value === "boolean") {
40
- safe[key] = value;
41
- } else if (typeof value === "number") {
42
- if (Number.isFinite(value)) safe[key] = value;
43
- } else if (typeof value === "string") {
44
- const trimmed = value.trim();
45
- if (trimmed.length > 0 && trimmed.length <= MAX_PROPERTY_LENGTH && !EMAIL_LIKE.test(trimmed)) {
46
- safe[key] = trimmed;
47
- }
48
- }
49
- }
50
- return safe;
51
- }
52
- function trackWebsiteEvent(name, properties) {
53
- if (typeof window === "undefined") return false;
54
- try {
55
- const rybbit = window.rybbit;
56
- if (typeof rybbit?.event !== "function") return false;
57
- const safe = sanitizeWebsiteEventProperties(properties);
58
- if (Object.keys(safe).length > 0) {
59
- rybbit.event(name, safe);
60
- } else {
61
- rybbit.event(name);
62
- }
63
- return true;
64
- } catch {
65
- return false;
66
- }
67
- }
68
-
69
- export { MAX_PROPERTY_LENGTH, RybbitAnalytics, sanitizeWebsiteEventProperties, trackWebsiteEvent };
1
+ export { RybbitAnalytics } from '../chunk-NZANQGDM.mjs';
2
+ export { MAX_PROPERTY_LENGTH, sanitizeWebsiteEventProperties, trackWebsiteEvent } from '../chunk-6YZUFMK2.mjs';
3
+ import '../chunk-VNS5WDWO.mjs';
70
4
  //# sourceMappingURL=index.mjs.map
71
5
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/analytics/rybbit.tsx","../../src/analytics/events.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Rybbit Analytics Component\n *\n * Injects the Rybbit tracking script into the page head.\n * Works with both Vite (CSR) and Next.js (SSR/CSR) applications.\n *\n * @example\n * ```tsx\n * // In your app layout or root component\n * import { RybbitAnalytics } from '@burdenoff/website-sdk/analytics'\n *\n * function App() {\n * return (\n * <>\n * <RybbitAnalytics siteId=\"1\" />\n * <Router>...</Router>\n * </>\n * )\n * }\n * ```\n */\n\"use client\";\n\nimport { useEffect } from \"react\";\n\nexport interface RybbitAnalyticsProps {\n /**\n * Rybbit site ID (obtained from the Rybbit dashboard)\n */\n siteId: string;\n\n /**\n * Rybbit script URL (defaults to the self-hosted instance)\n * @default \"https://analytics.burdenoff.com/api/script.js\"\n */\n scriptUrl?: string;\n\n /**\n * Whether to enable tracking (useful for disabling in development)\n * @default true\n */\n enabled?: boolean;\n}\n\n/**\n * RybbitAnalytics - Embeds the Rybbit analytics tracking script.\n *\n * The script is loaded asynchronously with `defer` to avoid blocking page load.\n * It automatically tracks page views and user interactions.\n *\n * In development mode (when `enabled` is false), no script is loaded.\n */\nexport function RybbitAnalytics({\n siteId,\n scriptUrl = \"https://analytics.burdenoff.com/api/script.js\",\n enabled = true,\n}: RybbitAnalyticsProps) {\n useEffect(() => {\n if (!enabled || !siteId) return;\n\n // Check if script already exists\n const existingScript = document.querySelector(\n `script[data-site-id=\"${siteId}\"]`,\n );\n if (existingScript) return;\n\n // Create and inject the script. `crossOrigin=\"anonymous\"` puts the\n // request in CORS-no-credentials mode so the browser does not include\n // any third-party cookies the visitor may have on the analytics origin\n // (e.g. `app.rybbit.io`'s own dashboard session cookie). Without this,\n // Lighthouse `third-party-cookies` audit flags the page even though we\n // never read those cookies ourselves.\n const script = document.createElement(\"script\");\n script.src = scriptUrl;\n script.defer = true;\n script.crossOrigin = \"anonymous\";\n script.setAttribute(\"data-site-id\", siteId);\n document.head.appendChild(script);\n\n // Cleanup on unmount\n return () => {\n const scriptToRemove = document.querySelector(\n `script[data-site-id=\"${siteId}\"]`,\n );\n if (scriptToRemove) {\n document.head.removeChild(scriptToRemove);\n }\n };\n }, [siteId, scriptUrl, enabled]);\n\n // This component renders nothing - it only manages the script tag\n return null;\n}\n","/**\n * Website analytics events (Rybbit custom events).\n *\n * Product websites load the Rybbit script, which records pageviews on its own.\n * Funnel facts that are not a pageview (for example a successful waitlist\n * signup) are sent through `trackWebsiteEvent`, which calls `window.rybbit.event`\n * when the script is present and does nothing otherwise.\n *\n * Privacy rules (BOFF-3167):\n * - Event names come from the closed `WebsiteEventName` union.\n * - Properties are short, low-cardinality strings, numbers or booleans. Strings\n * longer than `MAX_PROPERTY_LENGTH`, values that look like an email address,\n * and any other value type are dropped, so a caller cannot leak an email,\n * name or free text into the analytics provider by mistake.\n * - Tracking never throws: analytics must not break a signup.\n */\n\n/** Custom events product websites emit. */\nexport type WebsiteEventName = \"waitlist.signed_up\";\n\nexport type WebsiteEventPropertyValue = string | number | boolean;\n\nexport type WebsiteEventProperties = Record<string, WebsiteEventPropertyValue>;\n\ndeclare global {\n interface Window {\n /** Set by the Rybbit tracking script (https://app.rybbit.io/api/script.js). */\n rybbit?: {\n event?: (\n name: string,\n properties?: Record<string, WebsiteEventPropertyValue>,\n ) => void;\n };\n }\n}\n\n/** Longest string property value that is forwarded. */\nexport const MAX_PROPERTY_LENGTH = 64;\n\nconst EMAIL_LIKE = /[^\\s@]+@[^\\s@]+/;\n\n/**\n * Keeps only short, non-identifying primitive values. Exported for tests.\n */\nexport function sanitizeWebsiteEventProperties(\n properties: WebsiteEventProperties | undefined,\n): WebsiteEventProperties {\n const safe: WebsiteEventProperties = {};\n if (!properties) return safe;\n for (const [key, value] of Object.entries(properties)) {\n if (typeof value === \"boolean\") {\n safe[key] = value;\n } else if (typeof value === \"number\") {\n if (Number.isFinite(value)) safe[key] = value;\n } else if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (\n trimmed.length > 0 &&\n trimmed.length <= MAX_PROPERTY_LENGTH &&\n !EMAIL_LIKE.test(trimmed)\n ) {\n safe[key] = trimmed;\n }\n }\n }\n return safe;\n}\n\n/**\n * Sends a custom event to Rybbit when the tracking script is loaded.\n * Returns true when the event was handed to Rybbit.\n */\nexport function trackWebsiteEvent(\n name: WebsiteEventName,\n properties?: WebsiteEventProperties,\n): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n const rybbit = window.rybbit;\n if (typeof rybbit?.event !== \"function\") return false;\n const safe = sanitizeWebsiteEventProperties(properties);\n if (Object.keys(safe).length > 0) {\n rybbit.event(name, safe);\n } else {\n rybbit.event(name);\n }\n return true;\n } catch {\n // Analytics is best-effort and must never break the page.\n return false;\n }\n}\n"]}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs"}
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ require('../chunk-ZSTFX7FV.js');
3
4
  var livekitClient = require('livekit-client');
4
5
 
5
6
  var CLOSED_BY_SERVER = /* @__PURE__ */ new Set([
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/avatar-live/receive-only.ts"],"names":["DisconnectReason","Track","Room","RoomEvent"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * BOFF-9797. The live avatar's room, joined to watch and listen only.\n *\n * A visitor's browser on a product website holds a subscribe-only room token: it can receive the avatar worker's\n * video and voice and do nothing else. This adapter keeps to that from its side too:\n *\n * - **Nothing is subscribed automatically** (`autoSubscribe: false`). Only the tracks of `workerIdentity`, the avatar\n * worker the create answer named, are subscribed, and only their video and audio. Any other participant's track\n * (the server's own speaker included) is never asked for, and is dropped again should the server send it anyway.\n * - **Nothing is published.** No microphone, no camera, no data, no RPC method: this file never calls a publishing\n * API, and the token would refuse one.\n * - **Safari and iOS** hold sound back until a gesture. The room reports it (`playback-blocked`) and the panel offers\n * \"Tap to turn on sound\", whose tap calls `enablePlayback`.\n *\n * Written for the website from the media library's public API: nothing here is copied from the product apps.\n */\nimport {\n DisconnectReason,\n Room,\n RoomEvent,\n Track,\n type RemoteParticipant,\n type RemoteTrack,\n type RemoteTrackPublication,\n} from \"livekit-client\";\n\nimport type { ReceiveOnlyJoinOptions, ReceiveOnlySession } from \"./types\";\n\n/** The reasons a room closes because the server closed it, rather than because the connection was lost. */\nconst CLOSED_BY_SERVER: ReadonlySet<DisconnectReason> = new Set([\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.ROOM_CLOSED,\n DisconnectReason.PARTICIPANT_REMOVED,\n]);\n\nfunction isMedia(publication: RemoteTrackPublication): boolean {\n return (\n publication.kind === Track.Kind.Video ||\n publication.kind === Track.Kind.Audio\n );\n}\n\n/**\n * Join the room in `options.url` with the receive-only `options.token`, and play the avatar worker's video and voice\n * in `options.video` and `options.audio`. Resolves once connected; rejects when the room cannot be joined (the panel\n * then ends the session).\n */\nexport async function joinReceiveOnly(\n options: ReceiveOnlyJoinOptions,\n): Promise<ReceiveOnlySession> {\n const { url, token, workerIdentity, video, audio, onEvent } = options;\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(\"live avatar: the room address is not a URL\");\n }\n if (parsed.protocol !== \"wss:\") {\n throw new Error(\"live avatar: the room address must be wss:\");\n }\n if (!token || !workerIdentity) {\n throw new Error(\"live avatar: the room token or speaker is missing\");\n }\n\n const room = new Room({\n // One small video, always shown while the session lasts: no quality switching, no layer pausing.\n adaptiveStream: false,\n dynacast: false,\n // Closing the tab or leaving the page leaves the room at once. Belt and braces with the panel's own page-hide\n // handler, which also asks the server to end the session: either may run first, and both are safe to repeat.\n disconnectOnPageLeave: true,\n });\n let leaving = false;\n\n const isSpeaker = (participant: RemoteParticipant) =>\n participant.identity === workerIdentity;\n\n /** Subscribe to `publication` when it is the speaker's video or audio; leave everything else alone. */\n const consider = (\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving || !isSpeaker(participant) || !isMedia(publication)) return;\n if (!publication.isSubscribed) publication.setSubscribed(true);\n };\n\n const reportPlayback = () => {\n if (leaving) return;\n onEvent({\n type: \"playback-blocked\",\n blocked: !room.canPlaybackAudio || !room.canPlaybackVideo,\n });\n };\n\n room\n .on(RoomEvent.TrackPublished, consider)\n .on(\n RoomEvent.TrackSubscribed,\n (\n track: RemoteTrack,\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving) return;\n if (!isSpeaker(participant) || !isMedia(publication)) {\n // Never asked for: drop it rather than play it.\n publication.setSubscribed(false);\n return;\n }\n if (track.kind === Track.Kind.Video) {\n track.attach(video);\n onEvent({ type: \"speaker-track\", kind: \"video\" });\n } else {\n track.attach(audio);\n onEvent({ type: \"speaker-track\", kind: \"audio\" });\n }\n },\n )\n .on(RoomEvent.TrackUnsubscribed, (track: RemoteTrack) => {\n track.detach();\n })\n .on(RoomEvent.ParticipantDisconnected, (participant: RemoteParticipant) => {\n if (!leaving && isSpeaker(participant)) {\n onEvent({ type: \"speaker-left\" });\n }\n })\n .on(RoomEvent.AudioPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.VideoPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.Disconnected, (reason?: DisconnectReason) => {\n if (leaving) return;\n onEvent({\n type: \"disconnected\",\n byServer: reason !== undefined && CLOSED_BY_SERVER.has(reason),\n });\n });\n\n const detachAll = () => {\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n publication.track?.detach();\n }\n }\n video.srcObject = null;\n audio.srcObject = null;\n };\n\n try {\n await room.connect(url, token, { autoSubscribe: false });\n } catch (error) {\n leaving = true;\n room.removeAllListeners();\n detachAll();\n throw error;\n }\n\n // Whoever was in the room before this viewer joined: their tracks were published before the listener above.\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n consider(publication, participant);\n }\n }\n reportPlayback();\n\n return {\n enablePlayback: async () => {\n // Both started inside the visitor's gesture, before anything is awaited: Safari allows playback only there.\n // A refusal leaves the room as it was, and the report below keeps the button offered.\n await Promise.allSettled([room.startAudio(), room.startVideo()]);\n reportPlayback();\n },\n leave: async () => {\n if (leaving) return;\n leaving = true;\n detachAll();\n try {\n await room.disconnect();\n } finally {\n room.removeAllListeners();\n }\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/avatar-live/receive-only.ts"],"names":["DisconnectReason","Track","Room","RoomEvent"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * BOFF-9797. The live avatar's room, joined to watch and listen only.\n *\n * A visitor's browser on a product website holds a subscribe-only room token: it can receive the avatar worker's\n * video and voice and do nothing else. This adapter keeps to that from its side too:\n *\n * - **Nothing is subscribed automatically** (`autoSubscribe: false`). Only the tracks of `workerIdentity`, the avatar\n * worker the create answer named, are subscribed, and only their video and audio. Any other participant's track\n * (the server's own speaker included) is never asked for, and is dropped again should the server send it anyway.\n * - **Nothing is published.** No microphone, no camera, no data, no RPC method: this file never calls a publishing\n * API, and the token would refuse one.\n * - **Safari and iOS** hold sound back until a gesture. The room reports it (`playback-blocked`) and the panel offers\n * \"Tap to turn on sound\", whose tap calls `enablePlayback`.\n *\n * Written for the website from the media library's public API: nothing here is copied from the product apps.\n */\nimport {\n DisconnectReason,\n Room,\n RoomEvent,\n Track,\n type RemoteParticipant,\n type RemoteTrack,\n type RemoteTrackPublication,\n} from \"livekit-client\";\n\nimport type { ReceiveOnlyJoinOptions, ReceiveOnlySession } from \"./types\";\n\n/** The reasons a room closes because the server closed it, rather than because the connection was lost. */\nconst CLOSED_BY_SERVER: ReadonlySet<DisconnectReason> = new Set([\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.ROOM_CLOSED,\n DisconnectReason.PARTICIPANT_REMOVED,\n]);\n\nfunction isMedia(publication: RemoteTrackPublication): boolean {\n return (\n publication.kind === Track.Kind.Video ||\n publication.kind === Track.Kind.Audio\n );\n}\n\n/**\n * Join the room in `options.url` with the receive-only `options.token`, and play the avatar worker's video and voice\n * in `options.video` and `options.audio`. Resolves once connected; rejects when the room cannot be joined (the panel\n * then ends the session).\n */\nexport async function joinReceiveOnly(\n options: ReceiveOnlyJoinOptions,\n): Promise<ReceiveOnlySession> {\n const { url, token, workerIdentity, video, audio, onEvent } = options;\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(\"live avatar: the room address is not a URL\");\n }\n if (parsed.protocol !== \"wss:\") {\n throw new Error(\"live avatar: the room address must be wss:\");\n }\n if (!token || !workerIdentity) {\n throw new Error(\"live avatar: the room token or speaker is missing\");\n }\n\n const room = new Room({\n // One small video, always shown while the session lasts: no quality switching, no layer pausing.\n adaptiveStream: false,\n dynacast: false,\n // Closing the tab or leaving the page leaves the room at once. Belt and braces with the panel's own page-hide\n // handler, which also asks the server to end the session: either may run first, and both are safe to repeat.\n disconnectOnPageLeave: true,\n });\n let leaving = false;\n\n const isSpeaker = (participant: RemoteParticipant) =>\n participant.identity === workerIdentity;\n\n /** Subscribe to `publication` when it is the speaker's video or audio; leave everything else alone. */\n const consider = (\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving || !isSpeaker(participant) || !isMedia(publication)) return;\n if (!publication.isSubscribed) publication.setSubscribed(true);\n };\n\n const reportPlayback = () => {\n if (leaving) return;\n onEvent({\n type: \"playback-blocked\",\n blocked: !room.canPlaybackAudio || !room.canPlaybackVideo,\n });\n };\n\n room\n .on(RoomEvent.TrackPublished, consider)\n .on(\n RoomEvent.TrackSubscribed,\n (\n track: RemoteTrack,\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving) return;\n if (!isSpeaker(participant) || !isMedia(publication)) {\n // Never asked for: drop it rather than play it.\n publication.setSubscribed(false);\n return;\n }\n if (track.kind === Track.Kind.Video) {\n track.attach(video);\n onEvent({ type: \"speaker-track\", kind: \"video\" });\n } else {\n track.attach(audio);\n onEvent({ type: \"speaker-track\", kind: \"audio\" });\n }\n },\n )\n .on(RoomEvent.TrackUnsubscribed, (track: RemoteTrack) => {\n track.detach();\n })\n .on(RoomEvent.ParticipantDisconnected, (participant: RemoteParticipant) => {\n if (!leaving && isSpeaker(participant)) {\n onEvent({ type: \"speaker-left\" });\n }\n })\n .on(RoomEvent.AudioPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.VideoPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.Disconnected, (reason?: DisconnectReason) => {\n if (leaving) return;\n onEvent({\n type: \"disconnected\",\n byServer: reason !== undefined && CLOSED_BY_SERVER.has(reason),\n });\n });\n\n const detachAll = () => {\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n publication.track?.detach();\n }\n }\n video.srcObject = null;\n audio.srcObject = null;\n };\n\n try {\n await room.connect(url, token, { autoSubscribe: false });\n } catch (error) {\n leaving = true;\n room.removeAllListeners();\n detachAll();\n throw error;\n }\n\n // Whoever was in the room before this viewer joined: their tracks were published before the listener above.\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n consider(publication, participant);\n }\n }\n reportPlayback();\n\n return {\n enablePlayback: async () => {\n // Both started inside the visitor's gesture, before anything is awaited: Safari allows playback only there.\n // A refusal leaves the room as it was, and the report below keeps the button offered.\n await Promise.allSettled([room.startAudio(), room.startVideo()]);\n reportPlayback();\n },\n leave: async () => {\n if (leaving) return;\n leaving = true;\n detachAll();\n try {\n await room.disconnect();\n } finally {\n room.removeAllListeners();\n }\n },\n };\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import '../chunk-VNS5WDWO.mjs';
1
2
  import { DisconnectReason, Room, RoomEvent, Track } from 'livekit-client';
2
3
 
3
4
  var CLOSED_BY_SERVER = /* @__PURE__ */ new Set([
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/avatar-live/receive-only.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * BOFF-9797. The live avatar's room, joined to watch and listen only.\n *\n * A visitor's browser on a product website holds a subscribe-only room token: it can receive the avatar worker's\n * video and voice and do nothing else. This adapter keeps to that from its side too:\n *\n * - **Nothing is subscribed automatically** (`autoSubscribe: false`). Only the tracks of `workerIdentity`, the avatar\n * worker the create answer named, are subscribed, and only their video and audio. Any other participant's track\n * (the server's own speaker included) is never asked for, and is dropped again should the server send it anyway.\n * - **Nothing is published.** No microphone, no camera, no data, no RPC method: this file never calls a publishing\n * API, and the token would refuse one.\n * - **Safari and iOS** hold sound back until a gesture. The room reports it (`playback-blocked`) and the panel offers\n * \"Tap to turn on sound\", whose tap calls `enablePlayback`.\n *\n * Written for the website from the media library's public API: nothing here is copied from the product apps.\n */\nimport {\n DisconnectReason,\n Room,\n RoomEvent,\n Track,\n type RemoteParticipant,\n type RemoteTrack,\n type RemoteTrackPublication,\n} from \"livekit-client\";\n\nimport type { ReceiveOnlyJoinOptions, ReceiveOnlySession } from \"./types\";\n\n/** The reasons a room closes because the server closed it, rather than because the connection was lost. */\nconst CLOSED_BY_SERVER: ReadonlySet<DisconnectReason> = new Set([\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.ROOM_CLOSED,\n DisconnectReason.PARTICIPANT_REMOVED,\n]);\n\nfunction isMedia(publication: RemoteTrackPublication): boolean {\n return (\n publication.kind === Track.Kind.Video ||\n publication.kind === Track.Kind.Audio\n );\n}\n\n/**\n * Join the room in `options.url` with the receive-only `options.token`, and play the avatar worker's video and voice\n * in `options.video` and `options.audio`. Resolves once connected; rejects when the room cannot be joined (the panel\n * then ends the session).\n */\nexport async function joinReceiveOnly(\n options: ReceiveOnlyJoinOptions,\n): Promise<ReceiveOnlySession> {\n const { url, token, workerIdentity, video, audio, onEvent } = options;\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(\"live avatar: the room address is not a URL\");\n }\n if (parsed.protocol !== \"wss:\") {\n throw new Error(\"live avatar: the room address must be wss:\");\n }\n if (!token || !workerIdentity) {\n throw new Error(\"live avatar: the room token or speaker is missing\");\n }\n\n const room = new Room({\n // One small video, always shown while the session lasts: no quality switching, no layer pausing.\n adaptiveStream: false,\n dynacast: false,\n // Closing the tab or leaving the page leaves the room at once. Belt and braces with the panel's own page-hide\n // handler, which also asks the server to end the session: either may run first, and both are safe to repeat.\n disconnectOnPageLeave: true,\n });\n let leaving = false;\n\n const isSpeaker = (participant: RemoteParticipant) =>\n participant.identity === workerIdentity;\n\n /** Subscribe to `publication` when it is the speaker's video or audio; leave everything else alone. */\n const consider = (\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving || !isSpeaker(participant) || !isMedia(publication)) return;\n if (!publication.isSubscribed) publication.setSubscribed(true);\n };\n\n const reportPlayback = () => {\n if (leaving) return;\n onEvent({\n type: \"playback-blocked\",\n blocked: !room.canPlaybackAudio || !room.canPlaybackVideo,\n });\n };\n\n room\n .on(RoomEvent.TrackPublished, consider)\n .on(\n RoomEvent.TrackSubscribed,\n (\n track: RemoteTrack,\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving) return;\n if (!isSpeaker(participant) || !isMedia(publication)) {\n // Never asked for: drop it rather than play it.\n publication.setSubscribed(false);\n return;\n }\n if (track.kind === Track.Kind.Video) {\n track.attach(video);\n onEvent({ type: \"speaker-track\", kind: \"video\" });\n } else {\n track.attach(audio);\n onEvent({ type: \"speaker-track\", kind: \"audio\" });\n }\n },\n )\n .on(RoomEvent.TrackUnsubscribed, (track: RemoteTrack) => {\n track.detach();\n })\n .on(RoomEvent.ParticipantDisconnected, (participant: RemoteParticipant) => {\n if (!leaving && isSpeaker(participant)) {\n onEvent({ type: \"speaker-left\" });\n }\n })\n .on(RoomEvent.AudioPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.VideoPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.Disconnected, (reason?: DisconnectReason) => {\n if (leaving) return;\n onEvent({\n type: \"disconnected\",\n byServer: reason !== undefined && CLOSED_BY_SERVER.has(reason),\n });\n });\n\n const detachAll = () => {\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n publication.track?.detach();\n }\n }\n video.srcObject = null;\n audio.srcObject = null;\n };\n\n try {\n await room.connect(url, token, { autoSubscribe: false });\n } catch (error) {\n leaving = true;\n room.removeAllListeners();\n detachAll();\n throw error;\n }\n\n // Whoever was in the room before this viewer joined: their tracks were published before the listener above.\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n consider(publication, participant);\n }\n }\n reportPlayback();\n\n return {\n enablePlayback: async () => {\n // Both started inside the visitor's gesture, before anything is awaited: Safari allows playback only there.\n // A refusal leaves the room as it was, and the report below keeps the button offered.\n await Promise.allSettled([room.startAudio(), room.startVideo()]);\n reportPlayback();\n },\n leave: async () => {\n if (leaving) return;\n leaving = true;\n detachAll();\n try {\n await room.disconnect();\n } finally {\n room.removeAllListeners();\n }\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/avatar-live/receive-only.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * BOFF-9797. The live avatar's room, joined to watch and listen only.\n *\n * A visitor's browser on a product website holds a subscribe-only room token: it can receive the avatar worker's\n * video and voice and do nothing else. This adapter keeps to that from its side too:\n *\n * - **Nothing is subscribed automatically** (`autoSubscribe: false`). Only the tracks of `workerIdentity`, the avatar\n * worker the create answer named, are subscribed, and only their video and audio. Any other participant's track\n * (the server's own speaker included) is never asked for, and is dropped again should the server send it anyway.\n * - **Nothing is published.** No microphone, no camera, no data, no RPC method: this file never calls a publishing\n * API, and the token would refuse one.\n * - **Safari and iOS** hold sound back until a gesture. The room reports it (`playback-blocked`) and the panel offers\n * \"Tap to turn on sound\", whose tap calls `enablePlayback`.\n *\n * Written for the website from the media library's public API: nothing here is copied from the product apps.\n */\nimport {\n DisconnectReason,\n Room,\n RoomEvent,\n Track,\n type RemoteParticipant,\n type RemoteTrack,\n type RemoteTrackPublication,\n} from \"livekit-client\";\n\nimport type { ReceiveOnlyJoinOptions, ReceiveOnlySession } from \"./types\";\n\n/** The reasons a room closes because the server closed it, rather than because the connection was lost. */\nconst CLOSED_BY_SERVER: ReadonlySet<DisconnectReason> = new Set([\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.ROOM_CLOSED,\n DisconnectReason.PARTICIPANT_REMOVED,\n]);\n\nfunction isMedia(publication: RemoteTrackPublication): boolean {\n return (\n publication.kind === Track.Kind.Video ||\n publication.kind === Track.Kind.Audio\n );\n}\n\n/**\n * Join the room in `options.url` with the receive-only `options.token`, and play the avatar worker's video and voice\n * in `options.video` and `options.audio`. Resolves once connected; rejects when the room cannot be joined (the panel\n * then ends the session).\n */\nexport async function joinReceiveOnly(\n options: ReceiveOnlyJoinOptions,\n): Promise<ReceiveOnlySession> {\n const { url, token, workerIdentity, video, audio, onEvent } = options;\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(\"live avatar: the room address is not a URL\");\n }\n if (parsed.protocol !== \"wss:\") {\n throw new Error(\"live avatar: the room address must be wss:\");\n }\n if (!token || !workerIdentity) {\n throw new Error(\"live avatar: the room token or speaker is missing\");\n }\n\n const room = new Room({\n // One small video, always shown while the session lasts: no quality switching, no layer pausing.\n adaptiveStream: false,\n dynacast: false,\n // Closing the tab or leaving the page leaves the room at once. Belt and braces with the panel's own page-hide\n // handler, which also asks the server to end the session: either may run first, and both are safe to repeat.\n disconnectOnPageLeave: true,\n });\n let leaving = false;\n\n const isSpeaker = (participant: RemoteParticipant) =>\n participant.identity === workerIdentity;\n\n /** Subscribe to `publication` when it is the speaker's video or audio; leave everything else alone. */\n const consider = (\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving || !isSpeaker(participant) || !isMedia(publication)) return;\n if (!publication.isSubscribed) publication.setSubscribed(true);\n };\n\n const reportPlayback = () => {\n if (leaving) return;\n onEvent({\n type: \"playback-blocked\",\n blocked: !room.canPlaybackAudio || !room.canPlaybackVideo,\n });\n };\n\n room\n .on(RoomEvent.TrackPublished, consider)\n .on(\n RoomEvent.TrackSubscribed,\n (\n track: RemoteTrack,\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving) return;\n if (!isSpeaker(participant) || !isMedia(publication)) {\n // Never asked for: drop it rather than play it.\n publication.setSubscribed(false);\n return;\n }\n if (track.kind === Track.Kind.Video) {\n track.attach(video);\n onEvent({ type: \"speaker-track\", kind: \"video\" });\n } else {\n track.attach(audio);\n onEvent({ type: \"speaker-track\", kind: \"audio\" });\n }\n },\n )\n .on(RoomEvent.TrackUnsubscribed, (track: RemoteTrack) => {\n track.detach();\n })\n .on(RoomEvent.ParticipantDisconnected, (participant: RemoteParticipant) => {\n if (!leaving && isSpeaker(participant)) {\n onEvent({ type: \"speaker-left\" });\n }\n })\n .on(RoomEvent.AudioPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.VideoPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.Disconnected, (reason?: DisconnectReason) => {\n if (leaving) return;\n onEvent({\n type: \"disconnected\",\n byServer: reason !== undefined && CLOSED_BY_SERVER.has(reason),\n });\n });\n\n const detachAll = () => {\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n publication.track?.detach();\n }\n }\n video.srcObject = null;\n audio.srcObject = null;\n };\n\n try {\n await room.connect(url, token, { autoSubscribe: false });\n } catch (error) {\n leaving = true;\n room.removeAllListeners();\n detachAll();\n throw error;\n }\n\n // Whoever was in the room before this viewer joined: their tracks were published before the listener above.\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n consider(publication, participant);\n }\n }\n reportPlayback();\n\n return {\n enablePlayback: async () => {\n // Both started inside the visitor's gesture, before anything is awaited: Safari allows playback only there.\n // A refusal leaves the room as it was, and the report below keeps the button offered.\n await Promise.allSettled([room.startAudio(), room.startVideo()]);\n reportPlayback();\n },\n leave: async () => {\n if (leaving) return;\n leaving = true;\n detachAll();\n try {\n await room.disconnect();\n } finally {\n room.removeAllListeners();\n }\n },\n };\n}\n"]}
@@ -1,24 +1,12 @@
1
1
  'use strict';
2
2
 
3
+ var chunk23H4JUL4_js = require('../chunk-23H4JUL4.js');
4
+ var chunkO4GS3XPZ_js = require('../chunk-O4GS3XPZ.js');
5
+ var chunkC6OJIHNO_js = require('../chunk-C6OJIHNO.js');
6
+ require('../chunk-ZSTFX7FV.js');
3
7
  var react = require('react');
4
- var clsx = require('clsx');
5
- var tailwindMerge = require('tailwind-merge');
6
8
  var jsxRuntime = require('react/jsx-runtime');
7
9
 
8
- // src/components/explore-avatar-live-config.ts
9
- var SITE = /^[a-z][a-z0-9-]{1,31}$/;
10
- var VERSION = /^[A-Za-z0-9._-]{1,32}$/;
11
- function isAvatarSite(value) {
12
- return SITE.test(value);
13
- }
14
- function isAvatarVersion(value) {
15
- return VERSION.test(value);
16
- }
17
- var REVIEWED = /* @__PURE__ */ new Set(["en"]);
18
- function isAvatarLiveLocaleReviewed(locale) {
19
- return REVIEWED.has(locale);
20
- }
21
-
22
10
  // src/components/explore-avatar-api.ts
23
11
  var AVAILABILITY_TIMEOUT_MS = 8e3;
24
12
  var CREATE_TIMEOUT_MS = 3e4;
@@ -55,7 +43,7 @@ function readAvailability(status, body) {
55
43
  }
56
44
  if (reason === "ok") {
57
45
  const { consentVersion, maxSessionSeconds } = body;
58
- if (available !== true || typeof consentVersion !== "string" || !isAvatarVersion(consentVersion) || typeof maxSessionSeconds !== "number" || !Number.isInteger(maxSessionSeconds) || maxSessionSeconds < 1) {
46
+ if (available !== true || typeof consentVersion !== "string" || !chunk23H4JUL4_js.isAvatarVersion(consentVersion) || typeof maxSessionSeconds !== "number" || !Number.isInteger(maxSessionSeconds) || maxSessionSeconds < 1) {
59
47
  return INERT;
60
48
  }
61
49
  return { kind: "available", offer: { consentVersion, maxSessionSeconds } };
@@ -140,7 +128,7 @@ function deadline(timeoutMs, outer) {
140
128
  };
141
129
  }
142
130
  async function requestAvailability(target) {
143
- if (!isAvatarSite(target.site) || !LOCALE.test(target.locale)) return INERT;
131
+ if (!chunk23H4JUL4_js.isAvatarSite(target.site) || !LOCALE.test(target.locale)) return INERT;
144
132
  const query = new URLSearchParams({
145
133
  site: target.site,
146
134
  locale: target.locale
@@ -489,7 +477,7 @@ var CONSENT = /* @__PURE__ */ new Map([
489
477
  ]
490
478
  ]);
491
479
  function avatarConsentCopy(version, locale) {
492
- if (!isAvatarLiveLocaleReviewed(locale)) return null;
480
+ if (!chunk23H4JUL4_js.isAvatarLiveLocaleReviewed(locale)) return null;
493
481
  return CONSENT.get(version)?.[locale] ?? null;
494
482
  }
495
483
 
@@ -607,14 +595,6 @@ function runTurnstile(container, siteKey, action, cData) {
607
595
  }
608
596
  };
609
597
  }
610
-
611
- // src/components/explore-fill.ts
612
- function fill(template, values) {
613
- return template.replace(/\{(\w+)\}/g, (match, key) => values[key] ?? match);
614
- }
615
- function cn(...inputs) {
616
- return tailwindMerge.twMerge(clsx.clsx(inputs));
617
- }
618
598
  var PRIVACY_HREF = "/privacy#live-avatar";
619
599
  var SPEAKER_WAIT_MS = 3e4;
620
600
  var END_GRACE_MS = 1e4;
@@ -1187,7 +1167,7 @@ function AvatarLiveView({
1187
1167
  decoding: "async",
1188
1168
  width: small ? 64 : 128,
1189
1169
  height: small ? 64 : 128,
1190
- className: cn(
1170
+ className: chunkC6OJIHNO_js.cn(
1191
1171
  "shrink-0 rounded-full border border-border object-cover shadow-sm",
1192
1172
  small ? "h-16 w-16" : "h-32 w-32"
1193
1173
  )
@@ -1249,7 +1229,7 @@ function AvatarLiveView({
1249
1229
  className: "w-full max-w-sm rounded-lg border border-border p-4 text-start",
1250
1230
  children: [
1251
1231
  /* @__PURE__ */ jsxRuntime.jsx("h2", { id: consentTitleId, className: "text-base font-semibold", children: copy.title }),
1252
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm", children: productName === null ? copy.intro : fill(copy.introProduct, { product: productName }) }),
1232
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm", children: productName === null ? copy.intro : chunkO4GS3XPZ_js.fill(copy.introProduct, { product: productName }) }),
1253
1233
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: copy.processing }),
1254
1234
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: copy.storage }),
1255
1235
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -1327,7 +1307,7 @@ function AvatarLiveView({
1327
1307
  autoPlay: true,
1328
1308
  muted: true,
1329
1309
  onPlaying,
1330
- className: cn(
1310
+ className: chunkC6OJIHNO_js.cn(
1331
1311
  "h-full w-full object-cover",
1332
1312
  !playing && "opacity-0"
1333
1313
  )