@pyric/cli 0.1.0-alpha.11 → 0.1.0-alpha.13
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.
- package/dist/cli/serve.js +4 -4
- package/dist/cli/serve.js.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
- package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use.js +2 -2
- package/dist/conformance/.generated/can-i-use.js.map +1 -1
- package/dist/serve/bundler.d.ts +13 -8
- package/dist/serve/bundler.d.ts.map +1 -1
- package/dist/serve/bundler.js +31 -36
- package/dist/serve/bundler.js.map +1 -1
- package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
- package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/api-reference/index.html +2 -2
- package/dist/serve/docs-ui/docs/api-reference.md +1 -1
- package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/ai-logic/index.html +44 -22
- package/dist/serve/docs-ui/docs/build/ai-logic.md +69 -21
- package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
- package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
- package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +2 -1
- package/dist/serve/docs-ui/docs/get-started/how-the-swap-works.md +2 -0
- package/dist/serve/docs-ui/docs/get-started/start-building/index.html +3 -2
- package/dist/serve/docs-ui/docs/get-started/start-building.md +3 -1
- package/dist/serve/docs-ui/docs/index.json +349 -29
- package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/observe/resolve-runtime-status/index.html +28 -0
- package/dist/serve/docs-ui/docs/observe/resolve-runtime-status.md +47 -0
- package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
- package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
- package/dist/serve/docs-ui/docs/overview/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +916 -0
- package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api.md +706 -0
- package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +168 -76
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +156 -73
- package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +11 -8
- package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api.md +17 -4
- package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +37 -14
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +28 -8
- package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
- package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
- package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
- package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
- package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
- package/dist/serve/docs-ui/docs/tutorial/index.html +429 -0
- package/dist/serve/docs-ui/docs/tutorial.md +701 -0
- package/dist/serve/docs-ui/llms.txt +3 -1
- package/dist/serve/entries/init.d.ts.map +1 -1
- package/dist/serve/entries/init.js +3 -0
- package/dist/serve/entries/init.js.map +1 -1
- package/dist/serve/entries/runtime.d.ts.map +1 -1
- package/dist/serve/entries/runtime.js +20 -5
- package/dist/serve/entries/runtime.js.map +1 -1
- package/dist/serve/entries/worker-runtime.d.ts +2 -2
- package/dist/serve/entries/worker-runtime.d.ts.map +1 -1
- package/dist/serve/entries/worker-runtime.js +45 -10
- package/dist/serve/entries/worker-runtime.js.map +1 -1
- package/dist/serve/runtime/chip-config.d.ts +19 -0
- package/dist/serve/runtime/chip-config.d.ts.map +1 -0
- package/dist/serve/runtime/chip-config.js +19 -0
- package/dist/serve/runtime/chip-config.js.map +1 -0
- package/dist/serve/runtime/chip-install.d.ts +10 -0
- package/dist/serve/runtime/chip-install.d.ts.map +1 -0
- package/dist/serve/runtime/chip-install.js +15 -0
- package/dist/serve/runtime/chip-install.js.map +1 -0
- package/dist/serve/runtime/chip.d.ts +17 -0
- package/dist/serve/runtime/chip.d.ts.map +1 -0
- package/dist/serve/runtime/chip.js +257 -0
- package/dist/serve/runtime/chip.js.map +1 -0
- package/dist/serve/runtime/manifest.d.ts +22 -0
- package/dist/serve/runtime/manifest.d.ts.map +1 -0
- package/dist/serve/runtime/manifest.js +20 -0
- package/dist/serve/runtime/manifest.js.map +1 -0
- package/dist/serve/runtime/status.d.ts +49 -0
- package/dist/serve/runtime/status.d.ts.map +1 -0
- package/dist/serve/runtime/status.js +164 -0
- package/dist/serve/runtime/status.js.map +1 -0
- package/dist/serve/runtime/worker-connection.d.ts +4 -0
- package/dist/serve/runtime/worker-connection.d.ts.map +1 -0
- package/dist/serve/runtime/worker-connection.js +11 -0
- package/dist/serve/runtime/worker-connection.js.map +1 -0
- package/dist/serve/runtime/worker-generation.d.ts +14 -0
- package/dist/serve/runtime/worker-generation.d.ts.map +1 -0
- package/dist/serve/runtime/worker-generation.js +56 -0
- package/dist/serve/runtime/worker-generation.js.map +1 -0
- package/dist/serve/runtime/worker-replacement.d.ts +18 -0
- package/dist/serve/runtime/worker-replacement.d.ts.map +1 -0
- package/dist/serve/runtime/worker-replacement.js +29 -0
- package/dist/serve/runtime/worker-replacement.js.map +1 -0
- package/dist/serve/standalone-assets.d.ts +2 -2
- package/dist/serve/standalone-assets.d.ts.map +1 -1
- package/dist/serve/standalone-assets.js +1 -1
- package/dist/serve/standalone-assets.js.map +1 -1
- package/dist/serve/studio-ui/assets/{RulesCodeEditor-CxVYcZXw.js → RulesCodeEditor-BDlH-5lp.js} +1 -1
- package/dist/serve/studio-ui/assets/{index-cpSfgQ7H.js → index-Bf9SSE8L.js} +5 -5
- package/dist/serve/studio-ui/assets/{seed-DeuFKi4J.js → seed-D_UjxfOU.js} +1 -1
- package/dist/serve/studio-ui/index.html +1 -1
- package/dist/serve/vite-ai-config.d.ts +25 -0
- package/dist/serve/vite-ai-config.d.ts.map +1 -0
- package/dist/serve/vite-ai-config.js +45 -0
- package/dist/serve/vite-ai-config.js.map +1 -0
- package/dist/serve/vite-plugin.d.ts +17 -36
- package/dist/serve/vite-plugin.d.ts.map +1 -1
- package/dist/serve/vite-plugin.js +38 -57
- package/dist/serve/vite-plugin.js.map +1 -1
- package/dist/serve/vite-rules-source.d.ts +8 -0
- package/dist/serve/vite-rules-source.d.ts.map +1 -0
- package/dist/serve/vite-rules-source.js +23 -0
- package/dist/serve/vite-rules-source.js.map +1 -0
- package/dist/serve/vite-worker-runtime.d.ts +20 -0
- package/dist/serve/vite-worker-runtime.d.ts.map +1 -0
- package/dist/serve/vite-worker-runtime.js +25 -0
- package/dist/serve/vite-worker-runtime.js.map +1 -0
- package/dist/serve/worker/client/connection.d.ts +7 -2
- package/dist/serve/worker/client/connection.d.ts.map +1 -1
- package/dist/serve/worker/client/connection.js +13 -16
- package/dist/serve/worker/client/connection.js.map +1 -1
- package/dist/serve/worker/client/core.d.ts +7 -0
- package/dist/serve/worker/client/core.d.ts.map +1 -1
- package/dist/serve/worker/client/core.js +28 -0
- package/dist/serve/worker/client/core.js.map +1 -1
- package/dist/serve/worker/client/runtime-control.d.ts +8 -0
- package/dist/serve/worker/client/runtime-control.d.ts.map +1 -0
- package/dist/serve/worker/client/runtime-control.js +11 -0
- package/dist/serve/worker/client/runtime-control.js.map +1 -0
- package/dist/serve/worker/entry.js +44 -2
- package/dist/serve/worker/entry.js.map +1 -1
- package/dist/serve/worker/host/core.js +1 -1
- package/dist/serve/worker/host/core.js.map +1 -1
- package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -1
- package/dist/serve/worker/host/firestore-writes.js +3 -2
- package/dist/serve/worker/host/firestore-writes.js.map +1 -1
- package/dist/serve/worker/host/studio.js +1 -1
- package/dist/serve/worker/host/studio.js.map +1 -1
- package/dist/serve/worker/host-context.d.ts +1 -1
- package/dist/serve/worker/host-context.d.ts.map +1 -1
- package/dist/serve/worker/index.d.ts +2 -1
- package/dist/serve/worker/index.d.ts.map +1 -1
- package/dist/serve/worker/index.js +2 -1
- package/dist/serve/worker/index.js.map +1 -1
- package/dist/serve/worker/protocol.d.ts +15 -1
- package/dist/serve/worker/protocol.d.ts.map +1 -1
- package/dist/serve/worker/protocol.js +2 -2
- package/dist/serve/worker/protocol.js.map +1 -1
- package/dist/serve/worker/retirement.d.ts +21 -0
- package/dist/serve/worker/retirement.d.ts.map +1 -0
- package/dist/serve/worker/retirement.js +97 -0
- package/dist/serve/worker/retirement.js.map +1 -0
- package/dist/serve/worker/serve-init.d.ts.map +1 -1
- package/dist/serve/worker/serve-init.js +5 -6
- package/dist/serve/worker/serve-init.js.map +1 -1
- package/dist/vite.d.ts +1 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js.map +1 -1
- package/package.json +6 -4
- package/src/assurance/.generated/conformance-verdicts.ts +1090 -0
- package/src/assurance/attachment.ts +211 -0
- package/src/assurance/browser.ts +80 -0
- package/src/assurance/campaign.ts +496 -0
- package/src/assurance/capabilities.ts +446 -0
- package/src/assurance/cases.ts +43 -0
- package/src/assurance/index.ts +75 -0
- package/src/assurance/runner.ts +885 -0
- package/src/assurance/tool-names.ts +15 -0
- package/src/assurance/tools.ts +830 -0
- package/src/assurance/types.ts +352 -0
- package/src/assurance/validation.ts +434 -0
- package/src/bridge/client/bridge.ts +550 -0
- package/src/bridge/client/dispatch.ts +137 -0
- package/src/bridge/client.ts +45 -0
- package/src/bridge/protocol.ts +351 -0
- package/src/bridge/server/audit.ts +53 -0
- package/src/bridge/server/bridge.ts +597 -0
- package/src/bridge/server/headless.ts +160 -0
- package/src/bridge/server/json-schema-to-zod.ts +108 -0
- package/src/bridge/server/local-bridge.ts +66 -0
- package/src/bridge/server/logger.ts +50 -0
- package/src/bridge/server/mcp-contract.ts +93 -0
- package/src/bridge/server/mcp.ts +129 -0
- package/src/bridge/server/peer.ts +232 -0
- package/src/bridge/server/standalone.ts +347 -0
- package/src/bridge/server/tool-metadata.ts +99 -0
- package/src/bridge/server.ts +28 -0
- package/src/cli/can-i-use.ts +50 -0
- package/src/cli/cli.test.ts +493 -0
- package/src/cli/database-rules.ts +329 -0
- package/src/cli/dev-runner.ts +282 -0
- package/src/cli/firebase-json.ts +100 -0
- package/src/cli/firestore-indexes.ts +70 -0
- package/src/cli/index.ts +425 -0
- package/src/cli/init.ts +260 -0
- package/src/cli/mcp-proxy.ts +196 -0
- package/src/cli/parse-args.ts +80 -0
- package/src/cli/rules.ts +259 -0
- package/src/cli/scope.ts +67 -0
- package/src/cli/serve.ts +996 -0
- package/src/cli/service-commands.ts +73 -0
- package/src/cli/snapshot.ts +164 -0
- package/src/cli/storage-rules.ts +172 -0
- package/src/cli/verify.ts +416 -0
- package/src/conformance/.generated/can-i-use-browser.ts +113 -0
- package/src/conformance/.generated/can-i-use.ts +117 -0
- package/src/conformance/.generated/conformance-docs.ts +14 -0
- package/src/conformance/browser.ts +24 -0
- package/src/conformance/can-i-use-tool.ts +51 -0
- package/src/conformance/can-i-use.ts +18 -0
- package/src/conformance/docs.ts +8 -0
- package/src/conformance/index.ts +20 -0
- package/src/conformance/tools.ts +16 -0
- package/src/credentials/core/memoize-ttl.ts +142 -0
- package/src/credentials/core/types.ts +10 -0
- package/src/credentials/node/from-adc.ts +96 -0
- package/src/credentials/node/from-service-account.ts +133 -0
- package/src/credentials/node/index.ts +6 -0
- package/src/discover/concurrency.ts +127 -0
- package/src/discover/crawler-adapter.ts +142 -0
- package/src/discover/crawler.ts +1127 -0
- package/src/discover/credential-free.ts +27 -0
- package/src/discover/findCollectionGroup.ts +131 -0
- package/src/discover/firestore-source.ts +59 -0
- package/src/discover/index.ts +5 -0
- package/src/discover/merge.ts +523 -0
- package/src/discover/session.ts +402 -0
- package/src/discover/tools.ts +201 -0
- package/src/discover/types.ts +187 -0
- package/src/discover/wire.ts +324 -0
- package/src/functions-rtdb/child.ts +382 -0
- package/src/functions-rtdb/delivery.ts +7 -0
- package/src/functions-rtdb/discovery.ts +136 -0
- package/src/functions-rtdb/event.ts +64 -0
- package/src/functions-rtdb/execution.ts +111 -0
- package/src/functions-rtdb/in-memory-delivery.ts +40 -0
- package/src/functions-rtdb/project.ts +74 -0
- package/src/functions-rtdb/projection.ts +104 -0
- package/src/functions-rtdb/reference-pattern.ts +20 -0
- package/src/functions-rtdb/remote-delivery.ts +23 -0
- package/src/pkg-version.ts +49 -0
- package/src/register/esm-exports.ts +63 -0
- package/src/register/hooks.ts +30 -0
- package/src/register/index.ts +150 -0
- package/src/register/mapping.ts +33 -0
- package/src/remote/index.ts +1050 -0
- package/src/rtdb/crawl-snapshot.ts +118 -0
- package/src/rtdb/inspection.ts +137 -0
- package/src/rtdb/load-rules-document.ts +56 -0
- package/src/rtdb/rules-generation-tool.ts +38 -0
- package/src/rtdb/rules-json.ts +29 -0
- package/src/serve/activity-guard.ts +29 -0
- package/src/serve/activity-route.ts +164 -0
- package/src/serve/activity-warning.ts +26 -0
- package/src/serve/bridge-mount.ts +200 -0
- package/src/serve/bundler.ts +519 -0
- package/src/serve/capture-store.ts +65 -0
- package/src/serve/discovery.ts +199 -0
- package/src/serve/entries/ai.ts +292 -0
- package/src/serve/entries/app-backend.ts +4 -0
- package/src/serve/entries/app-client.ts +26 -0
- package/src/serve/entries/app-session-store.ts +47 -0
- package/src/serve/entries/app.ts +11 -0
- package/src/serve/entries/auth-helper-core.ts +181 -0
- package/src/serve/entries/auth-helper-dom.ts +134 -0
- package/src/serve/entries/auth-helper-runtime.ts +20 -0
- package/src/serve/entries/auth.ts +255 -0
- package/src/serve/entries/bridge-url.ts +31 -0
- package/src/serve/entries/database.ts +129 -0
- package/src/serve/entries/firestore.ts +232 -0
- package/src/serve/entries/init.ts +49 -0
- package/src/serve/entries/keepalive.ts +46 -0
- package/src/serve/entries/messaging-sw.ts +75 -0
- package/src/serve/entries/messaging.ts +96 -0
- package/src/serve/entries/runtime.ts +556 -0
- package/src/serve/entries/session-store.ts +97 -0
- package/src/serve/entries/storage.ts +92 -0
- package/src/serve/entries/tab-sync-wiring.ts +274 -0
- package/src/serve/entries/worker-runtime.ts +135 -0
- package/src/serve/init-payload.ts +42 -0
- package/src/serve/namespace.ts +675 -0
- package/src/serve/open-browser.ts +68 -0
- package/src/serve/rules.ts +272 -0
- package/src/serve/runtime/chip-config.ts +30 -0
- package/src/serve/runtime/chip-install.ts +27 -0
- package/src/serve/runtime/chip.ts +282 -0
- package/src/serve/runtime/manifest.ts +37 -0
- package/src/serve/runtime/status.ts +216 -0
- package/src/serve/runtime/worker-connection.ts +14 -0
- package/src/serve/runtime/worker-generation.ts +75 -0
- package/src/serve/runtime/worker-replacement.ts +42 -0
- package/src/serve/sandbox-marker.ts +31 -0
- package/src/serve/server.ts +428 -0
- package/src/serve/standalone-assets.ts +180 -0
- package/src/serve/state-store.ts +166 -0
- package/src/serve/studio/disk-project-store.ts +185 -0
- package/src/serve/studio/disk-workspace.ts +162 -0
- package/src/serve/studio/index.ts +20 -0
- package/src/serve/studio/routes.ts +237 -0
- package/src/serve/studio/store-types.ts +48 -0
- package/src/serve/studio/studio-storage.test.ts +289 -0
- package/src/serve/vite-ai-config.ts +77 -0
- package/src/serve/vite-plugin.ts +1075 -0
- package/src/serve/vite-rules-source.ts +28 -0
- package/src/serve/vite-worker-runtime.ts +51 -0
- package/src/serve/worker/activity-bootstrap.ts +24 -0
- package/src/serve/worker/client/admin-firestore.ts +38 -0
- package/src/serve/worker/client/ai.ts +124 -0
- package/src/serve/worker/client/auth.ts +489 -0
- package/src/serve/worker/client/connection.ts +227 -0
- package/src/serve/worker/client/core.ts +384 -0
- package/src/serve/worker/client/disconnect.ts +45 -0
- package/src/serve/worker/client/firestore-reads.ts +181 -0
- package/src/serve/worker/client/firestore-refs.ts +226 -0
- package/src/serve/worker/client/firestore-writes.ts +232 -0
- package/src/serve/worker/client/handles.ts +91 -0
- package/src/serve/worker/client/messaging.ts +119 -0
- package/src/serve/worker/client/presence.ts +174 -0
- package/src/serve/worker/client/rtdb.ts +311 -0
- package/src/serve/worker/client/rules.ts +63 -0
- package/src/serve/worker/client/runtime-control.ts +22 -0
- package/src/serve/worker/client/service-worker-connection.ts +59 -0
- package/src/serve/worker/client/snapshots.ts +86 -0
- package/src/serve/worker/client/storage.ts +195 -0
- package/src/serve/worker/client/studio.ts +88 -0
- package/src/serve/worker/client.ts +57 -0
- package/src/serve/worker/durable-persistence.ts +137 -0
- package/src/serve/worker/entry.ts +223 -0
- package/src/serve/worker/host/admin-firestore.ts +88 -0
- package/src/serve/worker/host/connection.ts +179 -0
- package/src/serve/worker/host/core.ts +387 -0
- package/src/serve/worker/host/dispatch.ts +316 -0
- package/src/serve/worker/host/firestore-reads.ts +117 -0
- package/src/serve/worker/host/firestore-writes.ts +456 -0
- package/src/serve/worker/host/presence.ts +312 -0
- package/src/serve/worker/host/rtdb.ts +136 -0
- package/src/serve/worker/host/rules.ts +128 -0
- package/src/serve/worker/host/storage.ts +312 -0
- package/src/serve/worker/host/studio.ts +75 -0
- package/src/serve/worker/host/subscriptions.ts +238 -0
- package/src/serve/worker/host-ai.ts +165 -0
- package/src/serve/worker/host-auth.ts +469 -0
- package/src/serve/worker/host-context.ts +273 -0
- package/src/serve/worker/host-events.ts +94 -0
- package/src/serve/worker/host-messaging.ts +240 -0
- package/src/serve/worker/host.ts +47 -0
- package/src/serve/worker/index.ts +196 -0
- package/src/serve/worker/presence-timing.ts +15 -0
- package/src/serve/worker/protocol.ts +1124 -0
- package/src/serve/worker/retirement.ts +125 -0
- package/src/serve/worker/serve-init.ts +605 -0
- package/src/serve/worker/service-worker-channel.ts +33 -0
- package/src/serve/worker/service-worker-relay.ts +86 -0
- package/src/serve/writer-lock.ts +53 -0
- package/src/verify/cases.ts +233 -0
- package/src/verify/fixture.ts +258 -0
- package/src/verify/index.ts +519 -0
- package/src/verify/tools.ts +108 -0
- package/src/version/compat-target.ts +17 -0
- package/src/vite.ts +18 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +0 -31
- package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api.md +0 -29
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharedWorker host — AI op + stream-subscription handlers (pyric/ai).
|
|
3
|
+
*
|
|
4
|
+
* Implements cdd-deltas "Where the engines live under pyric dev (#98)":
|
|
5
|
+
*
|
|
6
|
+
* 1. The broker and engines run IN-PROCESS with the sandbox — here, in the
|
|
7
|
+
* worker host. The scripted engine does no I/O anywhere.
|
|
8
|
+
* 2. The openai engine's default base URL in this (browser) context is the
|
|
9
|
+
* same-origin `/__pyric/ai-proxy` route, so a localhost upstream (Ollama)
|
|
10
|
+
* needs no CORS setup. Relative URLs resolve against the worker script's
|
|
11
|
+
* origin, so the plain path works from inside the SharedWorker.
|
|
12
|
+
* 3. `ai.generateContent` / `ai.countTokens` are unary ops exactly like
|
|
13
|
+
* `rtdb.*` / `auth.*`; `ai.streamGenerateContent` is a FINITE
|
|
14
|
+
* subscription that delivers `{ chunk }` snaps in order, then a terminal
|
|
15
|
+
* `{ done: true }` snap and auto-unsubscribes (see AiStreamSubMessage).
|
|
16
|
+
* 4. Engine choice + model mapping are per-sandbox config: the broker is
|
|
17
|
+
* recovered from `getAI(ctx.sandbox, …)` (the mirror's OWN per-sandbox
|
|
18
|
+
* broker — its events land on the shared sandbox's unified stream), and
|
|
19
|
+
* is created ONCE — host config (`ctx.aiEngine`) wins over the first
|
|
20
|
+
* op's `engine` field; later configs are ignored, mirroring `getAI`'s
|
|
21
|
+
* first-call-wins idempotence.
|
|
22
|
+
*
|
|
23
|
+
* Decomposition mirrors host-auth.ts / host-events.ts: host.ts routes
|
|
24
|
+
* `ai.*` ops and `{ service: 'ai' }` subs here; this module never imports
|
|
25
|
+
* host.ts (no cycles — it leans only on the host-context foundation).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { getAI, TARGET_SYMBOL, type AIOptions } from 'pyric/ai';
|
|
29
|
+
|
|
30
|
+
import { serializeError, type OpMessage, type AiStreamSubMessage, type AiEngineConfigWire } from './protocol.js';
|
|
31
|
+
import { type HostCtx, type PortLike, type AiBrokerLike, post, ok, fail } from './host-context.js';
|
|
32
|
+
|
|
33
|
+
/** The serve proxy route the browser openai engine defaults to (#98.2). */
|
|
34
|
+
export const AI_PROXY_PATH = '/__pyric/ai-proxy';
|
|
35
|
+
|
|
36
|
+
/** Is this op an AI op (routed here instead of handleOp)? */
|
|
37
|
+
export function isAiOp(method: OpMessage['method']): boolean {
|
|
38
|
+
return method.startsWith('ai.');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The two unary AI op variants, extracted from the OpMessage union. */
|
|
42
|
+
type AiOpMessage = Extract<OpMessage, { method: 'ai.generateContent' | 'ai.countTokens' }>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Wire config → the mirror's `EngineConfig`. The only transformation is the
|
|
46
|
+
* browser default: an openai config with no `baseUrl` targets the serve
|
|
47
|
+
* proxy path. Script entries pass through as-is (they are the broker's own
|
|
48
|
+
* plain authoring shapes; predicate matchers never made it across the port —
|
|
49
|
+
* structured clone rejects functions loudly at send time).
|
|
50
|
+
*/
|
|
51
|
+
function resolveEngineConfig(wire: AiEngineConfigWire): NonNullable<AIOptions['engine']> {
|
|
52
|
+
if (wire.kind === 'openai') {
|
|
53
|
+
return {
|
|
54
|
+
kind: 'openai',
|
|
55
|
+
baseUrl: wire.baseUrl ?? AI_PROXY_PATH,
|
|
56
|
+
...(wire.model !== undefined ? { model: wire.model } : {}),
|
|
57
|
+
...(wire.modelMap !== undefined ? { modelMap: wire.modelMap } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
kind: 'scripted',
|
|
62
|
+
...(wire.script !== undefined
|
|
63
|
+
? { script: wire.script as unknown as Extract<NonNullable<AIOptions['engine']>, { kind: 'scripted' }>['script'] }
|
|
64
|
+
: {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The sandbox's AiBroker, created once per worker lifetime. `getAI` is
|
|
70
|
+
* idempotent per sandbox+backend, so this IS the mirror's broker — a page
|
|
71
|
+
* embedding host and this handler resolve the same instance, and every
|
|
72
|
+
* broker op lands `service: 'ai'` events on the shared sandbox's unified
|
|
73
|
+
* event stream (what Studio consumes).
|
|
74
|
+
*
|
|
75
|
+
* `AiBroker` is not exported from `pyric/ai`'s public surface; the instance
|
|
76
|
+
* is recovered from the branded handle via the exported `TARGET_SYMBOL`
|
|
77
|
+
* (the mirror's own dispatch seam).
|
|
78
|
+
*/
|
|
79
|
+
export function ensureAiBroker(ctx: HostCtx, opEngine?: AiEngineConfigWire): AiBrokerLike {
|
|
80
|
+
if (ctx.aiBroker) return ctx.aiBroker;
|
|
81
|
+
const wire = ctx.aiEngine ?? opEngine;
|
|
82
|
+
const ai = getAI(ctx.sandbox, wire ? { engine: resolveEngineConfig(wire) } : undefined);
|
|
83
|
+
const target = (ai as unknown as {
|
|
84
|
+
[TARGET_SYMBOL]?: { broker: AiBrokerLike };
|
|
85
|
+
})[TARGET_SYMBOL];
|
|
86
|
+
if (!target?.broker) {
|
|
87
|
+
// Unreachable for a real Sandbox ctx; guards a mis-wired embedding host.
|
|
88
|
+
throw new Error('pyric worker: getAI(sandbox) did not produce a sandbox-target AI handle');
|
|
89
|
+
}
|
|
90
|
+
ctx.aiBroker = target.broker;
|
|
91
|
+
return target.broker;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Handle the unary AI ops. Requests/replies are plain Gemini-wire JSON. */
|
|
95
|
+
export async function handleAiOp(ctx: HostCtx, port: PortLike, msg: OpMessage): Promise<void> {
|
|
96
|
+
const aiMsg = msg as AiOpMessage;
|
|
97
|
+
try {
|
|
98
|
+
const broker = ensureAiBroker(ctx, aiMsg.engine);
|
|
99
|
+
switch (aiMsg.method) {
|
|
100
|
+
case 'ai.generateContent': {
|
|
101
|
+
ok(port, aiMsg.id, await broker.generateContent(aiMsg.request, aiMsg.model));
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case 'ai.countTokens': {
|
|
105
|
+
ok(port, aiMsg.id, await broker.countTokens(aiMsg.request, aiMsg.model));
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
default: {
|
|
109
|
+
fail(port, msg.id, new Error(`Unknown ai method: ${String((msg as { method: unknown }).method)}`));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {
|
|
113
|
+
// AiBrokerError envelopes ride the SerializedError whole (aiEnvelope) —
|
|
114
|
+
// serializeError detects them structurally.
|
|
115
|
+
fail(port, aiMsg.id, e);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Handle an AI stream subscription: pump the broker's chunk stream to the
|
|
121
|
+
* port as `{ chunk }` snaps IN ORDER, then post the terminal `{ done: true }`
|
|
122
|
+
* snap and drop the sub (finite — auto-unsub on done). The pump is detached
|
|
123
|
+
* (sub handling is synchronous, like the other sub handlers); a client
|
|
124
|
+
* `unsub` mid-stream flips the registered cancel and delivery stops.
|
|
125
|
+
*
|
|
126
|
+
* Broker validation runs eagerly inside `streamGenerateContent` (before
|
|
127
|
+
* iteration) — either way a throw lands as the shared `{ __error }` snap
|
|
128
|
+
* convention, terminal for this sub.
|
|
129
|
+
*/
|
|
130
|
+
export function handleAiSub(ctx: HostCtx, port: PortLike, msg: AiStreamSubMessage): void {
|
|
131
|
+
let portSubs = ctx.subs.get(port);
|
|
132
|
+
if (!portSubs) {
|
|
133
|
+
portSubs = new Map();
|
|
134
|
+
ctx.subs.set(port, portSubs);
|
|
135
|
+
}
|
|
136
|
+
if (portSubs.has(msg.subId)) return; // idempotent
|
|
137
|
+
|
|
138
|
+
let cancelled = false;
|
|
139
|
+
const cancel = (): void => {
|
|
140
|
+
cancelled = true;
|
|
141
|
+
};
|
|
142
|
+
portSubs.set(msg.subId, cancel);
|
|
143
|
+
|
|
144
|
+
void (async () => {
|
|
145
|
+
try {
|
|
146
|
+
const broker = ensureAiBroker(ctx, msg.engine);
|
|
147
|
+
for await (const chunk of broker.streamGenerateContent(msg.request, msg.model)) {
|
|
148
|
+
if (cancelled) return;
|
|
149
|
+
post(port, { t: 'snap', subId: msg.subId, value: { chunk } });
|
|
150
|
+
}
|
|
151
|
+
if (!cancelled) {
|
|
152
|
+
post(port, { t: 'snap', subId: msg.subId, value: { done: true } });
|
|
153
|
+
}
|
|
154
|
+
} catch (e) {
|
|
155
|
+
if (!cancelled) {
|
|
156
|
+
post(port, { t: 'snap', subId: msg.subId, value: { __error: serializeError(e) } });
|
|
157
|
+
}
|
|
158
|
+
} finally {
|
|
159
|
+
// Auto-unsub: drop OUR registration only (an early client unsub may
|
|
160
|
+
// have already removed it — handleUnsub ran the cancel and deleted it).
|
|
161
|
+
const subs = ctx.subs.get(port);
|
|
162
|
+
if (subs?.get(msg.subId) === cancel) subs.delete(msg.subId);
|
|
163
|
+
}
|
|
164
|
+
})();
|
|
165
|
+
}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharedWorker host — auth subsystem: PER-PORT SESSIONS (#754).
|
|
3
|
+
*
|
|
4
|
+
* The worker hosts ONE sandbox (one user pool, one data store, one ruleset)
|
|
5
|
+
* but each connected port (tab / client) owns its OWN session: sign-ins mint
|
|
6
|
+
* an authentic identity via `sandbox.mintSession` (credentials validated /
|
|
7
|
+
* identity really created — NOT the impersonation lens) and bind it to the
|
|
8
|
+
* port. Auth-state fan-out is port-scoped: tab A signing in does not fire
|
|
9
|
+
* tab B's `onAuthStateChanged`. Data ops without an explicit Studio lens
|
|
10
|
+
* run under the port's session (see `sessionDb` in host.ts).
|
|
11
|
+
*
|
|
12
|
+
* Session persistence is CLIENT-side: the page records its uid in web
|
|
13
|
+
* storage (SessionStore, honoring setPersistence) and re-establishes it on
|
|
14
|
+
* reload via the `auth.restorePortSession` op. The worker holds no session
|
|
15
|
+
* record — the user POOL still rides the sandbox snapshot (#629).
|
|
16
|
+
*
|
|
17
|
+
* Imports only from `./host-context.js` + external packages (no circular
|
|
18
|
+
* imports).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { type HostCtx, type PortLike, post, ok, fail, bestEffortFlush } from './host-context.js';
|
|
22
|
+
import {
|
|
23
|
+
getAuth,
|
|
24
|
+
sandbox as authSandboxOps,
|
|
25
|
+
type Auth,
|
|
26
|
+
type MintedSession,
|
|
27
|
+
type User,
|
|
28
|
+
} from 'pyric/auth';
|
|
29
|
+
import {
|
|
30
|
+
serializeUser,
|
|
31
|
+
type OpMessage,
|
|
32
|
+
type AuthSubMessage,
|
|
33
|
+
} from './protocol.js';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Synthetic password seeded for bridged provider identities (popup/redirect).
|
|
37
|
+
* Provider users never authenticate with a password — this just satisfies the
|
|
38
|
+
* SeedUser shape. Matches `ServeAuthHelper`'s in-page constant in spirit.
|
|
39
|
+
*/
|
|
40
|
+
const PROVIDER_SYNTHETIC_PASSWORD = '__pyric_popup_no_password__';
|
|
41
|
+
|
|
42
|
+
// ─── Auth: per-port sessions + port-scoped fan-out ────────────────────────
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Per-port auth subscription registry. Map<port, Map<subId, target>>.
|
|
46
|
+
*
|
|
47
|
+
* SEPARATE FROM `ctx.subs` (Firestore listeners): auth subs are routing
|
|
48
|
+
* entries only. With per-port sessions there is no worker-wide auth
|
|
49
|
+
* listener at all — a port's session changes fan out to THAT port's subs
|
|
50
|
+
* (see {@link setPortSession}).
|
|
51
|
+
*/
|
|
52
|
+
const _authSubs = new WeakMap<HostCtx, Map<PortLike, Map<string, 'authState' | 'idToken'>>>();
|
|
53
|
+
|
|
54
|
+
export function authSubsFor(ctx: HostCtx): Map<PortLike, Map<string, 'authState' | 'idToken'>> {
|
|
55
|
+
let m = _authSubs.get(ctx);
|
|
56
|
+
if (!m) {
|
|
57
|
+
m = new Map();
|
|
58
|
+
_authSubs.set(ctx, m);
|
|
59
|
+
}
|
|
60
|
+
return m;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Lazily create the ONE shared auth handle — the user pool + admin surface.
|
|
65
|
+
* Calling `getAuth(sandbox)` also (re)registers the auth service with the
|
|
66
|
+
* persistence registry, so the user DB rides the snapshot (#629). Its
|
|
67
|
+
* `currentUser` is never set in served mode; sessions live per-port.
|
|
68
|
+
*
|
|
69
|
+
* Idempotent: returns the cached `ctx.auth` on repeat calls.
|
|
70
|
+
*/
|
|
71
|
+
export function ensureAuth(ctx: HostCtx): Auth {
|
|
72
|
+
if (ctx.auth) return ctx.auth;
|
|
73
|
+
ctx.auth = getAuth(ctx.sandbox);
|
|
74
|
+
if (ctx.sessionMode === undefined) ctx.sessionMode = 'LOCAL';
|
|
75
|
+
return ctx.auth;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function portSessionsFor(ctx: HostCtx): Map<PortLike, MintedSession | null> {
|
|
79
|
+
return (ctx.portSessions ??= new Map());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The port's signed-in session, or null (absent from the map = signed out). */
|
|
83
|
+
export function portSession(ctx: HostCtx, port: PortLike): MintedSession | null {
|
|
84
|
+
return portSessionsFor(ctx).get(port) ?? null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Bind (or clear) a port's session and fan the change out to THAT PORT's
|
|
89
|
+
* authState/idToken subs — the per-port analog of the old cross-tab
|
|
90
|
+
* broadcast. Every sign-in mints a fresh token (mintSession did), so the
|
|
91
|
+
* idToken stream fires alongside authState, matching the real observers.
|
|
92
|
+
*/
|
|
93
|
+
function setPortSession(ctx: HostCtx, port: PortLike, session: MintedSession | null): void {
|
|
94
|
+
portSessionsFor(ctx).set(port, session);
|
|
95
|
+
|
|
96
|
+
// Prod parity on auth transitions: re-establish this port's session-bound
|
|
97
|
+
// Firestore listeners under the NEW identity, so a sign-out re-evaluates
|
|
98
|
+
// live streams (auth-gated data is revoked, not leaked) and a sign-in
|
|
99
|
+
// grants them. Studio-lens subs (explicit actAs) are untouched.
|
|
100
|
+
ctx.resubscribePortSubs?.(port);
|
|
101
|
+
|
|
102
|
+
const serialized = serializeUser(session?.user ?? null);
|
|
103
|
+
const bySubId = authSubsFor(ctx).get(port);
|
|
104
|
+
if (!bySubId) return;
|
|
105
|
+
for (const [subId] of bySubId) {
|
|
106
|
+
post(port, { t: 'snap', subId, value: serialized });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Apply a forced ID-token refresh to this port's data authorization state. */
|
|
111
|
+
function refreshPortAuthorization(
|
|
112
|
+
ctx: HostCtx,
|
|
113
|
+
port: PortLike,
|
|
114
|
+
session: MintedSession,
|
|
115
|
+
claims: Record<string, unknown>,
|
|
116
|
+
): void {
|
|
117
|
+
session.state.token = { ...claims };
|
|
118
|
+
|
|
119
|
+
// Frozen sandbox.withAuth handles capture the old token. Clear all
|
|
120
|
+
// session-handle caches so this port and any same-uid sibling rebuild from
|
|
121
|
+
// their own current session state on the next operation.
|
|
122
|
+
ctx.sessionDbs?.clear();
|
|
123
|
+
ctx.sessionRtdbs?.clear();
|
|
124
|
+
ctx.sessionStorages?.clear();
|
|
125
|
+
|
|
126
|
+
// Existing Firestore/RTDB streams must reauthorize under the refreshed
|
|
127
|
+
// claims. A token refresh is not an auth-state transition, so notify only
|
|
128
|
+
// onIdTokenChanged observers with the still-current user.
|
|
129
|
+
ctx.resubscribePortSubs?.(port);
|
|
130
|
+
const serialized = serializeUser(session.user);
|
|
131
|
+
for (const [subId, target] of authSubsFor(ctx).get(port) ?? []) {
|
|
132
|
+
if (target === 'idToken') post(port, { t: 'snap', subId, value: serialized });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Tear down a disconnected port's session (called from cleanupPort). */
|
|
137
|
+
export function cleanupPortSession(ctx: HostCtx, port: PortLike): void {
|
|
138
|
+
ctx.portSessions?.delete(port);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ─── Auth op handlers ─────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
/** Serialized UserCredential reply shape for a minted session. */
|
|
144
|
+
function credReply(session: MintedSession, providerId: string | null) {
|
|
145
|
+
return {
|
|
146
|
+
user: serializeUser(session.user),
|
|
147
|
+
providerId,
|
|
148
|
+
operationType: 'signIn' as const,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function handleAuthOp(ctx: HostCtx, port: PortLike, msg: OpMessage): Promise<void> {
|
|
153
|
+
const auth = ensureAuth(ctx);
|
|
154
|
+
|
|
155
|
+
switch (msg.method) {
|
|
156
|
+
case 'auth.createUser': {
|
|
157
|
+
try {
|
|
158
|
+
const session = authSandboxOps.mintSession(auth, {
|
|
159
|
+
kind: 'createPassword', email: msg.email, password: msg.password,
|
|
160
|
+
});
|
|
161
|
+
setPortSession(ctx, port, session);
|
|
162
|
+
await bestEffortFlush(ctx); // new user record must be durable at ack
|
|
163
|
+
ok(port, msg.id, credReply(session, null));
|
|
164
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
case 'auth.signInEmail': {
|
|
169
|
+
try {
|
|
170
|
+
const session = authSandboxOps.mintSession(auth, {
|
|
171
|
+
kind: 'password', email: msg.email, password: msg.password,
|
|
172
|
+
});
|
|
173
|
+
setPortSession(ctx, port, session);
|
|
174
|
+
ok(port, msg.id, credReply(session, null));
|
|
175
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
case 'auth.signInAnonymously': {
|
|
180
|
+
try {
|
|
181
|
+
// Match `firebase/auth` semantics per session: if THIS PORT is
|
|
182
|
+
// already anonymous, reuse that identity (StrictMode double-mounts
|
|
183
|
+
// must not leak a fresh uid per mount). Other ports' anonymous
|
|
184
|
+
// sessions are other users — that's the multi-user point.
|
|
185
|
+
const existing = portSession(ctx, port);
|
|
186
|
+
if (existing && existing.user.isAnonymous) {
|
|
187
|
+
ok(port, msg.id, credReply(existing, null));
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
const session = authSandboxOps.mintSession(auth, { kind: 'anonymous' });
|
|
191
|
+
setPortSession(ctx, port, session);
|
|
192
|
+
await bestEffortFlush(ctx); // anonymous sign-in creates a user record
|
|
193
|
+
ok(port, msg.id, credReply(session, null));
|
|
194
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
case 'auth.signOut': {
|
|
199
|
+
try {
|
|
200
|
+
setPortSession(ctx, port, null);
|
|
201
|
+
ok(port, msg.id, null);
|
|
202
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
case 'auth.restorePortSession': {
|
|
207
|
+
// Per-tab reload restore (#754): soft — an unknown/disabled uid means
|
|
208
|
+
// signed out (null), never an error. The page clears its stale record.
|
|
209
|
+
try {
|
|
210
|
+
let session: MintedSession | null = null;
|
|
211
|
+
try {
|
|
212
|
+
session = authSandboxOps.mintSession(auth, { kind: 'uid', uid: msg.uid });
|
|
213
|
+
} catch {
|
|
214
|
+
session = null;
|
|
215
|
+
}
|
|
216
|
+
if (session) setPortSession(ctx, port, session);
|
|
217
|
+
ok(port, msg.id, session ? serializeUser(session.user) : null);
|
|
218
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
case 'auth.getIdToken': {
|
|
223
|
+
try {
|
|
224
|
+
const session = requirePortSession(ctx, port, 'getIdToken');
|
|
225
|
+
const user = session.user;
|
|
226
|
+
const token = await user.getIdToken(msg.forceRefresh);
|
|
227
|
+
if (msg.forceRefresh) {
|
|
228
|
+
const result = await user.getIdTokenResult(false);
|
|
229
|
+
refreshPortAuthorization(ctx, port, session, result.claims);
|
|
230
|
+
}
|
|
231
|
+
ok(port, msg.id, token);
|
|
232
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
case 'auth.getIdTokenResult': {
|
|
237
|
+
try {
|
|
238
|
+
const session = requirePortSession(ctx, port, 'getIdTokenResult');
|
|
239
|
+
const user = session.user;
|
|
240
|
+
const r = await user.getIdTokenResult(msg.forceRefresh);
|
|
241
|
+
if (msg.forceRefresh) refreshPortAuthorization(ctx, port, session, r.claims);
|
|
242
|
+
ok(port, msg.id, {
|
|
243
|
+
token: r.token,
|
|
244
|
+
claims: r.claims,
|
|
245
|
+
expirationTime: r.expirationTime,
|
|
246
|
+
issuedAtTime: r.issuedAtTime,
|
|
247
|
+
authTime: r.authTime,
|
|
248
|
+
signInProvider: r.signInProvider ?? null,
|
|
249
|
+
});
|
|
250
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
case 'auth.setPersistence': {
|
|
255
|
+
// Accepted for surface parity; the CLIENT's SessionStore owns where
|
|
256
|
+
// (or whether) the session uid is recorded. Nothing to do worker-side.
|
|
257
|
+
ctx.sessionMode = msg.mode;
|
|
258
|
+
ok(port, msg.id, null);
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
case 'auth.getCurrentUser': {
|
|
263
|
+
try {
|
|
264
|
+
ok(port, msg.id, serializeUser(portSession(ctx, port)?.user ?? null));
|
|
265
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
case 'auth.updateProfile': {
|
|
270
|
+
// Update THIS PORT's signed-in user's profile (displayName / photoURL).
|
|
271
|
+
// Updates the stored record (by uid) via the sandbox op, then mutates the
|
|
272
|
+
// port session's `User` in place so a subsequent `auth.getCurrentUser`
|
|
273
|
+
// (and the client mirror hydrated from the reply) is consistent. Does NOT
|
|
274
|
+
// fire onAuthStateChanged/onIdTokenChanged — matching firebase/auth.
|
|
275
|
+
try {
|
|
276
|
+
const session = portSession(ctx, port);
|
|
277
|
+
if (!session) throw makeNoUserError('updateProfile');
|
|
278
|
+
const profile = { displayName: msg.displayName, photoURL: msg.photoURL };
|
|
279
|
+
authSandboxOps.updateProfile(auth, session.user.uid, profile);
|
|
280
|
+
applyProfileToUser(session.user, profile);
|
|
281
|
+
await bestEffortFlush(ctx);
|
|
282
|
+
ok(port, msg.id, serializeUser(session.user));
|
|
283
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
case 'auth.acceptIdentity': {
|
|
288
|
+
// Provider sign-in bridge: the page resolved a popup/redirect identity
|
|
289
|
+
// in-page (ServeAuthHelper) and hands it here. GATE FIRST: the page
|
|
290
|
+
// sandbox delegates provider enforcement to this worker (its picker
|
|
291
|
+
// opens unconditionally), so THIS is where Studio's provider toggles
|
|
292
|
+
// bite — a disabled provider throws `auth/operation-not-allowed`
|
|
293
|
+
// before any user-DB write. Then seed it into the user DB (so rules
|
|
294
|
+
// `request.auth.token.*` claims resolve AND it shows in the picker
|
|
295
|
+
// next time), and mint THIS PORT's session for it — provider users
|
|
296
|
+
// have no password. Mirrors ServeAuthHelper.add's seeding.
|
|
297
|
+
try {
|
|
298
|
+
const { uid, email, displayName, customClaims, providerId } = msg.identity;
|
|
299
|
+
authSandboxOps.assertAuthProviderEnabled(auth, providerId);
|
|
300
|
+
authSandboxOps.seedUsers(auth, [{
|
|
301
|
+
uid,
|
|
302
|
+
email: email ?? '',
|
|
303
|
+
password: PROVIDER_SYNTHETIC_PASSWORD,
|
|
304
|
+
displayName: displayName ?? undefined,
|
|
305
|
+
customClaims: customClaims ?? {},
|
|
306
|
+
providerId,
|
|
307
|
+
}]);
|
|
308
|
+
const session = authSandboxOps.mintSession(auth, { kind: 'uid', uid });
|
|
309
|
+
setPortSession(ctx, port, session);
|
|
310
|
+
await bestEffortFlush(ctx); // seeded provider identity must be durable
|
|
311
|
+
ok(port, msg.id, credReply(session, providerId));
|
|
312
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
case 'auth.listUsers': {
|
|
317
|
+
// Admin user-DB enumeration (Pyric Studio data browse).
|
|
318
|
+
try {
|
|
319
|
+
ok(port, msg.id, authSandboxOps.listUsers(auth));
|
|
320
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
case 'auth.adminCreateUser': {
|
|
325
|
+
try {
|
|
326
|
+
const created = authSandboxOps.createUser(
|
|
327
|
+
auth,
|
|
328
|
+
msg.request as Parameters<typeof authSandboxOps.createUser>[1],
|
|
329
|
+
);
|
|
330
|
+
await bestEffortFlush(ctx);
|
|
331
|
+
ok(port, msg.id, created);
|
|
332
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
case 'auth.adminUpdateUser': {
|
|
337
|
+
try {
|
|
338
|
+
const updated = authSandboxOps.updateUser(
|
|
339
|
+
auth,
|
|
340
|
+
msg.uid,
|
|
341
|
+
msg.request as Parameters<typeof authSandboxOps.updateUser>[2],
|
|
342
|
+
);
|
|
343
|
+
await bestEffortFlush(ctx);
|
|
344
|
+
ok(port, msg.id, updated);
|
|
345
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
case 'auth.adminDeleteUser': {
|
|
350
|
+
try {
|
|
351
|
+
authSandboxOps.deleteUser(auth, msg.uid);
|
|
352
|
+
await bestEffortFlush(ctx);
|
|
353
|
+
ok(port, msg.id, null);
|
|
354
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
case 'auth.adminClearUsers': {
|
|
359
|
+
try {
|
|
360
|
+
authSandboxOps.clearUsers(auth);
|
|
361
|
+
await bestEffortFlush(ctx);
|
|
362
|
+
ok(port, msg.id, null);
|
|
363
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
case 'auth.getProviderConfig': {
|
|
368
|
+
// Sign-in provider config (Pyric Studio S-AUTH "Sign-in providers"
|
|
369
|
+
// section). `setProviderConfig` emits a `provider_config_update`
|
|
370
|
+
// sandbox event, so a caller subscribed to the event stream (the
|
|
371
|
+
// worker's event feed, which `subscribeUsers` already rides) sees
|
|
372
|
+
// toggles live without a dedicated subscription message here.
|
|
373
|
+
try {
|
|
374
|
+
ok(port, msg.id, authSandboxOps.getAuthProviderConfig(auth));
|
|
375
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
case 'auth.setProviderConfig': {
|
|
380
|
+
try {
|
|
381
|
+
authSandboxOps.setAuthProviderConfig(auth, msg.providerId, msg.enabled);
|
|
382
|
+
ok(port, msg.id, null);
|
|
383
|
+
} catch (e) { fail(port, msg.id, e); }
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
default: {
|
|
388
|
+
fail(port, msg.id, new Error(`Unknown auth method: ${String((msg as { method: unknown }).method)}`));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** The port session's User, or throw `auth/no-current-user`. */
|
|
394
|
+
function requireSessionUser(ctx: HostCtx, port: PortLike, api: string): User {
|
|
395
|
+
return requirePortSession(ctx, port, api).user;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function requirePortSession(ctx: HostCtx, port: PortLike, api: string): MintedSession {
|
|
399
|
+
const session = portSession(ctx, port);
|
|
400
|
+
if (!session) throw makeNoUserError(api);
|
|
401
|
+
return session;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Mutate a port session's `User` `displayName` / `photoURL` (and the first
|
|
406
|
+
* `providerData` entry's) in place so a subsequent `auth.getCurrentUser`
|
|
407
|
+
* reflects an `auth.updateProfile`. Fields are `readonly` at the type level
|
|
408
|
+
* but plain data at runtime; only an explicitly-provided field is applied
|
|
409
|
+
* (`null` clears, `undefined` leaves untouched).
|
|
410
|
+
*/
|
|
411
|
+
function applyProfileToUser(
|
|
412
|
+
user: User,
|
|
413
|
+
profile: { displayName?: string | null; photoURL?: string | null },
|
|
414
|
+
): void {
|
|
415
|
+
const mutable = user as { -readonly [K in keyof User]: User[K] };
|
|
416
|
+
if (profile.displayName !== undefined) mutable.displayName = profile.displayName;
|
|
417
|
+
if (profile.photoURL !== undefined) mutable.photoURL = profile.photoURL;
|
|
418
|
+
const provider0 = user.providerData?.[0] as
|
|
419
|
+
| { -readonly [K in keyof NonNullable<User['providerData']>[number]]: NonNullable<User['providerData']>[number][K] }
|
|
420
|
+
| undefined;
|
|
421
|
+
if (provider0) {
|
|
422
|
+
if (profile.displayName !== undefined) provider0.displayName = profile.displayName;
|
|
423
|
+
if (profile.photoURL !== undefined) provider0.photoURL = profile.photoURL;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** Build an `auth/no-current-user`-style error for token ops with no user. */
|
|
428
|
+
function makeNoUserError(api: string): Error & { code: string } {
|
|
429
|
+
const err = new Error(`${api}: no current user is signed in.`) as Error & { code: string };
|
|
430
|
+
err.code = 'auth/no-current-user';
|
|
431
|
+
return err;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export function isAuthOp(method: OpMessage['method']): boolean {
|
|
435
|
+
return method.startsWith('auth.');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ─── Auth subscription handlers ───────────────────────────────────────────
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Register an auth subscription for a port. A routing entry only — the
|
|
442
|
+
* port's session changes (its own sign-ins/outs) fan out to it via
|
|
443
|
+
* {@link setPortSession}. Fires the PORT's current session immediately
|
|
444
|
+
* (initial-fire parity with onAuthStateChanged/onIdTokenChanged).
|
|
445
|
+
*/
|
|
446
|
+
export function handleAuthSub(ctx: HostCtx, port: PortLike, msg: AuthSubMessage): void {
|
|
447
|
+
ensureAuth(ctx);
|
|
448
|
+
const subs = authSubsFor(ctx);
|
|
449
|
+
let bySubId = subs.get(port);
|
|
450
|
+
if (!bySubId) {
|
|
451
|
+
bySubId = new Map();
|
|
452
|
+
subs.set(port, bySubId);
|
|
453
|
+
}
|
|
454
|
+
if (bySubId.has(msg.subId)) return; // idempotent
|
|
455
|
+
bySubId.set(msg.subId, msg.target);
|
|
456
|
+
|
|
457
|
+
// Initial fire — mirror the real observers, which invoke the callback once
|
|
458
|
+
// with the current state on registration. Per-port: THIS port's session.
|
|
459
|
+
post(port, { t: 'snap', subId: msg.subId, value: serializeUser(portSession(ctx, port)?.user ?? null) });
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function handleAuthUnsub(ctx: HostCtx, port: PortLike, subId: string): boolean {
|
|
463
|
+
const subs = authSubsFor(ctx);
|
|
464
|
+
const bySubId = subs.get(port);
|
|
465
|
+
if (!bySubId || !bySubId.has(subId)) return false;
|
|
466
|
+
bySubId.delete(subId);
|
|
467
|
+
if (bySubId.size === 0) subs.delete(port);
|
|
468
|
+
return true;
|
|
469
|
+
}
|