@palbase/web 1.0.1 → 1.1.1

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/index.cjs CHANGED
@@ -23,10 +23,13 @@ __export(index_exports, {
23
23
  BackendError: () => BackendError,
24
24
  PalbeAnalytics: () => PalbeAnalytics,
25
25
  PalbeAuth: () => PalbeAuth,
26
+ PalbeCalls: () => PalbeCalls,
26
27
  PalbeFlags: () => PalbeFlags,
28
+ PalbeMessaging: () => PalbeMessaging,
27
29
  PalbeRealtime: () => PalbeRealtime,
28
30
  RealtimeChannel: () => RealtimeChannel,
29
31
  VERSION: () => VERSION,
32
+ initMls: () => initMls,
30
33
  isBackendError: () => isBackendError,
31
34
  localStorageSessionStorage: () => localStorageSessionStorage,
32
35
  memorySessionStorage: () => memorySessionStorage,
@@ -35,9 +38,10 @@ __export(index_exports, {
35
38
  module.exports = __toCommonJS(index_exports);
36
39
 
37
40
  // src/api-key.ts
41
+ var API_KEY_RE = /^pb_([^_]+)_[cs][A-Za-z0-9]{20}$/;
38
42
  function endpointRefFromApiKey(apiKey) {
39
- const parts = apiKey.split("_");
40
- return parts.length >= 3 && parts[0] === "pb" ? parts[1] ?? "" : "";
43
+ const m = API_KEY_RE.exec(apiKey);
44
+ return m ? m[1] ?? "" : "";
41
45
  }
42
46
 
43
47
  // ../core/dist/index.js
@@ -696,6 +700,220 @@ var PalbeAuth = class {
696
700
  }
697
701
  };
698
702
 
703
+ // src/calls/facade.ts
704
+ var import_livekit_client = require("livekit-client");
705
+ var Call = class {
706
+ id;
707
+ _state = "connecting";
708
+ _participants = /* @__PURE__ */ new Map();
709
+ _listeners = /* @__PURE__ */ new Set();
710
+ _room;
711
+ _keyProvider;
712
+ /** @internal — obtain via PalbeCalls.start() / .accept() */
713
+ constructor(callId, room, keyProvider) {
714
+ this.id = callId;
715
+ this._room = room;
716
+ this._keyProvider = keyProvider;
717
+ this._wireRoomEvents();
718
+ }
719
+ // ─── State ──────────────────────────────────────────────────────────────
720
+ get state() {
721
+ return this._state;
722
+ }
723
+ /** Snapshot of current remote participants (does NOT include the local participant). */
724
+ get participants() {
725
+ return Array.from(this._participants.values());
726
+ }
727
+ get localParticipant() {
728
+ return this._room.localParticipant;
729
+ }
730
+ // ─── Subscribe ──────────────────────────────────────────────────────────
731
+ /**
732
+ * Subscribe to call changes (state + participant updates).
733
+ * Returns an `Unsubscribe` function — matches the SDK's flags/auth pattern.
734
+ */
735
+ subscribe(callback) {
736
+ this._listeners.add(callback);
737
+ return () => {
738
+ this._listeners.delete(callback);
739
+ };
740
+ }
741
+ // ─── Controls ───────────────────────────────────────────────────────────
742
+ /** Mute / unmute the local microphone. */
743
+ async mute(on) {
744
+ await this._room.localParticipant.setMicrophoneEnabled(!on);
745
+ }
746
+ /** Enable / disable the local camera. */
747
+ async setCamera(on) {
748
+ await this._room.localParticipant.setCameraEnabled(on);
749
+ }
750
+ /**
751
+ * Set a raw frame encryption key (ArrayBuffer) for this call.
752
+ * Design seam for SP-1.x web-MLS: call this with the MLS exporter secret
753
+ * to enable frame-level E2EE via ExternalE2EEKeyProvider.
754
+ *
755
+ * NOTE: The room must have been opened with `e2ee` options for this to take
756
+ * effect. Currently calls connect WITHOUT e2ee — see module docstring.
757
+ *
758
+ * TODO(SP-1-web): wire this to MLS-derived key on session join.
759
+ */
760
+ async setMediaKey(key) {
761
+ await this._keyProvider.setKey(key);
762
+ }
763
+ /** Leave the call and disconnect from the LiveKit room. */
764
+ async leave() {
765
+ await this._room.disconnect();
766
+ this._setState("ended");
767
+ }
768
+ // ─── Internal ───────────────────────────────────────────────────────────
769
+ _setState(next) {
770
+ if (this._state === next) return;
771
+ this._state = next;
772
+ this._emit();
773
+ }
774
+ _emit() {
775
+ for (const cb of this._listeners) cb(this);
776
+ }
777
+ _upsertParticipant(p) {
778
+ const existing = this._participants.get(p.identity);
779
+ const entry = {
780
+ identity: p.identity,
781
+ isSpeaking: p.isSpeaking,
782
+ audioMuted: !p.isMicrophoneEnabled,
783
+ videoEnabled: p.isCameraEnabled,
784
+ videoElement: existing?.videoElement ?? null,
785
+ audioElement: existing?.audioElement ?? null
786
+ };
787
+ this._participants.set(p.identity, entry);
788
+ }
789
+ _attachTrack(track, participant) {
790
+ const element = track.attach();
791
+ const entry = this._participants.get(participant.identity);
792
+ if (track.kind === import_livekit_client.Track.Kind.Video) {
793
+ if (entry) {
794
+ entry.videoElement = element;
795
+ }
796
+ } else if (track.kind === import_livekit_client.Track.Kind.Audio) {
797
+ if (entry) {
798
+ entry.audioElement = element;
799
+ element.play().catch(() => {
800
+ });
801
+ }
802
+ }
803
+ this._emit();
804
+ }
805
+ _wireRoomEvents() {
806
+ this._room.on(import_livekit_client.RoomEvent.Connected, () => {
807
+ this._setState("active");
808
+ }).on(
809
+ import_livekit_client.RoomEvent.TrackSubscribed,
810
+ (track, _pub, participant) => {
811
+ this._upsertParticipant(participant);
812
+ this._attachTrack(track, participant);
813
+ }
814
+ ).on(
815
+ import_livekit_client.RoomEvent.TrackUnsubscribed,
816
+ (_track, _pub, participant) => {
817
+ this._upsertParticipant(participant);
818
+ this._emit();
819
+ }
820
+ ).on(import_livekit_client.RoomEvent.ParticipantConnected, (participant) => {
821
+ this._upsertParticipant(participant);
822
+ this._emit();
823
+ }).on(import_livekit_client.RoomEvent.ParticipantDisconnected, (participant) => {
824
+ this._participants.delete(participant.identity);
825
+ this._emit();
826
+ }).on(import_livekit_client.RoomEvent.ActiveSpeakersChanged, (speakers) => {
827
+ for (const [id, entry] of this._participants) {
828
+ entry.isSpeaking = speakers.some((s) => s.identity === id);
829
+ }
830
+ this._emit();
831
+ }).on(import_livekit_client.RoomEvent.Disconnected, () => {
832
+ this._setState("ended");
833
+ });
834
+ }
835
+ };
836
+ var PalbeCalls = class {
837
+ rt;
838
+ constructor(rt) {
839
+ this.rt = rt;
840
+ }
841
+ /**
842
+ * Start a new call in a messaging group.
843
+ *
844
+ * @param groupId - Messaging group display-id (`grp_<uuidv7>`)
845
+ * @param media - Which media tracks to publish (default: audio + video)
846
+ * @returns A `Call` in `connecting` state; transitions to `active` once LiveKit connects.
847
+ *
848
+ * @throws BackendError('network', { code: 'call_service_unavailable' }) if LiveKit SFU is down (503)
849
+ * @throws BackendError('forbidden', { code: 'not_group_member' }) if the caller is not a member (403)
850
+ * @throws BackendError('conflict', { code: 'call_room_full' }) if the room is full (409)
851
+ */
852
+ async start(groupId, { media = ["audio", "video"] } = {}) {
853
+ if (!groupId) {
854
+ throw new BackendError("validation", {
855
+ code: "invalid_group_id",
856
+ message: "groupId must be a non-empty string"
857
+ });
858
+ }
859
+ const res = await palbeRequest(
860
+ this.rt,
861
+ "POST",
862
+ `/v1/messaging/groups/${encodeURIComponent(groupId)}/calls`,
863
+ { body: { media } }
864
+ );
865
+ return this._connect(res.call_id, res.token, res.edge_url, media);
866
+ }
867
+ /**
868
+ * Accept an incoming call in a messaging group.
869
+ *
870
+ * @param groupId - Messaging group display-id
871
+ * @param callId - Call identifier (from the push notification payload)
872
+ * @param media - Which media tracks to publish (default: audio + video)
873
+ */
874
+ async accept(groupId, callId, { media = ["audio", "video"] } = {}) {
875
+ const res = await palbeRequest(
876
+ this.rt,
877
+ "POST",
878
+ `/v1/messaging/groups/${encodeURIComponent(groupId)}/calls/${encodeURIComponent(callId)}/accept`,
879
+ { body: {} }
880
+ );
881
+ return this._connect(callId, res.token, res.edge_url, media);
882
+ }
883
+ /**
884
+ * Decline an incoming call.
885
+ *
886
+ * @param groupId - Messaging group display-id
887
+ * @param callId - Call identifier
888
+ * @param reason - Optional decline reason string
889
+ */
890
+ async decline(groupId, callId, reason) {
891
+ await palbeRequest(
892
+ this.rt,
893
+ "POST",
894
+ `/v1/messaging/groups/${encodeURIComponent(groupId)}/calls/${encodeURIComponent(callId)}/decline`,
895
+ { body: { reason: reason ?? "" } }
896
+ );
897
+ }
898
+ // ─── Internal ─────────────────────────────────────────────────────────
899
+ async _connect(callId, token, edgeUrl, media) {
900
+ const keyProvider = new import_livekit_client.ExternalE2EEKeyProvider();
901
+ const room = new import_livekit_client.Room({
902
+ adaptiveStream: true,
903
+ dynacast: true
904
+ });
905
+ const call = new Call(callId, room, keyProvider);
906
+ await room.connect(edgeUrl, token);
907
+ if (media.includes("audio")) {
908
+ await room.localParticipant.setMicrophoneEnabled(true);
909
+ }
910
+ if (media.includes("video")) {
911
+ await room.localParticipant.setCameraEnabled(true);
912
+ }
913
+ return call;
914
+ }
915
+ };
916
+
699
917
  // ../modules/flags/dist/index.js
700
918
  var FLAG_NAME_RE = /^[a-zA-Z0-9_-]+$/;
701
919
  var FlagsClient = class {
@@ -1174,218 +1392,3420 @@ function parsePersisted(raw) {
1174
1392
  syncVersion: rec.syncVersion
1175
1393
  };
1176
1394
  }
1177
- function defaultEnv() {
1178
- const storage = resolveStorage();
1179
- const visibility = resolveVisibility();
1180
- return {
1181
- now: () => Date.now(),
1182
- setInterval: (handler, ms) => setInterval(handler, ms),
1183
- clearInterval: (handle) => clearInterval(handle),
1184
- storage,
1185
- visibility
1186
- };
1395
+ function defaultEnv() {
1396
+ const storage = resolveStorage();
1397
+ const visibility = resolveVisibility();
1398
+ return {
1399
+ now: () => Date.now(),
1400
+ setInterval: (handler, ms) => setInterval(handler, ms),
1401
+ clearInterval: (handle) => clearInterval(handle),
1402
+ storage,
1403
+ visibility
1404
+ };
1405
+ }
1406
+ function resolveStorage() {
1407
+ try {
1408
+ if (typeof localStorage === "undefined") {
1409
+ return null;
1410
+ }
1411
+ const probe = "__palbase_flags_probe__";
1412
+ localStorage.setItem(probe, "1");
1413
+ localStorage.removeItem(probe);
1414
+ return localStorage;
1415
+ } catch {
1416
+ return null;
1417
+ }
1418
+ }
1419
+ function resolveVisibility() {
1420
+ if (typeof document === "undefined") {
1421
+ return null;
1422
+ }
1423
+ const doc = document;
1424
+ return {
1425
+ isHidden: () => doc.visibilityState === "hidden",
1426
+ onVisibilityChange: (cb) => {
1427
+ doc.addEventListener("visibilitychange", cb);
1428
+ return () => doc.removeEventListener("visibilitychange", cb);
1429
+ }
1430
+ };
1431
+ }
1432
+
1433
+ // src/flags-facade.ts
1434
+ function palbeAuthAdapter(auth) {
1435
+ return {
1436
+ onAuthStateChange(callback) {
1437
+ const unsubscribe = auth.onAuthStateChange(() => {
1438
+ const signedIn = auth.isSignedIn;
1439
+ callback(signedIn ? "SIGNED_IN" : "SIGNED_OUT", signedIn ? { signedIn } : null);
1440
+ });
1441
+ return { data: { subscription: { unsubscribe } } };
1442
+ }
1443
+ };
1444
+ }
1445
+ function serverPoolEnv() {
1446
+ return {
1447
+ now: () => Date.now(),
1448
+ setInterval: () => {
1449
+ throw new Error("palbe flags: polling is disabled server-side");
1450
+ },
1451
+ clearInterval: () => {
1452
+ },
1453
+ storage: null,
1454
+ visibility: null
1455
+ };
1456
+ }
1457
+ function sameFlagValue(a, b) {
1458
+ if (Object.is(a, b)) return true;
1459
+ if (typeof a === "object" && a !== null && typeof b === "object" && b !== null) {
1460
+ return JSON.stringify(a) === JSON.stringify(b);
1461
+ }
1462
+ return false;
1463
+ }
1464
+ var PalbeFlags = class {
1465
+ transport;
1466
+ pool;
1467
+ constructor(rt) {
1468
+ this.transport = new FlagsClient(rt.http);
1469
+ const browser = typeof document !== "undefined";
1470
+ const ref = endpointRefFromApiKey(rt.config.apiKey);
1471
+ const options = {
1472
+ auth: palbeAuthAdapter(rt.auth),
1473
+ ...ref ? { storageKey: `palbe.flags.${ref}` } : {},
1474
+ ...browser ? {} : { pollIntervalMs: 0, env: serverPoolEnv() }
1475
+ };
1476
+ this.pool = new FlagsPool(this.transport, options);
1477
+ if (browser) this.pool.start();
1478
+ }
1479
+ /** Resolves once the first snapshot (or persisted hydrate) is available. Auto-starts the pool. */
1480
+ ready() {
1481
+ return this.pool.ready();
1482
+ }
1483
+ /** Force an immediate re-snapshot. */
1484
+ refresh() {
1485
+ return this.pool.refresh();
1486
+ }
1487
+ /** Frozen snapshot of all cached values (identity-stable until a change). */
1488
+ all() {
1489
+ return this.pool.all();
1490
+ }
1491
+ /** Raw cached value for `key`, or `undefined` when not in the cache. */
1492
+ get(key) {
1493
+ return this.pool.all()[key];
1494
+ }
1495
+ /** `true` only when the cached value is strictly `true`; `fallback` when the key is absent. */
1496
+ isEnabled(key, fallback = false) {
1497
+ return this.pool.isEnabled(key, fallback);
1498
+ }
1499
+ /** Alias of {@link isEnabled} (iOS parity). */
1500
+ bool(key, fallback = false) {
1501
+ return this.isEnabled(key, fallback);
1502
+ }
1503
+ /** Cached value when it is a string, else `fallback`. */
1504
+ getString(key, fallback) {
1505
+ const value = this.pool.all()[key];
1506
+ return typeof value === "string" ? value : fallback;
1507
+ }
1508
+ /** Cached value when it is an integer number, else `fallback`. */
1509
+ getInt(key, fallback) {
1510
+ const value = this.pool.all()[key];
1511
+ return typeof value === "number" && Number.isInteger(value) ? value : fallback;
1512
+ }
1513
+ /** Cached value when it is a number (integers included), else `fallback`. */
1514
+ getDouble(key, fallback) {
1515
+ const value = this.pool.all()[key];
1516
+ return typeof value === "number" ? value : fallback;
1517
+ }
1518
+ /**
1519
+ * Resolve the multivariate variant for `key` — the variant name, or `null`
1520
+ * when the flag has no variant (or the read fails).
1521
+ *
1522
+ * Wire failures (network errors, 404, etc.) resolve to `null`.
1523
+ * Invalid flag names throw `BackendError('validation', { code: 'invalid_flag_name' })`.
1524
+ *
1525
+ * DEVIATION from iOS sync-cache parity: the platform does not propagate
1526
+ * variant metadata into the user-flags snapshot/delta cache, so this is an
1527
+ * ASYNC transport read (`GET /v1/flags/{key}/variant`), not a cache lookup.
1528
+ */
1529
+ async getVariant(key) {
1530
+ let res;
1531
+ try {
1532
+ res = await this.transport.getVariant(key);
1533
+ } catch (err) {
1534
+ throw new BackendError("validation", {
1535
+ code: "invalid_flag_name",
1536
+ message: err instanceof Error ? err.message : String(err)
1537
+ });
1538
+ }
1539
+ return res.data?.name ?? null;
1540
+ }
1541
+ /** Subscribe to any change in the cached flag set. */
1542
+ onChange(callback) {
1543
+ return this.pool.onChange(callback);
1544
+ }
1545
+ /**
1546
+ * Observe ONE key: fires only when that key's value actually changes
1547
+ * (per {@link sameFlagValue} — structural compare for objects), with the
1548
+ * new value (`undefined` = deleted). P5 React-hook substrate.
1549
+ */
1550
+ subscribeKey(key, callback) {
1551
+ let last = this.pool.all()[key];
1552
+ return this.pool.onChange(() => {
1553
+ const next = this.pool.all()[key];
1554
+ if (sameFlagValue(last, next)) return;
1555
+ last = next;
1556
+ callback(next);
1557
+ });
1558
+ }
1559
+ /**
1560
+ * Async iteration over flag changes: yields the new {@link all} view on
1561
+ * every pool change notification. The listener is detached when the
1562
+ * consumer `break`s/`return`s/`throw`s — including while a `next()` is
1563
+ * still pending (it resolves `{ done: true }` instead of hanging, which a
1564
+ * plain async-generator `finally` would not guarantee).
1565
+ */
1566
+ changes() {
1567
+ const queue = [];
1568
+ const pending = [];
1569
+ let finished = false;
1570
+ const unsubscribe = this.pool.onChange(() => {
1571
+ const snapshot = this.pool.all();
1572
+ const resolve = pending.shift();
1573
+ if (resolve) resolve({ value: snapshot, done: false });
1574
+ else queue.push(snapshot);
1575
+ });
1576
+ const finish = () => {
1577
+ if (finished) return;
1578
+ finished = true;
1579
+ unsubscribe();
1580
+ while (pending.length > 0) pending.shift()?.({ value: void 0, done: true });
1581
+ };
1582
+ return {
1583
+ next: () => {
1584
+ if (finished) return Promise.resolve({ value: void 0, done: true });
1585
+ const head = queue.shift();
1586
+ if (head !== void 0) return Promise.resolve({ value: head, done: false });
1587
+ return new Promise((resolve) => {
1588
+ pending.push(resolve);
1589
+ });
1590
+ },
1591
+ return: () => {
1592
+ finish();
1593
+ return Promise.resolve({ value: void 0, done: true });
1594
+ },
1595
+ throw: (error) => {
1596
+ finish();
1597
+ return Promise.reject(error);
1598
+ },
1599
+ [Symbol.asyncIterator]() {
1600
+ return this;
1601
+ }
1602
+ };
1603
+ }
1604
+ /** Stop polling and detach all listeners (auth + visibility + subscribers). */
1605
+ destroy() {
1606
+ this.pool.destroy();
1607
+ }
1608
+ };
1609
+
1610
+ // src/messaging/util.ts
1611
+ function toBase64(bytes) {
1612
+ if (typeof Buffer !== "undefined") {
1613
+ return Buffer.from(bytes).toString("base64");
1614
+ }
1615
+ let binary = "";
1616
+ const chunk = 32768;
1617
+ for (let i = 0; i < bytes.length; i += chunk) {
1618
+ binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
1619
+ }
1620
+ return btoa(binary);
1621
+ }
1622
+ function fromBase64(b64) {
1623
+ if (typeof Buffer !== "undefined") {
1624
+ return new Uint8Array(Buffer.from(b64, "base64"));
1625
+ }
1626
+ const binary = atob(b64);
1627
+ const out = new Uint8Array(binary.length);
1628
+ for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
1629
+ return out;
1630
+ }
1631
+ function bytesToHex(bytes) {
1632
+ let s = "";
1633
+ for (let i = 0; i < bytes.length; i++) {
1634
+ s += (bytes[i] ?? 0).toString(16).padStart(2, "0");
1635
+ }
1636
+ return s;
1637
+ }
1638
+ var utf8Encoder = new TextEncoder();
1639
+ var utf8Decoder = new TextDecoder();
1640
+ var encodeUtf8 = (s) => utf8Encoder.encode(s);
1641
+ var decodeUtf8 = (b) => utf8Decoder.decode(b);
1642
+ var deviceIdBytes = (deviceId) => encodeUtf8(deviceId);
1643
+ function randomId() {
1644
+ return crypto.randomUUID();
1645
+ }
1646
+ function mintDeviceId() {
1647
+ return `mdv_${uuidV7()}`;
1648
+ }
1649
+ function uuidV7() {
1650
+ const bytes = new Uint8Array(16);
1651
+ crypto.getRandomValues(bytes);
1652
+ const ts = Date.now();
1653
+ const view = new DataView(bytes.buffer);
1654
+ view.setUint32(0, Math.floor(ts / 2 ** 16));
1655
+ view.setUint16(4, ts & 65535);
1656
+ view.setUint8(6, view.getUint8(6) & 15 | 112);
1657
+ view.setUint8(8, view.getUint8(8) & 63 | 128);
1658
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
1659
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
1660
+ }
1661
+ function directKey(userA, userB) {
1662
+ const [a, b] = [userA, userB].sort();
1663
+ return `dm:${a}|${b}`;
1664
+ }
1665
+ function mintClientMsgId() {
1666
+ const b = new Uint8Array(16);
1667
+ crypto.getRandomValues(b);
1668
+ return `msg_${Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("")}`;
1669
+ }
1670
+ function truncateQuote(s) {
1671
+ const scalars = [...s];
1672
+ if (scalars.length <= 240) return [s, false];
1673
+ return [scalars.slice(0, 240).join(""), true];
1674
+ }
1675
+
1676
+ // src/messaging/wire.ts
1677
+ var GROUPS = "/v1/messaging/groups";
1678
+ var DEVICES = "/v1/messaging/devices";
1679
+ var KEYDIR = "/v1/messaging/keydir";
1680
+ var seg = (s) => encodeURIComponent(s);
1681
+ var MessagingPaths = {
1682
+ groups: GROUPS,
1683
+ groupsDirect: `${GROUPS}/direct`,
1684
+ group: (displayId) => `${GROUPS}/${seg(displayId)}`,
1685
+ groupMetadata: (displayId) => `${GROUPS}/${seg(displayId)}/metadata`,
1686
+ groupMembers: (displayId) => `${GROUPS}/${seg(displayId)}/members`,
1687
+ groupMemberDevice: (displayId, deviceId) => `${GROUPS}/${seg(displayId)}/members/${seg(deviceId)}`,
1688
+ groupMessages: (displayId) => `${GROUPS}/${seg(displayId)}/messages`,
1689
+ groupCommits: (displayId) => `${GROUPS}/${seg(displayId)}/commits`,
1690
+ groupRead: (displayId) => `${GROUPS}/${seg(displayId)}/read`,
1691
+ deviceWelcomes: (deviceId) => `${DEVICES}/${seg(deviceId)}/welcomes`,
1692
+ deviceQueue: (deviceId) => `${DEVICES}/${seg(deviceId)}/queue`,
1693
+ deviceQueueAck: (deviceId) => `${DEVICES}/${seg(deviceId)}/queue/ack`,
1694
+ keydirDevices: `${KEYDIR}/devices`,
1695
+ keydirDeviceKeyPackages: (deviceId) => `${KEYDIR}/devices/${seg(deviceId)}/keypackages`,
1696
+ keydirUserDevices: (userId) => `${KEYDIR}/users/${seg(userId)}/devices`,
1697
+ keydirUserClaim: (userId) => `${KEYDIR}/users/${seg(userId)}/keypackages/claim`,
1698
+ keydirPinsVerify: `${KEYDIR}/pins/verify`,
1699
+ prefs: "/v1/messaging/prefs"
1700
+ };
1701
+ var ContentType = {
1702
+ application: 1,
1703
+ proposal: 2,
1704
+ commit: 3
1705
+ };
1706
+
1707
+ // src/messaging/keydir.ts
1708
+ async function enrollDevice(rt, engine, deviceStore, opts = {}) {
1709
+ const keyPackageCount = opts.keyPackageCount ?? 10;
1710
+ const deviceId = engine.deviceId;
1711
+ const alreadyEnrolled = await deviceStore.load(engine.userId) === deviceId;
1712
+ if (!alreadyEnrolled) {
1713
+ const material = await engine.enrollmentMaterial();
1714
+ const body = {
1715
+ device_id: deviceId,
1716
+ platform: "web",
1717
+ device_name: opts.deviceName,
1718
+ cipher_suite: 1,
1719
+ signature_key: toBase64(material.signatureKey),
1720
+ credential: toBase64(material.credential),
1721
+ capabilities: toBase64(material.capabilities)
1722
+ };
1723
+ await palbeRequest(rt, "POST", MessagingPaths.keydirDevices, { body });
1724
+ await deviceStore.save(engine.userId, deviceId);
1725
+ }
1726
+ if (keyPackageCount > 0) {
1727
+ const raws = [];
1728
+ for (let i = 0; i < keyPackageCount; i++) {
1729
+ raws.push(toBase64(await engine.generateKeyPackage()));
1730
+ }
1731
+ await replenish(rt, deviceId, raws);
1732
+ }
1733
+ return deviceId;
1734
+ }
1735
+ async function replenish(rt, deviceId, rawKeyPackagesB64) {
1736
+ const body = { key_packages: rawKeyPackagesB64 };
1737
+ await palbeRequest(rt, "POST", MessagingPaths.keydirDeviceKeyPackages(deviceId), { body });
1738
+ }
1739
+ async function claimKeyPackages(rt, userId) {
1740
+ return palbeRequest(rt, "POST", MessagingPaths.keydirUserClaim(userId), {
1741
+ body: {}
1742
+ });
1743
+ }
1744
+ async function listDevices(rt, userId) {
1745
+ return palbeRequest(rt, "GET", MessagingPaths.keydirUserDevices(userId));
1746
+ }
1747
+
1748
+ // src/messaging/group-messaging.ts
1749
+ function encodeEnvelope(args) {
1750
+ const env = {
1751
+ v: 1,
1752
+ type: "text",
1753
+ client_msg_id: args.clientMsgId,
1754
+ text: args.text,
1755
+ ...args.replyTo ? { reply_to: args.replyTo } : {}
1756
+ };
1757
+ return encodeUtf8(JSON.stringify(env));
1758
+ }
1759
+ function decodeEnvelope(bytes) {
1760
+ const s = decodeUtf8(bytes);
1761
+ try {
1762
+ const o = JSON.parse(s);
1763
+ if (typeof o === "object" && o !== null && o.type === "text" && typeof o.v === "number") {
1764
+ return {
1765
+ text: o.text ?? null,
1766
+ clientMsgId: o.client_msg_id ?? "",
1767
+ replyTo: o.reply_to ?? null
1768
+ };
1769
+ }
1770
+ if (typeof o === "object" && o !== null && o.type === "text") {
1771
+ return { text: o.text ?? null, clientMsgId: "", replyTo: null };
1772
+ }
1773
+ if (typeof o === "object" && o !== null && o.type === "media")
1774
+ return { text: null, clientMsgId: "", replyTo: null };
1775
+ } catch {
1776
+ }
1777
+ return { text: s, clientMsgId: "", replyTo: null };
1778
+ }
1779
+ function resolveReply(ref, lookup) {
1780
+ const parent = lookup(ref.client_msg_id);
1781
+ if (parent !== null) {
1782
+ return {
1783
+ parentClientMsgId: ref.client_msg_id,
1784
+ state: "resolved",
1785
+ quoteSenderUserId: parent.senderUserId,
1786
+ quoteText: parent.text,
1787
+ quoteKind: ref.preview?.kind ?? "text"
1788
+ };
1789
+ }
1790
+ return {
1791
+ parentClientMsgId: ref.client_msg_id,
1792
+ state: "unavailable",
1793
+ quoteSenderUserId: ref.preview?.author_user_id ?? "",
1794
+ quoteText: ref.preview?.body ?? null,
1795
+ quoteKind: ref.preview?.kind ?? "text"
1796
+ };
1797
+ }
1798
+ var DEFAULT_REBASE = { maxAttempts: 6, baseDelayMs: 80, jitterMs: 120 };
1799
+ var RebaseExhausted = class extends Error {
1800
+ constructor(attempts, lastKnownEpoch) {
1801
+ super(`commit rebase exhausted after ${attempts} attempts`);
1802
+ this.attempts = attempts;
1803
+ this.lastKnownEpoch = lastKnownEpoch;
1804
+ this.name = "RebaseExhausted";
1805
+ }
1806
+ attempts;
1807
+ lastKnownEpoch;
1808
+ };
1809
+ var GroupMessaging = class {
1810
+ constructor(rt, engine, selfUserId, selfDeviceId, messageStore, rebase = DEFAULT_REBASE) {
1811
+ this.rt = rt;
1812
+ this.engine = engine;
1813
+ this.selfUserId = selfUserId;
1814
+ this.selfDeviceId = selfDeviceId;
1815
+ this.messageStore = messageStore;
1816
+ this.rebase = rebase;
1817
+ }
1818
+ rt;
1819
+ engine;
1820
+ selfUserId;
1821
+ selfDeviceId;
1822
+ messageStore;
1823
+ rebase;
1824
+ // ── Create ──
1825
+ /** Create a group: mint the rfc_group_id, POST create, return the server group.
1826
+ * No name sealing on web (metadata omitted — server-blind, label fallback). */
1827
+ async createGroup(cipherSuite = 1) {
1828
+ const rfcGroupId = await this.engine.createGroup();
1829
+ const body = {
1830
+ rfc_group_id: toBase64(rfcGroupId),
1831
+ cipher_suite: cipherSuite,
1832
+ creator_device_id: this.selfDeviceId,
1833
+ creator_leaf_index: 0
1834
+ };
1835
+ const res = await palbeRequest(this.rt, "POST", MessagingPaths.groups, {
1836
+ body
1837
+ });
1838
+ return {
1839
+ displayId: res.display_id,
1840
+ rfcGroupId: res.rfc_group_id,
1841
+ currentEpoch: res.current_epoch,
1842
+ ownerUserId: this.selfUserId,
1843
+ directKey: null,
1844
+ name: null
1845
+ };
1846
+ }
1847
+ // ── Direct (1:1) get-or-create ──
1848
+ /** Materialize a DIRECT chat keyed by the stable direct_key (sorted user pair).
1849
+ * EXISTS → adopt (the Welcome drains via the pump); ABSENT → create + add peer. */
1850
+ async getOrCreateDirect(peerUserId) {
1851
+ const dk = directKey(this.selfUserId, peerUserId);
1852
+ const rfcGroupId = await this.engine.createGroup();
1853
+ const body = {
1854
+ direct_key: dk,
1855
+ peer_user_id: peerUserId,
1856
+ rfc_group_id: toBase64(rfcGroupId),
1857
+ cipher_suite: 1,
1858
+ creator_device_id: this.selfDeviceId,
1859
+ creator_leaf_index: 0
1860
+ };
1861
+ const res = await palbeRequest(
1862
+ this.rt,
1863
+ "POST",
1864
+ MessagingPaths.groupsDirect,
1865
+ { body }
1866
+ );
1867
+ if (res.exists === true) {
1868
+ await this.engine.clearPendingCommit(rfcGroupId);
1869
+ return {
1870
+ displayId: res.display_id,
1871
+ rfcGroupId: res.rfc_group_id,
1872
+ currentEpoch: res.current_epoch,
1873
+ ownerUserId: res.owner_user_id ?? null,
1874
+ directKey: dk,
1875
+ name: null
1876
+ };
1877
+ }
1878
+ const group = {
1879
+ displayId: res.display_id,
1880
+ rfcGroupId: res.rfc_group_id,
1881
+ currentEpoch: res.current_epoch,
1882
+ ownerUserId: this.selfUserId,
1883
+ directKey: dk,
1884
+ name: null
1885
+ };
1886
+ const claimed = await claimKeyPackages(this.rt, peerUserId);
1887
+ await this.addMember(group, peerUserId, claimed);
1888
+ return group;
1889
+ }
1890
+ // ── Members ──
1891
+ /** The group roster (device leaves). The coordinator dedups to users. */
1892
+ async listMembers(displayId) {
1893
+ const res = await palbeRequest(
1894
+ this.rt,
1895
+ "GET",
1896
+ MessagingPaths.groupMembers(displayId)
1897
+ );
1898
+ return res.members;
1899
+ }
1900
+ /** Add a member: claim → stage the add commit → run the rebase loop. SP-1.6:
1901
+ * EVERY device of the user joins in ONE atomic commit (one shared Welcome). */
1902
+ async addMember(group, userId, claimed) {
1903
+ const claim = claimed ?? await claimKeyPackages(this.rt, userId);
1904
+ const adds = claim.key_packages.map((kp) => {
1905
+ try {
1906
+ return { deviceId: kp.device_id, kp: fromBase64(kp.key_package) };
1907
+ } catch {
1908
+ return null;
1909
+ }
1910
+ }).filter((x) => x !== null);
1911
+ if (adds.length === 0) {
1912
+ throw new BackendError("validation", {
1913
+ code: "no_keypackage",
1914
+ message: "no claimable key package for user",
1915
+ status: 422
1916
+ });
1917
+ }
1918
+ const userIds = adds.map(() => userId);
1919
+ await this.commitWithRebase(group.rfcGroupId, async () => {
1920
+ const out = await this.engine.commitChanges(
1921
+ fromBase64(group.rfcGroupId),
1922
+ adds.map((a) => a.kp),
1923
+ []
1924
+ );
1925
+ const welcomeB64 = out.welcome ? toBase64(out.welcome) : "";
1926
+ const welcomes = adds.map((a) => ({
1927
+ recipient_device_id: a.deviceId,
1928
+ recipient_user_id: userId,
1929
+ welcome_b64: welcomeB64
1930
+ }));
1931
+ const memberBody = {
1932
+ op: "add",
1933
+ commit_b64: toBase64(out.commit),
1934
+ target_device_ids: adds.map((a) => a.deviceId),
1935
+ leaf_indexes: out.addedLeafIndices,
1936
+ user_ids: userIds,
1937
+ key_package_refs: [],
1938
+ welcomes
1939
+ };
1940
+ return {
1941
+ method: "POST",
1942
+ path: MessagingPaths.groupMembers(group.displayId),
1943
+ body: memberBody
1944
+ };
1945
+ });
1946
+ }
1947
+ /** Remove a user = evict ALL their device leaves in ONE commit (SP-1.6 D4). */
1948
+ async removeMember(group, userId) {
1949
+ const members = await this.listMembers(group.displayId);
1950
+ const deviceIds = members.filter((m) => m.user_id === userId).map((m) => m.device_id);
1951
+ if (deviceIds.length === 0) {
1952
+ throw new BackendError("validation", {
1953
+ code: "not_a_member",
1954
+ message: "user has no device in this group",
1955
+ status: 404
1956
+ });
1957
+ }
1958
+ await this.stageMultiRemove(group, deviceIds, [userId]);
1959
+ }
1960
+ /** Leave = remove the CALLING device's leaf only (per-device self-leave). */
1961
+ async leaveGroup(group) {
1962
+ await this.stageMultiRemove(group, [this.selfDeviceId], [this.selfUserId]);
1963
+ }
1964
+ async stageMultiRemove(group, deviceIds, removedUserIds) {
1965
+ const pathDeviceId = deviceIds[0];
1966
+ if (!pathDeviceId) throw new Error("no device to remove");
1967
+ await this.commitWithRebase(group.rfcGroupId, async () => {
1968
+ const out = await this.engine.commitChanges(
1969
+ fromBase64(group.rfcGroupId),
1970
+ [],
1971
+ deviceIds.map(deviceIdBytes)
1972
+ );
1973
+ const body = {
1974
+ op: "remove",
1975
+ commit_b64: toBase64(out.commit),
1976
+ target_device_ids: deviceIds,
1977
+ user_ids: removedUserIds
1978
+ };
1979
+ return {
1980
+ method: "DELETE",
1981
+ path: MessagingPaths.groupMemberDevice(group.displayId, pathDeviceId),
1982
+ body
1983
+ };
1984
+ });
1985
+ }
1986
+ // ── Send ──
1987
+ /** Send a text message. Encrypt at the current epoch, POST, return the receipt +
1988
+ * the client-minted `clientMsgId` (needed by the Chat layer for reply indexing).
1989
+ * NEVER rebases (a 422 stale_application surfaces to the caller). */
1990
+ async sendText(group, text, replyTo) {
1991
+ const clientMsgId = mintClientMsgId();
1992
+ const plaintext = encodeEnvelope({ text, clientMsgId, replyTo });
1993
+ const ct = await this.engine.encryptApplication(fromBase64(group.rfcGroupId), plaintext);
1994
+ const body = {
1995
+ ciphertext_b64: toBase64(ct),
1996
+ client_idem_key: randomId()
1997
+ };
1998
+ const wire = await palbeRequest(
1999
+ this.rt,
2000
+ "POST",
2001
+ MessagingPaths.groupMessages(group.displayId),
2002
+ { body }
2003
+ );
2004
+ const stored = {
2005
+ id: `${group.rfcGroupId}#${wire.server_seq}`,
2006
+ direction: "outgoing",
2007
+ text,
2008
+ senderDeviceId: this.selfDeviceId,
2009
+ epoch: wire.epoch,
2010
+ serverSeq: wire.server_seq,
2011
+ at: Date.now(),
2012
+ clientMsgId,
2013
+ replyTo: replyTo ? {
2014
+ clientMsgId: replyTo.client_msg_id,
2015
+ previewBody: replyTo.preview?.body ?? null,
2016
+ previewAuthorUserId: replyTo.preview?.author_user_id ?? null,
2017
+ previewKind: replyTo.preview?.kind ?? "text"
2018
+ } : null
2019
+ };
2020
+ try {
2021
+ await this.messageStore.append(group.rfcGroupId, stored);
2022
+ } catch {
2023
+ }
2024
+ return { receipt: { serverSeq: wire.server_seq, epoch: wire.epoch }, clientMsgId };
2025
+ }
2026
+ // ── The rebase loop ──
2027
+ async commitWithRebase(rfcGroupId, build) {
2028
+ const gidBytes = fromBase64(rfcGroupId);
2029
+ let lastEpoch = null;
2030
+ for (let attempt = 1; attempt <= this.rebase.maxAttempts; attempt++) {
2031
+ const pending = await build();
2032
+ try {
2033
+ await palbeRequest(this.rt, pending.method, pending.path, {
2034
+ body: pending.body
2035
+ });
2036
+ await this.engine.applyPendingCommit(gidBytes);
2037
+ return;
2038
+ } catch (e) {
2039
+ if (isBackendError(e) && e.code === "epoch_conflict") {
2040
+ await this.engine.clearPendingCommit(gidBytes);
2041
+ const ce = e.data?.current_epoch;
2042
+ if (typeof ce === "number") lastEpoch = ce;
2043
+ if (attempt === this.rebase.maxAttempts) break;
2044
+ await this.drain(rfcGroupId);
2045
+ await this.backoff(attempt);
2046
+ continue;
2047
+ }
2048
+ throw e;
2049
+ }
2050
+ }
2051
+ await this.engine.clearPendingCommit(gidBytes);
2052
+ throw new RebaseExhausted(this.rebase.maxAttempts, lastEpoch);
2053
+ }
2054
+ /** Pull the device queue, process the winning commit(s) so the engine advances
2055
+ * to the new epoch, ack the commits (the delivery source owns app messages). */
2056
+ async drain(rfcGroupId) {
2057
+ const page = await palbeRequest(
2058
+ this.rt,
2059
+ "GET",
2060
+ MessagingPaths.deviceQueue(this.selfDeviceId)
2061
+ );
2062
+ const gidBytes = fromBase64(rfcGroupId);
2063
+ const ackIds = [];
2064
+ for (const row of page.messages) {
2065
+ if (row.content_type !== ContentType.commit) continue;
2066
+ try {
2067
+ await this.engine.processIncoming(gidBytes, fromBase64(row.ciphertext_b64));
2068
+ } catch {
2069
+ }
2070
+ ackIds.push(row.id);
2071
+ }
2072
+ if (ackIds.length > 0) {
2073
+ await palbeRequest(this.rt, "POST", MessagingPaths.deviceQueueAck(this.selfDeviceId), {
2074
+ body: { message_ids: ackIds }
2075
+ });
2076
+ }
2077
+ }
2078
+ async backoff(attempt) {
2079
+ const base = this.rebase.baseDelayMs * attempt;
2080
+ const jitter = this.rebase.jitterMs === 0 ? 0 : Math.random() * this.rebase.jitterMs;
2081
+ const total = base + jitter;
2082
+ if (total <= 0) return;
2083
+ await new Promise((r) => setTimeout(r, total));
2084
+ }
2085
+ };
2086
+
2087
+ // src/messaging/chat.ts
2088
+ var Chat = class {
2089
+ /** Stable, URL/log-safe id (the grp_ display id once active; a reserved local id while draft). */
2090
+ id;
2091
+ kind;
2092
+ _state;
2093
+ _group;
2094
+ _draft;
2095
+ backend;
2096
+ messageList = [];
2097
+ memberCache = [];
2098
+ typingList = [];
2099
+ presenceStates = /* @__PURE__ */ new Map();
2100
+ readWatermark = -1;
2101
+ titleOverride = null;
2102
+ seenKeys = /* @__PURE__ */ new Set();
2103
+ /** Index: clientMsgId → { text, senderUserId } for resolveReply lookups. */
2104
+ byClientMsgId = /* @__PURE__ */ new Map();
2105
+ loadedEarliestSeq = null;
2106
+ historyLoaded = false;
2107
+ wired = false;
2108
+ liveUnsub = null;
2109
+ listeners = /* @__PURE__ */ new Set();
2110
+ /** @internal — obtain via pb.messaging.directChat / groupChat / chat(id). */
2111
+ constructor(args) {
2112
+ this.backend = args.backend;
2113
+ if ("group" in args) {
2114
+ this.id = args.group.displayId;
2115
+ this.kind = args.kind;
2116
+ this._state = "active";
2117
+ this._group = args.group;
2118
+ this._draft = null;
2119
+ this.seedMembersFromGroup(args.group);
2120
+ } else {
2121
+ this.id = args.draft.reservedId;
2122
+ this.kind = args.kind;
2123
+ this._state = "draft";
2124
+ this._group = null;
2125
+ this._draft = args.draft;
2126
+ this.seedDraftMembers(args.draft);
2127
+ }
2128
+ }
2129
+ // ── Observability ──
2130
+ /** Subscribe to any change in this chat (messages/members/typing/state). */
2131
+ onChange(cb) {
2132
+ this.listeners.add(cb);
2133
+ this.ensureWired();
2134
+ return () => this.listeners.delete(cb);
2135
+ }
2136
+ emit() {
2137
+ for (const cb of this.listeners) cb();
2138
+ }
2139
+ // ── Snapshot getters ──
2140
+ get state() {
2141
+ return this._state;
2142
+ }
2143
+ get isDirect() {
2144
+ return this.kind === "direct";
2145
+ }
2146
+ get messages() {
2147
+ return this.messageList;
2148
+ }
2149
+ get members() {
2150
+ return this.memberCache;
2151
+ }
2152
+ get typing() {
2153
+ return this.typingList;
2154
+ }
2155
+ get lastMessage() {
2156
+ return this.messageList.at(-1) ?? null;
2157
+ }
2158
+ get unreadCount() {
2159
+ return this.messageList.filter(
2160
+ (m) => m.direction === "incoming" && m.serverSeq > this.readWatermark
2161
+ ).length;
2162
+ }
2163
+ get title() {
2164
+ if (this.titleOverride) return this.titleOverride;
2165
+ if (this._group?.name) return this._group.name;
2166
+ if (this._draft) {
2167
+ const mode = this._draft.mode;
2168
+ if (mode.kind === "direct") {
2169
+ const peer = this.memberCache.find((m) => m.userId === mode.peerUserId);
2170
+ return peer?.displayName ?? "Chat";
2171
+ }
2172
+ return "Group";
2173
+ }
2174
+ const tail = this.id.startsWith("grp_") ? this.id.slice(4) : this.id;
2175
+ const short = tail.slice(-4);
2176
+ return short ? `Group ${short}` : "Group";
2177
+ }
2178
+ presence(userId) {
2179
+ return this.presenceStates.get(userId) ?? null;
2180
+ }
2181
+ // ── Wiring (live + history) ──
2182
+ ensureWired() {
2183
+ if (this.wired || this._state !== "active" || !this._group) return;
2184
+ this.wired = true;
2185
+ this.liveUnsub = this.backend.subscribeLive(this._group, this);
2186
+ void this.hydrateHistory();
2187
+ void this.refreshMembers();
2188
+ }
2189
+ async hydrateHistory() {
2190
+ if (this.historyLoaded || !this._group) return;
2191
+ this.historyLoaded = true;
2192
+ const entries = await this.backend.history(this._group, 50);
2193
+ this.mergeHistory(entries);
2194
+ }
2195
+ mergeHistory(incoming) {
2196
+ let changed = false;
2197
+ for (const m of incoming) {
2198
+ if (m.serverSeq <= 0) continue;
2199
+ if (m.clientMsgId && m.text !== null) {
2200
+ this.byClientMsgId.set(m.clientMsgId, {
2201
+ text: m.text,
2202
+ senderUserId: m.senderUserId ?? ""
2203
+ });
2204
+ }
2205
+ }
2206
+ for (const m of incoming) {
2207
+ if (m.serverSeq <= 0) continue;
2208
+ const key = this.internalKey(m.serverSeq);
2209
+ if (this.seenKeys.has(key)) continue;
2210
+ this.seenKeys.add(key);
2211
+ this.messageList.push(m);
2212
+ changed = true;
2213
+ this.loadedEarliestSeq = Math.min(this.loadedEarliestSeq ?? m.serverSeq, m.serverSeq);
2214
+ }
2215
+ if (changed) {
2216
+ this.messageList.sort((a, b) => a.serverSeq - b.serverSeq);
2217
+ this.emit();
2218
+ }
2219
+ }
2220
+ /** @internal — called by the backend's live subscription. */
2221
+ async ingestLive(incoming) {
2222
+ if (!this._group) return;
2223
+ if (incoming.kind === "commit") {
2224
+ await this.refreshMembers();
2225
+ return;
2226
+ }
2227
+ if (incoming.serverSeq <= 0) return;
2228
+ const key = this.internalKey(incoming.serverSeq);
2229
+ if (this.seenKeys.has(key)) return;
2230
+ this.seenKeys.add(key);
2231
+ let senderUser = null;
2232
+ if (incoming.senderDeviceId) {
2233
+ senderUser = await this.backend.userIdForDevice(this._group, incoming.senderDeviceId);
2234
+ }
2235
+ const direction = senderUser !== null && senderUser === this.backend.selfUserId ? "outgoing" : "incoming";
2236
+ const incomingClientMsgId = incoming.clientMsgId;
2237
+ const incomingReplyRef = incoming.replyRef;
2238
+ let resolvedReplyTo = null;
2239
+ if (incomingReplyRef) {
2240
+ resolvedReplyTo = resolveReply(incomingReplyRef, (id) => this.byClientMsgId.get(id) ?? null);
2241
+ }
2242
+ const msg = {
2243
+ id: this.publicId(incoming.serverSeq),
2244
+ kind: this.kindOf(incoming),
2245
+ direction,
2246
+ senderUserId: senderUser,
2247
+ text: incoming.text,
2248
+ serverSeq: incoming.serverSeq,
2249
+ sentAt: incoming.receivedAt,
2250
+ clientMsgId: incomingClientMsgId,
2251
+ replyTo: resolvedReplyTo
2252
+ };
2253
+ if (incomingClientMsgId && incoming.text !== null) {
2254
+ this.byClientMsgId.set(incomingClientMsgId, {
2255
+ text: incoming.text,
2256
+ senderUserId: senderUser ?? ""
2257
+ });
2258
+ }
2259
+ this.messageList.push(msg);
2260
+ this.messageList.sort((a, b) => a.serverSeq - b.serverSeq);
2261
+ this.loadedEarliestSeq = Math.min(
2262
+ this.loadedEarliestSeq ?? incoming.serverSeq,
2263
+ incoming.serverSeq
2264
+ );
2265
+ this.emit();
2266
+ }
2267
+ /** @internal — called by the backend's conv subscription. */
2268
+ applyConv(event, payload) {
2269
+ const userId = typeof payload.user_id === "string" ? payload.user_id : null;
2270
+ if (!userId || userId === this.backend.selfUserId) return;
2271
+ if (event === "typing") {
2272
+ const isTyping = payload.typing === true;
2273
+ const member = this.memberCache.find((m) => m.userId === userId) ?? {
2274
+ id: userId,
2275
+ userId,
2276
+ displayName: null,
2277
+ role: "member",
2278
+ isSelf: false
2279
+ };
2280
+ this.typingList = isTyping ? [...this.typingList.filter((m) => m.userId !== userId), member] : this.typingList.filter((m) => m.userId !== userId);
2281
+ this.emit();
2282
+ } else if (event === "presence") {
2283
+ const online = payload.online === true;
2284
+ const ts = typeof payload.ts === "number" ? payload.ts : null;
2285
+ this.presenceStates.set(userId, {
2286
+ userId,
2287
+ isOnline: online,
2288
+ lastSeenAt: ts ? new Date(ts * 1e3) : null
2289
+ });
2290
+ this.emit();
2291
+ }
2292
+ }
2293
+ kindOf(incoming) {
2294
+ if (incoming.kind === "commit" || incoming.kind === "proposal" || incoming.kind === "welcome")
2295
+ return "system";
2296
+ return "text";
2297
+ }
2298
+ internalKey(serverSeq) {
2299
+ return this._group ? `${this._group.rfcGroupId}#${serverSeq}` : `draft#${serverSeq}`;
2300
+ }
2301
+ publicId(serverSeq) {
2302
+ return `${this.id}#${serverSeq}`;
2303
+ }
2304
+ /** Page older messages in. Returns how many were prepended. */
2305
+ async loadEarlier(limit = 50) {
2306
+ if (!this._group) return 0;
2307
+ const before = this.loadedEarliestSeq ?? void 0;
2308
+ const older = await this.backend.history(this._group, limit, before);
2309
+ const before0 = this.messageList.length;
2310
+ this.mergeHistory(older);
2311
+ return this.messageList.length - before0;
2312
+ }
2313
+ // ── Members ──
2314
+ async refreshMembers() {
2315
+ if (!this._group) return;
2316
+ const m = await this.backend.members(this._group);
2317
+ if (m.length > 0) {
2318
+ this.memberCache = m;
2319
+ this.emit();
2320
+ }
2321
+ }
2322
+ seedMembersFromGroup(group) {
2323
+ const seed = [
2324
+ {
2325
+ id: this.backend.selfUserId,
2326
+ userId: this.backend.selfUserId,
2327
+ displayName: null,
2328
+ role: "owner",
2329
+ isSelf: true
2330
+ }
2331
+ ];
2332
+ if (group.ownerUserId && group.ownerUserId !== this.backend.selfUserId) {
2333
+ seed.push({
2334
+ id: group.ownerUserId,
2335
+ userId: group.ownerUserId,
2336
+ displayName: null,
2337
+ role: "owner",
2338
+ isSelf: false
2339
+ });
2340
+ }
2341
+ this.memberCache = seed;
2342
+ }
2343
+ seedDraftMembers(draft) {
2344
+ const seed = [
2345
+ {
2346
+ id: this.backend.selfUserId,
2347
+ userId: this.backend.selfUserId,
2348
+ displayName: null,
2349
+ role: draft.mode.kind === "group" ? "owner" : "member",
2350
+ isSelf: true
2351
+ }
2352
+ ];
2353
+ if (draft.mode.kind === "direct") {
2354
+ seed.push({
2355
+ id: draft.mode.peerUserId,
2356
+ userId: draft.mode.peerUserId,
2357
+ displayName: null,
2358
+ role: "member",
2359
+ isSelf: false
2360
+ });
2361
+ } else {
2362
+ for (const uid of draft.mode.members) {
2363
+ seed.push({ id: uid, userId: uid, displayName: null, role: "member", isSelf: false });
2364
+ }
2365
+ }
2366
+ this.memberCache = seed;
2367
+ }
2368
+ // ── Send (with WhatsApp lazy materialize) ──
2369
+ async send(text, opts) {
2370
+ const group = await this.materializeIfNeeded();
2371
+ let replyRef = null;
2372
+ let resolvedReplyTo = null;
2373
+ const parent = opts?.replyTo;
2374
+ if (parent?.clientMsgId && parent.kind !== "system") {
2375
+ const [previewBody, wasTruncated] = parent.text ? truncateQuote(parent.text) : ["", false];
2376
+ replyRef = {
2377
+ v: 1,
2378
+ client_msg_id: parent.clientMsgId,
2379
+ preview: {
2380
+ kind: "text",
2381
+ author_user_id: parent.senderUserId ?? "",
2382
+ body: previewBody || void 0,
2383
+ body_truncated: wasTruncated
2384
+ }
2385
+ };
2386
+ resolvedReplyTo = resolveReply(replyRef, (id) => this.byClientMsgId.get(id) ?? null);
2387
+ }
2388
+ const { receipt, clientMsgId } = await this.backend.sendText(group, text, replyRef);
2389
+ this.appendOwnSend(text, receipt, clientMsgId, resolvedReplyTo);
2390
+ return receipt;
2391
+ }
2392
+ appendOwnSend(text, receipt, clientMsgId, resolvedReplyTo) {
2393
+ if (receipt.serverSeq <= 0) return;
2394
+ const key = this.internalKey(receipt.serverSeq);
2395
+ if (this.seenKeys.has(key)) return;
2396
+ this.seenKeys.add(key);
2397
+ if (clientMsgId) {
2398
+ this.byClientMsgId.set(clientMsgId, { text, senderUserId: this.backend.selfUserId });
2399
+ }
2400
+ this.messageList.push({
2401
+ id: this.publicId(receipt.serverSeq),
2402
+ kind: "text",
2403
+ direction: "outgoing",
2404
+ senderUserId: this.backend.selfUserId,
2405
+ text,
2406
+ serverSeq: receipt.serverSeq,
2407
+ sentAt: /* @__PURE__ */ new Date(),
2408
+ clientMsgId,
2409
+ replyTo: resolvedReplyTo
2410
+ });
2411
+ this.messageList.sort((a, b) => a.serverSeq - b.serverSeq);
2412
+ this.emit();
2413
+ }
2414
+ async materializeIfNeeded() {
2415
+ if (this._group) return this._group;
2416
+ if (!this._draft) throw new Error("messaging_not_configured");
2417
+ const group = await this.backend.materialize(this._draft);
2418
+ this._group = group;
2419
+ this.id = group.displayId;
2420
+ this._state = "active";
2421
+ this._draft = null;
2422
+ this.backend.registerActive(group);
2423
+ this.ensureWired();
2424
+ this.emit();
2425
+ return group;
2426
+ }
2427
+ // ── Membership (groups only) ──
2428
+ async addMemberUser(userId) {
2429
+ if (this.kind !== "group") throw new Error("chat_is_direct");
2430
+ const group = await this.materializeIfNeeded();
2431
+ await this.backend.addMember(group, userId);
2432
+ await this.refreshMembers();
2433
+ }
2434
+ async removeMemberUser(userId) {
2435
+ if (this.kind !== "group") throw new Error("chat_is_direct");
2436
+ if (!this._group) throw new Error("messaging_not_configured");
2437
+ await this.backend.removeMember(this._group, userId);
2438
+ await this.refreshMembers();
2439
+ }
2440
+ async leave() {
2441
+ if (!this._group) return;
2442
+ await this.backend.leave(this._group);
2443
+ this.liveUnsub?.();
2444
+ this.liveUnsub = null;
2445
+ }
2446
+ // ── Imperative ──
2447
+ setTyping(isTyping) {
2448
+ if (this._group) this.backend.setTyping(this._group, isTyping);
2449
+ }
2450
+ async markRead(message) {
2451
+ if (!this._group) return;
2452
+ await this.backend.markRead(this._group, message.serverSeq);
2453
+ this.readWatermark = Math.max(this.readWatermark, message.serverSeq);
2454
+ this.emit();
2455
+ }
2456
+ };
2457
+
2458
+ // src/messaging/delivery-source.ts
2459
+ var MessageHub = class {
2460
+ messageListeners = /* @__PURE__ */ new Map();
2461
+ convListeners = /* @__PURE__ */ new Map();
2462
+ onMessage(displayId, fn) {
2463
+ return this.add(this.messageListeners, displayId, fn);
2464
+ }
2465
+ emitMessage(displayId, m) {
2466
+ for (const fn of this.messageListeners.get(displayId) ?? []) fn(m);
2467
+ }
2468
+ onConv(displayId, fn) {
2469
+ return this.add(this.convListeners, displayId, fn);
2470
+ }
2471
+ emitConv(displayId, e) {
2472
+ for (const fn of this.convListeners.get(displayId) ?? []) fn(e);
2473
+ }
2474
+ add(map, key, fn) {
2475
+ let set = map.get(key);
2476
+ if (!set) {
2477
+ set = /* @__PURE__ */ new Set();
2478
+ map.set(key, set);
2479
+ }
2480
+ set.add(fn);
2481
+ return () => {
2482
+ set?.delete(fn);
2483
+ };
2484
+ }
2485
+ };
2486
+ var MessageDeliverySource = class {
2487
+ constructor(rt, engine, hub, registry, messageStore, deviceId, selfUserId) {
2488
+ this.rt = rt;
2489
+ this.engine = engine;
2490
+ this.hub = hub;
2491
+ this.registry = registry;
2492
+ this.messageStore = messageStore;
2493
+ this.deviceId = deviceId;
2494
+ this.selfUserId = selfUserId;
2495
+ }
2496
+ rt;
2497
+ engine;
2498
+ hub;
2499
+ registry;
2500
+ messageStore;
2501
+ deviceId;
2502
+ selfUserId;
2503
+ started = false;
2504
+ wakeUnsub = null;
2505
+ // Per-observed-group conv subscription + heartbeat (presence/typing/read).
2506
+ observed = /* @__PURE__ */ new Map();
2507
+ /** Subscribe the device wake topic + run an initial drain. Idempotent. */
2508
+ async start() {
2509
+ if (this.started) return;
2510
+ this.started = true;
2511
+ try {
2512
+ const channel = this.rt.realtime.channel(`messaging:device:${this.deviceId}`);
2513
+ const sub = channel.on("new_message", () => {
2514
+ void this.pumpOnce();
2515
+ });
2516
+ const offConn = this.rt.realtime.status.onChange((s) => {
2517
+ if (s.state === "connected") void this.pumpOnce();
2518
+ });
2519
+ this.wakeUnsub = () => {
2520
+ sub.cancel();
2521
+ offConn();
2522
+ };
2523
+ } catch {
2524
+ }
2525
+ await this.pumpOnce();
2526
+ }
2527
+ stop() {
2528
+ this.wakeUnsub?.();
2529
+ this.wakeUnsub = null;
2530
+ for (const [, o] of this.observed) {
2531
+ o.unsub();
2532
+ if (o.heartbeat) clearInterval(o.heartbeat);
2533
+ }
2534
+ this.observed.clear();
2535
+ this.started = false;
2536
+ }
2537
+ /** One full drain: welcomes → queue (decrypt + emit) → ack. Never throws. */
2538
+ async pumpOnce() {
2539
+ try {
2540
+ await this.pullWelcomes();
2541
+ } catch {
2542
+ }
2543
+ try {
2544
+ await this.pullQueue();
2545
+ } catch {
2546
+ }
2547
+ }
2548
+ // ── Welcomes ──
2549
+ async pullWelcomes() {
2550
+ const page = await palbeRequest(
2551
+ this.rt,
2552
+ "GET",
2553
+ MessagingPaths.deviceWelcomes(this.deviceId)
2554
+ );
2555
+ for (const w of page.welcomes) {
2556
+ try {
2557
+ const gidBytes = await this.engine.joinFromWelcome(fromBase64(w.welcome_b64));
2558
+ const group = {
2559
+ displayId: w.display_id,
2560
+ rfcGroupId: toBase64(gidBytes),
2561
+ currentEpoch: w.added_at_epoch,
2562
+ ownerUserId: null,
2563
+ directKey: null,
2564
+ name: null
2565
+ };
2566
+ this.registry.register(group);
2567
+ this.hub.emitMessage(group.displayId, {
2568
+ kind: "welcome",
2569
+ group,
2570
+ text: null,
2571
+ senderDeviceId: null,
2572
+ epoch: w.added_at_epoch,
2573
+ serverSeq: 0,
2574
+ receivedAt: /* @__PURE__ */ new Date(),
2575
+ clientMsgId: "",
2576
+ replyRef: null
2577
+ });
2578
+ } catch {
2579
+ const known = this.registry.all().some((g) => g.displayId === w.display_id);
2580
+ if (!known) {
2581
+ }
2582
+ }
2583
+ }
2584
+ }
2585
+ // ── Queue ──
2586
+ async pullQueue() {
2587
+ const page = await palbeRequest(
2588
+ this.rt,
2589
+ "GET",
2590
+ MessagingPaths.deviceQueue(this.deviceId)
2591
+ );
2592
+ const ackIds = [];
2593
+ for (const row of page.messages) {
2594
+ if (await this.process(row)) ackIds.push(row.id);
2595
+ }
2596
+ if (ackIds.length > 0) {
2597
+ await palbeRequest(this.rt, "POST", MessagingPaths.deviceQueueAck(this.deviceId), {
2598
+ body: { message_ids: ackIds }
2599
+ });
2600
+ }
2601
+ }
2602
+ /** Process one queue row; returns true if consumed (should ack). */
2603
+ async process(row) {
2604
+ let blob;
2605
+ try {
2606
+ blob = fromBase64(row.ciphertext_b64);
2607
+ } catch {
2608
+ return true;
2609
+ }
2610
+ const group = (row.rfc_group_id_b64 ? this.registry.group(row.rfc_group_id_b64) : void 0) ?? this.registry.soleGroup();
2611
+ if (!group) return false;
2612
+ try {
2613
+ const received = await this.engine.processIncoming(fromBase64(group.rfcGroupId), blob);
2614
+ if (received.type === "application") {
2615
+ const { text, clientMsgId, replyTo } = decodeEnvelope(received.data);
2616
+ const stored = {
2617
+ id: `${group.rfcGroupId}#${row.server_seq}`,
2618
+ direction: "incoming",
2619
+ text,
2620
+ senderDeviceId: row.sender_device_id ?? null,
2621
+ epoch: row.epoch,
2622
+ serverSeq: row.server_seq,
2623
+ at: Date.now(),
2624
+ clientMsgId: clientMsgId || void 0,
2625
+ replyTo: replyTo ? {
2626
+ clientMsgId: replyTo.client_msg_id,
2627
+ previewBody: replyTo.preview?.body ?? null,
2628
+ previewAuthorUserId: replyTo.preview?.author_user_id ?? null,
2629
+ previewKind: replyTo.preview?.kind ?? "text"
2630
+ } : null
2631
+ };
2632
+ try {
2633
+ await this.messageStore.append(group.rfcGroupId, stored);
2634
+ } catch {
2635
+ return false;
2636
+ }
2637
+ this.hub.emitMessage(group.displayId, {
2638
+ kind: "application",
2639
+ group,
2640
+ text,
2641
+ senderDeviceId: row.sender_device_id ?? null,
2642
+ epoch: row.epoch,
2643
+ serverSeq: row.server_seq,
2644
+ receivedAt: /* @__PURE__ */ new Date(),
2645
+ clientMsgId,
2646
+ replyRef: replyTo
2647
+ });
2648
+ return true;
2649
+ }
2650
+ if (received.type === "commitApplied") {
2651
+ const newEpoch = Math.max(row.epoch, group.currentEpoch + 1);
2652
+ this.registry.bumpEpoch(group.rfcGroupId, newEpoch);
2653
+ const fresh = this.registry.group(group.rfcGroupId) ?? group;
2654
+ this.hub.emitMessage(group.displayId, {
2655
+ kind: "commit",
2656
+ group: fresh,
2657
+ text: null,
2658
+ senderDeviceId: row.sender_device_id ?? null,
2659
+ epoch: newEpoch,
2660
+ serverSeq: 0,
2661
+ receivedAt: /* @__PURE__ */ new Date(),
2662
+ clientMsgId: "",
2663
+ replyRef: null
2664
+ });
2665
+ return true;
2666
+ }
2667
+ return true;
2668
+ } catch (e) {
2669
+ if (isOwnEchoOrConsumed(e)) return true;
2670
+ return false;
2671
+ }
2672
+ }
2673
+ // ── Conv topic (presence / typing / read) ──
2674
+ /** Begin observing a group's conv topic; fans events into the hub. Idempotent. */
2675
+ observeConversation(group) {
2676
+ if (this.observed.has(group.displayId)) return;
2677
+ try {
2678
+ const channel = this.rt.realtime.channel(`messaging:conv:${group.rfcGroupId}`);
2679
+ const subs = ["presence", "typing", "read"].map(
2680
+ (ev) => channel.on(ev, (payload) => {
2681
+ this.hub.emitConv(group.displayId, { event: ev, payload });
2682
+ })
2683
+ );
2684
+ const emitPresence = (online) => channel.send("presence", {
2685
+ user_id: this.selfUserId,
2686
+ device_id: this.deviceId,
2687
+ online,
2688
+ ts: Date.now() / 1e3
2689
+ });
2690
+ emitPresence(true);
2691
+ const heartbeat = setInterval(() => emitPresence(true), 25e3);
2692
+ this.observed.set(group.displayId, {
2693
+ unsub: () => {
2694
+ for (const s of subs) s.cancel();
2695
+ },
2696
+ heartbeat
2697
+ });
2698
+ } catch {
2699
+ }
2700
+ }
2701
+ /** Announce typing on a group's conv topic (the app calls per keystroke). */
2702
+ setTyping(group, isTyping) {
2703
+ this.observeConversation(group);
2704
+ try {
2705
+ this.rt.realtime.channel(`messaging:conv:${group.rfcGroupId}`).send("typing", {
2706
+ user_id: this.selfUserId,
2707
+ device_id: this.deviceId,
2708
+ typing: isTyping
2709
+ });
2710
+ } catch {
2711
+ }
2712
+ }
2713
+ /** Advance this device's read cursor (idempotent; the server clamps). */
2714
+ async markRead(group, upToServerSeq) {
2715
+ await palbeRequest(this.rt, "POST", MessagingPaths.groupRead(group.displayId), {
2716
+ body: { read_seq: upToServerSeq, read_epoch: group.currentEpoch, is_private: false }
2717
+ });
2718
+ }
2719
+ };
2720
+ function isOwnEchoOrConsumed(e) {
2721
+ const msg = e instanceof Error ? e.message : String(e);
2722
+ return msg.includes("message from self") || msg.includes("key not available, invalid generation");
2723
+ }
2724
+
2725
+ // src/messaging/history.ts
2726
+ var MessageStore = class {
2727
+ constructor(kv) {
2728
+ this.kv = kv;
2729
+ }
2730
+ kv;
2731
+ key(rfcGroupId) {
2732
+ return `msgs:${rfcGroupId}`;
2733
+ }
2734
+ /** Append (idempotent on id) and persist the group's transcript. */
2735
+ async append(rfcGroupId, msg) {
2736
+ const list = await this.load(rfcGroupId);
2737
+ if (list.some((m) => m.id === msg.id)) return;
2738
+ list.push(msg);
2739
+ list.sort((a, b) => a.serverSeq - b.serverSeq);
2740
+ await this.save(rfcGroupId, list);
2741
+ }
2742
+ /** True if a message id is already durable (the consumed-key recovery check). */
2743
+ async contains(rfcGroupId, id) {
2744
+ const list = await this.load(rfcGroupId);
2745
+ return list.some((m) => m.id === id);
2746
+ }
2747
+ /**
2748
+ * Read history for a group, newest-last. `before` pages older: returns up to
2749
+ * `limit` rows with serverSeq < before. nil `before` returns the newest page.
2750
+ */
2751
+ async history(rfcGroupId, limit, before) {
2752
+ const list = await this.load(rfcGroupId);
2753
+ const filtered = before == null ? list : list.filter((m) => m.serverSeq < before);
2754
+ return filtered.slice(Math.max(0, filtered.length - limit));
2755
+ }
2756
+ async load(rfcGroupId) {
2757
+ const raw = await this.kv.get(this.key(rfcGroupId));
2758
+ if (!raw) return [];
2759
+ try {
2760
+ return JSON.parse(decodeUtf8(raw));
2761
+ } catch {
2762
+ return [];
2763
+ }
2764
+ }
2765
+ async save(rfcGroupId, list) {
2766
+ await this.kv.set(this.key(rfcGroupId), encodeUtf8(JSON.stringify(list)));
2767
+ }
2768
+ async wipe() {
2769
+ for (const k of await this.kv.keys("msgs:")) await this.kv.delete(k);
2770
+ }
2771
+ };
2772
+ var GroupCatalog = class {
2773
+ constructor(kv) {
2774
+ this.kv = kv;
2775
+ }
2776
+ kv;
2777
+ async upsert(g) {
2778
+ await this.kv.set(`cat:${g.rfcGroupId}`, encodeUtf8(JSON.stringify(g)));
2779
+ }
2780
+ async remove(rfcGroupId) {
2781
+ await this.kv.delete(`cat:${rfcGroupId}`);
2782
+ }
2783
+ async all() {
2784
+ const keys = await this.kv.keys("cat:");
2785
+ const out = [];
2786
+ for (const k of keys) {
2787
+ const raw = await this.kv.get(k);
2788
+ if (!raw) continue;
2789
+ try {
2790
+ out.push(JSON.parse(decodeUtf8(raw)));
2791
+ } catch {
2792
+ }
2793
+ }
2794
+ return out;
2795
+ }
2796
+ async wipe() {
2797
+ for (const k of await this.kv.keys("cat:")) await this.kv.delete(k);
2798
+ }
2799
+ };
2800
+
2801
+ // src/messaging/wasm/pkg/palbe_mls_bg.js
2802
+ var palbe_mls_bg_exports = {};
2803
+ __export(palbe_mls_bg_exports, {
2804
+ MlsClient: () => MlsClient,
2805
+ MlsGroup: () => MlsGroup,
2806
+ __wbg_BigInt_231999d28f899902: () => __wbg_BigInt_231999d28f899902,
2807
+ __wbg_Error_fdd633d4bb5dd76a: () => __wbg_Error_fdd633d4bb5dd76a,
2808
+ __wbg_String_8564e559799eccda: () => __wbg_String_8564e559799eccda,
2809
+ __wbg___wbindgen_bigint_get_as_i64_d9e915702856f831: () => __wbg___wbindgen_bigint_get_as_i64_d9e915702856f831,
2810
+ __wbg___wbindgen_debug_string_8a447059637473e2: () => __wbg___wbindgen_debug_string_8a447059637473e2,
2811
+ __wbg___wbindgen_is_function_acc5528be2b923f2: () => __wbg___wbindgen_is_function_acc5528be2b923f2,
2812
+ __wbg___wbindgen_is_null_6d937fbfb6478470: () => __wbg___wbindgen_is_null_6d937fbfb6478470,
2813
+ __wbg___wbindgen_is_object_0beba4a1980d3eea: () => __wbg___wbindgen_is_object_0beba4a1980d3eea,
2814
+ __wbg___wbindgen_is_string_1fca8072260dd261: () => __wbg___wbindgen_is_string_1fca8072260dd261,
2815
+ __wbg___wbindgen_is_undefined_721f8decd50c87a3: () => __wbg___wbindgen_is_undefined_721f8decd50c87a3,
2816
+ __wbg___wbindgen_jsval_eq_4e8c38722cb8ff51: () => __wbg___wbindgen_jsval_eq_4e8c38722cb8ff51,
2817
+ __wbg___wbindgen_memory_9751d9a3017e7c25: () => __wbg___wbindgen_memory_9751d9a3017e7c25,
2818
+ __wbg___wbindgen_number_get_1cc01dd708740256: () => __wbg___wbindgen_number_get_1cc01dd708740256,
2819
+ __wbg___wbindgen_string_get_71bb4348194e31f0: () => __wbg___wbindgen_string_get_71bb4348194e31f0,
2820
+ __wbg___wbindgen_throw_ea4887a5f8f9a9db: () => __wbg___wbindgen_throw_ea4887a5f8f9a9db,
2821
+ __wbg_buffer_7447b9cc2267a9e5: () => __wbg_buffer_7447b9cc2267a9e5,
2822
+ __wbg_call_67f43c91d09298f2: () => __wbg_call_67f43c91d09298f2,
2823
+ __wbg_call_b51415974987aa44: () => __wbg_call_b51415974987aa44,
2824
+ __wbg_crypto_38df2bab126b63dc: () => __wbg_crypto_38df2bab126b63dc,
2825
+ __wbg_delete_21fafcbc7bb82c85: () => __wbg_delete_21fafcbc7bb82c85,
2826
+ __wbg_epoch_15c000ffe3004541: () => __wbg_epoch_15c000ffe3004541,
2827
+ __wbg_error_a6fa202b58aa1cd3: () => __wbg_error_a6fa202b58aa1cd3,
2828
+ __wbg_getRandomValues_c44a50d8cfdaebeb: () => __wbg_getRandomValues_c44a50d8cfdaebeb,
2829
+ __wbg_get_615446055a48103f: () => __wbg_get_615446055a48103f,
2830
+ __wbg_globalThis_6d268067835e6709: () => __wbg_globalThis_6d268067835e6709,
2831
+ __wbg_global_3fe6c6c8ad6e6fb2: () => __wbg_global_3fe6c6c8ad6e6fb2,
2832
+ __wbg_insert_2d611f2bf9b60fee: () => __wbg_insert_2d611f2bf9b60fee,
2833
+ __wbg_instanceof_Error_38f854307ecab4ce: () => __wbg_instanceof_Error_38f854307ecab4ce,
2834
+ __wbg_length_c552db98817b9523: () => __wbg_length_c552db98817b9523,
2835
+ __wbg_maxEpochId_0b176768b2f352fb: () => __wbg_maxEpochId_0b176768b2f352fb,
2836
+ __wbg_message_a05fcf872473ffc5: () => __wbg_message_a05fcf872473ffc5,
2837
+ __wbg_msCrypto_bd5a034af96bcba6: () => __wbg_msCrypto_bd5a034af96bcba6,
2838
+ __wbg_new_227d7c05414eb861: () => __wbg_new_227d7c05414eb861,
2839
+ __wbg_new_32de5cbf49ca7dcb: () => __wbg_new_32de5cbf49ca7dcb,
2840
+ __wbg_new_364c96143b8f3496: () => __wbg_new_364c96143b8f3496,
2841
+ __wbg_new_b8b71ca8104fc178: () => __wbg_new_b8b71ca8104fc178,
2842
+ __wbg_new_d9762fd75876aafe: () => __wbg_new_d9762fd75876aafe,
2843
+ __wbg_new_no_args_4010ad257320fa4f: () => __wbg_new_no_args_4010ad257320fa4f,
2844
+ __wbg_new_with_byte_offset_and_length_8b21e3b1308deb48: () => __wbg_new_with_byte_offset_and_length_8b21e3b1308deb48,
2845
+ __wbg_new_with_length_5fdafe029be917a5: () => __wbg_new_with_length_5fdafe029be917a5,
2846
+ __wbg_node_84ea875411254db1: () => __wbg_node_84ea875411254db1,
2847
+ __wbg_process_44c7a14e11e9f69e: () => __wbg_process_44c7a14e11e9f69e,
2848
+ __wbg_push_1303ce035391aed3: () => __wbg_push_1303ce035391aed3,
2849
+ __wbg_randomFillSync_6c25eac9869eb53c: () => __wbg_randomFillSync_6c25eac9869eb53c,
2850
+ __wbg_require_b4edbdcf3e2a1ef0: () => __wbg_require_b4edbdcf3e2a1ef0,
2851
+ __wbg_self_1035a7cbd1b0d959: () => __wbg_self_1035a7cbd1b0d959,
2852
+ __wbg_set_047d1ea37bb67c19: () => __wbg_set_047d1ea37bb67c19,
2853
+ __wbg_set_1ddc4b8cd44d0da4: () => __wbg_set_1ddc4b8cd44d0da4,
2854
+ __wbg_set_28cba565792ec75f: () => __wbg_set_28cba565792ec75f,
2855
+ __wbg_set_6be42768c690e380: () => __wbg_set_6be42768c690e380,
2856
+ __wbg_set_name_b4c29a3a72ebbddc: () => __wbg_set_name_b4c29a3a72ebbddc,
2857
+ __wbg_set_wasm: () => __wbg_set_wasm,
2858
+ __wbg_stack_3b0d974bbf31e44f: () => __wbg_stack_3b0d974bbf31e44f,
2859
+ __wbg_state_bd20456c0f3efbc9: () => __wbg_state_bd20456c0f3efbc9,
2860
+ __wbg_subarray_e0162dcdea48eb3a: () => __wbg_subarray_e0162dcdea48eb3a,
2861
+ __wbg_versions_276b2795b1c6a219: () => __wbg_versions_276b2795b1c6a219,
2862
+ __wbg_window_9c17850b5e99c0ab: () => __wbg_window_9c17850b5e99c0ab,
2863
+ __wbg_write_56b1cf5bb0e780d6: () => __wbg_write_56b1cf5bb0e780d6,
2864
+ __wbindgen_cast_0000000000000001: () => __wbindgen_cast_0000000000000001,
2865
+ __wbindgen_cast_0000000000000002: () => __wbindgen_cast_0000000000000002,
2866
+ __wbindgen_cast_0000000000000003: () => __wbindgen_cast_0000000000000003,
2867
+ __wbindgen_init_externref_table: () => __wbindgen_init_externref_table,
2868
+ generateClient: () => generateClient,
2869
+ setPanicHook: () => setPanicHook
2870
+ });
2871
+ var MlsClient = class _MlsClient {
2872
+ static __wrap(ptr) {
2873
+ const obj = Object.create(_MlsClient.prototype);
2874
+ obj.__wbg_ptr = ptr;
2875
+ MlsClientFinalization.register(obj, obj.__wbg_ptr, obj);
2876
+ return obj;
2877
+ }
2878
+ __destroy_into_raw() {
2879
+ const ptr = this.__wbg_ptr;
2880
+ this.__wbg_ptr = 0;
2881
+ MlsClientFinalization.unregister(this);
2882
+ return ptr;
2883
+ }
2884
+ free() {
2885
+ const ptr = this.__destroy_into_raw();
2886
+ wasm.__wbg_mlsclient_free(ptr, 0);
2887
+ }
2888
+ /**
2889
+ * The MLS Capabilities wire bytes (the keydir's `capabilities`).
2890
+ * @returns {Uint8Array}
2891
+ */
2892
+ capabilities() {
2893
+ const ret = wasm.mlsclient_capabilities(this.__wbg_ptr);
2894
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
2895
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
2896
+ return v1;
2897
+ }
2898
+ /**
2899
+ * Create a new group at epoch 0. `groupId` `undefined`/`null` lets mls-rs
2900
+ * mint a globally-unique id (the rfc_group_id routing key).
2901
+ * @param {Uint8Array | null} [group_id]
2902
+ * @returns {MlsGroup}
2903
+ */
2904
+ createGroup(group_id) {
2905
+ var ptr0 = isLikeNone(group_id) ? 0 : passArray8ToWasm0(group_id, wasm.__wbindgen_malloc);
2906
+ var len0 = WASM_VECTOR_LEN;
2907
+ const ret = wasm.mlsclient_createGroup(this.__wbg_ptr, ptr0, len0);
2908
+ if (ret[2]) {
2909
+ throw takeFromExternrefTable0(ret[1]);
2910
+ }
2911
+ return MlsGroup.__wrap(ret[0]);
2912
+ }
2913
+ /**
2914
+ * The MLS Credential wire bytes (the keydir's `credential`).
2915
+ * @returns {Uint8Array}
2916
+ */
2917
+ credential() {
2918
+ const ret = wasm.mlsclient_credential(this.__wbg_ptr);
2919
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
2920
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
2921
+ return v1;
2922
+ }
2923
+ /**
2924
+ * The MLS-encoded enrollment material `{ signatureKey, credential,
2925
+ * capabilities }` (byte fields as arrays-of-numbers — wrap in Uint8Array as
2926
+ * needed). Base64 these into the SP-0 keydir enroll body.
2927
+ * @returns {any}
2928
+ */
2929
+ enrollmentMaterial() {
2930
+ const ret = wasm.mlsclient_enrollmentMaterial(this.__wbg_ptr);
2931
+ if (ret[2]) {
2932
+ throw takeFromExternrefTable0(ret[1]);
2933
+ }
2934
+ return takeFromExternrefTable0(ret[0]);
2935
+ }
2936
+ /**
2937
+ * Mint a one-use KeyPackage (RAW RFC 9420 §10 bytes) — uploaded to the
2938
+ * keydir, consumed by an adder's `addMember`.
2939
+ * @returns {Uint8Array}
2940
+ */
2941
+ generateKeyPackage() {
2942
+ const ret = wasm.mlsclient_generateKeyPackage(this.__wbg_ptr);
2943
+ if (ret[3]) {
2944
+ throw takeFromExternrefTable0(ret[2]);
2945
+ }
2946
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
2947
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
2948
+ return v1;
2949
+ }
2950
+ /**
2951
+ * Join a group from a Welcome (opaque MLSMessage bytes).
2952
+ * @param {Uint8Array} welcome
2953
+ * @returns {MlsGroup}
2954
+ */
2955
+ joinGroup(welcome) {
2956
+ const ptr0 = passArray8ToWasm0(welcome, wasm.__wbindgen_malloc);
2957
+ const len0 = WASM_VECTOR_LEN;
2958
+ const ret = wasm.mlsclient_joinGroup(this.__wbg_ptr, ptr0, len0);
2959
+ if (ret[2]) {
2960
+ throw takeFromExternrefTable0(ret[1]);
2961
+ }
2962
+ return MlsGroup.__wrap(ret[0]);
2963
+ }
2964
+ /**
2965
+ * Re-hydrate a previously-persisted group by its `groupId` (restart-safe
2966
+ * reload of an already-joined group).
2967
+ * @param {Uint8Array} group_id
2968
+ * @returns {MlsGroup}
2969
+ */
2970
+ loadGroup(group_id) {
2971
+ const ptr0 = passArray8ToWasm0(group_id, wasm.__wbindgen_malloc);
2972
+ const len0 = WASM_VECTOR_LEN;
2973
+ const ret = wasm.mlsclient_loadGroup(this.__wbg_ptr, ptr0, len0);
2974
+ if (ret[2]) {
2975
+ throw takeFromExternrefTable0(ret[1]);
2976
+ }
2977
+ return MlsGroup.__wrap(ret[0]);
2978
+ }
2979
+ /**
2980
+ * The serialized STABLE signature keypair (secret-bearing). Persist sealed
2981
+ * and pass back as `signatureKeypair` on restart so the device keeps the
2982
+ * SAME MLS signing identity.
2983
+ * @returns {Uint8Array}
2984
+ */
2985
+ signatureKeypair() {
2986
+ const ret = wasm.mlsclient_signatureKeypair(this.__wbg_ptr);
2987
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
2988
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
2989
+ return v1;
2990
+ }
2991
+ /**
2992
+ * The device's STABLE signature PUBLIC key bytes (the keydir's
2993
+ * `signature_key`).
2994
+ * @returns {Uint8Array}
2995
+ */
2996
+ signaturePublicKey() {
2997
+ const ret = wasm.mlsclient_signaturePublicKey(this.__wbg_ptr);
2998
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
2999
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
3000
+ return v1;
3001
+ }
3002
+ };
3003
+ if (Symbol.dispose) MlsClient.prototype[Symbol.dispose] = MlsClient.prototype.free;
3004
+ var MlsGroup = class _MlsGroup {
3005
+ static __wrap(ptr) {
3006
+ const obj = Object.create(_MlsGroup.prototype);
3007
+ obj.__wbg_ptr = ptr;
3008
+ MlsGroupFinalization.register(obj, obj.__wbg_ptr, obj);
3009
+ return obj;
3010
+ }
3011
+ __destroy_into_raw() {
3012
+ const ptr = this.__wbg_ptr;
3013
+ this.__wbg_ptr = 0;
3014
+ MlsGroupFinalization.unregister(this);
3015
+ return ptr;
3016
+ }
3017
+ free() {
3018
+ const ptr = this.__destroy_into_raw();
3019
+ wasm.__wbg_mlsgroup_free(ptr, 0);
3020
+ }
3021
+ /**
3022
+ * Stage a member add. Returns `{ commit, welcome?, addedLeafIndices }`
3023
+ * (byte fields as arrays-of-numbers). The group is PENDING after this.
3024
+ * @param {Uint8Array} key_package_msg
3025
+ * @returns {any}
3026
+ */
3027
+ addMember(key_package_msg) {
3028
+ const ptr0 = passArray8ToWasm0(key_package_msg, wasm.__wbindgen_malloc);
3029
+ const len0 = WASM_VECTOR_LEN;
3030
+ const ret = wasm.mlsgroup_addMember(this.__wbg_ptr, ptr0, len0);
3031
+ if (ret[2]) {
3032
+ throw takeFromExternrefTable0(ret[1]);
3033
+ }
3034
+ return takeFromExternrefTable0(ret[0]);
3035
+ }
3036
+ /**
3037
+ * Apply the staged commit (advances the local epoch). Call ONLY after the
3038
+ * server accepted the commit.
3039
+ */
3040
+ applyPendingCommit() {
3041
+ const ret = wasm.mlsgroup_applyPendingCommit(this.__wbg_ptr);
3042
+ if (ret[1]) {
3043
+ throw takeFromExternrefTable0(ret[0]);
3044
+ }
3045
+ }
3046
+ /**
3047
+ * Discard the staged commit without advancing (the 409-rebase path).
3048
+ */
3049
+ clearPendingCommit() {
3050
+ wasm.mlsgroup_clearPendingCommit(this.__wbg_ptr);
3051
+ }
3052
+ /**
3053
+ * Stage a SINGLE atomic add+remove commit. `addKeyPackages` is an
3054
+ * `Array<Uint8Array>` of one-use key packages; `removeMemberIds` an
3055
+ * `Array<Uint8Array>` of member identity bytes. Returns the same
3056
+ * `AddResult` shape as `addMember`. PENDING after this.
3057
+ * @param {Uint8Array[]} add_key_packages
3058
+ * @param {Uint8Array[]} remove_member_ids
3059
+ * @returns {any}
3060
+ */
3061
+ commitChanges(add_key_packages, remove_member_ids) {
3062
+ const ptr0 = passArrayJsValueToWasm0(add_key_packages, wasm.__wbindgen_malloc);
3063
+ const len0 = WASM_VECTOR_LEN;
3064
+ const ptr1 = passArrayJsValueToWasm0(remove_member_ids, wasm.__wbindgen_malloc);
3065
+ const len1 = WASM_VECTOR_LEN;
3066
+ const ret = wasm.mlsgroup_commitChanges(this.__wbg_ptr, ptr0, len0, ptr1, len1);
3067
+ if (ret[2]) {
3068
+ throw takeFromExternrefTable0(ret[1]);
3069
+ }
3070
+ return takeFromExternrefTable0(ret[0]);
3071
+ }
3072
+ /**
3073
+ * The current (applied) epoch as a JS BigInt (u64 exceeds safe-integer).
3074
+ * @returns {bigint}
3075
+ */
3076
+ currentEpoch() {
3077
+ const ret = wasm.mlsgroup_currentEpoch(this.__wbg_ptr);
3078
+ return BigInt.asUintN(64, ret);
3079
+ }
3080
+ /**
3081
+ * Inspect an incoming Commit and return the membership delta it WOULD
3082
+ * effect WITHOUT applying it (the reconciliation gate's crypto-truth).
3083
+ * Returns `{ kind, addedIdentities, removedIdentities }`.
3084
+ * @param {Uint8Array} commit_msg
3085
+ * @returns {any}
3086
+ */
3087
+ describeIncomingCommit(commit_msg) {
3088
+ const ptr0 = passArray8ToWasm0(commit_msg, wasm.__wbindgen_malloc);
3089
+ const len0 = WASM_VECTOR_LEN;
3090
+ const ret = wasm.mlsgroup_describeIncomingCommit(this.__wbg_ptr, ptr0, len0);
3091
+ if (ret[2]) {
3092
+ throw takeFromExternrefTable0(ret[1]);
3093
+ }
3094
+ return takeFromExternrefTable0(ret[0]);
3095
+ }
3096
+ /**
3097
+ * Encrypt an application message at the current epoch. Returns the opaque
3098
+ * MLSMessage bytes (PrivateMessage).
3099
+ * @param {Uint8Array} plaintext
3100
+ * @returns {Uint8Array}
3101
+ */
3102
+ encryptApplicationMessage(plaintext) {
3103
+ const ptr0 = passArray8ToWasm0(plaintext, wasm.__wbindgen_malloc);
3104
+ const len0 = WASM_VECTOR_LEN;
3105
+ const ret = wasm.mlsgroup_encryptApplicationMessage(this.__wbg_ptr, ptr0, len0);
3106
+ if (ret[3]) {
3107
+ throw takeFromExternrefTable0(ret[2]);
3108
+ }
3109
+ var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
3110
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
3111
+ return v2;
3112
+ }
3113
+ /**
3114
+ * Derive the group's current-epoch exporter secret (RFC 9420 §8.5) — the SAME
3115
+ * engine call iOS uses (uniffi `exportSecret`), so a cross-platform call/group
3116
+ * derives a byte-identical key. The SP-4 web call sets this as the LiveKit
3117
+ * frame key (server + SFU stay blind); also group-name/media sealing.
3118
+ * @param {Uint8Array} label
3119
+ * @param {Uint8Array} context
3120
+ * @param {bigint} len
3121
+ * @returns {Uint8Array}
3122
+ */
3123
+ exportSecret(label, context, len) {
3124
+ const ptr0 = passArray8ToWasm0(label, wasm.__wbindgen_malloc);
3125
+ const len0 = WASM_VECTOR_LEN;
3126
+ const ptr1 = passArray8ToWasm0(context, wasm.__wbindgen_malloc);
3127
+ const len1 = WASM_VECTOR_LEN;
3128
+ const ret = wasm.mlsgroup_exportSecret(this.__wbg_ptr, ptr0, len0, ptr1, len1, len);
3129
+ if (ret[3]) {
3130
+ throw takeFromExternrefTable0(ret[2]);
3131
+ }
3132
+ var v3 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
3133
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
3134
+ return v3;
3135
+ }
3136
+ /**
3137
+ * The MLS group id (the rfc_group_id routing key / storage key).
3138
+ * @returns {Uint8Array}
3139
+ */
3140
+ groupId() {
3141
+ const ret = wasm.mlsgroup_groupId(this.__wbg_ptr);
3142
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
3143
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
3144
+ return v1;
3145
+ }
3146
+ /**
3147
+ * True while a staged (unapplied) commit exists.
3148
+ * @returns {boolean}
3149
+ */
3150
+ hasPendingCommit() {
3151
+ const ret = wasm.mlsgroup_hasPendingCommit(this.__wbg_ptr);
3152
+ return ret !== 0;
3153
+ }
3154
+ /**
3155
+ * Process an incoming MLSMessage and apply its effect. Returns a
3156
+ * `ReceivedMessage` plain object (serde-tagged enum): e.g.
3157
+ * `{ Application: { sender, data } }`, `{ CommitApplied: { epoch,
3158
+ * removedSelf } }`, `"Proposal"`, or `"Other"`.
3159
+ * @param {Uint8Array} message
3160
+ * @returns {any}
3161
+ */
3162
+ processIncomingMessage(message) {
3163
+ const ptr0 = passArray8ToWasm0(message, wasm.__wbindgen_malloc);
3164
+ const len0 = WASM_VECTOR_LEN;
3165
+ const ret = wasm.mlsgroup_processIncomingMessage(this.__wbg_ptr, ptr0, len0);
3166
+ if (ret[2]) {
3167
+ throw takeFromExternrefTable0(ret[1]);
3168
+ }
3169
+ return takeFromExternrefTable0(ret[0]);
3170
+ }
3171
+ /**
3172
+ * Stage a member remove (by the member's identity bytes). Returns
3173
+ * `{ commit }`. PENDING after this.
3174
+ * @param {Uint8Array} member_id
3175
+ * @returns {any}
3176
+ */
3177
+ removeMember(member_id) {
3178
+ const ptr0 = passArray8ToWasm0(member_id, wasm.__wbindgen_malloc);
3179
+ const len0 = WASM_VECTOR_LEN;
3180
+ const ret = wasm.mlsgroup_removeMember(this.__wbg_ptr, ptr0, len0);
3181
+ if (ret[2]) {
3182
+ throw takeFromExternrefTable0(ret[1]);
3183
+ }
3184
+ return takeFromExternrefTable0(ret[0]);
3185
+ }
3186
+ /**
3187
+ * Persist the current group state through the JS group storage callback.
3188
+ */
3189
+ writeToStorage() {
3190
+ const ret = wasm.mlsgroup_writeToStorage(this.__wbg_ptr);
3191
+ if (ret[1]) {
3192
+ throw takeFromExternrefTable0(ret[0]);
3193
+ }
3194
+ }
3195
+ };
3196
+ if (Symbol.dispose) MlsGroup.prototype[Symbol.dispose] = MlsGroup.prototype.free;
3197
+ function generateClient(id, group_storage, key_package_storage, signature_keypair) {
3198
+ const ptr0 = passArray8ToWasm0(id, wasm.__wbindgen_malloc);
3199
+ const len0 = WASM_VECTOR_LEN;
3200
+ var ptr1 = isLikeNone(signature_keypair) ? 0 : passArray8ToWasm0(signature_keypair, wasm.__wbindgen_malloc);
3201
+ var len1 = WASM_VECTOR_LEN;
3202
+ const ret = wasm.generateClient(ptr0, len0, group_storage, key_package_storage, ptr1, len1);
3203
+ if (ret[2]) {
3204
+ throw takeFromExternrefTable0(ret[1]);
3205
+ }
3206
+ return MlsClient.__wrap(ret[0]);
3207
+ }
3208
+ function setPanicHook() {
3209
+ wasm.setPanicHook();
3210
+ }
3211
+ function __wbg_BigInt_231999d28f899902() {
3212
+ return handleError(function(arg0) {
3213
+ const ret = BigInt(arg0);
3214
+ return ret;
3215
+ }, arguments);
3216
+ }
3217
+ function __wbg_Error_fdd633d4bb5dd76a(arg0, arg1) {
3218
+ const ret = Error(getStringFromWasm0(arg0, arg1));
3219
+ return ret;
3220
+ }
3221
+ function __wbg_String_8564e559799eccda(arg0, arg1) {
3222
+ const ret = String(arg1);
3223
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
3224
+ const len1 = WASM_VECTOR_LEN;
3225
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
3226
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
3227
+ }
3228
+ function __wbg___wbindgen_bigint_get_as_i64_d9e915702856f831(arg0, arg1) {
3229
+ const v = arg1;
3230
+ const ret = typeof v === "bigint" ? v : void 0;
3231
+ getDataViewMemory0().setBigInt64(arg0 + 8 * 1, isLikeNone(ret) ? BigInt(0) : ret, true);
3232
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
3233
+ }
3234
+ function __wbg___wbindgen_debug_string_8a447059637473e2(arg0, arg1) {
3235
+ const ret = debugString(arg1);
3236
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
3237
+ const len1 = WASM_VECTOR_LEN;
3238
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
3239
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
3240
+ }
3241
+ function __wbg___wbindgen_is_function_acc5528be2b923f2(arg0) {
3242
+ const ret = typeof arg0 === "function";
3243
+ return ret;
3244
+ }
3245
+ function __wbg___wbindgen_is_null_6d937fbfb6478470(arg0) {
3246
+ const ret = arg0 === null;
3247
+ return ret;
3248
+ }
3249
+ function __wbg___wbindgen_is_object_0beba4a1980d3eea(arg0) {
3250
+ const val = arg0;
3251
+ const ret = typeof val === "object" && val !== null;
3252
+ return ret;
3253
+ }
3254
+ function __wbg___wbindgen_is_string_1fca8072260dd261(arg0) {
3255
+ const ret = typeof arg0 === "string";
3256
+ return ret;
3257
+ }
3258
+ function __wbg___wbindgen_is_undefined_721f8decd50c87a3(arg0) {
3259
+ const ret = arg0 === void 0;
3260
+ return ret;
3261
+ }
3262
+ function __wbg___wbindgen_jsval_eq_4e8c38722cb8ff51(arg0, arg1) {
3263
+ const ret = arg0 === arg1;
3264
+ return ret;
3265
+ }
3266
+ function __wbg___wbindgen_memory_9751d9a3017e7c25() {
3267
+ const ret = wasm.memory;
3268
+ return ret;
3269
+ }
3270
+ function __wbg___wbindgen_number_get_1cc01dd708740256(arg0, arg1) {
3271
+ const obj = arg1;
3272
+ const ret = typeof obj === "number" ? obj : void 0;
3273
+ getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true);
3274
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
3275
+ }
3276
+ function __wbg___wbindgen_string_get_71bb4348194e31f0(arg0, arg1) {
3277
+ const obj = arg1;
3278
+ const ret = typeof obj === "string" ? obj : void 0;
3279
+ var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
3280
+ var len1 = WASM_VECTOR_LEN;
3281
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
3282
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
3283
+ }
3284
+ function __wbg___wbindgen_throw_ea4887a5f8f9a9db(arg0, arg1) {
3285
+ throw new Error(getStringFromWasm0(arg0, arg1));
3286
+ }
3287
+ function __wbg_buffer_7447b9cc2267a9e5(arg0) {
3288
+ const ret = arg0.buffer;
3289
+ return ret;
3290
+ }
3291
+ function __wbg_call_67f43c91d09298f2() {
3292
+ return handleError(function(arg0, arg1, arg2) {
3293
+ const ret = arg0.call(arg1, arg2);
3294
+ return ret;
3295
+ }, arguments);
3296
+ }
3297
+ function __wbg_call_b51415974987aa44() {
3298
+ return handleError(function(arg0, arg1) {
3299
+ const ret = arg0.call(arg1);
3300
+ return ret;
3301
+ }, arguments);
3302
+ }
3303
+ function __wbg_crypto_38df2bab126b63dc(arg0) {
3304
+ const ret = arg0.crypto;
3305
+ return ret;
3306
+ }
3307
+ function __wbg_delete_21fafcbc7bb82c85() {
3308
+ return handleError(function(arg0, arg1) {
3309
+ arg0.delete(arg1);
3310
+ }, arguments);
3311
+ }
3312
+ function __wbg_epoch_15c000ffe3004541() {
3313
+ return handleError(function(arg0, arg1, arg2) {
3314
+ const ret = arg0.epoch(arg1, BigInt.asUintN(64, arg2));
3315
+ return ret;
3316
+ }, arguments);
3317
+ }
3318
+ function __wbg_error_a6fa202b58aa1cd3(arg0, arg1) {
3319
+ let deferred0_0;
3320
+ let deferred0_1;
3321
+ try {
3322
+ deferred0_0 = arg0;
3323
+ deferred0_1 = arg1;
3324
+ console.error(getStringFromWasm0(arg0, arg1));
3325
+ } finally {
3326
+ wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
3327
+ }
3328
+ }
3329
+ function __wbg_getRandomValues_c44a50d8cfdaebeb() {
3330
+ return handleError(function(arg0, arg1) {
3331
+ arg0.getRandomValues(arg1);
3332
+ }, arguments);
3333
+ }
3334
+ function __wbg_get_615446055a48103f() {
3335
+ return handleError(function(arg0, arg1) {
3336
+ const ret = arg0.get(arg1);
3337
+ return ret;
3338
+ }, arguments);
3339
+ }
3340
+ function __wbg_globalThis_6d268067835e6709() {
3341
+ return handleError(function() {
3342
+ const ret = globalThis.globalThis;
3343
+ return ret;
3344
+ }, arguments);
3345
+ }
3346
+ function __wbg_global_3fe6c6c8ad6e6fb2() {
3347
+ return handleError(function() {
3348
+ const ret = global.global;
3349
+ return ret;
3350
+ }, arguments);
3351
+ }
3352
+ function __wbg_insert_2d611f2bf9b60fee() {
3353
+ return handleError(function(arg0, arg1, arg2) {
3354
+ arg0.insert(arg1, arg2);
3355
+ }, arguments);
3356
+ }
3357
+ function __wbg_instanceof_Error_38f854307ecab4ce(arg0) {
3358
+ let result;
3359
+ try {
3360
+ result = arg0 instanceof Error;
3361
+ } catch (_) {
3362
+ result = false;
3363
+ }
3364
+ const ret = result;
3365
+ return ret;
3366
+ }
3367
+ function __wbg_length_c552db98817b9523(arg0) {
3368
+ const ret = arg0.length;
3369
+ return ret;
3370
+ }
3371
+ function __wbg_maxEpochId_0b176768b2f352fb() {
3372
+ return handleError(function(arg0, arg1) {
3373
+ const ret = arg0.maxEpochId(arg1);
3374
+ return ret;
3375
+ }, arguments);
3376
+ }
3377
+ function __wbg_message_a05fcf872473ffc5(arg0) {
3378
+ const ret = arg0.message;
3379
+ return ret;
3380
+ }
3381
+ function __wbg_msCrypto_bd5a034af96bcba6(arg0) {
3382
+ const ret = arg0.msCrypto;
3383
+ return ret;
3384
+ }
3385
+ function __wbg_new_227d7c05414eb861() {
3386
+ const ret = new Error();
3387
+ return ret;
3388
+ }
3389
+ function __wbg_new_32de5cbf49ca7dcb(arg0) {
3390
+ const ret = new Uint8Array(arg0);
3391
+ return ret;
3392
+ }
3393
+ function __wbg_new_364c96143b8f3496() {
3394
+ const ret = new Object();
3395
+ return ret;
3396
+ }
3397
+ function __wbg_new_b8b71ca8104fc178(arg0, arg1) {
3398
+ const ret = new Error(getStringFromWasm0(arg0, arg1));
3399
+ return ret;
3400
+ }
3401
+ function __wbg_new_d9762fd75876aafe() {
3402
+ const ret = new Array();
3403
+ return ret;
3404
+ }
3405
+ function __wbg_new_no_args_4010ad257320fa4f(arg0, arg1) {
3406
+ const ret = new Function(getStringFromWasm0(arg0, arg1));
3407
+ return ret;
3408
+ }
3409
+ function __wbg_new_with_byte_offset_and_length_8b21e3b1308deb48(arg0, arg1, arg2) {
3410
+ const ret = new Uint8Array(arg0, arg1 >>> 0, arg2 >>> 0);
3411
+ return ret;
3412
+ }
3413
+ function __wbg_new_with_length_5fdafe029be917a5(arg0) {
3414
+ const ret = new Uint8Array(arg0 >>> 0);
3415
+ return ret;
3416
+ }
3417
+ function __wbg_node_84ea875411254db1(arg0) {
3418
+ const ret = arg0.node;
3419
+ return ret;
3420
+ }
3421
+ function __wbg_process_44c7a14e11e9f69e(arg0) {
3422
+ const ret = arg0.process;
3423
+ return ret;
3424
+ }
3425
+ function __wbg_push_1303ce035391aed3(arg0, arg1) {
3426
+ const ret = arg0.push(arg1);
3427
+ return ret;
3428
+ }
3429
+ function __wbg_randomFillSync_6c25eac9869eb53c() {
3430
+ return handleError(function(arg0, arg1) {
3431
+ arg0.randomFillSync(arg1);
3432
+ }, arguments);
3433
+ }
3434
+ function __wbg_require_b4edbdcf3e2a1ef0() {
3435
+ return handleError(function() {
3436
+ const ret = module.require;
3437
+ return ret;
3438
+ }, arguments);
3439
+ }
3440
+ function __wbg_self_1035a7cbd1b0d959() {
3441
+ return handleError(function() {
3442
+ const ret = self.self;
3443
+ return ret;
3444
+ }, arguments);
3445
+ }
3446
+ function __wbg_set_047d1ea37bb67c19(arg0, arg1, arg2) {
3447
+ arg0.set(arg1, arg2 >>> 0);
3448
+ }
3449
+ function __wbg_set_1ddc4b8cd44d0da4() {
3450
+ return handleError(function(arg0, arg1, arg2) {
3451
+ const ret = Reflect.set(arg0, arg1, arg2);
3452
+ return ret;
3453
+ }, arguments);
3454
+ }
3455
+ function __wbg_set_28cba565792ec75f(arg0, arg1, arg2) {
3456
+ arg0[arg1 >>> 0] = arg2;
3457
+ }
3458
+ function __wbg_set_6be42768c690e380(arg0, arg1, arg2) {
3459
+ arg0[arg1] = arg2;
3460
+ }
3461
+ function __wbg_set_name_b4c29a3a72ebbddc(arg0, arg1, arg2) {
3462
+ arg0.name = getStringFromWasm0(arg1, arg2);
3463
+ }
3464
+ function __wbg_stack_3b0d974bbf31e44f(arg0, arg1) {
3465
+ const ret = arg1.stack;
3466
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
3467
+ const len1 = WASM_VECTOR_LEN;
3468
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
3469
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
3470
+ }
3471
+ function __wbg_state_bd20456c0f3efbc9() {
3472
+ return handleError(function(arg0, arg1) {
3473
+ const ret = arg0.state(arg1);
3474
+ return ret;
3475
+ }, arguments);
3476
+ }
3477
+ function __wbg_subarray_e0162dcdea48eb3a(arg0, arg1, arg2) {
3478
+ const ret = arg0.subarray(arg1 >>> 0, arg2 >>> 0);
3479
+ return ret;
3480
+ }
3481
+ function __wbg_versions_276b2795b1c6a219(arg0) {
3482
+ const ret = arg0.versions;
3483
+ return ret;
3484
+ }
3485
+ function __wbg_window_9c17850b5e99c0ab() {
3486
+ return handleError(function() {
3487
+ const ret = window.window;
3488
+ return ret;
3489
+ }, arguments);
3490
+ }
3491
+ function __wbg_write_56b1cf5bb0e780d6() {
3492
+ return handleError(function(arg0, arg1, arg2, arg3, arg4) {
3493
+ arg0.write(arg1, arg2, arg3, arg4);
3494
+ }, arguments);
3495
+ }
3496
+ function __wbindgen_cast_0000000000000001(arg0) {
3497
+ const ret = arg0;
3498
+ return ret;
3499
+ }
3500
+ function __wbindgen_cast_0000000000000002(arg0, arg1) {
3501
+ const ret = getStringFromWasm0(arg0, arg1);
3502
+ return ret;
3503
+ }
3504
+ function __wbindgen_cast_0000000000000003(arg0) {
3505
+ const ret = BigInt.asUintN(64, arg0);
3506
+ return ret;
3507
+ }
3508
+ function __wbindgen_init_externref_table() {
3509
+ const table = wasm.__wbindgen_externrefs;
3510
+ const offset = table.grow(4);
3511
+ table.set(0, void 0);
3512
+ table.set(offset + 0, void 0);
3513
+ table.set(offset + 1, null);
3514
+ table.set(offset + 2, true);
3515
+ table.set(offset + 3, false);
3516
+ }
3517
+ var MlsClientFinalization = typeof FinalizationRegistry === "undefined" ? { register: () => {
3518
+ }, unregister: () => {
3519
+ } } : new FinalizationRegistry((ptr) => wasm.__wbg_mlsclient_free(ptr, 1));
3520
+ var MlsGroupFinalization = typeof FinalizationRegistry === "undefined" ? { register: () => {
3521
+ }, unregister: () => {
3522
+ } } : new FinalizationRegistry((ptr) => wasm.__wbg_mlsgroup_free(ptr, 1));
3523
+ function addToExternrefTable0(obj) {
3524
+ const idx = wasm.__externref_table_alloc();
3525
+ wasm.__wbindgen_externrefs.set(idx, obj);
3526
+ return idx;
3527
+ }
3528
+ function debugString(val) {
3529
+ const type = typeof val;
3530
+ if (type == "number" || type == "boolean" || val == null) {
3531
+ return `${val}`;
3532
+ }
3533
+ if (type == "string") {
3534
+ return `"${val}"`;
3535
+ }
3536
+ if (type == "symbol") {
3537
+ const description = val.description;
3538
+ if (description == null) {
3539
+ return "Symbol";
3540
+ } else {
3541
+ return `Symbol(${description})`;
3542
+ }
3543
+ }
3544
+ if (type == "function") {
3545
+ const name = val.name;
3546
+ if (typeof name == "string" && name.length > 0) {
3547
+ return `Function(${name})`;
3548
+ } else {
3549
+ return "Function";
3550
+ }
3551
+ }
3552
+ if (Array.isArray(val)) {
3553
+ const length = val.length;
3554
+ let debug = "[";
3555
+ if (length > 0) {
3556
+ debug += debugString(val[0]);
3557
+ }
3558
+ for (let i = 1; i < length; i++) {
3559
+ debug += ", " + debugString(val[i]);
3560
+ }
3561
+ debug += "]";
3562
+ return debug;
3563
+ }
3564
+ const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
3565
+ let className;
3566
+ if (builtInMatches && builtInMatches.length > 1) {
3567
+ className = builtInMatches[1];
3568
+ } else {
3569
+ return toString.call(val);
3570
+ }
3571
+ if (className == "Object") {
3572
+ try {
3573
+ return "Object(" + JSON.stringify(val) + ")";
3574
+ } catch (_) {
3575
+ return "Object";
3576
+ }
3577
+ }
3578
+ if (val instanceof Error) {
3579
+ return `${val.name}: ${val.message}
3580
+ ${val.stack}`;
3581
+ }
3582
+ return className;
3583
+ }
3584
+ function getArrayU8FromWasm0(ptr, len) {
3585
+ ptr = ptr >>> 0;
3586
+ return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
3587
+ }
3588
+ var cachedDataViewMemory0 = null;
3589
+ function getDataViewMemory0() {
3590
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || cachedDataViewMemory0.buffer.detached === void 0 && cachedDataViewMemory0.buffer !== wasm.memory.buffer) {
3591
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
3592
+ }
3593
+ return cachedDataViewMemory0;
3594
+ }
3595
+ function getStringFromWasm0(ptr, len) {
3596
+ return decodeText(ptr >>> 0, len);
3597
+ }
3598
+ var cachedUint8ArrayMemory0 = null;
3599
+ function getUint8ArrayMemory0() {
3600
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
3601
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
3602
+ }
3603
+ return cachedUint8ArrayMemory0;
3604
+ }
3605
+ function handleError(f, args) {
3606
+ try {
3607
+ return f.apply(this, args);
3608
+ } catch (e) {
3609
+ const idx = addToExternrefTable0(e);
3610
+ wasm.__wbindgen_exn_store(idx);
3611
+ }
3612
+ }
3613
+ function isLikeNone(x) {
3614
+ return x === void 0 || x === null;
3615
+ }
3616
+ function passArray8ToWasm0(arg, malloc) {
3617
+ const ptr = malloc(arg.length * 1, 1) >>> 0;
3618
+ getUint8ArrayMemory0().set(arg, ptr / 1);
3619
+ WASM_VECTOR_LEN = arg.length;
3620
+ return ptr;
3621
+ }
3622
+ function passArrayJsValueToWasm0(array, malloc) {
3623
+ const ptr = malloc(array.length * 4, 4) >>> 0;
3624
+ for (let i = 0; i < array.length; i++) {
3625
+ const add = addToExternrefTable0(array[i]);
3626
+ getDataViewMemory0().setUint32(ptr + 4 * i, add, true);
3627
+ }
3628
+ WASM_VECTOR_LEN = array.length;
3629
+ return ptr;
3630
+ }
3631
+ function passStringToWasm0(arg, malloc, realloc) {
3632
+ if (realloc === void 0) {
3633
+ const buf = cachedTextEncoder.encode(arg);
3634
+ const ptr2 = malloc(buf.length, 1) >>> 0;
3635
+ getUint8ArrayMemory0().subarray(ptr2, ptr2 + buf.length).set(buf);
3636
+ WASM_VECTOR_LEN = buf.length;
3637
+ return ptr2;
3638
+ }
3639
+ let len = arg.length;
3640
+ let ptr = malloc(len, 1) >>> 0;
3641
+ const mem = getUint8ArrayMemory0();
3642
+ let offset = 0;
3643
+ for (; offset < len; offset++) {
3644
+ const code = arg.charCodeAt(offset);
3645
+ if (code > 127) break;
3646
+ mem[ptr + offset] = code;
3647
+ }
3648
+ if (offset !== len) {
3649
+ if (offset !== 0) {
3650
+ arg = arg.slice(offset);
3651
+ }
3652
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
3653
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
3654
+ const ret = cachedTextEncoder.encodeInto(arg, view);
3655
+ offset += ret.written;
3656
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
3657
+ }
3658
+ WASM_VECTOR_LEN = offset;
3659
+ return ptr;
3660
+ }
3661
+ function takeFromExternrefTable0(idx) {
3662
+ const value = wasm.__wbindgen_externrefs.get(idx);
3663
+ wasm.__externref_table_dealloc(idx);
3664
+ return value;
3665
+ }
3666
+ var cachedTextDecoder = new TextDecoder("utf-8", { ignoreBOM: true, fatal: true });
3667
+ cachedTextDecoder.decode();
3668
+ var MAX_SAFARI_DECODE_BYTES = 2146435072;
3669
+ var numBytesDecoded = 0;
3670
+ function decodeText(ptr, len) {
3671
+ numBytesDecoded += len;
3672
+ if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
3673
+ cachedTextDecoder = new TextDecoder("utf-8", { ignoreBOM: true, fatal: true });
3674
+ cachedTextDecoder.decode();
3675
+ numBytesDecoded = len;
3676
+ }
3677
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
3678
+ }
3679
+ var cachedTextEncoder = new TextEncoder();
3680
+ if (!("encodeInto" in cachedTextEncoder)) {
3681
+ cachedTextEncoder.encodeInto = function(arg, view) {
3682
+ const buf = cachedTextEncoder.encode(arg);
3683
+ view.set(buf);
3684
+ return {
3685
+ read: arg.length,
3686
+ written: buf.length
3687
+ };
3688
+ };
3689
+ }
3690
+ var WASM_VECTOR_LEN = 0;
3691
+ var wasm;
3692
+ function __wbg_set_wasm(val) {
3693
+ wasm = val;
3694
+ }
3695
+
3696
+ // src/messaging/wasm/pkg/snippets/mls-rs-core-f99cdecbb456b09c/inline0.js
3697
+ var inline0_exports = {};
3698
+ __export(inline0_exports, {
3699
+ date_now: () => date_now
3700
+ });
3701
+ function date_now() {
3702
+ return Date.now();
3703
+ }
3704
+
3705
+ // src/messaging/wasm/loader.ts
3706
+ var import_meta = {};
3707
+ var GLUE_MODULE = "./palbe_mls_bg.js";
3708
+ var SNIPPET_MODULE = "./snippets/mls-rs-core-f99cdecbb456b09c/inline0.js";
3709
+ var inflight = null;
3710
+ var ready = null;
3711
+ async function loadWasmModule() {
3712
+ const wasmUrl = new URL("./pkg/palbe_mls_bg.wasm", import_meta.url);
3713
+ if (wasmUrl.protocol === "file:") {
3714
+ const fsSpecifier = ["node", "fs/promises"].join(":");
3715
+ const { readFile } = await import(
3716
+ /* webpackIgnore: true */
3717
+ fsSpecifier
3718
+ );
3719
+ const bytes = await readFile(wasmUrl);
3720
+ return WebAssembly.compile(bytes);
3721
+ }
3722
+ const response = await fetch(wasmUrl);
3723
+ if (typeof WebAssembly.compileStreaming === "function") {
3724
+ try {
3725
+ return await WebAssembly.compileStreaming(Promise.resolve(response));
3726
+ } catch {
3727
+ }
3728
+ }
3729
+ const buffer = await response.arrayBuffer();
3730
+ return WebAssembly.compile(buffer);
3731
+ }
3732
+ function buildImports() {
3733
+ return {
3734
+ [GLUE_MODULE]: palbe_mls_bg_exports,
3735
+ [SNIPPET_MODULE]: inline0_exports
3736
+ };
3737
+ }
3738
+ function initMls() {
3739
+ if (ready) return Promise.resolve(ready);
3740
+ if (inflight) return inflight;
3741
+ inflight = (async () => {
3742
+ const module2 = await loadWasmModule();
3743
+ const instance = await WebAssembly.instantiate(module2, buildImports());
3744
+ const setWasm = __wbg_set_wasm;
3745
+ setWasm(instance.exports);
3746
+ instance.exports.__wbindgen_start?.();
3747
+ const g = {
3748
+ generateClient,
3749
+ setPanicHook,
3750
+ MlsClient,
3751
+ MlsGroup
3752
+ };
3753
+ try {
3754
+ g.setPanicHook();
3755
+ } catch {
3756
+ }
3757
+ ready = g;
3758
+ return g;
3759
+ })();
3760
+ return inflight;
3761
+ }
3762
+ function requireMls() {
3763
+ if (!ready) {
3764
+ throw new Error("palbe-mls WASM not initialized \u2014 await initMls() first");
3765
+ }
3766
+ return ready;
3767
+ }
3768
+
3769
+ // src/messaging/wasm/bridge.ts
3770
+ function toBytes(v) {
3771
+ if (v instanceof Uint8Array) return v;
3772
+ if (Array.isArray(v)) return Uint8Array.from(v);
3773
+ if (v == null) return new Uint8Array(0);
3774
+ if (ArrayBuffer.isView(v)) return new Uint8Array(v.buffer);
3775
+ throw new Error("expected byte field (Uint8Array | number[])");
3776
+ }
3777
+ function optBytes(v) {
3778
+ if (v == null) return void 0;
3779
+ return toBytes(v);
3780
+ }
3781
+ var MlsGroupHandle = class {
3782
+ /** @internal */
3783
+ constructor(raw) {
3784
+ this.raw = raw;
3785
+ }
3786
+ raw;
3787
+ /** The MLS rfc_group_id (routing key / storage key) bytes. */
3788
+ groupId() {
3789
+ return this.raw.groupId();
3790
+ }
3791
+ /** Current applied epoch (BigInt → number; epochs stay well under 2^53). */
3792
+ currentEpoch() {
3793
+ return Number(this.raw.currentEpoch());
3794
+ }
3795
+ /** Stage a member add (PENDING after). */
3796
+ addMember(keyPackageMsg) {
3797
+ return normalizeAddResult(this.raw.addMember(keyPackageMsg));
3798
+ }
3799
+ /** Stage a member remove by identity bytes (PENDING after). */
3800
+ removeMember(memberId) {
3801
+ const r = this.raw.removeMember(memberId);
3802
+ return { commit: toBytes(r.commit) };
3803
+ }
3804
+ /** Stage a SINGLE atomic add+remove commit (PENDING after). */
3805
+ commitChanges(addKeyPackages, removeMemberIds) {
3806
+ return normalizeAddResult(this.raw.commitChanges(addKeyPackages, removeMemberIds));
3807
+ }
3808
+ /** Apply the staged commit (advance the local epoch). Only after server-accept. */
3809
+ applyPendingCommit() {
3810
+ this.raw.applyPendingCommit();
3811
+ }
3812
+ /** Discard the staged commit without advancing (the 409-rebase path). */
3813
+ clearPendingCommit() {
3814
+ this.raw.clearPendingCommit();
3815
+ }
3816
+ /** True while a staged (unapplied) commit exists. */
3817
+ hasPendingCommit() {
3818
+ return this.raw.hasPendingCommit();
3819
+ }
3820
+ /** Encrypt an application message at the current epoch → PrivateMessage bytes. */
3821
+ encryptApplicationMessage(plaintext) {
3822
+ return this.raw.encryptApplicationMessage(plaintext);
3823
+ }
3824
+ /** Process an incoming MLSMessage and apply its effect (normalized result). */
3825
+ processIncomingMessage(message) {
3826
+ return normalizeReceived(this.raw.processIncomingMessage(message));
3827
+ }
3828
+ /** Inspect an incoming Commit WITHOUT applying it (the gate's crypto-truth). */
3829
+ describeIncomingCommit(commitMsg) {
3830
+ const d = this.raw.describeIncomingCommit(commitMsg);
3831
+ return {
3832
+ kind: d.kind,
3833
+ addedIdentities: d.addedIdentities.map(toBytes),
3834
+ removedIdentities: d.removedIdentities.map(toBytes)
3835
+ };
3836
+ }
3837
+ /** Persist the current group state through the JS group-storage callback. */
3838
+ writeToStorage() {
3839
+ this.raw.writeToStorage();
3840
+ }
3841
+ };
3842
+ function normalizeAddResult(v) {
3843
+ const r = v;
3844
+ return {
3845
+ commit: toBytes(r.commit),
3846
+ welcome: optBytes(r.welcome),
3847
+ addedLeafIndices: (r.addedLeafIndices ?? []).map((n) => Number(n))
3848
+ };
3849
+ }
3850
+ function normalizeReceived(v) {
3851
+ if (v === "Proposal") return { type: "proposal" };
3852
+ if (v === "Other") return { type: "other" };
3853
+ const obj = v;
3854
+ if (obj.Application) {
3855
+ const a = obj.Application;
3856
+ return { type: "application", sender: toBytes(a.sender), data: toBytes(a.data) };
3857
+ }
3858
+ if (obj.CommitApplied) {
3859
+ const c = obj.CommitApplied;
3860
+ return { type: "commitApplied", epoch: Number(c.epoch), removedSelf: Boolean(c.removedSelf) };
3861
+ }
3862
+ return { type: "other" };
3863
+ }
3864
+ var MlsClientHandle = class {
3865
+ /** @internal */
3866
+ constructor(raw) {
3867
+ this.raw = raw;
3868
+ }
3869
+ raw;
3870
+ /** The SP-0 keydir enroll material (`{ signatureKey, credential, capabilities }`). */
3871
+ enrollmentMaterial() {
3872
+ const m = this.raw.enrollmentMaterial();
3873
+ return {
3874
+ signatureKey: toBytes(m.signatureKey),
3875
+ credential: toBytes(m.credential),
3876
+ capabilities: toBytes(m.capabilities)
3877
+ };
3878
+ }
3879
+ /** The device's STABLE signature PUBLIC key bytes (the keydir `signature_key`). */
3880
+ signaturePublicKey() {
3881
+ return this.raw.signaturePublicKey();
3882
+ }
3883
+ /** The MLS Credential wire bytes (the keydir `credential`). */
3884
+ credential() {
3885
+ return this.raw.credential();
3886
+ }
3887
+ /** The MLS Capabilities wire bytes (the keydir `capabilities`). */
3888
+ capabilities() {
3889
+ return this.raw.capabilities();
3890
+ }
3891
+ /** The serialized STABLE signature keypair (secret-bearing — persist sealed). */
3892
+ signatureKeypair() {
3893
+ return this.raw.signatureKeypair();
3894
+ }
3895
+ /** Mint a one-use KeyPackage (RAW RFC 9420 §10 bytes). */
3896
+ generateKeyPackage() {
3897
+ return this.raw.generateKeyPackage();
3898
+ }
3899
+ /** Create a new group at epoch 0 (engine mints the rfc_group_id). */
3900
+ createGroup(groupId) {
3901
+ return new MlsGroupHandle(this.raw.createGroup(groupId ?? void 0));
3902
+ }
3903
+ /** Join a group from a Welcome (opaque MLSMessage bytes). */
3904
+ joinGroup(welcome) {
3905
+ return new MlsGroupHandle(this.raw.joinGroup(welcome));
3906
+ }
3907
+ /** Re-hydrate an already-joined group by its rfc_group_id bytes. */
3908
+ loadGroup(groupId) {
3909
+ return new MlsGroupHandle(this.raw.loadGroup(groupId));
3910
+ }
3911
+ };
3912
+ function generateClient2(id, groupStorage, keyPackageStorage, signatureKeypair) {
3913
+ const { generateClient: gen } = requireMls();
3914
+ return new MlsClientHandle(
3915
+ gen(id, groupStorage, keyPackageStorage, signatureKeypair ?? void 0)
3916
+ );
3917
+ }
3918
+
3919
+ // src/messaging/mls-engine.ts
3920
+ var utf8 = new TextEncoder();
3921
+ var gidHex = bytesToHex;
3922
+ var MlsEngine = class _MlsEngine {
3923
+ constructor(userId, deviceId, client, groupStore, kpStore) {
3924
+ this.userId = userId;
3925
+ this.deviceId = deviceId;
3926
+ this.client = client;
3927
+ this.groupStore = groupStore;
3928
+ this.kpStore = kpStore;
3929
+ }
3930
+ userId;
3931
+ deviceId;
3932
+ client;
3933
+ groupStore;
3934
+ kpStore;
3935
+ groups = /* @__PURE__ */ new Map();
3936
+ chain = Promise.resolve();
3937
+ /**
3938
+ * Build the engine: init the WASM, hydrate the durable stores into the sync
3939
+ * caches, generate (or restore) the device client. Idempotent restore: the
3940
+ * persisted signature keypair keeps the SAME MLS signing identity across
3941
+ * reloads; the group-state cache makes already-joined groups reloadable.
3942
+ */
3943
+ static async create(opts) {
3944
+ await initMls();
3945
+ await Promise.all([opts.groupStore.hydrate(), opts.kpStore.hydrate()]);
3946
+ const persistedKeypair = await opts.sigStore.load(opts.userId);
3947
+ const idBytes = utf8.encode(opts.deviceId);
3948
+ const client = generateClient2(idBytes, opts.groupStore, opts.kpStore, persistedKeypair);
3949
+ if (!persistedKeypair) {
3950
+ await opts.sigStore.save(opts.userId, client.signatureKeypair());
3951
+ }
3952
+ return new _MlsEngine(opts.userId, opts.deviceId, client, opts.groupStore, opts.kpStore);
3953
+ }
3954
+ /** Serialize an engine op onto the chain so the critical section is exclusive. */
3955
+ enqueue(fn) {
3956
+ const run = this.chain.then(fn, fn);
3957
+ this.chain = run.then(
3958
+ () => void 0,
3959
+ () => void 0
3960
+ );
3961
+ return run;
3962
+ }
3963
+ /** Flush both storage caches durably (the post-mutation durability boundary). */
3964
+ async flush() {
3965
+ await Promise.all([this.groupStore.flush(), this.kpStore.flush()]);
3966
+ }
3967
+ // ── Enroll material / key packages ──
3968
+ enrollmentMaterial() {
3969
+ return this.enqueue(() => this.client.enrollmentMaterial());
3970
+ }
3971
+ signaturePublicKey() {
3972
+ return this.enqueue(() => this.client.signaturePublicKey());
3973
+ }
3974
+ generateKeyPackage() {
3975
+ return this.enqueue(async () => {
3976
+ const kp = this.client.generateKeyPackage();
3977
+ await this.flush();
3978
+ return kp;
3979
+ });
3980
+ }
3981
+ // ── Group create / join / reload ──
3982
+ /** Create a group; returns its rfc_group_id (the routing key). */
3983
+ createGroup() {
3984
+ return this.enqueue(async () => {
3985
+ const group = this.client.createGroup();
3986
+ const gid = group.groupId();
3987
+ this.groups.set(gidHex(gid), group);
3988
+ group.writeToStorage();
3989
+ await this.flush();
3990
+ return gid;
3991
+ });
3992
+ }
3993
+ /** Join from a Welcome; returns the joined group's rfc_group_id. */
3994
+ joinFromWelcome(welcome) {
3995
+ return this.enqueue(async () => {
3996
+ const group = this.client.joinGroup(welcome);
3997
+ const gid = group.groupId();
3998
+ this.groups.set(gidHex(gid), group);
3999
+ group.writeToStorage();
4000
+ await this.flush();
4001
+ return gid;
4002
+ });
4003
+ }
4004
+ /** The current applied epoch for a group (0 if unknown). */
4005
+ epoch(rfcGroupId) {
4006
+ return this.enqueue(() => {
4007
+ const g = this.handle(rfcGroupId);
4008
+ return g ? g.currentEpoch() : 0;
4009
+ });
4010
+ }
4011
+ // ── Membership commits (staged → applied/cleared by the rebase loop) ──
4012
+ /** Stage a SINGLE atomic add+remove commit. Returns the AddResult (PENDING). */
4013
+ commitChanges(rfcGroupId, adds, removeMemberIds) {
4014
+ return this.enqueue(() => {
4015
+ const g = this.requireHandle(rfcGroupId);
4016
+ return g.commitChanges(adds, removeMemberIds);
4017
+ });
4018
+ }
4019
+ /** Apply the staged commit (advance the epoch). Only after a server-accept. */
4020
+ applyPendingCommit(rfcGroupId) {
4021
+ return this.enqueue(async () => {
4022
+ const g = this.requireHandle(rfcGroupId);
4023
+ g.applyPendingCommit();
4024
+ g.writeToStorage();
4025
+ await this.flush();
4026
+ });
4027
+ }
4028
+ /** Discard the staged commit (the 409-rebase path). */
4029
+ clearPendingCommit(rfcGroupId) {
4030
+ return this.enqueue(() => {
4031
+ const g = this.handle(rfcGroupId);
4032
+ if (g?.hasPendingCommit()) g.clearPendingCommit();
4033
+ });
4034
+ }
4035
+ // ── Application messages ──
4036
+ /** Encrypt a plaintext at the current epoch → PrivateMessage bytes. */
4037
+ encryptApplication(rfcGroupId, plaintext) {
4038
+ return this.enqueue(async () => {
4039
+ const g = this.requireHandle(rfcGroupId);
4040
+ const ct = g.encryptApplicationMessage(plaintext);
4041
+ g.writeToStorage();
4042
+ await this.flush();
4043
+ return ct;
4044
+ });
4045
+ }
4046
+ /**
4047
+ * Process an incoming MLSMessage (decrypt application / apply commit). Returns
4048
+ * the normalized ReceivedMessage. `describeIncomingCommit` could gate a commit
4049
+ * before applying when the server fans a declaration; today the live server
4050
+ * does not, so we process directly (parity with the iOS nil-declaration path).
4051
+ */
4052
+ processIncoming(rfcGroupId, blob) {
4053
+ return this.enqueue(async () => {
4054
+ const g = this.requireHandle(rfcGroupId);
4055
+ const received = g.processIncomingMessage(blob);
4056
+ g.writeToStorage();
4057
+ await this.flush();
4058
+ return received;
4059
+ });
4060
+ }
4061
+ /** Inspect a commit WITHOUT applying (the reconciliation gate's crypto-truth). */
4062
+ describeIncomingCommit(rfcGroupId, commit) {
4063
+ return this.enqueue(() => this.requireHandle(rfcGroupId).describeIncomingCommit(commit));
4064
+ }
4065
+ /** Persist the current group state (explicit checkpoint). */
4066
+ writeToStorage(rfcGroupId) {
4067
+ return this.enqueue(async () => {
4068
+ const g = this.handle(rfcGroupId);
4069
+ if (g) {
4070
+ g.writeToStorage();
4071
+ await this.flush();
4072
+ }
4073
+ });
4074
+ }
4075
+ // ── Handle resolution ──
4076
+ /** Resolve (or reload from storage) the handle for a group, or undefined. */
4077
+ handle(rfcGroupId) {
4078
+ const key = gidHex(rfcGroupId);
4079
+ const cached = this.groups.get(key);
4080
+ if (cached) return cached;
4081
+ try {
4082
+ const g = this.client.loadGroup(rfcGroupId);
4083
+ this.groups.set(key, g);
4084
+ return g;
4085
+ } catch {
4086
+ return void 0;
4087
+ }
4088
+ }
4089
+ requireHandle(rfcGroupId) {
4090
+ const g = this.handle(rfcGroupId);
4091
+ if (!g) throw new Error(`MLS group not loaded: ${gidHex(rfcGroupId)}`);
4092
+ return g;
4093
+ }
4094
+ };
4095
+
4096
+ // src/messaging/registry.ts
4097
+ var GroupRegistry = class {
4098
+ byRfc = /* @__PURE__ */ new Map();
4099
+ catalog = null;
4100
+ chatFactory = null;
4101
+ onListChange = null;
4102
+ /** Wire the observable chat-list sink + the pointer-stable Chat factory, then
4103
+ * replay the current set so a sink attached after groups loaded reflects them. */
4104
+ attachChatList(onListChange, factory) {
4105
+ this.onListChange = onListChange;
4106
+ this.chatFactory = factory;
4107
+ this.publish();
4108
+ }
4109
+ /** Hydrate from the durable catalog (live entries win — fresher epoch). */
4110
+ async attachCatalog(catalog) {
4111
+ this.catalog = catalog;
4112
+ let changed = false;
4113
+ for (const c of await catalog.all()) {
4114
+ if (!this.byRfc.has(c.rfcGroupId)) {
4115
+ this.byRfc.set(c.rfcGroupId, {
4116
+ displayId: c.displayId,
4117
+ rfcGroupId: c.rfcGroupId,
4118
+ currentEpoch: c.currentEpoch,
4119
+ ownerUserId: c.ownerUserId,
4120
+ directKey: c.directKey,
4121
+ name: c.name
4122
+ });
4123
+ changed = true;
4124
+ }
4125
+ }
4126
+ if (changed) this.publish();
4127
+ }
4128
+ register(group) {
4129
+ const isNew = !this.byRfc.has(group.rfcGroupId);
4130
+ this.byRfc.set(group.rfcGroupId, group);
4131
+ void this.persist(group);
4132
+ if (isNew) this.publish();
4133
+ }
4134
+ remove(rfcGroupId) {
4135
+ if (!this.byRfc.has(rfcGroupId)) return;
4136
+ this.byRfc.delete(rfcGroupId);
4137
+ if (this.catalog) void this.catalog.remove(rfcGroupId);
4138
+ this.publish();
4139
+ }
4140
+ group(rfcGroupId) {
4141
+ return this.byRfc.get(rfcGroupId);
4142
+ }
4143
+ /** The single known group, if exactly one (the wire-gap routing fallback). */
4144
+ soleGroup() {
4145
+ return this.byRfc.size === 1 ? this.byRfc.values().next().value : void 0;
4146
+ }
4147
+ all() {
4148
+ return [...this.byRfc.values()];
4149
+ }
4150
+ /** Advance a cached epoch (after a processed commit). Does NOT publish (the
4151
+ * group SET is unchanged — only metadata). */
4152
+ bumpEpoch(rfcGroupId, epoch) {
4153
+ const g = this.byRfc.get(rfcGroupId);
4154
+ if (!g) return;
4155
+ const bumped = { ...g, currentEpoch: epoch };
4156
+ this.byRfc.set(rfcGroupId, bumped);
4157
+ void this.persist(bumped);
4158
+ }
4159
+ publish() {
4160
+ const factory = this.chatFactory;
4161
+ if (!this.onListChange || !factory) return;
4162
+ const chats = this.all().map((g) => factory(g));
4163
+ this.onListChange(chats);
4164
+ }
4165
+ async persist(group) {
4166
+ if (!this.catalog) return;
4167
+ try {
4168
+ await this.catalog.upsert({
4169
+ displayId: group.displayId,
4170
+ rfcGroupId: group.rfcGroupId,
4171
+ currentEpoch: group.currentEpoch,
4172
+ ownerUserId: group.ownerUserId,
4173
+ directKey: group.directKey,
4174
+ name: group.name
4175
+ });
4176
+ } catch {
4177
+ }
4178
+ }
4179
+ };
4180
+
4181
+ // src/messaging/storage.ts
4182
+ var hasIndexedDB = () => typeof indexedDB !== "undefined" && typeof crypto !== "undefined" && !!crypto.subtle;
4183
+ function copyToArrayBuffer(view) {
4184
+ const out = new ArrayBuffer(view.byteLength);
4185
+ new Uint8Array(out).set(view);
4186
+ return out;
4187
+ }
4188
+ var MemoryKV = class {
4189
+ map = /* @__PURE__ */ new Map();
4190
+ get(key) {
4191
+ return Promise.resolve(this.map.get(key));
4192
+ }
4193
+ set(key, value) {
4194
+ this.map.set(key, value.slice());
4195
+ return Promise.resolve();
4196
+ }
4197
+ delete(key) {
4198
+ this.map.delete(key);
4199
+ return Promise.resolve();
4200
+ }
4201
+ keys(prefix) {
4202
+ return Promise.resolve([...this.map.keys()].filter((k) => k.startsWith(prefix)));
4203
+ }
4204
+ };
4205
+ var DB_NAME = "palbe.messaging";
4206
+ var STORE = "kv";
4207
+ var KEY_STORE = "keys";
4208
+ var SEAL_KEY_ID = "seal-key-v1";
4209
+ function openDb() {
4210
+ return new Promise((resolve, reject) => {
4211
+ const req = indexedDB.open(DB_NAME, 1);
4212
+ req.onupgradeneeded = () => {
4213
+ const db = req.result;
4214
+ if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE);
4215
+ if (!db.objectStoreNames.contains(KEY_STORE)) db.createObjectStore(KEY_STORE);
4216
+ };
4217
+ req.onsuccess = () => resolve(req.result);
4218
+ req.onerror = () => reject(req.error);
4219
+ });
4220
+ }
4221
+ function idbReq(req) {
4222
+ return new Promise((resolve, reject) => {
4223
+ req.onsuccess = () => resolve(req.result);
4224
+ req.onerror = () => reject(req.error);
4225
+ });
4226
+ }
4227
+ async function getSealKey(db) {
4228
+ const existing = await idbReq(
4229
+ db.transaction(KEY_STORE, "readonly").objectStore(KEY_STORE).get(SEAL_KEY_ID)
4230
+ );
4231
+ if (existing) return existing;
4232
+ const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, [
4233
+ "encrypt",
4234
+ "decrypt"
4235
+ ]);
4236
+ await idbReq(db.transaction(KEY_STORE, "readwrite").objectStore(KEY_STORE).put(key, SEAL_KEY_ID));
4237
+ return key;
4238
+ }
4239
+ var IndexedDbKV = class {
4240
+ dbPromise = null;
4241
+ keyPromise = null;
4242
+ db() {
4243
+ if (!this.dbPromise) this.dbPromise = openDb();
4244
+ return this.dbPromise;
4245
+ }
4246
+ async sealKey() {
4247
+ if (!this.keyPromise) this.keyPromise = this.db().then(getSealKey);
4248
+ return this.keyPromise;
4249
+ }
4250
+ async seal(plain) {
4251
+ const key = await this.sealKey();
4252
+ const iv = crypto.getRandomValues(new Uint8Array(12));
4253
+ const ct = new Uint8Array(
4254
+ await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, copyToArrayBuffer(plain))
4255
+ );
4256
+ const out = new Uint8Array(iv.length + ct.length);
4257
+ out.set(iv, 0);
4258
+ out.set(ct, iv.length);
4259
+ return out;
4260
+ }
4261
+ async open(sealed) {
4262
+ const key = await this.sealKey();
4263
+ const iv = copyToArrayBuffer(sealed.subarray(0, 12));
4264
+ const ct = copyToArrayBuffer(sealed.subarray(12));
4265
+ const pt = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ct);
4266
+ return new Uint8Array(pt);
4267
+ }
4268
+ async get(k) {
4269
+ const db = await this.db();
4270
+ const sealed = await idbReq(db.transaction(STORE, "readonly").objectStore(STORE).get(k));
4271
+ if (!sealed) return void 0;
4272
+ return this.open(sealed);
4273
+ }
4274
+ async set(k, v) {
4275
+ const db = await this.db();
4276
+ const sealed = await this.seal(v);
4277
+ await idbReq(db.transaction(STORE, "readwrite").objectStore(STORE).put(sealed, k));
4278
+ }
4279
+ async delete(k) {
4280
+ const db = await this.db();
4281
+ await idbReq(db.transaction(STORE, "readwrite").objectStore(STORE).delete(k));
4282
+ }
4283
+ async keys(prefix) {
4284
+ const db = await this.db();
4285
+ const all = await idbReq(
4286
+ db.transaction(STORE, "readonly").objectStore(STORE).getAllKeys()
4287
+ );
4288
+ return all.map(String).filter((k) => k.startsWith(prefix));
4289
+ }
4290
+ };
4291
+ function createDurableKV() {
4292
+ return hasIndexedDB() ? new IndexedDbKV() : new MemoryKV();
4293
+ }
4294
+ function bytesKey(prefix, id) {
4295
+ return prefix + bytesToHex(id);
1187
4296
  }
