@palbase/web 7.3.9 → 7.4.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.
Files changed (47) hide show
  1. package/dist/{analytics-facade-BER0EyYT.d.ts → analytics-facade-But6gu2r.d.ts} +135 -16
  2. package/dist/{analytics-facade-CQXCQSoF.d.cts → analytics-facade-f8IVujJW.d.cts} +135 -16
  3. package/dist/{chunk-EAOROUSW.js → chunk-4WMJTUDN.js} +26 -17
  4. package/dist/chunk-4WMJTUDN.js.map +1 -0
  5. package/dist/{chunk-Q5DUDL77.js → chunk-G56VGUYZ.js} +1 -1
  6. package/dist/chunk-G56VGUYZ.js.map +1 -0
  7. package/dist/{chunk-FCIMEU2E.js → chunk-VHUCJBBQ.js} +2 -2
  8. package/dist/chunk-VHUCJBBQ.js.map +1 -0
  9. package/dist/{chunk-H6YSCGQC.js → chunk-WXBOPPHU.js} +502 -38
  10. package/dist/chunk-WXBOPPHU.js.map +1 -0
  11. package/dist/gen/cli.cjs +1 -1
  12. package/dist/gen/cli.cjs.map +1 -1
  13. package/dist/gen/cli.js +2 -2
  14. package/dist/gen/cli.js.map +1 -1
  15. package/dist/index.cjs +514 -50
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +3 -3
  18. package/dist/index.d.ts +3 -3
  19. package/dist/index.js +3 -3
  20. package/dist/internal.cjs +524 -51
  21. package/dist/internal.cjs.map +1 -1
  22. package/dist/internal.d.cts +24 -4
  23. package/dist/internal.d.ts +24 -4
  24. package/dist/internal.js +3 -3
  25. package/dist/next/client.cjs +518 -48
  26. package/dist/next/client.cjs.map +1 -1
  27. package/dist/next/client.js +3 -3
  28. package/dist/next/index.cjs +524 -51
  29. package/dist/next/index.cjs.map +1 -1
  30. package/dist/next/index.d.cts +2 -2
  31. package/dist/next/index.d.ts +2 -2
  32. package/dist/next/index.js +4 -4
  33. package/dist/next/index.js.map +1 -1
  34. package/dist/next/proxy.cjs.map +1 -1
  35. package/dist/next/proxy.js +3 -3
  36. package/dist/{pb-WAQvwCfM.d.cts → pb-9MMDNQ9p.d.cts} +6 -1
  37. package/dist/{pb-BRlmBIAm.d.ts → pb-DRmdWQzG.d.ts} +6 -1
  38. package/dist/react/index.cjs +22 -19
  39. package/dist/react/index.cjs.map +1 -1
  40. package/dist/react/index.d.cts +1 -1
  41. package/dist/react/index.d.ts +1 -1
  42. package/dist/react/index.js +3 -3
  43. package/package.json +1 -1
  44. package/dist/chunk-EAOROUSW.js.map +0 -1
  45. package/dist/chunk-FCIMEU2E.js.map +0 -1
  46. package/dist/chunk-H6YSCGQC.js.map +0 -1
  47. package/dist/chunk-Q5DUDL77.js.map +0 -1
