@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.
- 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/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 +1 -1
- 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 +1 -1
- package/dist/serve/docs-ui/docs/get-started/start-building/index.html +1 -1
- package/dist/serve/docs-ui/docs/index.json +200 -25
- package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
- 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 +131 -66
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +130 -65
- 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 +1 -1
- 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/llms.txt +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/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.js +2 -2
- package/dist/serve/worker/protocol.js.map +1 -1
- package/package.json +5 -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 +493 -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 +46 -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 +540 -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 +75 -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/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-plugin.ts +1102 -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 +202 -0
- package/src/serve/worker/client/core.ts +350 -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/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 +179 -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 +1112 -0
- package/src/serve/worker/serve-init.ts +606 -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 +17 -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,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
|
+
}
|