1188
- function resolveStorage() {
1189
- try {
1190
- if (typeof localStorage === "undefined") {
1191
- return null;
4297
+ var GroupStateStorage = class {
4298
+ constructor(kv) {
4299
+ this.kv = kv;
4300
+ }
4301
+ kv;
4302
+ // In-memory authoritative cache (the engine reads/writes this synchronously).
4303
+ stateCache = /* @__PURE__ */ new Map();
4304
+ epochCache = /* @__PURE__ */ new Map();
4305
+ maxEpochCache = /* @__PURE__ */ new Map();
4306
+ // Keys touched since the last flush (durable write queue).
4307
+ dirty = /* @__PURE__ */ new Set();
4308
+ // ── sync engine seam ──
4309
+ state(groupId) {
4310
+ return this.stateCache.get(bytesKey("", groupId));
4311
+ }
4312
+ epoch(groupId, epochId) {
4313
+ return this.epochCache.get(`${bytesKey("", groupId)}:${epochId}`);
4314
+ }
4315
+ write(groupId, groupState, epochInserts, epochUpdates) {
4316
+ const gid = bytesKey("", groupId);
4317
+ this.stateCache.set(gid, groupState.slice());
4318
+ this.dirty.add(`state:${gid}`);
4319
+ let max = this.maxEpochCache.get(gid) ?? -1n;
4320
+ for (const rec of [...epochInserts, ...epochUpdates]) {
4321
+ this.epochCache.set(`${gid}:${rec.id}`, rec.data.slice());
4322
+ this.dirty.add(`epoch:${gid}:${rec.id}`);
4323
+ if (rec.id > max) max = rec.id;
4324
+ }
4325
+ if (max >= 0n) {
4326
+ this.maxEpochCache.set(gid, max);
4327
+ this.dirty.add(`maxEpoch:${gid}`);
1192
4328
  }
1193
- const probe = "__palbase_flags_probe__";
1194
- localStorage.setItem(probe, "1");
1195
- localStorage.removeItem(probe);
1196
- return localStorage;
1197
- } catch {
1198
- return null;
1199
4329
  }
1200
- }
1201
- function resolveVisibility() {
1202
- if (typeof document === "undefined") {
1203
- return null;
4330
+ maxEpochId(groupId) {
4331
+ return this.maxEpochCache.get(bytesKey("", groupId));
1204
4332
  }
1205
- const doc = document;
1206
- return {
1207
- isHidden: () => doc.visibilityState === "hidden",
1208
- onVisibilityChange: (cb) => {
1209
- doc.addEventListener("visibilitychange", cb);
1210
- return () => doc.removeEventListener("visibilitychange", cb);
4333
+ // ── async durable flush (called by the engine wrapper after a mutating op) ──
4334
+ async flush() {
4335
+ if (this.dirty.size === 0) return;
4336
+ const pending = [...this.dirty];
4337
+ this.dirty.clear();
4338
+ await Promise.all(
4339
+ pending.map(async (key) => {
4340
+ if (key.startsWith("state:")) {
4341
+ const gid = key.slice("state:".length);
4342
+ const v = this.stateCache.get(gid);
4343
+ if (v) await this.kv.set(`gss:state:${gid}`, v);
4344
+ } else if (key.startsWith("epoch:")) {
4345
+ const rest = key.slice("epoch:".length);
4346
+ const v = this.epochCache.get(rest);
4347
+ if (v) await this.kv.set(`gss:epoch:${rest}`, v);
4348
+ } else if (key.startsWith("maxEpoch:")) {
4349
+ const gid = key.slice("maxEpoch:".length);
4350
+ const max = this.maxEpochCache.get(gid);
4351
+ if (max != null) await this.kv.set(`gss:maxEpoch:${gid}`, encodeBigint(max));
4352
+ }
4353
+ })
4354
+ );
4355
+ }
4356
+ /** Hydrate the in-memory cache from the durable KV (call before generateClient). */
4357
+ async hydrate() {
4358
+ const keys = await this.kv.keys("gss:");
4359
+ for (const k of keys) {
4360
+ const v = await this.kv.get(k);
4361
+ if (!v) continue;
4362
+ if (k.startsWith("gss:state:")) {
4363
+ this.stateCache.set(k.slice("gss:state:".length), v);
4364
+ } else if (k.startsWith("gss:epoch:")) {
4365
+ this.epochCache.set(k.slice("gss:epoch:".length), v);
4366
+ } else if (k.startsWith("gss:maxEpoch:")) {
4367
+ this.maxEpochCache.set(k.slice("gss:maxEpoch:".length), decodeBigint(v));
4368
+ }
1211
4369
  }
1212
- };
1213
- }
1214
-
1215
- // src/flags-facade.ts
1216
- function palbeAuthAdapter(auth) {
1217
- return {
1218
- onAuthStateChange(callback) {
1219
- const unsubscribe = auth.onAuthStateChange(() => {
1220
- const signedIn = auth.isSignedIn;
1221
- callback(signedIn ? "SIGNED_IN" : "SIGNED_OUT", signedIn ? { signedIn } : null);
1222
- });
1223
- return { data: { subscription: { unsubscribe } } };
4370
+ }
4371
+ async wipe() {
4372
+ this.stateCache.clear();
4373
+ this.epochCache.clear();
4374
+ this.maxEpochCache.clear();
4375
+ this.dirty.clear();
4376
+ for (const k of await this.kv.keys("gss:")) await this.kv.delete(k);
4377
+ }
4378
+ };
4379
+ var KeyPackageStorage = class {
4380
+ constructor(kv) {
4381
+ this.kv = kv;
4382
+ }
4383
+ kv;
4384
+ cache = /* @__PURE__ */ new Map();
4385
+ dirtySet = /* @__PURE__ */ new Set();
4386
+ dirtyDelete = /* @__PURE__ */ new Set();
4387
+ // ── sync engine seam ──
4388
+ delete(id) {
4389
+ const k = bytesKey("", id);
4390
+ this.cache.delete(k);
4391
+ this.dirtyDelete.add(k);
4392
+ this.dirtySet.delete(k);
4393
+ }
4394
+ insert(id, data) {
4395
+ const k = bytesKey("", id);
4396
+ this.cache.set(k, data.slice());
4397
+ this.dirtySet.add(k);
4398
+ this.dirtyDelete.delete(k);
4399
+ }
4400
+ get(id) {
4401
+ return this.cache.get(bytesKey("", id));
4402
+ }
4403
+ // ── async durable flush ──
4404
+ async flush() {
4405
+ if (this.dirtySet.size === 0 && this.dirtyDelete.size === 0) return;
4406
+ const sets = [...this.dirtySet];
4407
+ const dels = [...this.dirtyDelete];
4408
+ this.dirtySet.clear();
4409
+ this.dirtyDelete.clear();
4410
+ await Promise.all([
4411
+ ...sets.map((k) => {
4412
+ const v = this.cache.get(k);
4413
+ return v ? this.kv.set(`kps:${k}`, v) : Promise.resolve();
4414
+ }),
4415
+ ...dels.map((k) => this.kv.delete(`kps:${k}`))
4416
+ ]);
4417
+ }
4418
+ async hydrate() {
4419
+ for (const k of await this.kv.keys("kps:")) {
4420
+ const v = await this.kv.get(k);
4421
+ if (v) this.cache.set(k.slice("kps:".length), v);
1224
4422
  }
1225
- };
4423
+ }
4424
+ async wipe() {
4425
+ this.cache.clear();
4426
+ this.dirtySet.clear();
4427
+ this.dirtyDelete.clear();
4428
+ for (const k of await this.kv.keys("kps:")) await this.kv.delete(k);
4429
+ }
4430
+ };
4431
+ function encodeBigint(n) {
4432
+ return new TextEncoder().encode(n.toString());
1226
4433
  }
1227
- function serverPoolEnv() {
1228
- return {
1229
- now: () => Date.now(),
1230
- setInterval: () => {
1231
- throw new Error("palbe flags: polling is disabled server-side");
1232
- },
1233
- clearInterval: () => {
1234
- },
1235
- storage: null,
1236
- visibility: null
1237
- };
4434
+ function decodeBigint(b) {
4435
+ return BigInt(new TextDecoder().decode(b));
1238
4436
  }
1239
- function sameFlagValue(a, b) {
1240
- if (Object.is(a, b)) return true;
1241
- if (typeof a === "object" && a !== null && typeof b === "object" && b !== null) {
1242
- return JSON.stringify(a) === JSON.stringify(b);
4437
+ var enc = new TextEncoder();
4438
+ var dec = new TextDecoder();
4439
+ var DeviceIdStore = class {
4440
+ constructor(kv) {
4441
+ this.kv = kv;
1243
4442
  }
1244
- return false;
1245
- }
1246
- var PalbeFlags = class {
1247
- transport;
1248
- pool;
4443
+ kv;
4444
+ async load(userId) {
4445
+ const v = await this.kv.get(`devid:${userId}`);
4446
+ return v ? dec.decode(v) : void 0;
4447
+ }
4448
+ async save(userId, deviceId) {
4449
+ await this.kv.set(`devid:${userId}`, enc.encode(deviceId));
4450
+ }
4451
+ async clear(userId) {
4452
+ await this.kv.delete(`devid:${userId}`);
4453
+ }
4454
+ };
4455
+ var SignatureKeyStore = class {
4456
+ constructor(kv) {
4457
+ this.kv = kv;
4458
+ }
4459
+ kv;
4460
+ async load(userId) {
4461
+ return this.kv.get(`sigkey:${userId}`);
4462
+ }
4463
+ async save(userId, keypair) {
4464
+ await this.kv.set(`sigkey:${userId}`, keypair);
4465
+ }
4466
+ };
4467
+
4468
+ // src/messaging/coordinator.ts
4469
+ var MessagingCoordinator = class {
1249
4470
  constructor(rt) {
1250
- this.transport = new FlagsClient(rt.http);
1251
- const browser = typeof document !== "undefined";
1252
- const ref = endpointRefFromApiKey(rt.config.apiKey);
1253
- const options = {
1254
- auth: palbeAuthAdapter(rt.auth),
1255
- ...ref ? { storageKey: `palbe.flags.${ref}` } : {},
1256
- ...browser ? {} : { pollIntervalMs: 0, env: serverPoolEnv() }
4471
+ this.rt = rt;
4472
+ this.kv = createDurableKV();
4473
+ this.deviceStore = new DeviceIdStore(this.kv);
4474
+ this.sigStore = new SignatureKeyStore(this.kv);
4475
+ this.groupStore = new GroupStateStorage(this.kv);
4476
+ this.kpStore = new KeyPackageStorage(this.kv);
4477
+ this.registry.attachChatList(
4478
+ (chats) => {
4479
+ this.chatList = chats;
4480
+ this.emitList();
4481
+ },
4482
+ (group) => this.chatFor(group)
4483
+ );
4484
+ }
4485
+ rt;
4486
+ kv;
4487
+ deviceStore;
4488
+ sigStore;
4489
+ groupStore;
4490
+ kpStore;
4491
+ registry = new GroupRegistry();
4492
+ resolved = null;
4493
+ resolvePromise = null;
4494
+ // Pointer-stable Chat cache (one per rfc id) + the observable chat list.
4495
+ chatByRfc = /* @__PURE__ */ new Map();
4496
+ chatList = [];
4497
+ listListeners = /* @__PURE__ */ new Set();
4498
+ // ── ChatBackend identity ──
4499
+ get selfUserId() {
4500
+ const u = this.rt.auth.currentUser;
4501
+ if (!u) throw new Error("not_signed_in");
4502
+ return u.id;
4503
+ }
4504
+ // ── Public surface (used by the facade) ──
4505
+ /** Self-enroll + start the delivery source (idempotent). The first chat op
4506
+ * calls this implicitly; pb.messaging.start() is the opt-in warm-at-sign-in. */
4507
+ async ensureEnrolled() {
4508
+ await this.resolve();
4509
+ }
4510
+ /** Open the ONE direct chat with userId. INSTANT + LOCAL (no network); the
4511
+ * group materializes on the first send. Pointer-stable per peer pair. */
4512
+ directChat(peerUserId) {
4513
+ const selfId = this.selfUserId;
4514
+ const dk = directKey(selfId, peerUserId);
4515
+ for (const chat2 of this.chatByRfc.values()) {
4516
+ if (chat2.isDirect && chat2.state === "active") {
4517
+ const g = this.registry.all().find((x) => x.displayId === chat2.id);
4518
+ if (g?.directKey === dk) return chat2;
4519
+ }
4520
+ }
4521
+ const draft = {
4522
+ mode: { kind: "direct", peerUserId },
4523
+ reservedId: `draft:${dk}`
1257
4524
  };
1258
- this.pool = new FlagsPool(this.transport, options);
1259
- if (browser) this.pool.start();
4525
+ const cached = this.chatByRfc.get(draft.reservedId);
4526
+ if (cached) return cached;
4527
+ const chat = new Chat({ draft, kind: "direct", backend: this });
4528
+ this.chatByRfc.set(draft.reservedId, chat);
4529
+ void this.ensureEnrolled().catch(() => {
4530
+ });
4531
+ return chat;
1260
4532
  }
1261
- /** Resolves once the first snapshot (or persisted hydrate) is available. Auto-starts the pool. */
1262
- ready() {
1263
- return this.pool.ready();
4533
+ /** Open a multi-party group chat (LOCAL + LAZY; created on first send). */
4534
+ groupChat(members = []) {
4535
+ const draft = {
4536
+ mode: { kind: "group", members },
4537
+ reservedId: `draft:group:${crypto.randomUUID()}`
4538
+ };
4539
+ const chat = new Chat({ draft, kind: "group", backend: this });
4540
+ this.chatByRfc.set(draft.reservedId, chat);
4541
+ void this.ensureEnrolled().catch(() => {
4542
+ });
4543
+ return chat;
1264
4544
  }
1265
- /** Force an immediate re-snapshot. */
1266
- refresh() {
1267
- return this.pool.refresh();
4545
+ /** Look up an active chat by id (grp_ display id). */
4546
+ chatById(id) {
4547
+ const g = this.registry.all().find((x) => x.displayId === id);
4548
+ return g ? this.chatFor(g) : this.chatByRfc.get(id) ?? null;
1268
4549
  }
1269
- /** Frozen snapshot of all cached values (identity-stable until a change). */
1270
- all() {
1271
- return this.pool.all();
4550
+ /** The observable chat list (active chats only). */
4551
+ get chats() {
4552
+ void this.ensureEnrolled().catch(() => {
4553
+ });
4554
+ return this.chatList;
1272
4555
  }
1273
- /** Raw cached value for `key`, or `undefined` when not in the cache. */
1274
- get(key) {
1275
- return this.pool.all()[key];
4556
+ onChatsChange(cb) {
4557
+ this.listListeners.add(cb);
4558
+ void this.ensureEnrolled().catch(() => {
4559
+ });
4560
+ return () => this.listListeners.delete(cb);
4561
+ }
4562
+ emitList() {
4563
+ for (const cb of this.listListeners) cb();
4564
+ }
4565
+ /** Pointer-stable Chat for a group (one per rfc id). */
4566
+ chatFor(group) {
4567
+ const existing = this.chatByRfc.get(group.rfcGroupId);
4568
+ if (existing) return existing;
4569
+ const chat = new Chat({
4570
+ group,
4571
+ kind: group.directKey ? "direct" : "group",
4572
+ backend: this
4573
+ });
4574
+ this.chatByRfc.set(group.rfcGroupId, chat);
4575
+ return chat;
4576
+ }
4577
+ // ── Resolution (build-once) ──
4578
+ resolve() {
4579
+ if (this.resolved) return Promise.resolve(this.resolved);
4580
+ if (this.resolvePromise) return this.resolvePromise;
4581
+ this.resolvePromise = this.buildResolved();
4582
+ return this.resolvePromise;
4583
+ }
4584
+ async buildResolved() {
4585
+ const userId = this.selfUserId;
4586
+ const deviceId = await this.deviceStore.load(userId) ?? mintDeviceId();
4587
+ const engine = await MlsEngine.create({
4588
+ userId,
4589
+ deviceId,
4590
+ groupStore: this.groupStore,
4591
+ kpStore: this.kpStore,
4592
+ sigStore: this.sigStore
4593
+ });
4594
+ const messageStore = new MessageStore(this.kv);
4595
+ const catalog = new GroupCatalog(this.kv);
4596
+ const hub = new MessageHub();
4597
+ const groups = new GroupMessaging(this.rt, engine, userId, deviceId, messageStore);
4598
+ const source = new MessageDeliverySource(
4599
+ this.rt,
4600
+ engine,
4601
+ hub,
4602
+ this.registry,
4603
+ messageStore,
4604
+ deviceId,
4605
+ userId
4606
+ );
4607
+ await enrollDevice(this.rt, engine, this.deviceStore, {});
4608
+ await this.registry.attachCatalog(catalog);
4609
+ const resolved = {
4610
+ engine,
4611
+ groups,
4612
+ source,
4613
+ hub,
4614
+ messageStore,
4615
+ catalog,
4616
+ registry: this.registry,
4617
+ deviceId,
4618
+ userId
4619
+ };
4620
+ this.resolved = resolved;
4621
+ await source.start();
4622
+ return resolved;
4623
+ }
4624
+ // ── ChatBackend impl ──
4625
+ async materialize(draft) {
4626
+ const r = await this.resolve();
4627
+ let group;
4628
+ if (draft.mode.kind === "direct") {
4629
+ group = await r.groups.getOrCreateDirect(draft.mode.peerUserId);
4630
+ } else {
4631
+ group = await r.groups.createGroup();
4632
+ for (const userId of draft.mode.members) {
4633
+ await r.groups.addMember(group, userId);
4634
+ }
4635
+ }
4636
+ void r.catalog.upsert({
4637
+ displayId: group.displayId,
4638
+ rfcGroupId: group.rfcGroupId,
4639
+ currentEpoch: group.currentEpoch,
4640
+ ownerUserId: group.ownerUserId,
4641
+ directKey: group.directKey,
4642
+ name: group.name
4643
+ });
4644
+ return group;
4645
+ }
4646
+ async sendText(group, text, replyTo) {
4647
+ const r = await this.resolve();
4648
+ return r.groups.sendText(group, text, replyTo);
4649
+ }
4650
+ async history(group, limit, before) {
4651
+ const r = await this.resolve();
4652
+ const rows = await r.messageStore.history(group.rfcGroupId, limit, before);
4653
+ const lookup = /* @__PURE__ */ new Map();
4654
+ for (const s of rows) {
4655
+ const cid = s.clientMsgId ?? "";
4656
+ if (cid && s.text !== null) {
4657
+ const senderUserId = s.direction === "outgoing" ? this.selfUserId : "";
4658
+ lookup.set(cid, { text: s.text, senderUserId });
4659
+ }
4660
+ }
4661
+ return rows.map((s) => this.toChatMessage(group, s, (id) => lookup.get(id) ?? null));
4662
+ }
4663
+ toChatMessage(group, s, lookup) {
4664
+ const clientMsgId = s.clientMsgId ?? "";
4665
+ let replyTo = null;
4666
+ if (s.replyTo && lookup) {
4667
+ const ref = {
4668
+ v: 1,
4669
+ client_msg_id: s.replyTo.clientMsgId,
4670
+ preview: {
4671
+ kind: s.replyTo.previewKind,
4672
+ author_user_id: s.replyTo.previewAuthorUserId ?? "",
4673
+ body: s.replyTo.previewBody ?? void 0,
4674
+ body_truncated: false
4675
+ }
4676
+ };
4677
+ replyTo = resolveReply(ref, lookup);
4678
+ }
4679
+ return {
4680
+ id: `${group.displayId}#${s.serverSeq}`,
4681
+ kind: s.text != null ? "text" : "system",
4682
+ direction: s.direction,
4683
+ senderUserId: s.direction === "outgoing" ? this.selfUserId : null,
4684
+ text: s.text,
4685
+ serverSeq: s.serverSeq,
4686
+ sentAt: new Date(s.at),
4687
+ clientMsgId,
4688
+ replyTo
4689
+ };
1276
4690
  }
1277
- /** `true` only when the cached value is strictly `true`; `fallback` when the key is absent. */
1278
- isEnabled(key, fallback = false) {
1279
- return this.pool.isEnabled(key, fallback);
4691
+ async members(group) {
4692
+ const r = await this.resolve();
4693
+ try {
4694
+ const rows = await r.groups.listMembers(group.displayId);
4695
+ const byUser = /* @__PURE__ */ new Map();
4696
+ for (const row of rows) {
4697
+ if (!byUser.has(row.user_id)) {
4698
+ byUser.set(row.user_id, {
4699
+ id: row.user_id,
4700
+ userId: row.user_id,
4701
+ displayName: null,
4702
+ role: row.role,
4703
+ isSelf: row.user_id === this.selfUserId
4704
+ });
4705
+ }
4706
+ }
4707
+ return [...byUser.values()];
4708
+ } catch {
4709
+ return [];
4710
+ }
1280
4711
  }
1281
- /** Alias of {@link isEnabled} (iOS parity). */
1282
- bool(key, fallback = false) {
1283
- return this.isEnabled(key, fallback);
4712
+ async addMember(group, userId) {
4713
+ const r = await this.resolve();
4714
+ await r.groups.addMember(group, userId);
1284
4715
  }
1285
- /** Cached value when it is a string, else `fallback`. */
1286
- getString(key, fallback) {
1287
- const value = this.pool.all()[key];
1288
- return typeof value === "string" ? value : fallback;
4716
+ async removeMember(group, userId) {
4717
+ const r = await this.resolve();
4718
+ await r.groups.removeMember(group, userId);
1289
4719
  }
1290
- /** Cached value when it is an integer number, else `fallback`. */
1291
- getInt(key, fallback) {
1292
- const value = this.pool.all()[key];
1293
- return typeof value === "number" && Number.isInteger(value) ? value : fallback;
4720
+ async leave(group) {
4721
+ const r = await this.resolve();
4722
+ await r.groups.leaveGroup(group);
4723
+ r.registry.remove(group.rfcGroupId);
1294
4724
  }
1295
- /** Cached value when it is a number (integers included), else `fallback`. */
1296
- getDouble(key, fallback) {
1297
- const value = this.pool.all()[key];
1298
- return typeof value === "number" ? value : fallback;
4725
+ registerActive(group) {
4726
+ this.registry.register(group);
1299
4727
  }
1300
- /**
1301
- * Resolve the multivariate variant for `key` — the variant name, or `null`
1302
- * when the flag has no variant (or the read fails).
1303
- *
1304
- * Wire failures (network errors, 404, etc.) resolve to `null`.
1305
- * Invalid flag names throw `BackendError('validation', { code: 'invalid_flag_name' })`.
1306
- *
1307
- * DEVIATION from iOS sync-cache parity: the platform does not propagate
1308
- * variant metadata into the user-flags snapshot/delta cache, so this is an
1309
- * ASYNC transport read (`GET /v1/flags/{key}/variant`), not a cache lookup.
1310
- */
1311
- async getVariant(key) {
1312
- let res;
1313
- try {
1314
- res = await this.transport.getVariant(key);
1315
- } catch (err) {
1316
- throw new BackendError("validation", {
1317
- code: "invalid_flag_name",
1318
- message: err instanceof Error ? err.message : String(err)
4728
+ setTyping(group, isTyping) {
4729
+ void this.resolve().then((r) => r.source.setTyping(group, isTyping));
4730
+ }
4731
+ async markRead(group, upToServerSeq) {
4732
+ const r = await this.resolve();
4733
+ await r.source.markRead(group, upToServerSeq);
4734
+ }
4735
+ subscribeLive(group, chat) {
4736
+ let offMsg = null;
4737
+ let offConv = null;
4738
+ void this.resolve().then((r) => {
4739
+ offMsg = r.hub.onMessage(group.displayId, (m) => {
4740
+ void chat.ingestLive(m);
1319
4741
  });
4742
+ r.source.observeConversation(group);
4743
+ offConv = r.hub.onConv(group.displayId, (e) => chat.applyConv(e.event, e.payload));
4744
+ });
4745
+ return () => {
4746
+ offMsg?.();
4747
+ offConv?.();
4748
+ };
4749
+ }
4750
+ async userIdForDevice(group, deviceId) {
4751
+ const r = await this.resolve();
4752
+ try {
4753
+ const rows = await r.groups.listMembers(group.displayId);
4754
+ return rows.find((m) => m.device_id === deviceId)?.user_id ?? null;
4755
+ } catch {
4756
+ return null;
1320
4757
  }
1321
- return res.data?.name ?? null;
1322
4758
  }
1323
- /** Subscribe to any change in the cached flag set. */
1324
- onChange(callback) {
1325
- return this.pool.onChange(callback);
4759
+ // ── Trust (chat-scoped) ──
4760
+ /** List a user's devices (used by safety-number computation). */
4761
+ async listUserDevices(userId) {
4762
+ const res = await listDevices(this.rt, userId);
4763
+ return res.devices.map((d) => d.device_id);
4764
+ }
4765
+ };
4766
+
4767
+ // src/messaging/facade.ts
4768
+ var PalbeMessaging = class {
4769
+ coordinator;
4770
+ constructor(rt) {
4771
+ this.coordinator = new MessagingCoordinator(rt);
1326
4772
  }
1327
4773
  /**
1328
- * Observe ONE key: fires only when that key's value actually changes
1329
- * (per {@link sameFlagValue} structural compare for objects), with the
1330
- * new value (`undefined` = deleted). P5 React-hook substrate.
4774
+ * Warm messaging at sign-in (optional). Enrolls this device + starts the live
4775
+ * delivery source so incoming DMs/Welcomes drain before the chat list opens.
4776
+ * Never REQUIRED any chat op self-enrolls. Idempotent.
1331
4777
  */
1332
- subscribeKey(key, callback) {
1333
- let last = this.pool.all()[key];
1334
- return this.pool.onChange(() => {
1335
- const next = this.pool.all()[key];
1336
- if (sameFlagValue(last, next)) return;
1337
- last = next;
1338
- callback(next);
1339
- });
4778
+ async start() {
4779
+ await this.coordinator.ensureEnrolled();
1340
4780
  }
1341
4781
  /**
1342
- * Async iteration over flag changes: yields the new {@link all} view on
1343
- * every pool change notification. The listener is detached when the
1344
- * consumer `break`s/`return`s/`throw`s including while a `next()` is
1345
- * still pending (it resolves `{ done: true }` instead of hanging, which a
1346
- * plain async-generator `finally` would not guarantee).
4782
+ * Open the ONE direct chat with `userId`. INSTANT + LOCAL no network, nothing
4783
+ * created yet. Returns a draft `Chat` to show immediately; the server group +
4784
+ * MLS material materialize LAZILY on the first `chat.send`. Idempotent + stable:
4785
+ * the same two users always resolve to the SAME Chat.
1347
4786
  */
1348
- changes() {
1349
- const queue = [];
1350
- const pending = [];
1351
- let finished = false;
1352
- const unsubscribe = this.pool.onChange(() => {
1353
- const snapshot = this.pool.all();
1354
- const resolve = pending.shift();
1355
- if (resolve) resolve({ value: snapshot, done: false });
1356
- else queue.push(snapshot);
1357
- });
1358
- const finish = () => {
1359
- if (finished) return;
1360
- finished = true;
1361
- unsubscribe();
1362
- while (pending.length > 0) pending.shift()?.({ value: void 0, done: true });
1363
- };
1364
- return {
1365
- next: () => {
1366
- if (finished) return Promise.resolve({ value: void 0, done: true });
1367
- const head = queue.shift();
1368
- if (head !== void 0) return Promise.resolve({ value: head, done: false });
1369
- return new Promise((resolve) => {
1370
- pending.push(resolve);
1371
- });
1372
- },
1373
- return: () => {
1374
- finish();
1375
- return Promise.resolve({ value: void 0, done: true });
1376
- },
1377
- throw: (error) => {
1378
- finish();
1379
- return Promise.reject(error);
1380
- },
1381
- [Symbol.asyncIterator]() {
1382
- return this;
1383
- }
1384
- };
4787
+ directChat(userId) {
4788
+ return this.coordinator.directChat(userId);
1385
4789
  }
1386
- /** Stop polling and detach all listeners (auth + visibility + subscribers). */
1387
- destroy() {
1388
- this.pool.destroy();
4790
+ /**
4791
+ * Open a multi-party group chat. LOCAL + LAZY like `directChat`: pick the
4792
+ * members, get a draft Chat, the group is created on the FIRST `chat.send`.
4793
+ */
4794
+ groupChat(opts = {}) {
4795
+ return this.coordinator.groupChat(opts.members ?? []);
4796
+ }
4797
+ /** Look up a chat by id (e.g. from a push payload / deep link). null if unknown. */
4798
+ chat(id) {
4799
+ return this.coordinator.chatById(id);
4800
+ }
4801
+ /** The observable chat list (DMs + groups, active only). Read it after an
4802
+ * `onChatsChange` subscription to render the inbox. */
4803
+ get chats() {
4804
+ return this.coordinator.chats;
4805
+ }
4806
+ /** Subscribe to chat-list changes (a chat added/removed/hydrated). */
4807
+ onChatsChange(cb) {
4808
+ return this.coordinator.onChatsChange(cb);
1389
4809
  }
1390
4810
  };
1391
4811
 
@@ -1856,10 +5276,10 @@ var RealtimeSocket = class {
1856
5276
  this.flushPending(topic);
1857
5277
  }
1858
5278
  flushPending(topic) {
1859
- const ready = this.pendingBroadcasts.filter((p) => p.topic === topic);
1860
- if (ready.length === 0) return;
5279
+ const ready2 = this.pendingBroadcasts.filter((p) => p.topic === topic);
5280
+ if (ready2.length === 0) return;
1861
5281
  this.pendingBroadcasts = this.pendingBroadcasts.filter((p) => p.topic !== topic);
1862
- for (const p of ready) this.sendBroadcast(p.topic, p.event, p.payload);
5282
+ for (const p of ready2) this.sendBroadcast(p.topic, p.event, p.payload);
1863
5283
  }
1864
5284
  startHeartbeat() {
1865
5285
  this.stopHeartbeat();
@@ -2195,7 +5615,7 @@ function localStorageSessionStorage(key = DEFAULT_KEY) {
2195
5615
  }
2196
5616
 
2197
5617
  // src/version.ts
2198
- var VERSION = "1.0.1";
5618
+ var VERSION = "1.1.1";
2199
5619
 
2200
5620
  // src/internal.ts
2201
5621
  function getRuntime() {
@@ -2358,6 +5778,12 @@ function createClientProxy(resolveRt, nsAccessor) {
2358
5778
  },
2359
5779
  get analytics() {
2360
5780
  return resolveRt().analytics;
5781
+ },
5782
+ get calls() {
5783
+ return resolveRt().calls;
5784
+ },
5785
+ get messaging() {
5786
+ return resolveRt().messaging;
2361
5787
  }
2362
5788
  };
2363
5789
  return new Proxy(base, {
@@ -2382,10 +5808,13 @@ var pb = createClientProxy(getRuntime, getNamespace);
2382
5808
  BackendError,
2383
5809
  PalbeAnalytics,
2384
5810
  PalbeAuth,
5811
+ PalbeCalls,
2385
5812
  PalbeFlags,
5813
+ PalbeMessaging,
2386
5814
  PalbeRealtime,
2387
5815
  RealtimeChannel,
2388
5816
  VERSION,
5817
+ initMls,
2389
5818
  isBackendError,
2390
5819
  localStorageSessionStorage,
2391
5820
  memorySessionStorage,