@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/index.js CHANGED
@@ -1591,6 +1591,145 @@ function formatLastSeen(lastSeenMs, now = Date.now()) {
1591
1591
  return `Active ${formatRelativeTime(lastSeenMs, { style: "short", now })}`;
1592
1592
  }
1593
1593
 
1594
+ // src/core/session-identity.ts
1595
+ var RESOLVING = Object.freeze({
1596
+ state: "resolving",
1597
+ userId: null
1598
+ });
1599
+ var SERVER_SESSION_IDENTITY = RESOLVING;
1600
+ function authOf(client) {
1601
+ const candidate = client.auth;
1602
+ if (candidate === null || typeof candidate !== "object") return null;
1603
+ const auth = candidate;
1604
+ if (typeof auth.getSession !== "function") return null;
1605
+ if (typeof auth.onAuthStateChange !== "function") return null;
1606
+ return auth;
1607
+ }
1608
+ function userIdOf(session) {
1609
+ const id = session?.user?.id;
1610
+ return typeof id === "string" && id.length > 0 ? asUserId(id) : null;
1611
+ }
1612
+ var NO_AUTH_SOURCE = {
1613
+ message: "Messaging cannot tell who is signed in: the Supabase client it was given has no auth session source.",
1614
+ 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)."
1615
+ };
1616
+ function createSessionIdentity(options) {
1617
+ const { client, onDiagnostic } = options;
1618
+ const listeners = /* @__PURE__ */ new Set();
1619
+ let current = RESOLVING;
1620
+ let disposed = false;
1621
+ const publish = (state, userId) => {
1622
+ if (disposed) return;
1623
+ if (current.state === state && current.userId === userId) return;
1624
+ current = Object.freeze({ state, userId });
1625
+ for (const listener of [...listeners]) listener();
1626
+ };
1627
+ const subscribeOnly = (hasAuthSource) => ({
1628
+ hasAuthSource,
1629
+ snapshot: () => current,
1630
+ subscribe: (listener) => {
1631
+ listeners.add(listener);
1632
+ return () => {
1633
+ listeners.delete(listener);
1634
+ };
1635
+ },
1636
+ dispose: () => {
1637
+ disposed = true;
1638
+ listeners.clear();
1639
+ }
1640
+ });
1641
+ const auth = authOf(client);
1642
+ if (auth === null) {
1643
+ onDiagnostic?.({ level: "error", ...NO_AUTH_SOURCE });
1644
+ current = Object.freeze({ state: "resolved", userId: null });
1645
+ return subscribeOnly(false);
1646
+ }
1647
+ void (async () => {
1648
+ try {
1649
+ const result = await auth.getSession();
1650
+ publish("resolved", userIdOf(result?.data?.session));
1651
+ } catch (error) {
1652
+ onDiagnostic?.({
1653
+ level: "warn",
1654
+ message: `Messaging could not read the signed-in session: ${String(
1655
+ error?.message ?? error
1656
+ )}`,
1657
+ remedy: "Messages stay unavailable on this device until the session can be read. Reload the page; if it persists, sign in again."
1658
+ });
1659
+ publish("resolved", null);
1660
+ }
1661
+ })();
1662
+ const subscription = auth.onAuthStateChange((_event, session) => {
1663
+ publish("resolved", userIdOf(session));
1664
+ });
1665
+ const base = subscribeOnly(true);
1666
+ return {
1667
+ hasAuthSource: true,
1668
+ snapshot: base.snapshot,
1669
+ subscribe: base.subscribe,
1670
+ dispose: () => {
1671
+ base.dispose();
1672
+ try {
1673
+ subscription.data.subscription.unsubscribe();
1674
+ } catch {
1675
+ }
1676
+ }
1677
+ };
1678
+ }
1679
+ function actingUserIdOf(identity) {
1680
+ return new Promise((resolve, reject) => {
1681
+ const settle = () => {
1682
+ const snapshot = identity.snapshot();
1683
+ if (snapshot.state !== "resolved") return false;
1684
+ if (snapshot.userId === null) {
1685
+ reject(identity.hasAuthSource ? noSignedInUserError() : noAuthSourceError());
1686
+ return true;
1687
+ }
1688
+ resolve(snapshot.userId);
1689
+ return true;
1690
+ };
1691
+ if (settle()) return;
1692
+ const unsubscribe = identity.subscribe(() => {
1693
+ if (settle()) unsubscribe();
1694
+ });
1695
+ });
1696
+ }
1697
+ function noSignedInUserError() {
1698
+ return new MessagingError(
1699
+ "session-unavailable",
1700
+ "Messaging has no signed-in user: the Supabase client's session names nobody.",
1701
+ "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."
1702
+ );
1703
+ }
1704
+ function noAuthSourceError() {
1705
+ return new MessagingError(
1706
+ "misconfigured",
1707
+ NO_AUTH_SOURCE.message,
1708
+ NO_AUTH_SOURCE.remedy
1709
+ );
1710
+ }
1711
+ async function readActingUserId(client) {
1712
+ const auth = authOf(client);
1713
+ if (auth === null) throw noAuthSourceError();
1714
+ let session;
1715
+ try {
1716
+ const result = await auth.getSession();
1717
+ session = result?.data?.session;
1718
+ } catch (error) {
1719
+ throw new MessagingError(
1720
+ "session-unavailable",
1721
+ `Messaging could not read the signed-in session: ${String(
1722
+ error?.message ?? error
1723
+ )}`,
1724
+ "Reload the page and try again; if it persists, sign in again.",
1725
+ error
1726
+ );
1727
+ }
1728
+ const userId = userIdOf(session);
1729
+ if (userId === null) throw noSignedInUserError();
1730
+ return userId;
1731
+ }
1732
+
1594
1733
  // src/core/repository.ts
