@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/CHANGELOG.md +59 -0
- package/README.md +10 -3
- package/dist/index.cjs +187 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +154 -42
- package/dist/index.d.ts +154 -42
- package/dist/index.js +187 -22
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +246 -31
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +174 -50
- package/dist/react.d.ts +174 -50
- package/dist/react.js +246 -31
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
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
|
|
1584
|
-
const
|
|
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
|
-
|
|
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:
|
|
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,
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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([
|
|
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 ===
|
|
1969
|
+
role: userId === me ? "owner" : "member",
|
|
1813
1970
|
organization_id: org,
|
|
1814
|
-
created_by:
|
|
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:
|
|
1982
|
+
sender_id: me,
|
|
1825
1983
|
organization_id: org,
|
|
1826
|
-
created_by:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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",
|
|
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:
|
|
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
|
|
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
|
|
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,
|
|
1956
|
-
const
|
|
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
|
-
|
|
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:
|
|
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 &&
|
|
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
|
-
|
|
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) => {
|