stream-chat 9.21.0 → 9.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.browser.js +661 -38
- package/dist/cjs/index.browser.js.map +4 -4
- package/dist/cjs/index.node.js +656 -37
- package/dist/cjs/index.node.js.map +4 -4
- package/dist/esm/index.mjs +664 -41
- package/dist/esm/index.mjs.map +4 -4
- package/dist/types/channel.d.ts +11 -2
- package/dist/types/channel_state.d.ts +6 -0
- package/dist/types/client.d.ts +17 -5
- package/dist/types/events.d.ts +4 -4
- package/dist/types/index.d.ts +1 -0
- package/dist/types/messageDelivery/MessageDeliveryReporter.d.ts +74 -0
- package/dist/types/messageDelivery/MessageReceiptsTracker.d.ts +121 -0
- package/dist/types/messageDelivery/index.d.ts +2 -0
- package/dist/types/types.d.ts +11 -4
- package/dist/types/utils.d.ts +2 -1
- package/package.json +1 -1
- package/src/channel.ts +73 -7
- package/src/channel_state.ts +147 -14
- package/src/client.ts +60 -14
- package/src/events.ts +4 -6
- package/src/index.ts +1 -0
- package/src/messageDelivery/MessageDeliveryReporter.ts +259 -0
- package/src/messageDelivery/MessageReceiptsTracker.ts +417 -0
- package/src/messageDelivery/index.ts +2 -0
- package/src/thread.ts +1 -1
- package/src/types.ts +13 -4
- package/src/utils.ts +2 -1
package/dist/esm/index.mjs
CHANGED
|
@@ -30,6 +30,12 @@ var require_https = __commonJS({
|
|
|
30
30
|
}
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
// (disabled):node_modules/jsonwebtoken/index.js
|
|
34
|
+
var require_jsonwebtoken = __commonJS({
|
|
35
|
+
"(disabled):node_modules/jsonwebtoken/index.js"() {
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
33
39
|
// (disabled):crypto
|
|
34
40
|
var require_crypto = __commonJS({
|
|
35
41
|
"(disabled):crypto"() {
|
|
@@ -968,6 +974,21 @@ var runDetached = (callback, options) => {
|
|
|
968
974
|
};
|
|
969
975
|
|
|
970
976
|
// src/channel_state.ts
|
|
977
|
+
var messageSetBounds = (a, b) => ({
|
|
978
|
+
newestMessageA: new Date(a[0]?.created_at ?? 0),
|
|
979
|
+
oldestMessageA: new Date(a.slice(-1)[0]?.created_at ?? 0),
|
|
980
|
+
newestMessageB: new Date(b[0]?.created_at ?? 0),
|
|
981
|
+
oldestMessageB: new Date(b.slice(-1)[0]?.created_at ?? 0)
|
|
982
|
+
});
|
|
983
|
+
var aContainsOrEqualsB = (a, b) => {
|
|
984
|
+
const { newestMessageA, newestMessageB, oldestMessageA, oldestMessageB } = messageSetBounds(a, b);
|
|
985
|
+
return newestMessageA >= newestMessageB && oldestMessageB >= oldestMessageA;
|
|
986
|
+
};
|
|
987
|
+
var aOverlapsB = (a, b) => {
|
|
988
|
+
const { newestMessageA, newestMessageB, oldestMessageA, oldestMessageB } = messageSetBounds(a, b);
|
|
989
|
+
return oldestMessageA < oldestMessageB && oldestMessageB < newestMessageA && newestMessageA < newestMessageB;
|
|
990
|
+
};
|
|
991
|
+
var messageSetsOverlapByTimestamp = (a, b) => aContainsOrEqualsB(a, b) || aContainsOrEqualsB(b, a) || aOverlapsB(a, b) || aOverlapsB(b, a);
|
|
971
992
|
var ChannelState = class {
|
|
972
993
|
constructor(channel) {
|
|
973
994
|
/**
|
|
@@ -1046,6 +1067,22 @@ var ChannelState = class {
|
|
|
1046
1067
|
deletedAt: deletedAt ?? null
|
|
1047
1068
|
});
|
|
1048
1069
|
};
|
|
1070
|
+
/**
|
|
1071
|
+
* Identifies the set index into which a message set would pertain if its first item's creation date corresponded to oldestTimestampMs.
|
|
1072
|
+
* @param oldestTimestampMs
|
|
1073
|
+
*/
|
|
1074
|
+
this.findMessageSetByOldestTimestamp = (oldestTimestampMs) => {
|
|
1075
|
+
let lo = 0, hi = this.messageSets.length;
|
|
1076
|
+
while (lo < hi) {
|
|
1077
|
+
const mid = lo + hi >>> 1;
|
|
1078
|
+
const msgSet = this.messageSets[mid];
|
|
1079
|
+
if (msgSet.messages.length === 0) return -1;
|
|
1080
|
+
const oldestMessageTimestampInSet = msgSet.messages[0].created_at.getTime();
|
|
1081
|
+
if (oldestMessageTimestampInSet <= oldestTimestampMs) hi = mid;
|
|
1082
|
+
else lo = mid + 1;
|
|
1083
|
+
}
|
|
1084
|
+
return lo;
|
|
1085
|
+
};
|
|
1049
1086
|
this._channel = channel;
|
|
1050
1087
|
this.watcher_count = 0;
|
|
1051
1088
|
this.typing = {};
|
|
@@ -1571,6 +1608,25 @@ var ChannelState = class {
|
|
|
1571
1608
|
}
|
|
1572
1609
|
return this.messageSets[messageSetIndex].messages.find((m) => m.id === messageId);
|
|
1573
1610
|
}
|
|
1611
|
+
findMessageByTimestamp(timestampMs, parentMessageId, exactTsMatch = false) {
|
|
1612
|
+
if (parentMessageId && !this.threads[parentMessageId] || this.messageSets.length === 0)
|
|
1613
|
+
return null;
|
|
1614
|
+
const setIndex = this.findMessageSetByOldestTimestamp(timestampMs);
|
|
1615
|
+
const targetMsgSet = this.messageSets[setIndex]?.messages;
|
|
1616
|
+
if (!targetMsgSet?.length) return null;
|
|
1617
|
+
const firstMsgTimestamp = targetMsgSet[0].created_at.getTime();
|
|
1618
|
+
const lastMsgTimestamp = targetMsgSet.slice(-1)[0].created_at.getTime();
|
|
1619
|
+
const isOutOfBound = timestampMs < firstMsgTimestamp || lastMsgTimestamp < timestampMs;
|
|
1620
|
+
if (isOutOfBound && exactTsMatch) return null;
|
|
1621
|
+
let msgIndex = 0, hi = targetMsgSet.length - 1;
|
|
1622
|
+
while (msgIndex < hi) {
|
|
1623
|
+
const mid = msgIndex + hi >>> 1;
|
|
1624
|
+
if (timestampMs <= targetMsgSet[mid].created_at.getTime()) hi = mid;
|
|
1625
|
+
else msgIndex = mid + 1;
|
|
1626
|
+
}
|
|
1627
|
+
const foundMessage = targetMsgSet[msgIndex];
|
|
1628
|
+
return !exactTsMatch ? foundMessage : foundMessage.created_at.getTime() === timestampMs ? foundMessage : null;
|
|
1629
|
+
}
|
|
1574
1630
|
switchToMessageSet(index) {
|
|
1575
1631
|
const currentMessages = this.messageSets.find((s) => s.isCurrent);
|
|
1576
1632
|
if (!currentMessages) {
|
|
@@ -1590,35 +1646,77 @@ var ChannelState = class {
|
|
|
1590
1646
|
findTargetMessageSet(newMessages, addIfDoesNotExist = true, messageSetToAddToIfDoesNotExist = "current") {
|
|
1591
1647
|
let messagesToAdd = newMessages;
|
|
1592
1648
|
let targetMessageSetIndex;
|
|
1649
|
+
if (newMessages.length === 0)
|
|
1650
|
+
return { targetMessageSetIndex: 0, messagesToAdd: newMessages };
|
|
1593
1651
|
if (addIfDoesNotExist) {
|
|
1594
|
-
const
|
|
1652
|
+
const overlappingMessageSetIndicesByMsgIds = this.messageSets.map((_, i) => i).filter(
|
|
1595
1653
|
(i) => this.areMessageSetsOverlap(this.messageSets[i].messages, newMessages)
|
|
1596
1654
|
);
|
|
1655
|
+
const overlappingMessageSetIndicesByTimestamp = this.messageSets.map((_, i) => i).filter(
|
|
1656
|
+
(i) => messageSetsOverlapByTimestamp(
|
|
1657
|
+
this.messageSets[i].messages,
|
|
1658
|
+
newMessages.map(formatMessage)
|
|
1659
|
+
)
|
|
1660
|
+
);
|
|
1597
1661
|
switch (messageSetToAddToIfDoesNotExist) {
|
|
1598
1662
|
case "new":
|
|
1599
|
-
if (
|
|
1600
|
-
targetMessageSetIndex =
|
|
1663
|
+
if (overlappingMessageSetIndicesByMsgIds.length > 0) {
|
|
1664
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByMsgIds[0];
|
|
1665
|
+
} else if (overlappingMessageSetIndicesByTimestamp.length > 0) {
|
|
1666
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByTimestamp[0];
|
|
1601
1667
|
} else if (newMessages.some((m) => !m.parent_id)) {
|
|
1602
|
-
this.
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1668
|
+
const setIngestIndex = this.findMessageSetByOldestTimestamp(
|
|
1669
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1670
|
+
new Date(newMessages[0].created_at).getTime()
|
|
1671
|
+
);
|
|
1672
|
+
if (setIngestIndex === -1) {
|
|
1673
|
+
this.messageSets.push({
|
|
1674
|
+
messages: [],
|
|
1675
|
+
isCurrent: false,
|
|
1676
|
+
isLatest: false,
|
|
1677
|
+
pagination: DEFAULT_MESSAGE_SET_PAGINATION
|
|
1678
|
+
});
|
|
1679
|
+
targetMessageSetIndex = this.messageSets.length - 1;
|
|
1680
|
+
} else {
|
|
1681
|
+
const isLatest = setIngestIndex === 0;
|
|
1682
|
+
this.messageSets.splice(setIngestIndex, 0, {
|
|
1683
|
+
messages: [],
|
|
1684
|
+
isCurrent: false,
|
|
1685
|
+
isLatest,
|
|
1686
|
+
pagination: DEFAULT_MESSAGE_SET_PAGINATION
|
|
1687
|
+
// fixme: it is problematic decide about pagination without having data
|
|
1688
|
+
});
|
|
1689
|
+
if (isLatest) {
|
|
1690
|
+
this.messageSets.slice(1).forEach((set) => {
|
|
1691
|
+
set.isLatest = false;
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
targetMessageSetIndex = setIngestIndex;
|
|
1695
|
+
}
|
|
1609
1696
|
}
|
|
1610
1697
|
break;
|
|
1611
1698
|
case "current":
|
|
1612
|
-
|
|
1699
|
+
if (overlappingMessageSetIndicesByTimestamp.length > 0) {
|
|
1700
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByTimestamp[0];
|
|
1701
|
+
} else {
|
|
1702
|
+
targetMessageSetIndex = this.messageSets.findIndex((s) => s.isCurrent);
|
|
1703
|
+
}
|
|
1613
1704
|
break;
|
|
1614
1705
|
case "latest":
|
|
1615
|
-
|
|
1706
|
+
if (overlappingMessageSetIndicesByTimestamp.length > 0) {
|
|
1707
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByTimestamp[0];
|
|
1708
|
+
} else {
|
|
1709
|
+
targetMessageSetIndex = this.messageSets.findIndex((s) => s.isLatest);
|
|
1710
|
+
}
|
|
1616
1711
|
break;
|
|
1617
1712
|
default:
|
|
1618
1713
|
targetMessageSetIndex = -1;
|
|
1619
1714
|
}
|
|
1620
|
-
const mergeTargetMessageSetIndex =
|
|
1621
|
-
|
|
1715
|
+
const mergeTargetMessageSetIndex = overlappingMessageSetIndicesByMsgIds.splice(
|
|
1716
|
+
0,
|
|
1717
|
+
1
|
|
1718
|
+
)[0];
|
|
1719
|
+
const mergeSourceMessageSetIndices = [...overlappingMessageSetIndicesByMsgIds];
|
|
1622
1720
|
if (mergeTargetMessageSetIndex !== void 0 && mergeTargetMessageSetIndex !== targetMessageSetIndex) {
|
|
1623
1721
|
mergeSourceMessageSetIndices.push(targetMessageSetIndex);
|
|
1624
1722
|
}
|
|
@@ -6151,7 +6249,7 @@ var Thread = class extends WithSubscriptions {
|
|
|
6151
6249
|
if (this.ownUnreadCount === 0 && !force) {
|
|
6152
6250
|
return null;
|
|
6153
6251
|
}
|
|
6154
|
-
return await this.
|
|
6252
|
+
return await this.client.messageDeliveryReporter.markRead(this);
|
|
6155
6253
|
};
|
|
6156
6254
|
this.throttledMarkAsRead = throttle(
|
|
6157
6255
|
() => this.markAsRead(),
|
|
@@ -6881,6 +6979,444 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
|
|
|
6881
6979
|
_MessageComposer.generateId = generateUUIDv4;
|
|
6882
6980
|
var MessageComposer = _MessageComposer;
|
|
6883
6981
|
|
|
6982
|
+
// src/messageDelivery/MessageDeliveryReporter.ts
|
|
6983
|
+
var MAX_DELIVERED_MESSAGE_COUNT_IN_PAYLOAD = 100;
|
|
6984
|
+
var MARK_AS_DELIVERED_BUFFER_TIMEOUT = 1e3;
|
|
6985
|
+
var MARK_AS_READ_THROTTLE_TIMEOUT2 = 1e3;
|
|
6986
|
+
var isChannel = (item) => item instanceof Channel;
|
|
6987
|
+
var isThread = (item) => item instanceof Thread;
|
|
6988
|
+
var MessageDeliveryReporter = class {
|
|
6989
|
+
constructor({ client }) {
|
|
6990
|
+
this.deliveryReportCandidates = /* @__PURE__ */ new Map();
|
|
6991
|
+
this.nextDeliveryReportCandidates = /* @__PURE__ */ new Map();
|
|
6992
|
+
this.markDeliveredRequestPromise = null;
|
|
6993
|
+
this.markDeliveredTimeout = null;
|
|
6994
|
+
/**
|
|
6995
|
+
* Retrieve the reference to the latest message in the state that is nor read neither reported as delivered
|
|
6996
|
+
* @param collection
|
|
6997
|
+
*/
|
|
6998
|
+
this.getNextDeliveryReportCandidate = (collection) => {
|
|
6999
|
+
const ownUserId = this.client.user?.id;
|
|
7000
|
+
if (!ownUserId) return;
|
|
7001
|
+
let latestMessages = [];
|
|
7002
|
+
let lastDeliveredAt;
|
|
7003
|
+
let lastReadAt;
|
|
7004
|
+
let key = void 0;
|
|
7005
|
+
if (isChannel(collection)) {
|
|
7006
|
+
latestMessages = collection.state.latestMessages;
|
|
7007
|
+
const ownReadState = collection.state.read[ownUserId] ?? {};
|
|
7008
|
+
lastReadAt = ownReadState?.last_read;
|
|
7009
|
+
lastDeliveredAt = ownReadState?.last_delivered_at;
|
|
7010
|
+
key = collection.cid;
|
|
7011
|
+
} else if (isThread(collection)) {
|
|
7012
|
+
latestMessages = collection.state.getLatestValue().replies;
|
|
7013
|
+
const ownReadState = collection.state.getLatestValue().read[ownUserId] ?? {};
|
|
7014
|
+
lastReadAt = ownReadState?.lastReadAt;
|
|
7015
|
+
lastDeliveredAt = ownReadState?.lastDeliveredAt;
|
|
7016
|
+
key = `${collection.channel.cid}:${collection.id}`;
|
|
7017
|
+
return;
|
|
7018
|
+
} else {
|
|
7019
|
+
return;
|
|
7020
|
+
}
|
|
7021
|
+
if (!key) return;
|
|
7022
|
+
const [latestMessage] = latestMessages.slice(-1);
|
|
7023
|
+
const wholeCollectionIsRead = !latestMessage || lastReadAt >= latestMessage.created_at;
|
|
7024
|
+
if (wholeCollectionIsRead) return { key, id: null };
|
|
7025
|
+
const wholeCollectionIsMarkedDelivered = !latestMessage || (lastDeliveredAt ?? 0) >= latestMessage.created_at;
|
|
7026
|
+
if (wholeCollectionIsMarkedDelivered) return { key, id: null };
|
|
7027
|
+
return { key, id: latestMessage.id || null };
|
|
7028
|
+
};
|
|
7029
|
+
/**
|
|
7030
|
+
* Fires delivery announcement request followed by immediate delivery candidate buffer reset.
|
|
7031
|
+
* @param options
|
|
7032
|
+
*/
|
|
7033
|
+
this.announceDelivery = (options) => {
|
|
7034
|
+
if (this.markDeliveredRequestInFlight || !this.hasDeliveryCandidates) return;
|
|
7035
|
+
const { latest_delivered_messages, sendBuffer } = this.confirmationsFromDeliveryReportCandidates();
|
|
7036
|
+
if (!latest_delivered_messages.length) return;
|
|
7037
|
+
const payload = { ...options, latest_delivered_messages };
|
|
7038
|
+
const postFlightReconcile = () => {
|
|
7039
|
+
this.markDeliveredRequestPromise = null;
|
|
7040
|
+
for (const [k, v] of this.nextDeliveryReportCandidates.entries()) {
|
|
7041
|
+
this.deliveryReportCandidates.set(k, v);
|
|
7042
|
+
}
|
|
7043
|
+
this.nextDeliveryReportCandidates = /* @__PURE__ */ new Map();
|
|
7044
|
+
this.announceDeliveryBuffered(options);
|
|
7045
|
+
};
|
|
7046
|
+
const handleError = () => {
|
|
7047
|
+
for (const [k, v] of Object.entries(sendBuffer)) {
|
|
7048
|
+
if (!this.deliveryReportCandidates.has(k)) {
|
|
7049
|
+
this.deliveryReportCandidates.set(k, v);
|
|
7050
|
+
}
|
|
7051
|
+
}
|
|
7052
|
+
postFlightReconcile();
|
|
7053
|
+
};
|
|
7054
|
+
this.markDeliveredRequestPromise = this.client.markChannelsDelivered(payload).then(postFlightReconcile, handleError);
|
|
7055
|
+
};
|
|
7056
|
+
this.announceDeliveryBuffered = (options) => {
|
|
7057
|
+
if (this.hasTimer || this.markDeliveredRequestInFlight || !this.hasDeliveryCandidates)
|
|
7058
|
+
return;
|
|
7059
|
+
this.markDeliveredTimeout = setTimeout(() => {
|
|
7060
|
+
this.markDeliveredTimeout = null;
|
|
7061
|
+
this.announceDelivery(options);
|
|
7062
|
+
}, MARK_AS_DELIVERED_BUFFER_TIMEOUT);
|
|
7063
|
+
};
|
|
7064
|
+
/**
|
|
7065
|
+
* Delegates the mark-read call to the Channel or Thread instance
|
|
7066
|
+
* @param collection
|
|
7067
|
+
* @param options
|
|
7068
|
+
*/
|
|
7069
|
+
this.markRead = async (collection, options) => {
|
|
7070
|
+
let result = null;
|
|
7071
|
+
if (isChannel(collection)) {
|
|
7072
|
+
result = await collection.markAsReadRequest(options);
|
|
7073
|
+
} else if (isThread(collection)) {
|
|
7074
|
+
result = await collection.channel.markAsReadRequest({
|
|
7075
|
+
...options,
|
|
7076
|
+
thread_id: collection.id
|
|
7077
|
+
});
|
|
7078
|
+
}
|
|
7079
|
+
this.removeCandidateFor(collection);
|
|
7080
|
+
return result;
|
|
7081
|
+
};
|
|
7082
|
+
/**
|
|
7083
|
+
* Throttles the MessageDeliveryReporter.markRead call
|
|
7084
|
+
* @param collection
|
|
7085
|
+
* @param options
|
|
7086
|
+
*/
|
|
7087
|
+
this.throttledMarkRead = throttle(this.markRead, MARK_AS_READ_THROTTLE_TIMEOUT2, {
|
|
7088
|
+
leading: false,
|
|
7089
|
+
trailing: true
|
|
7090
|
+
});
|
|
7091
|
+
this.client = client;
|
|
7092
|
+
}
|
|
7093
|
+
get markDeliveredRequestInFlight() {
|
|
7094
|
+
return this.markDeliveredRequestPromise !== null;
|
|
7095
|
+
}
|
|
7096
|
+
get hasTimer() {
|
|
7097
|
+
return this.markDeliveredTimeout !== null;
|
|
7098
|
+
}
|
|
7099
|
+
get hasDeliveryCandidates() {
|
|
7100
|
+
return this.deliveryReportCandidates.size > 0;
|
|
7101
|
+
}
|
|
7102
|
+
/**
|
|
7103
|
+
* Build latest_delivered_messages payload from an arbitrary buffer (deliveryReportCandidates / nextDeliveryReportCandidates)
|
|
7104
|
+
*/
|
|
7105
|
+
confirmationsFrom(map2) {
|
|
7106
|
+
return Array.from(map2.entries()).map(([key, messageId]) => {
|
|
7107
|
+
const [type, id, parent_id] = key.split(":");
|
|
7108
|
+
return parent_id ? { cid: `${type}:${id}`, id: messageId, parent_id } : { cid: key, id: messageId };
|
|
7109
|
+
});
|
|
7110
|
+
}
|
|
7111
|
+
confirmationsFromDeliveryReportCandidates() {
|
|
7112
|
+
const entries = Array.from(this.deliveryReportCandidates);
|
|
7113
|
+
const sendBuffer = new Map(entries.slice(0, MAX_DELIVERED_MESSAGE_COUNT_IN_PAYLOAD));
|
|
7114
|
+
this.deliveryReportCandidates = new Map(
|
|
7115
|
+
entries.slice(MAX_DELIVERED_MESSAGE_COUNT_IN_PAYLOAD)
|
|
7116
|
+
);
|
|
7117
|
+
return { latest_delivered_messages: this.confirmationsFrom(sendBuffer), sendBuffer };
|
|
7118
|
+
}
|
|
7119
|
+
/**
|
|
7120
|
+
* Generate candidate key for storing in the candidates buffer
|
|
7121
|
+
* @param collection
|
|
7122
|
+
* @private
|
|
7123
|
+
*/
|
|
7124
|
+
candidateKeyFor(collection) {
|
|
7125
|
+
if (isChannel(collection)) return collection.cid;
|
|
7126
|
+
if (isThread(collection)) return `${collection.channel.cid}:${collection.id}`;
|
|
7127
|
+
}
|
|
7128
|
+
/**
|
|
7129
|
+
* Updates the delivery candidates buffer with the latest delivery candidates
|
|
7130
|
+
* @param collection
|
|
7131
|
+
*/
|
|
7132
|
+
trackDeliveredCandidate(collection) {
|
|
7133
|
+
if (isChannel(collection) && !collection.getConfig()?.read_events) return;
|
|
7134
|
+
if (isThread(collection) && !collection.channel.getConfig()?.read_events) return;
|
|
7135
|
+
const candidate = this.getNextDeliveryReportCandidate(collection);
|
|
7136
|
+
if (!candidate?.key) return;
|
|
7137
|
+
const buffer = this.markDeliveredRequestInFlight ? this.nextDeliveryReportCandidates : this.deliveryReportCandidates;
|
|
7138
|
+
if (candidate.id === null) buffer.delete(candidate.key);
|
|
7139
|
+
else buffer.set(candidate.key, candidate.id);
|
|
7140
|
+
}
|
|
7141
|
+
/**
|
|
7142
|
+
* Removes candidate from the delivery report buffer
|
|
7143
|
+
* @param collection
|
|
7144
|
+
* @private
|
|
7145
|
+
*/
|
|
7146
|
+
removeCandidateFor(collection) {
|
|
7147
|
+
const candidateKey = this.candidateKeyFor(collection);
|
|
7148
|
+
if (!candidateKey) return;
|
|
7149
|
+
this.deliveryReportCandidates.delete(candidateKey);
|
|
7150
|
+
this.nextDeliveryReportCandidates.delete(candidateKey);
|
|
7151
|
+
}
|
|
7152
|
+
/**
|
|
7153
|
+
* Records the latest message delivered for Channel or Thread instances and schedules the next report
|
|
7154
|
+
* if not already scheduled and candidates exist.
|
|
7155
|
+
* Should be used for WS handling (message.new) as well as for ingesting HTTP channel query results.
|
|
7156
|
+
* @param collections
|
|
7157
|
+
*/
|
|
7158
|
+
syncDeliveredCandidates(collections) {
|
|
7159
|
+
if (this.client.user?.privacy_settings?.delivery_receipts?.enabled === false) return;
|
|
7160
|
+
for (const c of collections) this.trackDeliveredCandidate(c);
|
|
7161
|
+
this.announceDeliveryBuffered();
|
|
7162
|
+
}
|
|
7163
|
+
};
|
|
7164
|
+
|
|
7165
|
+
// src/messageDelivery/MessageReceiptsTracker.ts
|
|
7166
|
+
var MIN_REF = { timestampMs: Number.NEGATIVE_INFINITY, msgId: "" };
|
|
7167
|
+
var compareRefsAsc = (a, b) => a.timestampMs !== b.timestampMs ? a.timestampMs - b.timestampMs : 0;
|
|
7168
|
+
var findIndex = (arr, target, keyOf) => {
|
|
7169
|
+
let lo = 0, hi = arr.length;
|
|
7170
|
+
while (lo < hi) {
|
|
7171
|
+
const mid = lo + hi >>> 1;
|
|
7172
|
+
if (compareRefsAsc(keyOf(arr[mid]), target) >= 0) hi = mid;
|
|
7173
|
+
else lo = mid + 1;
|
|
7174
|
+
}
|
|
7175
|
+
return lo;
|
|
7176
|
+
};
|
|
7177
|
+
var findUpperIndex = (arr, target, keyOf) => {
|
|
7178
|
+
let lo = 0, hi = arr.length;
|
|
7179
|
+
while (lo < hi) {
|
|
7180
|
+
const mid = lo + hi >>> 1;
|
|
7181
|
+
if (compareRefsAsc(keyOf(arr[mid]), target) > 0) hi = mid;
|
|
7182
|
+
else lo = mid + 1;
|
|
7183
|
+
}
|
|
7184
|
+
return lo;
|
|
7185
|
+
};
|
|
7186
|
+
var insertByKey = (arr, item, keyOf) => arr.splice(findUpperIndex(arr, keyOf(item), keyOf), 0, item);
|
|
7187
|
+
var removeByOldKey = (arr, item, oldKey, keyOf) => {
|
|
7188
|
+
let i = findIndex(arr, oldKey, keyOf);
|
|
7189
|
+
while (i < arr.length && compareRefsAsc(keyOf(arr[i]), oldKey) === 0) {
|
|
7190
|
+
if (arr[i].user.id === item.user.id) {
|
|
7191
|
+
arr.splice(i, 1);
|
|
7192
|
+
return;
|
|
7193
|
+
}
|
|
7194
|
+
i++;
|
|
7195
|
+
}
|
|
7196
|
+
};
|
|
7197
|
+
var MessageReceiptsTracker = class {
|
|
7198
|
+
constructor({ locateMessage }) {
|
|
7199
|
+
this.byUser = /* @__PURE__ */ new Map();
|
|
7200
|
+
this.readSorted = [];
|
|
7201
|
+
// asc by lastReadRef
|
|
7202
|
+
this.deliveredSorted = [];
|
|
7203
|
+
this.locateMessage = locateMessage;
|
|
7204
|
+
}
|
|
7205
|
+
/** Build initial state from server snapshots (single pass + sort). */
|
|
7206
|
+
ingestInitial(responses) {
|
|
7207
|
+
this.byUser.clear();
|
|
7208
|
+
this.readSorted = [];
|
|
7209
|
+
this.deliveredSorted = [];
|
|
7210
|
+
for (const r of responses) {
|
|
7211
|
+
const lastReadTimestamp = r.last_read ? new Date(r.last_read).getTime() : null;
|
|
7212
|
+
const lastDeliveredTimestamp = r.last_delivered_at ? new Date(r.last_delivered_at).getTime() : null;
|
|
7213
|
+
const lastReadRef = lastReadTimestamp ? this.locateMessage(lastReadTimestamp) ?? MIN_REF : MIN_REF;
|
|
7214
|
+
let lastDeliveredRef = lastDeliveredTimestamp ? this.locateMessage(lastDeliveredTimestamp) ?? MIN_REF : MIN_REF;
|
|
7215
|
+
const isReadAfterDelivered = compareRefsAsc(lastDeliveredRef, lastReadRef) < 0;
|
|
7216
|
+
if (isReadAfterDelivered) lastDeliveredRef = lastReadRef;
|
|
7217
|
+
const userProgress = { user: r.user, lastReadRef, lastDeliveredRef };
|
|
7218
|
+
this.byUser.set(r.user.id, userProgress);
|
|
7219
|
+
this.readSorted.splice(
|
|
7220
|
+
findIndex(this.readSorted, lastReadRef, (up) => up.lastReadRef),
|
|
7221
|
+
0,
|
|
7222
|
+
userProgress
|
|
7223
|
+
);
|
|
7224
|
+
this.deliveredSorted.splice(
|
|
7225
|
+
findIndex(this.deliveredSorted, lastDeliveredRef, (up) => up.lastDeliveredRef),
|
|
7226
|
+
0,
|
|
7227
|
+
userProgress
|
|
7228
|
+
);
|
|
7229
|
+
}
|
|
7230
|
+
}
|
|
7231
|
+
/** message.delivered — user device confirmed delivery up to and including messageId. */
|
|
7232
|
+
onMessageDelivered({
|
|
7233
|
+
user,
|
|
7234
|
+
deliveredAt,
|
|
7235
|
+
lastDeliveredMessageId
|
|
7236
|
+
}) {
|
|
7237
|
+
const timestampMs = new Date(deliveredAt).getTime();
|
|
7238
|
+
const msgRef = lastDeliveredMessageId ? { timestampMs, msgId: lastDeliveredMessageId } : this.locateMessage(new Date(deliveredAt).getTime());
|
|
7239
|
+
if (!msgRef) return;
|
|
7240
|
+
const userProgress = this.ensureUser(user);
|
|
7241
|
+
const newDelivered = compareRefsAsc(msgRef, userProgress.lastReadRef) < 0 ? userProgress.lastReadRef : msgRef;
|
|
7242
|
+
if (compareRefsAsc(newDelivered, userProgress.lastDeliveredRef) <= 0) return;
|
|
7243
|
+
removeByOldKey(
|
|
7244
|
+
this.deliveredSorted,
|
|
7245
|
+
userProgress,
|
|
7246
|
+
userProgress.lastDeliveredRef,
|
|
7247
|
+
(x) => x.lastDeliveredRef
|
|
7248
|
+
);
|
|
7249
|
+
userProgress.lastDeliveredRef = newDelivered;
|
|
7250
|
+
insertByKey(this.deliveredSorted, userProgress, (x) => x.lastDeliveredRef);
|
|
7251
|
+
}
|
|
7252
|
+
/** message.read — user read up to and including messageId. */
|
|
7253
|
+
onMessageRead({
|
|
7254
|
+
user,
|
|
7255
|
+
readAt,
|
|
7256
|
+
lastReadMessageId
|
|
7257
|
+
}) {
|
|
7258
|
+
const timestampMs = new Date(readAt).getTime();
|
|
7259
|
+
const msgRef = lastReadMessageId ? { timestampMs, msgId: lastReadMessageId } : this.locateMessage(timestampMs);
|
|
7260
|
+
if (!msgRef) return;
|
|
7261
|
+
const userProgress = this.ensureUser(user);
|
|
7262
|
+
if (compareRefsAsc(msgRef, userProgress.lastReadRef) <= 0) return;
|
|
7263
|
+
removeByOldKey(
|
|
7264
|
+
this.readSorted,
|
|
7265
|
+
userProgress,
|
|
7266
|
+
userProgress.lastReadRef,
|
|
7267
|
+
(x) => x.lastReadRef
|
|
7268
|
+
);
|
|
7269
|
+
userProgress.lastReadRef = msgRef;
|
|
7270
|
+
insertByKey(this.readSorted, userProgress, (x) => x.lastReadRef);
|
|
7271
|
+
if (compareRefsAsc(userProgress.lastDeliveredRef, userProgress.lastReadRef) < 0) {
|
|
7272
|
+
removeByOldKey(
|
|
7273
|
+
this.deliveredSorted,
|
|
7274
|
+
userProgress,
|
|
7275
|
+
userProgress.lastDeliveredRef,
|
|
7276
|
+
(x) => x.lastDeliveredRef
|
|
7277
|
+
);
|
|
7278
|
+
userProgress.lastDeliveredRef = userProgress.lastReadRef;
|
|
7279
|
+
insertByKey(this.deliveredSorted, userProgress, (x) => x.lastDeliveredRef);
|
|
7280
|
+
}
|
|
7281
|
+
}
|
|
7282
|
+
/** notification.mark_unread — user marked messages unread starting at `first_unread_message_id`.
|
|
7283
|
+
* Sets lastReadRef to the event’s last_read_* values. Delivery never moves backward.
|
|
7284
|
+
* The event is sent only to the user that triggered the action (own user), so we will never adjust read ref
|
|
7285
|
+
* for other users - we will not see changes in the UI for other users. However, this implementation does not
|
|
7286
|
+
* take into consideration this fact and is ready to handle the mark-unread event for any user.
|
|
7287
|
+
*/
|
|
7288
|
+
onNotificationMarkUnread({
|
|
7289
|
+
user,
|
|
7290
|
+
lastReadAt,
|
|
7291
|
+
lastReadMessageId
|
|
7292
|
+
}) {
|
|
7293
|
+
const userProgress = this.ensureUser(user);
|
|
7294
|
+
const newReadRef = lastReadAt ? { timestampMs: new Date(lastReadAt).getTime(), msgId: lastReadMessageId ?? "" } : { ...MIN_REF };
|
|
7295
|
+
if (compareRefsAsc(newReadRef, userProgress.lastReadRef) === 0 && newReadRef.msgId === userProgress.lastReadRef.msgId) {
|
|
7296
|
+
return;
|
|
7297
|
+
}
|
|
7298
|
+
removeByOldKey(
|
|
7299
|
+
this.readSorted,
|
|
7300
|
+
userProgress,
|
|
7301
|
+
userProgress.lastReadRef,
|
|
7302
|
+
(x) => x.lastReadRef
|
|
7303
|
+
);
|
|
7304
|
+
userProgress.lastReadRef = newReadRef;
|
|
7305
|
+
insertByKey(this.readSorted, userProgress, (x) => x.lastReadRef);
|
|
7306
|
+
if (compareRefsAsc(userProgress.lastDeliveredRef, userProgress.lastReadRef) < 0) {
|
|
7307
|
+
removeByOldKey(
|
|
7308
|
+
this.deliveredSorted,
|
|
7309
|
+
userProgress,
|
|
7310
|
+
userProgress.lastDeliveredRef,
|
|
7311
|
+
(x) => x.lastDeliveredRef
|
|
7312
|
+
);
|
|
7313
|
+
userProgress.lastDeliveredRef = userProgress.lastReadRef;
|
|
7314
|
+
insertByKey(this.deliveredSorted, userProgress, (x) => x.lastDeliveredRef);
|
|
7315
|
+
}
|
|
7316
|
+
}
|
|
7317
|
+
/** All users who READ this message. */
|
|
7318
|
+
readersForMessage(msgRef) {
|
|
7319
|
+
const index = findIndex(this.readSorted, msgRef, ({ lastReadRef }) => lastReadRef);
|
|
7320
|
+
return this.readSorted.slice(index).map((x) => x.user);
|
|
7321
|
+
}
|
|
7322
|
+
/** All users who have it DELIVERED (includes readers). */
|
|
7323
|
+
deliveredForMessage(msgRef) {
|
|
7324
|
+
const pos = findIndex(
|
|
7325
|
+
this.deliveredSorted,
|
|
7326
|
+
msgRef,
|
|
7327
|
+
({ lastDeliveredRef }) => lastDeliveredRef
|
|
7328
|
+
);
|
|
7329
|
+
return this.deliveredSorted.slice(pos).map((x) => x.user);
|
|
7330
|
+
}
|
|
7331
|
+
/** Users who delivered but have NOT read. */
|
|
7332
|
+
deliveredNotReadForMessage(msgRef) {
|
|
7333
|
+
const pos = findIndex(
|
|
7334
|
+
this.deliveredSorted,
|
|
7335
|
+
msgRef,
|
|
7336
|
+
({ lastDeliveredRef }) => lastDeliveredRef
|
|
7337
|
+
);
|
|
7338
|
+
const usersDeliveredNotRead = [];
|
|
7339
|
+
for (let i = pos; i < this.deliveredSorted.length; i++) {
|
|
7340
|
+
const userProgress = this.deliveredSorted[i];
|
|
7341
|
+
if (compareRefsAsc(userProgress.lastReadRef, msgRef) < 0)
|
|
7342
|
+
usersDeliveredNotRead.push(userProgress.user);
|
|
7343
|
+
}
|
|
7344
|
+
return usersDeliveredNotRead;
|
|
7345
|
+
}
|
|
7346
|
+
/** Users for whom `msgRef` is their *last read* (exact match). */
|
|
7347
|
+
usersWhoseLastReadIs(msgRef) {
|
|
7348
|
+
if (!msgRef.msgId) return [];
|
|
7349
|
+
const start = findIndex(this.readSorted, msgRef, (x) => x.lastReadRef);
|
|
7350
|
+
const end = findUpperIndex(this.readSorted, msgRef, (x) => x.lastReadRef);
|
|
7351
|
+
const users = [];
|
|
7352
|
+
for (let i = start; i < end; i++) {
|
|
7353
|
+
const up = this.readSorted[i];
|
|
7354
|
+
if (up.lastReadRef.msgId === msgRef.msgId) users.push(up.user);
|
|
7355
|
+
}
|
|
7356
|
+
return users;
|
|
7357
|
+
}
|
|
7358
|
+
/** Users for whom `msgRef` is their *last delivered* (exact match). */
|
|
7359
|
+
usersWhoseLastDeliveredIs(msgRef) {
|
|
7360
|
+
if (!msgRef.msgId) return [];
|
|
7361
|
+
const start = findIndex(this.deliveredSorted, msgRef, (x) => x.lastDeliveredRef);
|
|
7362
|
+
const end = findUpperIndex(this.deliveredSorted, msgRef, (x) => x.lastDeliveredRef);
|
|
7363
|
+
const users = [];
|
|
7364
|
+
for (let i = start; i < end; i++) {
|
|
7365
|
+
const up = this.deliveredSorted[i];
|
|
7366
|
+
if (up.lastDeliveredRef.msgId === msgRef.msgId) users.push(up.user);
|
|
7367
|
+
}
|
|
7368
|
+
return users;
|
|
7369
|
+
}
|
|
7370
|
+
// ---- queries: per-user status ----
|
|
7371
|
+
hasUserRead(msgRef, userId) {
|
|
7372
|
+
const up = this.byUser.get(userId);
|
|
7373
|
+
return !!up && compareRefsAsc(up.lastReadRef, msgRef) >= 0;
|
|
7374
|
+
}
|
|
7375
|
+
hasUserDelivered(msgRef, userId) {
|
|
7376
|
+
const up = this.byUser.get(userId);
|
|
7377
|
+
return !!up && compareRefsAsc(up.lastDeliveredRef, msgRef) >= 0;
|
|
7378
|
+
}
|
|
7379
|
+
getUserProgress(userId) {
|
|
7380
|
+
const userProgress = this.byUser.get(userId);
|
|
7381
|
+
if (!userProgress) return null;
|
|
7382
|
+
return userProgress;
|
|
7383
|
+
}
|
|
7384
|
+
groupUsersByLastReadMessage() {
|
|
7385
|
+
return Array.from(this.byUser.values()).reduce(
|
|
7386
|
+
(acc, userProgress) => {
|
|
7387
|
+
const msgId = userProgress.lastReadRef.msgId;
|
|
7388
|
+
if (!msgId) return acc;
|
|
7389
|
+
if (!acc[msgId]) acc[msgId] = [];
|
|
7390
|
+
acc[msgId].push(userProgress.user);
|
|
7391
|
+
return acc;
|
|
7392
|
+
},
|
|
7393
|
+
{}
|
|
7394
|
+
);
|
|
7395
|
+
}
|
|
7396
|
+
groupUsersByLastDeliveredMessage() {
|
|
7397
|
+
return Array.from(this.byUser.values()).reduce(
|
|
7398
|
+
(acc, userProgress) => {
|
|
7399
|
+
const msgId = userProgress.lastDeliveredRef.msgId;
|
|
7400
|
+
if (!msgId) return acc;
|
|
7401
|
+
if (!acc[msgId]) acc[msgId] = [];
|
|
7402
|
+
acc[msgId].push(userProgress.user);
|
|
7403
|
+
return acc;
|
|
7404
|
+
},
|
|
7405
|
+
{}
|
|
7406
|
+
);
|
|
7407
|
+
}
|
|
7408
|
+
ensureUser(user) {
|
|
7409
|
+
let up = this.byUser.get(user.id);
|
|
7410
|
+
if (!up) {
|
|
7411
|
+
up = { user, lastReadRef: MIN_REF, lastDeliveredRef: MIN_REF };
|
|
7412
|
+
this.byUser.set(user.id, up);
|
|
7413
|
+
insertByKey(this.readSorted, up, (x) => x.lastReadRef);
|
|
7414
|
+
insertByKey(this.deliveredSorted, up, (x) => x.lastDeliveredRef);
|
|
7415
|
+
}
|
|
7416
|
+
return up;
|
|
7417
|
+
}
|
|
7418
|
+
};
|
|
7419
|
+
|
|
6884
7420
|
// src/channel.ts
|
|
6885
7421
|
var Channel = class {
|
|
6886
7422
|
/**
|
|
@@ -6962,6 +7498,12 @@ var Channel = class {
|
|
|
6962
7498
|
client: this._client,
|
|
6963
7499
|
compositionContext: this
|
|
6964
7500
|
});
|
|
7501
|
+
this.messageReceiptsTracker = new MessageReceiptsTracker({
|
|
7502
|
+
locateMessage: (timestampMs) => {
|
|
7503
|
+
const msg = this.state.findMessageByTimestamp(timestampMs);
|
|
7504
|
+
return msg && { timestampMs, msgId: msg.id };
|
|
7505
|
+
}
|
|
7506
|
+
});
|
|
6965
7507
|
}
|
|
6966
7508
|
/**
|
|
6967
7509
|
* getClient - Get the chat client for this channel. If client.disconnect() was called, this function will error
|
|
@@ -7771,15 +8313,24 @@ var Channel = class {
|
|
|
7771
8313
|
return messageSlice[0];
|
|
7772
8314
|
}
|
|
7773
8315
|
/**
|
|
7774
|
-
* markRead - Send the mark read event for this user, only works if the `read_events` setting is enabled
|
|
8316
|
+
* markRead - Send the mark read event for this user, only works if the `read_events` setting is enabled. Syncs the message delivery report candidates local state.
|
|
7775
8317
|
*
|
|
7776
8318
|
* @param {MarkReadOptions} data
|
|
7777
8319
|
* @return {Promise<EventAPIResponse | null>} Description
|
|
7778
8320
|
*/
|
|
7779
8321
|
async markRead(data = {}) {
|
|
8322
|
+
return await this.getClient().messageDeliveryReporter.markRead(this, data);
|
|
8323
|
+
}
|
|
8324
|
+
/**
|
|
8325
|
+
* markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is enabled
|
|
8326
|
+
*
|
|
8327
|
+
* @param {MarkReadOptions} data
|
|
8328
|
+
* @return {Promise<EventAPIResponse | null>} Description
|
|
8329
|
+
*/
|
|
8330
|
+
async markAsReadRequest(data = {}) {
|
|
7780
8331
|
this._checkInitialized();
|
|
7781
8332
|
if (!this.getConfig()?.read_events && !this.getClient()._isUsingServerAuth()) {
|
|
7782
|
-
return
|
|
8333
|
+
return null;
|
|
7783
8334
|
}
|
|
7784
8335
|
return await this.getClient().post(this._channelURL() + "/read", {
|
|
7785
8336
|
...data
|
|
@@ -8087,6 +8638,7 @@ var Channel = class {
|
|
|
8087
8638
|
}),
|
|
8088
8639
|
{ method: "upsertChannels" }
|
|
8089
8640
|
);
|
|
8641
|
+
this.getClient().syncDeliveredCandidates([this]);
|
|
8090
8642
|
return state;
|
|
8091
8643
|
}
|
|
8092
8644
|
/**
|
|
@@ -8358,17 +8910,44 @@ var Channel = class {
|
|
|
8358
8910
|
break;
|
|
8359
8911
|
case "message.read":
|
|
8360
8912
|
if (event.user?.id && event.created_at) {
|
|
8913
|
+
const previousReadState = channelState.read[event.user.id];
|
|
8361
8914
|
channelState.read[event.user.id] = {
|
|
8915
|
+
// in case we already have delivery information
|
|
8916
|
+
...previousReadState,
|
|
8362
8917
|
last_read: new Date(event.created_at),
|
|
8363
8918
|
last_read_message_id: event.last_read_message_id,
|
|
8364
8919
|
user: event.user,
|
|
8365
8920
|
unread_messages: 0
|
|
8366
8921
|
};
|
|
8367
|
-
|
|
8922
|
+
this.messageReceiptsTracker.onMessageRead({
|
|
8923
|
+
user: event.user,
|
|
8924
|
+
readAt: event.created_at,
|
|
8925
|
+
lastReadMessageId: event.last_read_message_id
|
|
8926
|
+
});
|
|
8927
|
+
const client = this.getClient();
|
|
8928
|
+
const isOwnEvent = event.user?.id === client.user?.id;
|
|
8929
|
+
if (isOwnEvent) {
|
|
8368
8930
|
channelState.unreadCount = 0;
|
|
8931
|
+
client.syncDeliveredCandidates([this]);
|
|
8369
8932
|
}
|
|
8370
8933
|
}
|
|
8371
8934
|
break;
|
|
8935
|
+
case "message.delivered":
|
|
8936
|
+
if (event.user?.id && event.created_at) {
|
|
8937
|
+
const previousReadState = channelState.read[event.user.id];
|
|
8938
|
+
channelState.read[event.user.id] = {
|
|
8939
|
+
...previousReadState,
|
|
8940
|
+
last_delivered_at: event.last_delivered_at ? new Date(event.last_delivered_at) : void 0,
|
|
8941
|
+
last_delivered_message_id: event.last_delivered_message_id,
|
|
8942
|
+
user: event.user
|
|
8943
|
+
};
|
|
8944
|
+
this.messageReceiptsTracker.onMessageDelivered({
|
|
8945
|
+
user: event.user,
|
|
8946
|
+
deliveredAt: event.created_at,
|
|
8947
|
+
lastDeliveredMessageId: event.last_delivered_message_id
|
|
8948
|
+
});
|
|
8949
|
+
}
|
|
8950
|
+
break;
|
|
8372
8951
|
case "user.watching.start":
|
|
8373
8952
|
case "user.updated":
|
|
8374
8953
|
if (event.user?.id) {
|
|
@@ -8402,7 +8981,8 @@ var Channel = class {
|
|
|
8402
8981
|
break;
|
|
8403
8982
|
case "message.new":
|
|
8404
8983
|
if (event.message) {
|
|
8405
|
-
const
|
|
8984
|
+
const client = this.getClient();
|
|
8985
|
+
const ownMessage = event.user?.id === client.user?.id;
|
|
8406
8986
|
const isThreadMessage = event.message.parent_id && !event.message.show_in_channel;
|
|
8407
8987
|
if (this.state.isUpToDate || isThreadMessage) {
|
|
8408
8988
|
channelState.addMessageSorted(event.message, ownMessage);
|
|
@@ -8418,7 +8998,9 @@ var Channel = class {
|
|
|
8418
8998
|
channelState.read[event.user.id] = {
|
|
8419
8999
|
last_read: new Date(event.created_at),
|
|
8420
9000
|
user: event.user,
|
|
8421
|
-
unread_messages: 0
|
|
9001
|
+
unread_messages: 0,
|
|
9002
|
+
last_delivered_at: new Date(event.created_at),
|
|
9003
|
+
last_delivered_message_id: event.message.id
|
|
8422
9004
|
};
|
|
8423
9005
|
} else {
|
|
8424
9006
|
channelState.read[userId].unread_messages += 1;
|
|
@@ -8428,6 +9010,7 @@ var Channel = class {
|
|
|
8428
9010
|
if (this._countMessageAsUnread(event.message)) {
|
|
8429
9011
|
channelState.unreadCount = channelState.unreadCount + 1;
|
|
8430
9012
|
}
|
|
9013
|
+
client.syncDeliveredCandidates([this]);
|
|
8431
9014
|
}
|
|
8432
9015
|
break;
|
|
8433
9016
|
case "message.updated":
|
|
@@ -8510,9 +9093,12 @@ var Channel = class {
|
|
|
8510
9093
|
break;
|
|
8511
9094
|
case "notification.mark_unread": {
|
|
8512
9095
|
const ownMessage = event.user?.id === this.getClient().user?.id;
|
|
8513
|
-
if (!
|
|
9096
|
+
if (!ownMessage || !event.user) break;
|
|
8514
9097
|
const unreadCount = event.unread_messages ?? 0;
|
|
9098
|
+
const currentState = channelState.read[event.user.id];
|
|
8515
9099
|
channelState.read[event.user.id] = {
|
|
9100
|
+
// keep the message delivery info
|
|
9101
|
+
...currentState,
|
|
8516
9102
|
first_unread_message_id: event.first_unread_message_id,
|
|
8517
9103
|
last_read: new Date(event.last_read_at),
|
|
8518
9104
|
last_read_message_id: event.last_read_message_id,
|
|
@@ -8520,6 +9106,11 @@ var Channel = class {
|
|
|
8520
9106
|
unread_messages: unreadCount
|
|
8521
9107
|
};
|
|
8522
9108
|
channelState.unreadCount = unreadCount;
|
|
9109
|
+
this.messageReceiptsTracker.onNotificationMarkUnread({
|
|
9110
|
+
user: event.user,
|
|
9111
|
+
lastReadAt: event.last_read_at,
|
|
9112
|
+
lastReadMessageId: event.last_read_message_id
|
|
9113
|
+
});
|
|
8523
9114
|
break;
|
|
8524
9115
|
}
|
|
8525
9116
|
case "channel.updated":
|
|
@@ -8671,6 +9262,7 @@ var Channel = class {
|
|
|
8671
9262
|
this.state.unreadCount = this.state.read[read.user.id].unread_messages;
|
|
8672
9263
|
}
|
|
8673
9264
|
}
|
|
9265
|
+
this.messageReceiptsTracker.ingestInitial(state.read);
|
|
8674
9266
|
}
|
|
8675
9267
|
return {
|
|
8676
9268
|
messageSet
|
|
@@ -9280,8 +9872,8 @@ var StableWSConnection = class {
|
|
|
9280
9872
|
};
|
|
9281
9873
|
|
|
9282
9874
|
// src/signing.ts
|
|
9875
|
+
var import_jsonwebtoken = __toESM(require_jsonwebtoken());
|
|
9283
9876
|
var import_crypto = __toESM(require_crypto());
|
|
9284
|
-
import jwt from "jsonwebtoken";
|
|
9285
9877
|
function JWTUserToken(apiSecret, userId, extraData = {}, jwtOptions = {}) {
|
|
9286
9878
|
if (typeof userId !== "string") {
|
|
9287
9879
|
throw new TypeError("userId should be a string");
|
|
@@ -9290,7 +9882,7 @@ function JWTUserToken(apiSecret, userId, extraData = {}, jwtOptions = {}) {
|
|
|
9290
9882
|
user_id: userId,
|
|
9291
9883
|
...extraData
|
|
9292
9884
|
};
|
|
9293
|
-
if (
|
|
9885
|
+
if (import_jsonwebtoken.default == null || import_jsonwebtoken.default.sign == null) {
|
|
9294
9886
|
throw Error(
|
|
9295
9887
|
`Unable to find jwt crypto, if you are getting this error is probably because you are trying to generate tokens on browser or React Native (or other environment where crypto functions are not available). Please Note: token should only be generated server-side.`
|
|
9296
9888
|
);
|
|
@@ -9302,7 +9894,7 @@ function JWTUserToken(apiSecret, userId, extraData = {}, jwtOptions = {}) {
|
|
|
9302
9894
|
if (payload.iat) {
|
|
9303
9895
|
opts.noTimestamp = false;
|
|
9304
9896
|
}
|
|
9305
|
-
return
|
|
9897
|
+
return import_jsonwebtoken.default.sign(payload, apiSecret, opts);
|
|
9306
9898
|
}
|
|
9307
9899
|
function JWTServerToken(apiSecret, jwtOptions = {}) {
|
|
9308
9900
|
const payload = {
|
|
@@ -9312,7 +9904,7 @@ function JWTServerToken(apiSecret, jwtOptions = {}) {
|
|
|
9312
9904
|
{ algorithm: "HS256", noTimestamp: true },
|
|
9313
9905
|
jwtOptions
|
|
9314
9906
|
);
|
|
9315
|
-
return
|
|
9907
|
+
return import_jsonwebtoken.default.sign(payload, apiSecret, opts);
|
|
9316
9908
|
}
|
|
9317
9909
|
function UserFromToken(token) {
|
|
9318
9910
|
const fragments = token.split(".");
|
|
@@ -12221,6 +12813,7 @@ var StreamChat = class _StreamChat {
|
|
|
12221
12813
|
this.threads = new ThreadManager({ client: this });
|
|
12222
12814
|
this.polls = new PollManager({ client: this });
|
|
12223
12815
|
this.reminders = new ReminderManager({ client: this });
|
|
12816
|
+
this.messageDeliveryReporter = new MessageDeliveryReporter({ client: this });
|
|
12224
12817
|
}
|
|
12225
12818
|
static getInstance(key, secretOrOptions, options) {
|
|
12226
12819
|
if (!_StreamChat._instance) {
|
|
@@ -12891,6 +13484,7 @@ var StreamChat = class _StreamChat {
|
|
|
12891
13484
|
c.messageComposer.initStateFromChannelResponse(channelState);
|
|
12892
13485
|
channels.push(c);
|
|
12893
13486
|
}
|
|
13487
|
+
this.syncDeliveredCandidates(channels);
|
|
12894
13488
|
return channels;
|
|
12895
13489
|
}
|
|
12896
13490
|
/**
|
|
@@ -13655,10 +14249,26 @@ var StreamChat = class _StreamChat {
|
|
|
13655
14249
|
}
|
|
13656
14250
|
);
|
|
13657
14251
|
}
|
|
13658
|
-
|
|
14252
|
+
/**
|
|
14253
|
+
* deleteMessage - Delete a message
|
|
14254
|
+
*
|
|
14255
|
+
* @param {string} messageID The id of the message to delete
|
|
14256
|
+
* @param {boolean | DeleteMessageOptions | undefined} [optionsOrHardDelete]
|
|
14257
|
+
* @return {Promise<APIResponse & { message: MessageResponse }>} The API response
|
|
14258
|
+
*/
|
|
14259
|
+
// fixme: remove the signature with optionsOrHardDelete boolean with the next major release
|
|
14260
|
+
async deleteMessage(messageID, optionsOrHardDelete) {
|
|
14261
|
+
let options = {};
|
|
14262
|
+
if (typeof optionsOrHardDelete === "boolean") {
|
|
14263
|
+
options = optionsOrHardDelete ? { hardDelete: true } : {};
|
|
14264
|
+
} else if (optionsOrHardDelete?.deleteForMe) {
|
|
14265
|
+
options = { deleteForMe: true };
|
|
14266
|
+
} else if (optionsOrHardDelete?.hardDelete) {
|
|
14267
|
+
options = { hardDelete: true };
|
|
14268
|
+
}
|
|
13659
14269
|
try {
|
|
13660
14270
|
if (this.offlineDb) {
|
|
13661
|
-
if (hardDelete) {
|
|
14271
|
+
if (options.hardDelete) {
|
|
13662
14272
|
await this.offlineDb.hardDeleteMessage({ id: messageID });
|
|
13663
14273
|
} else {
|
|
13664
14274
|
await this.offlineDb.softDeleteMessage({ id: messageID });
|
|
@@ -13667,7 +14277,7 @@ var StreamChat = class _StreamChat {
|
|
|
13667
14277
|
{
|
|
13668
14278
|
task: {
|
|
13669
14279
|
messageId: messageID,
|
|
13670
|
-
payload: [messageID,
|
|
14280
|
+
payload: [messageID, options],
|
|
13671
14281
|
type: "delete-message"
|
|
13672
14282
|
}
|
|
13673
14283
|
}
|
|
@@ -13679,17 +14289,27 @@ var StreamChat = class _StreamChat {
|
|
|
13679
14289
|
error
|
|
13680
14290
|
});
|
|
13681
14291
|
}
|
|
13682
|
-
return this._deleteMessage(messageID,
|
|
14292
|
+
return this._deleteMessage(messageID, options);
|
|
13683
14293
|
}
|
|
13684
|
-
|
|
14294
|
+
// fixme: remove the signature with optionsOrHardDelete boolean with the next major release
|
|
14295
|
+
async _deleteMessage(messageID, optionsOrHardDelete) {
|
|
14296
|
+
const { deleteForMe, hardDelete } = typeof optionsOrHardDelete === "boolean" ? { hardDelete: optionsOrHardDelete } : optionsOrHardDelete ?? {};
|
|
13685
14297
|
let params = {};
|
|
13686
14298
|
if (hardDelete) {
|
|
13687
14299
|
params = { hard: true };
|
|
13688
14300
|
}
|
|
13689
|
-
|
|
14301
|
+
if (deleteForMe) {
|
|
14302
|
+
params = { ...params, delete_for_me: true };
|
|
14303
|
+
}
|
|
14304
|
+
const result = await this.delete(
|
|
13690
14305
|
this.baseURL + `/messages/${encodeURIComponent(messageID)}`,
|
|
13691
14306
|
params
|
|
13692
14307
|
);
|
|
14308
|
+
if (deleteForMe) {
|
|
14309
|
+
result.message.deleted_for_me = true;
|
|
14310
|
+
result.message.type = "deleted";
|
|
14311
|
+
}
|
|
14312
|
+
return result;
|
|
13693
14313
|
}
|
|
13694
14314
|
/**
|
|
13695
14315
|
* undeleteMessage - Undelete a message
|
|
@@ -13825,7 +14445,7 @@ var StreamChat = class _StreamChat {
|
|
|
13825
14445
|
if (this.userAgent) {
|
|
13826
14446
|
return this.userAgent;
|
|
13827
14447
|
}
|
|
13828
|
-
const version = "9.
|
|
14448
|
+
const version = "9.22.1";
|
|
13829
14449
|
const clientBundle = "browser-esm";
|
|
13830
14450
|
let userAgentString = "";
|
|
13831
14451
|
if (this.sdkIdentifier) {
|
|
@@ -14974,19 +15594,21 @@ var StreamChat = class _StreamChat {
|
|
|
14974
15594
|
return this.delete(`${this.baseURL}/uploads/image`, { url });
|
|
14975
15595
|
}
|
|
14976
15596
|
/**
|
|
14977
|
-
* Send the mark delivered event for this user
|
|
15597
|
+
* Send the mark delivered event for this user
|
|
14978
15598
|
*
|
|
14979
15599
|
* @param {MarkDeliveredOptions} data
|
|
14980
15600
|
* @return {Promise<EventAPIResponse | void>} Description
|
|
14981
15601
|
*/
|
|
14982
15602
|
async markChannelsDelivered(data) {
|
|
14983
|
-
|
|
14984
|
-
if (!deliveryReceiptsEnabled) return;
|
|
15603
|
+
if (!data?.latest_delivered_messages?.length) return;
|
|
14985
15604
|
return await this.post(
|
|
14986
15605
|
this.baseURL + "/channels/delivered",
|
|
14987
15606
|
data ?? {}
|
|
14988
15607
|
);
|
|
14989
15608
|
}
|
|
15609
|
+
syncDeliveredCandidates(collections) {
|
|
15610
|
+
this.messageDeliveryReporter.syncDeliveredCandidates(collections);
|
|
15611
|
+
}
|
|
14990
15612
|
};
|
|
14991
15613
|
|
|
14992
15614
|
// src/events.ts
|
|
@@ -15023,6 +15645,7 @@ var EVENT_MAP = {
|
|
|
15023
15645
|
"notification.mark_unread": true,
|
|
15024
15646
|
"notification.message_new": true,
|
|
15025
15647
|
"notification.mutes_updated": true,
|
|
15648
|
+
"notification.reminder_due": true,
|
|
15026
15649
|
"notification.removed_from_channel": true,
|
|
15027
15650
|
"notification.thread_message_new": true,
|
|
15028
15651
|
"poll.closed": true,
|
|
@@ -15033,6 +15656,9 @@ var EVENT_MAP = {
|
|
|
15033
15656
|
"reaction.deleted": true,
|
|
15034
15657
|
"reaction.new": true,
|
|
15035
15658
|
"reaction.updated": true,
|
|
15659
|
+
"reminder.created": true,
|
|
15660
|
+
"reminder.deleted": true,
|
|
15661
|
+
"reminder.updated": true,
|
|
15036
15662
|
"thread.updated": true,
|
|
15037
15663
|
"typing.start": true,
|
|
15038
15664
|
"typing.stop": true,
|
|
@@ -15057,12 +15683,7 @@ var EVENT_MAP = {
|
|
|
15057
15683
|
"transport.changed": true,
|
|
15058
15684
|
"capabilities.changed": true,
|
|
15059
15685
|
"live_location_sharing.started": true,
|
|
15060
|
-
"live_location_sharing.stopped": true
|
|
15061
|
-
// Reminder events
|
|
15062
|
-
"reminder.created": true,
|
|
15063
|
-
"reminder.updated": true,
|
|
15064
|
-
"reminder.deleted": true,
|
|
15065
|
-
"notification.reminder_due": true
|
|
15686
|
+
"live_location_sharing.stopped": true
|
|
15066
15687
|
};
|
|
15067
15688
|
|
|
15068
15689
|
// src/permissions.ts
|
|
@@ -16219,7 +16840,9 @@ export {
|
|
|
16219
16840
|
MergedStateStore,
|
|
16220
16841
|
MessageComposer,
|
|
16221
16842
|
MessageComposerMiddlewareExecutor,
|
|
16843
|
+
MessageDeliveryReporter,
|
|
16222
16844
|
MessageDraftComposerMiddlewareExecutor,
|
|
16845
|
+
MessageReceiptsTracker,
|
|
16223
16846
|
MessageSearchSource,
|
|
16224
16847
|
MiddlewareExecutor,
|
|
16225
16848
|
MinPriority,
|