@useragent-kit/sdk 0.0.0 → 0.0.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.
Files changed (352) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +61 -3
  3. package/dist/adapters.d.ts +94 -0
  4. package/dist/adapters.d.ts.map +1 -0
  5. package/dist/adapters.js +188 -0
  6. package/dist/adapters.js.map +1 -0
  7. package/dist/api-protocol.d.ts +9 -0
  8. package/dist/api-protocol.d.ts.map +1 -0
  9. package/dist/api-protocol.js +9 -0
  10. package/dist/api-protocol.js.map +1 -0
  11. package/dist/bridge/contract.d.ts +66 -0
  12. package/dist/bridge/contract.d.ts.map +1 -0
  13. package/dist/bridge/contract.js +160 -0
  14. package/dist/bridge/contract.js.map +1 -0
  15. package/dist/bridge/index.d.ts +53 -0
  16. package/dist/bridge/index.d.ts.map +1 -0
  17. package/dist/bridge/index.js +302 -0
  18. package/dist/bridge/index.js.map +1 -0
  19. package/dist/bridge/types.d.ts +93 -0
  20. package/dist/bridge/types.d.ts.map +1 -0
  21. package/dist/bridge/types.js +9 -0
  22. package/dist/bridge/types.js.map +1 -0
  23. package/dist/chain.d.ts +198 -0
  24. package/dist/chain.d.ts.map +1 -0
  25. package/dist/chain.js +424 -0
  26. package/dist/chain.js.map +1 -0
  27. package/dist/errors.d.ts +22 -0
  28. package/dist/errors.d.ts.map +1 -0
  29. package/dist/errors.js +41 -0
  30. package/dist/errors.js.map +1 -0
  31. package/dist/events.d.ts +252 -0
  32. package/dist/events.d.ts.map +1 -0
  33. package/dist/events.js +54 -0
  34. package/dist/events.js.map +1 -0
  35. package/dist/experimental/file-transfer.d.ts +97 -0
  36. package/dist/experimental/file-transfer.d.ts.map +1 -0
  37. package/dist/experimental/file-transfer.js +1377 -0
  38. package/dist/experimental/file-transfer.js.map +1 -0
  39. package/dist/experimental/index.d.ts +7 -0
  40. package/dist/experimental/index.d.ts.map +1 -0
  41. package/dist/experimental/index.js +4 -0
  42. package/dist/experimental/index.js.map +1 -0
  43. package/dist/experimental/logger.d.ts +5 -0
  44. package/dist/experimental/logger.d.ts.map +1 -0
  45. package/dist/experimental/logger.js +74 -0
  46. package/dist/experimental/logger.js.map +1 -0
  47. package/dist/experimental/transport.d.ts +34 -0
  48. package/dist/experimental/transport.d.ts.map +1 -0
  49. package/dist/experimental/transport.js +43 -0
  50. package/dist/experimental/transport.js.map +1 -0
  51. package/dist/guest/claw.d.ts +10 -0
  52. package/dist/guest/claw.d.ts.map +1 -0
  53. package/dist/guest/claw.js +21 -0
  54. package/dist/guest/claw.js.map +1 -0
  55. package/dist/guest/crdt.d.ts +19 -0
  56. package/dist/guest/crdt.d.ts.map +1 -0
  57. package/dist/guest/crdt.js +64 -0
  58. package/dist/guest/crdt.js.map +1 -0
  59. package/dist/guest/data.d.ts +27 -0
  60. package/dist/guest/data.d.ts.map +1 -0
  61. package/dist/guest/data.js +61 -0
  62. package/dist/guest/data.js.map +1 -0
  63. package/dist/guest/files.d.ts +15 -0
  64. package/dist/guest/files.d.ts.map +1 -0
  65. package/dist/guest/files.js +24 -0
  66. package/dist/guest/files.js.map +1 -0
  67. package/dist/guest/index.d.ts +43 -0
  68. package/dist/guest/index.d.ts.map +1 -0
  69. package/dist/guest/index.js +44 -0
  70. package/dist/guest/index.js.map +1 -0
  71. package/dist/guest/media.d.ts +44 -0
  72. package/dist/guest/media.d.ts.map +1 -0
  73. package/dist/guest/media.js +97 -0
  74. package/dist/guest/media.js.map +1 -0
  75. package/dist/guest/mesh.d.ts +79 -0
  76. package/dist/guest/mesh.d.ts.map +1 -0
  77. package/dist/guest/mesh.js +153 -0
  78. package/dist/guest/mesh.js.map +1 -0
  79. package/dist/guest/navigate.d.ts +17 -0
  80. package/dist/guest/navigate.d.ts.map +1 -0
  81. package/dist/guest/navigate.js +169 -0
  82. package/dist/guest/navigate.js.map +1 -0
  83. package/dist/guest/statements.d.ts +18 -0
  84. package/dist/guest/statements.d.ts.map +1 -0
  85. package/dist/guest/statements.js +26 -0
  86. package/dist/guest/statements.js.map +1 -0
  87. package/dist/guest/telemetry.d.ts +88 -0
  88. package/dist/guest/telemetry.d.ts.map +1 -0
  89. package/dist/guest/telemetry.js +184 -0
  90. package/dist/guest/telemetry.js.map +1 -0
  91. package/dist/guest/types.d.ts +255 -0
  92. package/dist/guest/types.d.ts.map +1 -0
  93. package/dist/guest/types.js +9 -0
  94. package/dist/guest/types.js.map +1 -0
  95. package/dist/host-runtime-contract.d.ts +19 -0
  96. package/dist/host-runtime-contract.d.ts.map +1 -0
  97. package/dist/host-runtime-contract.js +80 -0
  98. package/dist/host-runtime-contract.js.map +1 -0
  99. package/dist/host-runtime-frame.d.ts +128 -0
  100. package/dist/host-runtime-frame.d.ts.map +1 -0
  101. package/dist/host-runtime-frame.js +881 -0
  102. package/dist/host-runtime-frame.js.map +1 -0
  103. package/dist/host-runtime-registry.d.ts +16 -0
  104. package/dist/host-runtime-registry.d.ts.map +1 -0
  105. package/dist/host-runtime-registry.js +53 -0
  106. package/dist/host-runtime-registry.js.map +1 -0
  107. package/dist/host-runtime.d.ts +106 -0
  108. package/dist/host-runtime.d.ts.map +1 -0
  109. package/dist/host-runtime.js +290 -0
  110. package/dist/host-runtime.js.map +1 -0
  111. package/dist/identity-adapter.d.ts +42 -0
  112. package/dist/identity-adapter.d.ts.map +1 -0
  113. package/dist/identity-adapter.js +43 -0
  114. package/dist/identity-adapter.js.map +1 -0
  115. package/dist/identity.d.ts +86 -0
  116. package/dist/identity.d.ts.map +1 -0
  117. package/dist/identity.js +223 -0
  118. package/dist/identity.js.map +1 -0
  119. package/dist/index.d.ts +91 -0
  120. package/dist/index.d.ts.map +1 -0
  121. package/dist/index.js +356 -0
  122. package/dist/index.js.map +1 -0
  123. package/dist/internal/api-protocol/api/protocol.d.ts +1402 -0
  124. package/dist/internal/api-protocol/api/protocol.d.ts.map +1 -0
  125. package/dist/internal/api-protocol/api/protocol.js +311 -0
  126. package/dist/internal/api-protocol/api/protocol.js.map +1 -0
  127. package/dist/internal/api-protocol/api/types.d.ts +24 -0
  128. package/dist/internal/api-protocol/api/types.d.ts.map +1 -0
  129. package/dist/internal/api-protocol/api/types.js +9 -0
  130. package/dist/internal/api-protocol/api/types.js.map +1 -0
  131. package/dist/internal/api-protocol/host-facade/connectionManager.d.ts +44 -0
  132. package/dist/internal/api-protocol/host-facade/connectionManager.d.ts.map +1 -0
  133. package/dist/internal/api-protocol/host-facade/connectionManager.js +349 -0
  134. package/dist/internal/api-protocol/host-facade/connectionManager.js.map +1 -0
  135. package/dist/internal/api-protocol/host-facade/protocolHandler.d.ts +24 -0
  136. package/dist/internal/api-protocol/host-facade/protocolHandler.d.ts.map +1 -0
  137. package/dist/internal/api-protocol/host-facade/protocolHandler.js +505 -0
  138. package/dist/internal/api-protocol/host-facade/protocolHandler.js.map +1 -0
  139. package/dist/internal/api-protocol/host-facade/types.d.ts +82 -0
  140. package/dist/internal/api-protocol/host-facade/types.d.ts.map +1 -0
  141. package/dist/internal/api-protocol/host-facade/types.js +14 -0
  142. package/dist/internal/api-protocol/host-facade/types.js.map +1 -0
  143. package/dist/internal/api-protocol/index.d.ts +39 -0
  144. package/dist/internal/api-protocol/index.d.ts.map +1 -0
  145. package/dist/internal/api-protocol/index.js +41 -0
  146. package/dist/internal/api-protocol/index.js.map +1 -0
  147. package/dist/internal/api-protocol/product-facade/hostApi.d.ts +392 -0
  148. package/dist/internal/api-protocol/product-facade/hostApi.d.ts.map +1 -0
  149. package/dist/internal/api-protocol/product-facade/hostApi.js +241 -0
  150. package/dist/internal/api-protocol/product-facade/hostApi.js.map +1 -0
  151. package/dist/internal/api-protocol/shared/codec/adapter.d.ts +41 -0
  152. package/dist/internal/api-protocol/shared/codec/adapter.d.ts.map +1 -0
  153. package/dist/internal/api-protocol/shared/codec/adapter.js +10 -0
  154. package/dist/internal/api-protocol/shared/codec/adapter.js.map +1 -0
  155. package/dist/internal/api-protocol/shared/codec/negotiation.d.ts +80 -0
  156. package/dist/internal/api-protocol/shared/codec/negotiation.d.ts.map +1 -0
  157. package/dist/internal/api-protocol/shared/codec/negotiation.js +129 -0
  158. package/dist/internal/api-protocol/shared/codec/negotiation.js.map +1 -0
  159. package/dist/internal/api-protocol/shared/codec/scale/adapter.d.ts +18 -0
  160. package/dist/internal/api-protocol/shared/codec/scale/adapter.d.ts.map +1 -0
  161. package/dist/internal/api-protocol/shared/codec/scale/adapter.js +23 -0
  162. package/dist/internal/api-protocol/shared/codec/scale/adapter.js.map +1 -0
  163. package/dist/internal/api-protocol/shared/codec/scale/primitives.d.ts +35 -0
  164. package/dist/internal/api-protocol/shared/codec/scale/primitives.d.ts.map +1 -0
  165. package/dist/internal/api-protocol/shared/codec/scale/primitives.js +96 -0
  166. package/dist/internal/api-protocol/shared/codec/scale/primitives.js.map +1 -0
  167. package/dist/internal/api-protocol/shared/codec/scale/v1/accounts.d.ts +65 -0
  168. package/dist/internal/api-protocol/shared/codec/scale/v1/accounts.d.ts.map +1 -0
  169. package/dist/internal/api-protocol/shared/codec/scale/v1/accounts.js +43 -0
  170. package/dist/internal/api-protocol/shared/codec/scale/v1/accounts.js.map +1 -0
  171. package/dist/internal/api-protocol/shared/codec/scale/v1/chainInteraction.d.ts +162 -0
  172. package/dist/internal/api-protocol/shared/codec/scale/v1/chainInteraction.d.ts.map +1 -0
  173. package/dist/internal/api-protocol/shared/codec/scale/v1/chainInteraction.js +81 -0
  174. package/dist/internal/api-protocol/shared/codec/scale/v1/chainInteraction.js.map +1 -0
  175. package/dist/internal/api-protocol/shared/codec/scale/v1/chat.d.ts +307 -0
  176. package/dist/internal/api-protocol/shared/codec/scale/v1/chat.d.ts.map +1 -0
  177. package/dist/internal/api-protocol/shared/codec/scale/v1/chat.js +105 -0
  178. package/dist/internal/api-protocol/shared/codec/scale/v1/chat.js.map +1 -0
  179. package/dist/internal/api-protocol/shared/codec/scale/v1/commonCodecs.d.ts +5 -0
  180. package/dist/internal/api-protocol/shared/codec/scale/v1/commonCodecs.d.ts.map +1 -0
  181. package/dist/internal/api-protocol/shared/codec/scale/v1/commonCodecs.js +6 -0
  182. package/dist/internal/api-protocol/shared/codec/scale/v1/commonCodecs.js.map +1 -0
  183. package/dist/internal/api-protocol/shared/codec/scale/v1/createTransaction.d.ts +71 -0
  184. package/dist/internal/api-protocol/shared/codec/scale/v1/createTransaction.d.ts.map +1 -0
  185. package/dist/internal/api-protocol/shared/codec/scale/v1/createTransaction.js +34 -0
  186. package/dist/internal/api-protocol/shared/codec/scale/v1/createTransaction.js.map +1 -0
  187. package/dist/internal/api-protocol/shared/codec/scale/v1/customRenderer.d.ts +106 -0
  188. package/dist/internal/api-protocol/shared/codec/scale/v1/customRenderer.d.ts.map +1 -0
  189. package/dist/internal/api-protocol/shared/codec/scale/v1/customRenderer.js +84 -0
  190. package/dist/internal/api-protocol/shared/codec/scale/v1/customRenderer.js.map +1 -0
  191. package/dist/internal/api-protocol/shared/codec/scale/v1/devicePermission.d.ts +4 -0
  192. package/dist/internal/api-protocol/shared/codec/scale/v1/devicePermission.d.ts.map +1 -0
  193. package/dist/internal/api-protocol/shared/codec/scale/v1/devicePermission.js +4 -0
  194. package/dist/internal/api-protocol/shared/codec/scale/v1/devicePermission.js.map +1 -0
  195. package/dist/internal/api-protocol/shared/codec/scale/v1/feature.d.ts +7 -0
  196. package/dist/internal/api-protocol/shared/codec/scale/v1/feature.d.ts.map +1 -0
  197. package/dist/internal/api-protocol/shared/codec/scale/v1/feature.js +7 -0
  198. package/dist/internal/api-protocol/shared/codec/scale/v1/feature.js.map +1 -0
  199. package/dist/internal/api-protocol/shared/codec/scale/v1/handshake.d.ts +13 -0
  200. package/dist/internal/api-protocol/shared/codec/scale/v1/handshake.d.ts.map +1 -0
  201. package/dist/internal/api-protocol/shared/codec/scale/v1/handshake.js +10 -0
  202. package/dist/internal/api-protocol/shared/codec/scale/v1/handshake.js.map +1 -0
  203. package/dist/internal/api-protocol/shared/codec/scale/v1/localStorage.d.ts +15 -0
  204. package/dist/internal/api-protocol/shared/codec/scale/v1/localStorage.d.ts.map +1 -0
  205. package/dist/internal/api-protocol/shared/codec/scale/v1/localStorage.js +12 -0
  206. package/dist/internal/api-protocol/shared/codec/scale/v1/localStorage.js.map +1 -0
  207. package/dist/internal/api-protocol/shared/codec/scale/v1/navigation.d.ts +10 -0
  208. package/dist/internal/api-protocol/shared/codec/scale/v1/navigation.d.ts.map +1 -0
  209. package/dist/internal/api-protocol/shared/codec/scale/v1/navigation.js +10 -0
  210. package/dist/internal/api-protocol/shared/codec/scale/v1/navigation.js.map +1 -0
  211. package/dist/internal/api-protocol/shared/codec/scale/v1/notification.d.ts +7 -0
  212. package/dist/internal/api-protocol/shared/codec/scale/v1/notification.d.ts.map +1 -0
  213. package/dist/internal/api-protocol/shared/codec/scale/v1/notification.js +7 -0
  214. package/dist/internal/api-protocol/shared/codec/scale/v1/notification.js.map +1 -0
  215. package/dist/internal/api-protocol/shared/codec/scale/v1/payment.d.ts +74 -0
  216. package/dist/internal/api-protocol/shared/codec/scale/v1/payment.d.ts.map +1 -0
  217. package/dist/internal/api-protocol/shared/codec/scale/v1/payment.js +38 -0
  218. package/dist/internal/api-protocol/shared/codec/scale/v1/payment.js.map +1 -0
  219. package/dist/internal/api-protocol/shared/codec/scale/v1/preimage.d.ts +12 -0
  220. package/dist/internal/api-protocol/shared/codec/scale/v1/preimage.d.ts.map +1 -0
  221. package/dist/internal/api-protocol/shared/codec/scale/v1/preimage.js +11 -0
  222. package/dist/internal/api-protocol/shared/codec/scale/v1/preimage.js.map +1 -0
  223. package/dist/internal/api-protocol/shared/codec/scale/v1/remotePermission.d.ts +10 -0
  224. package/dist/internal/api-protocol/shared/codec/scale/v1/remotePermission.d.ts.map +1 -0
  225. package/dist/internal/api-protocol/shared/codec/scale/v1/remotePermission.js +8 -0
  226. package/dist/internal/api-protocol/shared/codec/scale/v1/remotePermission.js.map +1 -0
  227. package/dist/internal/api-protocol/shared/codec/scale/v1/sign.d.ts +61 -0
  228. package/dist/internal/api-protocol/shared/codec/scale/v1/sign.d.ts.map +1 -0
  229. package/dist/internal/api-protocol/shared/codec/scale/v1/sign.js +42 -0
  230. package/dist/internal/api-protocol/shared/codec/scale/v1/sign.js.map +1 -0
  231. package/dist/internal/api-protocol/shared/codec/scale/v1/statementStore.d.ts +137 -0
  232. package/dist/internal/api-protocol/shared/codec/scale/v1/statementStore.d.ts.map +1 -0
  233. package/dist/internal/api-protocol/shared/codec/scale/v1/statementStore.js +55 -0
  234. package/dist/internal/api-protocol/shared/codec/scale/v1/statementStore.js.map +1 -0
  235. package/dist/internal/api-protocol/shared/codec/structured/index.d.ts +10 -0
  236. package/dist/internal/api-protocol/shared/codec/structured/index.d.ts.map +1 -0
  237. package/dist/internal/api-protocol/shared/codec/structured/index.js +19 -0
  238. package/dist/internal/api-protocol/shared/codec/structured/index.js.map +1 -0
  239. package/dist/internal/api-protocol/shared/transport/messagePortProvider.d.ts +18 -0
  240. package/dist/internal/api-protocol/shared/transport/messagePortProvider.d.ts.map +1 -0
  241. package/dist/internal/api-protocol/shared/transport/messagePortProvider.js +98 -0
  242. package/dist/internal/api-protocol/shared/transport/messagePortProvider.js.map +1 -0
  243. package/dist/internal/api-protocol/shared/transport/provider.d.ts +42 -0
  244. package/dist/internal/api-protocol/shared/transport/provider.d.ts.map +1 -0
  245. package/dist/internal/api-protocol/shared/transport/provider.js +13 -0
  246. package/dist/internal/api-protocol/shared/transport/provider.js.map +1 -0
  247. package/dist/internal/api-protocol/shared/transport/transport.d.ts +72 -0
  248. package/dist/internal/api-protocol/shared/transport/transport.d.ts.map +1 -0
  249. package/dist/internal/api-protocol/shared/transport/transport.js +420 -0
  250. package/dist/internal/api-protocol/shared/transport/transport.js.map +1 -0
  251. package/dist/internal/api-protocol/shared/transport/windowProvider.d.ts +13 -0
  252. package/dist/internal/api-protocol/shared/transport/windowProvider.d.ts.map +1 -0
  253. package/dist/internal/api-protocol/shared/transport/windowProvider.js +85 -0
  254. package/dist/internal/api-protocol/shared/transport/windowProvider.js.map +1 -0
  255. package/dist/internal/api-protocol/shared/util/helpers.d.ts +27 -0
  256. package/dist/internal/api-protocol/shared/util/helpers.d.ts.map +1 -0
  257. package/dist/internal/api-protocol/shared/util/helpers.js +43 -0
  258. package/dist/internal/api-protocol/shared/util/helpers.js.map +1 -0
  259. package/dist/internal/api-protocol/shared/util/idFactory.d.ts +9 -0
  260. package/dist/internal/api-protocol/shared/util/idFactory.d.ts.map +1 -0
  261. package/dist/internal/api-protocol/shared/util/idFactory.js +12 -0
  262. package/dist/internal/api-protocol/shared/util/idFactory.js.map +1 -0
  263. package/dist/internal/api-protocol/shared/util/logger.d.ts +15 -0
  264. package/dist/internal/api-protocol/shared/util/logger.d.ts.map +1 -0
  265. package/dist/internal/api-protocol/shared/util/logger.js +20 -0
  266. package/dist/internal/api-protocol/shared/util/logger.js.map +1 -0
  267. package/dist/mesh-attachment.d.ts +19 -0
  268. package/dist/mesh-attachment.d.ts.map +1 -0
  269. package/dist/mesh-attachment.js +43 -0
  270. package/dist/mesh-attachment.js.map +1 -0
  271. package/dist/mesh-chat.d.ts +48 -0
  272. package/dist/mesh-chat.d.ts.map +1 -0
  273. package/dist/mesh-chat.js +84 -0
  274. package/dist/mesh-chat.js.map +1 -0
  275. package/dist/mesh-delivery-diagnostics.d.ts +12 -0
  276. package/dist/mesh-delivery-diagnostics.d.ts.map +1 -0
  277. package/dist/mesh-delivery-diagnostics.js +45 -0
  278. package/dist/mesh-delivery-diagnostics.js.map +1 -0
  279. package/dist/mesh-notification.d.ts +29 -0
  280. package/dist/mesh-notification.d.ts.map +1 -0
  281. package/dist/mesh-notification.js +52 -0
  282. package/dist/mesh-notification.js.map +1 -0
  283. package/dist/mesh-pairing.d.ts +40 -0
  284. package/dist/mesh-pairing.d.ts.map +1 -0
  285. package/dist/mesh-pairing.js +78 -0
  286. package/dist/mesh-pairing.js.map +1 -0
  287. package/dist/product-host-runtime.d.ts +59 -0
  288. package/dist/product-host-runtime.d.ts.map +1 -0
  289. package/dist/product-host-runtime.js +150 -0
  290. package/dist/product-host-runtime.js.map +1 -0
  291. package/dist/product-protocol.d.ts +49 -0
  292. package/dist/product-protocol.d.ts.map +1 -0
  293. package/dist/product-protocol.js +389 -0
  294. package/dist/product-protocol.js.map +1 -0
  295. package/dist/product-view-hosted.d.ts +63 -0
  296. package/dist/product-view-hosted.d.ts.map +1 -0
  297. package/dist/product-view-hosted.js +272 -0
  298. package/dist/product-view-hosted.js.map +1 -0
  299. package/dist/product-view-service-worker.d.ts +6 -0
  300. package/dist/product-view-service-worker.d.ts.map +1 -0
  301. package/dist/product-view-service-worker.js +57 -0
  302. package/dist/product-view-service-worker.js.map +1 -0
  303. package/dist/product-view.d.ts +459 -0
  304. package/dist/product-view.d.ts.map +1 -0
  305. package/dist/product-view.js +2671 -0
  306. package/dist/product-view.js.map +1 -0
  307. package/dist/protocol.d.ts +39 -0
  308. package/dist/protocol.d.ts.map +1 -0
  309. package/dist/protocol.js +25 -0
  310. package/dist/protocol.js.map +1 -0
  311. package/dist/remote-runtime-provider.d.ts +9 -0
  312. package/dist/remote-runtime-provider.d.ts.map +1 -0
  313. package/dist/remote-runtime-provider.js +1002 -0
  314. package/dist/remote-runtime-provider.js.map +1 -0
  315. package/dist/runtime-chain-service.d.ts +109 -0
  316. package/dist/runtime-chain-service.d.ts.map +1 -0
  317. package/dist/runtime-chain-service.js +846 -0
  318. package/dist/runtime-chain-service.js.map +1 -0
  319. package/dist/runtime-chain.d.ts +8 -0
  320. package/dist/runtime-chain.d.ts.map +1 -0
  321. package/dist/runtime-chain.js +4 -0
  322. package/dist/runtime-chain.js.map +1 -0
  323. package/dist/runtime-storage-access.d.ts +39 -0
  324. package/dist/runtime-storage-access.d.ts.map +1 -0
  325. package/dist/runtime-storage-access.js +209 -0
  326. package/dist/runtime-storage-access.js.map +1 -0
  327. package/dist/scoped-frame.d.ts +55 -0
  328. package/dist/scoped-frame.d.ts.map +1 -0
  329. package/dist/scoped-frame.js +234 -0
  330. package/dist/scoped-frame.js.map +1 -0
  331. package/dist/sdk-interfaces.d.ts +159 -0
  332. package/dist/sdk-interfaces.d.ts.map +1 -0
  333. package/dist/sdk-interfaces.js +2 -0
  334. package/dist/sdk-interfaces.js.map +1 -0
  335. package/dist/statement-store.d.ts +166 -0
  336. package/dist/statement-store.d.ts.map +1 -0
  337. package/dist/statement-store.js +627 -0
  338. package/dist/statement-store.js.map +1 -0
  339. package/dist/telemetry.d.ts +218 -0
  340. package/dist/telemetry.d.ts.map +1 -0
  341. package/dist/telemetry.js +162 -0
  342. package/dist/telemetry.js.map +1 -0
  343. package/dist/types.d.ts +255 -0
  344. package/dist/types.d.ts.map +1 -0
  345. package/dist/types.js +77 -0
  346. package/dist/types.js.map +1 -0
  347. package/dist/wallet-session.d.ts +89 -0
  348. package/dist/wallet-session.d.ts.map +1 -0
  349. package/dist/wallet-session.js +135 -0
  350. package/dist/wallet-session.js.map +1 -0
  351. package/package.json +86 -5
  352. package/index.js +0 -1
