@ai-matrx/messaging 0.11.8 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs CHANGED
@@ -52,6 +52,7 @@ __export(react_exports, {
52
52
  RPCS: () => RPCS,
53
53
  ReferenceCard: () => ReferenceCard,
54
54
  ReplyIcon: () => ReplyIcon,
55
+ SERVER_SESSION_IDENTITY: () => SERVER_SESSION_IDENTITY,
55
56
  SearchIcon: () => SearchIcon,
56
57
  SendIcon: () => SendIcon,
57
58
  SparklesIcon: () => SparklesIcon,
@@ -73,6 +74,7 @@ __export(react_exports, {
73
74
  createMessagingStore: () => createMessagingStore,
74
75
  createOutbox: () => createOutbox,
75
76
  createReadCache: () => createReadCache,
77
+ createSessionIdentity: () => createSessionIdentity,
76
78
  createWebOutboxStorage: () => createWebOutboxStorage,
77
79
  effectiveActorKey: () => effectiveActorKey,
78
80
  extractReferences: () => extractReferences,
@@ -1544,6 +1546,145 @@ function createReadCache(options) {
1544
1546
  };
1545
1547
  }
1546
1548
 
1549
+ // src/core/session-identity.ts
1550
+ var RESOLVING = Object.freeze({
1551
+ state: "resolving",
1552
+ userId: null
1553
+ });
1554
+ var SERVER_SESSION_IDENTITY = RESOLVING;
1555
+ function authOf(client) {
1556
+ const candidate = client.auth;
1557
+ if (candidate === null || typeof candidate !== "object") return null;
1558
+ const auth = candidate;
1559
+ if (typeof auth.getSession !== "function") return null;
1560
+ if (typeof auth.onAuthStateChange !== "function") return null;
1561
+ return auth;
1562
+ }
1563
+ function userIdOf(session) {
1564
+ const id = session?.user?.id;
1565
+ return typeof id === "string" && id.length > 0 ? asUserId(id) : null;
1566
+ }
1567
+ var NO_AUTH_SOURCE = {
1568
+ message: "Messaging cannot tell who is signed in: the Supabase client it was given has no auth session source.",
1569
+ remedy: "Pass your app's browser Supabase client \u2014 the same client it signs in with \u2014 to <MessagingProvider client={...}> or createMessagingRepository({ client }). Messaging reads the acting user from that client's session; there is no prop or option for a user id, on purpose (DD-241)."
1570
+ };
1571
+ function createSessionIdentity(options) {
1572
+ const { client, onDiagnostic } = options;
1573
+ const listeners = /* @__PURE__ */ new Set();
1574
+ let current = RESOLVING;
1575
+ let disposed = false;
1576
+ const publish = (state, userId) => {
1577
+ if (disposed) return;
1578
+ if (current.state === state && current.userId === userId) return;
1579
+ current = Object.freeze({ state, userId });
1580
+ for (const listener of [...listeners]) listener();
1581
+ };
1582
+ const subscribeOnly = (hasAuthSource) => ({
1583
+ hasAuthSource,
1584
+ snapshot: () => current,
1585
+ subscribe: (listener) => {
1586
+ listeners.add(listener);
1587
+ return () => {
1588
+ listeners.delete(listener);
1589
+ };
1590
+ },
1591
+ dispose: () => {
1592
+ disposed = true;
1593
+ listeners.clear();
1594
+ }
1595
+ });
1596
+ const auth = authOf(client);
1597
+ if (auth === null) {
1598
+ onDiagnostic?.({ level: "error", ...NO_AUTH_SOURCE });
1599
+ current = Object.freeze({ state: "resolved", userId: null });
1600
+ return subscribeOnly(false);
1601
+ }
1602
+ void (async () => {
1603
+ try {
1604
+ const result = await auth.getSession();
1605
+ publish("resolved", userIdOf(result?.data?.session));
1606
+ } catch (error) {
1607
+ onDiagnostic?.({
1608
+ level: "warn",
1609
+ message: `Messaging could not read the signed-in session: ${String(
1610
+ error?.message ?? error
1611
+ )}`,
1612
+ remedy: "Messages stay unavailable on this device until the session can be read. Reload the page; if it persists, sign in again."
1613
+ });
1614
+ publish("resolved", null);
1615
+ }
1616
+ })();
1617
+ const subscription = auth.onAuthStateChange((_event, session) => {
1618
+ publish("resolved", userIdOf(session));
1619
+ });
1620
+ const base = subscribeOnly(true);
1621
+ return {
1622
+ hasAuthSource: true,
1623
+ snapshot: base.snapshot,
1624
+ subscribe: base.subscribe,
1625
+ dispose: () => {
1626
+ base.dispose();
1627
+ try {
1628
+ subscription.data.subscription.unsubscribe();
1629
+ } catch {
1630
+ }
1631
+ }
1632
+ };
1633
+ }
1634
+ function actingUserIdOf(identity) {
1635
+ return new Promise((resolve, reject) => {
1636
+ const settle = () => {
1637
+ const snapshot = identity.snapshot();
1638
+ if (snapshot.state !== "resolved") return false;
1639
+ if (snapshot.userId === null) {
1640
+ reject(identity.hasAuthSource ? noSignedInUserError() : noAuthSourceError());
1641
+ return true;
1642
+ }
1643
+ resolve(snapshot.userId);
1644
+ return true;
1645
+ };
1646
+ if (settle()) return;
1647
+ const unsubscribe = identity.subscribe(() => {
1648
+ if (settle()) unsubscribe();
1649
+ });
1650
+ });
1651
+ }
1652
+ function noSignedInUserError() {
1653
+ return new MessagingError(
1654
+ "session-unavailable",
1655
+ "Messaging has no signed-in user: the Supabase client's session names nobody.",
1656
+ "Sign in, then try again. Messaging takes the acting user from the session of the client it was given \u2014 never from an id handed in by the caller (DD-241), because a handed-in id goes stale the moment the auth cookie rotates and lands on the wire as sender_id and created_by."
1657
+ );
1658
+ }
1659
+ function noAuthSourceError() {
1660
+ return new MessagingError(
1661
+ "misconfigured",
1662
+ NO_AUTH_SOURCE.message,
1663
+ NO_AUTH_SOURCE.remedy
1664
+ );
1665
+ }
1666
+ async function readActingUserId(client) {
1667
+ const auth = authOf(client);
1668
+ if (auth === null) throw noAuthSourceError();
1669
+ let session;
1670
+ try {
1671
+ const result = await auth.getSession();
1672
+ session = result?.data?.session;
1673
+ } catch (error) {
1674
+ throw new MessagingError(
1675
+ "session-unavailable",
1676
+ `Messaging could not read the signed-in session: ${String(
1677
+ error?.message ?? error
1678
+ )}`,
1679
+ "Reload the page and try again; if it persists, sign in again.",
1680
+ error
1681
+ );
1682
+ }
1683
+ const userId = userIdOf(session);
1684
+ if (userId === null) throw noSignedInUserError();
1685
+ return userId;
1686
+ }
1687
+
1547
1688
  // src/core/repository.ts
1548
1689
  var MESSAGING_SCHEMA = "communication";
1549
1690
  var MESSAGING_RPC_SCHEMA = "public";
@@ -1580,8 +1721,15 @@ function sleep(ms) {
1580
1721
  return new Promise((resolve) => setTimeout(resolve, jittered));
1581
1722
  }
1582
1723
  function createMessagingRepository(options) {
1583
- const { identity } = options;
1584
- const org = requireOrg(identity.organizationId, "createMessagingRepository");
1724
+ const org = requireOrg(options.organizationId, "createMessagingRepository");
1725
+ const handedIn = options.identity;
1726
+ if (handedIn !== void 0 && handedIn !== null && handedIn.userId !== void 0) {
1727
+ options.onDiagnostic?.(
1728
+ "createMessagingRepository({ identity: { userId } }) is ignored: since 0.12.0 messaging reads the acting user from the session of the client it was given (DD-241). Remove the option and pass { client, organizationId } \u2014 a handed-in id goes stale the moment the auth cookie rotates, and it lands on the wire as sender_id and created_by."
1729
+ );
1730
+ }
1731
+ const sessionIdentity = options.sessionIdentity;
1732
+ const actingUserId = () => sessionIdentity !== void 0 ? actingUserIdOf(sessionIdentity) : readActingUserId(options.client);
1585
1733
  const userCache = createReadCache({
1586
1734
  ttlMs: options.userTtlMs ?? 5 * 6e4,
1587
1735
  ...options.now !== void 0 ? { now: options.now } : {}
@@ -1618,6 +1766,9 @@ function createMessagingRepository(options) {
1618
1766
  }
1619
1767
  }
1620
1768
  }
1769
+ function actingUser(operation) {
1770
+ return withSessionRetry(operation, () => actingUserId());
1771
+ }
1621
1772
  async function rpc(fn, args, operation) {
1622
1773
  const { data, error } = await rpcDb().rpc(fn, args);
1623
1774
  if (error !== null) throw normalizeMessagingError(error, operation);
@@ -1641,17 +1792,19 @@ function createMessagingRepository(options) {
1641
1792
  return found;
1642
1793
  }
1643
1794
  const repository = {
1644
- identity,
1795
+ organizationId: org,
1796
+ actingUserId,
1645
1797
  async listConversations(args = {}) {
1646
1798
  const limit = args.limit ?? 30;
1647
1799
  const archived = args.archived ?? DEFAULT_MESSAGING_ARCHIVE_FILTER;
1648
1800
  const operation = "listConversations";
1801
+ const me = await actingUser(operation);
1649
1802
  const rows = await withSessionRetry(
1650
1803
  operation,
1651
1804
  () => rpc(
1652
1805
  RPCS.conversationsWithDetails,
1653
1806
  {
1654
- p_user_id: identity.userId,
1807
+ p_user_id: me,
1655
1808
  p_limit: limit + 1,
1656
1809
  p_before_sort_at: args.cursor?.beforeSortAt ?? null,
1657
1810
  p_before_conversation_id: args.cursor?.beforeConversationId ?? null,
@@ -1668,7 +1821,7 @@ function createMessagingRepository(options) {
1668
1821
  throw invalidResponse(operation, `${RPCS.conversationsWithDetails} did not return rows`);
1669
1822
  }
1670
1823
  const projected = (rows ?? []).map(
1671
- (row) => projectConversationSummary(row, identity.userId, org)
1824
+ (row) => projectConversationSummary(row, me, org)
1672
1825
  );
1673
1826
  const hasMore = projected.length > limit;
1674
1827
  const items = hasMore ? projected.slice(0, limit) : projected;
@@ -1682,12 +1835,13 @@ function createMessagingRepository(options) {
1682
1835
  async countArchivedConversations(args = {}) {
1683
1836
  const limit = args.limit ?? 100;
1684
1837
  const operation = "countArchivedConversations";
1838
+ const me = await actingUser(operation);
1685
1839
  const rows = await withSessionRetry(
1686
1840
  operation,
1687
1841
  () => rpc(
1688
1842
  RPCS.conversationsWithDetails,
1689
1843
  {
1690
- p_user_id: identity.userId,
1844
+ p_user_id: me,
1691
1845
  p_limit: limit + 1,
1692
1846
  p_before_sort_at: null,
1693
1847
  p_before_conversation_id: null,
@@ -1704,6 +1858,7 @@ function createMessagingRepository(options) {
1704
1858
  },
1705
1859
  async getConversation(id) {
1706
1860
  const operation = "getConversation";
1861
+ const me = await actingUser(operation);
1707
1862
  const { data, error } = await withSessionRetry(
1708
1863
  operation,
1709
1864
  async () => db().from(TABLES.conversations).select(CONVERSATION_COLUMNS).eq("id", id).single()
@@ -1724,7 +1879,7 @@ function createMessagingRepository(options) {
1724
1879
  participants: [],
1725
1880
  unread_count: 0
1726
1881
  },
1727
- identity.userId,
1882
+ me,
1728
1883
  org
1729
1884
  );
1730
1885
  return summary.conversation;
@@ -1768,6 +1923,7 @@ function createMessagingRepository(options) {
1768
1923
  },
1769
1924
  async getOrCreateDirectConversation(otherUserId) {
1770
1925
  const operation = "getOrCreateDirectConversation";
1926
+ const me = await actingUser(operation);
1771
1927
  const id = await withSessionRetry(
1772
1928
  operation,
1773
1929
  () => rpc(
@@ -1776,7 +1932,7 @@ function createMessagingRepository(options) {
1776
1932
  // The RPC's own guard requires an `authenticated` caller to pass
1777
1933
  // THEMSELVES as user1; passing them in the other slot is a denial,
1778
1934
  // not a preference.
1779
- p_user1_id: identity.userId,
1935
+ p_user1_id: me,
1780
1936
  p_user2_id: otherUserId,
1781
1937
  p_organization_id: org
1782
1938
  },
@@ -1790,13 +1946,14 @@ function createMessagingRepository(options) {
1790
1946
  },
1791
1947
  async createGroupConversation({ name, memberIds }) {
1792
1948
  const operation = "createGroupConversation";
1949
+ const me = await actingUser(operation);
1793
1950
  const { data, error } = await withSessionRetry(
1794
1951
  operation,
1795
1952
  async () => db().from(TABLES.conversations).insert({
1796
1953
  type: "group",
1797
1954
  group_name: name,
1798
1955
  organization_id: org,
1799
- created_by: identity.userId
1956
+ created_by: me
1800
1957
  }).select("id").single()
1801
1958
  );
1802
1959
  if (error !== null) throw normalizeMessagingError(error, operation);
@@ -1804,14 +1961,14 @@ function createMessagingRepository(options) {
1804
1961
  if (typeof conversationId !== "string") {
1805
1962
  throw invalidResponse(operation, "insert returned no conversation id");
1806
1963
  }
1807
- const members = [.../* @__PURE__ */ new Set([identity.userId, ...memberIds])];
1964
+ const members = [.../* @__PURE__ */ new Set([me, ...memberIds])];
1808
1965
  const { error: memberError } = await db().from(TABLES.participants).insert(
1809
1966
  members.map((userId) => ({
1810
1967
  conversation_id: conversationId,
1811
1968
  user_id: userId,
1812
- role: userId === identity.userId ? "owner" : "member",
1969
+ role: userId === me ? "owner" : "member",
1813
1970
  organization_id: org,
1814
- created_by: identity.userId
1971
+ created_by: me
1815
1972
  }))
1816
1973
  );
1817
1974
  if (memberError !== null) throw normalizeMessagingError(memberError, operation);
@@ -1819,11 +1976,12 @@ function createMessagingRepository(options) {
1819
1976
  },
1820
1977
  async insertMessage(draft, clientMessageId) {
1821
1978
  const operation = "insertMessage";
1979
+ const me = await actingUser(operation);
1822
1980
  const { data, error } = await db().from(TABLES.messages).insert({
1823
1981
  conversation_id: draft.conversationId,
1824
- sender_id: identity.userId,
1982
+ sender_id: me,
1825
1983
  organization_id: org,
1826
- created_by: identity.userId,
1984
+ created_by: me,
1827
1985
  content: draft.content,
1828
1986
  message_type: draft.kind ?? "text",
1829
1987
  status: "sent",
@@ -1845,10 +2003,11 @@ function createMessagingRepository(options) {
1845
2003
  },
1846
2004
  async editMessage(id, content) {
1847
2005
  const operation = "editMessage";
2006
+ const me = await actingUser(operation);
1848
2007
  const { data, error } = await db().from(TABLES.messages).update({
1849
2008
  content,
1850
2009
  edited_at: (/* @__PURE__ */ new Date()).toISOString(),
1851
- updated_by: identity.userId
2010
+ updated_by: me
1852
2011
  }).eq("id", id).select(MESSAGE_COLUMNS).single();
1853
2012
  if (error !== null) throw normalizeMessagingError(error, operation);
1854
2013
  if (data === null) throw invalidResponse(operation, "update returned no row");
@@ -1856,47 +2015,53 @@ function createMessagingRepository(options) {
1856
2015
  },
1857
2016
  async deleteMessage(id, forEveryone) {
1858
2017
  const operation = "deleteMessage";
2018
+ const me = await actingUser(operation);
1859
2019
  const { error } = await db().from(TABLES.messages).update({
1860
2020
  deleted_at: (/* @__PURE__ */ new Date()).toISOString(),
1861
2021
  deleted_for_everyone: forEveryone,
1862
- updated_by: identity.userId
2022
+ updated_by: me
1863
2023
  }).eq("id", id);
1864
2024
  if (error !== null) throw normalizeMessagingError(error, operation);
1865
2025
  },
1866
2026
  async markRead(conversationId, at) {
1867
2027
  const operation = "markRead";
1868
- const { error } = await db().from(TABLES.participants).update({ last_read_at: at ?? (/* @__PURE__ */ new Date()).toISOString(), updated_by: identity.userId }).eq("conversation_id", conversationId).eq("user_id", identity.userId);
2028
+ const me = await actingUser(operation);
2029
+ const { error } = await db().from(TABLES.participants).update({ last_read_at: at ?? (/* @__PURE__ */ new Date()).toISOString(), updated_by: me }).eq("conversation_id", conversationId).eq("user_id", me);
1869
2030
  if (error !== null) throw normalizeMessagingError(error, operation);
1870
2031
  },
1871
2032
  async setConversationFlags(conversationId, flags) {
1872
2033
  const operation = "setConversationFlags";
1873
- const patch = { updated_by: identity.userId };
2034
+ const me = await actingUser(operation);
2035
+ const patch = { updated_by: me };
1874
2036
  if (flags.isMuted !== void 0) patch["is_muted"] = flags.isMuted;
1875
2037
  if (flags.isArchived !== void 0) patch["is_archived"] = flags.isArchived;
1876
- const { error } = await db().from(TABLES.participants).update(patch).eq("conversation_id", conversationId).eq("user_id", identity.userId);
2038
+ const { error } = await db().from(TABLES.participants).update(patch).eq("conversation_id", conversationId).eq("user_id", me);
1877
2039
  if (error !== null) throw normalizeMessagingError(error, operation);
1878
2040
  },
1879
2041
  async addMembers(conversationId, memberIds) {
1880
2042
  const operation = "addMembers";
2043
+ const me = await actingUser(operation);
1881
2044
  const { error } = await db().from(TABLES.participants).insert(
1882
2045
  memberIds.map((userId) => ({
1883
2046
  conversation_id: conversationId,
1884
2047
  user_id: userId,
1885
2048
  role: "member",
1886
2049
  organization_id: org,
1887
- created_by: identity.userId
2050
+ created_by: me
1888
2051
  }))
1889
2052
  );
1890
2053
  if (error !== null) throw normalizeMessagingError(error, operation);
1891
2054
  },
1892
2055
  async removeMember(conversationId, memberId) {
1893
2056
  const operation = "removeMember";
1894
- const { error } = await db().from(TABLES.participants).update({ deleted_at: (/* @__PURE__ */ new Date()).toISOString(), updated_by: identity.userId }).eq("conversation_id", conversationId).eq("user_id", memberId);
2057
+ const me = await actingUser(operation);
2058
+ const { error } = await db().from(TABLES.participants).update({ deleted_at: (/* @__PURE__ */ new Date()).toISOString(), updated_by: me }).eq("conversation_id", conversationId).eq("user_id", memberId);
1895
2059
  if (error !== null) throw normalizeMessagingError(error, operation);
1896
2060
  },
1897
2061
  async setMemberRole(conversationId, memberId, role) {
1898
2062
  const operation = "setMemberRole";
1899
- const { error } = await db().from(TABLES.participants).update({ role, updated_by: identity.userId }).eq("conversation_id", conversationId).eq("user_id", memberId);
2063
+ const me = await actingUser(operation);
2064
+ const { error } = await db().from(TABLES.participants).update({ role, updated_by: me }).eq("conversation_id", conversationId).eq("user_id", memberId);
1900
2065
  if (error !== null) throw normalizeMessagingError(error, operation);
1901
2066
  },
1902
2067
  getUser(userId) {
@@ -1951,22 +2116,69 @@ function defaultDiagnostics(event) {
1951
2116
  else if (event.level === "warn") console.warn(line);
1952
2117
  else console.info(line);
1953
2118
  }
2119
+ function useSessionUserId(client, onDiagnostic) {
2120
+ const diagnosticRef = (0, import_react.useRef)(onDiagnostic);
2121
+ diagnosticRef.current = onDiagnostic;
2122
+ const identity = (0, import_react.useMemo)(() => {
2123
+ if (client == null) return null;
2124
+ return createSessionIdentity({
2125
+ client,
2126
+ onDiagnostic: (event) => {
2127
+ (diagnosticRef.current ?? defaultDiagnostics)(event);
2128
+ }
2129
+ });
2130
+ }, [client]);
2131
+ (0, import_react.useEffect)(() => {
2132
+ if (identity === null) return void 0;
2133
+ return () => {
2134
+ identity.dispose();
2135
+ };
2136
+ }, [identity]);
2137
+ const snapshot = (0, import_react.useSyncExternalStore)(
2138
+ (listener) => identity === null ? () => void 0 : identity.subscribe(listener),
2139
+ () => identity?.snapshot() ?? SERVER_SESSION_IDENTITY,
2140
+ () => SERVER_SESSION_IDENTITY
2141
+ );
2142
+ return { identity, snapshot };
2143
+ }
1954
2144
  function MessagingProvider(props) {
1955
- const { client, userId, organizationId } = props;
1956
- const ready = client != null && typeof userId === "string" && userId.length > 0 && typeof organizationId === "string" && organizationId.length > 0;
2145
+ const { client, organizationId } = props;
2146
+ const strayUserId = props.userId;
2147
+ const reportedStray = (0, import_react.useRef)(false);
2148
+ (0, import_react.useEffect)(() => {
2149
+ if (typeof strayUserId !== "string" || strayUserId.length === 0) return;
2150
+ if (reportedStray.current) return;
2151
+ reportedStray.current = true;
2152
+ (props.onDiagnostic ?? defaultDiagnostics)({
2153
+ level: "error",
2154
+ message: "<MessagingProvider userId> is ignored: since 0.12.0 messaging reads the acting user from the Supabase client's own session (DD-241).",
2155
+ remedy: "Delete the userId prop. A copy of a user id goes stale the moment the auth cookie rotates, and in messaging it lands on the wire as sender_id and created_by \u2014 so it writes rows attributed to somebody the tab cannot prove it is."
2156
+ });
2157
+ }, [strayUserId]);
2158
+ const session = useSessionUserId(client, props.onDiagnostic);
2159
+ const userId = session.snapshot.userId;
2160
+ const ready = client != null && userId !== null && typeof organizationId === "string" && organizationId.length > 0;
1957
2161
  const hostProvidesRealtime = (0, import_react2.useIsRealtimeProviderMounted)();
1958
- if (hostProvidesRealtime) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessagingRuntime, { ...props });
2162
+ const runtime = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2163
+ MessagingRuntime,
2164
+ {
2165
+ ...props,
2166
+ sessionUserId: userId,
2167
+ sessionIdentity: session.identity
2168
+ }
2169
+ );
2170
+ if (hostProvidesRealtime) return runtime;
1959
2171
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1960
2172
  import_react2.RealtimeProvider,
1961
2173
  {
1962
2174
  client: ready ? client : null,
1963
2175
  actorId: userId ?? void 0,
1964
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessagingRuntime, { ...props })
2176
+ children: runtime
1965
2177
  }
1966
2178
  );
1967
2179
  }
1968
2180
  function MessagingRuntime(props) {
1969
- const { client, userId, organizationId, children } = props;
2181
+ const { client, sessionUserId: userId, sessionIdentity, organizationId, children } = props;
1970
2182
  const manager = (0, import_react2.useRealtimeManager)();
1971
2183
  const diagnosticRef = (0, import_react.useRef)(props.onDiagnostic);
1972
2184
  diagnosticRef.current = props.onDiagnostic;
@@ -1981,9 +2193,9 @@ function MessagingRuntime(props) {
1981
2193
  actionRegistry.register(handler);
1982
2194
  });
1983
2195
  }, [actionRegistry, handlers]);
1984
- const ready = client != null && manager !== null && typeof userId === "string" && userId.length > 0 && typeof organizationId === "string" && organizationId.length > 0;
2196
+ const ready = client != null && manager !== null && sessionIdentity !== null && userId !== null && typeof organizationId === "string" && organizationId.length > 0;
1985
2197
  const engine = (0, import_react.useMemo)(() => {
1986
- if (!ready || manager === null) return null;
2198
+ if (!ready || manager === null || sessionIdentity === null || userId === null) return null;
1987
2199
  const identity = {
1988
2200
  userId,
1989
2201
  organizationId
@@ -1997,7 +2209,10 @@ function MessagingRuntime(props) {
1997
2209
  // supabase-shape.ts); everything below this line works against the rich
1998
2210
  // contract, which `supabase-shape.test.ts` proves a real client honors.
1999
2211
  client,
2000
- identity,
2212
+ organizationId,
2213
+ // THE SAME READER the render loop above uses. Two readers would be two
2214
+ // answers during the tick an account switch takes.
2215
+ sessionIdentity,
2001
2216
  // An automatic heal ANNOUNCES itself: a lock collision the package
2002
2217
  // re-ran is a warning in the host's diagnostics, never a banner on the
2003
2218
  // inbox and never silence.
@@ -2027,7 +2242,7 @@ function MessagingRuntime(props) {
2027
2242
  }
2028
2243
  });
2029
2244
  return built;
2030
- }, [ready, manager, userId, organizationId]);
2245
+ }, [ready, manager, sessionIdentity, userId, organizationId]);
2031
2246
  (0, import_react.useEffect)(() => {
2032
2247
  if (engine === null) return void 0;
2033
2248
  void engine.start().catch((error) => {