@palbase/web 1.6.2 → 1.8.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.
@@ -622,6 +622,16 @@ interface PalbeConfig {
622
622
  apiKey: string;
623
623
  /** Informational — url+apiKey are already branch-specific (endpointRef embeds the branch slug); gen bakes all three consistently. */
624
624
  branch?: string;
625
+ /**
626
+ * F2 app-registration: the registered app `identifier` from
627
+ * `palbase-config.json` — for web this is the declared ORIGIN (e.g.
628
+ * `https://app.example.com`). When non-empty, `configure` REFUSES to build the
629
+ * runtime if `window.location.origin` does not match (SDK-REFUSES), and the
630
+ * value rides on `X-Palbase-Bundle` on EVERY request so the Kong gate can match
631
+ * it (the IDENTICAL header + value the iOS SDK sends). Empty/absent → legacy /
632
+ * tooling: no guard, no header.
633
+ */
634
+ identifier?: string;
625
635
  oauth?: PalbeOAuthConfig;
626
636
  /** Refresh-token persistence. Default: endpoint-scoped localStorage in browsers, memory elsewhere. */
627
637
  storage?: SessionStorageAdapter;
@@ -825,6 +835,49 @@ interface ChatMessage {
825
835
  * Mirrors iOS `ChatMessage.effectiveExpiry` (which it derives from).
826
836
  */
827
837
  readonly expiresAt: Date | null;
838
+ /**
839
+ * Server-metadata cluster — the double-tick DELIVERED watermark on THIS user's OWN
840
+ * (`direction === 'outgoing'`) messages, FOLDED TO USER-LEVEL. The wire carries the
841
+ * acking device_id; the SDK folds across a peer's devices and across peers to ONE
842
+ * user-level server_seq (MONOTONIC max — a stale lower incoming tick NEVER lowers it).
843
+ * A per-message tick is "is `message.serverSeq <= deliveredUpTo`?". Absent (`undefined`)
844
+ * until a `delivered` conv event covers this message; never set on an incoming message.
845
+ * Mirrors iOS `Chat.deliveredUpTo` (a chat-level scalar there; per-message here).
846
+ */
847
+ readonly deliveredUpTo?: number;
848
+ /**
849
+ * Server-metadata cluster — the double-tick READ watermark on THIS user's OWN
850
+ * (`direction === 'outgoing'`) messages, FOLDED TO USER-LEVEL + MONOTONIC max (same
851
+ * fold as `deliveredUpTo`). Set by a `read` conv event. Read STAYS gated by the peer's
852
+ * `share_receipts` server-side (the broadcast simply never arrives when muted); the
853
+ * client fold is identical to delivered. Mirrors iOS `Chat.readUpTo`.
854
+ */
855
+ readonly readUpTo?: number;
856
+ }
857
+ /**
858
+ * The per-(user, group) notify scope (the server-metadata MUTE toggle). `'all'`
859
+ * (default, absent row) fires the push wake; `'none'` (muted) suppresses it. The raw
860
+ * wire value is the snake-free `"all"`/`"none"` string. An unknown/garbage server value
861
+ * maps to `'all'` at the surface — fail-OPEN, never silently mutes (mirrors iOS
862
+ * `NotifyScope`). The server stores this opaque enum and stays MLS-blind (no content).
863
+ */
864
+ type NotifyScope = 'all' | 'none';
865
+ /**
866
+ * The caller's OWN unread view (the `GET /v1/messaging/groups/{gid}/unread` response).
867
+ * Every field is an OPAQUE server-metadata integer (no message content): the group's max
868
+ * server_seq, this caller's read + delivered cursors, and the server-derived unread
869
+ * count. The camelCase mirror of the snake_case wire (`group_max_seq` → `groupMaxSeq`,
870
+ * etc). Mirrors iOS `UnreadView`.
871
+ */
872
+ interface UnreadView {
873
+ /** The highest server_seq the group has reached (the newest message's seq). */
874
+ readonly groupMaxSeq: number;
875
+ /** The furthest server_seq THIS caller has marked read. */
876
+ readonly lastReadSeq: number;
877
+ /** The furthest server_seq THIS caller's devices have acked delivered. */
878
+ readonly deliveredSeq: number;
879
+ /** The server-derived unread count for this caller (clamped `>= 0` at the surface). */
880
+ readonly unreadCount: number;
828
881
  }
829
882
  /** The receipt from a send — the server's monotonic sequence + accepted epoch. */
830
883
  interface SentReceipt {
@@ -1001,7 +1054,12 @@ interface ChatBackend {
1001
1054
  registerActive(group: MessagingGroup): void;
1002
1055
  setTyping(group: MessagingGroup, isTyping: boolean): void;
1003
1056
  markRead(group: MessagingGroup, upToServerSeq: number): Promise<void>;
1004
- /** Subscribe live message + conv (presence/typing/read) for an active group. */
1057
+ /** Server-metadata cluster: set/get the per-(user,group) notify scope (mute) and
1058
+ * fetch the caller's own unread view. The server stays MLS-blind (opaque metadata). */
1059
+ setNotifyScope(group: MessagingGroup, scope: NotifyScope): Promise<NotifyScope>;
1060
+ getNotifyScope(group: MessagingGroup): Promise<NotifyScope>;
1061
+ unread(group: MessagingGroup): Promise<UnreadView>;
1062
+ /** Subscribe live message + conv (presence/typing/read/delivered) for an active group. */
1005
1063
  subscribeLive(group: MessagingGroup, chat: Chat): Unsubscribe$1;
1006
1064
  /** Resolve a sender device id → its owning user id (for senderUserId). */
1007
1065
  userIdForDevice(group: MessagingGroup, deviceId: string): Promise<string | null>;
@@ -1306,6 +1364,20 @@ declare class Chat {
1306
1364
  leave(): Promise<void>;
1307
1365
  setTyping(isTyping: boolean): void;
1308
1366
  markRead(message: ChatMessage): Promise<void>;
1367
+ /** Set this chat's notify scope (the mute toggle). `'none'` mutes the push wake;
1368
+ * `'all'` unmutes (the default). Materializes a draft first (the scope is a
1369
+ * per-(user,group) server row), PUTs `/notify`, and returns the server-echoed scope.
1370
+ * Mirrors iOS `Chat.setNotifyScope`. */
1371
+ setNotifyScope(scope: NotifyScope): Promise<NotifyScope>;
1372
+ /** Refresh + return this chat's notify scope from the server (fail-OPEN to `'all'`).
1373
+ * Returns `'all'` for a draft chat (no server row yet). */
1374
+ getNotifyScope(): Promise<NotifyScope>;
1375
+ /** Fetch the caller's authoritative SERVER unread count (opaque server metadata).
1376
+ * Returns the clamped count (`max(0, …)`); `0` for a draft chat. The local computed
1377
+ * `unreadCount` getter stays the instant, offline best-effort badge — this is the
1378
+ * canonical count on demand. Named distinctly so it does not shadow the observable
1379
+ * `unreadCount` snapshot getter. Mirrors iOS `Chat.refreshUnread`. */
1380
+ unreadCountFromServer(): Promise<number>;
1309
1381
  /** Add an emoji reaction to a message. No-op if the message isn't reactable
1310
1382
  * (empty clientMsgId — a legacy/system row). The reaction folds locally with
1311
1383
  * the server receipt's `(epoch, serverSeq)` so the target's tally updates
@@ -1374,6 +1446,153 @@ declare class PalbeMessaging {
1374
1446
  onChatsChange(cb: () => void): Unsubscribe$1;
1375
1447
  }
1376
1448
 
1449
+ /** The Faz-1 trace kinds plus the Faz-1.5 web-vital family. */
1450
+ type PerfTraceType = 'network' | 'app_start' | 'custom' | 'web_vital';
1451
+ /**
1452
+ * One perf measurement, shaped EXACTLY as the ingest wire expects. Keys are
1453
+ * snake_case to match the Go decode tags — do not rename to camelCase.
1454
+ */
1455
+ interface PerfItem {
1456
+ /** Client-generated UUIDv7 — server dedup key. */
1457
+ row_id: string;
1458
+ trace_type: PerfTraceType;
1459
+ name: string;
1460
+ /** Duration in milliseconds. */
1461
+ value: number;
1462
+ counters?: Record<string, number>;
1463
+ attrs?: Record<string, string>;
1464
+ request_id?: string;
1465
+ /** Unix milliseconds. */
1466
+ timestamp: number;
1467
+ }
1468
+
1469
+ /**
1470
+ * Bounded FIFO perf-item queue. `enqueue` appends and evicts the oldest on
1471
+ * overflow; `drainAll` returns everything (oldest-first) and clears the store;
1472
+ * `count` is the current depth; `dropped` is the cumulative eviction counter.
1473
+ *
1474
+ * When `localStorage` is present the queue is read-through / write-through to a
1475
+ * fixed key, so a fresh instance after a reload sees the persisted items. With
1476
+ * no `localStorage` it is purely in-memory.
1477
+ */
1478
+ declare class PerfOfflineQueue {
1479
+ private items;
1480
+ private _dropped;
1481
+ private readonly maxItems;
1482
+ constructor(maxItems?: number);
1483
+ /** Append items; FIFO-evict the oldest when over `maxItems`. */
1484
+ enqueue(items: PerfItem[]): void;
1485
+ /** Return all queued items (oldest-first) and clear the queue + store. */
1486
+ drainAll(): PerfItem[];
1487
+ /** Current queue depth. */
1488
+ get count(): number;
1489
+ /** Cumulative count of FIFO-evicted items (a `dropped` metric, not silent). */
1490
+ get dropped(): number;
1491
+ /** Drop the oldest items until at most `maxItems` remain, counting each. */
1492
+ private trim;
1493
+ private persist;
1494
+ private clearStore;
1495
+ }
1496
+
1497
+ /**
1498
+ * The mutable PalPerf state shared between the facade and the network hook.
1499
+ * `testDevice` is signalled to the server via the `X-Palbase-Test-Device: 1`
1500
+ * request header (server-authoritative tagging — the SDK only flips the bit).
1501
+ */
1502
+ declare class PerfState {
1503
+ /** Pending, un-flushed perf items (FIFO). */
1504
+ readonly buffer: PerfItem[];
1505
+ /** When true, every flush carries `X-Palbase-Test-Device: 1`. */
1506
+ testDevice: boolean;
1507
+ enqueue(item: PerfItem): void;
1508
+ /** Detach up to `MAX_PERF_BATCH` items for one POST (FIFO order preserved). */
1509
+ take(limit?: number): PerfItem[];
1510
+ get size(): number;
1511
+ }
1512
+
1513
+ /**
1514
+ * A running custom trace. `putAttribute`/`incrementMetric` accumulate metadata;
1515
+ * `stop()` records ONE `custom` PerfItem whose `value` is the elapsed
1516
+ * milliseconds since `startTrace`. Idempotent: a second `stop()` is a no-op.
1517
+ */
1518
+ declare class PerfTrace {
1519
+ private readonly name;
1520
+ private readonly onStop;
1521
+ private readonly attrs;
1522
+ private readonly counters;
1523
+ private readonly startedAt;
1524
+ private stopped;
1525
+ constructor(name: string, onStop: (item: PerfItem) => void);
1526
+ putAttribute(key: string, value: string): void;
1527
+ incrementMetric(name: string, by?: number): void;
1528
+ stop(): void;
1529
+ }
1530
+ declare class PalbePerf {
1531
+ private readonly rt;
1532
+ private readonly state;
1533
+ /** Durable offline buffer for failed flushes (persist-on-fail). Browser →
1534
+ * localStorage-backed; server → in-memory. */
1535
+ private readonly queue;
1536
+ private flushTimer;
1537
+ /** Browser → buffer + size/timer flush. Server (no document) → immediate
1538
+ * per-item flush, zero timers (nothing leaks into RSC/route handlers). */
1539
+ private readonly browser;
1540
+ /** Bound `online` handler so it can be removed on `dispose()` (no leak). */
1541
+ private readonly onOnline;
1542
+ /** Server-controlled client-side sample rate (0..100). 100 until the config
1543
+ * client fetches `/v1/analytics/perf/config` — the SDK obeys this ceiling and
1544
+ * never raises its own rate (invariant 3). `record` drops an item whose
1545
+ * deterministic `row_id` bucket is >= this pct, mirroring the server's
1546
+ * `SampleDecision` so client + server keep the SAME rows. */
1547
+ private samplePct;
1548
+ constructor(rt: PalbeRuntime, state?: PerfState,
1549
+ /** Durable offline buffer for failed flushes (persist-on-fail). Browser →
1550
+ * localStorage-backed; server → in-memory. */
1551
+ queue?: PerfOfflineQueue);
1552
+ /** Remove the `online` listener. Called when the runtime is replaced so the
1553
+ * handler does not outlive this facade. No-op outside the browser. */
1554
+ dispose(): void;
1555
+ /** Mark (or unmark) this client's traffic as test — the server tags the rows
1556
+ * when the `X-Palbase-Test-Device: 1` header rides along on flush. */
1557
+ setTestDevice(on: boolean): void;
1558
+ /** Apply the server-resolved client-side sample rate (0..100), clamped. Called
1559
+ * by `PerfConfigClient` after fetching `/v1/analytics/perf/config`. The SDK
1560
+ * OBEYS this value — it is a ceiling, never raised locally. */
1561
+ setSamplePct(pct: number): void;
1562
+ /** Pending (un-flushed) buffer depth. Test-only visibility into the buffer so
1563
+ * the remote-sampling tests can assert how many items were sampled in. */
1564
+ get bufferSizeForTest(): number;
1565
+ /** Opt-in: wrap the global `fetch` so every app-level request is recorded as a
1566
+ * redacted `network` perf item (the `/v1/analytics/*` ingest paths are
1567
+ * self-excluded). OFF by default — swizzling a global is a page-wide side
1568
+ * effect. Returns an `uninstall` that restores the original `fetch`. */
1569
+ enableFetchCapture(): () => void;
1570
+ /** Start a custom trace; the returned handle records a `custom` item on
1571
+ * `.stop()`. */
1572
+ startTrace(name: string): PerfTrace;
1573
+ /** Buffer one perf item. In the browser, flush on size/timer; on the server
1574
+ * flush immediately (no timers). Never throws.
1575
+ *
1576
+ * Client-side remote sampling: an item whose deterministic `row_id` bucket is
1577
+ * NOT below the server-controlled `samplePct` is dropped before buffering —
1578
+ * the SAME FNV-1a-mod-100 decision the server applies at ingest, so the two
1579
+ * keep identical rows and the SDK saves the upload (defense-in-depth: the
1580
+ * server re-samples authoritatively). */
1581
+ record(item: PerfItem): void;
1582
+ /** Buffer a network trace — called by `request.ts` around `rt.http.request`
1583
+ * (the analytics ingest path is excluded by the caller to avoid recursion). */
1584
+ recordNetwork(method: string, url: string, status: number, durationMs: number, requestId?: string): void;
1585
+ /** Drain the offline queue (oldest-first) and the live buffer to
1586
+ * `/v1/analytics/perf` (≤100 items per request, sequential). Resolves when
1587
+ * delivery finished; NEVER rejects. A failed slice is NOT dropped — it goes
1588
+ * to the offline queue (persist-on-fail) so the next flush (size/timer or the
1589
+ * `online` reconnect event) retries it. Items keep their original `row_id`,
1590
+ * so a redelivery dedups server-side (ReplacingMergeTree). */
1591
+ flush(): Promise<void>;
1592
+ private startTimer;
1593
+ private cancelTimer;
1594
+ }
1595
+
1377
1596
  /**
1378
1597
  * The connection state of the shared realtime WebSocket (iOS enum parity), plus
1379
1598
  * `'error'` — surfaced when a channel could not be recovered after a
@@ -1471,6 +1690,14 @@ declare class PalbeRealtime {
1471
1690
 
1472
1691
  interface PalbeRuntime {
1473
1692
  config: PalbeConfig;
1693
+ /**
1694
+ * F2 app-registration: the registered app `identifier` (web ORIGIN) stashed at
1695
+ * configure. Rides on `X-Palbase-Bundle` on EVERY request (request.ts) so the
1696
+ * Kong gate can match it. Empty string when the binding is unconfigured/tooling
1697
+ * (no header is sent). Origin-match is enforced in `buildRuntime` BEFORE this
1698
+ * runtime is returned — a mismatch throws there and no runtime is built.
1699
+ */
1700
+ appIdentifier: string;
1474
1701
  http: HttpClient$1;
1475
1702
  tokenManager: TokenManager$1;
1476
1703
  authClient: AuthClient;
@@ -1480,6 +1707,13 @@ interface PalbeRuntime {
1480
1707
  analytics: PalbeAnalytics;
1481
1708
  calls: PalbeCalls;
1482
1709
  messaging: PalbeMessaging;
1710
+ /**
1711
+ * PalPerf surface. EAGER (like analytics identity): `request.ts` records a
1712
+ * `network` trace around EVERY SDK fetch, so the facade must exist from the
1713
+ * first request — never lazy. The buffer/timer it owns is inert until the
1714
+ * first item is recorded.
1715
+ */
1716
+ perf: PalbePerf;
1483
1717
  storage: SessionStorageAdapter;
1484
1718
  /**
1485
1719
  * Destroy the realtime facade if it was already constructed (no-op otherwise).
@@ -1731,4 +1965,4 @@ declare class PalbeAnalytics {
1731
1965
  private post;
1732
1966
  }
1733
1967
 
1734
- export { type AnalyticsProperties as A, type PresenceState as B, Call as C, type RealtimeConnectionState as D, type RealtimeHandler as E, type FlagsView as F, type RealtimePayload as G, type RealtimeStatus as H, type RealtimeStatusSnapshot as I, type RealtimeSubscription as J, type ResolvedMention as K, type ResolvedReply as L, type MagicLinkResult as M, type PalbeRuntime as N, type OAuthExchangeResult as O, PalbeAnalytics as P, buildRuntime as Q, RealtimeChannel as R, type SentReceipt as S, type Unsubscribe as U, type AuthChangeEvent as a, type AuthState as b, type AuthSuccess as c, type AuthUser as d, type CallChangeCallback as e, type CallParticipant as f, type CallState as g, Chat as h, type ChatBackend as i, type ChatDraft as j, type ChatKind as k, type ChatMember as l, type ChatMessage as m, type ChatMessageKind as n, type ChatRole as o, type ChatState as p, type CommsPrefs as q, type MentionRange as r, type MessageDirection as s, PalbeAuth as t, PalbeCalls as u, type PalbeConfig as v, PalbeFlags as w, PalbeMessaging as x, type PalbeOAuthConfig as y, PalbeRealtime as z };
1968
+ export { type AnalyticsProperties as A, type PresenceState as B, Call as C, type RealtimeConnectionState as D, type RealtimeHandler as E, type FlagsView as F, type RealtimePayload as G, type RealtimeStatus as H, type RealtimeStatusSnapshot as I, type RealtimeSubscription as J, type ResolvedMention as K, type ResolvedReply as L, type MagicLinkResult as M, type NotifyScope as N, type OAuthExchangeResult as O, PalbeAnalytics as P, type Unsubscribe as Q, RealtimeChannel as R, type SentReceipt as S, type PalbeRuntime as T, type UnreadView as U, buildRuntime as V, PalbePerf as W, type AuthChangeEvent as a, type AuthState as b, type AuthSuccess as c, type AuthUser as d, type CallChangeCallback as e, type CallParticipant as f, type CallState as g, Chat as h, type ChatBackend as i, type ChatDraft as j, type ChatKind as k, type ChatMember as l, type ChatMessage as m, type ChatMessageKind as n, type ChatRole as o, type ChatState as p, type CommsPrefs as q, type MentionRange as r, type MessageDirection as s, PalbeAuth as t, PalbeCalls as u, type PalbeConfig as v, PalbeFlags as w, PalbeMessaging as x, type PalbeOAuthConfig as y, PalbeRealtime as z };
@@ -622,6 +622,16 @@ interface PalbeConfig {
622
622
  apiKey: string;
623
623
  /** Informational — url+apiKey are already branch-specific (endpointRef embeds the branch slug); gen bakes all three consistently. */
624
624
  branch?: string;
625
+ /**
626
+ * F2 app-registration: the registered app `identifier` from
627
+ * `palbase-config.json` — for web this is the declared ORIGIN (e.g.
628
+ * `https://app.example.com`). When non-empty, `configure` REFUSES to build the
629
+ * runtime if `window.location.origin` does not match (SDK-REFUSES), and the
630
+ * value rides on `X-Palbase-Bundle` on EVERY request so the Kong gate can match
631
+ * it (the IDENTICAL header + value the iOS SDK sends). Empty/absent → legacy /
632
+ * tooling: no guard, no header.
633
+ */
634
+ identifier?: string;
625
635
  oauth?: PalbeOAuthConfig;
626
636
  /** Refresh-token persistence. Default: endpoint-scoped localStorage in browsers, memory elsewhere. */
627
637
  storage?: SessionStorageAdapter;
@@ -825,6 +835,49 @@ interface ChatMessage {
825
835
  * Mirrors iOS `ChatMessage.effectiveExpiry` (which it derives from).
826
836
  */
827
837
  readonly expiresAt: Date | null;
838
+ /**
839
+ * Server-metadata cluster — the double-tick DELIVERED watermark on THIS user's OWN
840
+ * (`direction === 'outgoing'`) messages, FOLDED TO USER-LEVEL. The wire carries the
841
+ * acking device_id; the SDK folds across a peer's devices and across peers to ONE
842
+ * user-level server_seq (MONOTONIC max — a stale lower incoming tick NEVER lowers it).
843
+ * A per-message tick is "is `message.serverSeq <= deliveredUpTo`?". Absent (`undefined`)
844
+ * until a `delivered` conv event covers this message; never set on an incoming message.
845
+ * Mirrors iOS `Chat.deliveredUpTo` (a chat-level scalar there; per-message here).
846
+ */
847
+ readonly deliveredUpTo?: number;
848
+ /**
849
+ * Server-metadata cluster — the double-tick READ watermark on THIS user's OWN
850
+ * (`direction === 'outgoing'`) messages, FOLDED TO USER-LEVEL + MONOTONIC max (same
851
+ * fold as `deliveredUpTo`). Set by a `read` conv event. Read STAYS gated by the peer's
852
+ * `share_receipts` server-side (the broadcast simply never arrives when muted); the
853
+ * client fold is identical to delivered. Mirrors iOS `Chat.readUpTo`.
854
+ */
855
+ readonly readUpTo?: number;
856
+ }
857
+ /**
858
+ * The per-(user, group) notify scope (the server-metadata MUTE toggle). `'all'`
859
+ * (default, absent row) fires the push wake; `'none'` (muted) suppresses it. The raw
860
+ * wire value is the snake-free `"all"`/`"none"` string. An unknown/garbage server value
861
+ * maps to `'all'` at the surface — fail-OPEN, never silently mutes (mirrors iOS
862
+ * `NotifyScope`). The server stores this opaque enum and stays MLS-blind (no content).
863
+ */
864
+ type NotifyScope = 'all' | 'none';
865
+ /**
866
+ * The caller's OWN unread view (the `GET /v1/messaging/groups/{gid}/unread` response).
867
+ * Every field is an OPAQUE server-metadata integer (no message content): the group's max
868
+ * server_seq, this caller's read + delivered cursors, and the server-derived unread
869
+ * count. The camelCase mirror of the snake_case wire (`group_max_seq` → `groupMaxSeq`,
870
+ * etc). Mirrors iOS `UnreadView`.
871
+ */
872
+ interface UnreadView {
873
+ /** The highest server_seq the group has reached (the newest message's seq). */
874
+ readonly groupMaxSeq: number;
875
+ /** The furthest server_seq THIS caller has marked read. */
876
+ readonly lastReadSeq: number;
877
+ /** The furthest server_seq THIS caller's devices have acked delivered. */
878
+ readonly deliveredSeq: number;
879
+ /** The server-derived unread count for this caller (clamped `>= 0` at the surface). */
880
+ readonly unreadCount: number;
828
881
  }
829
882
  /** The receipt from a send — the server's monotonic sequence + accepted epoch. */
830
883
  interface SentReceipt {
@@ -1001,7 +1054,12 @@ interface ChatBackend {
1001
1054
  registerActive(group: MessagingGroup): void;
1002
1055
  setTyping(group: MessagingGroup, isTyping: boolean): void;
1003
1056
  markRead(group: MessagingGroup, upToServerSeq: number): Promise<void>;
1004
- /** Subscribe live message + conv (presence/typing/read) for an active group. */
1057
+ /** Server-metadata cluster: set/get the per-(user,group) notify scope (mute) and
1058
+ * fetch the caller's own unread view. The server stays MLS-blind (opaque metadata). */
1059
+ setNotifyScope(group: MessagingGroup, scope: NotifyScope): Promise<NotifyScope>;
1060
+ getNotifyScope(group: MessagingGroup): Promise<NotifyScope>;
1061
+ unread(group: MessagingGroup): Promise<UnreadView>;
1062
+ /** Subscribe live message + conv (presence/typing/read/delivered) for an active group. */
1005
1063
  subscribeLive(group: MessagingGroup, chat: Chat): Unsubscribe$1;
1006
1064
  /** Resolve a sender device id → its owning user id (for senderUserId). */
1007
1065
  userIdForDevice(group: MessagingGroup, deviceId: string): Promise<string | null>;
@@ -1306,6 +1364,20 @@ declare class Chat {
1306
1364
  leave(): Promise<void>;
1307
1365
  setTyping(isTyping: boolean): void;
1308
1366
  markRead(message: ChatMessage): Promise<void>;
1367
+ /** Set this chat's notify scope (the mute toggle). `'none'` mutes the push wake;
1368
+ * `'all'` unmutes (the default). Materializes a draft first (the scope is a
1369
+ * per-(user,group) server row), PUTs `/notify`, and returns the server-echoed scope.
1370
+ * Mirrors iOS `Chat.setNotifyScope`. */
1371
+ setNotifyScope(scope: NotifyScope): Promise<NotifyScope>;
1372
+ /** Refresh + return this chat's notify scope from the server (fail-OPEN to `'all'`).
1373
+ * Returns `'all'` for a draft chat (no server row yet). */
1374
+ getNotifyScope(): Promise<NotifyScope>;
1375
+ /** Fetch the caller's authoritative SERVER unread count (opaque server metadata).
1376
+ * Returns the clamped count (`max(0, …)`); `0` for a draft chat. The local computed
1377
+ * `unreadCount` getter stays the instant, offline best-effort badge — this is the
1378
+ * canonical count on demand. Named distinctly so it does not shadow the observable
1379
+ * `unreadCount` snapshot getter. Mirrors iOS `Chat.refreshUnread`. */
1380
+ unreadCountFromServer(): Promise<number>;
1309
1381
  /** Add an emoji reaction to a message. No-op if the message isn't reactable
1310
1382
  * (empty clientMsgId — a legacy/system row). The reaction folds locally with
1311
1383
  * the server receipt's `(epoch, serverSeq)` so the target's tally updates
@@ -1374,6 +1446,153 @@ declare class PalbeMessaging {
1374
1446
  onChatsChange(cb: () => void): Unsubscribe$1;
1375
1447
  }
1376
1448
 
1449
+ /** The Faz-1 trace kinds plus the Faz-1.5 web-vital family. */
1450
+ type PerfTraceType = 'network' | 'app_start' | 'custom' | 'web_vital';
1451
+ /**
1452
+ * One perf measurement, shaped EXACTLY as the ingest wire expects. Keys are
1453
+ * snake_case to match the Go decode tags — do not rename to camelCase.
1454
+ */
1455
+ interface PerfItem {
1456
+ /** Client-generated UUIDv7 — server dedup key. */
1457
+ row_id: string;
1458
+ trace_type: PerfTraceType;
1459
+ name: string;
1460
+ /** Duration in milliseconds. */
1461
+ value: number;
1462
+ counters?: Record<string, number>;
1463
+ attrs?: Record<string, string>;
1464
+ request_id?: string;
1465
+ /** Unix milliseconds. */
1466
+ timestamp: number;
1467
+ }
1468
+
1469
+ /**
1470
+ * Bounded FIFO perf-item queue. `enqueue` appends and evicts the oldest on
1471
+ * overflow; `drainAll` returns everything (oldest-first) and clears the store;
1472
+ * `count` is the current depth; `dropped` is the cumulative eviction counter.
1473
+ *
1474
+ * When `localStorage` is present the queue is read-through / write-through to a
1475
+ * fixed key, so a fresh instance after a reload sees the persisted items. With
1476
+ * no `localStorage` it is purely in-memory.
1477
+ */
1478
+ declare class PerfOfflineQueue {
1479
+ private items;
1480
+ private _dropped;
1481
+ private readonly maxItems;
1482
+ constructor(maxItems?: number);
1483
+ /** Append items; FIFO-evict the oldest when over `maxItems`. */
1484
+ enqueue(items: PerfItem[]): void;
1485
+ /** Return all queued items (oldest-first) and clear the queue + store. */
1486
+ drainAll(): PerfItem[];
1487
+ /** Current queue depth. */
1488
+ get count(): number;
1489
+ /** Cumulative count of FIFO-evicted items (a `dropped` metric, not silent). */
1490
+ get dropped(): number;
1491
+ /** Drop the oldest items until at most `maxItems` remain, counting each. */
1492
+ private trim;
1493
+ private persist;
1494
+ private clearStore;
1495
+ }
1496
+
1497
+ /**
1498
+ * The mutable PalPerf state shared between the facade and the network hook.
1499
+ * `testDevice` is signalled to the server via the `X-Palbase-Test-Device: 1`
1500
+ * request header (server-authoritative tagging — the SDK only flips the bit).
1501
+ */
1502
+ declare class PerfState {
1503
+ /** Pending, un-flushed perf items (FIFO). */
1504
+ readonly buffer: PerfItem[];
1505
+ /** When true, every flush carries `X-Palbase-Test-Device: 1`. */
1506
+ testDevice: boolean;
1507
+ enqueue(item: PerfItem): void;
1508
+ /** Detach up to `MAX_PERF_BATCH` items for one POST (FIFO order preserved). */
1509
+ take(limit?: number): PerfItem[];
1510
+ get size(): number;
1511
+ }
1512
+
1513
+ /**
1514
+ * A running custom trace. `putAttribute`/`incrementMetric` accumulate metadata;
1515
+ * `stop()` records ONE `custom` PerfItem whose `value` is the elapsed
1516
+ * milliseconds since `startTrace`. Idempotent: a second `stop()` is a no-op.
1517
+ */
1518
+ declare class PerfTrace {
1519
+ private readonly name;
1520
+ private readonly onStop;
1521
+ private readonly attrs;
1522
+ private readonly counters;
1523
+ private readonly startedAt;
1524
+ private stopped;
1525
+ constructor(name: string, onStop: (item: PerfItem) => void);
1526
+ putAttribute(key: string, value: string): void;
1527
+ incrementMetric(name: string, by?: number): void;
1528
+ stop(): void;
1529
+ }
1530
+ declare class PalbePerf {
1531
+ private readonly rt;
1532
+ private readonly state;
1533
+ /** Durable offline buffer for failed flushes (persist-on-fail). Browser →
1534
+ * localStorage-backed; server → in-memory. */
1535
+ private readonly queue;
1536
+ private flushTimer;
1537
+ /** Browser → buffer + size/timer flush. Server (no document) → immediate
1538
+ * per-item flush, zero timers (nothing leaks into RSC/route handlers). */
1539
+ private readonly browser;
1540
+ /** Bound `online` handler so it can be removed on `dispose()` (no leak). */
1541
+ private readonly onOnline;
1542
+ /** Server-controlled client-side sample rate (0..100). 100 until the config
1543
+ * client fetches `/v1/analytics/perf/config` — the SDK obeys this ceiling and
1544
+ * never raises its own rate (invariant 3). `record` drops an item whose
1545
+ * deterministic `row_id` bucket is >= this pct, mirroring the server's
1546
+ * `SampleDecision` so client + server keep the SAME rows. */
1547
+ private samplePct;
1548
+ constructor(rt: PalbeRuntime, state?: PerfState,
1549
+ /** Durable offline buffer for failed flushes (persist-on-fail). Browser →
1550
+ * localStorage-backed; server → in-memory. */
1551
+ queue?: PerfOfflineQueue);
1552
+ /** Remove the `online` listener. Called when the runtime is replaced so the
1553
+ * handler does not outlive this facade. No-op outside the browser. */
1554
+ dispose(): void;
1555
+ /** Mark (or unmark) this client's traffic as test — the server tags the rows
1556
+ * when the `X-Palbase-Test-Device: 1` header rides along on flush. */
1557
+ setTestDevice(on: boolean): void;
1558
+ /** Apply the server-resolved client-side sample rate (0..100), clamped. Called
1559
+ * by `PerfConfigClient` after fetching `/v1/analytics/perf/config`. The SDK
1560
+ * OBEYS this value — it is a ceiling, never raised locally. */
1561
+ setSamplePct(pct: number): void;
1562
+ /** Pending (un-flushed) buffer depth. Test-only visibility into the buffer so
1563
+ * the remote-sampling tests can assert how many items were sampled in. */
1564
+ get bufferSizeForTest(): number;
1565
+ /** Opt-in: wrap the global `fetch` so every app-level request is recorded as a
1566
+ * redacted `network` perf item (the `/v1/analytics/*` ingest paths are
1567
+ * self-excluded). OFF by default — swizzling a global is a page-wide side
1568
+ * effect. Returns an `uninstall` that restores the original `fetch`. */
1569
+ enableFetchCapture(): () => void;
1570
+ /** Start a custom trace; the returned handle records a `custom` item on
1571
+ * `.stop()`. */
1572
+ startTrace(name: string): PerfTrace;
1573
+ /** Buffer one perf item. In the browser, flush on size/timer; on the server
1574
+ * flush immediately (no timers). Never throws.
1575
+ *
1576
+ * Client-side remote sampling: an item whose deterministic `row_id` bucket is
1577
+ * NOT below the server-controlled `samplePct` is dropped before buffering —
1578
+ * the SAME FNV-1a-mod-100 decision the server applies at ingest, so the two
1579
+ * keep identical rows and the SDK saves the upload (defense-in-depth: the
1580
+ * server re-samples authoritatively). */
1581
+ record(item: PerfItem): void;
1582
+ /** Buffer a network trace — called by `request.ts` around `rt.http.request`
1583
+ * (the analytics ingest path is excluded by the caller to avoid recursion). */
1584
+ recordNetwork(method: string, url: string, status: number, durationMs: number, requestId?: string): void;
1585
+ /** Drain the offline queue (oldest-first) and the live buffer to
1586
+ * `/v1/analytics/perf` (≤100 items per request, sequential). Resolves when
1587
+ * delivery finished; NEVER rejects. A failed slice is NOT dropped — it goes
1588
+ * to the offline queue (persist-on-fail) so the next flush (size/timer or the
1589
+ * `online` reconnect event) retries it. Items keep their original `row_id`,
1590
+ * so a redelivery dedups server-side (ReplacingMergeTree). */
1591
+ flush(): Promise<void>;
1592
+ private startTimer;
1593
+ private cancelTimer;
1594
+ }
1595
+
1377
1596
  /**
1378
1597
  * The connection state of the shared realtime WebSocket (iOS enum parity), plus
1379
1598
  * `'error'` — surfaced when a channel could not be recovered after a
@@ -1471,6 +1690,14 @@ declare class PalbeRealtime {
1471
1690
 
1472
1691
  interface PalbeRuntime {
1473
1692
  config: PalbeConfig;
1693
+ /**
1694
+ * F2 app-registration: the registered app `identifier` (web ORIGIN) stashed at
1695
+ * configure. Rides on `X-Palbase-Bundle` on EVERY request (request.ts) so the
1696
+ * Kong gate can match it. Empty string when the binding is unconfigured/tooling
1697
+ * (no header is sent). Origin-match is enforced in `buildRuntime` BEFORE this
1698
+ * runtime is returned — a mismatch throws there and no runtime is built.
1699
+ */
1700
+ appIdentifier: string;
1474
1701
  http: HttpClient$1;
1475
1702
  tokenManager: TokenManager$1;
1476
1703
  authClient: AuthClient;
@@ -1480,6 +1707,13 @@ interface PalbeRuntime {
1480
1707
  analytics: PalbeAnalytics;
1481
1708
  calls: PalbeCalls;
1482
1709
  messaging: PalbeMessaging;
1710
+ /**
1711
+ * PalPerf surface. EAGER (like analytics identity): `request.ts` records a
1712
+ * `network` trace around EVERY SDK fetch, so the facade must exist from the
1713
+ * first request — never lazy. The buffer/timer it owns is inert until the
1714
+ * first item is recorded.
1715
+ */
1716
+ perf: PalbePerf;
1483
1717
  storage: SessionStorageAdapter;
1484
1718
  /**
1485
1719
  * Destroy the realtime facade if it was already constructed (no-op otherwise).
@@ -1731,4 +1965,4 @@ declare class PalbeAnalytics {
1731
1965
  private post;
1732
1966
  }
1733
1967
 
1734
- export { type AnalyticsProperties as A, type PresenceState as B, Call as C, type RealtimeConnectionState as D, type RealtimeHandler as E, type FlagsView as F, type RealtimePayload as G, type RealtimeStatus as H, type RealtimeStatusSnapshot as I, type RealtimeSubscription as J, type ResolvedMention as K, type ResolvedReply as L, type MagicLinkResult as M, type PalbeRuntime as N, type OAuthExchangeResult as O, PalbeAnalytics as P, buildRuntime as Q, RealtimeChannel as R, type SentReceipt as S, type Unsubscribe as U, type AuthChangeEvent as a, type AuthState as b, type AuthSuccess as c, type AuthUser as d, type CallChangeCallback as e, type CallParticipant as f, type CallState as g, Chat as h, type ChatBackend as i, type ChatDraft as j, type ChatKind as k, type ChatMember as l, type ChatMessage as m, type ChatMessageKind as n, type ChatRole as o, type ChatState as p, type CommsPrefs as q, type MentionRange as r, type MessageDirection as s, PalbeAuth as t, PalbeCalls as u, type PalbeConfig as v, PalbeFlags as w, PalbeMessaging as x, type PalbeOAuthConfig as y, PalbeRealtime as z };
1968
+ export { type AnalyticsProperties as A, type PresenceState as B, Call as C, type RealtimeConnectionState as D, type RealtimeHandler as E, type FlagsView as F, type RealtimePayload as G, type RealtimeStatus as H, type RealtimeStatusSnapshot as I, type RealtimeSubscription as J, type ResolvedMention as K, type ResolvedReply as L, type MagicLinkResult as M, type NotifyScope as N, type OAuthExchangeResult as O, PalbeAnalytics as P, type Unsubscribe as Q, RealtimeChannel as R, type SentReceipt as S, type PalbeRuntime as T, type UnreadView as U, buildRuntime as V, PalbePerf as W, type AuthChangeEvent as a, type AuthState as b, type AuthSuccess as c, type AuthUser as d, type CallChangeCallback as e, type CallParticipant as f, type CallState as g, Chat as h, type ChatBackend as i, type ChatDraft as j, type ChatKind as k, type ChatMember as l, type ChatMessage as m, type ChatMessageKind as n, type ChatRole as o, type ChatState as p, type CommsPrefs as q, type MentionRange as r, type MessageDirection as s, PalbeAuth as t, PalbeCalls as u, type PalbeConfig as v, PalbeFlags as w, PalbeMessaging as x, type PalbeOAuthConfig as y, PalbeRealtime as z };