@palbase/web 1.0.0 → 1.1.0
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/analytics-facade-CAKBIH_U.d.cts +1302 -0
- package/dist/analytics-facade-DLH-KivI.d.ts +1302 -0
- package/dist/chunk-XVLR3HGD.js +6843 -0
- package/dist/chunk-XVLR3HGD.js.map +1 -0
- package/dist/index.cjs +3617 -187
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +208 -7
- package/dist/index.d.ts +208 -7
- package/dist/index.js +7 -1
- package/dist/internal.cjs +3613 -167
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +5 -6
- package/dist/internal.d.ts +5 -6
- package/dist/internal.js +1 -1
- package/dist/next/client.cjs +3611 -177
- package/dist/next/client.cjs.map +1 -1
- package/dist/next/client.js +1 -1
- package/dist/next/index.cjs +3616 -170
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +4 -11
- package/dist/next/index.d.ts +4 -11
- package/dist/next/index.js +14 -5
- package/dist/next/index.js.map +1 -1
- package/dist/{pb-Cudze7Kb.d.cts → pb-DioxNuEV.d.cts} +3 -1
- package/dist/{pb-BmgkAe97.d.ts → pb-HegMSSk-.d.ts} +3 -1
- package/dist/pkg/palbe_mls_bg.wasm +0 -0
- package/dist/react/index.cjs +97 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +30 -6
- package/dist/react/index.d.ts +30 -6
- package/dist/react/index.js +86 -1
- package/dist/react/index.js.map +1 -1
- package/package.json +8 -7
- package/dist/analytics-facade-DkOwkEpi.d.ts +0 -454
- package/dist/analytics-facade-t6UrFdn7.d.cts +0 -454
- package/dist/chunk-JVT65V4E.js +0 -3384
- package/dist/chunk-JVT65V4E.js.map +0 -1
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/index.tsx"],"sourcesContent":["'use client';\n\n// `palbe/react` — thin React hooks over the observable `pb.*` facades. Each\n// value hook is a `useSyncExternalStore` (the React 18+ concurrent-safe\n// primitive) binding: `subscribe` is the facade's Unsubscribe-returning\n// listener, `getSnapshot` is the matching getter. NO new SDK behaviour lives\n// here — the hooks re-render exactly when (and only when) their slice of state\n// changes, mirroring the iOS `@Observable` granularity.\n//\n// `react` is an OPTIONAL peer dependency: importing `palbe` (the core entry)\n// never pulls React; only `palbe/react` does.\n//\n// Client-only: the module starts with `'use client'` (Next.js App Router). SSR\n// initial values come from the `getServerSnapshot` arms (null user, empty flag\n// set) so a server render never touches the runtime singleton.\n\nimport { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';\nimport type { AuthUser, Unsubscribe } from '../auth-facade.js';\nimport type { FlagsView, FlagValue } from '../flags-facade.js';\nimport { onConfigured } from '../internal.js';\nimport { pb } from '../pb.js';\nimport type { RealtimeConnectionState, RealtimePayload } from '../realtime/facade.js';\n\n/** The session slice `useSession` exposes. */\nexport interface SessionState {\n signedIn: boolean;\n user: AuthUser | null;\n}\n\n/** Frozen empty flag view — the stable server/unconfigured fallback for\n * `useFlags` (a fresh `{}` each call would loop `useSyncExternalStore`). */\nconst EMPTY_FLAGS: FlagsView = Object.freeze({});\n\n/** The signed-out session snapshot — a single frozen instance so the\n * unconfigured/server snapshot is referentially stable across calls. */\nconst SIGNED_OUT: SessionState = Object.freeze({ signedIn: false, user: null });\n\n/**\n * Wraps a facade-listener factory so hooks that mount BEFORE `__configure`\n * runs self-heal when configuration arrives (or changes on re-configure /\n * watch-mode regen).\n *\n * Behaviour:\n * - If already configured at subscribe time, attaches the facade listener\n * immediately AND registers an `onConfigured` watcher for future re-configures.\n * - If NOT yet configured at subscribe time, only registers `onConfigured`;\n * the facade listener is attached when configuration arrives.\n * - On every configure event: detaches the old facade listener (if any),\n * attaches a fresh one against the new runtime, then calls `onStoreChange`\n * so React re-reads the snapshot from the new runtime.\n * - The returned unsubscribe tears down both the onConfigured registration\n * and the current facade listener.\n *\n * @param getFacadeListener - factory called when a runtime is available;\n * must return an Unsubscribe; called with `onStoreChange` as its argument.\n */\nfunction subscribeWithConfig(\n getFacadeListener: (onStoreChange: () => void) => Unsubscribe,\n): (onStoreChange: () => void) => Unsubscribe {\n return (onStoreChange: () => void): Unsubscribe => {\n let currentFacadeUnsub: Unsubscribe | null = null;\n\n function attachFacade(): void {\n currentFacadeUnsub?.();\n try {\n currentFacadeUnsub = getFacadeListener(onStoreChange);\n } catch {\n // notConfigured — will be retried when onConfigured fires\n currentFacadeUnsub = null;\n }\n }\n\n // Register for future (re-)configure events.\n const offConfigured = onConfigured(() => {\n attachFacade();\n // Notify React to re-read the snapshot from the new runtime.\n onStoreChange();\n });\n\n // If already configured right now, attach immediately too.\n attachFacade();\n\n return () => {\n offConfigured();\n currentFacadeUnsub?.();\n currentFacadeUnsub = null;\n };\n };\n}\n\n/**\n * Current authenticated user, or `null` when signed out. Re-renders on BOTH\n * auth-state transitions (sign-in adopts a user, sign-out clears it) and\n * user-profile changes (e.g. an email-verified flip via `refreshUser`).\n *\n * `getSnapshot` is guarded: before the generated `palbe.gen.ts` runs\n * `__configure`, `pb.auth` throws `notConfigured` — a tree rendered that early\n * gets `null`, never a render crash. Once `__configure` runs the hook\n * self-heals: it re-subscribes to the new runtime's auth facade and notifies\n * React to re-read the snapshot.\n */\nexport function useUser(): AuthUser | null {\n return useSyncExternalStore(subscribeUserWithConfig, getUserSnapshot, getNullUser);\n}\n\nconst subscribeUserWithConfig = subscribeWithConfig((onStoreChange) => {\n // Two sources, one composite unsubscribe:\n // onAuthStateChange → sign-in (new user) and sign-out (null)\n // onUserChange → in-place profile edits (email-verified, …)\n // Both fire immediately on subscribe (iOS parity), which simply re-reads the\n // already-current snapshot — harmless.\n const offState = pb.auth.onAuthStateChange(onStoreChange);\n const offUser = pb.auth.onUserChange(onStoreChange);\n return () => {\n offState();\n offUser();\n };\n});\n\nfunction getUserSnapshot(): AuthUser | null {\n try {\n return pb.auth.currentUser;\n } catch {\n return null;\n }\n}\n\nfunction getNullUser(): AuthUser | null {\n return null;\n}\n\n/**\n * Session slice: `{ signedIn, user }`. Re-renders on auth-state transitions.\n *\n * `getSnapshot` MUST be referentially stable when nothing changed — returning a\n * fresh object every call would put `useSyncExternalStore` into an infinite\n * render loop. A per-component ref caches the last value and returns the SAME\n * object until `signedIn` or `user` actually changes.\n *\n * Self-heals when `__configure` runs after mount (same as `useUser`).\n */\nexport function useSession(): SessionState {\n const lastRef = useRef<SessionState>(SIGNED_OUT);\n const getSnapshot = useCallback((): SessionState => {\n const next = readSession();\n const prev = lastRef.current;\n if (prev.signedIn === next.signedIn && prev.user === next.user) {\n return prev; // unchanged — keep the stable reference\n }\n lastRef.current = next;\n return next;\n }, []);\n return useSyncExternalStore(subscribeSessionWithConfig, getSnapshot, getSignedOut);\n}\n\nconst subscribeSessionWithConfig = subscribeWithConfig((onStoreChange) =>\n pb.auth.onAuthStateChange(onStoreChange),\n);\n\nfunction readSession(): SessionState {\n try {\n const user = pb.auth.currentUser;\n const signedIn = pb.auth.isSignedIn;\n if (!signedIn && user === null) return SIGNED_OUT;\n return { signedIn, user };\n } catch {\n return SIGNED_OUT;\n }\n}\n\nfunction getSignedOut(): SessionState {\n return SIGNED_OUT;\n}\n\n/**\n * Subscribe to ONE flag. Re-renders only when THIS key's value changes\n * (`pb.flags.subscribeKey` is the per-key source — structural compare for\n * objects), returning `fallback` until the key has a value.\n *\n * The value is read at the dynamic flag boundary where the concrete type `T`\n * is caller-asserted (the cached value is whatever the server sent); the\n * contained `as T` is the documented escape hatch — flags are untyped on the\n * wire, and the caller owns the `fallback`'s type.\n *\n * NOTE: passing an inline-object `fallback` for an absent key defeats snapshot\n * caching (a new object reference each render re-enters the last-value ref).\n * Memoize the fallback (e.g. a module-level const or `useMemo`) if it matters.\n *\n * Self-heals when `__configure` runs after mount (same as `useUser`).\n */\nexport function useFlag<T extends FlagValue>(key: string, fallback: T): T {\n // Subscribe identity must change when `key` changes so useSyncExternalStore\n // re-subscribes to the new key. subscribeWithConfig wraps the per-key\n // factory so the hook also self-heals on (re-)configure.\n // biome-ignore lint/correctness/useExhaustiveDependencies: `key` is a runtime param, not an outer-scope value — dependency IS required\n const subscribe = useCallback(\n subscribeWithConfig((onStoreChange) => pb.flags.subscribeKey(key, onStoreChange)),\n [key],\n );\n\n // Cache the last value so getSnapshot is referentially stable (objects come\n // back as the frozen pooled reference — stable identity until a real change).\n const lastRef = useRef<T>(fallback);\n const getSnapshot = useCallback((): T => {\n let next: T;\n try {\n const value = pb.flags.get(key);\n next = value === undefined ? fallback : (value as T);\n } catch {\n next = fallback;\n }\n if (!Object.is(next, lastRef.current)) {\n lastRef.current = next;\n }\n return lastRef.current;\n }, [key, fallback]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Subscribe to the whole flag set. Re-renders on any change. `pb.flags.all()`\n * returns a frozen, identity-stable view (a NEW frozen object only when the set\n * actually changes), so it is `useSyncExternalStore`-safe as-is.\n *\n * Self-heals when `__configure` runs after mount (same as `useUser`).\n */\nexport function useFlags(): FlagsView {\n return useSyncExternalStore(subscribeFlagsWithConfig, getFlagsSnapshot, getEmptyFlags);\n}\n\nconst subscribeFlagsWithConfig = subscribeWithConfig((onStoreChange) =>\n pb.flags.onChange(onStoreChange),\n);\n\nfunction getFlagsSnapshot(): FlagsView {\n try {\n return pb.flags.all();\n } catch {\n return EMPTY_FLAGS;\n }\n}\n\nfunction getEmptyFlags(): FlagsView {\n return EMPTY_FLAGS;\n}\n\n/** The reported channel status: the shared-socket connection state, plus\n * `'unavailable'` when `pb.realtime.channel()` is not usable in this\n * environment (no WebSocket / SSR). */\nexport type ChannelStatus = RealtimeConnectionState | 'unavailable';\n\n/**\n * Subscribe a `handler` to one realtime `event` on `channel(name)` for the\n * lifetime of the component. Returns the live connection `{ status }`.\n *\n * The handler is held in a ref and refreshed every render, so passing a fresh\n * closure each render (the common case — callers needn't `useCallback`) does\n * NOT tear down and re-create the subscription. The subscription itself is\n * keyed on `[name, event]`: only a name/event change re-subscribes.\n *\n * StrictMode-safe: the facade refcounts joins, so React's mount→cleanup→\n * remount (double-invoke in dev) nets to exactly one live subscription via the\n * effect's `cancel()` cleanup — NO once-guard (a once-guard would leave the\n * subscription dead after the first cleanup; that was the P4 live-smoke bug).\n *\n * SSR / no-WebSocket: `pb.realtime.channel()` throws; the effect catches it and\n * reports `status: 'unavailable'` instead of crashing. (Effects don't run on\n * the server, so the initial server status is the idle default.)\n */\nexport function useChannel(\n name: string,\n event: string,\n handler: (payload: RealtimePayload) => void,\n): { status: ChannelStatus } {\n const handlerRef = useRef(handler);\n // Refresh the handler every render WITHOUT re-subscribing — the effect below\n // calls `handlerRef.current`, so the latest closure always runs.\n useEffect(() => {\n handlerRef.current = handler;\n });\n\n const [status, setStatus] = useState<ChannelStatus>('idle');\n\n // A configure-epoch counter: bumped by onConfigured so the channel effect\n // re-runs when a runtime arrives (handles the pre-config mount case and\n // watch-mode runtime swaps). Does not re-subscribe on every render.\n const [configEpoch, setConfigEpoch] = useState(0);\n useEffect(() => {\n return onConfigured(() => setConfigEpoch((n) => n + 1));\n }, []);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: `configEpoch` is intentionally a re-run trigger for configure-arrive / watch-mode\n useEffect(() => {\n let channel: ReturnType<typeof pb.realtime.channel>;\n try {\n channel = pb.realtime.channel(name);\n } catch {\n // notConfigured or no-WebSocket / server environment — degrade gracefully.\n setStatus('unavailable');\n return;\n }\n const sub = channel.on(event, (payload) => handlerRef.current(payload));\n // Seed from the current connection state, then track transitions.\n setStatus(pb.realtime.status.state);\n const offStatus = pb.realtime.status.onChange((snapshot) => setStatus(snapshot.state));\n return () => {\n offStatus();\n sub.cancel();\n };\n }, [name, event, configEpoch]);\n\n return { status };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/index.tsx"],"sourcesContent":["'use client';\n\n// `palbe/react` — thin React hooks over the observable `pb.*` facades. Each\n// value hook is a `useSyncExternalStore` (the React 18+ concurrent-safe\n// primitive) binding: `subscribe` is the facade's Unsubscribe-returning\n// listener, `getSnapshot` is the matching getter. NO new SDK behaviour lives\n// here — the hooks re-render exactly when (and only when) their slice of state\n// changes, mirroring the iOS `@Observable` granularity.\n//\n// `react` is an OPTIONAL peer dependency: importing `palbe` (the core entry)\n// never pulls React; only `palbe/react` does.\n//\n// Client-only: the module starts with `'use client'` (Next.js App Router). SSR\n// initial values come from the `getServerSnapshot` arms (null user, empty flag\n// set) so a server render never touches the runtime singleton.\n\nimport { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';\nimport type { AuthUser, Unsubscribe } from '../auth-facade.js';\nimport type { FlagsView, FlagValue } from '../flags-facade.js';\nimport { onConfigured } from '../internal.js';\nimport type { Chat } from '../messaging/chat.js';\nimport type { ChatMember, ChatMessage } from '../messaging/types.js';\nimport { pb } from '../pb.js';\nimport type { RealtimeConnectionState, RealtimePayload } from '../realtime/facade.js';\n\n/** The session slice `useSession` exposes. */\nexport interface SessionState {\n signedIn: boolean;\n user: AuthUser | null;\n}\n\n/** Frozen empty flag view — the stable server/unconfigured fallback for\n * `useFlags` (a fresh `{}` each call would loop `useSyncExternalStore`). */\nconst EMPTY_FLAGS: FlagsView = Object.freeze({});\n\n/** The signed-out session snapshot — a single frozen instance so the\n * unconfigured/server snapshot is referentially stable across calls. */\nconst SIGNED_OUT: SessionState = Object.freeze({ signedIn: false, user: null });\n\n/**\n * Wraps a facade-listener factory so hooks that mount BEFORE `__configure`\n * runs self-heal when configuration arrives (or changes on re-configure /\n * watch-mode regen).\n *\n * Behaviour:\n * - If already configured at subscribe time, attaches the facade listener\n * immediately AND registers an `onConfigured` watcher for future re-configures.\n * - If NOT yet configured at subscribe time, only registers `onConfigured`;\n * the facade listener is attached when configuration arrives.\n * - On every configure event: detaches the old facade listener (if any),\n * attaches a fresh one against the new runtime, then calls `onStoreChange`\n * so React re-reads the snapshot from the new runtime.\n * - The returned unsubscribe tears down both the onConfigured registration\n * and the current facade listener.\n *\n * @param getFacadeListener - factory called when a runtime is available;\n * must return an Unsubscribe; called with `onStoreChange` as its argument.\n */\nfunction subscribeWithConfig(\n getFacadeListener: (onStoreChange: () => void) => Unsubscribe,\n): (onStoreChange: () => void) => Unsubscribe {\n return (onStoreChange: () => void): Unsubscribe => {\n let currentFacadeUnsub: Unsubscribe | null = null;\n\n function attachFacade(): void {\n currentFacadeUnsub?.();\n try {\n currentFacadeUnsub = getFacadeListener(onStoreChange);\n } catch {\n // notConfigured — will be retried when onConfigured fires\n currentFacadeUnsub = null;\n }\n }\n\n // Register for future (re-)configure events.\n const offConfigured = onConfigured(() => {\n attachFacade();\n // Notify React to re-read the snapshot from the new runtime.\n onStoreChange();\n });\n\n // If already configured right now, attach immediately too.\n attachFacade();\n\n return () => {\n offConfigured();\n currentFacadeUnsub?.();\n currentFacadeUnsub = null;\n };\n };\n}\n\n/**\n * Current authenticated user, or `null` when signed out. Re-renders on BOTH\n * auth-state transitions (sign-in adopts a user, sign-out clears it) and\n * user-profile changes (e.g. an email-verified flip via `refreshUser`).\n *\n * `getSnapshot` is guarded: before the generated `palbe.gen.ts` runs\n * `__configure`, `pb.auth` throws `notConfigured` — a tree rendered that early\n * gets `null`, never a render crash. Once `__configure` runs the hook\n * self-heals: it re-subscribes to the new runtime's auth facade and notifies\n * React to re-read the snapshot.\n */\nexport function useUser(): AuthUser | null {\n return useSyncExternalStore(subscribeUserWithConfig, getUserSnapshot, getNullUser);\n}\n\nconst subscribeUserWithConfig = subscribeWithConfig((onStoreChange) => {\n // Two sources, one composite unsubscribe:\n // onAuthStateChange → sign-in (new user) and sign-out (null)\n // onUserChange → in-place profile edits (email-verified, …)\n // Both fire immediately on subscribe (iOS parity), which simply re-reads the\n // already-current snapshot — harmless.\n const offState = pb.auth.onAuthStateChange(onStoreChange);\n const offUser = pb.auth.onUserChange(onStoreChange);\n return () => {\n offState();\n offUser();\n };\n});\n\nfunction getUserSnapshot(): AuthUser | null {\n try {\n return pb.auth.currentUser;\n } catch {\n return null;\n }\n}\n\nfunction getNullUser(): AuthUser | null {\n return null;\n}\n\n/**\n * Session slice: `{ signedIn, user }`. Re-renders on auth-state transitions.\n *\n * `getSnapshot` MUST be referentially stable when nothing changed — returning a\n * fresh object every call would put `useSyncExternalStore` into an infinite\n * render loop. A per-component ref caches the last value and returns the SAME\n * object until `signedIn` or `user` actually changes.\n *\n * Self-heals when `__configure` runs after mount (same as `useUser`).\n */\nexport function useSession(): SessionState {\n const lastRef = useRef<SessionState>(SIGNED_OUT);\n const getSnapshot = useCallback((): SessionState => {\n const next = readSession();\n const prev = lastRef.current;\n if (prev.signedIn === next.signedIn && prev.user === next.user) {\n return prev; // unchanged — keep the stable reference\n }\n lastRef.current = next;\n return next;\n }, []);\n return useSyncExternalStore(subscribeSessionWithConfig, getSnapshot, getSignedOut);\n}\n\nconst subscribeSessionWithConfig = subscribeWithConfig((onStoreChange) =>\n pb.auth.onAuthStateChange(onStoreChange),\n);\n\nfunction readSession(): SessionState {\n try {\n const user = pb.auth.currentUser;\n const signedIn = pb.auth.isSignedIn;\n if (!signedIn && user === null) return SIGNED_OUT;\n return { signedIn, user };\n } catch {\n return SIGNED_OUT;\n }\n}\n\nfunction getSignedOut(): SessionState {\n return SIGNED_OUT;\n}\n\n/**\n * Subscribe to ONE flag. Re-renders only when THIS key's value changes\n * (`pb.flags.subscribeKey` is the per-key source — structural compare for\n * objects), returning `fallback` until the key has a value.\n *\n * The value is read at the dynamic flag boundary where the concrete type `T`\n * is caller-asserted (the cached value is whatever the server sent); the\n * contained `as T` is the documented escape hatch — flags are untyped on the\n * wire, and the caller owns the `fallback`'s type.\n *\n * NOTE: passing an inline-object `fallback` for an absent key defeats snapshot\n * caching (a new object reference each render re-enters the last-value ref).\n * Memoize the fallback (e.g. a module-level const or `useMemo`) if it matters.\n *\n * Self-heals when `__configure` runs after mount (same as `useUser`).\n */\nexport function useFlag<T extends FlagValue>(key: string, fallback: T): T {\n // Subscribe identity must change when `key` changes so useSyncExternalStore\n // re-subscribes to the new key. subscribeWithConfig wraps the per-key\n // factory so the hook also self-heals on (re-)configure.\n // biome-ignore lint/correctness/useExhaustiveDependencies: `key` is a runtime param, not an outer-scope value — dependency IS required\n const subscribe = useCallback(\n subscribeWithConfig((onStoreChange) => pb.flags.subscribeKey(key, onStoreChange)),\n [key],\n );\n\n // Cache the last value so getSnapshot is referentially stable (objects come\n // back as the frozen pooled reference — stable identity until a real change).\n const lastRef = useRef<T>(fallback);\n const getSnapshot = useCallback((): T => {\n let next: T;\n try {\n const value = pb.flags.get(key);\n next = value === undefined ? fallback : (value as T);\n } catch {\n next = fallback;\n }\n if (!Object.is(next, lastRef.current)) {\n lastRef.current = next;\n }\n return lastRef.current;\n }, [key, fallback]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Subscribe to the whole flag set. Re-renders on any change. `pb.flags.all()`\n * returns a frozen, identity-stable view (a NEW frozen object only when the set\n * actually changes), so it is `useSyncExternalStore`-safe as-is.\n *\n * Self-heals when `__configure` runs after mount (same as `useUser`).\n */\nexport function useFlags(): FlagsView {\n return useSyncExternalStore(subscribeFlagsWithConfig, getFlagsSnapshot, getEmptyFlags);\n}\n\nconst subscribeFlagsWithConfig = subscribeWithConfig((onStoreChange) =>\n pb.flags.onChange(onStoreChange),\n);\n\nfunction getFlagsSnapshot(): FlagsView {\n try {\n return pb.flags.all();\n } catch {\n return EMPTY_FLAGS;\n }\n}\n\nfunction getEmptyFlags(): FlagsView {\n return EMPTY_FLAGS;\n}\n\n/** The reported channel status: the shared-socket connection state, plus\n * `'unavailable'` when `pb.realtime.channel()` is not usable in this\n * environment (no WebSocket / SSR). */\nexport type ChannelStatus = RealtimeConnectionState | 'unavailable';\n\n/**\n * Subscribe a `handler` to one realtime `event` on `channel(name)` for the\n * lifetime of the component. Returns the live connection `{ status }`.\n *\n * The handler is held in a ref and refreshed every render, so passing a fresh\n * closure each render (the common case — callers needn't `useCallback`) does\n * NOT tear down and re-create the subscription. The subscription itself is\n * keyed on `[name, event]`: only a name/event change re-subscribes.\n *\n * StrictMode-safe: the facade refcounts joins, so React's mount→cleanup→\n * remount (double-invoke in dev) nets to exactly one live subscription via the\n * effect's `cancel()` cleanup — NO once-guard (a once-guard would leave the\n * subscription dead after the first cleanup; that was the P4 live-smoke bug).\n *\n * SSR / no-WebSocket: `pb.realtime.channel()` throws; the effect catches it and\n * reports `status: 'unavailable'` instead of crashing. (Effects don't run on\n * the server, so the initial server status is the idle default.)\n */\nexport function useChannel(\n name: string,\n event: string,\n handler: (payload: RealtimePayload) => void,\n): { status: ChannelStatus } {\n const handlerRef = useRef(handler);\n // Refresh the handler every render WITHOUT re-subscribing — the effect below\n // calls `handlerRef.current`, so the latest closure always runs.\n useEffect(() => {\n handlerRef.current = handler;\n });\n\n const [status, setStatus] = useState<ChannelStatus>('idle');\n\n // A configure-epoch counter: bumped by onConfigured so the channel effect\n // re-runs when a runtime arrives (handles the pre-config mount case and\n // watch-mode runtime swaps). Does not re-subscribe on every render.\n const [configEpoch, setConfigEpoch] = useState(0);\n useEffect(() => {\n return onConfigured(() => setConfigEpoch((n) => n + 1));\n }, []);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: `configEpoch` is intentionally a re-run trigger for configure-arrive / watch-mode\n useEffect(() => {\n let channel: ReturnType<typeof pb.realtime.channel>;\n try {\n channel = pb.realtime.channel(name);\n } catch {\n // notConfigured or no-WebSocket / server environment — degrade gracefully.\n setStatus('unavailable');\n return;\n }\n const sub = channel.on(event, (payload) => handlerRef.current(payload));\n // Seed from the current connection state, then track transitions.\n setStatus(pb.realtime.status.state);\n const offStatus = pb.realtime.status.onChange((snapshot) => setStatus(snapshot.state));\n return () => {\n offStatus();\n sub.cancel();\n };\n }, [name, event, configEpoch]);\n\n return { status };\n}\n\n// ─── Messaging hooks (Web-MLS Phase 2) ───────────────────────────────────────\n\nconst EMPTY_CHATS: readonly Chat[] = Object.freeze([]);\nconst EMPTY_MESSAGES: readonly ChatMessage[] = Object.freeze([]);\nconst EMPTY_MEMBERS: readonly ChatMember[] = Object.freeze([]);\nconst EMPTY_TYPING: readonly ChatMember[] = Object.freeze([]);\n\n/**\n * The observable chat list (DMs + groups, active only). Re-renders when a chat\n * is added (you start one / a peer DMs you and the Welcome drains), removed, or\n * the list hydrates from the durable catalog on launch.\n *\n * Self-heals when `__configure` runs after mount (same as `useUser`).\n */\nexport function useChats(): readonly Chat[] {\n const lastRef = useRef<readonly Chat[]>(EMPTY_CHATS);\n const getSnapshot = useCallback((): readonly Chat[] => {\n try {\n const next = pb.messaging.chats;\n // Identity-stable: only swap the cached array when the set changed.\n if (next.length !== lastRef.current.length || next.some((c, i) => c !== lastRef.current[i])) {\n lastRef.current = next.slice();\n }\n return lastRef.current;\n } catch {\n return EMPTY_CHATS;\n }\n }, []);\n return useSyncExternalStore(subscribeChatsWithConfig, getSnapshot, getEmptyChats);\n}\n\nconst subscribeChatsWithConfig = subscribeWithConfig((onStoreChange) =>\n pb.messaging.onChatsChange(onStoreChange),\n);\n\nfunction getEmptyChats(): readonly Chat[] {\n return EMPTY_CHATS;\n}\n\n/**\n * Bind to one `Chat`'s changes. Re-renders whenever the chat's observable state\n * changes (a new message, members refresh, typing, materialize draft→active).\n * Returns the SAME `chat` instance for ergonomic access to `chat.messages`,\n * `chat.send(...)`, etc.\n */\nexport function useChat(chat: Chat): Chat {\n const subscribe = useCallback(\n (onStoreChange: () => void) => chat.onChange(onStoreChange),\n [chat],\n );\n // The chat instance is pointer-stable; a per-render counter forces re-render\n // on each onChange. Snapshot returns the instance itself (stable identity).\n const getSnapshot = useCallback(() => chat, [chat]);\n // Tick on every change so consumers reading chat.messages re-render.\n const [, force] = useState(0);\n useEffect(() => chat.onChange(() => force((n) => n + 1)), [chat]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * The ordered message transcript for a chat (newest last). Re-renders on a new\n * message / history page / own-send echo. A cached snapshot keeps the array\n * reference stable until the list actually changes.\n */\nexport function useMessages(chat: Chat): readonly ChatMessage[] {\n const lastRef = useRef<readonly ChatMessage[]>(EMPTY_MESSAGES);\n const subscribe = useCallback(\n (onStoreChange: () => void) => chat.onChange(onStoreChange),\n [chat],\n );\n const getSnapshot = useCallback((): readonly ChatMessage[] => {\n const next = chat.messages;\n if (next.length !== lastRef.current.length || next.some((m, i) => m !== lastRef.current[i])) {\n lastRef.current = next.slice();\n }\n return lastRef.current;\n }, [chat]);\n return useSyncExternalStore(subscribe, getSnapshot, () => EMPTY_MESSAGES);\n}\n\n/** The members of a chat (users). Re-renders when the roster changes. */\nexport function useChatMembers(chat: Chat): readonly ChatMember[] {\n const lastRef = useRef<readonly ChatMember[]>(EMPTY_MEMBERS);\n const subscribe = useCallback(\n (onStoreChange: () => void) => chat.onChange(onStoreChange),\n [chat],\n );\n const getSnapshot = useCallback((): readonly ChatMember[] => {\n const next = chat.members;\n if (next.length !== lastRef.current.length || next.some((m, i) => m !== lastRef.current[i])) {\n lastRef.current = next.slice();\n }\n return lastRef.current;\n }, [chat]);\n return useSyncExternalStore(subscribe, getSnapshot, () => EMPTY_MEMBERS);\n}\n\n/** The users currently typing in a chat. Re-renders on typing start/stop. */\nexport function useTyping(chat: Chat): readonly ChatMember[] {\n const lastRef = useRef<readonly ChatMember[]>(EMPTY_TYPING);\n const subscribe = useCallback(\n (onStoreChange: () => void) => chat.onChange(onStoreChange),\n [chat],\n );\n const getSnapshot = useCallback((): readonly ChatMember[] => {\n const next = chat.typing;\n if (next.length !== lastRef.current.length || next.some((m, i) => m !== lastRef.current[i])) {\n lastRef.current = next.slice();\n }\n return lastRef.current;\n }, [chat]);\n return useSyncExternalStore(subscribe, getSnapshot, () => EMPTY_TYPING);\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@palbase/web",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Palbe — the Palbase client SDK for web. One entry point: pb.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -82,11 +82,6 @@
|
|
|
82
82
|
"files": [
|
|
83
83
|
"dist"
|
|
84
84
|
],
|
|
85
|
-
"dependencies": {
|
|
86
|
-
"@palbase/core": "^2.0.0",
|
|
87
|
-
"@palbase/auth": "^0.7.3",
|
|
88
|
-
"@palbase/flags": "^1.0.0"
|
|
89
|
-
},
|
|
90
85
|
"peerDependencies": {
|
|
91
86
|
"next": ">=15",
|
|
92
87
|
"react": ">=18"
|
|
@@ -108,11 +103,17 @@
|
|
|
108
103
|
"react-dom": "^19.0.0",
|
|
109
104
|
"tsup": "^8.4.0",
|
|
110
105
|
"typescript": "^5.8.0",
|
|
111
|
-
"vitest": "^3.1.0"
|
|
106
|
+
"vitest": "^3.1.0",
|
|
107
|
+
"@palbase/auth": "^0.7.3",
|
|
108
|
+
"@palbase/core": "^2.0.0",
|
|
109
|
+
"@palbase/flags": "^1.0.0"
|
|
112
110
|
},
|
|
113
111
|
"publishConfig": {
|
|
114
112
|
"access": "public"
|
|
115
113
|
},
|
|
114
|
+
"dependencies": {
|
|
115
|
+
"livekit-client": "^2.19.2"
|
|
116
|
+
},
|
|
116
117
|
"scripts": {
|
|
117
118
|
"prebuild": "node scripts/sync-version.mjs",
|
|
118
119
|
"build": "tsup",
|
|
@@ -1,454 +0,0 @@
|
|
|
1
|
-
import { HttpClient, TokenManager, Session } from '@palbase/core';
|
|
2
|
-
import { AuthClient } from '@palbase/auth';
|
|
3
|
-
import { S as SessionStorageAdapter } from './storage-BPaeSG8K.js';
|
|
4
|
-
import { FlagValue } from '@palbase/flags';
|
|
5
|
-
|
|
6
|
-
interface PalbeOAuthConfig {
|
|
7
|
-
google?: {
|
|
8
|
-
enabled?: boolean;
|
|
9
|
-
clientId?: string;
|
|
10
|
-
};
|
|
11
|
-
apple?: {
|
|
12
|
-
enabled?: boolean;
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
/** Baked into palbe.gen.ts by `palbase web link` — the generated web config. */
|
|
16
|
-
interface PalbeConfig {
|
|
17
|
-
url: string;
|
|
18
|
-
apiKey: string;
|
|
19
|
-
/** Informational — url+apiKey are already branch-specific (endpointRef embeds the branch slug); gen bakes all three consistently. */
|
|
20
|
-
branch?: string;
|
|
21
|
-
oauth?: PalbeOAuthConfig;
|
|
22
|
-
/** Refresh-token persistence. Default: endpoint-scoped localStorage in browsers, memory elsewhere. */
|
|
23
|
-
storage?: SessionStorageAdapter;
|
|
24
|
-
/** Extra headers on every request. */
|
|
25
|
-
headers?: Record<string, string>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Frozen view of all cached flag values (what `all()` returns / `changes()` yields). */
|
|
29
|
-
type FlagsView = Readonly<Record<string, FlagValue>>;
|
|
30
|
-
/**
|
|
31
|
-
* `pb.flags` — iOS-parity facade over `FlagsPool` (cache + delta polling +
|
|
32
|
-
* auth binding) and `FlagsClient` (stateless transport).
|
|
33
|
-
*
|
|
34
|
-
* Start semantics: the facade is constructed lazily (first `pb.flags` touch).
|
|
35
|
-
* - Browser: the pool starts right here in the constructor — cold snapshot +
|
|
36
|
-
* 30s delta polling + visibility pause. "First use starts the machinery"
|
|
37
|
-
* without per-method start checks.
|
|
38
|
-
* - Server (no `document`): NEVER auto-starts. `ready()`/`refresh()` are
|
|
39
|
-
* one-shot snapshot fetches with zero timers and zero storage access, so a
|
|
40
|
-
* pbServer request that reads flags pays exactly one HTTP call.
|
|
41
|
-
*
|
|
42
|
-
* localStorage persistence is scoped per endpoint ref (`palbe.flags.<ref>`),
|
|
43
|
-
* mirroring the session-storage key convention.
|
|
44
|
-
*/
|
|
45
|
-
declare class PalbeFlags {
|
|
46
|
-
private readonly transport;
|
|
47
|
-
private readonly pool;
|
|
48
|
-
constructor(rt: PalbeRuntime);
|
|
49
|
-
/** Resolves once the first snapshot (or persisted hydrate) is available. Auto-starts the pool. */
|
|
50
|
-
ready(): Promise<void>;
|
|
51
|
-
/** Force an immediate re-snapshot. */
|
|
52
|
-
refresh(): Promise<void>;
|
|
53
|
-
/** Frozen snapshot of all cached values (identity-stable until a change). */
|
|
54
|
-
all(): FlagsView;
|
|
55
|
-
/** Raw cached value for `key`, or `undefined` when not in the cache. */
|
|
56
|
-
get(key: string): FlagValue | undefined;
|
|
57
|
-
/** `true` only when the cached value is strictly `true`; `fallback` when the key is absent. */
|
|
58
|
-
isEnabled(key: string, fallback?: boolean): boolean;
|
|
59
|
-
/** Alias of {@link isEnabled} (iOS parity). */
|
|
60
|
-
bool(key: string, fallback?: boolean): boolean;
|
|
61
|
-
/** Cached value when it is a string, else `fallback`. */
|
|
62
|
-
getString(key: string, fallback: string): string;
|
|
63
|
-
/** Cached value when it is an integer number, else `fallback`. */
|
|
64
|
-
getInt(key: string, fallback: number): number;
|
|
65
|
-
/** Cached value when it is a number (integers included), else `fallback`. */
|
|
66
|
-
getDouble(key: string, fallback: number): number;
|
|
67
|
-
/**
|
|
68
|
-
* Resolve the multivariate variant for `key` — the variant name, or `null`
|
|
69
|
-
* when the flag has no variant (or the read fails).
|
|
70
|
-
*
|
|
71
|
-
* Wire failures (network errors, 404, etc.) resolve to `null`.
|
|
72
|
-
* Invalid flag names throw `BackendError('validation', { code: 'invalid_flag_name' })`.
|
|
73
|
-
*
|
|
74
|
-
* DEVIATION from iOS sync-cache parity: the platform does not propagate
|
|
75
|
-
* variant metadata into the user-flags snapshot/delta cache, so this is an
|
|
76
|
-
* ASYNC transport read (`GET /v1/flags/{key}/variant`), not a cache lookup.
|
|
77
|
-
*/
|
|
78
|
-
getVariant(key: string): Promise<string | null>;
|
|
79
|
-
/** Subscribe to any change in the cached flag set. */
|
|
80
|
-
onChange(callback: () => void): Unsubscribe;
|
|
81
|
-
/**
|
|
82
|
-
* Observe ONE key: fires only when that key's value actually changes
|
|
83
|
-
* (per {@link sameFlagValue} — structural compare for objects), with the
|
|
84
|
-
* new value (`undefined` = deleted). P5 React-hook substrate.
|
|
85
|
-
*/
|
|
86
|
-
subscribeKey(key: string, callback: (value: FlagValue | undefined) => void): Unsubscribe;
|
|
87
|
-
/**
|
|
88
|
-
* Async iteration over flag changes: yields the new {@link all} view on
|
|
89
|
-
* every pool change notification. The listener is detached when the
|
|
90
|
-
* consumer `break`s/`return`s/`throw`s — including while a `next()` is
|
|
91
|
-
* still pending (it resolves `{ done: true }` instead of hanging, which a
|
|
92
|
-
* plain async-generator `finally` would not guarantee).
|
|
93
|
-
*/
|
|
94
|
-
changes(): AsyncIterableIterator<FlagsView>;
|
|
95
|
-
/** Stop polling and detach all listeners (auth + visibility + subscribers). */
|
|
96
|
-
destroy(): void;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* The connection state of the shared realtime WebSocket (iOS enum parity), plus
|
|
101
|
-
* `'error'` — surfaced when a channel could not be recovered after a
|
|
102
|
-
* token-expiry close exhausted its rejoin attempts (the socket itself may still
|
|
103
|
-
* be up; the app should stop expecting events on the dead channel).
|
|
104
|
-
*/
|
|
105
|
-
type RealtimeConnectionState = 'idle' | 'connected' | 'reconnecting' | 'error';
|
|
106
|
-
|
|
107
|
-
/** An inbound broadcast payload (the inner `payload` of the Phoenix envelope). */
|
|
108
|
-
type RealtimePayload = Record<string, unknown>;
|
|
109
|
-
type RealtimeHandler = (payload: RealtimePayload) => void;
|
|
110
|
-
/** A cancellable realtime subscription. `cancel()` removes the handler and,
|
|
111
|
-
* when it was the channel's last, leaves the channel on the shared socket.
|
|
112
|
-
* Cancelling twice is a no-op. */
|
|
113
|
-
interface RealtimeSubscription {
|
|
114
|
-
cancel(): void;
|
|
115
|
-
}
|
|
116
|
-
interface RealtimeStatusSnapshot {
|
|
117
|
-
state: RealtimeConnectionState;
|
|
118
|
-
lastEventAt: Date | null;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* The observable connection status (`pb.realtime.status`) — the plain-JS
|
|
122
|
-
* analogue of the iOS `@Observable RealtimeStatusStore`. `state` drives a
|
|
123
|
-
* live "connected" badge; `lastEventAt` proves a push actually arrived.
|
|
124
|
-
* `onChange` fires on state transitions and on every recorded event.
|
|
125
|
-
*/
|
|
126
|
-
interface RealtimeStatus {
|
|
127
|
-
readonly state: RealtimeConnectionState;
|
|
128
|
-
readonly lastEventAt: Date | null;
|
|
129
|
-
onChange(callback: (status: RealtimeStatusSnapshot) => void): Unsubscribe;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* A subscription handle for one realtime channel (returned by
|
|
133
|
-
* `pb.realtime.channel(name)` — the SAME instance per name). A channel is
|
|
134
|
-
* joined on the shared socket when its first handler is added (or on the
|
|
135
|
-
* first `send`) and left when its last handler cancels.
|
|
136
|
-
*/
|
|
137
|
-
declare class RealtimeChannel {
|
|
138
|
-
/** The app-defined channel name (bare sub-topic, no "realtime:" prefix). */
|
|
139
|
-
readonly name: string;
|
|
140
|
-
private readonly owner;
|
|
141
|
-
/** @internal — obtain channels via `pb.realtime.channel(name)`. */
|
|
142
|
-
constructor(name: string, owner: PalbeRealtime);
|
|
143
|
-
/**
|
|
144
|
-
* Subscribe `handler` to `event` on this channel. Fire-and-forget: the
|
|
145
|
-
* join rides the shared socket asynchronously. Hold the returned
|
|
146
|
-
* subscription and `cancel()` it to stop.
|
|
147
|
-
*/
|
|
148
|
-
on(event: string, handler: RealtimeHandler): RealtimeSubscription;
|
|
149
|
-
/**
|
|
150
|
-
* Broadcast `payload` to the channel's other subscribers (web addition —
|
|
151
|
-
* iOS is receive-only). Joins the channel if it isn't already; queued
|
|
152
|
-
* until the join is live on the socket.
|
|
153
|
-
*/
|
|
154
|
-
send(event: string, payload?: RealtimePayload): void;
|
|
155
|
-
}
|
|
156
|
-
declare class PalbeRealtime {
|
|
157
|
-
private readonly rt;
|
|
158
|
-
private socket;
|
|
159
|
-
private readonly channels;
|
|
160
|
-
private readonly statusStore;
|
|
161
|
-
private readonly handlers;
|
|
162
|
-
private readonly topicRefcount;
|
|
163
|
-
constructor(rt: PalbeRuntime);
|
|
164
|
-
/**
|
|
165
|
-
* Get the handle for an app-defined channel (e.g. "room:42"). The same
|
|
166
|
-
* name returns the SAME instance. Throws a guided error on server-side
|
|
167
|
-
* hosts (SSR/RSC/Node) — realtime is client-only.
|
|
168
|
-
*/
|
|
169
|
-
channel(name: string): RealtimeChannel;
|
|
170
|
-
/**
|
|
171
|
-
* Tear down the shared socket and clear all channel state. Called by
|
|
172
|
-
* `__configure` and `__reset` when the runtime is replaced, so old sockets
|
|
173
|
-
* are not left open and heartbeat timers do not leak.
|
|
174
|
-
*/
|
|
175
|
-
destroy(): void;
|
|
176
|
-
/** The observable connection status. Safe to read anywhere (reports `idle` until a socket exists). */
|
|
177
|
-
get status(): RealtimeStatus;
|
|
178
|
-
/** @internal */
|
|
179
|
-
subscribe(topic: string, event: string, handler: RealtimeHandler): RealtimeSubscription;
|
|
180
|
-
/** @internal */
|
|
181
|
-
send(topic: string, event: string, payload: RealtimePayload): void;
|
|
182
|
-
/** Lazily build + start the shared socket on first subscription/send. */
|
|
183
|
-
private ensureSocket;
|
|
184
|
-
private route;
|
|
185
|
-
/**
|
|
186
|
-
* A channel died for good (token-expiry rejoin exhausted N attempts). Drop its
|
|
187
|
-
* handlers + refcount so the app stops expecting events on it, and flip the
|
|
188
|
-
* status observable to `'error'` (the React `useChannel` hook reports this).
|
|
189
|
-
* A later `.on()` for the same topic re-joins from scratch (refcount 1).
|
|
190
|
-
*/
|
|
191
|
-
private handleChannelError;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
interface PalbeRuntime {
|
|
195
|
-
config: PalbeConfig;
|
|
196
|
-
http: HttpClient;
|
|
197
|
-
tokenManager: TokenManager;
|
|
198
|
-
authClient: AuthClient;
|
|
199
|
-
auth: PalbeAuth;
|
|
200
|
-
flags: PalbeFlags;
|
|
201
|
-
realtime: PalbeRealtime;
|
|
202
|
-
analytics: PalbeAnalytics;
|
|
203
|
-
storage: SessionStorageAdapter;
|
|
204
|
-
/**
|
|
205
|
-
* Destroy the realtime facade if it was already constructed (no-op otherwise).
|
|
206
|
-
* Called by `__configure` / `__reset` when the runtime is replaced so the old
|
|
207
|
-
* socket is closed and heartbeat timers do not leak.
|
|
208
|
-
*/
|
|
209
|
-
destroyRealtime(): void;
|
|
210
|
-
}
|
|
211
|
-
declare function buildRuntime(config: PalbeConfig): PalbeRuntime;
|
|
212
|
-
|
|
213
|
-
type MagicLinkResult = ({
|
|
214
|
-
status: 'signedIn';
|
|
215
|
-
} & AuthSuccess) | {
|
|
216
|
-
status: 'mfaRequired';
|
|
217
|
-
mfaToken: string;
|
|
218
|
-
factors: string[];
|
|
219
|
-
};
|
|
220
|
-
type OAuthExchangeResult = ({
|
|
221
|
-
status: 'signedIn';
|
|
222
|
-
} & AuthSuccess) | {
|
|
223
|
-
status: 'mfaRequired';
|
|
224
|
-
mfaToken: string;
|
|
225
|
-
factors: string[];
|
|
226
|
-
};
|
|
227
|
-
interface AuthUser {
|
|
228
|
-
id: string;
|
|
229
|
-
/** null for phone-only users (the server omits email). */
|
|
230
|
-
email: string | null;
|
|
231
|
-
emailVerified: boolean;
|
|
232
|
-
createdAt: string;
|
|
233
|
-
}
|
|
234
|
-
interface AuthSuccess {
|
|
235
|
-
user: AuthUser;
|
|
236
|
-
session: Session;
|
|
237
|
-
}
|
|
238
|
-
type AuthState = {
|
|
239
|
-
status: 'signedIn';
|
|
240
|
-
user: AuthUser;
|
|
241
|
-
} | {
|
|
242
|
-
status: 'signedOut';
|
|
243
|
-
};
|
|
244
|
-
type AuthChangeEvent = {
|
|
245
|
-
type: 'signedIn';
|
|
246
|
-
user: AuthUser;
|
|
247
|
-
} | {
|
|
248
|
-
type: 'signedOut';
|
|
249
|
-
reason: 'userInitiated' | 'sessionExpired';
|
|
250
|
-
} | {
|
|
251
|
-
type: 'tokenRefreshed';
|
|
252
|
-
};
|
|
253
|
-
type Unsubscribe = () => void;
|
|
254
|
-
declare class PalbeAuth {
|
|
255
|
-
private readonly rt;
|
|
256
|
-
private cachedUser;
|
|
257
|
-
private signingOut;
|
|
258
|
-
private signingIn;
|
|
259
|
-
private signedInState;
|
|
260
|
-
private readonly stateListeners;
|
|
261
|
-
private readonly eventListeners;
|
|
262
|
-
private readonly userListeners;
|
|
263
|
-
constructor(rt: PalbeRuntime);
|
|
264
|
-
get currentUser(): AuthUser | null;
|
|
265
|
-
get isSignedIn(): boolean;
|
|
266
|
-
signUp(params: {
|
|
267
|
-
email: string;
|
|
268
|
-
password: string;
|
|
269
|
-
}): Promise<AuthSuccess>;
|
|
270
|
-
signIn(params: {
|
|
271
|
-
email: string;
|
|
272
|
-
password: string;
|
|
273
|
-
}): Promise<AuthSuccess>;
|
|
274
|
-
signOut(): Promise<void>;
|
|
275
|
-
getUser(): Promise<AuthUser>;
|
|
276
|
-
refreshUser(): Promise<AuthUser>;
|
|
277
|
-
/**
|
|
278
|
-
* Subscribe to signed-in/signed-out state. Fires immediately with the
|
|
279
|
-
* current snapshot (iOS parity). NOTE: a restored session (page reload) has
|
|
280
|
-
* no cached user yet, so the immediate snapshot reports signedOut even when
|
|
281
|
-
* `isSignedIn` is true — call `refreshUser()` on boot to populate the user
|
|
282
|
-
* and rely on `isSignedIn` for the session truth.
|
|
283
|
-
*/
|
|
284
|
-
onAuthStateChange(callback: (state: AuthState) => void): Unsubscribe;
|
|
285
|
-
onAuthEvent(callback: (event: AuthChangeEvent) => void): Unsubscribe;
|
|
286
|
-
/** Subscribe to user-profile changes. Replays the cached user on subscribe (iOS parity). */
|
|
287
|
-
onUserChange(callback: (user: AuthUser) => void): Unsubscribe;
|
|
288
|
-
private snapshotState;
|
|
289
|
-
/**
|
|
290
|
-
* Listener exception isolation: a throwing consumer callback must never
|
|
291
|
-
* break delivery to other listeners nor propagate into the emit caller
|
|
292
|
-
* (tokenManager.clearSession callers, pb.call paths, signOut, ...).
|
|
293
|
-
*/
|
|
294
|
-
private safeInvoke;
|
|
295
|
-
private emitState;
|
|
296
|
-
private emitEvent;
|
|
297
|
-
/** Cache the user + announce sign-in. Session was already set by AuthClient. */
|
|
298
|
-
private adopt;
|
|
299
|
-
/** Adopt a raw palauth AuthResult wire shape: wire tokens, cache user, announce. */
|
|
300
|
-
private adoptWire;
|
|
301
|
-
/** Run a sign-in flow under the signingIn flag to suppress spurious tokenRefreshed. */
|
|
302
|
-
private withSigningIn;
|
|
303
|
-
/**
|
|
304
|
-
* Defensively decode a 200 union body (AuthResult | mfa-required) and
|
|
305
|
-
* complete the flow. The wire contract promises one of the two shapes, but
|
|
306
|
-
* a literal-`null` (or otherwise malformed) JSON body must surface as
|
|
307
|
-
* BackendError('decode') — never a raw TypeError from probing a non-object.
|
|
308
|
-
* The mfa branch validates its fields: `mfa_token` is required; the factor
|
|
309
|
-
* list (named `factors` by magic-link verify, `mfa_factors` by the OAuth
|
|
310
|
-
* callback — extraction-verified against palauth) tolerates a missing or
|
|
311
|
-
* malformed value as []. The signedIn branch validates the FULL AuthResult
|
|
312
|
-
* shape (asWireAuthResult) — an incomplete body falls through to decode,
|
|
313
|
-
* never half-adopts.
|
|
314
|
-
*/
|
|
315
|
-
private completeAuthUnion;
|
|
316
|
-
private decodeError;
|
|
317
|
-
signInWithOTP(params: {
|
|
318
|
-
phone: string;
|
|
319
|
-
}): Promise<void>;
|
|
320
|
-
verifyOTP(params: {
|
|
321
|
-
phone: string;
|
|
322
|
-
token: string;
|
|
323
|
-
}): Promise<AuthSuccess>;
|
|
324
|
-
resetPassword(email: string): Promise<void>;
|
|
325
|
-
confirmPasswordReset(params: {
|
|
326
|
-
token: string;
|
|
327
|
-
newPassword: string;
|
|
328
|
-
}): Promise<void>;
|
|
329
|
-
updatePassword(params: {
|
|
330
|
-
currentPassword: string;
|
|
331
|
-
newPassword: string;
|
|
332
|
-
}): Promise<void>;
|
|
333
|
-
verifyEmail(params: {
|
|
334
|
-
token: string;
|
|
335
|
-
} | {
|
|
336
|
-
code: string;
|
|
337
|
-
email: string;
|
|
338
|
-
}): Promise<void>;
|
|
339
|
-
resendVerification(email: string): Promise<void>;
|
|
340
|
-
signInWithMagicLink(email: string): Promise<void>;
|
|
341
|
-
verifyMagicLink(token: string): Promise<MagicLinkResult>;
|
|
342
|
-
signInWithCredential(params: {
|
|
343
|
-
provider: string;
|
|
344
|
-
credential: string;
|
|
345
|
-
}): Promise<AuthSuccess>;
|
|
346
|
-
signInWithOAuth(params: {
|
|
347
|
-
provider: string;
|
|
348
|
-
redirectTo?: string;
|
|
349
|
-
/** Set false to skip the browser redirect (popup/manual flows). Default true. */
|
|
350
|
-
redirect?: boolean;
|
|
351
|
-
}): Promise<{
|
|
352
|
-
url: string;
|
|
353
|
-
}>;
|
|
354
|
-
/**
|
|
355
|
-
* Complete the OAuth redirect flow: trade the provider's `code` + `state`
|
|
356
|
-
* (from the app's redirect_uri query) for a session. PKCE verifier + state
|
|
357
|
-
* live server-side in palauth — the client only relays the two values.
|
|
358
|
-
*/
|
|
359
|
-
exchangeCodeForSession(params: {
|
|
360
|
-
provider: string;
|
|
361
|
-
code: string;
|
|
362
|
-
state: string;
|
|
363
|
-
}): Promise<OAuthExchangeResult>;
|
|
364
|
-
/** Config-as-code gate for the zero-arg provider sugar. */
|
|
365
|
-
private requireProvider;
|
|
366
|
-
signInWithGoogle(opts?: {
|
|
367
|
-
redirectTo?: string;
|
|
368
|
-
redirect?: boolean;
|
|
369
|
-
}): Promise<{
|
|
370
|
-
url: string;
|
|
371
|
-
}>;
|
|
372
|
-
signInWithApple(opts?: {
|
|
373
|
-
redirectTo?: string;
|
|
374
|
-
redirect?: boolean;
|
|
375
|
-
}): Promise<{
|
|
376
|
-
url: string;
|
|
377
|
-
}>;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/** Free-form event property bag (JSON object on the wire). */
|
|
381
|
-
type AnalyticsProperties = Record<string, unknown>;
|
|
382
|
-
/**
|
|
383
|
-
* Runtime-scoped analytics identity — constructed EAGERLY in `buildRuntime`
|
|
384
|
-
* (cheap: listener wiring only, no I/O) because the X-Distinct-Id header
|
|
385
|
-
* interceptor must stamp every request from the first one, even when
|
|
386
|
-
* `pb.analytics` is never touched (iOS: the resolver is wired at
|
|
387
|
-
* PalBackend init). The facade wraps this state lazily.
|
|
388
|
-
*
|
|
389
|
-
* Auth events: while no facade exists, the state maintains identity only
|
|
390
|
-
* (user id for the header, anon rotation on user-initiated sign-out). Once
|
|
391
|
-
* the facade attaches, it takes over wholesale and adds the ingest
|
|
392
|
-
* side-effects (flush + identify / flush + reset).
|
|
393
|
-
*/
|
|
394
|
-
declare class AnalyticsState {
|
|
395
|
-
identifiedUserId: string | null;
|
|
396
|
-
/** Set by PalbeAnalytics on construction — delegates auth events to it. */
|
|
397
|
-
facadeAuthHandler: ((event: AuthChangeEvent) => void) | null;
|
|
398
|
-
private anonId;
|
|
399
|
-
private readonly store;
|
|
400
|
-
private readonly idKey;
|
|
401
|
-
private readonly optOutKey;
|
|
402
|
-
constructor(rt: PalbeRuntime);
|
|
403
|
-
/** User id when identified, else the stable anon id. NEVER empty —
|
|
404
|
-
* this is what `identify()` links, so stamping it on every request lets
|
|
405
|
-
* the trace pipeline stitch pre-login activity to the user. */
|
|
406
|
-
distinctId(): string;
|
|
407
|
-
anonymousId(): string;
|
|
408
|
-
rotateAnonymousId(): string;
|
|
409
|
-
isOptedOut(): boolean;
|
|
410
|
-
setOptOut(on: boolean): void;
|
|
411
|
-
}
|
|
412
|
-
declare class PalbeAnalytics {
|
|
413
|
-
private readonly rt;
|
|
414
|
-
private readonly state;
|
|
415
|
-
private readonly buffer;
|
|
416
|
-
private flushTimer;
|
|
417
|
-
/** Browser → buffer + size/timer flush. Server (no document) → immediate
|
|
418
|
-
* per-call POST, zero timers (nothing leaks into RSC/route handlers). */
|
|
419
|
-
private readonly browser;
|
|
420
|
-
constructor(rt: PalbeRuntime, state: AnalyticsState);
|
|
421
|
-
/** Record an event. Invalid names are dropped (one warn per process). */
|
|
422
|
-
capture(event: string, properties?: AnalyticsProperties): void;
|
|
423
|
-
/** Record a screen view — server-canonical `$screen` + `screen_name`.
|
|
424
|
-
* Validates non-empty only (the live wire at /v1/analytics/screen requires
|
|
425
|
-
* non-empty screen_name; the event-name regex does NOT apply here). */
|
|
426
|
-
screen(name: string, properties?: AnalyticsProperties): void;
|
|
427
|
-
/** Link the current anon id to `userId` and adopt it as the distinct id.
|
|
428
|
-
* Immediate POST (not buffered). Re-identifying a DIFFERENT user
|
|
429
|
-
* auto-resets first (iOS K10) so one anon id never links two users. */
|
|
430
|
-
identify(userId: string, traits?: AnalyticsProperties): void;
|
|
431
|
-
/** Merge distinct id `from` into `to` (identity stitching). Immediate POST. */
|
|
432
|
-
alias(from: string, to: string): void;
|
|
433
|
-
/** Drop the buffer, clear the identified user and rotate the anon id
|
|
434
|
-
* (sign-out hygiene). Callers wanting pending events delivered flush first
|
|
435
|
-
* — the auth binding does. */
|
|
436
|
-
reset(): void;
|
|
437
|
-
/** Persisted GDPR opt-out. Opting out drops anything pending so nothing
|
|
438
|
-
* already-buffered leaks after the user said stop. */
|
|
439
|
-
setOptOut(on: boolean): void;
|
|
440
|
-
/** Drain the buffer to /v1/analytics/batch (≤100 per request, sequential).
|
|
441
|
-
* Resolves when delivery finished; NEVER rejects — failed slices are
|
|
442
|
-
* dropped with one warn per flush. 207 partial-reject is also warned once
|
|
443
|
-
* (fire-and-forget: no retry for already-delivered batches). */
|
|
444
|
-
flush(): Promise<void>;
|
|
445
|
-
private ingest;
|
|
446
|
-
/** iOS PalBackend auth-binding parity: signedIn → flush + identify;
|
|
447
|
-
* userInitiated signedOut → flush + reset; sessionExpired → flush only. */
|
|
448
|
-
private handleAuthEvent;
|
|
449
|
-
private startTimer;
|
|
450
|
-
private cancelTimer;
|
|
451
|
-
private post;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
export { type AnalyticsProperties as A, type FlagsView as F, type MagicLinkResult as M, type OAuthExchangeResult as O, PalbeAnalytics as P, RealtimeChannel as R, type Unsubscribe as U, type AuthChangeEvent as a, type AuthState as b, type AuthSuccess as c, type AuthUser as d, PalbeAuth as e, type PalbeConfig as f, PalbeFlags as g, type PalbeOAuthConfig as h, PalbeRealtime as i, type RealtimeConnectionState as j, type RealtimeHandler as k, type RealtimePayload as l, type RealtimeStatus as m, type RealtimeStatusSnapshot as n, type RealtimeSubscription as o, type PalbeRuntime as p, buildRuntime as q };
|