@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.js
CHANGED
|
@@ -1449,6 +1449,145 @@ function createReadCache(options) {
|
|
|
1449
1449
|
};
|
|
1450
1450
|
}
|
|
1451
1451
|
|
|
1452
|
+
// src/core/session-identity.ts
|
|
1453
|
+
var RESOLVING = Object.freeze({
|
|
1454
|
+
state: "resolving",
|
|
1455
|
+
userId: null
|
|
1456
|
+
});
|
|
1457
|
+
var SERVER_SESSION_IDENTITY = RESOLVING;
|
|
1458
|
+
function authOf(client) {
|
|
1459
|
+
const candidate = client.auth;
|
|
1460
|
+
if (candidate === null || typeof candidate !== "object") return null;
|
|
1461
|
+
const auth = candidate;
|
|
1462
|
+
if (typeof auth.getSession !== "function") return null;
|
|
1463
|
+
if (typeof auth.onAuthStateChange !== "function") return null;
|
|
1464
|
+
return auth;
|
|
1465
|
+
}
|
|
1466
|
+
function userIdOf(session) {
|
|
1467
|
+
const id = session?.user?.id;
|
|
1468
|
+
return typeof id === "string" && id.length > 0 ? asUserId(id) : null;
|
|
1469
|
+
}
|
|
1470
|
+
var NO_AUTH_SOURCE = {
|
|
1471
|
+
message: "Messaging cannot tell who is signed in: the Supabase client it was given has no auth session source.",
|
|
1472
|
+
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)."
|
|
1473
|
+
};
|
|
1474
|
+
function createSessionIdentity(options) {
|
|
1475
|
+
const { client, onDiagnostic } = options;
|
|
1476
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1477
|
+
let current = RESOLVING;
|
|
1478
|
+
let disposed = false;
|
|
1479
|
+
const publish = (state, userId) => {
|
|
1480
|
+
if (disposed) return;
|
|
1481
|
+
if (current.state === state && current.userId === userId) return;
|
|
1482
|
+
current = Object.freeze({ state, userId });
|
|
1483
|
+
for (const listener of [...listeners]) listener();
|
|
1484
|
+
};
|
|
1485
|
+
const subscribeOnly = (hasAuthSource) => ({
|
|
1486
|
+
hasAuthSource,
|
|
1487
|
+
snapshot: () => current,
|
|
1488
|
+
subscribe: (listener) => {
|
|
1489
|
+
listeners.add(listener);
|
|
1490
|
+
return () => {
|
|
1491
|
+
listeners.delete(listener);
|
|
1492
|
+
};
|
|
1493
|
+
},
|
|
1494
|
+
dispose: () => {
|
|
1495
|
+
disposed = true;
|
|
1496
|
+
listeners.clear();
|
|
1497
|
+
}
|
|
1498
|
+
});
|
|
1499
|
+
const auth = authOf(client);
|
|
1500
|
+
if (auth === null) {
|
|
1501
|
+
onDiagnostic?.({ level: "error", ...NO_AUTH_SOURCE });
|
|
1502
|
+
current = Object.freeze({ state: "resolved", userId: null });
|
|
1503
|
+
return subscribeOnly(false);
|
|
1504
|
+
}
|
|
1505
|
+
void (async () => {
|
|
1506
|
+
try {
|
|
1507
|
+
const result = await auth.getSession();
|
|
1508
|
+
publish("resolved", userIdOf(result?.data?.session));
|
|
1509
|
+
} catch (error) {
|
|
1510
|
+
onDiagnostic?.({
|
|
1511
|
+
level: "warn",
|
|
1512
|
+
message: `Messaging could not read the signed-in session: ${String(
|
|
1513
|
+
error?.message ?? error
|
|
1514
|
+
)}`,
|
|
1515
|
+
remedy: "Messages stay unavailable on this device until the session can be read. Reload the page; if it persists, sign in again."
|
|
1516
|
+
});
|
|
1517
|
+
publish("resolved", null);
|
|
1518
|
+
}
|
|
1519
|
+
})();
|
|
1520
|
+
const subscription = auth.onAuthStateChange((_event, session) => {
|
|
1521
|
+
publish("resolved", userIdOf(session));
|
|
1522
|
+
});
|
|
1523
|
+
const base = subscribeOnly(true);
|
|
1524
|
+
return {
|
|
1525
|
+
hasAuthSource: true,
|
|
1526
|
+
snapshot: base.snapshot,
|
|
1527
|
+
subscribe: base.subscribe,
|
|
1528
|
+
dispose: () => {
|
|
1529
|
+
base.dispose();
|
|
1530
|
+
try {
|
|
1531
|
+
subscription.data.subscription.unsubscribe();
|
|
1532
|
+
} catch {
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
function actingUserIdOf(identity) {
|
|
1538
|
+
return new Promise((resolve, reject) => {
|
|
1539
|
+
const settle = () => {
|
|
1540
|
+
const snapshot = identity.snapshot();
|
|
1541
|
+
if (snapshot.state !== "resolved") return false;
|
|
1542
|
+
if (snapshot.userId === null) {
|
|
1543
|
+
reject(identity.hasAuthSource ? noSignedInUserError() : noAuthSourceError());
|
|
1544
|
+
return true;
|
|
1545
|
+
}
|
|
1546
|
+
resolve(snapshot.userId);
|
|
1547
|
+
return true;
|
|
1548
|
+
};
|
|
1549
|
+
if (settle()) return;
|
|
1550
|
+
const unsubscribe = identity.subscribe(() => {
|
|
1551
|
+
if (settle()) unsubscribe();
|
|
1552
|
+
});
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
function noSignedInUserError() {
|
|
1556
|
+
return new MessagingError(
|
|
1557
|
+
"session-unavailable",
|
|
1558
|
+
"Messaging has no signed-in user: the Supabase client's session names nobody.",
|
|
1559
|
+
"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."
|
|
1560
|
+
);
|
|
1561
|
+
}
|
|
1562
|
+
function noAuthSourceError() {
|
|
1563
|
+
return new MessagingError(
|
|
1564
|
+
"misconfigured",
|
|
1565
|
+
NO_AUTH_SOURCE.message,
|
|
1566
|
+
NO_AUTH_SOURCE.remedy
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1569
|
+
async function readActingUserId(client) {
|
|
1570
|
+
const auth = authOf(client);
|
|
1571
|
+
if (auth === null) throw noAuthSourceError();
|
|
1572
|
+
let session;
|
|
1573
|
+
try {
|
|
1574
|
+
const result = await auth.getSession();
|
|
1575
|
+
session = result?.data?.session;
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
throw new MessagingError(
|
|
1578
|
+
"session-unavailable",
|
|
1579
|
+
`Messaging could not read the signed-in session: ${String(
|
|
1580
|
+
error?.message ?? error
|
|
1581
|
+
)}`,
|
|
1582
|
+
"Reload the page and try again; if it persists, sign in again.",
|
|
1583
|
+
error
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
const userId = userIdOf(session);
|
|
1587
|
+
if (userId === null) throw noSignedInUserError();
|
|
1588
|
+
return userId;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1452
1591
|
// src/core/repository.ts
|
|
1453
1592
|
var MESSAGING_SCHEMA = "communication";
|
|
1454
1593
|
var MESSAGING_RPC_SCHEMA = "public";
|
|
@@ -1485,8 +1624,15 @@ function sleep(ms) {
|
|
|
1485
1624
|
return new Promise((resolve) => setTimeout(resolve, jittered));
|
|
1486
1625
|
}
|
|
1487
1626
|
function createMessagingRepository(options) {
|
|
1488
|
-
const
|
|
1489
|
-
const
|
|
1627
|
+
const org = requireOrg(options.organizationId, "createMessagingRepository");
|
|
1628
|
+
const handedIn = options.identity;
|
|
1629
|
+
if (handedIn !== void 0 && handedIn !== null && handedIn.userId !== void 0) {
|
|
1630
|
+
options.onDiagnostic?.(
|
|
1631
|
+
"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."
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
const sessionIdentity = options.sessionIdentity;
|
|
1635
|
+
const actingUserId = () => sessionIdentity !== void 0 ? actingUserIdOf(sessionIdentity) : readActingUserId(options.client);
|
|
1490
1636
|
const userCache = createReadCache({
|
|
1491
1637
|
ttlMs: options.userTtlMs ?? 5 * 6e4,
|
|
1492
1638
|
...options.now !== void 0 ? { now: options.now } : {}
|
|
@@ -1523,6 +1669,9 @@ function createMessagingRepository(options) {
|
|
|
1523
1669
|
}
|
|
1524
1670
|
}
|
|
1525
1671
|
}
|
|
1672
|
+
function actingUser(operation) {
|
|
1673
|
+
return withSessionRetry(operation, () => actingUserId());
|
|
1674
|
+
}
|
|
1526
1675
|
async function rpc(fn, args, operation) {
|
|
1527
1676
|
const { data, error } = await rpcDb().rpc(fn, args);
|
|
1528
1677
|
if (error !== null) throw normalizeMessagingError(error, operation);
|
|
@@ -1546,17 +1695,19 @@ function createMessagingRepository(options) {
|
|
|
1546
1695
|
return found;
|
|
1547
1696
|
}
|
|
1548
1697
|
const repository = {
|
|
1549
|
-
|
|
1698
|
+
organizationId: org,
|
|
1699
|
+
actingUserId,
|
|
1550
1700
|
async listConversations(args = {}) {
|
|
1551
1701
|
const limit = args.limit ?? 30;
|
|
1552
1702
|
const archived = args.archived ?? DEFAULT_MESSAGING_ARCHIVE_FILTER;
|
|
1553
1703
|
const operation = "listConversations";
|
|
1704
|
+
const me = await actingUser(operation);
|
|
1554
1705
|
const rows = await withSessionRetry(
|
|
1555
1706
|
operation,
|
|
1556
1707
|
() => rpc(
|
|
1557
1708
|
RPCS.conversationsWithDetails,
|
|
1558
1709
|
{
|
|
1559
|
-
p_user_id:
|
|
1710
|
+
p_user_id: me,
|
|
1560
1711
|
p_limit: limit + 1,
|
|
1561
1712
|
p_before_sort_at: args.cursor?.beforeSortAt ?? null,
|
|
1562
1713
|
p_before_conversation_id: args.cursor?.beforeConversationId ?? null,
|
|
@@ -1573,7 +1724,7 @@ function createMessagingRepository(options) {
|
|
|
1573
1724
|
throw invalidResponse(operation, `${RPCS.conversationsWithDetails} did not return rows`);
|
|
1574
1725
|
}
|
|
1575
1726
|
const projected = (rows ?? []).map(
|
|
1576
|
-
(row) => projectConversationSummary(row,
|
|
1727
|
+
(row) => projectConversationSummary(row, me, org)
|
|
1577
1728
|
);
|
|
1578
1729
|
const hasMore = projected.length > limit;
|
|
1579
1730
|
const items = hasMore ? projected.slice(0, limit) : projected;
|
|
@@ -1587,12 +1738,13 @@ function createMessagingRepository(options) {
|
|
|
1587
1738
|
async countArchivedConversations(args = {}) {
|
|
1588
1739
|
const limit = args.limit ?? 100;
|
|
1589
1740
|
const operation = "countArchivedConversations";
|
|
1741
|
+
const me = await actingUser(operation);
|
|
1590
1742
|
const rows = await withSessionRetry(
|
|
1591
1743
|
operation,
|
|
1592
1744
|
() => rpc(
|
|
1593
1745
|
RPCS.conversationsWithDetails,
|
|
1594
1746
|
{
|
|
1595
|
-
p_user_id:
|
|
1747
|
+
p_user_id: me,
|
|
1596
1748
|
p_limit: limit + 1,
|
|
1597
1749
|
p_before_sort_at: null,
|
|
1598
1750
|
p_before_conversation_id: null,
|
|
@@ -1609,6 +1761,7 @@ function createMessagingRepository(options) {
|
|
|
1609
1761
|
},
|
|
1610
1762
|
async getConversation(id) {
|
|
1611
1763
|
const operation = "getConversation";
|
|
1764
|
+
const me = await actingUser(operation);
|
|
1612
1765
|
const { data, error } = await withSessionRetry(
|
|
1613
1766
|
operation,
|
|
1614
1767
|
async () => db().from(TABLES.conversations).select(CONVERSATION_COLUMNS).eq("id", id).single()
|
|
@@ -1629,7 +1782,7 @@ function createMessagingRepository(options) {
|
|
|
1629
1782
|
participants: [],
|
|
1630
1783
|
unread_count: 0
|
|
1631
1784
|
},
|
|
1632
|
-
|
|
1785
|
+
me,
|
|
1633
1786
|
org
|
|
1634
1787
|
);
|
|
1635
1788
|
return summary.conversation;
|
|
@@ -1673,6 +1826,7 @@ function createMessagingRepository(options) {
|
|
|
1673
1826
|
},
|
|
1674
1827
|
async getOrCreateDirectConversation(otherUserId) {
|
|
1675
1828
|
const operation = "getOrCreateDirectConversation";
|
|
1829
|
+
const me = await actingUser(operation);
|
|
1676
1830
|
const id = await withSessionRetry(
|
|
1677
1831
|
operation,
|
|
1678
1832
|
() => rpc(
|
|
@@ -1681,7 +1835,7 @@ function createMessagingRepository(options) {
|
|
|
1681
1835
|
// The RPC's own guard requires an `authenticated` caller to pass
|
|
1682
1836
|
// THEMSELVES as user1; passing them in the other slot is a denial,
|
|
1683
1837
|
// not a preference.
|
|
1684
|
-
p_user1_id:
|
|
1838
|
+
p_user1_id: me,
|
|
1685
1839
|
p_user2_id: otherUserId,
|
|
1686
1840
|
p_organization_id: org
|
|
1687
1841
|
},
|
|
@@ -1695,13 +1849,14 @@ function createMessagingRepository(options) {
|
|
|
1695
1849
|
},
|
|
1696
1850
|
async createGroupConversation({ name, memberIds }) {
|
|
1697
1851
|
const operation = "createGroupConversation";
|
|
1852
|
+
const me = await actingUser(operation);
|
|
1698
1853
|
const { data, error } = await withSessionRetry(
|
|
1699
1854
|
operation,
|
|
1700
1855
|
async () => db().from(TABLES.conversations).insert({
|
|
1701
1856
|
type: "group",
|
|
1702
1857
|
group_name: name,
|
|
1703
1858
|
organization_id: org,
|
|
1704
|
-
created_by:
|
|
1859
|
+
created_by: me
|
|
1705
1860
|
}).select("id").single()
|
|
1706
1861
|
);
|
|
1707
1862
|
if (error !== null) throw normalizeMessagingError(error, operation);
|
|
@@ -1709,14 +1864,14 @@ function createMessagingRepository(options) {
|
|
|
1709
1864
|
if (typeof conversationId !== "string") {
|
|
1710
1865
|
throw invalidResponse(operation, "insert returned no conversation id");
|
|
1711
1866
|
}
|
|
1712
|
-
const members = [.../* @__PURE__ */ new Set([
|
|
1867
|
+
const members = [.../* @__PURE__ */ new Set([me, ...memberIds])];
|
|
1713
1868
|
const { error: memberError } = await db().from(TABLES.participants).insert(
|
|
1714
1869
|
members.map((userId) => ({
|
|
1715
1870
|
conversation_id: conversationId,
|
|
1716
1871
|
user_id: userId,
|
|
1717
|
-
role: userId ===
|
|
1872
|
+
role: userId === me ? "owner" : "member",
|
|
1718
1873
|
organization_id: org,
|
|
1719
|
-
created_by:
|
|
1874
|
+
created_by: me
|
|
1720
1875
|
}))
|
|
1721
1876
|
);
|
|
1722
1877
|
if (memberError !== null) throw normalizeMessagingError(memberError, operation);
|
|
@@ -1724,11 +1879,12 @@ function createMessagingRepository(options) {
|
|
|
1724
1879
|
},
|
|
1725
1880
|
async insertMessage(draft, clientMessageId) {
|
|
1726
1881
|
const operation = "insertMessage";
|
|
1882
|
+
const me = await actingUser(operation);
|
|
1727
1883
|
const { data, error } = await db().from(TABLES.messages).insert({
|
|
1728
1884
|
conversation_id: draft.conversationId,
|
|
1729
|
-
sender_id:
|
|
1885
|
+
sender_id: me,
|
|
1730
1886
|
organization_id: org,
|
|
1731
|
-
created_by:
|
|
1887
|
+
created_by: me,
|
|
1732
1888
|
content: draft.content,
|
|
1733
1889
|
message_type: draft.kind ?? "text",
|
|
1734
1890
|
status: "sent",
|
|
@@ -1750,10 +1906,11 @@ function createMessagingRepository(options) {
|
|
|
1750
1906
|
},
|
|
1751
1907
|
async editMessage(id, content) {
|
|
1752
1908
|
const operation = "editMessage";
|
|
1909
|
+
const me = await actingUser(operation);
|
|
1753
1910
|
const { data, error } = await db().from(TABLES.messages).update({
|
|
1754
1911
|
content,
|
|
1755
1912
|
edited_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1756
|
-
updated_by:
|
|
1913
|
+
updated_by: me
|
|
1757
1914
|
}).eq("id", id).select(MESSAGE_COLUMNS).single();
|
|
1758
1915
|
if (error !== null) throw normalizeMessagingError(error, operation);
|
|
1759
1916
|
if (data === null) throw invalidResponse(operation, "update returned no row");
|
|
@@ -1761,47 +1918,53 @@ function createMessagingRepository(options) {
|
|
|
1761
1918
|
},
|
|
1762
1919
|
async deleteMessage(id, forEveryone) {
|
|
1763
1920
|
const operation = "deleteMessage";
|
|
1921
|
+
const me = await actingUser(operation);
|
|
1764
1922
|
const { error } = await db().from(TABLES.messages).update({
|
|
1765
1923
|
deleted_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1766
1924
|
deleted_for_everyone: forEveryone,
|
|
1767
|
-
updated_by:
|
|
1925
|
+
updated_by: me
|
|
1768
1926
|
}).eq("id", id);
|
|
1769
1927
|
if (error !== null) throw normalizeMessagingError(error, operation);
|
|
1770
1928
|
},
|
|
1771
1929
|
async markRead(conversationId, at) {
|
|
1772
1930
|
const operation = "markRead";
|
|
1773
|
-
const
|
|
1931
|
+
const me = await actingUser(operation);
|
|
1932
|
+
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);
|
|
1774
1933
|
if (error !== null) throw normalizeMessagingError(error, operation);
|
|
1775
1934
|
},
|
|
1776
1935
|
async setConversationFlags(conversationId, flags) {
|
|
1777
1936
|
const operation = "setConversationFlags";
|
|
1778
|
-
const
|
|
1937
|
+
const me = await actingUser(operation);
|
|
1938
|
+
const patch = { updated_by: me };
|
|
1779
1939
|
if (flags.isMuted !== void 0) patch["is_muted"] = flags.isMuted;
|
|
1780
1940
|
if (flags.isArchived !== void 0) patch["is_archived"] = flags.isArchived;
|
|
1781
|
-
const { error } = await db().from(TABLES.participants).update(patch).eq("conversation_id", conversationId).eq("user_id",
|
|
1941
|
+
const { error } = await db().from(TABLES.participants).update(patch).eq("conversation_id", conversationId).eq("user_id", me);
|
|
1782
1942
|
if (error !== null) throw normalizeMessagingError(error, operation);
|
|
1783
1943
|
},
|
|
1784
1944
|
async addMembers(conversationId, memberIds) {
|
|
1785
1945
|
const operation = "addMembers";
|
|
1946
|
+
const me = await actingUser(operation);
|
|
1786
1947
|
const { error } = await db().from(TABLES.participants).insert(
|
|
1787
1948
|
memberIds.map((userId) => ({
|
|
1788
1949
|
conversation_id: conversationId,
|
|
1789
1950
|
user_id: userId,
|
|
1790
1951
|
role: "member",
|
|
1791
1952
|
organization_id: org,
|
|
1792
|
-
created_by:
|
|
1953
|
+
created_by: me
|
|
1793
1954
|
}))
|
|
1794
1955
|
);
|
|
1795
1956
|
if (error !== null) throw normalizeMessagingError(error, operation);
|
|
1796
1957
|
},
|
|
1797
1958
|
async removeMember(conversationId, memberId) {
|
|
1798
1959
|
const operation = "removeMember";
|
|
1799
|
-
const
|
|
1960
|
+
const me = await actingUser(operation);
|
|
1961
|
+
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);
|
|
1800
1962
|
if (error !== null) throw normalizeMessagingError(error, operation);
|
|
1801
1963
|
},
|
|
1802
1964
|
async setMemberRole(conversationId, memberId, role) {
|
|
1803
1965
|
const operation = "setMemberRole";
|
|
1804
|
-
const
|
|
1966
|
+
const me = await actingUser(operation);
|
|
1967
|
+
const { error } = await db().from(TABLES.participants).update({ role, updated_by: me }).eq("conversation_id", conversationId).eq("user_id", memberId);
|
|
1805
1968
|
if (error !== null) throw normalizeMessagingError(error, operation);
|
|
1806
1969
|
},
|
|
1807
1970
|
getUser(userId) {
|
|
@@ -1856,22 +2019,69 @@ function defaultDiagnostics(event) {
|
|
|
1856
2019
|
else if (event.level === "warn") console.warn(line);
|
|
1857
2020
|
else console.info(line);
|
|
1858
2021
|
}
|
|
2022
|
+
function useSessionUserId(client, onDiagnostic) {
|
|
2023
|
+
const diagnosticRef = useRef(onDiagnostic);
|
|
2024
|
+
diagnosticRef.current = onDiagnostic;
|
|
2025
|
+
const identity = useMemo(() => {
|
|
2026
|
+
if (client == null) return null;
|
|
2027
|
+
return createSessionIdentity({
|
|
2028
|
+
client,
|
|
2029
|
+
onDiagnostic: (event) => {
|
|
2030
|
+
(diagnosticRef.current ?? defaultDiagnostics)(event);
|
|
2031
|
+
}
|
|
2032
|
+
});
|
|
2033
|
+
}, [client]);
|
|
2034
|
+
useEffect(() => {
|
|
2035
|
+
if (identity === null) return void 0;
|
|
2036
|
+
return () => {
|
|
2037
|
+
identity.dispose();
|
|
2038
|
+
};
|
|
2039
|
+
}, [identity]);
|
|
2040
|
+
const snapshot = useSyncExternalStore(
|
|
2041
|
+
(listener) => identity === null ? () => void 0 : identity.subscribe(listener),
|
|
2042
|
+
() => identity?.snapshot() ?? SERVER_SESSION_IDENTITY,
|
|
2043
|
+
() => SERVER_SESSION_IDENTITY
|
|
2044
|
+
);
|
|
2045
|
+
return { identity, snapshot };
|
|
2046
|
+
}
|
|
1859
2047
|
function MessagingProvider(props) {
|
|
1860
|
-
const { client,
|
|
1861
|
-
const
|
|
2048
|
+
const { client, organizationId } = props;
|
|
2049
|
+
const strayUserId = props.userId;
|
|
2050
|
+
const reportedStray = useRef(false);
|
|
2051
|
+
useEffect(() => {
|
|
2052
|
+
if (typeof strayUserId !== "string" || strayUserId.length === 0) return;
|
|
2053
|
+
if (reportedStray.current) return;
|
|
2054
|
+
reportedStray.current = true;
|
|
2055
|
+
(props.onDiagnostic ?? defaultDiagnostics)({
|
|
2056
|
+
level: "error",
|
|
2057
|
+
message: "<MessagingProvider userId> is ignored: since 0.12.0 messaging reads the acting user from the Supabase client's own session (DD-241).",
|
|
2058
|
+
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."
|
|
2059
|
+
});
|
|
2060
|
+
}, [strayUserId]);
|
|
2061
|
+
const session = useSessionUserId(client, props.onDiagnostic);
|
|
2062
|
+
const userId = session.snapshot.userId;
|
|
2063
|
+
const ready = client != null && userId !== null && typeof organizationId === "string" && organizationId.length > 0;
|
|
1862
2064
|
const hostProvidesRealtime = useIsRealtimeProviderMounted();
|
|
1863
|
-
|
|
2065
|
+
const runtime = /* @__PURE__ */ jsx(
|
|
2066
|
+
MessagingRuntime,
|
|
2067
|
+
{
|
|
2068
|
+
...props,
|
|
2069
|
+
sessionUserId: userId,
|
|
2070
|
+
sessionIdentity: session.identity
|
|
2071
|
+
}
|
|
2072
|
+
);
|
|
2073
|
+
if (hostProvidesRealtime) return runtime;
|
|
1864
2074
|
return /* @__PURE__ */ jsx(
|
|
1865
2075
|
RealtimeProvider,
|
|
1866
2076
|
{
|
|
1867
2077
|
client: ready ? client : null,
|
|
1868
2078
|
actorId: userId ?? void 0,
|
|
1869
|
-
children:
|
|
2079
|
+
children: runtime
|
|
1870
2080
|
}
|
|
1871
2081
|
);
|
|
1872
2082
|
}
|
|
1873
2083
|
function MessagingRuntime(props) {
|
|
1874
|
-
const { client, userId, organizationId, children } = props;
|
|
2084
|
+
const { client, sessionUserId: userId, sessionIdentity, organizationId, children } = props;
|
|
1875
2085
|
const manager = useRealtimeManager();
|
|
1876
2086
|
const diagnosticRef = useRef(props.onDiagnostic);
|
|
1877
2087
|
diagnosticRef.current = props.onDiagnostic;
|
|
@@ -1886,9 +2096,9 @@ function MessagingRuntime(props) {
|
|
|
1886
2096
|
actionRegistry.register(handler);
|
|
1887
2097
|
});
|
|
1888
2098
|
}, [actionRegistry, handlers]);
|
|
1889
|
-
const ready = client != null && manager !== null &&
|
|
2099
|
+
const ready = client != null && manager !== null && sessionIdentity !== null && userId !== null && typeof organizationId === "string" && organizationId.length > 0;
|
|
1890
2100
|
const engine = useMemo(() => {
|
|
1891
|
-
if (!ready || manager === null) return null;
|
|
2101
|
+
if (!ready || manager === null || sessionIdentity === null || userId === null) return null;
|
|
1892
2102
|
const identity = {
|
|
1893
2103
|
userId,
|
|
1894
2104
|
organizationId
|
|
@@ -1902,7 +2112,10 @@ function MessagingRuntime(props) {
|
|
|
1902
2112
|
// supabase-shape.ts); everything below this line works against the rich
|
|
1903
2113
|
// contract, which `supabase-shape.test.ts` proves a real client honors.
|
|
1904
2114
|
client,
|
|
1905
|
-
|
|
2115
|
+
organizationId,
|
|
2116
|
+
// THE SAME READER the render loop above uses. Two readers would be two
|
|
2117
|
+
// answers during the tick an account switch takes.
|
|
2118
|
+
sessionIdentity,
|
|
1906
2119
|
// An automatic heal ANNOUNCES itself: a lock collision the package
|
|
1907
2120
|
// re-ran is a warning in the host's diagnostics, never a banner on the
|
|
1908
2121
|
// inbox and never silence.
|
|
@@ -1932,7 +2145,7 @@ function MessagingRuntime(props) {
|
|
|
1932
2145
|
}
|
|
1933
2146
|
});
|
|
1934
2147
|
return built;
|
|
1935
|
-
}, [ready, manager, userId, organizationId]);
|
|
2148
|
+
}, [ready, manager, sessionIdentity, userId, organizationId]);
|
|
1936
2149
|
useEffect(() => {
|
|
1937
2150
|
if (engine === null) return void 0;
|
|
1938
2151
|
void engine.start().catch((error) => {
|
|
@@ -3333,6 +3546,7 @@ export {
|
|
|
3333
3546
|
RPCS,
|
|
3334
3547
|
ReferenceCard,
|
|
3335
3548
|
ReplyIcon,
|
|
3549
|
+
SERVER_SESSION_IDENTITY,
|
|
3336
3550
|
SearchIcon,
|
|
3337
3551
|
SendIcon,
|
|
3338
3552
|
SparklesIcon,
|
|
@@ -3354,6 +3568,7 @@ export {
|
|
|
3354
3568
|
createMessagingStore,
|
|
3355
3569
|
createOutbox,
|
|
3356
3570
|
createReadCache,
|
|
3571
|
+
createSessionIdentity,
|
|
3357
3572
|
createWebOutboxStorage,
|
|
3358
3573
|
effectiveActorKey,
|
|
3359
3574
|
extractReferences,
|