@@ -612,9 +612,15 @@ declare class PalbeCalls {
612
612
  * @param media - Which media tracks to publish (default: audio + video)
613
613
  * @returns A `Call` in `connecting` state; transitions to `active` once the media room connects.
614
614
  *
615
- * @throws BackendError('network', { code: 'call_service_unavailable' }) if the SFU is down (503)
616
- * @throws BackendError('forbidden', { code: 'not_group_member' }) if the caller is not a member (403)
617
- * @throws BackendError('conflict', { code: 'call_room_full' }) if the room is full (409)
615
+ * Every failure below arrives as kind `'server'` this SDK's error kinds are
616
+ * `notConfigured | validation | unauthorized | rateLimited | server | network
617
+ * | decode`, and only a 401, a 429 or a 400 carrying a field-error array gets
618
+ * its own kind. So branch on `err.code`, never on the kind.
619
+ *
620
+ * @throws BackendError('server', { code: 'calling_unavailable' }) no SFU configured on this stack (503)
621
+ * @throws BackendError('server', { code: 'not_group_member' }) the caller is not a member of the group (403)
622
+ * @throws BackendError('server', { code: 'call_room_full' }) the call is at its participant limit (409)
623
+ * @throws BackendError('server', { code: 'call_provider_error' }) the media provider failed upstream (502)
618
624
  */
619
625
  start(groupId: string, { media }?: {
620
626
  media?: Array<'audio' | 'video'>;
@@ -623,7 +629,11 @@ declare class PalbeCalls {
623
629
  * Accept an incoming call in a messaging group.
624
630
  *
625
631
  * @param groupId - Messaging group display-id
626
- * @param callId - Call identifier (from the push notification payload)
632
+ * @param callId - Call identifier, carried by the `call_invite` the server
633
+ * fans onto the group's conversation topic
634
+ * (`pb.realtime.channel('messaging:conv:<rfcGroupId>').on('call_invite', …)`).
635
+ * This SDK ships no web push: on the web the invite arrives over realtime or
636
+ * over whatever signalling the app already runs.
627
637
  * @param media - Which media tracks to publish (default: audio + video)
628
638
  */
629
639
  accept(groupId: string, callId: string, { media }?: {
@@ -1579,6 +1589,19 @@ interface RealtimeStatus {
1579
1589
  readonly lastEventAt: Date | null;
1580
1590
  onChange(callback: (status: RealtimeStatusSnapshot) => void): Unsubscribe;
1581
1591
  }
1592
+ /** Why the server refused a channel. */
1593
+ interface RealtimeChannelError {
1594
+ /** The channel this is about. */
1595
+ topic: string;
1596
+ /** Exactly what the server said: `"unauthorized"` or `"unavailable"`. */
1597
+ reason: string;
1598
+ /**
1599
+ * Whether asking again could succeed. `unavailable` (the project could not be
1600
+ * reached to decide) is retryable and the socket retries it on reconnect;
1601
+ * `unauthorized` is a settled answer and is not.
1602
+ */
1603
+ retryable: boolean;
1604
+ }
1582
1605
  /**
1583
1606
  * A subscription handle for one realtime channel (returned by
1584
1607
  * `pb.realtime.channel(name)` — the SAME instance per name). A channel is
@@ -1595,12 +1618,62 @@ declare class RealtimeChannel {
1595
1618
  * subscription and `cancel()` it to stop.
1596
1619
  */
1597
1620
  on(event: string, handler: RealtimeHandler): RealtimeSubscription;
1621
+ /**
1622
+ * Called when the server REFUSES this channel.
1623
+ *
1624
+ * A join is an authorization decision, and a decision the app never hears is
1625
+ * the same as no decision at all: silence looks exactly like a slow network.
1626
+ * This is where a refusal arrives, carrying the server's own distinction —
1627
+ * `retryable: false` means "you may not" and asking again will not help;
1628
+ * `retryable: true` means the project could not be asked, and the socket's
1629
+ * next reconnect retries on its own.
1630
+ *
1631
+ * pb.realtime.channel('room:42').onError((err) => {
1632
+ * if (!err.retryable) showJoinDenied()
1633
+ * })
1634
+ */
1635
+ onError(handler: (error: RealtimeChannelError) => void): RealtimeSubscription;
1598
1636
  /**
1599
1637
  * Broadcast `payload` to the channel's other subscribers (web addition —
1600
1638
  * iOS is receive-only). Joins the channel if it isn't already; queued
1601
1639
  * until the join is live on the socket.
1602
1640
  */
1603
1641
  send(event: string, payload?: RealtimePayload): void;
1642
+ /**
1643
+ * Watch one key of the channel's shared state.
1644
+ *
1645
+ * The handler is called with the CURRENT value straight away when there is
1646
+ * one, and again on every change. That is the difference from `on()`: a
1647
+ * subscriber to an event learns nothing until the next one, while state is
1648
+ * already there when you ask.
1649
+ */
1650
+ onState(key: string, handler: (value: unknown) => void): RealtimeSubscription;
1651
+ /** Read one key without subscribing. */
1652
+ stateValue(key: string): unknown;
1653
+ /** Write one key. `durable` outlives this connection; `ephemeral` is reaped
1654
+ * when it closes, which is what presence is built on. */
1655
+ setState(key: string, value: unknown, opts?: {
1656
+ life?: 'ephemeral' | 'durable';
1657
+ }): void;
1658
+ /** Remove a key this connection wrote. */
1659
+ clearState(key: string): void;
1660
+ /**
1661
+ * Announce this client on the channel. `meta` is whatever other participants
1662
+ * should see — a name, an avatar, a cursor.
1663
+ *
1664
+ * It is ephemeral state keyed by this connection, so it disappears when the
1665
+ * socket does. Nothing has to be un-announced on a crash.
1666
+ */
1667
+ presenceEnter(meta: RealtimePayload): void;
1668
+ /** Replace the announced value — a cursor moving is this, at whatever rate
1669
+ * the app likes: the server conflates writes it cannot deliver fast enough. */
1670
+ presenceUpdate(meta: RealtimePayload): void;
1671
+ /** Leave early. Closing the socket does this on its own. */
1672
+ presenceLeave(): void;
1673
+ /** Everyone else currently announced, this client excluded. */
1674
+ presenceOthers(): unknown[];
1675
+ /** Watch the announced set. Called on every change. */
1676
+ onPresence(handler: (others: unknown[]) => void): RealtimeSubscription;
1604
1677
  }
1605
1678
  declare class PalbeRealtime {
1606
1679
  private readonly rt;
@@ -1608,7 +1681,13 @@ declare class PalbeRealtime {
1608
1681
  private readonly channels;
1609
1682
  private readonly statusStore;
1610
1683
  private readonly handlers;
1684
+ private readonly errorHandlers;
1611
1685
  private readonly topicRefcount;
1686
+ private readonly states;
1687
+ private readonly connIds;
1688
+ private readonly pendingPresence;
1689
+ private readonly stateWatchers;
1690
+ private readonly presenceWatchers;
1612
1691
  constructor(rt: PalbeRuntime);
1613
1692
  /**
1614
1693
  * Get the handle for an app-defined channel (e.g. "room:42"). The same
@@ -1624,16 +1703,52 @@ declare class PalbeRealtime {
1624
1703
  destroy(): void;
1625
1704
  /** The observable connection status. Safe to read anywhere (reports `idle` until a socket exists). */
1626
1705
  get status(): RealtimeStatus;
1706
+ /**
1707
+ * Hand the socket a fresher credential for the SAME user, without rejoining.
1708
+ *
1709
+ * Called when the app's token rotates. The alternative — letting the channels
1710
+ * die at expiry and rejoining — reaps this connection's ephemeral entries
1711
+ * (its presence, on every channel) and forces a full state resync. A token
1712
+ * getting older is not a disconnection and must not cost one.
1713
+ *
1714
+ * A no-op before a socket exists: the first connect resolves a fresh token
1715
+ * on its own.
1716
+ */
1717
+ refreshToken(token: string): void;
1627
1718
  /** Lazily build + start the shared socket on first subscription/send. */
1628
1719
  private ensureSocket;
1629
1720
  private route;
1630
1721
  /**
1631
- * A channel died for good (token-expiry rejoin exhausted N attempts). Drop its
1632
- * handlers + refcount so the app stops expecting events on it, and flip the
1633
- * status observable to `'error'` (the React `useChannel` hook reports this).
1634
- * A later `.on()` for the same topic re-joins from scratch (refcount 1).
1722
+ /**
1723
+ * The server refused a channel. Tell whoever asked, and when the answer is
1724
+ * final stop pretending the app is subscribed to it.
1725
+ *
1726
+ * `unavailable` leaves everything in place: the project could not be reached
1727
+ * to decide, the socket rejoins on its next reconnect, and dropping the app's
1728
+ * handlers would make a momentary outage look like a permanent denial.
1729
+ */
1730
+ private handleChannelRefused;
1731
+ /**
1732
+ * A channel died for good — a token-expiry rejoin that exhausted its attempts.
1733
+ * The app must stop expecting anything on it, and the status observable flips
1734
+ * to `'error'` (the React `useChannel` hook reports that).
1735
+ *
1736
+ * It clears the SAME registries a final refusal clears, and used to clear only
1737
+ * the event handlers. A state or presence watcher left behind here waits
1738
+ * forever for a snapshot that cannot arrive, and fires twice if the app ever
1739
+ * re-subscribes — the identical failure its sibling three functions up
1740
+ * describes, on a channel that was given up on rather than refused.
1635
1741
  */
1636
1742
  private handleChannelError;
1743
+ /** Drop every registration this topic holds. One place, because "which
1744
+ * registries" is a question two callers must never answer differently. */
1745
+ private forgetTopic;
1746
+ private retainTopic;
1747
+ private releaseTopic;
1748
+ private applySnapshot;
1749
+ private applyDiff;
1750
+ private applyJoinInfo;
1751
+ private notifyState;
1637
1752
  }
1638
1753
 
1639
1754
  interface PalbeRuntime {
@@ -1758,10 +1873,16 @@ declare class PalbeAuth {
1758
1873
  * Self-service account erasure ("right to be forgotten"). Calls palauth
1759
1874
  * `DELETE /auth/user` (session-authed + fresh re-auth): password users pass
1760
1875
  * `{ password }`; passwordless/OAuth users rely on a freshly stepped-up
1761
- * session. On 202 the erasure workflow is durably queued AND every session is
1762
- * already revoked server-side — so we tear down local state (mirroring
1763
- * `signOut()`'s clear, minus the pointless `/logout`) and emit
1764
- * `signedOut{reason:'accountDeleted'}`.
1876
+ * session. The handler answers **204 with no body** the erasure has already
1877
+ * finished and every session is already revoked server-side — so we tear down
1878
+ * local state (mirroring `signOut()`'s clear, minus the pointless `/logout`)
1879
+ * and emit `signedOut{reason:'accountDeleted'}`.
1880
+ *
1881
+ * Returns nothing, because there is nothing to return: the handler used to
1882
+ * answer 202 with an `{erasure_id}` naming an asynchronous run, and that id
1883
+ * was removed along with the run (`gdpr_handlers.go:221-229`). Reading it
1884
+ * here threw a `TypeError` AFTER the account was already gone — a success
1885
+ * reported as a failure.
1765
1886
  *
1766
1887
  * On 401 `reauth_required` / 503 `not_configured` (or `erasure_unavailable`)
1767
1888
  * the request throws BEFORE the teardown line, so the local session is left
@@ -1769,9 +1890,7 @@ declare class PalbeAuth {
1769
1890
  */
1770
1891
  deleteAccount(params?: {
1771
1892
  password?: string;
1772
- }): Promise<{
1773
- erasureId: string;
1774
- }>;
1893
+ }): Promise<void>;
1775
1894
  getUser(): Promise<AuthUser>;
1776
1895
  /**
1777
1896
  * Which credentials this account actually has.
@@ -2014,4 +2133,4 @@ declare class PalbeAnalytics {
2014
2133
  private post;
2015
2134
  }
2016
2135
 
2017
- export { type AuthUser as A, PalbeMessaging as B, Chat as C, type PalbeOAuthConfig as D, PalbeRealtime as E, type FlagsView as F, type PresenceState as G, RealtimeChannel as H, type RealtimeHandler as I, type RealtimeStatus as J, type RealtimeStatusSnapshot as K, type RealtimeSubscription as L, type MagicLinkResult as M, type NotifyScope as N, type OAuthExchangeResult as O, type PalbeConfig as P, type ResolvedMention as Q, type RealtimeConnectionState as R, type ResolvedReply as S, type SentReceipt as T, type SignInMethods as U, type UnreadView as V, type Unsubscribe as W, PalbePerf as X, type RealtimePayload as a, type ChatMember as b, type ChatMessage as c, type PalbeRuntime as d, buildRuntime as e, type AnalyticsProperties as f, type AuthChangeEvent as g, type AuthState as h, type AuthSuccess as i, Call as j, type CallChangeCallback as k, type CallParticipant as l, type CallState as m, type ChatBackend as n, type ChatDraft as o, type ChatKind as p, type ChatMessageKind as q, type ChatRole as r, type ChatState as s, type CommsPrefs as t, type MentionRange as u, type MessageDirection as v, PalbeAnalytics as w, PalbeAuth as x, PalbeCalls as y, PalbeFlags as z };
2136
+ export { type AuthUser as A, PalbeMessaging as B, Chat as C, type PalbeOAuthConfig as D, PalbeRealtime as E, type FlagsView as F, type PresenceState as G, RealtimeChannel as H, type RealtimeChannelError as I, type RealtimeHandler as J, type RealtimeStatus as K, type RealtimeStatusSnapshot as L, type MagicLinkResult as M, type NotifyScope as N, type OAuthExchangeResult as O, type PalbeConfig as P, type RealtimeSubscription as Q, type RealtimeConnectionState as R, type ResolvedMention as S, type ResolvedReply as T, type SentReceipt as U, type SignInMethods as V, type UnreadView as W, type Unsubscribe as X, PalbePerf as Y, type RealtimePayload as a, type ChatMember as b, type ChatMessage as c, type PalbeRuntime as d, buildRuntime as e, type AnalyticsProperties as f, type AuthChangeEvent as g, type AuthState as h, type AuthSuccess as i, Call as j, type CallChangeCallback as k, type CallParticipant as l, type CallState as m, type ChatBackend as n, type ChatDraft as o, type ChatKind as p, type ChatMessageKind as q, type ChatRole as r, type ChatState as s, type CommsPrefs as t, type MentionRange as u, type MessageDirection as v, PalbeAnalytics as w, PalbeAuth as x, PalbeCalls as y, PalbeFlags as z };
@@ -612,9 +612,15 @@ declare class PalbeCalls {
612
612
  * @param media - Which media tracks to publish (default: audio + video)
613
613
  * @returns A `Call` in `connecting` state; transitions to `active` once the media room connects.
614
614
  *
615
- * @throws BackendError('network', { code: 'call_service_unavailable' }) if the SFU is down (503)
616
- * @throws BackendError('forbidden', { code: 'not_group_member' }) if the caller is not a member (403)
617
- * @throws BackendError('conflict', { code: 'call_room_full' }) if the room is full (409)
615
+ * Every failure below arrives as kind `'server'` this SDK's error kinds are
616
+ * `notConfigured | validation | unauthorized | rateLimited | server | network
617
+ * | decode`, and only a 401, a 429 or a 400 carrying a field-error array gets
618
+ * its own kind. So branch on `err.code`, never on the kind.
619
+ *
620
+ * @throws BackendError('server', { code: 'calling_unavailable' }) no SFU configured on this stack (503)
621
+ * @throws BackendError('server', { code: 'not_group_member' }) the caller is not a member of the group (403)
622
+ * @throws BackendError('server', { code: 'call_room_full' }) the call is at its participant limit (409)
623
+ * @throws BackendError('server', { code: 'call_provider_error' }) the media provider failed upstream (502)
618
624
  */
619
625
  start(groupId: string, { media }?: {
620
626
  media?: Array<'audio' | 'video'>;
@@ -623,7 +629,11 @@ declare class PalbeCalls {
623
629
  * Accept an incoming call in a messaging group.
624
630
  *
625
631
  * @param groupId - Messaging group display-id
626
- * @param callId - Call identifier (from the push notification payload)
632
+ * @param callId - Call identifier, carried by the `call_invite` the server
633
+ * fans onto the group's conversation topic
634
+ * (`pb.realtime.channel('messaging:conv:<rfcGroupId>').on('call_invite', …)`).
635
+ * This SDK ships no web push: on the web the invite arrives over realtime or
636
+ * over whatever signalling the app already runs.
627
637
  * @param media - Which media tracks to publish (default: audio + video)
628
638
  */
629
639
  accept(groupId: string, callId: string, { media }?: {
@@ -1579,6 +1589,19 @@ interface RealtimeStatus {
1579
1589
  readonly lastEventAt: Date | null;
1580
1590
  onChange(callback: (status: RealtimeStatusSnapshot) => void): Unsubscribe;
1581
1591
  }
1592
+ /** Why the server refused a channel. */
1593
+ interface RealtimeChannelError {
1594
+ /** The channel this is about. */
1595
+ topic: string;
1596
+ /** Exactly what the server said: `"unauthorized"` or `"unavailable"`. */
1597
+ reason: string;
1598
+ /**
1599
+ * Whether asking again could succeed. `unavailable` (the project could not be
1600
+ * reached to decide) is retryable and the socket retries it on reconnect;
1601
+ * `unauthorized` is a settled answer and is not.
1602
+ */
1603
+ retryable: boolean;
1604
+ }
1582
1605
  /**
1583
1606
  * A subscription handle for one realtime channel (returned by
1584
1607
  * `pb.realtime.channel(name)` — the SAME instance per name). A channel is
@@ -1595,12 +1618,62 @@ declare class RealtimeChannel {
1595
1618
  * subscription and `cancel()` it to stop.
1596
1619
  */
1597
1620
  on(event: string, handler: RealtimeHandler): RealtimeSubscription;
1621
+ /**
1622
+ * Called when the server REFUSES this channel.
1623
+ *
1624
+ * A join is an authorization decision, and a decision the app never hears is
1625
+ * the same as no decision at all: silence looks exactly like a slow network.
1626
+ * This is where a refusal arrives, carrying the server's own distinction —
1627
+ * `retryable: false` means "you may not" and asking again will not help;
1628
+ * `retryable: true` means the project could not be asked, and the socket's
1629
+ * next reconnect retries on its own.
1630
+ *
1631
+ * pb.realtime.channel('room:42').onError((err) => {
1632
+ * if (!err.retryable) showJoinDenied()
1633
+ * })
1634
+ */
1635
+ onError(handler: (error: RealtimeChannelError) => void): RealtimeSubscription;
1598
1636
  /**
1599
1637
  * Broadcast `payload` to the channel's other subscribers (web addition —
1600
1638
  * iOS is receive-only). Joins the channel if it isn't already; queued
1601
1639
  * until the join is live on the socket.
1602
1640
  */
1603
1641
  send(event: string, payload?: RealtimePayload): void;
1642
+ /**
1643
+ * Watch one key of the channel's shared state.
1644
+ *
1645
+ * The handler is called with the CURRENT value straight away when there is
1646
+ * one, and again on every change. That is the difference from `on()`: a
1647
+ * subscriber to an event learns nothing until the next one, while state is
1648
+ * already there when you ask.
1649
+ */
1650
+ onState(key: string, handler: (value: unknown) => void): RealtimeSubscription;
1651
+ /** Read one key without subscribing. */
1652
+ stateValue(key: string): unknown;
1653
+ /** Write one key. `durable` outlives this connection; `ephemeral` is reaped
1654
+ * when it closes, which is what presence is built on. */
1655
+ setState(key: string, value: unknown, opts?: {
1656
+ life?: 'ephemeral' | 'durable';
1657
+ }): void;
1658
+ /** Remove a key this connection wrote. */
1659
+ clearState(key: string): void;
1660
+ /**
1661
+ * Announce this client on the channel. `meta` is whatever other participants
1662
+ * should see — a name, an avatar, a cursor.
1663
+ *
1664
+ * It is ephemeral state keyed by this connection, so it disappears when the
1665
+ * socket does. Nothing has to be un-announced on a crash.
1666
+ */
1667
+ presenceEnter(meta: RealtimePayload): void;
1668
+ /** Replace the announced value — a cursor moving is this, at whatever rate
1669
+ * the app likes: the server conflates writes it cannot deliver fast enough. */
1670
+ presenceUpdate(meta: RealtimePayload): void;
1671
+ /** Leave early. Closing the socket does this on its own. */
1672
+ presenceLeave(): void;
1673
+ /** Everyone else currently announced, this client excluded. */
1674
+ presenceOthers(): unknown[];
1675
+ /** Watch the announced set. Called on every change. */
1676
+ onPresence(handler: (others: unknown[]) => void): RealtimeSubscription;
1604
1677
  }
1605
1678
  declare class PalbeRealtime {
1606
1679
  private readonly rt;
@@ -1608,7 +1681,13 @@ declare class PalbeRealtime {
1608
1681
  private readonly channels;
1609
1682
  private readonly statusStore;
1610
1683
  private readonly handlers;
1684
+ private readonly errorHandlers;
1611
1685
  private readonly topicRefcount;
1686
+ private readonly states;
1687
+ private readonly connIds;
1688
+ private readonly pendingPresence;
1689
+ private readonly stateWatchers;
1690
+ private readonly presenceWatchers;
1612
1691
  constructor(rt: PalbeRuntime);
1613
1692
  /**
1614
1693
  * Get the handle for an app-defined channel (e.g. "room:42"). The same
@@ -1624,16 +1703,52 @@ declare class PalbeRealtime {
1624
1703
  destroy(): void;
1625
1704
  /** The observable connection status. Safe to read anywhere (reports `idle` until a socket exists). */
1626
1705
  get status(): RealtimeStatus;
1706
+ /**
1707
+ * Hand the socket a fresher credential for the SAME user, without rejoining.
1708
+ *
1709
+ * Called when the app's token rotates. The alternative — letting the channels
1710
+ * die at expiry and rejoining — reaps this connection's ephemeral entries
1711
+ * (its presence, on every channel) and forces a full state resync. A token
1712
+ * getting older is not a disconnection and must not cost one.
1713
+ *
1714
+ * A no-op before a socket exists: the first connect resolves a fresh token
1715
+ * on its own.
1716
+ */
1717
+ refreshToken(token: string): void;
1627
1718
  /** Lazily build + start the shared socket on first subscription/send. */
1628
1719
  private ensureSocket;
1629
1720
  private route;
1630
1721
  /**
1631
- * A channel died for good (token-expiry rejoin exhausted N attempts). Drop its
1632
- * handlers + refcount so the app stops expecting events on it, and flip the
1633
- * status observable to `'error'` (the React `useChannel` hook reports this).
1634
- * A later `.on()` for the same topic re-joins from scratch (refcount 1).
1722
+ /**
1723
+ * The server refused a channel. Tell whoever asked, and when the answer is
1724
+ * final stop pretending the app is subscribed to it.
1725
+ *
1726
+ * `unavailable` leaves everything in place: the project could not be reached
1727
+ * to decide, the socket rejoins on its next reconnect, and dropping the app's
1728
+ * handlers would make a momentary outage look like a permanent denial.
1729
+ */
1730
+ private handleChannelRefused;
1731
+ /**
1732
+ * A channel died for good — a token-expiry rejoin that exhausted its attempts.
1733
+ * The app must stop expecting anything on it, and the status observable flips
1734
+ * to `'error'` (the React `useChannel` hook reports that).
1735
+ *
1736
+ * It clears the SAME registries a final refusal clears, and used to clear only
1737
+ * the event handlers. A state or presence watcher left behind here waits
1738
+ * forever for a snapshot that cannot arrive, and fires twice if the app ever
1739
+ * re-subscribes — the identical failure its sibling three functions up
1740
+ * describes, on a channel that was given up on rather than refused.
1635
1741
  */
1636
1742
  private handleChannelError;
1743
+ /** Drop every registration this topic holds. One place, because "which
1744
+ * registries" is a question two callers must never answer differently. */
1745
+ private forgetTopic;
1746
+ private retainTopic;
1747
+ private releaseTopic;
1748
+ private applySnapshot;
1749
+ private applyDiff;
1750
+ private applyJoinInfo;
1751
+ private notifyState;
1637
1752
  }
1638
1753
 
1639
1754
  interface PalbeRuntime {
@@ -1758,10 +1873,16 @@ declare class PalbeAuth {
1758
1873
  * Self-service account erasure ("right to be forgotten"). Calls palauth
1759
1874
  * `DELETE /auth/user` (session-authed + fresh re-auth): password users pass
1760
1875
  * `{ password }`; passwordless/OAuth users rely on a freshly stepped-up
1761
- * session. On 202 the erasure workflow is durably queued AND every session is
1762
- * already revoked server-side — so we tear down local state (mirroring
1763
- * `signOut()`'s clear, minus the pointless `/logout`) and emit
1764
- * `signedOut{reason:'accountDeleted'}`.
1876
+ * session. The handler answers **204 with no body** the erasure has already
1877
+ * finished and every session is already revoked server-side — so we tear down
1878
+ * local state (mirroring `signOut()`'s clear, minus the pointless `/logout`)
1879
+ * and emit `signedOut{reason:'accountDeleted'}`.
1880
+ *
1881
+ * Returns nothing, because there is nothing to return: the handler used to
1882
+ * answer 202 with an `{erasure_id}` naming an asynchronous run, and that id
1883
+ * was removed along with the run (`gdpr_handlers.go:221-229`). Reading it
1884
+ * here threw a `TypeError` AFTER the account was already gone — a success
1885
+ * reported as a failure.
1765
1886
  *
1766
1887
  * On 401 `reauth_required` / 503 `not_configured` (or `erasure_unavailable`)
1767
1888
  * the request throws BEFORE the teardown line, so the local session is left
@@ -1769,9 +1890,7 @@ declare class PalbeAuth {
1769
1890
  */
1770
1891
  deleteAccount(params?: {
1771
1892
  password?: string;
1772
- }): Promise<{
1773
- erasureId: string;
1774
- }>;
1893
+ }): Promise<void>;
1775
1894
  getUser(): Promise<AuthUser>;
1776
1895
  /**
1777
1896
  * Which credentials this account actually has.
@@ -2014,4 +2133,4 @@ declare class PalbeAnalytics {
2014
2133
  private post;
2015
2134
  }
2016
2135
 
2017
- export { type AuthUser as A, PalbeMessaging as B, Chat as C, type PalbeOAuthConfig as D, PalbeRealtime as E, type FlagsView as F, type PresenceState as G, RealtimeChannel as H, type RealtimeHandler as I, type RealtimeStatus as J, type RealtimeStatusSnapshot as K, type RealtimeSubscription as L, type MagicLinkResult as M, type NotifyScope as N, type OAuthExchangeResult as O, type PalbeConfig as P, type ResolvedMention as Q, type RealtimeConnectionState as R, type ResolvedReply as S, type SentReceipt as T, type SignInMethods as U, type UnreadView as V, type Unsubscribe as W, PalbePerf as X, type RealtimePayload as a, type ChatMember as b, type ChatMessage as c, type PalbeRuntime as d, buildRuntime as e, type AnalyticsProperties as f, type AuthChangeEvent as g, type AuthState as h, type AuthSuccess as i, Call as j, type CallChangeCallback as k, type CallParticipant as l, type CallState as m, type ChatBackend as n, type ChatDraft as o, type ChatKind as p, type ChatMessageKind as q, type ChatRole as r, type ChatState as s, type CommsPrefs as t, type MentionRange as u, type MessageDirection as v, PalbeAnalytics as w, PalbeAuth as x, PalbeCalls as y, PalbeFlags as z };
2136
+ export { type AuthUser as A, PalbeMessaging as B, Chat as C, type PalbeOAuthConfig as D, PalbeRealtime as E, type FlagsView as F, type PresenceState as G, RealtimeChannel as H, type RealtimeChannelError as I, type RealtimeHandler as J, type RealtimeStatus as K, type RealtimeStatusSnapshot as L, type MagicLinkResult as M, type NotifyScope as N, type OAuthExchangeResult as O, type PalbeConfig as P, type RealtimeSubscription as Q, type RealtimeConnectionState as R, type ResolvedMention as S, type ResolvedReply as T, type SentReceipt as U, type SignInMethods as V, type UnreadView as W, type Unsubscribe as X, PalbePerf as Y, type RealtimePayload as a, type ChatMember as b, type ChatMessage as c, type PalbeRuntime as d, buildRuntime as e, type AnalyticsProperties as f, type AuthChangeEvent as g, type AuthState as h, type AuthSuccess as i, Call as j, type CallChangeCallback as k, type CallParticipant as l, type CallState as m, type ChatBackend as n, type ChatDraft as o, type ChatKind as p, type ChatMessageKind as q, type ChatRole as r, type ChatState as s, type CommsPrefs as t, type MentionRange as u, type MessageDirection as v, PalbeAnalytics as w, PalbeAuth as x, PalbeCalls as y, PalbeFlags as z };
@@ -144,6 +144,15 @@ function parseEnvironmentRef(apiKey) {
144
144
  var MAX_RETRIES = 3;
145
145
  var INITIAL_BACKOFF_MS = 200;
146
146
  var MAX_RETRY_DELAY_MS = 1e4;
147
+ function withRetryHint(body, response) {
148
+ if (response.status !== 429) return body;
149
+ const data = body?.data;
150
+ const alreadyStated = typeof body?.retry_after === "number" || typeof data === "object" && data !== null && "retryAfter" in data;
151
+ if (alreadyStated) return body;
152
+ const seconds = Number.parseInt(response.headers.get("Retry-After") ?? "", 10);
153
+ if (Number.isNaN(seconds) || seconds <= 0) return body;
154
+ return { ...body, retry_after: seconds };
155
+ }
147
156
  var HttpClient = class _HttpClient {
148
157
  apiKey;
149
158
  options;
@@ -319,7 +328,7 @@ var HttpClient = class _HttpClient {
319
328
  errorBody?.error ?? "unknown_error",
320
329
  errorBody?.error_description ?? response.statusText,
321
330
  response.status,
322
- errorBody
331
+ withRetryHint(errorBody, response)
323
332
  ),
324
333
  status: response.status
325
334
  };
@@ -442,6 +451,15 @@ function isFieldErrorArray(value) {
442
451
  (v) => typeof v === "object" && v !== null && typeof v.field === "string" && typeof v.message === "string"
443
452
  );
444
453
  }
454
+ function fieldErrorsOf(envelope) {
455
+ const scoped = pickField(pickField(envelope, "data"), "fields");
456
+ if (isFieldErrorArray(scoped)) return scoped;
457
+ const flat = pickField(envelope, "fields");
458
+ return isFieldErrorArray(flat) ? flat : void 0;
459
+ }
460
+ function retryAfterOf(envelope) {
461
+ return pickNumber(envelope, "retry_after") ?? pickNumber(pickField(envelope, "data"), "retryAfter");
462
+ }
445
463
  function pickField(value, key) {
446
464
  if (typeof value === "object" && value !== null) {
447
465
  return value[key];
@@ -467,20 +485,15 @@ function fromPalbaseError(err) {
467
485
  if (err.code === "network_error") return new BackendError("network", base);
468
486
  if (err.status === 401) return new BackendError("unauthorized", base);
469
487
  if (err.status === 429)
470
- return new BackendError("rateLimited", {
471
- ...base,
472
- retryAfter: pickNumber(err.details, "retry_after")
473
- });
474
- const nested = pickField(err.details, "details");
475
- if (err.status === 400 && isFieldErrorArray(nested))
476
- return new BackendError("validation", { ...base, fields: nested });
488
+ return new BackendError("rateLimited", { ...base, retryAfter: retryAfterOf(err.details) });
489
+ const fields = fieldErrorsOf(err.details);
490
+ if (err.status === 400 && fields) return new BackendError("validation", { ...base, fields });
477
491
  return new BackendError("server", base);
478
492
  }
479
493
  function fromEnvelope(status, body) {
480
494
  const code = pickString(body, "error") ?? "http_error";
481
495
  const message = pickString(body, "error_description") ?? `HTTP ${status}`;
482
496
  const requestId = pickString(body, "request_id");
483
- const details = pickField(body, "details");
484
497
  const params = {
485
498
  code,
486
499
  message,
@@ -490,13 +503,9 @@ function fromEnvelope(status, body) {
490
503
  };
491
504
  if (status === 401) return new BackendError("unauthorized", params);
492
505
  if (status === 429)
493
- return new BackendError("rateLimited", {
494
- ...params,
495
- // Real 429 wire body has TOP-LEVEL retry_after; nested details is a fallback.
496
- retryAfter: pickNumber(body, "retry_after") ?? pickNumber(details, "retry_after")
497
- });
498
- if (status === 400 && isFieldErrorArray(details))
499
- return new BackendError("validation", { ...params, fields: details });
506
+ return new BackendError("rateLimited", { ...params, retryAfter: retryAfterOf(body) });
507
+ const fields = fieldErrorsOf(body);
508
+ if (status === 400 && fields) return new BackendError("validation", { ...params, fields });
500
509
  return new BackendError("server", params);
501
510
  }
502
511
  function isBackendError(e) {
@@ -525,4 +534,4 @@ export {
525
534
  asPalbaseError,
526
535
  unwrap
527
536
  };
528
- //# sourceMappingURL=chunk-EAOROUSW.js.map
537
+ //# sourceMappingURL=chunk-4WMJTUDN.js.map