@@ -0,0 +1,2671 @@
1
+ /**
2
+ * mountProductView — sandboxed iframe container for Polkadot product dApps.
3
+ *
4
+ * Works in web, Electron, and Tauri environments. The iframe is given
5
+ * `sandbox="allow-scripts"` — no same-origin access, no forms, no popups.
6
+ * Direct network access is blocked by default, with optional host-approved
7
+ * exact HTTPS origin exceptions. All communication happens through postMessage.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const handle = mountProductView({
12
+ * container: document.getElementById("app-frame")!,
13
+ * productId: "com.example.myapp",
14
+ * assets: assetsMap,
15
+ * sdk,
16
+ * delegate: {
17
+ * onMessage(data, productId) {
18
+ * const outcome = sdk.handleMessage(data, productId);
19
+ * // ... handle outcome
20
+ * },
21
+ * },
22
+ * });
23
+ * // Later:
24
+ * handle.sendResponse(responseBytes);
25
+ * handle.destroy();
26
+ * ```
27
+ */
28
+ import { createBridgeMetadata } from "./bridge/contract.js";
29
+ import { isProductAnalyticsMessage } from "./telemetry.js";
30
+ // ---------------------------------------------------------------------------
31
+ // Internal bridge script adapted for iframe postMessage transport
32
+ // ---------------------------------------------------------------------------
33
+ const WEB_BRIDGE_SCRIPT = `
34
+ (function() {
35
+ 'use strict';
36
+ if (window.__hostApiBridge) { return; }
37
+ window.__hostApiBridge = true;
38
+ window.__HOST_WEBVIEW_MARK__ = true;
39
+ if (!window.host) { window.host = {}; }
40
+ if (!window.host.storage) { window.host.storage = {}; }
41
+ var __session = window.__HOST_PRODUCT_VIEW_SESSION__ || null;
42
+ var listeners = [];
43
+ var started = false;
44
+ var pending = [];
45
+ function dispatch(data) {
46
+ var ev = { data: data };
47
+ for (var i = 0; i < listeners.length; i++) {
48
+ try { listeners[i](ev); } catch(e) {}
49
+ }
50
+ }
51
+ window.__HOST_API_PORT__ = {
52
+ postMessage: function(data) {
53
+ var bytes;
54
+ if (data instanceof Uint8Array) { bytes = data; }
55
+ else if (data instanceof ArrayBuffer) { bytes = new Uint8Array(data); }
56
+ else if (ArrayBuffer.isView(data)) { bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); }
57
+ else { return; }
58
+ parent.postMessage({ type: 'hostapi', data: bytes, session: __session }, '*');
59
+ },
60
+ addEventListener: function(type, handler) {
61
+ if (type === 'message') { listeners.push(handler); }
62
+ },
63
+ removeEventListener: function(type, handler) {
64
+ if (type === 'message') {
65
+ listeners = listeners.filter(function(l) { return l !== handler; });
66
+ }
67
+ },
68
+ start: function() {
69
+ started = true;
70
+ for (var i = 0; i < pending.length; i++) { dispatch(pending[i]); }
71
+ pending = [];
72
+ }
73
+ };
74
+ window.addEventListener('message', function(ev) {
75
+ if (!ev.data || ev.data.type !== 'hostapi-reply') { return; }
76
+ if (__session && ev.data.session !== __session) { return; }
77
+ var data = ev.data.data;
78
+ if (started) { dispatch(data); } else { pending.push(data); }
79
+ });
80
+ })();
81
+ `;
82
+ const HOST_PRODUCT_VIEW_SESSION_QUERY_KEY = "__hostsdk_session";
83
+ function createProductViewSessionToken() {
84
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
85
+ return crypto.randomUUID().replace(/-/g, "");
86
+ }
87
+ return Math.random().toString(36).slice(2, 18);
88
+ }
89
+ function buildProductViewSessionBootstrapScript() {
90
+ return `(function() {
91
+ 'use strict';
92
+ var token = null;
93
+ try {
94
+ var params = new URLSearchParams(window.location.search);
95
+ token = params.get("${HOST_PRODUCT_VIEW_SESSION_QUERY_KEY}");
96
+ } catch (e) {}
97
+ if (!token) { return; }
98
+ try {
99
+ Object.defineProperty(window, "__HOST_PRODUCT_VIEW_SESSION__", {
100
+ value: token,
101
+ writable: false,
102
+ configurable: false,
103
+ enumerable: false,
104
+ });
105
+ } catch (e) {
106
+ try { window.__HOST_PRODUCT_VIEW_SESSION__ = token; } catch (e2) {}
107
+ }
108
+ try {
109
+ if (history && history.replaceState) {
110
+ var nextSearch = "";
111
+ try {
112
+ var nextParams = new URLSearchParams(window.location.search);
113
+ nextParams.delete("${HOST_PRODUCT_VIEW_SESSION_QUERY_KEY}");
114
+ var encoded = nextParams.toString();
115
+ nextSearch = encoded ? "?" + encoded : "";
116
+ } catch (e2) {}
117
+ history.replaceState(null, "", location.pathname + nextSearch + location.hash);
118
+ }
119
+ } catch (e) {}
120
+ })();`;
121
+ }
122
+ /**
123
+ * Build the web iframe bridge script.
124
+ *
125
+ * Uses `WEB_BRIDGE_SCRIPT` which creates a shim `window.__HOST_API_PORT__`
126
+ * that communicates with the host via `parent.postMessage` directly (raw
127
+ * Uint8Array, no base64 encoding, no MessageChannel indirection).
128
+ *
129
+ * Security note: `'*'` is unavoidable in the iframe-to-host direction
130
+ * because the sandboxed iframe (no allow-same-origin) has an opaque `null`
131
+ * origin and cannot know its parent's origin. The host-side security
132
+ * boundary is the `event.source === iframe.contentWindow` check in
133
+ * mountProductView's `onMessage` handler.
134
+ *
135
+ * For host-to-iframe replies the host uses `'null'` as the targetOrigin
136
+ * (srcdoc) or the real frame origin (URL-backed). This restricts delivery
137
+ * to the correct iframe.
138
+ *
139
+ * @internal Exported for testing only -- not part of the public API.
140
+ */
141
+ export function buildIframeBridgeScript(interactive = false) {
142
+ // Grant port setup is only needed for interactive sandbox mode.
143
+ const grantPortSetup = interactive
144
+ ? `
145
+ var grantCh = new MessageChannel();
146
+ window.__HOST_SANDBOX_GRANT_PORT__ = grantCh.port2;
147
+ grantCh.port2.start();
148
+ try {
149
+ parent.postMessage({ type: 'hostapi-grant-port', session: window.__HOST_PRODUCT_VIEW_SESSION__ || null }, '*', [grantCh.port1]);
150
+ } catch (e) {}`
151
+ : "";
152
+ return WEB_BRIDGE_SCRIPT.replace("})();", `${grantPortSetup}
153
+ function __emitHostHeartbeat() {
154
+ try {
155
+ parent.postMessage({ type: 'hostapi-heartbeat', session: window.__HOST_PRODUCT_VIEW_SESSION__ || null }, '*');
156
+ } catch (e) {}
157
+ }
158
+ __emitHostHeartbeat();
159
+ window.setInterval(__emitHostHeartbeat, 500);
160
+ })();`);
161
+ }
162
+ /**
163
+ * Build a script that installs the stable `window.host` bridge surface in the
164
+ * sandboxed iframe. Each method routes through a JSON postMessage channel to
165
+ * the host frame.
166
+ *
167
+ * Must be injected AFTER `buildIframeBridgeScript()` (which creates
168
+ * `window.host`) and BEFORE `buildBridgeMetadataScript()` and the sandbox
169
+ * lockdown script.
170
+ *
171
+ * @internal Exported for testing only — not part of the public API.
172
+ */
173
+ export function buildStableBridgeScript() {
174
+ return `(function() {
175
+ 'use strict';
176
+ if (!window.host) { window.host = {}; }
177
+ var __session = window.__HOST_PRODUCT_VIEW_SESSION__ || null;
178
+ var callId = 0;
179
+ var pending = {};
180
+ var listeners = {};
181
+
182
+ function bridgeCall(method, args) {
183
+ return new Promise(function(resolve, reject) {
184
+ var id = ++callId;
185
+ pending[id] = { resolve: resolve, reject: reject };
186
+ parent.postMessage({ type: 'host-bridge-call', callId: id, method: method, args: args, session: __session }, '*');
187
+ });
188
+ }
189
+
190
+ window.addEventListener('message', function(ev) {
191
+ if (!ev.data) { return; }
192
+ if (ev.data.type === 'host-bridge-reply') {
193
+ if (__session && ev.data.session !== __session) { return; }
194
+ var p = pending[ev.data.callId];
195
+ if (p) {
196
+ delete pending[ev.data.callId];
197
+ if (ev.data.error) {
198
+ var err = new Error(ev.data.error);
199
+ err.code = ev.data.errorCode || 'ERR_HOST_RUNTIME_UNAVAILABLE';
200
+ p.reject(err);
201
+ } else {
202
+ p.resolve(ev.data.result);
203
+ }
204
+ }
205
+ } else if (ev.data.type === 'host-bridge-event') {
206
+ var cbs = listeners[ev.data.event];
207
+ if (cbs) {
208
+ for (var i = 0; i < cbs.length; i++) {
209
+ try { cbs[i](ev.data.payload); } catch (e) {}
210
+ }
211
+ }
212
+ }
213
+ });
214
+
215
+ window.host.getAddress = function() {
216
+ return bridgeCall('getAddress', []);
217
+ };
218
+
219
+ if (!window.host.storage) { window.host.storage = {}; }
220
+ window.host.storage.get = function(key) {
221
+ return bridgeCall('storage.get', [key]);
222
+ };
223
+ window.host.storage.set = function(key, value) {
224
+ return bridgeCall('storage.set', [key, value]);
225
+ };
226
+ window.host.storage.remove = function(key) {
227
+ return bridgeCall('storage.remove', [key]);
228
+ };
229
+
230
+ if (!window.host.statements) { window.host.statements = {}; }
231
+ window.host.statements.subscribe = function(channel) {
232
+ return bridgeCall('statements.subscribe', [channel]);
233
+ };
234
+ window.host.statements.write = function(channel, data) {
235
+ return bridgeCall('statements.write', [channel, data]);
236
+ };
237
+ window.host.statements.status = function() {
238
+ return bridgeCall('statements.status', []);
239
+ };
240
+
241
+ if (!window.host.identity) { window.host.identity = {}; }
242
+ window.host.identity.resolveUsername = function(username) {
243
+ return bridgeCall('identity.resolveUsername', [username]);
244
+ };
245
+
246
+ window.host.on = function(event, callback) {
247
+ if (!listeners[event]) { listeners[event] = []; }
248
+ listeners[event].push(callback);
249
+ };
250
+ window.host.off = function(event, callback) {
251
+ var cbs = listeners[event];
252
+ if (!cbs) { return; }
253
+ var idx = cbs.indexOf(callback);
254
+ if (idx !== -1) { cbs.splice(idx, 1); }
255
+ };
256
+
257
+ window.host.destroy = function() {
258
+ // Lifecycle management belongs to the host frame; guest-side no-op.
259
+ };
260
+ })();`;
261
+ }
262
+ /**
263
+ * Build a bootstrap script that exposes immutable `window.host.__bridge`
264
+ * metadata before product code and extension scripts run.
265
+ *
266
+ * The current web `srcdoc` profile still uses the binary host-api port for its
267
+ * core transport, so only extension capabilities are advertised here.
268
+ *
269
+ * @internal Exported for testing only — not part of the public API.
270
+ */
271
+ export function buildBridgeMetadataScript(extensionInjectScript, profile = "web-srcdoc", coreCapabilities) {
272
+ const metadata = createBridgeMetadata({
273
+ profile,
274
+ coreCapabilities: {
275
+ storage: coreCapabilities?.storage ?? false,
276
+ statements: coreCapabilities?.statements ?? false,
277
+ },
278
+ extCapabilities: {
279
+ claw: extensionInjectScript.includes("window.host.ext.claw"),
280
+ data: extensionInjectScript.includes("window.host.ext.data"),
281
+ media: extensionInjectScript.includes("window.host.ext.media"),
282
+ files: extensionInjectScript.includes("window.host.ext.files"),
283
+ mesh: extensionInjectScript.includes("window.host.ext.mesh"),
284
+ vrf: extensionInjectScript.includes("window.host.ext.vrf"),
285
+ },
286
+ }, coreCapabilities?.identity ?? false);
287
+ const metadataJson = JSON.stringify(metadata);
288
+ return `(function() {
289
+ 'use strict';
290
+ if (!window.host) { window.host = {}; }
291
+ if (Object.prototype.hasOwnProperty.call(window.host, '__bridge')) { return; }
292
+ var metadata = ${metadataJson};
293
+ if (Object.freeze) {
294
+ try {
295
+ Object.freeze(metadata.productProtocol);
296
+ Object.freeze(metadata.capabilities.ext);
297
+ Object.freeze(metadata.capabilities);
298
+ Object.freeze(metadata);
299
+ } catch (e) {}
300
+ }
301
+ Object.defineProperty(window.host, '__bridge', {
302
+ value: metadata,
303
+ writable: false,
304
+ configurable: false,
305
+ enumerable: true,
306
+ });
307
+ })();`;
308
+ }
309
+ /**
310
+ * Build a script that injects `window.__HOST_TRACE_CONTEXT__` into the iframe.
311
+ *
312
+ * The object is frozen to prevent product code from tampering with it.
313
+ * Only injected when the host provides a `traceContext` in mount options.
314
+ *
315
+ * @internal Exported for testing only — not part of the public API.
316
+ */
317
+ export function buildTraceContextScript(ctx) {
318
+ const json = JSON.stringify({
319
+ traceId: ctx.traceId,
320
+ parentSpanId: ctx.parentSpanId,
321
+ sampled: ctx.sampled,
322
+ ...(ctx.productId ? { productId: ctx.productId } : {}),
323
+ });
324
+ return `(function() {
325
+ 'use strict';
326
+ var ctx = ${json};
327
+ if (Object.freeze) { try { Object.freeze(ctx); } catch(e) {} }
328
+ Object.defineProperty(window, '__HOST_TRACE_CONTEXT__', {
329
+ value: ctx,
330
+ writable: false,
331
+ configurable: false,
332
+ enumerable: false,
333
+ });
334
+ })();`;
335
+ }
336
+ /** Sandbox attribute applied to the product iframe. */
337
+ export const IFRAME_SANDBOX_ATTR = "allow-scripts";
338
+ export const URL_BACKED_IFRAME_SANDBOX_ATTR = "allow-scripts allow-same-origin";
339
+ function normalizeApprovedNetworkOrigins(origins = []) {
340
+ const seen = new Set();
341
+ const normalized = [];
342
+ for (const origin of origins) {
343
+ if (typeof origin !== "string" || !origin.startsWith("https://"))
344
+ continue;
345
+ const rest = origin.slice("https://".length);
346
+ if (rest.length === 0 ||
347
+ rest.includes("/") ||
348
+ rest.includes("?") ||
349
+ rest.includes("#") ||
350
+ rest.includes("*") ||
351
+ rest.includes(" ") ||
352
+ rest.startsWith(":") ||
353
+ rest.endsWith(":")) {
354
+ continue;
355
+ }
356
+ if (seen.has(origin))
357
+ continue;
358
+ seen.add(origin);
359
+ normalized.push(origin);
360
+ }
361
+ return normalized;
362
+ }
363
+ function normalizeApprovedWebSocketOrigins(origins = []) {
364
+ const seen = new Set();
365
+ const normalized = [];
366
+ for (const origin of origins) {
367
+ if (typeof origin !== "string" || !origin.startsWith("wss://"))
368
+ continue;
369
+ const rest = origin.slice("wss://".length);
370
+ if (rest.length === 0 ||
371
+ rest.includes("/") ||
372
+ rest.includes("?") ||
373
+ rest.includes("#") ||
374
+ rest.includes("*") ||
375
+ rest.includes(" ") ||
376
+ rest.startsWith(":") ||
377
+ rest.endsWith(":")) {
378
+ continue;
379
+ }
380
+ if (seen.has(origin))
381
+ continue;
382
+ seen.add(origin);
383
+ normalized.push(origin);
384
+ }
385
+ return normalized;
386
+ }
387
+ function normalizeApprovedWebRtcServerUrls(urls = []) {
388
+ const seen = new Set();
389
+ const normalized = [];
390
+ for (const rawUrl of urls) {
391
+ if (typeof rawUrl !== "string")
392
+ continue;
393
+ const trimmed = rawUrl.trim();
394
+ if (!/^(stun|turn|turns):/i.test(trimmed))
395
+ continue;
396
+ if (trimmed.includes("?") || trimmed.includes("#") || trimmed.includes(" "))
397
+ continue;
398
+ if (seen.has(trimmed))
399
+ continue;
400
+ seen.add(trimmed);
401
+ normalized.push(trimmed);
402
+ }
403
+ return normalized;
404
+ }
405
+ export function buildIframeCsp(approvedNetworkOrigins = [], approvedWebSocketOrigins = []) {
406
+ const connectSrc = [
407
+ ...normalizeApprovedNetworkOrigins(approvedNetworkOrigins),
408
+ ...normalizeApprovedWebSocketOrigins(approvedWebSocketOrigins),
409
+ ];
410
+ const connectDirective = connectSrc.length === 0 ? "'none'" : connectSrc.join(" ");
411
+ return ("default-src 'none'; " +
412
+ "script-src 'unsafe-inline' 'wasm-unsafe-eval' data: blob:; " +
413
+ "style-src 'unsafe-inline'; " +
414
+ "frame-src 'none'; " +
415
+ `connect-src ${connectDirective}; ` +
416
+ "img-src blob: data:; " +
417
+ "font-src 'none'; " +
418
+ "media-src 'none'; " +
419
+ "object-src 'none'; " +
420
+ "base-uri 'none'; " +
421
+ "form-action 'none'");
422
+ }
423
+ export const IFRAME_CSP = buildIframeCsp();
424
+ /** Synthetic absolute origin used only when rewriting srcdoc bundle URLs. */
425
+ const SYNTHETIC_PRODUCT_BUNDLE_ORIGIN = "https://product.bundle.invalid";
426
+ export function buildUrlBackedIframeCsp(approvedNetworkOrigins = [], approvedWebSocketOrigins = []) {
427
+ const connectSrc = [
428
+ ...normalizeApprovedNetworkOrigins(approvedNetworkOrigins),
429
+ ...normalizeApprovedWebSocketOrigins(approvedWebSocketOrigins),
430
+ ];
431
+ const connectDirective = connectSrc.length === 0 ? "'self'" : ["'self'", ...connectSrc].join(" ");
432
+ return ("default-src 'none'; " +
433
+ "script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' data: blob:; " +
434
+ "style-src 'self' 'unsafe-inline'; " +
435
+ "frame-src 'none'; " +
436
+ `connect-src ${connectDirective}; ` +
437
+ "img-src 'self' blob: data:; " +
438
+ "font-src 'self'; " +
439
+ "media-src 'none'; " +
440
+ "object-src 'none'; " +
441
+ "base-uri 'self'; " +
442
+ "form-action 'none'");
443
+ }
444
+ function buildAllowedNetworkHelpers(approvedNetworkOrigins, approvedWebSocketOrigins = [], approvedWebRtcServerUrls = []) {
445
+ const allowed = JSON.stringify(normalizeApprovedNetworkOrigins(approvedNetworkOrigins));
446
+ const allowedWebSockets = JSON.stringify(normalizeApprovedWebSocketOrigins(approvedWebSocketOrigins));
447
+ const allowedWebRtcServers = JSON.stringify(normalizeApprovedWebRtcServerUrls(approvedWebRtcServerUrls));
448
+ return `
449
+ var __approvedNetworkOrigins = ${allowed};
450
+ var __approvedWebSocketOrigins = ${allowedWebSockets};
451
+ var __approvedWebRtcServerUrls = ${allowedWebRtcServers};
452
+ function __isApprovedNetworkUrl(input) {
453
+ try {
454
+ var parsed = new URL(String(input), location.href);
455
+ return parsed.protocol === 'https:' &&
456
+ __approvedNetworkOrigins.indexOf(parsed.origin) !== -1;
457
+ } catch (e) {
458
+ return false;
459
+ }
460
+ }
461
+ function __extractApprovedWebSocketOrigin(input) {
462
+ try {
463
+ var parsed = new URL(String(input), location.href);
464
+ if (parsed.protocol !== 'wss:') { return null; }
465
+ return parsed.origin;
466
+ } catch (e) {
467
+ return null;
468
+ }
469
+ }
470
+ function __isApprovedWebSocketUrl(input) {
471
+ var origin = __extractApprovedWebSocketOrigin(input);
472
+ return origin !== null && __approvedWebSocketOrigins.indexOf(origin) !== -1;
473
+ }
474
+ function __normalizeWebRtcServerUrl(input) {
475
+ if (typeof input !== 'string') { return null; }
476
+ var trimmed = input.trim();
477
+ if (!/^(stun|turn|turns):/i.test(trimmed)) { return null; }
478
+ if (trimmed.indexOf('?') !== -1 || trimmed.indexOf('#') !== -1 || trimmed.indexOf(' ') !== -1) {
479
+ return null;
480
+ }
481
+ return trimmed;
482
+ }
483
+ function __extractFirstWebRtcServerUrl(config) {
484
+ if (!config || !config.iceServers) { return null; }
485
+ var iceServers = Array.isArray(config.iceServers) ? config.iceServers : [config.iceServers];
486
+ for (var i = 0; i < iceServers.length; i++) {
487
+ var entry = iceServers[i];
488
+ if (!entry) { continue; }
489
+ var urls = entry.urls;
490
+ if (Array.isArray(urls) && urls.length > 0) {
491
+ return __normalizeWebRtcServerUrl(String(urls[0]));
492
+ }
493
+ if (typeof urls === 'string') {
494
+ return __normalizeWebRtcServerUrl(urls);
495
+ }
496
+ if (typeof entry.url === 'string') {
497
+ return __normalizeWebRtcServerUrl(entry.url);
498
+ }
499
+ }
500
+ return null;
501
+ }
502
+ function __isApprovedWebRtcConfiguration(config) {
503
+ if (!config || !config.iceServers) { return false; }
504
+ var iceServers = Array.isArray(config.iceServers) ? config.iceServers : [config.iceServers];
505
+ if (iceServers.length === 0) { return false; }
506
+ for (var i = 0; i < iceServers.length; i++) {
507
+ var entry = iceServers[i];
508
+ if (!entry) { return false; }
509
+ var urls = entry.urls;
510
+ var values = Array.isArray(urls)
511
+ ? urls
512
+ : (typeof urls === 'string' ? [urls] : (typeof entry.url === 'string' ? [entry.url] : []));
513
+ if (values.length === 0) { return false; }
514
+ for (var j = 0; j < values.length; j++) {
515
+ var normalizedUrl = __normalizeWebRtcServerUrl(String(values[j]));
516
+ if (!normalizedUrl || __approvedWebRtcServerUrls.indexOf(normalizedUrl) === -1) {
517
+ return false;
518
+ }
519
+ }
520
+ }
521
+ return true;
522
+ }
523
+ `;
524
+ }
525
+ function buildNavigationGuardBlock(mode) {
526
+ const rejectNavigation = mode === "lockdown"
527
+ ? `
528
+ throw new DOMException(
529
+ 'navigation to javascript: and data: URLs is blocked in sandboxed products', 'SecurityError'
530
+ );`
531
+ : mode === "monitor"
532
+ ? `
533
+ reportAndThrow('Navigation', { url: blocked });
534
+ return;`
535
+ : `
536
+ report('Navigation', { url: blocked }, 'always_blocked');
537
+ return;`;
538
+ return `
539
+ function __blockedNavigationTarget(input) {
540
+ if (typeof input !== 'string') { return null; }
541
+ var normalized = input.trim();
542
+ return /^(javascript|data):/i.test(normalized) ? normalized : null;
543
+ }
544
+ function __patchLocationMethod(target, name) {
545
+ if (!target) { return; }
546
+ try {
547
+ var original = target[name];
548
+ if (typeof original !== 'function') { return; }
549
+ Object.defineProperty(target, name, {
550
+ value: function(url) {
551
+ var blocked = __blockedNavigationTarget(String(url));
552
+ if (blocked) {${rejectNavigation}
553
+ }
554
+ return original.apply(window.location, arguments);
555
+ },
556
+ writable: false,
557
+ configurable: false,
558
+ });
559
+ } catch (e) {}
560
+ }
561
+ function __patchLocationHref(target) {
562
+ if (!target) { return; }
563
+ try {
564
+ var descriptor = Object.getOwnPropertyDescriptor(target, 'href');
565
+ if (!descriptor || typeof descriptor.set !== 'function') { return; }
566
+ Object.defineProperty(target, 'href', {
567
+ get: descriptor.get ? function() { return descriptor.get.call(window.location); } : undefined,
568
+ set: function(url) {
569
+ var blocked = __blockedNavigationTarget(String(url));
570
+ if (blocked) {${rejectNavigation}
571
+ }
572
+ return descriptor.set.call(window.location, url);
573
+ },
574
+ configurable: false,
575
+ });
576
+ } catch (e) {}
577
+ }
578
+ var __locationProto = Object.getPrototypeOf(window.location);
579
+ __patchLocationMethod(__locationProto, 'assign');
580
+ __patchLocationMethod(__locationProto, 'replace');
581
+ __patchLocationMethod(window.location, 'assign');
582
+ __patchLocationMethod(window.location, 'replace');
583
+ __patchLocationHref(__locationProto);
584
+ __patchLocationHref(window.location);
585
+ `;
586
+ }
587
+ /**
588
+ * Production lockdown script: neutralise browser APIs that could exfiltrate
589
+ * data or establish out-of-band connections. Silently blocks without reporting.
590
+ *
591
+ * Mirrors `SANDBOX_LOCKDOWN_SCRIPT` from `host-product-view/src/security.rs`.
592
+ */
593
+ export function buildLockdownScript(approvedNetworkOrigins = [], approvedWebSocketOrigins = [], approvedWebRtcServerUrls = []) {
594
+ const normalized = normalizeApprovedNetworkOrigins(approvedNetworkOrigins);
595
+ const normalizedWebSockets = normalizeApprovedWebSocketOrigins(approvedWebSocketOrigins);
596
+ const normalizedWebRtc = normalizeApprovedWebRtcServerUrls(approvedWebRtcServerUrls);
597
+ const remoteHelpers = normalized.length > 0 || normalizedWebSockets.length > 0 || normalizedWebRtc.length > 0
598
+ ? buildAllowedNetworkHelpers(normalized, normalizedWebSockets, normalizedWebRtc)
599
+ : "";
600
+ const navigationSetup = buildNavigationGuardBlock("lockdown");
601
+ const networkSetup = normalized.length === 0
602
+ ? `
603
+ var _origFetch = window.fetch;
604
+ if (_origFetch) {
605
+ window.fetch = function(input, init) {
606
+ var url = (typeof input === 'string') ? input : (input && input.url ? input.url : String(input));
607
+ var parsed = new URL(url, location.href);
608
+ // In a sandboxed srcdoc iframe location.origin is the string "null";
609
+ // the origin comparison would be a no-op, so we block all requests.
610
+ if (location.origin === 'null' || parsed.origin !== location.origin) {
611
+ throw new DOMException(
612
+ 'fetch is blocked for cross-origin requests in sandboxed products', 'SecurityError'
613
+ );
614
+ }
615
+ return _origFetch.apply(this, arguments);
616
+ };
617
+ }
618
+ if (window.XMLHttpRequest && window.XMLHttpRequest.prototype && window.XMLHttpRequest.prototype.open) {
619
+ var _origXhrOpen = window.XMLHttpRequest.prototype.open;
620
+ window.XMLHttpRequest.prototype.open = function(method, url) {
621
+ var parsed = new URL(String(url), location.href);
622
+ // In a sandboxed srcdoc iframe location.origin is the string "null";
623
+ // the origin comparison would be a no-op, so we block all requests.
624
+ if (location.origin === 'null' || parsed.origin !== location.origin) {
625
+ throw new DOMException(
626
+ 'XMLHttpRequest is blocked for cross-origin requests in sandboxed products', 'SecurityError'
627
+ );
628
+ }
629
+ return _origXhrOpen.apply(this, arguments);
630
+ };
631
+ }
632
+ `
633
+ : `
634
+ var _origFetch = window.fetch;
635
+ if (_origFetch) {
636
+ window.fetch = function(input, init) {
637
+ var url = (typeof input === 'string') ? input : (input && input.url ? input.url : String(input));
638
+ if (!__isApprovedNetworkUrl(url)) {
639
+ throw new DOMException(
640
+ 'fetch is blocked for unapproved origins in sandboxed products', 'SecurityError'
641
+ );
642
+ }
643
+ return _origFetch.apply(this, arguments);
644
+ };
645
+ }
646
+ if (window.XMLHttpRequest && window.XMLHttpRequest.prototype && window.XMLHttpRequest.prototype.open) {
647
+ var _origXhrOpen = window.XMLHttpRequest.prototype.open;
648
+ window.XMLHttpRequest.prototype.open = function(method, url) {
649
+ if (!__isApprovedNetworkUrl(url)) {
650
+ throw new DOMException(
651
+ 'XMLHttpRequest is blocked for unapproved origins in sandboxed products', 'SecurityError'
652
+ );
653
+ }
654
+ return _origXhrOpen.apply(this, arguments);
655
+ };
656
+ }
657
+ `;
658
+ const webSocketSetup = normalizedWebSockets.length === 0
659
+ ? `try { window.WebSocket = undefined; } catch(e) {}`
660
+ : `
661
+ var _origWebSocket = window.WebSocket;
662
+ if (_origWebSocket) {
663
+ window.WebSocket = function(url, protocols) {
664
+ if (!__isApprovedWebSocketUrl(url)) {
665
+ throw new DOMException(
666
+ 'WebSocket is blocked for unapproved origins in sandboxed products', 'SecurityError'
667
+ );
668
+ }
669
+ return new _origWebSocket(url, protocols);
670
+ };
671
+ window.WebSocket.prototype = _origWebSocket.prototype;
672
+ }`;
673
+ const webRtcSetup = normalizedWebRtc.length === 0
674
+ ? `
675
+ try { window.RTCPeerConnection = undefined; } catch(e) {}
676
+ try { window.RTCSessionDescription = undefined; } catch(e) {}
677
+ try { window.RTCIceCandidate = undefined; } catch(e) {}
678
+ try { window.RTCDataChannel = undefined; } catch(e) {}`
679
+ : `
680
+ var _origRTCPeerConnection = window.RTCPeerConnection;
681
+ if (_origRTCPeerConnection) {
682
+ window.RTCPeerConnection = function(config, constraints) {
683
+ if (!__isApprovedWebRtcConfiguration(config)) {
684
+ throw new DOMException(
685
+ 'RTCPeerConnection is blocked for unapproved ICE servers in sandboxed products', 'SecurityError'
686
+ );
687
+ }
688
+ return new _origRTCPeerConnection(config, constraints);
689
+ };
690
+ window.RTCPeerConnection.prototype = _origRTCPeerConnection.prototype;
691
+ }`;
692
+ return `(function() {
693
+ 'use strict';
694
+ ${remoteHelpers}
695
+ ${navigationSetup}
696
+
697
+ // Network APIs
698
+ ${webSocketSetup}
699
+ try { window.EventSource = undefined; } catch(e) {}
700
+ try { window.Worker = undefined; } catch(e) {}
701
+ ${networkSetup}
702
+
703
+ var __serviceWorker;
704
+ try { __serviceWorker = navigator.serviceWorker; } catch (e) { __serviceWorker = null; }
705
+ if (__serviceWorker) {
706
+ try {
707
+ Object.defineProperty(navigator, 'serviceWorker', {
708
+ get: function() {
709
+ throw new DOMException(
710
+ 'serviceWorker is blocked in sandboxed products', 'SecurityError'
711
+ );
712
+ },
713
+ configurable: false,
714
+ });
715
+ } catch (e) { /* already non-configurable on this engine — silently accept */ }
716
+ }
717
+
718
+ if (navigator.sendBeacon) {
719
+ try {
720
+ Object.defineProperty(navigator, 'sendBeacon', {
721
+ value: function() { return false; },
722
+ writable: false,
723
+ configurable: false,
724
+ });
725
+ } catch (e) { /* already non-configurable on this engine — silently accept */ }
726
+ }
727
+
728
+ // WebRTC
729
+ ${webRtcSetup}
730
+
731
+ // Cross-product communication
732
+ try { window.SharedWorker = undefined; } catch(e) {}
733
+ try { window.BroadcastChannel = undefined; } catch(e) {}
734
+
735
+ // localStorage — products must use the host bridge for persistence
736
+ try {
737
+ Object.defineProperty(window, 'localStorage', {
738
+ get: function() {
739
+ throw new DOMException(
740
+ 'localStorage is blocked in sandboxed products; use the host bridge', 'SecurityError'
741
+ );
742
+ },
743
+ configurable: false,
744
+ });
745
+ } catch (e) {}
746
+
747
+ if (window.caches) {
748
+ try {
749
+ Object.defineProperty(window.caches, 'open', {
750
+ value: function() {
751
+ throw new DOMException('caches.open is blocked in sandboxed products', 'SecurityError');
752
+ },
753
+ writable: false,
754
+ configurable: false,
755
+ });
756
+ } catch (e) {}
757
+ try {
758
+ Object.defineProperty(window.caches, 'delete', {
759
+ value: function() {
760
+ throw new DOMException('caches.delete is blocked in sandboxed products', 'SecurityError');
761
+ },
762
+ writable: false,
763
+ configurable: false,
764
+ });
765
+ } catch (e) {}
766
+ }
767
+
768
+ // Cookies
769
+ try {
770
+ Object.defineProperty(document, 'cookie', {
771
+ get: function() { return ''; },
772
+ set: function() {},
773
+ configurable: false,
774
+ });
775
+ } catch (e) {}
776
+
777
+ // Wallet extension sniffing
778
+ ['injectedWeb3', 'polkadot', 'ethereum'].forEach(function(key) {
779
+ try {
780
+ Object.defineProperty(window, key, {
781
+ value: undefined,
782
+ writable: false,
783
+ configurable: false,
784
+ });
785
+ } catch (e) {}
786
+ });
787
+
788
+ // Nested iframes — CSP meta tags don't propagate to child frames.
789
+ var _origCreateElement = document.createElement.bind(document);
790
+ document.createElement = function(tag) {
791
+ if (tag && tag.toLowerCase() === 'iframe') {
792
+ throw new DOMException('iframe creation is blocked in sandboxed products', 'SecurityError');
793
+ }
794
+ return _origCreateElement.apply(this, arguments);
795
+ };
796
+ // createElementNS is a separate code path that also produces elements.
797
+ var _origCreateElementNS = document.createElementNS.bind(document);
798
+ document.createElementNS = function(ns, tag) {
799
+ if (tag && tag.toLowerCase() === 'iframe') {
800
+ throw new DOMException('iframe creation is blocked in sandboxed products', 'SecurityError');
801
+ }
802
+ return _origCreateElementNS.apply(this, arguments);
803
+ };
804
+ })();`;
805
+ }
806
+ export const LOCKDOWN_SCRIPT = buildLockdownScript();
807
+ /**
808
+ * Development monitor script: wraps every blocked API with an instrumented stub
809
+ * that sends a SANDBOX_VIOLATION postMessage before throwing SecurityError.
810
+ *
811
+ * Use this instead of LOCKDOWN_SCRIPT during development by passing
812
+ * `enableViolationMonitor: true` to `mountProductView`.
813
+ *
814
+ * Mirrors `SANDBOX_MONITOR_SCRIPT` from `host-product-view/src/security.rs`.
815
+ */
816
+ export function buildMonitorScript(approvedNetworkOrigins = [], approvedWebSocketOrigins = [], approvedWebRtcServerUrls = []) {
817
+ const normalized = normalizeApprovedNetworkOrigins(approvedNetworkOrigins);
818
+ const normalizedWebSockets = normalizeApprovedWebSocketOrigins(approvedWebSocketOrigins);
819
+ const normalizedWebRtc = normalizeApprovedWebRtcServerUrls(approvedWebRtcServerUrls);
820
+ const remoteHelpers = normalized.length > 0 || normalizedWebSockets.length > 0 || normalizedWebRtc.length > 0
821
+ ? buildAllowedNetworkHelpers(normalized, normalizedWebSockets, normalizedWebRtc)
822
+ : "";
823
+ const navigationSetup = buildNavigationGuardBlock("monitor");
824
+ const networkSetup = normalized.length === 0
825
+ ? `
826
+ var _origFetch = window.fetch;
827
+ if (_origFetch) {
828
+ window.fetch = function(input, init) {
829
+ var url;
830
+ try {
831
+ url = (typeof input === 'string') ? input : (input.url || String(input));
832
+ var parsed = new URL(url, location.href);
833
+ // In a sandboxed srcdoc iframe location.origin is the string "null";
834
+ // always report since origin comparison is a no-op in that context.
835
+ if (location.origin === 'null' || parsed.origin !== location.origin) {
836
+ report('fetch', { url: url, origin: parsed.origin });
837
+ }
838
+ } catch (e) {
839
+ report('fetch', { url: String(url) });
840
+ }
841
+ return _origFetch.apply(this, arguments);
842
+ };
843
+ }
844
+
845
+ var _origXhrOpen = XMLHttpRequest.prototype.open;
846
+ if (_origXhrOpen) {
847
+ XMLHttpRequest.prototype.open = function(method, url) {
848
+ try {
849
+ var parsed = new URL(String(url), location.href);
850
+ // In a sandboxed srcdoc iframe location.origin is the string "null";
851
+ // always report since origin comparison is a no-op in that context.
852
+ if (location.origin === 'null' || parsed.origin !== location.origin) {
853
+ report('XMLHttpRequest', { method: String(method), url: String(url), origin: parsed.origin });
854
+ }
855
+ } catch (e) {
856
+ report('XMLHttpRequest', { method: String(method), url: String(url) });
857
+ }
858
+ return _origXhrOpen.apply(this, arguments);
859
+ };
860
+ }
861
+ `
862
+ : `
863
+ var _origFetch = window.fetch;
864
+ if (_origFetch) {
865
+ window.fetch = function(input, init) {
866
+ var url = (typeof input === 'string') ? input : (input && input.url ? input.url : String(input));
867
+ if (!__isApprovedNetworkUrl(url)) {
868
+ report('fetch', { url: String(url) });
869
+ }
870
+ return _origFetch.apply(this, arguments);
871
+ };
872
+ }
873
+
874
+ var _origXhrOpen = XMLHttpRequest.prototype.open;
875
+ if (_origXhrOpen) {
876
+ XMLHttpRequest.prototype.open = function(method, url) {
877
+ if (!__isApprovedNetworkUrl(url)) {
878
+ report('XMLHttpRequest', { method: String(method), url: String(url) });
879
+ }
880
+ return _origXhrOpen.apply(this, arguments);
881
+ };
882
+ }
883
+ `;
884
+ const webSocketSetup = normalizedWebSockets.length === 0
885
+ ? `window.WebSocket = function(url) { reportAndThrow('WebSocket', { url: String(url) }); };`
886
+ : `
887
+ var _origWebSocket = window.WebSocket;
888
+ if (_origWebSocket) {
889
+ window.WebSocket = function(url, protocols) {
890
+ if (!__isApprovedWebSocketUrl(url)) {
891
+ reportAndThrow('WebSocket', { origin: __extractApprovedWebSocketOrigin(url) || String(url) });
892
+ }
893
+ return new _origWebSocket(url, protocols);
894
+ };
895
+ window.WebSocket.prototype = _origWebSocket.prototype;
896
+ }`;
897
+ const webRtcSetup = normalizedWebRtc.length === 0
898
+ ? `
899
+ window.RTCPeerConnection = function() { reportAndThrow('RTCPeerConnection', {}); };
900
+ window.RTCSessionDescription = function() { reportAndThrow('RTCSessionDescription', {}); };
901
+ window.RTCIceCandidate = function() { reportAndThrow('RTCIceCandidate', {}); };
902
+ window.RTCDataChannel = function() { reportAndThrow('RTCDataChannel', {}); };`
903
+ : `
904
+ var _origRTCPeerConnection = window.RTCPeerConnection;
905
+ if (_origRTCPeerConnection) {
906
+ window.RTCPeerConnection = function(config, constraints) {
907
+ if (!__isApprovedWebRtcConfiguration(config)) {
908
+ reportAndThrow('RTCPeerConnection', { server: __extractFirstWebRtcServerUrl(config) || 'unknown' });
909
+ }
910
+ return new _origRTCPeerConnection(config, constraints);
911
+ };
912
+ window.RTCPeerConnection.prototype = _origRTCPeerConnection.prototype;
913
+ }`;
914
+ return `(function() {
915
+ 'use strict';
916
+ ${remoteHelpers}
917
+
918
+ // report() only sends the violation postMessage — it does NOT throw.
919
+ // In monitor mode the intent is to observe, not block. Network wrappers
920
+ // call report() and then proceed with the original request. Hard-blocked
921
+ // APIs (storage, WebSocket, etc.) use reportAndThrow() instead.
922
+ function report(api, details) {
923
+ var payload = {
924
+ type: 'SANDBOX_VIOLATION',
925
+ api: api,
926
+ details: details || {},
927
+ timestamp: Date.now(),
928
+ };
929
+ try {
930
+ var target = (window.parent !== window) ? window.parent : window;
931
+ target.postMessage(payload, '*');
932
+ } catch (e) {}
933
+ }
934
+
935
+ function reportAndThrow(api, details) {
936
+ report(api, details);
937
+ throw new DOMException(api + ' is blocked in sandboxed products', 'SecurityError');
938
+ }
939
+
940
+ ${navigationSetup}
941
+
942
+ // Network APIs
943
+ ${webSocketSetup}
944
+ window.EventSource = function(url) { reportAndThrow('EventSource', { url: String(url) }); };
945
+ window.Worker = function(url) { reportAndThrow('Worker', { url: String(url) }); };
946
+
947
+ var __serviceWorker;
948
+ try { __serviceWorker = navigator.serviceWorker; } catch (e) { __serviceWorker = null; }
949
+ if (__serviceWorker) {
950
+ try {
951
+ Object.defineProperty(navigator, 'serviceWorker', {
952
+ get: function() { reportAndThrow('ServiceWorker', {}); },
953
+ configurable: false,
954
+ });
955
+ } catch (e) { /* already non-configurable on this engine — silently accept */ }
956
+ }
957
+ ${networkSetup}
958
+
959
+ if (navigator.sendBeacon) {
960
+ try {
961
+ Object.defineProperty(navigator, 'sendBeacon', {
962
+ value: function(url) { reportAndThrow('sendBeacon', { url: String(url) }); },
963
+ writable: false,
964
+ configurable: false,
965
+ });
966
+ } catch (e) { /* already non-configurable on this engine — silently accept */ }
967
+ }
968
+
969
+ // WebRTC
970
+ ${webRtcSetup}
971
+
972
+ // Cross-product communication
973
+ window.SharedWorker = function(url) { reportAndThrow('SharedWorker', { url: String(url) }); };
974
+ window.BroadcastChannel = function(name) { reportAndThrow('BroadcastChannel', { name: String(name) }); };
975
+
976
+ // Storage
977
+ try {
978
+ Object.defineProperty(window, 'localStorage', {
979
+ get: function() { reportAndThrow('localStorage', {}); },
980
+ configurable: false,
981
+ });
982
+ } catch (e) {}
983
+
984
+ if (window.caches) {
985
+ try {
986
+ Object.defineProperty(window.caches, 'open', {
987
+ value: function(cacheName) { reportAndThrow('caches.open', { cacheName: String(cacheName) }); },
988
+ writable: false,
989
+ configurable: false,
990
+ });
991
+ } catch (e) {}
992
+ try {
993
+ Object.defineProperty(window.caches, 'delete', {
994
+ value: function(cacheName) { reportAndThrow('caches.delete', { cacheName: String(cacheName) }); },
995
+ writable: false,
996
+ configurable: false,
997
+ });
998
+ } catch (e) {}
999
+ }
1000
+
1001
+ // Cookies
1002
+ try {
1003
+ Object.defineProperty(document, 'cookie', {
1004
+ get: function() { reportAndThrow('document.cookie (read)', {}); },
1005
+ set: function(value) { reportAndThrow('document.cookie (write)', { value: String(value).slice(0, 64) }); },
1006
+ configurable: false,
1007
+ });
1008
+ } catch (e) {}
1009
+
1010
+ // Wallet extension sniffing
1011
+ ['injectedWeb3', 'polkadot', 'ethereum'].forEach(function(key) {
1012
+ try {
1013
+ Object.defineProperty(window, key, {
1014
+ get: function() { reportAndThrow('window.' + key, {}); },
1015
+ set: function() { reportAndThrow('window.' + key + ' (set)', {}); },
1016
+ configurable: false,
1017
+ });
1018
+ } catch (e) {}
1019
+ });
1020
+
1021
+ // Nested iframes
1022
+ var _origCreateElement = document.createElement.bind(document);
1023
+ document.createElement = function(tag) {
1024
+ if (tag && tag.toLowerCase() === 'iframe') {
1025
+ reportAndThrow('createElement(iframe)', { tag: tag });
1026
+ }
1027
+ return _origCreateElement.apply(this, arguments);
1028
+ };
1029
+ // createElementNS is a separate code path that also produces elements.
1030
+ var _origCreateElementNS = document.createElementNS.bind(document);
1031
+ document.createElementNS = function(ns, tag) {
1032
+ if (tag && tag.toLowerCase() === 'iframe') {
1033
+ reportAndThrow('createElementNS(iframe)', { tag: tag });
1034
+ }
1035
+ return _origCreateElementNS.apply(this, arguments);
1036
+ };
1037
+ })();`;
1038
+ }
1039
+ export const MONITOR_SCRIPT = buildMonitorScript();
1040
+ /**
1041
+ * Interactive sandbox script: wraps network APIs with a mutable allowlist.
1042
+ *
1043
+ * - fetch/XHR: checked against an origin allowlist. Unapproved requests
1044
+ * throw SecurityError and emit SANDBOX_VIOLATION with `disposition: "promptable"`.
1045
+ * - Grant delivery: the host sends `{ type: "sandbox-grant", origin, kind: "network" }`
1046
+ * over a dedicated MessagePort created by the iframe bridge. The sandbox
1047
+ * re-validates and adds the origin, then deletes the temporary window handle
1048
+ * before product code runs.
1049
+ * - Non-network APIs: blocked + reported with `disposition: "always_blocked"`.
1050
+ * - fetch/XHR wrappers use `configurable: false, writable: false` to prevent bypass.
1051
+ * - The allowlist Set is frozen after each mutation to prevent product manipulation.
1052
+ *
1053
+ * Mirrors `generate_interactive_sandbox_script()` from `host-product-view/src/security.rs`.
1054
+ *
1055
+ * @internal Exported for testing only.
1056
+ */
1057
+ export function buildInteractiveScript(approvedNetworkOrigins = [], approvedWebSocketOrigins = [], approvedWebRtcServerUrls = []) {
1058
+ const origins = JSON.stringify(normalizeApprovedNetworkOrigins(approvedNetworkOrigins));
1059
+ const webSocketOrigins = JSON.stringify(normalizeApprovedWebSocketOrigins(approvedWebSocketOrigins));
1060
+ const webRtcServerUrls = JSON.stringify(normalizeApprovedWebRtcServerUrls(approvedWebRtcServerUrls));
1061
+ const navigationSetup = buildNavigationGuardBlock("interactive");
1062
+ return `(function() {
1063
+ 'use strict';
1064
+
1065
+ // ── Mutable allowlist (frozen after each mutation) ────────────────────
1066
+ var __origins = new Set(${origins});
1067
+ var __webSocketOrigins = new Set(${webSocketOrigins});
1068
+ var __webRtcServerUrls = new Set(${webRtcServerUrls});
1069
+ Object.freeze(__origins);
1070
+ Object.freeze(__webSocketOrigins);
1071
+ Object.freeze(__webRtcServerUrls);
1072
+
1073
+ function __addOrigin(origin) {
1074
+ // Re-validate defense-in-depth: must be https://, no wildcards, no paths
1075
+ if (typeof origin !== 'string') return;
1076
+ if (origin.indexOf('*') !== -1) return;
1077
+ if (!origin.match(/^https:\\/\\/[^\\/\\s?#]+$/)) return;
1078
+ try { new URL(origin); } catch (e) { return; }
1079
+ // Rebuild as a new frozen Set — freeze before assigning so the unfrozen
1080
+ // Set is never observable through __origins.
1081
+ var next = new Set(__origins);
1082
+ next.add(origin);
1083
+ Object.freeze(next);
1084
+ __origins = next;
1085
+ }
1086
+ function __addWebSocketOrigin(origin) {
1087
+ if (typeof origin !== 'string') return;
1088
+ if (origin.indexOf('*') !== -1) return;
1089
+ if (!origin.match(/^wss:\\/\\/[^\\/\\s?#]+$/)) return;
1090
+ try { new URL(origin); } catch (e) { return; }
1091
+ var next = new Set(__webSocketOrigins);
1092
+ next.add(origin);
1093
+ Object.freeze(next);
1094
+ __webSocketOrigins = next;
1095
+ }
1096
+ function __addWebRtcServerUrl(url) {
1097
+ if (typeof url !== 'string') return;
1098
+ var trimmed = url.trim();
1099
+ if (!trimmed.match(/^(stun|turn|turns):/i)) return;
1100
+ if (trimmed.indexOf('?') !== -1 || trimmed.indexOf('#') !== -1 || trimmed.indexOf(' ') !== -1) {
1101
+ return;
1102
+ }
1103
+ var next = new Set(__webRtcServerUrls);
1104
+ next.add(trimmed);
1105
+ Object.freeze(next);
1106
+ __webRtcServerUrls = next;
1107
+ }
1108
+
1109
+ function __isAllowed(input) {
1110
+ try {
1111
+ var url = (typeof input === 'string') ? input
1112
+ : (input && typeof input === 'object' && typeof input.url === 'string') ? input.url
1113
+ : String(input);
1114
+ var parsed = new URL(url, location.href);
1115
+ if (parsed.protocol !== 'https:') return false;
1116
+ return __origins.has(parsed.origin);
1117
+ } catch (e) {
1118
+ return false;
1119
+ }
1120
+ }
1121
+
1122
+ function __extractOrigin(input) {
1123
+ try {
1124
+ var url = (typeof input === 'string') ? input
1125
+ : (input && typeof input === 'object' && typeof input.url === 'string') ? input.url
1126
+ : String(input);
1127
+ var parsed = new URL(url, location.href);
1128
+ return parsed.protocol === 'https:' ? parsed.origin : null;
1129
+ } catch (e) {
1130
+ return null;
1131
+ }
1132
+ }
1133
+ function __extractWebSocketOrigin(input) {
1134
+ try {
1135
+ var parsed = new URL(String(input), location.href);
1136
+ return parsed.protocol === 'wss:' ? parsed.origin : null;
1137
+ } catch (e) {
1138
+ return null;
1139
+ }
1140
+ }
1141
+ function __isAllowedWebSocket(input) {
1142
+ var origin = __extractWebSocketOrigin(input);
1143
+ return origin !== null && __webSocketOrigins.has(origin);
1144
+ }
1145
+ function __normalizeWebRtcServerUrl(input) {
1146
+ if (typeof input !== 'string') return null;
1147
+ var trimmed = input.trim();
1148
+ if (!trimmed.match(/^(stun|turn|turns):/i)) return null;
1149
+ if (trimmed.indexOf('?') !== -1 || trimmed.indexOf('#') !== -1 || trimmed.indexOf(' ') !== -1) {
1150
+ return null;
1151
+ }
1152
+ return trimmed;
1153
+ }
1154
+ function __extractFirstWebRtcServerUrl(config) {
1155
+ if (!config || !config.iceServers) return null;
1156
+ var iceServers = Array.isArray(config.iceServers) ? config.iceServers : [config.iceServers];
1157
+ for (var i = 0; i < iceServers.length; i++) {
1158
+ var entry = iceServers[i];
1159
+ if (!entry) continue;
1160
+ var urls = entry.urls;
1161
+ if (Array.isArray(urls) && urls.length > 0) {
1162
+ return __normalizeWebRtcServerUrl(String(urls[0]));
1163
+ }
1164
+ if (typeof urls === 'string') {
1165
+ return __normalizeWebRtcServerUrl(urls);
1166
+ }
1167
+ if (typeof entry.url === 'string') {
1168
+ return __normalizeWebRtcServerUrl(entry.url);
1169
+ }
1170
+ }
1171
+ return null;
1172
+ }
1173
+ function __isAllowedWebRtc(config) {
1174
+ if (!config || !config.iceServers) return false;
1175
+ var iceServers = Array.isArray(config.iceServers) ? config.iceServers : [config.iceServers];
1176
+ if (iceServers.length === 0) return false;
1177
+ for (var i = 0; i < iceServers.length; i++) {
1178
+ var entry = iceServers[i];
1179
+ if (!entry) return false;
1180
+ var urls = entry.urls;
1181
+ var values = Array.isArray(urls)
1182
+ ? urls
1183
+ : (typeof urls === 'string' ? [urls] : (typeof entry.url === 'string' ? [entry.url] : []));
1184
+ if (values.length === 0) return false;
1185
+ for (var j = 0; j < values.length; j++) {
1186
+ var normalizedUrl = __normalizeWebRtcServerUrl(String(values[j]));
1187
+ if (!normalizedUrl || !__webRtcServerUrls.has(normalizedUrl)) {
1188
+ return false;
1189
+ }
1190
+ }
1191
+ }
1192
+ return true;
1193
+ }
1194
+
1195
+ // ── Violation reporter ───────────────────────────────────────────────
1196
+ function report(api, details, disposition) {
1197
+ var payload = {
1198
+ type: 'SANDBOX_VIOLATION',
1199
+ api: api,
1200
+ details: details || {},
1201
+ disposition: disposition || 'always_blocked',
1202
+ timestamp: Date.now(),
1203
+ };
1204
+ try {
1205
+ var target = (window.parent !== window) ? window.parent : window;
1206
+ target.postMessage(payload, '*');
1207
+ } catch (e) { /* postMessage failure must never suppress the throw below */ }
1208
+ throw new DOMException(
1209
+ api + ' is blocked in sandboxed products', 'SecurityError'
1210
+ );
1211
+ }
1212
+
1213
+ ${navigationSetup}
1214
+
1215
+ // ── Grant listener ───────────────────────────────────────────────────
1216
+ // Consume the dedicated grant port created by buildIframeBridgeScript().
1217
+ // The temporary window handle is deleted before any product code runs so
1218
+ // products cannot self-grant origins.
1219
+ var __grantPort = window.__HOST_SANDBOX_GRANT_PORT__;
1220
+ if (__grantPort) {
1221
+ __grantPort.start();
1222
+ __grantPort.onmessage = function(ev) {
1223
+ if (ev.data && ev.data.type === 'sandbox-grant') {
1224
+ if (ev.data.kind === 'network') {
1225
+ __addOrigin(ev.data.origin);
1226
+ } else if (ev.data.kind === 'websocket') {
1227
+ __addWebSocketOrigin(ev.data.origin);
1228
+ } else if (ev.data.kind === 'webrtc') {
1229
+ __addWebRtcServerUrl(ev.data.url);
1230
+ }
1231
+ }
1232
+ };
1233
+ try { delete window.__HOST_SANDBOX_GRANT_PORT__; } catch (e) {}
1234
+ }
1235
+
1236
+ // ── fetch wrapper (primary interactive gate) ─────────────────────────
1237
+ var _origFetch = window.fetch;
1238
+ if (_origFetch) {
1239
+ Object.defineProperty(window, 'fetch', {
1240
+ value: function(input, init) {
1241
+ if (__isAllowed(input)) {
1242
+ return _origFetch.apply(this, arguments);
1243
+ }
1244
+ var origin = __extractOrigin(input);
1245
+ var url = (typeof input === 'string') ? input
1246
+ : (input && typeof input === 'object' && typeof input.url === 'string') ? input.url
1247
+ : String(input);
1248
+ if (origin) {
1249
+ // Report origin only — do not leak full URL path/query to parent frame
1250
+ report('fetch', { origin: origin, method: (init && init.method) || 'GET' }, 'promptable');
1251
+ } else {
1252
+ report('fetch', {}, 'always_blocked');
1253
+ }
1254
+ },
1255
+ writable: false,
1256
+ configurable: false,
1257
+ });
1258
+ }
1259
+
1260
+ // ── XHR wrapper ──────────────────────────────────────────────────────
1261
+ var _origXhrOpen = XMLHttpRequest.prototype.open;
1262
+ if (_origXhrOpen) {
1263
+ Object.defineProperty(XMLHttpRequest.prototype, 'open', {
1264
+ value: function(method, url) {
1265
+ if (__isAllowed(url)) {
1266
+ return _origXhrOpen.apply(this, arguments);
1267
+ }
1268
+ var origin = __extractOrigin(url);
1269
+ if (origin) {
1270
+ // Report origin only — do not leak full URL path/query to parent frame
1271
+ report('XMLHttpRequest', { method: String(method), origin: origin }, 'promptable');
1272
+ } else {
1273
+ report('XMLHttpRequest', { method: String(method) }, 'always_blocked');
1274
+ }
1275
+ },
1276
+ writable: false,
1277
+ configurable: false,
1278
+ });
1279
+ }
1280
+
1281
+ // ── Non-network APIs: blocked + reported ─────────────────────────────
1282
+
1283
+ var _origWebSocket = window.WebSocket;
1284
+ if (_origWebSocket) {
1285
+ window.WebSocket = function(url, protocols) {
1286
+ if (__isAllowedWebSocket(url)) {
1287
+ return new _origWebSocket(url, protocols);
1288
+ }
1289
+ var origin = __extractWebSocketOrigin(url);
1290
+ if (origin) {
1291
+ report('WebSocket', { origin: origin }, 'promptable');
1292
+ }
1293
+ report('WebSocket', { url: String(url) }, 'always_blocked');
1294
+ };
1295
+ window.WebSocket.prototype = _origWebSocket.prototype;
1296
+ }
1297
+
1298
+ window.EventSource = function(url) {
1299
+ report('EventSource', { url: String(url) }, 'always_blocked');
1300
+ };
1301
+
1302
+ window.Worker = function(url) {
1303
+ report('Worker', { url: String(url) }, 'always_blocked');
1304
+ };
1305
+
1306
+ var __serviceWorker;
1307
+ try { __serviceWorker = navigator.serviceWorker; } catch (e) { __serviceWorker = null; }
1308
+ if (__serviceWorker) {
1309
+ try {
1310
+ Object.defineProperty(navigator, 'serviceWorker', {
1311
+ get: function() { report('ServiceWorker', {}, 'always_blocked'); },
1312
+ configurable: false,
1313
+ });
1314
+ } catch (e) { /* already non-configurable on this engine — silently accept */ }
1315
+ }
1316
+
1317
+ if (navigator.sendBeacon) {
1318
+ try {
1319
+ Object.defineProperty(navigator, 'sendBeacon', {
1320
+ value: function(url) {
1321
+ report('sendBeacon', { url: String(url) }, 'always_blocked');
1322
+ },
1323
+ writable: false,
1324
+ configurable: false,
1325
+ });
1326
+ } catch (e) { /* already non-configurable on this engine — silently accept */ }
1327
+ }
1328
+
1329
+ // WebRTC
1330
+ var _origRTCPeerConnection = window.RTCPeerConnection;
1331
+ if (_origRTCPeerConnection) {
1332
+ window.RTCPeerConnection = function(config, constraints) {
1333
+ if (__isAllowedWebRtc(config)) {
1334
+ return new _origRTCPeerConnection(config, constraints);
1335
+ }
1336
+ var server = __extractFirstWebRtcServerUrl(config);
1337
+ if (server) {
1338
+ report('RTCPeerConnection', { server: server }, 'promptable');
1339
+ }
1340
+ report('RTCPeerConnection', {}, 'always_blocked');
1341
+ };
1342
+ window.RTCPeerConnection.prototype = _origRTCPeerConnection.prototype;
1343
+ }
1344
+ window.RTCSessionDescription = function() {
1345
+ report('RTCSessionDescription', {}, 'always_blocked');
1346
+ };
1347
+ window.RTCIceCandidate = function() {
1348
+ report('RTCIceCandidate', {}, 'always_blocked');
1349
+ };
1350
+ window.RTCDataChannel = function() {
1351
+ report('RTCDataChannel', {}, 'always_blocked');
1352
+ };
1353
+
1354
+ // Cross-product communication
1355
+ window.SharedWorker = function(url) {
1356
+ report('SharedWorker', { url: String(url) }, 'always_blocked');
1357
+ };
1358
+ window.BroadcastChannel = function(name) {
1359
+ report('BroadcastChannel', { name: String(name) }, 'always_blocked');
1360
+ };
1361
+
1362
+ // Storage
1363
+ try {
1364
+ Object.defineProperty(window, 'localStorage', {
1365
+ get: function() { report('localStorage', {}, 'always_blocked'); },
1366
+ configurable: false,
1367
+ });
1368
+ } catch (e) { /* already non-configurable — silently accept */ }
1369
+
1370
+ if (window.caches) {
1371
+ try {
1372
+ Object.defineProperty(window.caches, 'open', {
1373
+ value: function(cacheName) {
1374
+ report('caches.open', { cacheName: String(cacheName) }, 'always_blocked');
1375
+ },
1376
+ writable: false,
1377
+ configurable: false,
1378
+ });
1379
+ } catch (e) {}
1380
+ try {
1381
+ Object.defineProperty(window.caches, 'delete', {
1382
+ value: function(cacheName) {
1383
+ report('caches.delete', { cacheName: String(cacheName) }, 'always_blocked');
1384
+ },
1385
+ writable: false,
1386
+ configurable: false,
1387
+ });
1388
+ } catch (e) {}
1389
+ }
1390
+
1391
+ // Cookies
1392
+ try {
1393
+ Object.defineProperty(document, 'cookie', {
1394
+ get: function() {
1395
+ report('document.cookie (read)', {}, 'always_blocked');
1396
+ },
1397
+ set: function(value) {
1398
+ report('document.cookie (write)', { value: String(value).slice(0, 64) }, 'always_blocked');
1399
+ },
1400
+ configurable: false,
1401
+ });
1402
+ } catch (e) { /* non-configurable environment — silently accept */ }
1403
+
1404
+ // Wallet extension sniffing
1405
+ ['injectedWeb3', 'polkadot', 'ethereum'].forEach(function(key) {
1406
+ try {
1407
+ Object.defineProperty(window, key, {
1408
+ get: function() { report('window.' + key, {}, 'always_blocked'); },
1409
+ set: function(v) { report('window.' + key + ' (set)', {}, 'always_blocked'); },
1410
+ configurable: false,
1411
+ });
1412
+ } catch (e) { /* already non-configurable — silently accept */ }
1413
+ });
1414
+
1415
+ // Nested iframes
1416
+ var _origCreateElement = document.createElement.bind(document);
1417
+ document.createElement = function(tag) {
1418
+ if (tag && tag.toLowerCase() === 'iframe') {
1419
+ report('createElement(iframe)', { tag: tag }, 'always_blocked');
1420
+ }
1421
+ return _origCreateElement.apply(this, arguments);
1422
+ };
1423
+ // createElementNS is a separate code path that also produces elements.
1424
+ var _origCreateElementNS = document.createElementNS.bind(document);
1425
+ document.createElementNS = function(ns, tag) {
1426
+ if (tag && tag.toLowerCase() === 'iframe') {
1427
+ report('createElementNS(iframe)', { tag: tag }, 'always_blocked');
1428
+ }
1429
+ return _origCreateElementNS.apply(this, arguments);
1430
+ };
1431
+ })();`;
1432
+ }
1433
+ export const INTERACTIVE_SCRIPT = buildInteractiveScript();
1434
+ /** Content-Security-Policy for interactive sandbox mode (connect-src https: wss:). */
1435
+ export const IFRAME_CSP_INTERACTIVE = "default-src 'none'; " +
1436
+ "script-src 'unsafe-inline' 'wasm-unsafe-eval' data: blob:; " +
1437
+ "style-src 'unsafe-inline'; " +
1438
+ "frame-src 'none'; " +
1439
+ "connect-src https: wss:; " +
1440
+ "img-src blob: data:; " +
1441
+ "font-src 'none'; " +
1442
+ "media-src 'none'; " +
1443
+ "object-src 'none'; " +
1444
+ "base-uri 'none'; " +
1445
+ "form-action 'none'";
1446
+ export const IFRAME_CSP_URL_BACKED_INTERACTIVE = "default-src 'none'; " +
1447
+ "script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' data: blob:; " +
1448
+ "style-src 'self' 'unsafe-inline'; " +
1449
+ "frame-src 'none'; " +
1450
+ "connect-src 'self' https: wss:; " +
1451
+ "img-src 'self' blob: data:; " +
1452
+ "font-src 'self'; " +
1453
+ "media-src 'none'; " +
1454
+ "object-src 'none'; " +
1455
+ "base-uri 'self'; " +
1456
+ "form-action 'none'";
1457
+ // ---------------------------------------------------------------------------
1458
+ // srcdoc builder
1459
+ // ---------------------------------------------------------------------------
1460
+ /**
1461
+ * Decode a Uint8Array to a UTF-8 string.
1462
+ * Uses TextDecoder when available (browser / Node 11+), otherwise falls back
1463
+ * to a pure-JS loop so the module remains dependency-free.
1464
+ */
1465
+ function decodeUtf8(bytes) {
1466
+ if (typeof TextDecoder !== "undefined") {
1467
+ return new TextDecoder().decode(bytes);
1468
+ }
1469
+ // Fallback: latin-1 safe for ASCII-only HTML; non-ASCII will be garbled
1470
+ // but the CSP will block any active exploits regardless.
1471
+ let out = "";
1472
+ for (let i = 0; i < bytes.length; i++) {
1473
+ out += String.fromCharCode(bytes[i]);
1474
+ }
1475
+ return out;
1476
+ }
1477
+ function encodeUtf8(source) {
1478
+ if (typeof TextEncoder !== "undefined") {
1479
+ return new TextEncoder().encode(source);
1480
+ }
1481
+ const out = new Uint8Array(source.length);
1482
+ for (let i = 0; i < source.length; i++) {
1483
+ out[i] = source.charCodeAt(i) & 0xff;
1484
+ }
1485
+ return out;
1486
+ }
1487
+ /**
1488
+ * Escape a raw HTML string for safe embedding as a <script> body.
1489
+ * Replaces `</script>` and `<!--` to prevent premature tag closure.
1490
+ */
1491
+ function escapeScriptContent(src) {
1492
+ return src.replace(/<\/script>/gi, "<\\/script>").replace(/<!--/g, "<\\!--");
1493
+ }
1494
+ /**
1495
+ * Build the full srcdoc string for the sandboxed iframe.
1496
+ *
1497
+ * Preserves the product's original `<head>` structure (meta tags, CSS links,
1498
+ * module scripts, import maps) and injects bridge/lockdown scripts before
1499
+ * any product content. Bundle-local CSS and JS assets referenced by the HTML
1500
+ * are inlined since srcdoc iframes cannot reliably fetch relative paths.
1501
+ *
1502
+ * @internal Exported for testing only — not part of the public API.
1503
+ */
1504
+ export function buildSrcdoc(assets, bridgeScript, sandboxScript, extensionInjectScript, approvedNetworkOrigins = [], approvedWebSocketOrigins = [], cspOverride, coreCapabilities) {
1505
+ const bridgeMetadataScript = buildBridgeMetadataScript(extensionInjectScript, "web-srcdoc", coreCapabilities);
1506
+ const sessionBootstrapScript = buildProductViewSessionBootstrapScript();
1507
+ const csp = cspOverride ?? buildIframeCsp(approvedNetworkOrigins, approvedWebSocketOrigins);
1508
+ const baseTag = `<base href="${escapeHtmlAttr(`${SYNTHETIC_PRODUCT_BUNDLE_ORIGIN}/`)}">`;
1509
+ const embeddedAssetScript = buildEmbeddedBundleAssetScript(assets);
1510
+ // Scripts that must run before any product JS.
1511
+ const extensionTag = extensionInjectScript.length > 0
1512
+ ? `<script>${escapeScriptContent(extensionInjectScript)}</script>`
1513
+ : "";
1514
+ const hostScripts = `<meta http-equiv="Content-Security-Policy" content="${csp}">` +
1515
+ baseTag +
1516
+ `<script>${escapeScriptContent(sessionBootstrapScript)}</script>` +
1517
+ `<script>${escapeScriptContent(bridgeScript)}</script>` +
1518
+ `<script>${escapeScriptContent(bridgeMetadataScript)}</script>` +
1519
+ extensionTag +
1520
+ `<script>${escapeScriptContent(sandboxScript)}</script>` +
1521
+ (embeddedAssetScript.length > 0
1522
+ ? `<script>${escapeScriptContent(embeddedAssetScript)}</script>`
1523
+ : "");
1524
+ const indexBytes = assets.get("index.html");
1525
+ if (indexBytes === undefined) {
1526
+ return `<!DOCTYPE html><html><head>${hostScripts}</head><body></body></html>`;
1527
+ }
1528
+ let html = decodeUtf8(indexBytes);
1529
+ // Find which JS/CSS paths are already referenced in the HTML so we
1530
+ // don't double-inline them.
1531
+ const referencedScripts = findReferencedPaths(html, /<script[^>]+src=["']([^"']+)["']/gi);
1532
+ const referencedStyles = findReferencedPaths(html, /<link[^>]+href=["']([^"']+)["']/gi);
1533
+ // Replace bundle-local <link rel="stylesheet" href="X"> with inline <style>.
1534
+ html = inlineLinkedStylesheets(html, assets);
1535
+ // Replace bundle-local <script src="X"> with inline <script> content.
1536
+ // Preserves type="module" and other attributes.
1537
+ html = inlineLinkedScripts(html, assets);
1538
+ // Rewrite remaining bundle-local HTML embeds (images, icons, posters, etc.)
1539
+ // to self-contained data URLs so the guest never falls back to the host origin.
1540
+ html = rewriteBundleLocalHtmlEmbeds(html, assets);
1541
+ // Strip bundle-local modulepreload hints. Once scripts are inlined into the
1542
+ // srcdoc document, these preloads no longer provide value and will otherwise
1543
+ // attempt blocked network fetches from the opaque iframe origin.
1544
+ html = stripBundleModulePreloads(html, assets);
1545
+ // Inject host scripts at the start of <head> (or create <head> if missing).
1546
+ html = injectIntoHead(html, hostScripts);
1547
+ const bundleImportMap = buildBundleImportMap(assets, referencedScripts);
1548
+ if (bundleImportMap.length > 0) {
1549
+ html = injectBeforeHeadClose(html, bundleImportMap);
1550
+ }
1551
+ // Inline unreferenced CSS assets at the end of <head>.
1552
+ const extraStyles = [];
1553
+ for (const [path, bytes] of assets) {
1554
+ if (!path.endsWith(".css"))
1555
+ continue;
1556
+ if (referencedStyles.has(path))
1557
+ continue;
1558
+ const src = rewriteCssWithEmbeddedAssets(decodeUtf8(bytes), assets);
1559
+ extraStyles.push(`<style>${escapeStyleContent(src)}</style>`);
1560
+ }
1561
+ if (extraStyles.length > 0) {
1562
+ html = injectBeforeHeadClose(html, extraStyles.join(""));
1563
+ }
1564
+ // Ensure DOCTYPE exists.
1565
+ if (!/<!DOCTYPE/i.test(html)) {
1566
+ html = "<!DOCTYPE html>" + html;
1567
+ }
1568
+ return html;
1569
+ }
1570
+ /**
1571
+ * Build a URL-backed product document.
1572
+ *
1573
+ * Unlike `buildSrcdoc()`, this path preserves the bundle's file structure and
1574
+ * expects the host to serve the returned assets from `routeBasePath`.
1575
+ */
1576
+ export function buildUrlBackedDocument(assets, bridgeScript, sandboxScript, extensionInjectScript, routeBasePath, approvedNetworkOrigins = [], approvedWebSocketOrigins = [], cspOverride, coreCapabilities) {
1577
+ const bridgeMetadataScript = buildBridgeMetadataScript(extensionInjectScript, "web-relay", coreCapabilities);
1578
+ const sessionBootstrapScript = buildProductViewSessionBootstrapScript();
1579
+ const csp = cspOverride ?? buildUrlBackedIframeCsp(approvedNetworkOrigins, approvedWebSocketOrigins);
1580
+ const normalizedBase = normalizeRouteBasePath(routeBasePath);
1581
+ const baseTag = `<base href="${escapeHtmlAttr(normalizedBase)}">`;
1582
+ const extensionTag = extensionInjectScript.length > 0
1583
+ ? `<script>${escapeScriptContent(extensionInjectScript)}</script>`
1584
+ : "";
1585
+ const hostScripts = `<meta http-equiv="Content-Security-Policy" content="${csp}">` +
1586
+ baseTag +
1587
+ `<script>${escapeScriptContent(sessionBootstrapScript)}</script>` +
1588
+ `<script>${escapeScriptContent(bridgeScript)}</script>` +
1589
+ `<script>${escapeScriptContent(bridgeMetadataScript)}</script>` +
1590
+ extensionTag +
1591
+ `<script>${escapeScriptContent(sandboxScript)}</script>`;
1592
+ const indexBytes = assets.get("index.html");
1593
+ if (indexBytes === undefined) {
1594
+ return `<!DOCTYPE html><html><head>${hostScripts}</head><body></body></html>`;
1595
+ }
1596
+ let html = decodeUtf8(indexBytes);
1597
+ html = rewriteBundleLocalHtmlPaths(html, assets, normalizedBase);
1598
+ html = rewriteBundleLocalTextPaths(html, assets, normalizedBase);
1599
+ html = injectIntoHead(html, hostScripts);
1600
+ if (!/<!DOCTYPE/i.test(html)) {
1601
+ html = "<!DOCTYPE html>" + html;
1602
+ }
1603
+ return html;
1604
+ }
1605
+ function normalizeRouteBasePath(routeBasePath) {
1606
+ const trimmed = routeBasePath.trim();
1607
+ if (trimmed === "" || trimmed === "/") {
1608
+ return "/";
1609
+ }
1610
+ const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
1611
+ return withLeadingSlash.endsWith("/") ? withLeadingSlash : `${withLeadingSlash}/`;
1612
+ }
1613
+ function resolveProductViewSessionToken(frameUrl) {
1614
+ try {
1615
+ const parsed = new URL(frameUrl, window.location.href);
1616
+ const directToken = parsed.searchParams.get(HOST_PRODUCT_VIEW_SESSION_QUERY_KEY);
1617
+ if (directToken != null && directToken !== "") {
1618
+ return directToken;
1619
+ }
1620
+ const nestedProductUrl = parsed.searchParams.get("productUrl");
1621
+ if (nestedProductUrl != null && nestedProductUrl !== "") {
1622
+ const nested = new URL(nestedProductUrl, parsed);
1623
+ const nestedToken = nested.searchParams.get(HOST_PRODUCT_VIEW_SESSION_QUERY_KEY);
1624
+ if (nestedToken != null && nestedToken !== "") {
1625
+ return nestedToken;
1626
+ }
1627
+ }
1628
+ }
1629
+ catch {
1630
+ return undefined;
1631
+ }
1632
+ return undefined;
1633
+ }
1634
+ function appendProductViewSessionToken(frameUrl, sessionToken) {
1635
+ const parsed = new URL(frameUrl, window.location.href);
1636
+ const nestedProductUrl = parsed.searchParams.get("productUrl");
1637
+ if (nestedProductUrl != null && nestedProductUrl !== "") {
1638
+ const nested = new URL(nestedProductUrl, parsed);
1639
+ nested.searchParams.set(HOST_PRODUCT_VIEW_SESSION_QUERY_KEY, sessionToken);
1640
+ parsed.searchParams.set("productUrl", nested.toString());
1641
+ return parsed.toString();
1642
+ }
1643
+ parsed.searchParams.set(HOST_PRODUCT_VIEW_SESSION_QUERY_KEY, sessionToken);
1644
+ return parsed.toString();
1645
+ }
1646
+ function rewriteBundleLocalHtmlPaths(html, assets, routeBasePath) {
1647
+ return html.replace(/\b(src|href|poster)\s*=\s*(["'])([^"']+)\2/gi, (fullMatch, attrName, quote, rawValue) => {
1648
+ if (!rawValue.startsWith("/")) {
1649
+ return fullMatch;
1650
+ }
1651
+ const normalized = normalizeAssetPath(rawValue);
1652
+ if (!assets.has(normalized)) {
1653
+ return fullMatch;
1654
+ }
1655
+ return `${attrName}=${quote}${routeBasePath}${normalized}${quote}`;
1656
+ });
1657
+ }
1658
+ function rewriteBundleLocalCssPaths(css, assets, routeBasePath) {
1659
+ const rewrittenUrls = css.replace(/url\(\s*(["']?)(\/[^"')]+)\1\s*\)/gi, (fullMatch, quote, rawValue) => {
1660
+ const normalized = normalizeAssetPath(rawValue);
1661
+ if (!assets.has(normalized)) {
1662
+ return fullMatch;
1663
+ }
1664
+ return `url(${quote}${routeBasePath}${normalized}${quote})`;
1665
+ });
1666
+ return rewrittenUrls.replace(/(@import\s+["'])(\/[^"']+)(["'])/gi, (fullMatch, prefix, rawValue, suffix) => {
1667
+ const normalized = normalizeAssetPath(rawValue);
1668
+ if (!assets.has(normalized)) {
1669
+ return fullMatch;
1670
+ }
1671
+ return `${prefix}${routeBasePath}${normalized}${suffix}`;
1672
+ });
1673
+ }
1674
+ function isBundleLocalAbsolutePath(path, assets) {
1675
+ if (path === "") {
1676
+ return false;
1677
+ }
1678
+ if (assets.has(path)) {
1679
+ return true;
1680
+ }
1681
+ const directoryPrefix = path.endsWith("/") ? path : `${path}/`;
1682
+ for (const assetPath of assets.keys()) {
1683
+ if (assetPath.startsWith(directoryPrefix)) {
1684
+ return true;
1685
+ }
1686
+ }
1687
+ return false;
1688
+ }
1689
+ function rewriteBundleLocalTextPaths(source, assets, routeBasePath) {
1690
+ const rewriteRawPath = (rawValue) => {
1691
+ if (rawValue.startsWith("//")) {
1692
+ return null;
1693
+ }
1694
+ const normalized = normalizeAssetPath(rawValue);
1695
+ if (!isBundleLocalAbsolutePath(normalized, assets)) {
1696
+ return null;
1697
+ }
1698
+ const suffixMatch = rawValue.match(/[?#].*$/);
1699
+ const suffix = suffixMatch?.[0] ?? "";
1700
+ const needsTrailingSlash = rawValue.endsWith("/") && !suffix && !normalized.endsWith("/");
1701
+ return `${routeBasePath}${normalized}${needsTrailingSlash ? "/" : ""}${suffix}`;
1702
+ };
1703
+ const rewriteDirectQuotes = source.replace(/(["'])((?![a-zA-Z][a-zA-Z0-9+.-]*:|\/\/|\.{1,2}\/|#)[^"'\\]+)\1/g, (fullMatch, quote, rawValue) => {
1704
+ const nextPath = rewriteRawPath(rawValue);
1705
+ return nextPath == null ? fullMatch : `${quote}${nextPath}${quote}`;
1706
+ });
1707
+ return rewriteDirectQuotes.replace(/(\\["'])((?![a-zA-Z][a-zA-Z0-9+.-]*:|\/\/|\.{1,2}\/|#)[^"'\\]+)(\\["'])/g, (fullMatch, prefix, rawValue, suffix) => {
1708
+ const nextPath = rewriteRawPath(rawValue);
1709
+ return nextPath == null ? fullMatch : `${prefix}${nextPath}${suffix}`;
1710
+ });
1711
+ }
1712
+ function rewriteBundleLocalHtmlEmbeds(html, assets) {
1713
+ return html.replace(/\b(src|href|poster)\s*=\s*(["'])([^"']+)\2/gi, (fullMatch, attrName, quote, rawValue) => {
1714
+ const normalized = normalizeAssetPath(rawValue);
1715
+ if (normalized === "") {
1716
+ return fullMatch;
1717
+ }
1718
+ const bytes = assets.get(normalized);
1719
+ if (bytes === undefined) {
1720
+ return fullMatch;
1721
+ }
1722
+ if (normalized.endsWith(".js") || normalized.endsWith(".css")) {
1723
+ return fullMatch;
1724
+ }
1725
+ return `${attrName}=${quote}${bytesToDataUrl(bytes, guessMimeType(normalized))}${quote}`;
1726
+ });
1727
+ }
1728
+ function rewriteCssWithEmbeddedAssets(css, assets) {
1729
+ const rewrittenUrls = css.replace(/url\(\s*(["']?)([^"')]+)\1\s*\)/gi, (fullMatch, quote, rawValue) => {
1730
+ const normalized = normalizeAssetPath(rawValue);
1731
+ const bytes = assets.get(normalized);
1732
+ if (bytes === undefined) {
1733
+ return fullMatch;
1734
+ }
1735
+ return `url(${quote}${bytesToDataUrl(bytes, guessMimeType(normalized))}${quote})`;
1736
+ });
1737
+ return rewrittenUrls.replace(/(@import\s+["'])([^"']+)(["'])/gi, (fullMatch, prefix, rawValue, suffix) => {
1738
+ const normalized = normalizeAssetPath(rawValue);
1739
+ const bytes = assets.get(normalized);
1740
+ if (bytes === undefined) {
1741
+ return fullMatch;
1742
+ }
1743
+ return `${prefix}${bytesToDataUrl(bytes, guessMimeType(normalized))}${suffix}`;
1744
+ });
1745
+ }
1746
+ /** Find all paths referenced by a regex with a capture group for the path. */
1747
+ function findReferencedPaths(html, regex) {
1748
+ const paths = new Set();
1749
+ let match;
1750
+ while ((match = regex.exec(html)) !== null) {
1751
+ const path = normalizeAssetPath(match[1]);
1752
+ paths.add(path);
1753
+ }
1754
+ return paths;
1755
+ }
1756
+ function normalizeAssetPath(path) {
1757
+ const stripped = path
1758
+ .replace(/^[./]+/, "")
1759
+ .replace(/^\//, "")
1760
+ .replace(/[?#].*$/, "");
1761
+ try {
1762
+ return decodeURIComponent(stripped);
1763
+ }
1764
+ catch {
1765
+ return stripped;
1766
+ }
1767
+ }
1768
+ /** Replace <link rel="stylesheet" href="X"> with <style>content</style> for bundle-local assets. */
1769
+ function inlineLinkedStylesheets(html, assets) {
1770
+ return html.replace(/<link\b([^>]*)\/?>/gi, (fullMatch, attrs) => {
1771
+ const rel = readHtmlAttr(attrs, "rel");
1772
+ if (rel?.toLowerCase() !== "stylesheet") {
1773
+ return fullMatch;
1774
+ }
1775
+ const href = readHtmlAttr(attrs, "href");
1776
+ if (href == null) {
1777
+ return fullMatch;
1778
+ }
1779
+ const path = normalizeAssetPath(href);
1780
+ const bytes = assets.get(path);
1781
+ if (bytes === undefined)
1782
+ return fullMatch; // external — keep as-is
1783
+ return `<style>${escapeStyleContent(rewriteCssWithEmbeddedAssets(decodeUtf8(bytes), assets))}</style>`;
1784
+ });
1785
+ }
1786
+ /** Replace <script src="X"> with inline <script>content</script> for bundle-local assets.
1787
+ *
1788
+ * Preserves the original script type (classic, module, nomodule). Does NOT
1789
+ * inject type="module" on classic scripts — that would change execution
1790
+ * timing, scope, and strict-mode behavior.
1791
+ */
1792
+ function inlineLinkedScripts(html, assets) {
1793
+ return html.replace(/<script\b([^>]*)>\s*<\/script>/gi, (fullMatch, attrs) => {
1794
+ const src = readHtmlAttr(attrs, "src");
1795
+ if (src == null) {
1796
+ return fullMatch;
1797
+ }
1798
+ const path = normalizeAssetPath(src);
1799
+ const bytes = assets.get(path);
1800
+ if (bytes === undefined)
1801
+ return fullMatch; // external — keep as-is
1802
+ const nextAttrs = stripHtmlAttr(attrs, "src").trim();
1803
+ return `<script${nextAttrs ? " " + nextAttrs : ""}>${escapeScriptContent(decodeJavascriptAsset(path, bytes, assets))}</script>`;
1804
+ });
1805
+ }
1806
+ function stripBundleModulePreloads(html, assets) {
1807
+ return html.replace(/<link\b([^>]*)\/?>/gi, (fullMatch, attrs) => {
1808
+ const rel = readHtmlAttr(attrs, "rel");
1809
+ if (rel?.toLowerCase() !== "modulepreload") {
1810
+ return fullMatch;
1811
+ }
1812
+ const href = readHtmlAttr(attrs, "href");
1813
+ if (href == null) {
1814
+ return fullMatch;
1815
+ }
1816
+ const path = normalizeAssetPath(href);
1817
+ return assets.has(path) ? "" : fullMatch;
1818
+ });
1819
+ }
1820
+ function buildBundleImportMap(assets, _referencedScripts) {
1821
+ const imports = {};
1822
+ const moduleUrlCache = new Map();
1823
+ for (const [path, bytes] of assets) {
1824
+ if (!path.endsWith(".js"))
1825
+ continue;
1826
+ const specifiers = bundleImportSpecifiers(path);
1827
+ const dataUrl = buildJavascriptModuleDataUrl(path, assets, moduleUrlCache);
1828
+ for (const specifier of specifiers) {
1829
+ imports[specifier] = dataUrl;
1830
+ }
1831
+ }
1832
+ if (Object.keys(imports).length === 0) {
1833
+ return "";
1834
+ }
1835
+ return `<script type="importmap">${JSON.stringify({ imports })}</script>`;
1836
+ }
1837
+ function bundleImportSpecifiers(path) {
1838
+ const normalized = normalizeAssetPath(path);
1839
+ const parts = normalized.split("/");
1840
+ const basename = parts[parts.length - 1] ?? normalized;
1841
+ return Array.from(new Set([
1842
+ normalized,
1843
+ `./${normalized}`,
1844
+ `/${normalized}`,
1845
+ basename,
1846
+ `./${basename}`,
1847
+ `/${basename}`,
1848
+ ]));
1849
+ }
1850
+ function stringToDataUrl(source, mimeType) {
1851
+ const bytes = encodeUtf8(source);
1852
+ return bytesToDataUrl(bytes, mimeType);
1853
+ }
1854
+ function bytesToDataUrl(bytes, mimeType) {
1855
+ let binary = "";
1856
+ for (let i = 0; i < bytes.length; i++) {
1857
+ binary += String.fromCharCode(bytes[i]);
1858
+ }
1859
+ if (typeof btoa === "function") {
1860
+ return `data:${mimeType};base64,${btoa(binary)}`;
1861
+ }
1862
+ const bufferCtor = globalThis.Buffer;
1863
+ if (bufferCtor != null) {
1864
+ return `data:${mimeType};base64,${bufferCtor.from(bytes).toString("base64")}`;
1865
+ }
1866
+ throw new Error("base64 encoding unavailable");
1867
+ }
1868
+ function decodeJavascriptAsset(path, bytes, assets) {
1869
+ const source = decodeUtf8(bytes);
1870
+ const syntheticUrl = `${SYNTHETIC_PRODUCT_BUNDLE_ORIGIN}/${normalizeAssetPath(path)}`;
1871
+ const rewrittenImports = rewriteJavascriptModuleSpecifiers(source, path, assets);
1872
+ return rewrittenImports.replace(/\bimport\.meta\.url\b/g, JSON.stringify(syntheticUrl));
1873
+ }
1874
+ function buildJavascriptModuleDataUrl(path, assets, cache, visiting = new Set()) {
1875
+ const normalizedPath = normalizeAssetPath(path);
1876
+ const cached = cache.get(normalizedPath);
1877
+ if (cached != null) {
1878
+ return cached;
1879
+ }
1880
+ const bytes = assets.get(normalizedPath);
1881
+ if (bytes === undefined) {
1882
+ throw new Error(`[product-view] missing JS asset '${normalizedPath}'`);
1883
+ }
1884
+ if (visiting.has(normalizedPath)) {
1885
+ return `${SYNTHETIC_PRODUCT_BUNDLE_ORIGIN}/${normalizedPath}`;
1886
+ }
1887
+ visiting.add(normalizedPath);
1888
+ const source = decodeUtf8(bytes);
1889
+ const rewrittenImports = rewriteJavascriptModuleSpecifiers(source, normalizedPath, assets, cache, visiting);
1890
+ const syntheticUrl = `${SYNTHETIC_PRODUCT_BUNDLE_ORIGIN}/${normalizedPath}`;
1891
+ const rewrittenSource = rewrittenImports.replace(/\bimport\.meta\.url\b/g, JSON.stringify(syntheticUrl));
1892
+ const dataUrl = stringToDataUrl(rewrittenSource, "text/javascript");
1893
+ cache.set(normalizedPath, dataUrl);
1894
+ visiting.delete(normalizedPath);
1895
+ return dataUrl;
1896
+ }
1897
+ function rewriteJavascriptModuleSpecifiers(source, currentPath, assets, cache = new Map(), visiting = new Set()) {
1898
+ const rewriteSpecifier = (rawSpecifier) => {
1899
+ const resolvedPath = resolveJavascriptAssetPath(currentPath, rawSpecifier, assets);
1900
+ if (resolvedPath == null) {
1901
+ return rawSpecifier;
1902
+ }
1903
+ if (resolvedPath.endsWith(".js") || resolvedPath.endsWith(".mjs")) {
1904
+ return buildJavascriptModuleDataUrl(resolvedPath, assets, cache, visiting);
1905
+ }
1906
+ const bytes = assets.get(resolvedPath);
1907
+ if (bytes === undefined) {
1908
+ return rawSpecifier;
1909
+ }
1910
+ return bytesToDataUrl(bytes, guessMimeType(resolvedPath));
1911
+ };
1912
+ const replaceModuleSpecifier = (code, pattern) => code.replace(pattern, (fullMatch, prefix, specifier, suffix) => {
1913
+ const nextSpecifier = rewriteSpecifier(specifier);
1914
+ if (nextSpecifier === specifier) {
1915
+ return fullMatch;
1916
+ }
1917
+ return `${prefix}${nextSpecifier}${suffix}`;
1918
+ });
1919
+ let next = source;
1920
+ next = replaceModuleSpecifier(next, /(\bimport\s*\(\s*["'])([^"']+)(["']\s*\))/g);
1921
+ next = replaceModuleSpecifier(next, /(\bfrom\s*["'])([^"']+)(["'])/g);
1922
+ next = replaceModuleSpecifier(next, /(\bimport\s*["'])([^"']+)(["'])/g);
1923
+ return next;
1924
+ }
1925
+ function resolveJavascriptAssetPath(currentPath, rawSpecifier, assets) {
1926
+ if (rawSpecifier.startsWith("data:") ||
1927
+ rawSpecifier.startsWith("blob:") ||
1928
+ rawSpecifier.startsWith("http:") ||
1929
+ rawSpecifier.startsWith("https:")) {
1930
+ return null;
1931
+ }
1932
+ const currentDir = currentPath.includes("/")
1933
+ ? currentPath.slice(0, currentPath.lastIndexOf("/") + 1)
1934
+ : "";
1935
+ let resolvedPath;
1936
+ if (rawSpecifier.startsWith("/")) {
1937
+ resolvedPath = normalizeAssetPath(rawSpecifier);
1938
+ }
1939
+ else {
1940
+ resolvedPath = normalizeAssetPath(new URL(rawSpecifier, `${SYNTHETIC_PRODUCT_BUNDLE_ORIGIN}/${currentDir}`).pathname);
1941
+ }
1942
+ return assets.has(resolvedPath) ? resolvedPath : null;
1943
+ }
1944
+ function readHtmlAttr(attrs, name) {
1945
+ const pattern = new RegExp(`(?:^|\\s)${name}\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]+))`, "i");
1946
+ const match = attrs.match(pattern);
1947
+ return match == null ? null : (match[1] ?? match[2] ?? match[3] ?? null);
1948
+ }
1949
+ function stripHtmlAttr(attrs, name) {
1950
+ const pattern = new RegExp(`(^|\\s)${name}\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s>]+)`, "ig");
1951
+ return attrs.replace(pattern, "$1").replace(/\s+/g, " ").trim();
1952
+ }
1953
+ /** Escape CSS content that could break out of a <style> block. */
1954
+ function escapeStyleContent(src) {
1955
+ return src.replace(/<\/style>/gi, "<\\/style>");
1956
+ }
1957
+ function escapeHtmlAttr(value) {
1958
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
1959
+ }
1960
+ function guessMimeType(path) {
1961
+ const lower = normalizeAssetPath(path).toLowerCase();
1962
+ if (lower.endsWith(".html") || lower.endsWith(".htm"))
1963
+ return "text/html";
1964
+ if (lower.endsWith(".css"))
1965
+ return "text/css";
1966
+ if (lower.endsWith(".js") || lower.endsWith(".mjs"))
1967
+ return "text/javascript";
1968
+ if (lower.endsWith(".json"))
1969
+ return "application/json";
1970
+ if (lower.endsWith(".wasm"))
1971
+ return "application/wasm";
1972
+ if (lower.endsWith(".svg"))
1973
+ return "image/svg+xml";
1974
+ if (lower.endsWith(".png"))
1975
+ return "image/png";
1976
+ if (lower.endsWith(".jpg") || lower.endsWith(".jpeg"))
1977
+ return "image/jpeg";
1978
+ if (lower.endsWith(".gif"))
1979
+ return "image/gif";
1980
+ if (lower.endsWith(".webp"))
1981
+ return "image/webp";
1982
+ if (lower.endsWith(".woff"))
1983
+ return "font/woff";
1984
+ if (lower.endsWith(".woff2"))
1985
+ return "font/woff2";
1986
+ if (lower.endsWith(".txt"))
1987
+ return "text/plain";
1988
+ return "application/octet-stream";
1989
+ }
1990
+ function buildEmbeddedBundleAssetScript(assets) {
1991
+ const bundleAssets = {};
1992
+ for (const [path, bytes] of assets) {
1993
+ if (path === "index.html") {
1994
+ continue;
1995
+ }
1996
+ bundleAssets[`${SYNTHETIC_PRODUCT_BUNDLE_ORIGIN}/${normalizeAssetPath(path)}`] = bytesToDataUrl(bytes, guessMimeType(path));
1997
+ }
1998
+ if (Object.keys(bundleAssets).length === 0) {
1999
+ return "";
2000
+ }
2001
+ return `(function() {
2002
+ 'use strict';
2003
+ var bundleAssets = ${JSON.stringify(bundleAssets)};
2004
+ function resolve(input) {
2005
+ try {
2006
+ var candidate = (typeof input === 'string') ? input : (input && input.url ? input.url : String(input));
2007
+ return new URL(candidate, document.baseURI || location.href).toString();
2008
+ } catch (e) {
2009
+ return null;
2010
+ }
2011
+ }
2012
+ function bundleDataUrlFor(input) {
2013
+ var resolved = resolve(input);
2014
+ if (resolved === null) {
2015
+ return null;
2016
+ }
2017
+ return Object.prototype.hasOwnProperty.call(bundleAssets, resolved) ? bundleAssets[resolved] : null;
2018
+ }
2019
+ window.__HOST_PRODUCT_BUNDLE__ = {
2020
+ resolve: resolve,
2021
+ assetFor: bundleDataUrlFor,
2022
+ };
2023
+ var _origFetch = window.fetch;
2024
+ if (_origFetch) {
2025
+ window.fetch = function(input, init) {
2026
+ var dataUrl = bundleDataUrlFor(input);
2027
+ if (dataUrl !== null) {
2028
+ return _origFetch.call(this, dataUrl);
2029
+ }
2030
+ return _origFetch.apply(this, arguments);
2031
+ };
2032
+ }
2033
+ if (window.XMLHttpRequest && window.XMLHttpRequest.prototype && window.XMLHttpRequest.prototype.open) {
2034
+ var _origXhrOpen = window.XMLHttpRequest.prototype.open;
2035
+ window.XMLHttpRequest.prototype.open = function(method, url) {
2036
+ var dataUrl = bundleDataUrlFor(url);
2037
+ var nextUrl = dataUrl !== null ? dataUrl : url;
2038
+ return _origXhrOpen.apply(this, [method, nextUrl].concat(Array.prototype.slice.call(arguments, 2)));
2039
+ };
2040
+ }
2041
+ })();`;
2042
+ }
2043
+ /** Inject HTML content right after the opening <head> tag. */
2044
+ function injectIntoHead(html, content) {
2045
+ const headMatch = html.match(/<head[^>]*>/i);
2046
+ if (headMatch !== null) {
2047
+ const idx = headMatch.index + headMatch[0].length;
2048
+ return html.slice(0, idx) + content + html.slice(idx);
2049
+ }
2050
+ // No <head> — wrap in one.
2051
+ const htmlMatch = html.match(/<html[^>]*>/i);
2052
+ if (htmlMatch !== null) {
2053
+ const idx = htmlMatch.index + htmlMatch[0].length;
2054
+ return html.slice(0, idx) + `<head>${content}</head>` + html.slice(idx);
2055
+ }
2056
+ return `<head>${content}</head>` + html;
2057
+ }
2058
+ /** Inject HTML content just before </head>. */
2059
+ function injectBeforeHeadClose(html, content) {
2060
+ const idx = html.search(/<\/head>/i);
2061
+ if (idx === -1)
2062
+ return html;
2063
+ return html.slice(0, idx) + content + html.slice(idx);
2064
+ }
2065
+ /** Inject HTML content just before </body>. */
2066
+ function injectBeforeBodyClose(html, content) {
2067
+ const idx = html.search(/<\/body>/i);
2068
+ if (idx === -1)
2069
+ return html + content;
2070
+ return html.slice(0, idx) + content + html.slice(idx);
2071
+ }
2072
+ function resolveProductViewScripts(sdk, options) {
2073
+ const { approvedNetworkOrigins = [], approvedWebSocketOrigins = [], approvedWebRtcServerUrls = [], enableViolationMonitor, enableInteractiveSandbox, traceContext, bridgeAdapter, } = options;
2074
+ if (enableViolationMonitor === true && enableInteractiveSandbox === true) {
2075
+ throw new Error("[product-view] enableViolationMonitor and enableInteractiveSandbox are mutually exclusive");
2076
+ }
2077
+ const iframeBridgeScript = buildIframeBridgeScript(enableInteractiveSandbox === true);
2078
+ let sandboxScript;
2079
+ let cspOverride;
2080
+ if (enableInteractiveSandbox === true) {
2081
+ sandboxScript = buildInteractiveScript(approvedNetworkOrigins, approvedWebSocketOrigins, approvedWebRtcServerUrls);
2082
+ cspOverride = IFRAME_CSP_INTERACTIVE;
2083
+ }
2084
+ else if (enableViolationMonitor === true) {
2085
+ sandboxScript = buildMonitorScript(approvedNetworkOrigins, approvedWebSocketOrigins, approvedWebRtcServerUrls);
2086
+ }
2087
+ else {
2088
+ sandboxScript = buildLockdownScript(approvedNetworkOrigins, approvedWebSocketOrigins, approvedWebRtcServerUrls);
2089
+ }
2090
+ const extensionInjectScript = sdk.extensionInjectScript();
2091
+ const traceContextScript = traceContext ? buildTraceContextScript(traceContext) : "";
2092
+ // Only inject the stable bridge script when a bridge adapter is provided.
2093
+ // Without an adapter, bridge calls would never settle — better to not
2094
+ // expose the methods at all.
2095
+ const stableBridgeScript = bridgeAdapter !== undefined ? buildStableBridgeScript() : "";
2096
+ const coreCapabilities = {
2097
+ storage: bridgeAdapter?.storage !== undefined,
2098
+ statements: bridgeAdapter?.statements !== undefined,
2099
+ identity: bridgeAdapter?.identity !== undefined,
2100
+ };
2101
+ const combinedBridgeScript = [traceContextScript, iframeBridgeScript, stableBridgeScript]
2102
+ .filter(Boolean)
2103
+ .join("\n");
2104
+ return {
2105
+ combinedBridgeScript,
2106
+ sandboxScript,
2107
+ cspOverride,
2108
+ extensionInjectScript,
2109
+ coreCapabilities,
2110
+ };
2111
+ }
2112
+ // ---------------------------------------------------------------------------
2113
+ // Stable bridge call helpers
2114
+ // ---------------------------------------------------------------------------
2115
+ /**
2116
+ * Dispatch a `host-bridge-call` from the guest to the host adapter and post
2117
+ * the reply back to the iframe.
2118
+ */
2119
+ function handleBridgeCall(adapter, method, args, callId, iframe, targetOrigin, sessionToken) {
2120
+ const reply = (result, error, errorCode) => {
2121
+ const payload = error !== undefined
2122
+ ? sessionToken !== undefined
2123
+ ? { type: "host-bridge-reply", callId, error, errorCode, session: sessionToken }
2124
+ : { type: "host-bridge-reply", callId, error, errorCode }
2125
+ : sessionToken !== undefined
2126
+ ? { type: "host-bridge-reply", callId, result: result ?? null, session: sessionToken }
2127
+ : { type: "host-bridge-reply", callId, result: result ?? null };
2128
+ iframe.contentWindow?.postMessage(payload, targetOrigin);
2129
+ };
2130
+ if (adapter === undefined) {
2131
+ reply(undefined, "Bridge adapter not configured", "ERR_HOST_RUNTIME_UNAVAILABLE");
2132
+ return;
2133
+ }
2134
+ const call = resolveBridgeMethod(adapter, method, args);
2135
+ if (call === null) {
2136
+ reply(undefined, `Unsupported method: ${method}`, "ERR_UNSUPPORTED_CAPABILITY");
2137
+ return;
2138
+ }
2139
+ call
2140
+ .then((result) => reply(result))
2141
+ .catch((err) => {
2142
+ const msg = err instanceof Error ? err.message : String(err);
2143
+ const code = err !== null && typeof err === "object" && "code" in err && typeof err.code === "string"
2144
+ ? err.code
2145
+ : "ERR_HOST_RUNTIME_UNAVAILABLE";
2146
+ reply(undefined, msg, code);
2147
+ });
2148
+ }
2149
+ /**
2150
+ * Map a bridge method name to a promise from the adapter.
2151
+ *
2152
+ * Returns `null` when the method is unknown or the adapter does not implement
2153
+ * it, which causes the caller to reject with `ERR_UNSUPPORTED_CAPABILITY`.
2154
+ */
2155
+ function invalidArgError(method, expected) {
2156
+ return Promise.reject(Object.assign(new Error(`${method} requires ${expected} argument(s)`), {
2157
+ code: "ERR_INVALID_ARGUMENT",
2158
+ }));
2159
+ }
2160
+ function resolveBridgeMethod(adapter, method, args) {
2161
+ switch (method) {
2162
+ case "getAddress":
2163
+ return adapter.getAddress?.() ?? null;
2164
+ case "storage.get":
2165
+ if (args.length < 1 || typeof args[0] !== "string")
2166
+ return invalidArgError(method, 1);
2167
+ return adapter.storage?.get(args[0]) ?? null;
2168
+ case "storage.set":
2169
+ if (args.length < 2 || typeof args[0] !== "string" || typeof args[1] !== "string")
2170
+ return invalidArgError(method, 2);
2171
+ return adapter.storage?.set(args[0], args[1]).then(() => true) ?? null;
2172
+ case "storage.remove":
2173
+ if (args.length < 1 || typeof args[0] !== "string")
2174
+ return invalidArgError(method, 1);
2175
+ return adapter.storage?.remove(args[0]).then(() => true) ?? null;
2176
+ case "statements.subscribe":
2177
+ if (args.length < 1 || typeof args[0] !== "string")
2178
+ return invalidArgError(method, 1);
2179
+ return adapter.statements?.subscribe(args[0]) ?? null;
2180
+ case "statements.write":
2181
+ if (args.length < 2 || typeof args[0] !== "string" || typeof args[1] !== "string")
2182
+ return invalidArgError(method, 2);
2183
+ return adapter.statements?.write(args[0], args[1]) ?? null;
2184
+ case "statements.status":
2185
+ return adapter.statements?.status() ?? null;
2186
+ case "identity.resolveUsername":
2187
+ if (args.length < 1 || typeof args[0] !== "string")
2188
+ return invalidArgError(method, 1);
2189
+ return adapter.identity?.resolveUsername(args[0]) ?? null;
2190
+ default:
2191
+ return null;
2192
+ }
2193
+ }
2194
+ // ---------------------------------------------------------------------------
2195
+ // Main export
2196
+ // ---------------------------------------------------------------------------
2197
+ export function prepareProductViewAssets(options) {
2198
+ const { assets, sdk, approvedNetworkOrigins = [], approvedWebSocketOrigins = [], approvedWebRtcServerUrls = [], enableViolationMonitor, enableInteractiveSandbox, traceContext, routeBasePath, bridgeAdapter, } = options;
2199
+ const scripts = resolveProductViewScripts(sdk, {
2200
+ approvedNetworkOrigins,
2201
+ approvedWebSocketOrigins,
2202
+ approvedWebRtcServerUrls,
2203
+ enableViolationMonitor,
2204
+ enableInteractiveSandbox,
2205
+ traceContext,
2206
+ bridgeAdapter,
2207
+ });
2208
+ const urlBackedCspOverride = enableInteractiveSandbox === true ? IFRAME_CSP_URL_BACKED_INTERACTIVE : undefined;
2209
+ const preparedAssets = new Map(assets);
2210
+ const preparedHtml = buildUrlBackedDocument(assets, scripts.combinedBridgeScript, scripts.sandboxScript, scripts.extensionInjectScript, routeBasePath, approvedNetworkOrigins, approvedWebSocketOrigins, urlBackedCspOverride, scripts.coreCapabilities);
2211
+ preparedAssets.set("index.html", encodeUtf8(preparedHtml));
2212
+ const normalizedBase = normalizeRouteBasePath(routeBasePath);
2213
+ for (const [path, bytes] of preparedAssets) {
2214
+ if (path.endsWith(".css")) {
2215
+ preparedAssets.set(path, encodeUtf8(rewriteBundleLocalCssPaths(decodeUtf8(bytes), preparedAssets, normalizedBase)));
2216
+ continue;
2217
+ }
2218
+ if (path.endsWith(".js") ||
2219
+ path.endsWith(".mjs") ||
2220
+ path.endsWith(".json") ||
2221
+ path.endsWith(".txt")) {
2222
+ preparedAssets.set(path, encodeUtf8(rewriteBundleLocalTextPaths(decodeUtf8(bytes), preparedAssets, normalizedBase)));
2223
+ }
2224
+ }
2225
+ return preparedAssets;
2226
+ }
2227
+ export function mountProductView(options) {
2228
+ const { container, productId, assets, sdk, delegate, enableViolationMonitor, enableInteractiveSandbox, approvedNetworkOrigins = [], approvedWebSocketOrigins = [], approvedWebRtcServerUrls = [], traceContext, analyticsRouter, bridgeAdapter, } = options;
2229
+ // Determine whether violation routing is active.
2230
+ const violationRoutingActive = enableViolationMonitor === true || enableInteractiveSandbox === true;
2231
+ // Notify delegate: entering loading state.
2232
+ delegate.onLoadStateChanged?.("loading");
2233
+ const scripts = resolveProductViewScripts(sdk, {
2234
+ approvedNetworkOrigins,
2235
+ approvedWebSocketOrigins,
2236
+ approvedWebRtcServerUrls,
2237
+ enableViolationMonitor,
2238
+ enableInteractiveSandbox,
2239
+ traceContext,
2240
+ bridgeAdapter,
2241
+ });
2242
+ const srcdoc = buildSrcdoc(assets, scripts.combinedBridgeScript, scripts.sandboxScript, scripts.extensionInjectScript, approvedNetworkOrigins, approvedWebSocketOrigins, scripts.cspOverride, scripts.coreCapabilities);
2243
+ // Create the iframe element.
2244
+ const iframe = document.createElement("iframe");
2245
+ iframe.setAttribute("sandbox", IFRAME_SANDBOX_ATTR);
2246
+ iframe.style.width = "100%";
2247
+ iframe.style.height = "100%";
2248
+ iframe.style.border = "none";
2249
+ // Message handler: relay messages from the iframe to the delegate.
2250
+ let sandboxGrantPort = null;
2251
+ let livenessTimeoutId = null;
2252
+ const clearLivenessWatchdog = () => {
2253
+ if (livenessTimeoutId !== null) {
2254
+ window.clearTimeout(livenessTimeoutId);
2255
+ livenessTimeoutId = null;
2256
+ }
2257
+ };
2258
+ const refreshLivenessWatchdog = () => {
2259
+ clearLivenessWatchdog();
2260
+ livenessTimeoutId = window.setTimeout(() => {
2261
+ handleUnexpectedNavigation();
2262
+ }, 1500);
2263
+ };
2264
+ const onMessage = (event) => {
2265
+ if (unexpectedNavigationHandled)
2266
+ return;
2267
+ // Only accept messages from our iframe's contentWindow.
2268
+ if (event.source !== iframe.contentWindow)
2269
+ return;
2270
+ const msg = event.data;
2271
+ if (msg === null || typeof msg !== "object")
2272
+ return;
2273
+ if (msg["type"] === "hostapi-heartbeat") {
2274
+ refreshLivenessWatchdog();
2275
+ return;
2276
+ }
2277
+ // Only accept the grant port in interactive sandbox mode. In monitor or
2278
+ // lockdown modes the iframe bridge never sends this message, but an
2279
+ // untrusted product could try to spoof it to gain a persistent postMessage
2280
+ // channel. Silently drop the message in non-interactive modes.
2281
+ if (msg["type"] === "hostapi-grant-port") {
2282
+ if (enableInteractiveSandbox === true) {
2283
+ const port = event.ports?.[0];
2284
+ if (port instanceof MessagePort) {
2285
+ sandboxGrantPort?.close();
2286
+ sandboxGrantPort = port;
2287
+ sandboxGrantPort.start();
2288
+ }
2289
+ }
2290
+ return;
2291
+ }
2292
+ if (msg["type"] === "SANDBOX_VIOLATION" && violationRoutingActive) {
2293
+ const now = Date.now();
2294
+ const violation = {
2295
+ api: typeof msg["api"] === "string" ? msg["api"] : String(msg["api"] ?? "unknown"),
2296
+ details: msg["details"] !== null && typeof msg["details"] === "object"
2297
+ ? msg["details"]
2298
+ : {},
2299
+ timestamp: typeof msg["timestamp"] === "number" ? msg["timestamp"] : now,
2300
+ hostTimestamp: now,
2301
+ disposition: msg["disposition"] === "promptable" || msg["disposition"] === "always_blocked"
2302
+ ? msg["disposition"]
2303
+ : undefined,
2304
+ };
2305
+ try {
2306
+ delegate.onSandboxViolation?.(violation, productId);
2307
+ }
2308
+ catch (err) {
2309
+ delegate.onError?.(err instanceof Error ? err : new Error(String(err)));
2310
+ }
2311
+ return;
2312
+ }
2313
+ if (isProductAnalyticsMessage(msg) && traceContext?.sampled === true) {
2314
+ const analyticsEvent = msg.event;
2315
+ try {
2316
+ analyticsRouter?.dispatch(productId, analyticsEvent);
2317
+ delegate.onProductAnalytics?.(analyticsEvent, productId);
2318
+ }
2319
+ catch (err) {
2320
+ delegate.onError?.(err instanceof Error ? err : new Error(String(err)));
2321
+ }
2322
+ return;
2323
+ }
2324
+ if (msg["type"] === "host-bridge-call") {
2325
+ const callId = msg["callId"];
2326
+ const method = msg["method"];
2327
+ const args = msg["args"];
2328
+ if (typeof callId !== "number" ||
2329
+ !Number.isInteger(callId) ||
2330
+ typeof method !== "string" ||
2331
+ !Array.isArray(args))
2332
+ return;
2333
+ handleBridgeCall(bridgeAdapter, method, args, callId, iframe, "null");
2334
+ return;
2335
+ }
2336
+ if (msg["type"] !== "hostapi")
2337
+ return;
2338
+ const raw = msg["data"];
2339
+ const bytes = raw instanceof Uint8Array
2340
+ ? raw
2341
+ : raw instanceof ArrayBuffer
2342
+ ? new Uint8Array(raw)
2343
+ : undefined;
2344
+ if (!bytes)
2345
+ return;
2346
+ try {
2347
+ delegate.onMessage(bytes, productId);
2348
+ }
2349
+ catch (err) {
2350
+ delegate.onError?.(err instanceof Error ? err : new Error(String(err)));
2351
+ }
2352
+ };
2353
+ window.addEventListener("message", onMessage);
2354
+ let initialLoadCompleted = false;
2355
+ let unexpectedNavigationHandled = false;
2356
+ const handleUnexpectedNavigation = () => {
2357
+ if (unexpectedNavigationHandled)
2358
+ return;
2359
+ unexpectedNavigationHandled = true;
2360
+ clearLivenessWatchdog();
2361
+ delegate.onLoadStateChanged?.("failed");
2362
+ delegate.onError?.(new Error(`[product-view] iframe navigated away from the injected product document for product '${productId}'`));
2363
+ // Replace the escaped document with a blank inert page so the host does not
2364
+ // keep rendering untrusted content after a failed sandbox escape attempt.
2365
+ iframe.srcdoc = "<!doctype html><html><body></body></html>";
2366
+ };
2367
+ iframe.addEventListener("load", () => {
2368
+ if (!initialLoadCompleted) {
2369
+ initialLoadCompleted = true;
2370
+ delegate.onLoadStateChanged?.("ready");
2371
+ refreshLivenessWatchdog();
2372
+ return;
2373
+ }
2374
+ handleUnexpectedNavigation();
2375
+ });
2376
+ iframe.addEventListener("error", () => {
2377
+ delegate.onLoadStateChanged?.("failed");
2378
+ delegate.onError?.(new Error(`[product-view] iframe failed to load for product '${productId}'`));
2379
+ });
2380
+ iframe.srcdoc = srcdoc;
2381
+ container.appendChild(iframe);
2382
+ // ---------------------------------------------------------------------------
2383
+ // Handle
2384
+ // ---------------------------------------------------------------------------
2385
+ const sendResponse = (data) => {
2386
+ const cw = iframe.contentWindow;
2387
+ if (cw === null)
2388
+ return;
2389
+ // Use 'null' as the targetOrigin. The srcdoc iframe's serialized origin
2390
+ // is "null" (sandbox without allow-same-origin), so the browser delivers
2391
+ // the message only to null-origin frames.
2392
+ cw.postMessage({ type: "hostapi-reply", data }, "null");
2393
+ };
2394
+ const destroy = () => {
2395
+ window.removeEventListener("message", onMessage);
2396
+ clearLivenessWatchdog();
2397
+ sandboxGrantPort?.close();
2398
+ sandboxGrantPort = null;
2399
+ iframe.remove();
2400
+ delegate.onLoadStateChanged?.("idle");
2401
+ };
2402
+ // Return InteractiveProductViewHandle with grantNetworkOrigin when interactive.
2403
+ if (enableInteractiveSandbox === true) {
2404
+ const postSandboxGrant = (payload) => {
2405
+ sandboxGrantPort?.postMessage(payload);
2406
+ };
2407
+ const interactiveHandle = {
2408
+ get productId() {
2409
+ return productId;
2410
+ },
2411
+ sendResponse,
2412
+ destroy,
2413
+ grantNetworkOrigin(origin) {
2414
+ // Validate the origin before sending to the iframe.
2415
+ const validated = normalizeApprovedNetworkOrigins([origin]);
2416
+ if (validated.length === 0)
2417
+ return;
2418
+ postSandboxGrant({
2419
+ type: "sandbox-grant",
2420
+ origin: validated[0],
2421
+ kind: "network",
2422
+ });
2423
+ },
2424
+ grantWebSocketOrigin(origin) {
2425
+ const validated = normalizeApprovedWebSocketOrigins([origin]);
2426
+ if (validated.length === 0)
2427
+ return;
2428
+ postSandboxGrant({
2429
+ type: "sandbox-grant",
2430
+ origin: validated[0],
2431
+ kind: "websocket",
2432
+ });
2433
+ },
2434
+ grantWebRtcServerUrl(url) {
2435
+ const validated = normalizeApprovedWebRtcServerUrls([url]);
2436
+ if (validated.length === 0)
2437
+ return;
2438
+ postSandboxGrant({
2439
+ type: "sandbox-grant",
2440
+ url: validated[0],
2441
+ kind: "webrtc",
2442
+ });
2443
+ },
2444
+ };
2445
+ return interactiveHandle;
2446
+ }
2447
+ const handle = {
2448
+ get productId() {
2449
+ return productId;
2450
+ },
2451
+ sendResponse,
2452
+ destroy,
2453
+ };
2454
+ return handle;
2455
+ }
2456
+ export function mountProductViewUrl(options) {
2457
+ const { container, productId, frameUrl, delegate, enableViolationMonitor, enableInteractiveSandbox, traceContext, analyticsRouter, bridgeAdapter, } = options;
2458
+ if (enableViolationMonitor === true && enableInteractiveSandbox === true) {
2459
+ throw new Error("[product-view] enableViolationMonitor and enableInteractiveSandbox are mutually exclusive");
2460
+ }
2461
+ delegate.onLoadStateChanged?.("loading");
2462
+ const violationRoutingActive = enableViolationMonitor === true || enableInteractiveSandbox === true;
2463
+ const iframe = document.createElement("iframe");
2464
+ // URL-backed archive apps need a same-origin iframe so module scripts and
2465
+ // service-worker-backed asset fetches can resolve under a real route.
2466
+ iframe.setAttribute("sandbox", URL_BACKED_IFRAME_SANDBOX_ATTR);
2467
+ iframe.style.width = "100%";
2468
+ iframe.style.height = "100%";
2469
+ iframe.style.border = "none";
2470
+ const existingSessionToken = resolveProductViewSessionToken(frameUrl);
2471
+ const expectedSessionToken = existingSessionToken ?? createProductViewSessionToken();
2472
+ const effectiveFrameUrl = existingSessionToken !== undefined
2473
+ ? frameUrl
2474
+ : appendProductViewSessionToken(frameUrl, expectedSessionToken);
2475
+ const frameOrigin = new URL(effectiveFrameUrl, window.location.href).origin;
2476
+ let sandboxGrantPort = null;
2477
+ let livenessTimeoutId = null;
2478
+ const clearLivenessWatchdog = () => {
2479
+ if (livenessTimeoutId !== null) {
2480
+ window.clearTimeout(livenessTimeoutId);
2481
+ livenessTimeoutId = null;
2482
+ }
2483
+ };
2484
+ const refreshLivenessWatchdog = () => {
2485
+ clearLivenessWatchdog();
2486
+ livenessTimeoutId = window.setTimeout(() => {
2487
+ handleUnexpectedNavigation();
2488
+ }, 1500);
2489
+ };
2490
+ const onMessage = (event) => {
2491
+ if (unexpectedNavigationHandled)
2492
+ return;
2493
+ if (event.source !== iframe.contentWindow)
2494
+ return;
2495
+ if (event.origin !== frameOrigin)
2496
+ return;
2497
+ const msg = event.data;
2498
+ if (msg === null || typeof msg !== "object")
2499
+ return;
2500
+ if (msg["session"] !== expectedSessionToken)
2501
+ return;
2502
+ if (msg["type"] === "hostapi-heartbeat") {
2503
+ refreshLivenessWatchdog();
2504
+ return;
2505
+ }
2506
+ if (msg["type"] === "hostapi-grant-port") {
2507
+ if (enableInteractiveSandbox === true) {
2508
+ const port = event.ports?.[0];
2509
+ if (port instanceof MessagePort) {
2510
+ sandboxGrantPort?.close();
2511
+ sandboxGrantPort = port;
2512
+ sandboxGrantPort.start();
2513
+ }
2514
+ }
2515
+ return;
2516
+ }
2517
+ if (msg["type"] === "SANDBOX_VIOLATION" && violationRoutingActive) {
2518
+ const now = Date.now();
2519
+ const violation = {
2520
+ api: typeof msg["api"] === "string" ? msg["api"] : String(msg["api"] ?? "unknown"),
2521
+ details: msg["details"] !== null && typeof msg["details"] === "object"
2522
+ ? msg["details"]
2523
+ : {},
2524
+ timestamp: typeof msg["timestamp"] === "number" ? msg["timestamp"] : now,
2525
+ hostTimestamp: now,
2526
+ disposition: msg["disposition"] === "promptable" || msg["disposition"] === "always_blocked"
2527
+ ? msg["disposition"]
2528
+ : undefined,
2529
+ };
2530
+ try {
2531
+ delegate.onSandboxViolation?.(violation, productId);
2532
+ }
2533
+ catch (err) {
2534
+ delegate.onError?.(err instanceof Error ? err : new Error(String(err)));
2535
+ }
2536
+ return;
2537
+ }
2538
+ if (isProductAnalyticsMessage(msg) && traceContext?.sampled === true) {
2539
+ const analyticsEvent = msg.event;
2540
+ try {
2541
+ analyticsRouter?.dispatch(productId, analyticsEvent);
2542
+ delegate.onProductAnalytics?.(analyticsEvent, productId);
2543
+ }
2544
+ catch (err) {
2545
+ delegate.onError?.(err instanceof Error ? err : new Error(String(err)));
2546
+ }
2547
+ return;
2548
+ }
2549
+ if (msg["type"] === "host-bridge-call") {
2550
+ const callId = msg["callId"];
2551
+ const method = msg["method"];
2552
+ const args = msg["args"];
2553
+ if (typeof callId !== "number" ||
2554
+ !Number.isInteger(callId) ||
2555
+ typeof method !== "string" ||
2556
+ !Array.isArray(args))
2557
+ return;
2558
+ handleBridgeCall(bridgeAdapter, method, args, callId, iframe, frameOrigin, expectedSessionToken);
2559
+ return;
2560
+ }
2561
+ if (msg["type"] !== "hostapi")
2562
+ return;
2563
+ const raw = msg["data"];
2564
+ const bytes = raw instanceof Uint8Array
2565
+ ? raw
2566
+ : raw instanceof ArrayBuffer
2567
+ ? new Uint8Array(raw)
2568
+ : undefined;
2569
+ if (!bytes)
2570
+ return;
2571
+ try {
2572
+ delegate.onMessage(bytes, productId);
2573
+ }
2574
+ catch (err) {
2575
+ delegate.onError?.(err instanceof Error ? err : new Error(String(err)));
2576
+ }
2577
+ };
2578
+ window.addEventListener("message", onMessage);
2579
+ let initialLoadCompleted = false;
2580
+ let unexpectedNavigationHandled = false;
2581
+ const handleUnexpectedNavigation = () => {
2582
+ if (unexpectedNavigationHandled)
2583
+ return;
2584
+ unexpectedNavigationHandled = true;
2585
+ clearLivenessWatchdog();
2586
+ delegate.onLoadStateChanged?.("failed");
2587
+ delegate.onError?.(new Error(`[product-view] iframe lost the injected product bridge for product '${productId}'`));
2588
+ iframe.src = "about:blank";
2589
+ };
2590
+ iframe.addEventListener("load", () => {
2591
+ if (!initialLoadCompleted) {
2592
+ initialLoadCompleted = true;
2593
+ delegate.onLoadStateChanged?.("ready");
2594
+ refreshLivenessWatchdog();
2595
+ return;
2596
+ }
2597
+ handleUnexpectedNavigation();
2598
+ });
2599
+ iframe.addEventListener("error", () => {
2600
+ delegate.onLoadStateChanged?.("failed");
2601
+ delegate.onError?.(new Error(`[product-view] iframe failed to load for product '${productId}'`));
2602
+ });
2603
+ iframe.src = effectiveFrameUrl;
2604
+ container.appendChild(iframe);
2605
+ const sendResponse = (data) => {
2606
+ const cw = iframe.contentWindow;
2607
+ if (cw === null)
2608
+ return;
2609
+ // URL-backed frames keep their real origin, so replies can be pinned to
2610
+ // the exact frame origin instead of the opaque "null" target used by srcdoc.
2611
+ cw.postMessage({ type: "hostapi-reply", data, session: expectedSessionToken }, frameOrigin);
2612
+ };
2613
+ const destroy = () => {
2614
+ window.removeEventListener("message", onMessage);
2615
+ clearLivenessWatchdog();
2616
+ sandboxGrantPort?.close();
2617
+ sandboxGrantPort = null;
2618
+ iframe.remove();
2619
+ delegate.onLoadStateChanged?.("idle");
2620
+ };
2621
+ if (enableInteractiveSandbox === true) {
2622
+ const postSandboxGrant = (payload) => {
2623
+ sandboxGrantPort?.postMessage(payload);
2624
+ };
2625
+ return {
2626
+ get productId() {
2627
+ return productId;
2628
+ },
2629
+ sendResponse,
2630
+ destroy,
2631
+ grantNetworkOrigin(origin) {
2632
+ const validated = normalizeApprovedNetworkOrigins([origin]);
2633
+ if (validated.length === 0)
2634
+ return;
2635
+ postSandboxGrant({
2636
+ type: "sandbox-grant",
2637
+ origin: validated[0],
2638
+ kind: "network",
2639
+ });
2640
+ },
2641
+ grantWebSocketOrigin(origin) {
2642
+ const validated = normalizeApprovedWebSocketOrigins([origin]);
2643
+ if (validated.length === 0)
2644
+ return;
2645
+ postSandboxGrant({
2646
+ type: "sandbox-grant",
2647
+ origin: validated[0],
2648
+ kind: "websocket",
2649
+ });
2650
+ },
2651
+ grantWebRtcServerUrl(url) {
2652
+ const validated = normalizeApprovedWebRtcServerUrls([url]);
2653
+ if (validated.length === 0)
2654
+ return;
2655
+ postSandboxGrant({
2656
+ type: "sandbox-grant",
2657
+ url: validated[0],
2658
+ kind: "webrtc",
2659
+ });
2660
+ },
2661
+ };
2662
+ }
2663
+ return {
2664
+ get productId() {
2665
+ return productId;
2666
+ },
2667
+ sendResponse,
2668
+ destroy,
2669
+ };
2670
+ }
2671
+ //# sourceMappingURL=product-view.js.map