1595
1734
  var MESSAGING_SCHEMA = "communication";
1596
1735
  var MESSAGING_RPC_SCHEMA = "public";
@@ -1627,8 +1766,15 @@ function sleep(ms) {
1627
1766
  return new Promise((resolve) => setTimeout(resolve, jittered));
1628
1767
  }
1629
1768
  function createMessagingRepository(options) {
1630
- const { identity } = options;
1631
- const org = requireOrg(identity.organizationId, "createMessagingRepository");
1769
+ const org = requireOrg(options.organizationId, "createMessagingRepository");
1770
+ const handedIn = options.identity;
1771
+ if (handedIn !== void 0 && handedIn !== null && handedIn.userId !== void 0) {
1772
+ options.onDiagnostic?.(
1773
+ "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."
1774
+ );
1775
+ }
1776
+ const sessionIdentity = options.sessionIdentity;
1777
+ const actingUserId = () => sessionIdentity !== void 0 ? actingUserIdOf(sessionIdentity) : readActingUserId(options.client);
1632
1778
  const userCache = createReadCache({
1633
1779
  ttlMs: options.userTtlMs ?? 5 * 6e4,
1634
1780
  ...options.now !== void 0 ? { now: options.now } : {}
@@ -1665,6 +1811,9 @@ function createMessagingRepository(options) {
1665
1811
  }
1666
1812
  }
1667
1813
  }
1814
+ function actingUser(operation) {
1815
+ return withSessionRetry(operation, () => actingUserId());
1816
+ }
1668
1817
  async function rpc(fn, args, operation) {
1669
1818
  const { data, error } = await rpcDb().rpc(fn, args);
1670
1819
  if (error !== null) throw normalizeMessagingError(error, operation);
@@ -1688,17 +1837,19 @@ function createMessagingRepository(options) {
1688
1837
  return found;
1689
1838
  }
1690
1839
  const repository = {
1691
- identity,
1840
+ organizationId: org,
1841
+ actingUserId,
1692
1842
  async listConversations(args = {}) {
1693
1843
  const limit = args.limit ?? 30;
1694
1844
  const archived = args.archived ?? DEFAULT_MESSAGING_ARCHIVE_FILTER;
1695
1845
  const operation = "listConversations";
1846
+ const me = await actingUser(operation);
1696
1847
  const rows = await withSessionRetry(
1697
1848
  operation,
1698
1849
  () => rpc(
1699
1850
  RPCS.conversationsWithDetails,
1700
1851
  {
1701
- p_user_id: identity.userId,
1852
+ p_user_id: me,
1702
1853
  p_limit: limit + 1,
1703
1854
  p_before_sort_at: args.cursor?.beforeSortAt ?? null,
1704
1855
  p_before_conversation_id: args.cursor?.beforeConversationId ?? null,
@@ -1715,7 +1866,7 @@ function createMessagingRepository(options) {
1715
1866
  throw invalidResponse(operation, `${RPCS.conversationsWithDetails} did not return rows`);
1716
1867
  }
1717
1868
  const projected = (rows ?? []).map(
1718
- (row) => projectConversationSummary(row, identity.userId, org)
1869
+ (row) => projectConversationSummary(row, me, org)
1719
1870
  );
1720
1871
  const hasMore = projected.length > limit;
1721
1872
  const items = hasMore ? projected.slice(0, limit) : projected;
@@ -1729,12 +1880,13 @@ function createMessagingRepository(options) {
1729
1880
  async countArchivedConversations(args = {}) {
1730
1881
  const limit = args.limit ?? 100;
1731
1882
  const operation = "countArchivedConversations";
1883
+ const me = await actingUser(operation);
1732
1884
  const rows = await withSessionRetry(
1733
1885
  operation,
1734
1886
  () => rpc(
1735
1887
  RPCS.conversationsWithDetails,
1736
1888
  {
1737
- p_user_id: identity.userId,
1889
+ p_user_id: me,
1738
1890
  p_limit: limit + 1,
1739
1891
  p_before_sort_at: null,
1740
1892
  p_before_conversation_id: null,
@@ -1751,6 +1903,7 @@ function createMessagingRepository(options) {
1751
1903
  },
1752
1904
  async getConversation(id) {
1753
1905
  const operation = "getConversation";
1906
+ const me = await actingUser(operation);
1754
1907
  const { data, error } = await withSessionRetry(
1755
1908
  operation,
1756
1909
  async () => db().from(TABLES.conversations).select(CONVERSATION_COLUMNS).eq("id", id).single()
@@ -1771,7 +1924,7 @@ function createMessagingRepository(options) {
1771
1924
  participants: [],
1772
1925
  unread_count: 0
1773
1926
  },
1774
- identity.userId,
1927
+ me,
1775
1928
  org
1776
1929
  );
1777
1930
  return summary.conversation;
@@ -1815,6 +1968,7 @@ function createMessagingRepository(options) {
1815
1968
  },
1816
1969
  async getOrCreateDirectConversation(otherUserId) {
1817
1970
  const operation = "getOrCreateDirectConversation";
1971
+ const me = await actingUser(operation);
1818
1972
  const id = await withSessionRetry(
1819
1973
  operation,
1820
1974
  () => rpc(
@@ -1823,7 +1977,7 @@ function createMessagingRepository(options) {
1823
1977
  // The RPC's own guard requires an `authenticated` caller to pass
1824
1978
  // THEMSELVES as user1; passing them in the other slot is a denial,
1825
1979
  // not a preference.
1826
- p_user1_id: identity.userId,
1980
+ p_user1_id: me,
1827
1981
  p_user2_id: otherUserId,
1828
1982
  p_organization_id: org
1829
1983
  },
@@ -1837,13 +1991,14 @@ function createMessagingRepository(options) {
1837
1991
  },
1838
1992
  async createGroupConversation({ name, memberIds }) {
1839
1993
  const operation = "createGroupConversation";
1994
+ const me = await actingUser(operation);
1840
1995
  const { data, error } = await withSessionRetry(
1841
1996
  operation,
1842
1997
  async () => db().from(TABLES.conversations).insert({
1843
1998
  type: "group",
1844
1999
  group_name: name,
1845
2000
  organization_id: org,
1846
- created_by: identity.userId
2001
+ created_by: me
1847
2002
  }).select("id").single()
1848
2003
  );
1849
2004
  if (error !== null) throw normalizeMessagingError(error, operation);
@@ -1851,14 +2006,14 @@ function createMessagingRepository(options) {
1851
2006
  if (typeof conversationId !== "string") {
1852
2007
  throw invalidResponse(operation, "insert returned no conversation id");
1853
2008
  }
1854
- const members = [.../* @__PURE__ */ new Set([identity.userId, ...memberIds])];
2009
+ const members = [.../* @__PURE__ */ new Set([me, ...memberIds])];
1855
2010
  const { error: memberError } = await db().from(TABLES.participants).insert(
1856
2011
  members.map((userId) => ({
1857
2012
  conversation_id: conversationId,
1858
2013
  user_id: userId,
1859
- role: userId === identity.userId ? "owner" : "member",
2014
+ role: userId === me ? "owner" : "member",
1860
2015
  organization_id: org,
1861
- created_by: identity.userId
2016
+ created_by: me
1862
2017
  }))
1863
2018
  );
1864
2019
  if (memberError !== null) throw normalizeMessagingError(memberError, operation);
@@ -1866,11 +2021,12 @@ function createMessagingRepository(options) {
1866
2021
  },
1867
2022
  async insertMessage(draft, clientMessageId) {
1868
2023
  const operation = "insertMessage";
2024
+ const me = await actingUser(operation);
1869
2025
  const { data, error } = await db().from(TABLES.messages).insert({
1870
2026
  conversation_id: draft.conversationId,
1871
- sender_id: identity.userId,
2027
+ sender_id: me,
1872
2028
  organization_id: org,
1873
- created_by: identity.userId,
2029
+ created_by: me,
1874
2030
  content: draft.content,
1875
2031
  message_type: draft.kind ?? "text",
1876
2032
  status: "sent",
@@ -1892,10 +2048,11 @@ function createMessagingRepository(options) {
1892
2048
  },
1893
2049
  async editMessage(id, content) {
1894
2050
  const operation = "editMessage";
2051
+ const me = await actingUser(operation);
1895
2052
  const { data, error } = await db().from(TABLES.messages).update({
1896
2053
  content,
1897
2054
  edited_at: (/* @__PURE__ */ new Date()).toISOString(),
1898
- updated_by: identity.userId
2055
+ updated_by: me
1899
2056
  }).eq("id", id).select(MESSAGE_COLUMNS).single();
1900
2057
  if (error !== null) throw normalizeMessagingError(error, operation);
1901
2058
  if (data === null) throw invalidResponse(operation, "update returned no row");
@@ -1903,47 +2060,53 @@ function createMessagingRepository(options) {
1903
2060
  },
1904
2061
  async deleteMessage(id, forEveryone) {
1905
2062
  const operation = "deleteMessage";
2063
+ const me = await actingUser(operation);
1906
2064
  const { error } = await db().from(TABLES.messages).update({
1907
2065
  deleted_at: (/* @__PURE__ */ new Date()).toISOString(),
1908
2066
  deleted_for_everyone: forEveryone,
1909
- updated_by: identity.userId
2067
+ updated_by: me
1910
2068
  }).eq("id", id);
1911
2069
  if (error !== null) throw normalizeMessagingError(error, operation);
1912
2070
  },
1913
2071
  async markRead(conversationId, at) {
1914
2072
  const operation = "markRead";
1915
- 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);
2073
+ const me = await actingUser(operation);
2074
+ 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);
1916
2075
  if (error !== null) throw normalizeMessagingError(error, operation);
1917
2076
  },
1918
2077
  async setConversationFlags(conversationId, flags) {
1919
2078
  const operation = "setConversationFlags";
1920
- const patch = { updated_by: identity.userId };
2079
+ const me = await actingUser(operation);
2080
+ const patch = { updated_by: me };
1921
2081
  if (flags.isMuted !== void 0) patch["is_muted"] = flags.isMuted;
1922
2082
  if (flags.isArchived !== void 0) patch["is_archived"] = flags.isArchived;
1923
- const { error } = await db().from(TABLES.participants).update(patch).eq("conversation_id", conversationId).eq("user_id", identity.userId);
2083
+ const { error } = await db().from(TABLES.participants).update(patch).eq("conversation_id", conversationId).eq("user_id", me);
1924
2084
  if (error !== null) throw normalizeMessagingError(error, operation);
1925
2085
  },
1926
2086
  async addMembers(conversationId, memberIds) {
1927
2087
  const operation = "addMembers";
2088
+ const me = await actingUser(operation);
1928
2089
  const { error } = await db().from(TABLES.participants).insert(
1929
2090
  memberIds.map((userId) => ({
1930
2091
  conversation_id: conversationId,
1931
2092
  user_id: userId,
1932
2093
  role: "member",
1933
2094
  organization_id: org,
1934
- created_by: identity.userId
2095
+ created_by: me
1935
2096
  }))
1936
2097
  );
1937
2098
  if (error !== null) throw normalizeMessagingError(error, operation);
1938
2099
  },
1939
2100
  async removeMember(conversationId, memberId) {
1940
2101
  const operation = "removeMember";
1941
- 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);
2102
+ const me = await actingUser(operation);
2103
+ 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);
1942
2104
  if (error !== null) throw normalizeMessagingError(error, operation);
1943
2105
  },
1944
2106
  async setMemberRole(conversationId, memberId, role) {
1945
2107
  const operation = "setMemberRole";
1946
- const { error } = await db().from(TABLES.participants).update({ role, updated_by: identity.userId }).eq("conversation_id", conversationId).eq("user_id", memberId);
2108
+ const me = await actingUser(operation);
2109
+ const { error } = await db().from(TABLES.participants).update({ role, updated_by: me }).eq("conversation_id", conversationId).eq("user_id", memberId);
1947
2110
  if (error !== null) throw normalizeMessagingError(error, operation);
1948
2111
  },
1949
2112
  getUser(userId) {
@@ -1989,6 +2152,7 @@ export {
1989
2152
  MESSAGING_SCHEMA,
1990
2153
  MessagingError,
1991
2154
  RPCS,
2155
+ SERVER_SESSION_IDENTITY,
1992
2156
  TABLES,
1993
2157
  asClientMessageId,
1994
2158
  asConversationId,
@@ -2005,6 +2169,7 @@ export {
2005
2169
  createMessagingStore,
2006
2170
  createOutbox,
2007
2171
  createReadCache,
2172
+ createSessionIdentity,
2008
2173
  createWebOutboxStorage,
2009
2174
  effectiveActorKey,
2010
2175
  extractReferences,