@pyric/cli 0.1.0-alpha.11 → 0.1.0-alpha.12

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 (301) hide show
  1. package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
  2. package/dist/conformance/.generated/can-i-use-browser.js +1 -1
  3. package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
  4. package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
  5. package/dist/conformance/.generated/can-i-use.js +2 -2
  6. package/dist/conformance/.generated/can-i-use.js.map +1 -1
  7. package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
  8. package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
  9. package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
  10. package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
  11. package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
  12. package/dist/serve/docs-ui/docs/api-reference/index.html +2 -2
  13. package/dist/serve/docs-ui/docs/api-reference.md +1 -1
  14. package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
  15. package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
  16. package/dist/serve/docs-ui/docs/build/ai-logic/index.html +1 -1
  17. package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
  18. package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
  19. package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
  20. package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
  21. package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
  22. package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
  23. package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
  24. package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
  25. package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
  26. package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
  27. package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +1 -1
  28. package/dist/serve/docs-ui/docs/get-started/start-building/index.html +1 -1
  29. package/dist/serve/docs-ui/docs/index.json +200 -25
  30. package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
  31. package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
  32. package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
  33. package/dist/serve/docs-ui/docs/overview/index.html +1 -1
  34. package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
  35. package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
  36. package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
  37. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
  38. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +916 -0
  39. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api.md +706 -0
  40. package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
  41. package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
  42. package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
  43. package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
  44. package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
  45. package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
  46. package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
  47. package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
  48. package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
  49. package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
  50. package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
  51. package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
  52. package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
  53. package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
  54. package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
  55. package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
  56. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +131 -66
  57. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +130 -65
  58. package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
  59. package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +1 -1
  60. package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
  61. package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
  62. package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
  63. package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
  64. package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
  65. package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
  66. package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
  67. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +37 -14
  68. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +28 -8
  69. package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
  70. package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
  71. package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
  72. package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
  73. package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
  74. package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
  75. package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
  76. package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
  77. package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
  78. package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
  79. package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
  80. package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
  81. package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
  82. package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
  83. package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
  84. package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
  85. package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
  86. package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
  87. package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
  88. package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
  89. package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
  90. package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
  91. package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
  92. package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
  93. package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
  94. package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
  95. package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
  96. package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
  97. package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
  98. package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
  99. package/dist/serve/docs-ui/llms.txt +1 -1
  100. package/dist/serve/worker/host/core.js +1 -1
  101. package/dist/serve/worker/host/core.js.map +1 -1
  102. package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -1
  103. package/dist/serve/worker/host/firestore-writes.js +3 -2
  104. package/dist/serve/worker/host/firestore-writes.js.map +1 -1
  105. package/dist/serve/worker/index.d.ts +2 -1
  106. package/dist/serve/worker/index.d.ts.map +1 -1
  107. package/dist/serve/worker/index.js +2 -1
  108. package/dist/serve/worker/index.js.map +1 -1
  109. package/dist/serve/worker/protocol.js +2 -2
  110. package/dist/serve/worker/protocol.js.map +1 -1
  111. package/package.json +5 -4
  112. package/src/assurance/.generated/conformance-verdicts.ts +1090 -0
  113. package/src/assurance/attachment.ts +211 -0
  114. package/src/assurance/browser.ts +80 -0
  115. package/src/assurance/campaign.ts +496 -0
  116. package/src/assurance/capabilities.ts +446 -0
  117. package/src/assurance/cases.ts +43 -0
  118. package/src/assurance/index.ts +75 -0
  119. package/src/assurance/runner.ts +885 -0
  120. package/src/assurance/tool-names.ts +15 -0
  121. package/src/assurance/tools.ts +830 -0
  122. package/src/assurance/types.ts +352 -0
  123. package/src/assurance/validation.ts +434 -0
  124. package/src/bridge/client/bridge.ts +550 -0
  125. package/src/bridge/client/dispatch.ts +137 -0
  126. package/src/bridge/client.ts +45 -0
  127. package/src/bridge/protocol.ts +351 -0
  128. package/src/bridge/server/audit.ts +53 -0
  129. package/src/bridge/server/bridge.ts +597 -0
  130. package/src/bridge/server/headless.ts +160 -0
  131. package/src/bridge/server/json-schema-to-zod.ts +108 -0
  132. package/src/bridge/server/local-bridge.ts +66 -0
  133. package/src/bridge/server/logger.ts +50 -0
  134. package/src/bridge/server/mcp-contract.ts +93 -0
  135. package/src/bridge/server/mcp.ts +129 -0
  136. package/src/bridge/server/peer.ts +232 -0
  137. package/src/bridge/server/standalone.ts +347 -0
  138. package/src/bridge/server/tool-metadata.ts +99 -0
  139. package/src/bridge/server.ts +28 -0
  140. package/src/cli/can-i-use.ts +50 -0
  141. package/src/cli/cli.test.ts +493 -0
  142. package/src/cli/database-rules.ts +329 -0
  143. package/src/cli/dev-runner.ts +282 -0
  144. package/src/cli/firebase-json.ts +100 -0
  145. package/src/cli/firestore-indexes.ts +70 -0
  146. package/src/cli/index.ts +425 -0
  147. package/src/cli/init.ts +260 -0
  148. package/src/cli/mcp-proxy.ts +196 -0
  149. package/src/cli/parse-args.ts +80 -0
  150. package/src/cli/rules.ts +259 -0
  151. package/src/cli/scope.ts +67 -0
  152. package/src/cli/serve.ts +996 -0
  153. package/src/cli/service-commands.ts +73 -0
  154. package/src/cli/snapshot.ts +164 -0
  155. package/src/cli/storage-rules.ts +172 -0
  156. package/src/cli/verify.ts +416 -0
  157. package/src/conformance/.generated/can-i-use-browser.ts +113 -0
  158. package/src/conformance/.generated/can-i-use.ts +117 -0
  159. package/src/conformance/.generated/conformance-docs.ts +14 -0
  160. package/src/conformance/browser.ts +24 -0
  161. package/src/conformance/can-i-use-tool.ts +51 -0
  162. package/src/conformance/can-i-use.ts +18 -0
  163. package/src/conformance/docs.ts +8 -0
  164. package/src/conformance/index.ts +20 -0
  165. package/src/conformance/tools.ts +16 -0
  166. package/src/credentials/core/memoize-ttl.ts +142 -0
  167. package/src/credentials/core/types.ts +10 -0
  168. package/src/credentials/node/from-adc.ts +96 -0
  169. package/src/credentials/node/from-service-account.ts +133 -0
  170. package/src/credentials/node/index.ts +6 -0
  171. package/src/discover/concurrency.ts +127 -0
  172. package/src/discover/crawler-adapter.ts +142 -0
  173. package/src/discover/crawler.ts +1127 -0
  174. package/src/discover/credential-free.ts +27 -0
  175. package/src/discover/findCollectionGroup.ts +131 -0
  176. package/src/discover/firestore-source.ts +59 -0
  177. package/src/discover/index.ts +5 -0
  178. package/src/discover/merge.ts +523 -0
  179. package/src/discover/session.ts +402 -0
  180. package/src/discover/tools.ts +201 -0
  181. package/src/discover/types.ts +187 -0
  182. package/src/discover/wire.ts +324 -0
  183. package/src/functions-rtdb/child.ts +382 -0
  184. package/src/functions-rtdb/delivery.ts +7 -0
  185. package/src/functions-rtdb/discovery.ts +136 -0
  186. package/src/functions-rtdb/event.ts +64 -0
  187. package/src/functions-rtdb/execution.ts +111 -0
  188. package/src/functions-rtdb/in-memory-delivery.ts +40 -0
  189. package/src/functions-rtdb/project.ts +74 -0
  190. package/src/functions-rtdb/projection.ts +104 -0
  191. package/src/functions-rtdb/reference-pattern.ts +20 -0
  192. package/src/functions-rtdb/remote-delivery.ts +23 -0
  193. package/src/pkg-version.ts +49 -0
  194. package/src/register/esm-exports.ts +63 -0
  195. package/src/register/hooks.ts +30 -0
  196. package/src/register/index.ts +150 -0
  197. package/src/register/mapping.ts +33 -0
  198. package/src/remote/index.ts +1050 -0
  199. package/src/rtdb/crawl-snapshot.ts +118 -0
  200. package/src/rtdb/inspection.ts +137 -0
  201. package/src/rtdb/load-rules-document.ts +56 -0
  202. package/src/rtdb/rules-generation-tool.ts +38 -0
  203. package/src/rtdb/rules-json.ts +29 -0
  204. package/src/serve/activity-guard.ts +29 -0
  205. package/src/serve/activity-route.ts +164 -0
  206. package/src/serve/activity-warning.ts +26 -0
  207. package/src/serve/bridge-mount.ts +200 -0
  208. package/src/serve/bundler.ts +493 -0
  209. package/src/serve/capture-store.ts +65 -0
  210. package/src/serve/discovery.ts +199 -0
  211. package/src/serve/entries/ai.ts +292 -0
  212. package/src/serve/entries/app-backend.ts +4 -0
  213. package/src/serve/entries/app-client.ts +26 -0
  214. package/src/serve/entries/app-session-store.ts +47 -0
  215. package/src/serve/entries/app.ts +11 -0
  216. package/src/serve/entries/auth-helper-core.ts +181 -0
  217. package/src/serve/entries/auth-helper-dom.ts +134 -0
  218. package/src/serve/entries/auth-helper-runtime.ts +20 -0
  219. package/src/serve/entries/auth.ts +255 -0
  220. package/src/serve/entries/bridge-url.ts +31 -0
  221. package/src/serve/entries/database.ts +129 -0
  222. package/src/serve/entries/firestore.ts +232 -0
  223. package/src/serve/entries/init.ts +46 -0
  224. package/src/serve/entries/keepalive.ts +46 -0
  225. package/src/serve/entries/messaging-sw.ts +75 -0
  226. package/src/serve/entries/messaging.ts +96 -0
  227. package/src/serve/entries/runtime.ts +540 -0
  228. package/src/serve/entries/session-store.ts +97 -0
  229. package/src/serve/entries/storage.ts +92 -0
  230. package/src/serve/entries/tab-sync-wiring.ts +274 -0
  231. package/src/serve/entries/worker-runtime.ts +75 -0
  232. package/src/serve/init-payload.ts +42 -0
  233. package/src/serve/namespace.ts +675 -0
  234. package/src/serve/open-browser.ts +68 -0
  235. package/src/serve/rules.ts +272 -0
  236. package/src/serve/sandbox-marker.ts +31 -0
  237. package/src/serve/server.ts +428 -0
  238. package/src/serve/standalone-assets.ts +180 -0
  239. package/src/serve/state-store.ts +166 -0
  240. package/src/serve/studio/disk-project-store.ts +185 -0
  241. package/src/serve/studio/disk-workspace.ts +162 -0
  242. package/src/serve/studio/index.ts +20 -0
  243. package/src/serve/studio/routes.ts +237 -0
  244. package/src/serve/studio/store-types.ts +48 -0
  245. package/src/serve/studio/studio-storage.test.ts +289 -0
  246. package/src/serve/vite-plugin.ts +1102 -0
  247. package/src/serve/worker/activity-bootstrap.ts +24 -0
  248. package/src/serve/worker/client/admin-firestore.ts +38 -0
  249. package/src/serve/worker/client/ai.ts +124 -0
  250. package/src/serve/worker/client/auth.ts +489 -0
  251. package/src/serve/worker/client/connection.ts +202 -0
  252. package/src/serve/worker/client/core.ts +350 -0
  253. package/src/serve/worker/client/disconnect.ts +45 -0
  254. package/src/serve/worker/client/firestore-reads.ts +181 -0
  255. package/src/serve/worker/client/firestore-refs.ts +226 -0
  256. package/src/serve/worker/client/firestore-writes.ts +232 -0
  257. package/src/serve/worker/client/handles.ts +91 -0
  258. package/src/serve/worker/client/messaging.ts +119 -0
  259. package/src/serve/worker/client/presence.ts +174 -0
  260. package/src/serve/worker/client/rtdb.ts +311 -0
  261. package/src/serve/worker/client/rules.ts +63 -0
  262. package/src/serve/worker/client/service-worker-connection.ts +59 -0
  263. package/src/serve/worker/client/snapshots.ts +86 -0
  264. package/src/serve/worker/client/storage.ts +195 -0
  265. package/src/serve/worker/client/studio.ts +88 -0
  266. package/src/serve/worker/client.ts +57 -0
  267. package/src/serve/worker/durable-persistence.ts +137 -0
  268. package/src/serve/worker/entry.ts +179 -0
  269. package/src/serve/worker/host/admin-firestore.ts +88 -0
  270. package/src/serve/worker/host/connection.ts +179 -0
  271. package/src/serve/worker/host/core.ts +387 -0
  272. package/src/serve/worker/host/dispatch.ts +316 -0
  273. package/src/serve/worker/host/firestore-reads.ts +117 -0
  274. package/src/serve/worker/host/firestore-writes.ts +456 -0
  275. package/src/serve/worker/host/presence.ts +312 -0
  276. package/src/serve/worker/host/rtdb.ts +136 -0
  277. package/src/serve/worker/host/rules.ts +128 -0
  278. package/src/serve/worker/host/storage.ts +312 -0
  279. package/src/serve/worker/host/studio.ts +75 -0
  280. package/src/serve/worker/host/subscriptions.ts +238 -0
  281. package/src/serve/worker/host-ai.ts +165 -0
  282. package/src/serve/worker/host-auth.ts +469 -0
  283. package/src/serve/worker/host-context.ts +273 -0
  284. package/src/serve/worker/host-events.ts +94 -0
  285. package/src/serve/worker/host-messaging.ts +240 -0
  286. package/src/serve/worker/host.ts +47 -0
  287. package/src/serve/worker/index.ts +196 -0
  288. package/src/serve/worker/presence-timing.ts +15 -0
  289. package/src/serve/worker/protocol.ts +1112 -0
  290. package/src/serve/worker/serve-init.ts +606 -0
  291. package/src/serve/worker/service-worker-channel.ts +33 -0
  292. package/src/serve/worker/service-worker-relay.ts +86 -0
  293. package/src/serve/writer-lock.ts +53 -0
  294. package/src/verify/cases.ts +233 -0
  295. package/src/verify/fixture.ts +258 -0
  296. package/src/verify/index.ts +519 -0
  297. package/src/verify/tools.ts +108 -0
  298. package/src/version/compat-target.ts +17 -0
  299. package/src/vite.ts +17 -0
  300. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +0 -31
  301. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api.md +0 -29
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Framework-free provider account picker controller for `pyric dev`.
3
+ *
4
+ * The controller is intentionally backend-agnostic. It receives an identity
5
+ * directory and returns a Firebase-shaped credential; it never constructs an
6
+ * Auth handle or mutates a Sandbox. In SharedWorker mode the directory reads
7
+ * the worker's user pool and the picked credential is committed by
8
+ * `auth.acceptIdentity`. The in-page fallback supplies a small adapter over its
9
+ * local auth user store.
10
+ */
11
+ import type {
12
+ AuthFlowRequest,
13
+ AuthFlowResolver,
14
+ User,
15
+ UserCredential,
16
+ } from 'pyric/auth';
17
+
18
+ export interface NewIdentitySpec {
19
+ email: string;
20
+ displayName?: string;
21
+ /** Parsed custom claims (the emulator's `customAttributes`). */
22
+ customClaims?: Record<string, unknown>;
23
+ }
24
+
25
+ export interface HelperIdentity {
26
+ uid: string;
27
+ email: string | null;
28
+ displayName: string | null;
29
+ customClaims: Record<string, unknown>;
30
+ }
31
+
32
+ export interface HelperIdentityDirectory {
33
+ list(): HelperIdentity[] | Promise<HelperIdentity[]>;
34
+ /** Optional on the worker path: `auth.acceptIdentity` performs the write. */
35
+ add?(identity: HelperIdentity): void | Promise<void>;
36
+ }
37
+
38
+ export interface HelperSnapshot {
39
+ /** The in-flight request, or null when no popup/redirect is pending. */
40
+ request: AuthFlowRequest | null;
41
+ identities: readonly HelperIdentity[];
42
+ }
43
+
44
+ type Pending = {
45
+ req: AuthFlowRequest;
46
+ resolve: (c: UserCredential) => void;
47
+ reject: (e: unknown) => void;
48
+ };
49
+
50
+ export class ServeAuthHelper {
51
+ private pending: Pending | null = null;
52
+ private readonly listeners = new Set<() => void>();
53
+ private cached: HelperSnapshot | null = null;
54
+ private identities: HelperIdentity[] = [];
55
+
56
+ constructor(private readonly directory: HelperIdentityDirectory) {
57
+ this.refreshIdentities();
58
+ }
59
+
60
+ resolver(): AuthFlowResolver {
61
+ const open = (req: AuthFlowRequest): Promise<UserCredential> =>
62
+ new Promise<UserCredential>((resolve, reject) => {
63
+ if (this.pending) this.cancel();
64
+ this.pending = { req, resolve, reject };
65
+ this.emit();
66
+ this.refreshIdentities();
67
+ });
68
+ return { openPopup: open, openRedirect: open };
69
+ }
70
+
71
+ subscribe(fn: () => void): () => void {
72
+ this.listeners.add(fn);
73
+ return () => this.listeners.delete(fn);
74
+ }
75
+
76
+ snapshot(): HelperSnapshot {
77
+ this.cached ??= {
78
+ request: this.pending?.req ?? null,
79
+ identities: this.identities,
80
+ };
81
+ return this.cached;
82
+ }
83
+
84
+ private emit(): void {
85
+ this.cached = null;
86
+ for (const listener of this.listeners) listener();
87
+ }
88
+
89
+ private refreshIdentities(): void {
90
+ try {
91
+ const listed = this.directory.list();
92
+ if (listed instanceof Promise) {
93
+ void listed.then(
94
+ (identities) => {
95
+ this.identities = identities;
96
+ this.emit();
97
+ },
98
+ (error) => {
99
+ const pending = this.take();
100
+ pending?.reject(error);
101
+ },
102
+ );
103
+ } else {
104
+ this.identities = listed;
105
+ this.emit();
106
+ }
107
+ } catch (error) {
108
+ const pending = this.take();
109
+ pending?.reject(error);
110
+ }
111
+ }
112
+
113
+ pick(uid: string): void {
114
+ const pending = this.take();
115
+ if (!pending) return;
116
+ const identity = this.identities.find((candidate) => candidate.uid === uid);
117
+ if (!identity) {
118
+ pending.reject(authError('auth/internal-error', `unknown identity ${uid}`));
119
+ return;
120
+ }
121
+ pending.resolve(this.credential(identity, pending.req.providerId));
122
+ }
123
+
124
+ add(spec: NewIdentitySpec): void {
125
+ const pending = this.take();
126
+ if (!pending) return;
127
+ const identity: HelperIdentity = {
128
+ uid: `${pending.req.providerId}:${spec.email}`,
129
+ email: spec.email,
130
+ displayName: spec.displayName ?? null,
131
+ customClaims: spec.customClaims ?? {},
132
+ };
133
+ void Promise.resolve(this.directory.add?.(identity)).then(
134
+ () => pending.resolve(this.credential(identity, pending.req.providerId)),
135
+ pending.reject,
136
+ );
137
+ }
138
+
139
+ cancel(): void {
140
+ const pending = this.take();
141
+ if (!pending) return;
142
+ pending.reject(
143
+ authError(
144
+ 'auth/popup-closed-by-user',
145
+ 'The popup has been closed by the user before finalizing the operation.',
146
+ ),
147
+ );
148
+ }
149
+
150
+ private take(): Pending | null {
151
+ const pending = this.pending;
152
+ this.pending = null;
153
+ this.emit();
154
+ return pending;
155
+ }
156
+
157
+ private credential(identity: HelperIdentity, providerId: string): UserCredential {
158
+ const user: User = {
159
+ uid: identity.uid,
160
+ email: identity.email,
161
+ displayName: identity.displayName,
162
+ isAnonymous: false,
163
+ getIdToken: async () => `pyric-serve-${identity.uid}`,
164
+ getIdTokenResult: async () => ({
165
+ token: `pyric-serve-${identity.uid}`,
166
+ claims: { sub: identity.uid, ...identity.customClaims },
167
+ expirationTime: new Date(Date.now() + 3600_000).toISOString(),
168
+ issuedAtTime: new Date().toISOString(),
169
+ authTime: new Date().toISOString(),
170
+ }),
171
+ };
172
+ return { user, providerId, operationType: 'signIn' };
173
+ }
174
+ }
175
+
176
+ function authError(code: string, message: string): Error & { code: string } {
177
+ const error = new Error(message) as Error & { code: string };
178
+ error.name = 'FirebaseError';
179
+ error.code = code;
180
+ return error;
181
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * The `<dialog>` shell over {@link ServeAuthHelper} — pyric dev's analog
3
+ * of the Auth emulator's sign-in widget. Pure-DOM (no framework): renders an
4
+ * account picker + an add-account form (email, display name, custom-claims
5
+ * JSON — emulator parity, `customAttributes`), opens when a popup/redirect
6
+ * request parks in the helper, closes on settle. Backdrop/Escape cancel →
7
+ * `auth/popup-closed-by-user` via the core.
8
+ *
9
+ * Verified through the scripted browser gate (the bun suite covers the core;
10
+ * this file is intentionally view-only).
11
+ */
12
+ import type { ServeAuthHelper } from './auth-helper-core.js';
13
+
14
+ const PROVIDER_LABEL: Record<string, string> = {
15
+ 'google.com': 'Google',
16
+ 'facebook.com': 'Facebook',
17
+ 'github.com': 'GitHub',
18
+ 'apple.com': 'Apple',
19
+ };
20
+
21
+ const STYLE = `
22
+ dialog[data-pyric-auth] { border: 1px solid #2a2a35; border-radius: 12px; background: #16161d;
23
+ color: #e8e8ee; font: 14px/1.45 system-ui, sans-serif; padding: 20px; width: min(420px, 92vw); }
24
+ dialog[data-pyric-auth]::backdrop { background: rgb(0 0 0 / 0.6); }
25
+ dialog[data-pyric-auth] h2 { margin: 0 0 4px; font-size: 16px; }
26
+ dialog[data-pyric-auth] p { margin: 0 0 14px; font-size: 12px; color: #9a9aa8; }
27
+ dialog[data-pyric-auth] .ids { display: grid; gap: 8px; margin-bottom: 14px; }
28
+ dialog[data-pyric-auth] .ids button { display: flex; gap: 10px; align-items: center; text-align: left;
29
+ padding: 8px 10px; border: 1px solid #2a2a35; border-radius: 8px; background: none; color: inherit; cursor: pointer; }
30
+ dialog[data-pyric-auth] .ids button:hover { border-color: #4a4a5a; background: rgb(255 255 255 / 0.05); }
31
+ dialog[data-pyric-auth] form { display: grid; gap: 8px; border-top: 1px solid #2a2a35; padding-top: 14px; }
32
+ dialog[data-pyric-auth] input, dialog[data-pyric-auth] textarea { background: none; color: inherit;
33
+ border: 1px solid #2a2a35; border-radius: 8px; padding: 8px 10px; font: inherit; }
34
+ dialog[data-pyric-auth] textarea { font-family: ui-monospace, monospace; font-size: 12px; }
35
+ dialog[data-pyric-auth] .row { display: flex; justify-content: flex-end; gap: 8px; margin-top: 6px; }
36
+ dialog[data-pyric-auth] .row button { padding: 8px 14px; border-radius: 8px; border: none; cursor: pointer; font: inherit; }
37
+ dialog[data-pyric-auth] .cancel { background: none; color: #9a9aa8; }
38
+ dialog[data-pyric-auth] .submit { background: #5b5bd6; color: white; }
39
+ dialog[data-pyric-auth] .err { color: #ff6b6b; font-size: 12px; }
40
+ `;
41
+
42
+ export function mountAuthHelperDialog(helper: ServeAuthHelper): void {
43
+ const style = document.createElement('style');
44
+ style.textContent = STYLE;
45
+ document.head.appendChild(style);
46
+
47
+ const dialog = document.createElement('dialog');
48
+ dialog.setAttribute('data-pyric-auth', '');
49
+ document.body.appendChild(dialog);
50
+
51
+ // <dialog> cancel (Escape) + backdrop click → cancel the flow.
52
+ dialog.addEventListener('cancel', () => helper.cancel());
53
+ dialog.addEventListener('click', (e) => {
54
+ if (e.target === dialog) helper.cancel(); // backdrop only
55
+ });
56
+
57
+ const render = (): void => {
58
+ const snap = helper.snapshot();
59
+ if (!snap.request) {
60
+ if (dialog.open) dialog.close();
61
+ return;
62
+ }
63
+ const provider = PROVIDER_LABEL[snap.request.providerId] ?? snap.request.providerId;
64
+ dialog.replaceChildren();
65
+
66
+ const h2 = document.createElement('h2');
67
+ h2.textContent = `Sign in with ${provider}`;
68
+ const p = document.createElement('p');
69
+ p.textContent =
70
+ 'pyric dev sign-in helper — pick a test account or add one. Custom claims let rules using request.auth.token.* run against this identity.';
71
+ dialog.append(h2, p);
72
+
73
+ if (snap.identities.length > 0) {
74
+ const ids = document.createElement('div');
75
+ ids.className = 'ids';
76
+ for (const id of snap.identities) {
77
+ const btn = document.createElement('button');
78
+ btn.type = 'button';
79
+ const label = id.displayName || id.email || id.uid;
80
+ btn.textContent = id.email && id.displayName ? `${label} — ${id.email}` : label;
81
+ btn.addEventListener('click', () => helper.pick(id.uid));
82
+ ids.appendChild(btn);
83
+ }
84
+ dialog.appendChild(ids);
85
+ }
86
+
87
+ const form = document.createElement('form');
88
+ form.method = 'dialog';
89
+ const email = document.createElement('input');
90
+ email.type = 'email';
91
+ email.placeholder = 'email@example.com';
92
+ email.required = true;
93
+ const name = document.createElement('input');
94
+ name.type = 'text';
95
+ name.placeholder = 'Display name (optional)';
96
+ const claims = document.createElement('textarea');
97
+ claims.rows = 3;
98
+ claims.placeholder = 'Custom claims JSON (optional)\ne.g. { "admin": true }';
99
+ const err = document.createElement('div');
100
+ err.className = 'err';
101
+ const row = document.createElement('div');
102
+ row.className = 'row';
103
+ const cancel = document.createElement('button');
104
+ cancel.type = 'button';
105
+ cancel.className = 'cancel';
106
+ cancel.textContent = 'Cancel';
107
+ cancel.addEventListener('click', () => helper.cancel());
108
+ const submit = document.createElement('button');
109
+ submit.type = 'submit';
110
+ submit.className = 'submit';
111
+ submit.textContent = 'Sign in';
112
+ row.append(cancel, submit);
113
+ form.append(email, name, claims, err, row);
114
+ form.addEventListener('submit', (e) => {
115
+ e.preventDefault();
116
+ let parsed: Record<string, unknown> | undefined;
117
+ if (claims.value.trim()) {
118
+ try {
119
+ parsed = JSON.parse(claims.value) as Record<string, unknown>;
120
+ } catch {
121
+ err.textContent = 'Custom claims must be valid JSON.';
122
+ return;
123
+ }
124
+ }
125
+ helper.add({ email: email.value.trim(), displayName: name.value.trim() || undefined, customClaims: parsed });
126
+ });
127
+ dialog.appendChild(form);
128
+
129
+ if (!dialog.open) dialog.showModal();
130
+ };
131
+
132
+ helper.subscribe(render);
133
+ render();
134
+ }
@@ -0,0 +1,20 @@
1
+ /** Shared resolver slot connecting the injected init entry to firebase/auth. */
2
+ import type { AuthFlowRequest, AuthFlowResolver, UserCredential } from 'pyric/auth';
3
+
4
+ let resolver: AuthFlowResolver | null = null;
5
+
6
+ export function installServeAuthResolver(next: AuthFlowResolver): void {
7
+ resolver = next;
8
+ }
9
+
10
+ export function resolveServeAuthFlow(
11
+ request: AuthFlowRequest,
12
+ kind: 'popup' | 'redirect',
13
+ ): Promise<UserCredential> {
14
+ if (!resolver) {
15
+ return Promise.reject(
16
+ new Error('pyric dev provider helper is not initialized; load /__pyric/sdk/init.js first'),
17
+ );
18
+ }
19
+ return kind === 'popup' ? resolver.openPopup(request) : resolver.openRedirect(request);
20
+ }
@@ -0,0 +1,255 @@
1
+ /**
2
+ * The bundle the import map serves for `firebase/auth`.
3
+ *
4
+ * DUAL-PATH (Phase 3c): when a SharedWorker is available, auth routes to the
5
+ * ONE worker-hosted sandbox — shared user pool + data, PER-TAB sessions
6
+ * (#754: each tab signs in as its own user). Otherwise it runs against the
7
+ * in-page sandbox — the unchanged fallback. Branch picked ONCE at load
8
+ * (`useWorker`).
9
+ *
10
+ * PROVIDER SIGN-IN: the picker UI lives in-page, but its controller is
11
+ * backend-free. In worker mode it lists identities from the worker and returns
12
+ * a plain credential to `auth.acceptIdentity`; no page-local Auth handle signs
13
+ * in or seeds a user. Email/password + anonymous also go straight to the
14
+ * worker. The COMPLETE surface is exported on both paths (import-time parity).
15
+ */
16
+ import * as ipAuth from 'pyric/auth';
17
+ import { getAuth as pyricGetAuth, setPersistence as pyricSetPersistence } from 'pyric/auth';
18
+ import * as wcRaw from '../worker/client.js';
19
+ import { acceptProviderCredential, restorePortSession } from '../worker/client.js';
20
+ import { useWorker } from './worker-runtime.js';
21
+ import { sessionStoreForApp } from './app-session-store.js';
22
+ import { resolveServeAuthFlow } from './auth-helper-runtime.js';
23
+ import type { SessionMode } from './session-store.js';
24
+ import { getApp, type FirebaseApp } from 'pyric/app';
25
+ import { workerClientForApp } from './app-client.js';
26
+
27
+ // Worker-client auth, cast to the canonical pyric/auth surface for the picked
28
+ // bindings (same names + shapes). Provider-bridge specifics are explicit below.
29
+ const wc = wcRaw as unknown as typeof ipAuth;
30
+
31
+ /** Observers + email/anon sign-in: worker-client or in-page. */
32
+ const A = useWorker ? wc : ipAuth;
33
+ export const onAuthStateChanged = A.onAuthStateChanged;
34
+ export const onIdTokenChanged = A.onIdTokenChanged;
35
+ /**
36
+ * Worker mode: the gate can't run before a worker-committed transition
37
+ * (see `worker/client.ts`'s `beforeAuthStateChanged` doc) — registering
38
+ * throws immediately rather than silently accepting a no-op callback.
39
+ * In-page mode: full block-and-abort semantics via `pyric/auth`.
40
+ */
41
+ export const beforeAuthStateChanged = A.beforeAuthStateChanged;
42
+ export const signInAnonymously = A.signInAnonymously;
43
+ export const signInWithEmailAndPassword = A.signInWithEmailAndPassword;
44
+ export const createUserWithEmailAndPassword = A.createUserWithEmailAndPassword;
45
+ export const signOut = A.signOut;
46
+ export const connectAuthEmulator = A.connectAuthEmulator;
47
+
48
+ /**
49
+ * `updateProfile(user, { displayName?, photoURL? })` — updates the signed-in
50
+ * user's profile on the picked path (worker or in-page). Both `wc` and `ipAuth`
51
+ * export a top-level `updateProfile`; cast to the canonical `pyric/auth` shape.
52
+ * (Fixes #746: the served `firebase/auth` entry didn't re-export this at all.)
53
+ */
54
+ export const updateProfile = (useWorker ? wc.updateProfile : ipAuth.updateProfile) as typeof ipAuth.updateProfile;
55
+
56
+ // ── Provider value classes + persistence markers — path-independent ───────
57
+ export const GoogleAuthProvider = ipAuth.GoogleAuthProvider;
58
+ export const EmailAuthProvider = ipAuth.EmailAuthProvider;
59
+ export const FacebookAuthProvider = ipAuth.FacebookAuthProvider;
60
+ export const GithubAuthProvider = ipAuth.GithubAuthProvider;
61
+ export const OAuthProvider = ipAuth.OAuthProvider;
62
+ export const browserLocalPersistence = ipAuth.browserLocalPersistence;
63
+ export const browserSessionPersistence = ipAuth.browserSessionPersistence;
64
+ export const inMemoryPersistence = ipAuth.inMemoryPersistence;
65
+
66
+ // ── getAuth — worker-backed auth (reusing the shared worker port) or in-page.
67
+ const workerAuthByApp = new WeakMap<FirebaseApp, ReturnType<typeof pyricGetAuth>>();
68
+ const persistenceWired = new WeakSet<FirebaseApp>();
69
+
70
+ function wireAppPersistence(
71
+ app: FirebaseApp,
72
+ auth: ReturnType<typeof pyricGetAuth>,
73
+ ): void {
74
+ if (persistenceWired.has(app)) return;
75
+ persistenceWired.add(app);
76
+ const store = sessionStoreForApp(app);
77
+ const stored = store.load();
78
+ if (useWorker && stored) {
79
+ // Posting restore before listener registration preserves FIFO ordering on
80
+ // this app's MessagePort, so the initial listener value is the restore.
81
+ void restorePortSession(auth as never, stored.uid).then((user) => {
82
+ if (!user) store.clear();
83
+ });
84
+ } else if (!useWorker && stored) {
85
+ try {
86
+ ipAuth.sandbox.restoreSession(auth, stored.uid);
87
+ } catch {
88
+ store.clear();
89
+ }
90
+ }
91
+ A.onAuthStateChanged(auth, (user) => {
92
+ if (user) store.save(user.uid);
93
+ else store.clear();
94
+ });
95
+ }
96
+
97
+ export const getAuth = ((app?: FirebaseApp) => {
98
+ const resolved = app ?? getApp();
99
+ if (!useWorker) {
100
+ const handle = pyricGetAuth(resolved);
101
+ wireAppPersistence(resolved, handle);
102
+ return handle;
103
+ }
104
+ const existing = workerAuthByApp.get(resolved);
105
+ if (existing) return existing;
106
+ const client = workerClientForApp(resolved);
107
+ const handle = Object.assign(wc.getAuth(client as never), {
108
+ app: resolved,
109
+ }) as unknown as ReturnType<typeof pyricGetAuth>;
110
+ workerAuthByApp.set(resolved, handle);
111
+ wireAppPersistence(resolved, handle);
112
+ return handle;
113
+ }) as typeof pyricGetAuth;
114
+
115
+ /**
116
+ * Narrow a `Persistence.type` to the three web-storage slots the session store
117
+ * actually has.
118
+ *
119
+ * `'COOKIE'` (upstream's `browserCookiePersistence`, for SSR) is the fourth
120
+ * member of the union and has no slot of its own here. It maps to `'LOCAL'`:
121
+ * both are long-lived session storage, and the distinction is invisible to
122
+ * consumer code once the session is restored either way. The alternative —
123
+ * widening SessionMode — would add a storage backend the serve layer does not
124
+ * have.
125
+ */
126
+ function toSessionMode(type: 'LOCAL' | 'SESSION' | 'NONE' | 'COOKIE'): SessionMode {
127
+ return type === 'COOKIE' ? 'LOCAL' : type;
128
+ }
129
+
130
+ /** `setPersistence`: selects which web storage holds THIS TAB's session (and
131
+ * migrates the current one). On the worker path the record is still client-
132
+ * side (#754 — per-port sessions restore via `auth.restorePortSession`); the
133
+ * RPC just keeps the worker's surface-parity mode in sync. */
134
+ export const setPersistence = (
135
+ useWorker
136
+ ? (async (
137
+ auth: Parameters<typeof pyricSetPersistence>[0],
138
+ persistence: Parameters<typeof pyricSetPersistence>[1],
139
+ ) => {
140
+ sessionStoreForApp(auth.app ?? getApp()).setMode(toSessionMode(persistence.type));
141
+ return (wc.setPersistence as typeof pyricSetPersistence)(auth, persistence);
142
+ })
143
+ : async (
144
+ auth: Parameters<typeof pyricSetPersistence>[0],
145
+ persistence: Parameters<typeof pyricSetPersistence>[1],
146
+ ) => {
147
+ sessionStoreForApp(auth.app ?? getApp()).setMode(toSessionMode(persistence.type));
148
+ return pyricSetPersistence(auth, persistence);
149
+ }
150
+ ) as typeof pyricSetPersistence;
151
+
152
+ // ── Provider sign-in (popup/redirect) — the in-page→worker bridge ─────────
153
+
154
+ /**
155
+ * Resolve a provider identity through the backend-free ServeAuthHelper, then
156
+ * hand it to the worker. The
157
+ * helper seeds `{ sub, ...claims }` into the resolved credential's token, so we
158
+ * strip `sub` to recover the original custom claims for the worker seed.
159
+ *
160
+ * Enforcement lives at the hand-off: `auth.acceptIdentity` gates against the
161
+ * WORKER's provider config and rejects `auth/operation-not-allowed` for a
162
+ * disabled provider (matching prod, where the popup opens and the error
163
+ * surfaces after the interaction).
164
+ */
165
+ async function bridgeProviderSignIn(
166
+ auth: Parameters<typeof ipAuth.signInWithPopup>[0],
167
+ provider: Parameters<typeof ipAuth.signInWithPopup>[1],
168
+ kind: 'popup' | 'redirect',
169
+ ): Promise<unknown> {
170
+ const providerId = (provider as { providerId?: string }).providerId ?? 'oidc';
171
+ const cred = await resolveServeAuthFlow(
172
+ { providerId, authType: 'signIn' },
173
+ kind,
174
+ );
175
+ const tokenResult = await cred.user.getIdTokenResult();
176
+ const { sub: _sub, ...customClaims } = (tokenResult.claims ?? {}) as Record<string, unknown>;
177
+ return acceptProviderCredential(auth as never, {
178
+ uid: cred.user.uid,
179
+ email: cred.user.email,
180
+ displayName: cred.user.displayName,
181
+ customClaims,
182
+ providerId,
183
+ });
184
+ }
185
+
186
+ export const signInWithPopup = (
187
+ useWorker
188
+ ? (auth: Parameters<typeof ipAuth.signInWithPopup>[0], provider: Parameters<typeof ipAuth.signInWithPopup>[1]) =>
189
+ bridgeProviderSignIn(auth, provider, 'popup')
190
+ : ipAuth.signInWithPopup
191
+ ) as typeof ipAuth.signInWithPopup;
192
+
193
+ /**
194
+ * `signInWithRedirect` over the worker resolves like popup — the pyric
195
+ * emulator helper does not actually navigate, so the identity is picked + the
196
+ * worker signs in immediately. The result is delivered through the sign-in +
197
+ * `onAuthStateChanged`; `getRedirectResult` returns null on the worker path
198
+ * (there is no deferred post-navigation result to replay). v1 behavior.
199
+ */
200
+ export const signInWithRedirect = (
201
+ useWorker
202
+ ? (auth: Parameters<typeof ipAuth.signInWithRedirect>[0], provider: Parameters<typeof ipAuth.signInWithRedirect>[1]) =>
203
+ bridgeProviderSignIn(auth, provider, 'redirect')
204
+ : ipAuth.signInWithRedirect
205
+ ) as typeof ipAuth.signInWithRedirect;
206
+
207
+ export const getRedirectResult = (
208
+ useWorker
209
+ ? async (_auth?: unknown) => null
210
+ : ipAuth.getRedirectResult
211
+ ) as typeof ipAuth.getRedirectResult;
212
+
213
+ /** Direct credential sign-in: unsupported over the worker in v1 (the
214
+ * worker-client rejects with a clear error); in-page passes through. */
215
+ export const signInWithCredential = (
216
+ useWorker ? wc.signInWithCredential : ipAuth.signInWithCredential
217
+ ) as typeof ipAuth.signInWithCredential;
218
+
219
+ // ── Low-hanging-fruit exports (issue #149) ────────────────────────────────
220
+
221
+ function unsupportedWorkerAuthApi(name: string): never {
222
+ throw new Error(
223
+ `firebase/auth ${name}() is not supported over the pyric SharedWorker yet. ` +
224
+ 'Use the in-page fallback for this operation.',
225
+ );
226
+ }
227
+
228
+ /** `initializeAuth(app, deps?)` — aliases the picked `getAuth` (worker- or
229
+ * in-page-backed). Path-independent: `deps` is accepted for parity, ignored. */
230
+ export const initializeAuth = ((app?: unknown, _deps?: unknown) =>
231
+ getAuth(app as never)) as typeof ipAuth.initializeAuth;
232
+
233
+ /** `useDeviceLanguage(auth)` — accepted no-op (no device locale in the served
234
+ * sandbox). Path-independent. */
235
+ export const useDeviceLanguage = ((_auth?: unknown) => {}) as typeof ipAuth.useDeviceLanguage;
236
+
237
+ // User-mutation ops act on a `User` object. In-page they route through the
238
+ // sandbox User's dispatch hook; over the SharedWorker the client has no
239
+ // client-facing binding for them yet, so worker mode throws the standard
240
+ // "use the in-page fallback" error (mirrors the RTDB entry's unsupported ops).
241
+ export const deleteUser = (
242
+ useWorker ? (() => unsupportedWorkerAuthApi('deleteUser')) : ipAuth.deleteUser
243
+ ) as typeof ipAuth.deleteUser;
244
+ export const updateEmail = (
245
+ useWorker ? (() => unsupportedWorkerAuthApi('updateEmail')) : ipAuth.updateEmail
246
+ ) as typeof ipAuth.updateEmail;
247
+ export const updatePassword = (
248
+ useWorker ? (() => unsupportedWorkerAuthApi('updatePassword')) : ipAuth.updatePassword
249
+ ) as typeof ipAuth.updatePassword;
250
+ export const reload = (
251
+ useWorker ? (() => unsupportedWorkerAuthApi('reload')) : ipAuth.reload
252
+ ) as typeof ipAuth.reload;
253
+ export const updateCurrentUser = (
254
+ useWorker ? (() => unsupportedWorkerAuthApi('updateCurrentUser')) : ipAuth.updateCurrentUser
255
+ ) as typeof ipAuth.updateCurrentUser;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Re-anchor a bridge WebSocket URL to the page's own origin.
3
+ *
4
+ * `pyric dev` / the vite plugin bake their OWN host into the bridge URL it
5
+ * sends the page (e.g. `ws://localhost:5173/__pyric/sandbox`). But the page may
6
+ * have been loaded over a different host (Tailscale, a LAN IP) or scheme
7
+ * (`https` via `tailscale serve`, which then requires `wss`). Connecting to the
8
+ * baked `localhost` from a remote tab dials the WRONG machine (the client's own
9
+ * localhost), so the WS fails.
10
+ *
11
+ * Keep only the PATH from the server's URL and rebuild the scheme + host from the
12
+ * page's `location`. The bridge is always mounted on the same server that served
13
+ * the page, so the page's origin is the correct target wherever it is reached,
14
+ * with no plugin configuration. This also sidesteps the localhost / 127.0.0.1 /
15
+ * ::1 family ambiguity, because the browser dials the exact host it loaded from.
16
+ *
17
+ * Pure (location is injected) so it is unit-testable. Returns `raw` unchanged if
18
+ * it cannot be parsed.
19
+ */
20
+ export function toPageOriginWsUrl(
21
+ raw: string,
22
+ loc: { href: string; protocol: string; host: string },
23
+ ): string {
24
+ try {
25
+ const path = new URL(raw, loc.href).pathname;
26
+ const scheme = loc.protocol === 'https:' ? 'wss:' : 'ws:';
27
+ return `${scheme}//${loc.host}${path}`;
28
+ } catch {
29
+ return raw;
30
+ }
31
+ }