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
|
@@ -36,6 +36,12 @@ var require_https = __commonJS({
|
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
// (disabled):node_modules/jsonwebtoken/index.js
|
|
40
|
+
var require_jsonwebtoken = __commonJS({
|
|
41
|
+
"(disabled):node_modules/jsonwebtoken/index.js"() {
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
39
45
|
// (disabled):crypto
|
|
40
46
|
var require_crypto = __commonJS({
|
|
41
47
|
"(disabled):crypto"() {
|
|
@@ -96,7 +102,9 @@ __export(index_exports, {
|
|
|
96
102
|
MergedStateStore: () => MergedStateStore,
|
|
97
103
|
MessageComposer: () => MessageComposer,
|
|
98
104
|
MessageComposerMiddlewareExecutor: () => MessageComposerMiddlewareExecutor,
|
|
105
|
+
MessageDeliveryReporter: () => MessageDeliveryReporter,
|
|
99
106
|
MessageDraftComposerMiddlewareExecutor: () => MessageDraftComposerMiddlewareExecutor,
|
|
107
|
+
MessageReceiptsTracker: () => MessageReceiptsTracker,
|
|
100
108
|
MessageSearchSource: () => MessageSearchSource,
|
|
101
109
|
MiddlewareExecutor: () => MiddlewareExecutor,
|
|
102
110
|
MinPriority: () => MinPriority,
|
|
@@ -1147,6 +1155,21 @@ var runDetached = (callback, options) => {
|
|
|
1147
1155
|
};
|
|
1148
1156
|
|
|
1149
1157
|
// src/channel_state.ts
|
|
1158
|
+
var messageSetBounds = (a, b) => ({
|
|
1159
|
+
newestMessageA: new Date(a[0]?.created_at ?? 0),
|
|
1160
|
+
oldestMessageA: new Date(a.slice(-1)[0]?.created_at ?? 0),
|
|
1161
|
+
newestMessageB: new Date(b[0]?.created_at ?? 0),
|
|
1162
|
+
oldestMessageB: new Date(b.slice(-1)[0]?.created_at ?? 0)
|
|
1163
|
+
});
|
|
1164
|
+
var aContainsOrEqualsB = (a, b) => {
|
|
1165
|
+
const { newestMessageA, newestMessageB, oldestMessageA, oldestMessageB } = messageSetBounds(a, b);
|
|
1166
|
+
return newestMessageA >= newestMessageB && oldestMessageB >= oldestMessageA;
|
|
1167
|
+
};
|
|
1168
|
+
var aOverlapsB = (a, b) => {
|
|
1169
|
+
const { newestMessageA, newestMessageB, oldestMessageA, oldestMessageB } = messageSetBounds(a, b);
|
|
1170
|
+
return oldestMessageA < oldestMessageB && oldestMessageB < newestMessageA && newestMessageA < newestMessageB;
|
|
1171
|
+
};
|
|
1172
|
+
var messageSetsOverlapByTimestamp = (a, b) => aContainsOrEqualsB(a, b) || aContainsOrEqualsB(b, a) || aOverlapsB(a, b) || aOverlapsB(b, a);
|
|
1150
1173
|
var ChannelState = class {
|
|
1151
1174
|
constructor(channel) {
|
|
1152
1175
|
/**
|
|
@@ -1225,6 +1248,22 @@ var ChannelState = class {
|
|
|
1225
1248
|
deletedAt: deletedAt ?? null
|
|
1226
1249
|
});
|
|
1227
1250
|
};
|
|
1251
|
+
/**
|
|
1252
|
+
* Identifies the set index into which a message set would pertain if its first item's creation date corresponded to oldestTimestampMs.
|
|
1253
|
+
* @param oldestTimestampMs
|
|
1254
|
+
*/
|
|
1255
|
+
this.findMessageSetByOldestTimestamp = (oldestTimestampMs) => {
|
|
1256
|
+
let lo = 0, hi = this.messageSets.length;
|
|
1257
|
+
while (lo < hi) {
|
|
1258
|
+
const mid = lo + hi >>> 1;
|
|
1259
|
+
const msgSet = this.messageSets[mid];
|
|
1260
|
+
if (msgSet.messages.length === 0) return -1;
|
|
1261
|
+
const oldestMessageTimestampInSet = msgSet.messages[0].created_at.getTime();
|
|
1262
|
+
if (oldestMessageTimestampInSet <= oldestTimestampMs) hi = mid;
|
|
1263
|
+
else lo = mid + 1;
|
|
1264
|
+
}
|
|
1265
|
+
return lo;
|
|
1266
|
+
};
|
|
1228
1267
|
this._channel = channel;
|
|
1229
1268
|
this.watcher_count = 0;
|
|
1230
1269
|
this.typing = {};
|
|
@@ -1750,6 +1789,25 @@ var ChannelState = class {
|
|
|
1750
1789
|
}
|
|
1751
1790
|
return this.messageSets[messageSetIndex].messages.find((m) => m.id === messageId);
|
|
1752
1791
|
}
|
|
1792
|
+
findMessageByTimestamp(timestampMs, parentMessageId, exactTsMatch = false) {
|
|
1793
|
+
if (parentMessageId && !this.threads[parentMessageId] || this.messageSets.length === 0)
|
|
1794
|
+
return null;
|
|
1795
|
+
const setIndex = this.findMessageSetByOldestTimestamp(timestampMs);
|
|
1796
|
+
const targetMsgSet = this.messageSets[setIndex]?.messages;
|
|
1797
|
+
if (!targetMsgSet?.length) return null;
|
|
1798
|
+
const firstMsgTimestamp = targetMsgSet[0].created_at.getTime();
|
|
1799
|
+
const lastMsgTimestamp = targetMsgSet.slice(-1)[0].created_at.getTime();
|
|
1800
|
+
const isOutOfBound = timestampMs < firstMsgTimestamp || lastMsgTimestamp < timestampMs;
|
|
1801
|
+
if (isOutOfBound && exactTsMatch) return null;
|
|
1802
|
+
let msgIndex = 0, hi = targetMsgSet.length - 1;
|
|
1803
|
+
while (msgIndex < hi) {
|
|
1804
|
+
const mid = msgIndex + hi >>> 1;
|
|
1805
|
+
if (timestampMs <= targetMsgSet[mid].created_at.getTime()) hi = mid;
|
|
1806
|
+
else msgIndex = mid + 1;
|
|
1807
|
+
}
|
|
1808
|
+
const foundMessage = targetMsgSet[msgIndex];
|
|
1809
|
+
return !exactTsMatch ? foundMessage : foundMessage.created_at.getTime() === timestampMs ? foundMessage : null;
|
|
1810
|
+
}
|
|
1753
1811
|
switchToMessageSet(index) {
|
|
1754
1812
|
const currentMessages = this.messageSets.find((s) => s.isCurrent);
|
|
1755
1813
|
if (!currentMessages) {
|
|
@@ -1769,35 +1827,77 @@ var ChannelState = class {
|
|
|
1769
1827
|
findTargetMessageSet(newMessages, addIfDoesNotExist = true, messageSetToAddToIfDoesNotExist = "current") {
|
|
1770
1828
|
let messagesToAdd = newMessages;
|
|
1771
1829
|
let targetMessageSetIndex;
|
|
1830
|
+
if (newMessages.length === 0)
|
|
1831
|
+
return { targetMessageSetIndex: 0, messagesToAdd: newMessages };
|
|
1772
1832
|
if (addIfDoesNotExist) {
|
|
1773
|
-
const
|
|
1833
|
+
const overlappingMessageSetIndicesByMsgIds = this.messageSets.map((_, i) => i).filter(
|
|
1774
1834
|
(i) => this.areMessageSetsOverlap(this.messageSets[i].messages, newMessages)
|
|
1775
1835
|
);
|
|
1836
|
+
const overlappingMessageSetIndicesByTimestamp = this.messageSets.map((_, i) => i).filter(
|
|
1837
|
+
(i) => messageSetsOverlapByTimestamp(
|
|
1838
|
+
this.messageSets[i].messages,
|
|
1839
|
+
newMessages.map(formatMessage)
|
|
1840
|
+
)
|
|
1841
|
+
);
|
|
1776
1842
|
switch (messageSetToAddToIfDoesNotExist) {
|
|
1777
1843
|
case "new":
|
|
1778
|
-
if (
|
|
1779
|
-
targetMessageSetIndex =
|
|
1844
|
+
if (overlappingMessageSetIndicesByMsgIds.length > 0) {
|
|
1845
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByMsgIds[0];
|
|
1846
|
+
} else if (overlappingMessageSetIndicesByTimestamp.length > 0) {
|
|
1847
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByTimestamp[0];
|
|
1780
1848
|
} else if (newMessages.some((m) => !m.parent_id)) {
|
|
1781
|
-
this.
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1849
|
+
const setIngestIndex = this.findMessageSetByOldestTimestamp(
|
|
1850
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1851
|
+
new Date(newMessages[0].created_at).getTime()
|
|
1852
|
+
);
|
|
1853
|
+
if (setIngestIndex === -1) {
|
|
1854
|
+
this.messageSets.push({
|
|
1855
|
+
messages: [],
|
|
1856
|
+
isCurrent: false,
|
|
1857
|
+
isLatest: false,
|
|
1858
|
+
pagination: DEFAULT_MESSAGE_SET_PAGINATION
|
|
1859
|
+
});
|
|
1860
|
+
targetMessageSetIndex = this.messageSets.length - 1;
|
|
1861
|
+
} else {
|
|
1862
|
+
const isLatest = setIngestIndex === 0;
|
|
1863
|
+
this.messageSets.splice(setIngestIndex, 0, {
|
|
1864
|
+
messages: [],
|
|
1865
|
+
isCurrent: false,
|
|
1866
|
+
isLatest,
|
|
1867
|
+
pagination: DEFAULT_MESSAGE_SET_PAGINATION
|
|
1868
|
+
// fixme: it is problematic decide about pagination without having data
|
|
1869
|
+
});
|
|
1870
|
+
if (isLatest) {
|
|
1871
|
+
this.messageSets.slice(1).forEach((set) => {
|
|
1872
|
+
set.isLatest = false;
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
targetMessageSetIndex = setIngestIndex;
|
|
1876
|
+
}
|
|
1788
1877
|
}
|
|
1789
1878
|
break;
|
|
1790
1879
|
case "current":
|
|
1791
|
-
|
|
1880
|
+
if (overlappingMessageSetIndicesByTimestamp.length > 0) {
|
|
1881
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByTimestamp[0];
|
|
1882
|
+
} else {
|
|
1883
|
+
targetMessageSetIndex = this.messageSets.findIndex((s) => s.isCurrent);
|
|
1884
|
+
}
|
|
1792
1885
|
break;
|
|
1793
1886
|
case "latest":
|
|
1794
|
-
|
|
1887
|
+
if (overlappingMessageSetIndicesByTimestamp.length > 0) {
|
|
1888
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByTimestamp[0];
|
|
1889
|
+
} else {
|
|
1890
|
+
targetMessageSetIndex = this.messageSets.findIndex((s) => s.isLatest);
|
|
1891
|
+
}
|
|
1795
1892
|
break;
|
|
1796
1893
|
default:
|
|
1797
1894
|
targetMessageSetIndex = -1;
|
|
1798
1895
|
}
|
|
1799
|
-
const mergeTargetMessageSetIndex =
|
|
1800
|
-
|
|
1896
|
+
const mergeTargetMessageSetIndex = overlappingMessageSetIndicesByMsgIds.splice(
|
|
1897
|
+
0,
|
|
1898
|
+
1
|
|
1899
|
+
)[0];
|
|
1900
|
+
const mergeSourceMessageSetIndices = [...overlappingMessageSetIndicesByMsgIds];
|
|
1801
1901
|
if (mergeTargetMessageSetIndex !== void 0 && mergeTargetMessageSetIndex !== targetMessageSetIndex) {
|
|
1802
1902
|
mergeSourceMessageSetIndices.push(targetMessageSetIndex);
|
|
1803
1903
|
}
|
|
@@ -6330,7 +6430,7 @@ var Thread = class extends WithSubscriptions {
|
|
|
6330
6430
|
if (this.ownUnreadCount === 0 && !force) {
|
|
6331
6431
|
return null;
|
|
6332
6432
|
}
|
|
6333
|
-
return await this.
|
|
6433
|
+
return await this.client.messageDeliveryReporter.markRead(this);
|
|
6334
6434
|
};
|
|
6335
6435
|
this.throttledMarkAsRead = throttle(
|
|
6336
6436
|
() => this.markAsRead(),
|
|
@@ -7060,6 +7160,444 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
|
|
|
7060
7160
|
_MessageComposer.generateId = generateUUIDv4;
|
|
7061
7161
|
var MessageComposer = _MessageComposer;
|
|
7062
7162
|
|
|
7163
|
+
// src/messageDelivery/MessageDeliveryReporter.ts
|
|
7164
|
+
var MAX_DELIVERED_MESSAGE_COUNT_IN_PAYLOAD = 100;
|
|
7165
|
+
var MARK_AS_DELIVERED_BUFFER_TIMEOUT = 1e3;
|
|
7166
|
+
var MARK_AS_READ_THROTTLE_TIMEOUT2 = 1e3;
|
|
7167
|
+
var isChannel = (item) => item instanceof Channel;
|
|
7168
|
+
var isThread = (item) => item instanceof Thread;
|
|
7169
|
+
var MessageDeliveryReporter = class {
|
|
7170
|
+
constructor({ client }) {
|
|
7171
|
+
this.deliveryReportCandidates = /* @__PURE__ */ new Map();
|
|
7172
|
+
this.nextDeliveryReportCandidates = /* @__PURE__ */ new Map();
|
|
7173
|
+
this.markDeliveredRequestPromise = null;
|
|
7174
|
+
this.markDeliveredTimeout = null;
|
|
7175
|
+
/**
|
|
7176
|
+
* Retrieve the reference to the latest message in the state that is nor read neither reported as delivered
|
|
7177
|
+
* @param collection
|
|
7178
|
+
*/
|
|
7179
|
+
this.getNextDeliveryReportCandidate = (collection) => {
|
|
7180
|
+
const ownUserId = this.client.user?.id;
|
|
7181
|
+
if (!ownUserId) return;
|
|
7182
|
+
let latestMessages = [];
|
|
7183
|
+
let lastDeliveredAt;
|
|
7184
|
+
let lastReadAt;
|
|
7185
|
+
let key = void 0;
|
|
7186
|
+
if (isChannel(collection)) {
|
|
7187
|
+
latestMessages = collection.state.latestMessages;
|
|
7188
|
+
const ownReadState = collection.state.read[ownUserId] ?? {};
|
|
7189
|
+
lastReadAt = ownReadState?.last_read;
|
|
7190
|
+
lastDeliveredAt = ownReadState?.last_delivered_at;
|
|
7191
|
+
key = collection.cid;
|
|
7192
|
+
} else if (isThread(collection)) {
|
|
7193
|
+
latestMessages = collection.state.getLatestValue().replies;
|
|
7194
|
+
const ownReadState = collection.state.getLatestValue().read[ownUserId] ?? {};
|
|
7195
|
+
lastReadAt = ownReadState?.lastReadAt;
|
|
7196
|
+
lastDeliveredAt = ownReadState?.lastDeliveredAt;
|
|
7197
|
+
key = `${collection.channel.cid}:${collection.id}`;
|
|
7198
|
+
return;
|
|
7199
|
+
} else {
|
|
7200
|
+
return;
|
|
7201
|
+
}
|
|
7202
|
+
if (!key) return;
|
|
7203
|
+
const [latestMessage] = latestMessages.slice(-1);
|
|
7204
|
+
const wholeCollectionIsRead = !latestMessage || lastReadAt >= latestMessage.created_at;
|
|
7205
|
+
if (wholeCollectionIsRead) return { key, id: null };
|
|
7206
|
+
const wholeCollectionIsMarkedDelivered = !latestMessage || (lastDeliveredAt ?? 0) >= latestMessage.created_at;
|
|
7207
|
+
if (wholeCollectionIsMarkedDelivered) return { key, id: null };
|
|
7208
|
+
return { key, id: latestMessage.id || null };
|
|
7209
|
+
};
|
|
7210
|
+
/**
|
|
7211
|
+
* Fires delivery announcement request followed by immediate delivery candidate buffer reset.
|
|
7212
|
+
* @param options
|
|
7213
|
+
*/
|
|
7214
|
+
this.announceDelivery = (options) => {
|
|
7215
|
+
if (this.markDeliveredRequestInFlight || !this.hasDeliveryCandidates) return;
|
|
7216
|
+
const { latest_delivered_messages, sendBuffer } = this.confirmationsFromDeliveryReportCandidates();
|
|
7217
|
+
if (!latest_delivered_messages.length) return;
|
|
7218
|
+
const payload = { ...options, latest_delivered_messages };
|
|
7219
|
+
const postFlightReconcile = () => {
|
|
7220
|
+
this.markDeliveredRequestPromise = null;
|
|
7221
|
+
for (const [k, v] of this.nextDeliveryReportCandidates.entries()) {
|
|
7222
|
+
this.deliveryReportCandidates.set(k, v);
|
|
7223
|
+
}
|
|
7224
|
+
this.nextDeliveryReportCandidates = /* @__PURE__ */ new Map();
|
|
7225
|
+
this.announceDeliveryBuffered(options);
|
|
7226
|
+
};
|
|
7227
|
+
const handleError = () => {
|
|
7228
|
+
for (const [k, v] of Object.entries(sendBuffer)) {
|
|
7229
|
+
if (!this.deliveryReportCandidates.has(k)) {
|
|
7230
|
+
this.deliveryReportCandidates.set(k, v);
|
|
7231
|
+
}
|
|
7232
|
+
}
|
|
7233
|
+
postFlightReconcile();
|
|
7234
|
+
};
|
|
7235
|
+
this.markDeliveredRequestPromise = this.client.markChannelsDelivered(payload).then(postFlightReconcile, handleError);
|
|
7236
|
+
};
|
|
7237
|
+
this.announceDeliveryBuffered = (options) => {
|
|
7238
|
+
if (this.hasTimer || this.markDeliveredRequestInFlight || !this.hasDeliveryCandidates)
|
|
7239
|
+
return;
|
|
7240
|
+
this.markDeliveredTimeout = setTimeout(() => {
|
|
7241
|
+
this.markDeliveredTimeout = null;
|
|
7242
|
+
this.announceDelivery(options);
|
|
7243
|
+
}, MARK_AS_DELIVERED_BUFFER_TIMEOUT);
|
|
7244
|
+
};
|
|
7245
|
+
/**
|
|
7246
|
+
* Delegates the mark-read call to the Channel or Thread instance
|
|
7247
|
+
* @param collection
|
|
7248
|
+
* @param options
|
|
7249
|
+
*/
|
|
7250
|
+
this.markRead = async (collection, options) => {
|
|
7251
|
+
let result = null;
|
|
7252
|
+
if (isChannel(collection)) {
|
|
7253
|
+
result = await collection.markAsReadRequest(options);
|
|
7254
|
+
} else if (isThread(collection)) {
|
|
7255
|
+
result = await collection.channel.markAsReadRequest({
|
|
7256
|
+
...options,
|
|
7257
|
+
thread_id: collection.id
|
|
7258
|
+
});
|
|
7259
|
+
}
|
|
7260
|
+
this.removeCandidateFor(collection);
|
|
7261
|
+
return result;
|
|
7262
|
+
};
|
|
7263
|
+
/**
|
|
7264
|
+
* Throttles the MessageDeliveryReporter.markRead call
|
|
7265
|
+
* @param collection
|
|
7266
|
+
* @param options
|
|
7267
|
+
*/
|
|
7268
|
+
this.throttledMarkRead = throttle(this.markRead, MARK_AS_READ_THROTTLE_TIMEOUT2, {
|
|
7269
|
+
leading: false,
|
|
7270
|
+
trailing: true
|
|
7271
|
+
});
|
|
7272
|
+
this.client = client;
|
|
7273
|
+
}
|
|
7274
|
+
get markDeliveredRequestInFlight() {
|
|
7275
|
+
return this.markDeliveredRequestPromise !== null;
|
|
7276
|
+
}
|
|
7277
|
+
get hasTimer() {
|
|
7278
|
+
return this.markDeliveredTimeout !== null;
|
|
7279
|
+
}
|
|
7280
|
+
get hasDeliveryCandidates() {
|
|
7281
|
+
return this.deliveryReportCandidates.size > 0;
|
|
7282
|
+
}
|
|
7283
|
+
/**
|
|
7284
|
+
* Build latest_delivered_messages payload from an arbitrary buffer (deliveryReportCandidates / nextDeliveryReportCandidates)
|
|
7285
|
+
*/
|
|
7286
|
+
confirmationsFrom(map2) {
|
|
7287
|
+
return Array.from(map2.entries()).map(([key, messageId]) => {
|
|
7288
|
+
const [type, id, parent_id] = key.split(":");
|
|
7289
|
+
return parent_id ? { cid: `${type}:${id}`, id: messageId, parent_id } : { cid: key, id: messageId };
|
|
7290
|
+
});
|
|
7291
|
+
}
|
|
7292
|
+
confirmationsFromDeliveryReportCandidates() {
|
|
7293
|
+
const entries = Array.from(this.deliveryReportCandidates);
|
|
7294
|
+
const sendBuffer = new Map(entries.slice(0, MAX_DELIVERED_MESSAGE_COUNT_IN_PAYLOAD));
|
|
7295
|
+
this.deliveryReportCandidates = new Map(
|
|
7296
|
+
entries.slice(MAX_DELIVERED_MESSAGE_COUNT_IN_PAYLOAD)
|
|
7297
|
+
);
|
|
7298
|
+
return { latest_delivered_messages: this.confirmationsFrom(sendBuffer), sendBuffer };
|
|
7299
|
+
}
|
|
7300
|
+
/**
|
|
7301
|
+
* Generate candidate key for storing in the candidates buffer
|
|
7302
|
+
* @param collection
|
|
7303
|
+
* @private
|
|
7304
|
+
*/
|
|
7305
|
+
candidateKeyFor(collection) {
|
|
7306
|
+
if (isChannel(collection)) return collection.cid;
|
|
7307
|
+
if (isThread(collection)) return `${collection.channel.cid}:${collection.id}`;
|
|
7308
|
+
}
|
|
7309
|
+
/**
|
|
7310
|
+
* Updates the delivery candidates buffer with the latest delivery candidates
|
|
7311
|
+
* @param collection
|
|
7312
|
+
*/
|
|
7313
|
+
trackDeliveredCandidate(collection) {
|
|
7314
|
+
if (isChannel(collection) && !collection.getConfig()?.read_events) return;
|
|
7315
|
+
if (isThread(collection) && !collection.channel.getConfig()?.read_events) return;
|
|
7316
|
+
const candidate = this.getNextDeliveryReportCandidate(collection);
|
|
7317
|
+
if (!candidate?.key) return;
|
|
7318
|
+
const buffer = this.markDeliveredRequestInFlight ? this.nextDeliveryReportCandidates : this.deliveryReportCandidates;
|
|
7319
|
+
if (candidate.id === null) buffer.delete(candidate.key);
|
|
7320
|
+
else buffer.set(candidate.key, candidate.id);
|
|
7321
|
+
}
|
|
7322
|
+
/**
|
|
7323
|
+
* Removes candidate from the delivery report buffer
|
|
7324
|
+
* @param collection
|
|
7325
|
+
* @private
|
|
7326
|
+
*/
|
|
7327
|
+
removeCandidateFor(collection) {
|
|
7328
|
+
const candidateKey = this.candidateKeyFor(collection);
|
|
7329
|
+
if (!candidateKey) return;
|
|
7330
|
+
this.deliveryReportCandidates.delete(candidateKey);
|
|
7331
|
+
this.nextDeliveryReportCandidates.delete(candidateKey);
|
|
7332
|
+
}
|
|
7333
|
+
/**
|
|
7334
|
+
* Records the latest message delivered for Channel or Thread instances and schedules the next report
|
|
7335
|
+
* if not already scheduled and candidates exist.
|
|
7336
|
+
* Should be used for WS handling (message.new) as well as for ingesting HTTP channel query results.
|
|
7337
|
+
* @param collections
|
|
7338
|
+
*/
|
|
7339
|
+
syncDeliveredCandidates(collections) {
|
|
7340
|
+
if (this.client.user?.privacy_settings?.delivery_receipts?.enabled === false) return;
|
|
7341
|
+
for (const c of collections) this.trackDeliveredCandidate(c);
|
|
7342
|
+
this.announceDeliveryBuffered();
|
|
7343
|
+
}
|
|
7344
|
+
};
|
|
7345
|
+
|
|
7346
|
+
// src/messageDelivery/MessageReceiptsTracker.ts
|
|
7347
|
+
var MIN_REF = { timestampMs: Number.NEGATIVE_INFINITY, msgId: "" };
|
|
7348
|
+
var compareRefsAsc = (a, b) => a.timestampMs !== b.timestampMs ? a.timestampMs - b.timestampMs : 0;
|
|
7349
|
+
var findIndex = (arr, target, keyOf) => {
|
|
7350
|
+
let lo = 0, hi = arr.length;
|
|
7351
|
+
while (lo < hi) {
|
|
7352
|
+
const mid = lo + hi >>> 1;
|
|
7353
|
+
if (compareRefsAsc(keyOf(arr[mid]), target) >= 0) hi = mid;
|
|
7354
|
+
else lo = mid + 1;
|
|
7355
|
+
}
|
|
7356
|
+
return lo;
|
|
7357
|
+
};
|
|
7358
|
+
var findUpperIndex = (arr, target, keyOf) => {
|
|
7359
|
+
let lo = 0, hi = arr.length;
|
|
7360
|
+
while (lo < hi) {
|
|
7361
|
+
const mid = lo + hi >>> 1;
|
|
7362
|
+
if (compareRefsAsc(keyOf(arr[mid]), target) > 0) hi = mid;
|
|
7363
|
+
else lo = mid + 1;
|
|
7364
|
+
}
|
|
7365
|
+
return lo;
|
|
7366
|
+
};
|
|
7367
|
+
var insertByKey = (arr, item, keyOf) => arr.splice(findUpperIndex(arr, keyOf(item), keyOf), 0, item);
|
|
7368
|
+
var removeByOldKey = (arr, item, oldKey, keyOf) => {
|
|
7369
|
+
let i = findIndex(arr, oldKey, keyOf);
|
|
7370
|
+
while (i < arr.length && compareRefsAsc(keyOf(arr[i]), oldKey) === 0) {
|
|
7371
|
+
if (arr[i].user.id === item.user.id) {
|
|
7372
|
+
arr.splice(i, 1);
|
|
7373
|
+
return;
|
|
7374
|
+
}
|
|
7375
|
+
i++;
|
|
7376
|
+
}
|
|
7377
|
+
};
|
|
7378
|
+
var MessageReceiptsTracker = class {
|
|
7379
|
+
constructor({ locateMessage }) {
|
|
7380
|
+
this.byUser = /* @__PURE__ */ new Map();
|
|
7381
|
+
this.readSorted = [];
|
|
7382
|
+
// asc by lastReadRef
|
|
7383
|
+
this.deliveredSorted = [];
|
|
7384
|
+
this.locateMessage = locateMessage;
|
|
7385
|
+
}
|
|
7386
|
+
/** Build initial state from server snapshots (single pass + sort). */
|
|
7387
|
+
ingestInitial(responses) {
|
|
7388
|
+
this.byUser.clear();
|
|
7389
|
+
this.readSorted = [];
|
|
7390
|
+
this.deliveredSorted = [];
|
|
7391
|
+
for (const r of responses) {
|
|
7392
|
+
const lastReadTimestamp = r.last_read ? new Date(r.last_read).getTime() : null;
|
|
7393
|
+
const lastDeliveredTimestamp = r.last_delivered_at ? new Date(r.last_delivered_at).getTime() : null;
|
|
7394
|
+
const lastReadRef = lastReadTimestamp ? this.locateMessage(lastReadTimestamp) ?? MIN_REF : MIN_REF;
|
|
7395
|
+
let lastDeliveredRef = lastDeliveredTimestamp ? this.locateMessage(lastDeliveredTimestamp) ?? MIN_REF : MIN_REF;
|
|
7396
|
+
const isReadAfterDelivered = compareRefsAsc(lastDeliveredRef, lastReadRef) < 0;
|
|
7397
|
+
if (isReadAfterDelivered) lastDeliveredRef = lastReadRef;
|
|
7398
|
+
const userProgress = { user: r.user, lastReadRef, lastDeliveredRef };
|
|
7399
|
+
this.byUser.set(r.user.id, userProgress);
|
|
7400
|
+
this.readSorted.splice(
|
|
7401
|
+
findIndex(this.readSorted, lastReadRef, (up) => up.lastReadRef),
|
|
7402
|
+
0,
|
|
7403
|
+
userProgress
|
|
7404
|
+
);
|
|
7405
|
+
this.deliveredSorted.splice(
|
|
7406
|
+
findIndex(this.deliveredSorted, lastDeliveredRef, (up) => up.lastDeliveredRef),
|
|
7407
|
+
0,
|
|
7408
|
+
userProgress
|
|
7409
|
+
);
|
|
7410
|
+
}
|
|
7411
|
+
}
|
|
7412
|
+
/** message.delivered — user device confirmed delivery up to and including messageId. */
|
|
7413
|
+
onMessageDelivered({
|
|
7414
|
+
user,
|
|
7415
|
+
deliveredAt,
|
|
7416
|
+
lastDeliveredMessageId
|
|
7417
|
+
}) {
|
|
7418
|
+
const timestampMs = new Date(deliveredAt).getTime();
|
|
7419
|
+
const msgRef = lastDeliveredMessageId ? { timestampMs, msgId: lastDeliveredMessageId } : this.locateMessage(new Date(deliveredAt).getTime());
|
|
7420
|
+
if (!msgRef) return;
|
|
7421
|
+
const userProgress = this.ensureUser(user);
|
|
7422
|
+
const newDelivered = compareRefsAsc(msgRef, userProgress.lastReadRef) < 0 ? userProgress.lastReadRef : msgRef;
|
|
7423
|
+
if (compareRefsAsc(newDelivered, userProgress.lastDeliveredRef) <= 0) return;
|
|
7424
|
+
removeByOldKey(
|
|
7425
|
+
this.deliveredSorted,
|
|
7426
|
+
userProgress,
|
|
7427
|
+
userProgress.lastDeliveredRef,
|
|
7428
|
+
(x) => x.lastDeliveredRef
|
|
7429
|
+
);
|
|
7430
|
+
userProgress.lastDeliveredRef = newDelivered;
|
|
7431
|
+
insertByKey(this.deliveredSorted, userProgress, (x) => x.lastDeliveredRef);
|
|
7432
|
+
}
|
|
7433
|
+
/** message.read — user read up to and including messageId. */
|
|
7434
|
+
onMessageRead({
|
|
7435
|
+
user,
|
|
7436
|
+
readAt,
|
|
7437
|
+
lastReadMessageId
|
|
7438
|
+
}) {
|
|
7439
|
+
const timestampMs = new Date(readAt).getTime();
|
|
7440
|
+
const msgRef = lastReadMessageId ? { timestampMs, msgId: lastReadMessageId } : this.locateMessage(timestampMs);
|
|
7441
|
+
if (!msgRef) return;
|
|
7442
|
+
const userProgress = this.ensureUser(user);
|
|
7443
|
+
if (compareRefsAsc(msgRef, userProgress.lastReadRef) <= 0) return;
|
|
7444
|
+
removeByOldKey(
|
|
7445
|
+
this.readSorted,
|
|
7446
|
+
userProgress,
|
|
7447
|
+
userProgress.lastReadRef,
|
|
7448
|
+
(x) => x.lastReadRef
|
|
7449
|
+
);
|
|
7450
|
+
userProgress.lastReadRef = msgRef;
|
|
7451
|
+
insertByKey(this.readSorted, userProgress, (x) => x.lastReadRef);
|
|
7452
|
+
if (compareRefsAsc(userProgress.lastDeliveredRef, userProgress.lastReadRef) < 0) {
|
|
7453
|
+
removeByOldKey(
|
|
7454
|
+
this.deliveredSorted,
|
|
7455
|
+
userProgress,
|
|
7456
|
+
userProgress.lastDeliveredRef,
|
|
7457
|
+
(x) => x.lastDeliveredRef
|
|
7458
|
+
);
|
|
7459
|
+
userProgress.lastDeliveredRef = userProgress.lastReadRef;
|
|
7460
|
+
insertByKey(this.deliveredSorted, userProgress, (x) => x.lastDeliveredRef);
|
|
7461
|
+
}
|
|
7462
|
+
}
|
|
7463
|
+
/** notification.mark_unread — user marked messages unread starting at `first_unread_message_id`.
|
|
7464
|
+
* Sets lastReadRef to the event’s last_read_* values. Delivery never moves backward.
|
|
7465
|
+
* The event is sent only to the user that triggered the action (own user), so we will never adjust read ref
|
|
7466
|
+
* for other users - we will not see changes in the UI for other users. However, this implementation does not
|
|
7467
|
+
* take into consideration this fact and is ready to handle the mark-unread event for any user.
|
|
7468
|
+
*/
|
|
7469
|
+
onNotificationMarkUnread({
|
|
7470
|
+
user,
|
|
7471
|
+
lastReadAt,
|
|
7472
|
+
lastReadMessageId
|
|
7473
|
+
}) {
|
|
7474
|
+
const userProgress = this.ensureUser(user);
|
|
7475
|
+
const newReadRef = lastReadAt ? { timestampMs: new Date(lastReadAt).getTime(), msgId: lastReadMessageId ?? "" } : { ...MIN_REF };
|
|
7476
|
+
if (compareRefsAsc(newReadRef, userProgress.lastReadRef) === 0 && newReadRef.msgId === userProgress.lastReadRef.msgId) {
|
|
7477
|
+
return;
|
|
7478
|
+
}
|
|
7479
|
+
removeByOldKey(
|
|
7480
|
+
this.readSorted,
|
|
7481
|
+
userProgress,
|
|
7482
|
+
userProgress.lastReadRef,
|
|
7483
|
+
(x) => x.lastReadRef
|
|
7484
|
+
);
|
|
7485
|
+
userProgress.lastReadRef = newReadRef;
|
|
7486
|
+
insertByKey(this.readSorted, userProgress, (x) => x.lastReadRef);
|
|
7487
|
+
if (compareRefsAsc(userProgress.lastDeliveredRef, userProgress.lastReadRef) < 0) {
|
|
7488
|
+
removeByOldKey(
|
|
7489
|
+
this.deliveredSorted,
|
|
7490
|
+
userProgress,
|
|
7491
|
+
userProgress.lastDeliveredRef,
|
|
7492
|
+
(x) => x.lastDeliveredRef
|
|
7493
|
+
);
|
|
7494
|
+
userProgress.lastDeliveredRef = userProgress.lastReadRef;
|
|
7495
|
+
insertByKey(this.deliveredSorted, userProgress, (x) => x.lastDeliveredRef);
|
|
7496
|
+
}
|
|
7497
|
+
}
|
|
7498
|
+
/** All users who READ this message. */
|
|
7499
|
+
readersForMessage(msgRef) {
|
|
7500
|
+
const index = findIndex(this.readSorted, msgRef, ({ lastReadRef }) => lastReadRef);
|
|
7501
|
+
return this.readSorted.slice(index).map((x) => x.user);
|
|
7502
|
+
}
|
|
7503
|
+
/** All users who have it DELIVERED (includes readers). */
|
|
7504
|
+
deliveredForMessage(msgRef) {
|
|
7505
|
+
const pos = findIndex(
|
|
7506
|
+
this.deliveredSorted,
|
|
7507
|
+
msgRef,
|
|
7508
|
+
({ lastDeliveredRef }) => lastDeliveredRef
|
|
7509
|
+
);
|
|
7510
|
+
return this.deliveredSorted.slice(pos).map((x) => x.user);
|
|
7511
|
+
}
|
|
7512
|
+
/** Users who delivered but have NOT read. */
|
|
7513
|
+
deliveredNotReadForMessage(msgRef) {
|
|
7514
|
+
const pos = findIndex(
|
|
7515
|
+
this.deliveredSorted,
|
|
7516
|
+
msgRef,
|
|
7517
|
+
({ lastDeliveredRef }) => lastDeliveredRef
|
|
7518
|
+
);
|
|
7519
|
+
const usersDeliveredNotRead = [];
|
|
7520
|
+
for (let i = pos; i < this.deliveredSorted.length; i++) {
|
|
7521
|
+
const userProgress = this.deliveredSorted[i];
|
|
7522
|
+
if (compareRefsAsc(userProgress.lastReadRef, msgRef) < 0)
|
|
7523
|
+
usersDeliveredNotRead.push(userProgress.user);
|
|
7524
|
+
}
|
|
7525
|
+
return usersDeliveredNotRead;
|
|
7526
|
+
}
|
|
7527
|
+
/** Users for whom `msgRef` is their *last read* (exact match). */
|
|
7528
|
+
usersWhoseLastReadIs(msgRef) {
|
|
7529
|
+
if (!msgRef.msgId) return [];
|
|
7530
|
+
const start = findIndex(this.readSorted, msgRef, (x) => x.lastReadRef);
|
|
7531
|
+
const end = findUpperIndex(this.readSorted, msgRef, (x) => x.lastReadRef);
|
|
7532
|
+
const users = [];
|
|
7533
|
+
for (let i = start; i < end; i++) {
|
|
7534
|
+
const up = this.readSorted[i];
|
|
7535
|
+
if (up.lastReadRef.msgId === msgRef.msgId) users.push(up.user);
|
|
7536
|
+
}
|
|
7537
|
+
return users;
|
|
7538
|
+
}
|
|
7539
|
+
/** Users for whom `msgRef` is their *last delivered* (exact match). */
|
|
7540
|
+
usersWhoseLastDeliveredIs(msgRef) {
|
|
7541
|
+
if (!msgRef.msgId) return [];
|
|
7542
|
+
const start = findIndex(this.deliveredSorted, msgRef, (x) => x.lastDeliveredRef);
|
|
7543
|
+
const end = findUpperIndex(this.deliveredSorted, msgRef, (x) => x.lastDeliveredRef);
|
|
7544
|
+
const users = [];
|
|
7545
|
+
for (let i = start; i < end; i++) {
|
|
7546
|
+
const up = this.deliveredSorted[i];
|
|
7547
|
+
if (up.lastDeliveredRef.msgId === msgRef.msgId) users.push(up.user);
|
|
7548
|
+
}
|
|
7549
|
+
return users;
|
|
7550
|
+
}
|
|
7551
|
+
// ---- queries: per-user status ----
|
|
7552
|
+
hasUserRead(msgRef, userId) {
|
|
7553
|
+
const up = this.byUser.get(userId);
|
|
7554
|
+
return !!up && compareRefsAsc(up.lastReadRef, msgRef) >= 0;
|
|
7555
|
+
}
|
|
7556
|
+
hasUserDelivered(msgRef, userId) {
|
|
7557
|
+
const up = this.byUser.get(userId);
|
|
7558
|
+
return !!up && compareRefsAsc(up.lastDeliveredRef, msgRef) >= 0;
|
|
7559
|
+
}
|
|
7560
|
+
getUserProgress(userId) {
|
|
7561
|
+
const userProgress = this.byUser.get(userId);
|
|
7562
|
+
if (!userProgress) return null;
|
|
7563
|
+
return userProgress;
|
|
7564
|
+
}
|
|
7565
|
+
groupUsersByLastReadMessage() {
|
|
7566
|
+
return Array.from(this.byUser.values()).reduce(
|
|
7567
|
+
(acc, userProgress) => {
|
|
7568
|
+
const msgId = userProgress.lastReadRef.msgId;
|
|
7569
|
+
if (!msgId) return acc;
|
|
7570
|
+
if (!acc[msgId]) acc[msgId] = [];
|
|
7571
|
+
acc[msgId].push(userProgress.user);
|
|
7572
|
+
return acc;
|
|
7573
|
+
},
|
|
7574
|
+
{}
|
|
7575
|
+
);
|
|
7576
|
+
}
|
|
7577
|
+
groupUsersByLastDeliveredMessage() {
|
|
7578
|
+
return Array.from(this.byUser.values()).reduce(
|
|
7579
|
+
(acc, userProgress) => {
|
|
7580
|
+
const msgId = userProgress.lastDeliveredRef.msgId;
|
|
7581
|
+
if (!msgId) return acc;
|
|
7582
|
+
if (!acc[msgId]) acc[msgId] = [];
|
|
7583
|
+
acc[msgId].push(userProgress.user);
|
|
7584
|
+
return acc;
|
|
7585
|
+
},
|
|
7586
|
+
{}
|
|
7587
|
+
);
|
|
7588
|
+
}
|
|
7589
|
+
ensureUser(user) {
|
|
7590
|
+
let up = this.byUser.get(user.id);
|
|
7591
|
+
if (!up) {
|
|
7592
|
+
up = { user, lastReadRef: MIN_REF, lastDeliveredRef: MIN_REF };
|
|
7593
|
+
this.byUser.set(user.id, up);
|
|
7594
|
+
insertByKey(this.readSorted, up, (x) => x.lastReadRef);
|
|
7595
|
+
insertByKey(this.deliveredSorted, up, (x) => x.lastDeliveredRef);
|
|
7596
|
+
}
|
|
7597
|
+
return up;
|
|
7598
|
+
}
|
|
7599
|
+
};
|
|
7600
|
+
|
|
7063
7601
|
// src/channel.ts
|
|
7064
7602
|
var Channel = class {
|
|
7065
7603
|
/**
|
|
@@ -7141,6 +7679,12 @@ var Channel = class {
|
|
|
7141
7679
|
client: this._client,
|
|
7142
7680
|
compositionContext: this
|
|
7143
7681
|
});
|
|
7682
|
+
this.messageReceiptsTracker = new MessageReceiptsTracker({
|
|
7683
|
+
locateMessage: (timestampMs) => {
|
|
7684
|
+
const msg = this.state.findMessageByTimestamp(timestampMs);
|
|
7685
|
+
return msg && { timestampMs, msgId: msg.id };
|
|
7686
|
+
}
|
|
7687
|
+
});
|
|
7144
7688
|
}
|
|
7145
7689
|
/**
|
|
7146
7690
|
* getClient - Get the chat client for this channel. If client.disconnect() was called, this function will error
|
|
@@ -7950,15 +8494,24 @@ var Channel = class {
|
|
|
7950
8494
|
return messageSlice[0];
|
|
7951
8495
|
}
|
|
7952
8496
|
/**
|
|
7953
|
-
* markRead - Send the mark read event for this user, only works if the `read_events` setting is enabled
|
|
8497
|
+
* 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.
|
|
7954
8498
|
*
|
|
7955
8499
|
* @param {MarkReadOptions} data
|
|
7956
8500
|
* @return {Promise<EventAPIResponse | null>} Description
|
|
7957
8501
|
*/
|
|
7958
8502
|
async markRead(data = {}) {
|
|
8503
|
+
return await this.getClient().messageDeliveryReporter.markRead(this, data);
|
|
8504
|
+
}
|
|
8505
|
+
/**
|
|
8506
|
+
* markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is enabled
|
|
8507
|
+
*
|
|
8508
|
+
* @param {MarkReadOptions} data
|
|
8509
|
+
* @return {Promise<EventAPIResponse | null>} Description
|
|
8510
|
+
*/
|
|
8511
|
+
async markAsReadRequest(data = {}) {
|
|
7959
8512
|
this._checkInitialized();
|
|
7960
8513
|
if (!this.getConfig()?.read_events && !this.getClient()._isUsingServerAuth()) {
|
|
7961
|
-
return
|
|
8514
|
+
return null;
|
|
7962
8515
|
}
|
|
7963
8516
|
return await this.getClient().post(this._channelURL() + "/read", {
|
|
7964
8517
|
...data
|
|
@@ -8266,6 +8819,7 @@ var Channel = class {
|
|
|
8266
8819
|
}),
|
|
8267
8820
|
{ method: "upsertChannels" }
|
|
8268
8821
|
);
|
|
8822
|
+
this.getClient().syncDeliveredCandidates([this]);
|
|
8269
8823
|
return state;
|
|
8270
8824
|
}
|
|
8271
8825
|
/**
|
|
@@ -8537,17 +9091,44 @@ var Channel = class {
|
|
|
8537
9091
|
break;
|
|
8538
9092
|
case "message.read":
|
|
8539
9093
|
if (event.user?.id && event.created_at) {
|
|
9094
|
+
const previousReadState = channelState.read[event.user.id];
|
|
8540
9095
|
channelState.read[event.user.id] = {
|
|
9096
|
+
// in case we already have delivery information
|
|
9097
|
+
...previousReadState,
|
|
8541
9098
|
last_read: new Date(event.created_at),
|
|
8542
9099
|
last_read_message_id: event.last_read_message_id,
|
|
8543
9100
|
user: event.user,
|
|
8544
9101
|
unread_messages: 0
|
|
8545
9102
|
};
|
|
8546
|
-
|
|
9103
|
+
this.messageReceiptsTracker.onMessageRead({
|
|
9104
|
+
user: event.user,
|
|
9105
|
+
readAt: event.created_at,
|
|
9106
|
+
lastReadMessageId: event.last_read_message_id
|
|
9107
|
+
});
|
|
9108
|
+
const client = this.getClient();
|
|
9109
|
+
const isOwnEvent = event.user?.id === client.user?.id;
|
|
9110
|
+
if (isOwnEvent) {
|
|
8547
9111
|
channelState.unreadCount = 0;
|
|
9112
|
+
client.syncDeliveredCandidates([this]);
|
|
8548
9113
|
}
|
|
8549
9114
|
}
|
|
8550
9115
|
break;
|
|
9116
|
+
case "message.delivered":
|
|
9117
|
+
if (event.user?.id && event.created_at) {
|
|
9118
|
+
const previousReadState = channelState.read[event.user.id];
|
|
9119
|
+
channelState.read[event.user.id] = {
|
|
9120
|
+
...previousReadState,
|
|
9121
|
+
last_delivered_at: event.last_delivered_at ? new Date(event.last_delivered_at) : void 0,
|
|
9122
|
+
last_delivered_message_id: event.last_delivered_message_id,
|
|
9123
|
+
user: event.user
|
|
9124
|
+
};
|
|
9125
|
+
this.messageReceiptsTracker.onMessageDelivered({
|
|
9126
|
+
user: event.user,
|
|
9127
|
+
deliveredAt: event.created_at,
|
|
9128
|
+
lastDeliveredMessageId: event.last_delivered_message_id
|
|
9129
|
+
});
|
|
9130
|
+
}
|
|
9131
|
+
break;
|
|
8551
9132
|
case "user.watching.start":
|
|
8552
9133
|
case "user.updated":
|
|
8553
9134
|
if (event.user?.id) {
|
|
@@ -8581,7 +9162,8 @@ var Channel = class {
|
|
|
8581
9162
|
break;
|
|
8582
9163
|
case "message.new":
|
|
8583
9164
|
if (event.message) {
|
|
8584
|
-
const
|
|
9165
|
+
const client = this.getClient();
|
|
9166
|
+
const ownMessage = event.user?.id === client.user?.id;
|
|
8585
9167
|
const isThreadMessage = event.message.parent_id && !event.message.show_in_channel;
|
|
8586
9168
|
if (this.state.isUpToDate || isThreadMessage) {
|
|
8587
9169
|
channelState.addMessageSorted(event.message, ownMessage);
|
|
@@ -8597,7 +9179,9 @@ var Channel = class {
|
|
|
8597
9179
|
channelState.read[event.user.id] = {
|
|
8598
9180
|
last_read: new Date(event.created_at),
|
|
8599
9181
|
user: event.user,
|
|
8600
|
-
unread_messages: 0
|
|
9182
|
+
unread_messages: 0,
|
|
9183
|
+
last_delivered_at: new Date(event.created_at),
|
|
9184
|
+
last_delivered_message_id: event.message.id
|
|
8601
9185
|
};
|
|
8602
9186
|
} else {
|
|
8603
9187
|
channelState.read[userId].unread_messages += 1;
|
|
@@ -8607,6 +9191,7 @@ var Channel = class {
|
|
|
8607
9191
|
if (this._countMessageAsUnread(event.message)) {
|
|
8608
9192
|
channelState.unreadCount = channelState.unreadCount + 1;
|
|
8609
9193
|
}
|
|
9194
|
+
client.syncDeliveredCandidates([this]);
|
|
8610
9195
|
}
|
|
8611
9196
|
break;
|
|
8612
9197
|
case "message.updated":
|
|
@@ -8689,9 +9274,12 @@ var Channel = class {
|
|
|
8689
9274
|
break;
|
|
8690
9275
|
case "notification.mark_unread": {
|
|
8691
9276
|
const ownMessage = event.user?.id === this.getClient().user?.id;
|
|
8692
|
-
if (!
|
|
9277
|
+
if (!ownMessage || !event.user) break;
|
|
8693
9278
|
const unreadCount = event.unread_messages ?? 0;
|
|
9279
|
+
const currentState = channelState.read[event.user.id];
|
|
8694
9280
|
channelState.read[event.user.id] = {
|
|
9281
|
+
// keep the message delivery info
|
|
9282
|
+
...currentState,
|
|
8695
9283
|
first_unread_message_id: event.first_unread_message_id,
|
|
8696
9284
|
last_read: new Date(event.last_read_at),
|
|
8697
9285
|
last_read_message_id: event.last_read_message_id,
|
|
@@ -8699,6 +9287,11 @@ var Channel = class {
|
|
|
8699
9287
|
unread_messages: unreadCount
|
|
8700
9288
|
};
|
|
8701
9289
|
channelState.unreadCount = unreadCount;
|
|
9290
|
+
this.messageReceiptsTracker.onNotificationMarkUnread({
|
|
9291
|
+
user: event.user,
|
|
9292
|
+
lastReadAt: event.last_read_at,
|
|
9293
|
+
lastReadMessageId: event.last_read_message_id
|
|
9294
|
+
});
|
|
8702
9295
|
break;
|
|
8703
9296
|
}
|
|
8704
9297
|
case "channel.updated":
|
|
@@ -8850,6 +9443,7 @@ var Channel = class {
|
|
|
8850
9443
|
this.state.unreadCount = this.state.read[read.user.id].unread_messages;
|
|
8851
9444
|
}
|
|
8852
9445
|
}
|
|
9446
|
+
this.messageReceiptsTracker.ingestInitial(state.read);
|
|
8853
9447
|
}
|
|
8854
9448
|
return {
|
|
8855
9449
|
messageSet
|
|
@@ -9459,7 +10053,7 @@ var StableWSConnection = class {
|
|
|
9459
10053
|
};
|
|
9460
10054
|
|
|
9461
10055
|
// src/signing.ts
|
|
9462
|
-
var import_jsonwebtoken = __toESM(
|
|
10056
|
+
var import_jsonwebtoken = __toESM(require_jsonwebtoken());
|
|
9463
10057
|
var import_crypto = __toESM(require_crypto());
|
|
9464
10058
|
function JWTUserToken(apiSecret, userId, extraData = {}, jwtOptions = {}) {
|
|
9465
10059
|
if (typeof userId !== "string") {
|
|
@@ -12400,6 +12994,7 @@ var StreamChat = class _StreamChat {
|
|
|
12400
12994
|
this.threads = new ThreadManager({ client: this });
|
|
12401
12995
|
this.polls = new PollManager({ client: this });
|
|
12402
12996
|
this.reminders = new ReminderManager({ client: this });
|
|
12997
|
+
this.messageDeliveryReporter = new MessageDeliveryReporter({ client: this });
|
|
12403
12998
|
}
|
|
12404
12999
|
static getInstance(key, secretOrOptions, options) {
|
|
12405
13000
|
if (!_StreamChat._instance) {
|
|
@@ -13070,6 +13665,7 @@ var StreamChat = class _StreamChat {
|
|
|
13070
13665
|
c.messageComposer.initStateFromChannelResponse(channelState);
|
|
13071
13666
|
channels.push(c);
|
|
13072
13667
|
}
|
|
13668
|
+
this.syncDeliveredCandidates(channels);
|
|
13073
13669
|
return channels;
|
|
13074
13670
|
}
|
|
13075
13671
|
/**
|
|
@@ -13834,10 +14430,26 @@ var StreamChat = class _StreamChat {
|
|
|
13834
14430
|
}
|
|
13835
14431
|
);
|
|
13836
14432
|
}
|
|
13837
|
-
|
|
14433
|
+
/**
|
|
14434
|
+
* deleteMessage - Delete a message
|
|
14435
|
+
*
|
|
14436
|
+
* @param {string} messageID The id of the message to delete
|
|
14437
|
+
* @param {boolean | DeleteMessageOptions | undefined} [optionsOrHardDelete]
|
|
14438
|
+
* @return {Promise<APIResponse & { message: MessageResponse }>} The API response
|
|
14439
|
+
*/
|
|
14440
|
+
// fixme: remove the signature with optionsOrHardDelete boolean with the next major release
|
|
14441
|
+
async deleteMessage(messageID, optionsOrHardDelete) {
|
|
14442
|
+
let options = {};
|
|
14443
|
+
if (typeof optionsOrHardDelete === "boolean") {
|
|
14444
|
+
options = optionsOrHardDelete ? { hardDelete: true } : {};
|
|
14445
|
+
} else if (optionsOrHardDelete?.deleteForMe) {
|
|
14446
|
+
options = { deleteForMe: true };
|
|
14447
|
+
} else if (optionsOrHardDelete?.hardDelete) {
|
|
14448
|
+
options = { hardDelete: true };
|
|
14449
|
+
}
|
|
13838
14450
|
try {
|
|
13839
14451
|
if (this.offlineDb) {
|
|
13840
|
-
if (hardDelete) {
|
|
14452
|
+
if (options.hardDelete) {
|
|
13841
14453
|
await this.offlineDb.hardDeleteMessage({ id: messageID });
|
|
13842
14454
|
} else {
|
|
13843
14455
|
await this.offlineDb.softDeleteMessage({ id: messageID });
|
|
@@ -13846,7 +14458,7 @@ var StreamChat = class _StreamChat {
|
|
|
13846
14458
|
{
|
|
13847
14459
|
task: {
|
|
13848
14460
|
messageId: messageID,
|
|
13849
|
-
payload: [messageID,
|
|
14461
|
+
payload: [messageID, options],
|
|
13850
14462
|
type: "delete-message"
|
|
13851
14463
|
}
|
|
13852
14464
|
}
|
|
@@ -13858,17 +14470,27 @@ var StreamChat = class _StreamChat {
|
|
|
13858
14470
|
error
|
|
13859
14471
|
});
|
|
13860
14472
|
}
|
|
13861
|
-
return this._deleteMessage(messageID,
|
|
14473
|
+
return this._deleteMessage(messageID, options);
|
|
13862
14474
|
}
|
|
13863
|
-
|
|
14475
|
+
// fixme: remove the signature with optionsOrHardDelete boolean with the next major release
|
|
14476
|
+
async _deleteMessage(messageID, optionsOrHardDelete) {
|
|
14477
|
+
const { deleteForMe, hardDelete } = typeof optionsOrHardDelete === "boolean" ? { hardDelete: optionsOrHardDelete } : optionsOrHardDelete ?? {};
|
|
13864
14478
|
let params = {};
|
|
13865
14479
|
if (hardDelete) {
|
|
13866
14480
|
params = { hard: true };
|
|
13867
14481
|
}
|
|
13868
|
-
|
|
14482
|
+
if (deleteForMe) {
|
|
14483
|
+
params = { ...params, delete_for_me: true };
|
|
14484
|
+
}
|
|
14485
|
+
const result = await this.delete(
|
|
13869
14486
|
this.baseURL + `/messages/${encodeURIComponent(messageID)}`,
|
|
13870
14487
|
params
|
|
13871
14488
|
);
|
|
14489
|
+
if (deleteForMe) {
|
|
14490
|
+
result.message.deleted_for_me = true;
|
|
14491
|
+
result.message.type = "deleted";
|
|
14492
|
+
}
|
|
14493
|
+
return result;
|
|
13872
14494
|
}
|
|
13873
14495
|
/**
|
|
13874
14496
|
* undeleteMessage - Undelete a message
|
|
@@ -14004,7 +14626,7 @@ var StreamChat = class _StreamChat {
|
|
|
14004
14626
|
if (this.userAgent) {
|
|
14005
14627
|
return this.userAgent;
|
|
14006
14628
|
}
|
|
14007
|
-
const version = "9.
|
|
14629
|
+
const version = "9.22.1";
|
|
14008
14630
|
const clientBundle = "browser-cjs";
|
|
14009
14631
|
let userAgentString = "";
|
|
14010
14632
|
if (this.sdkIdentifier) {
|
|
@@ -15153,19 +15775,21 @@ var StreamChat = class _StreamChat {
|
|
|
15153
15775
|
return this.delete(`${this.baseURL}/uploads/image`, { url });
|
|
15154
15776
|
}
|
|
15155
15777
|
/**
|
|
15156
|
-
* Send the mark delivered event for this user
|
|
15778
|
+
* Send the mark delivered event for this user
|
|
15157
15779
|
*
|
|
15158
15780
|
* @param {MarkDeliveredOptions} data
|
|
15159
15781
|
* @return {Promise<EventAPIResponse | void>} Description
|
|
15160
15782
|
*/
|
|
15161
15783
|
async markChannelsDelivered(data) {
|
|
15162
|
-
|
|
15163
|
-
if (!deliveryReceiptsEnabled) return;
|
|
15784
|
+
if (!data?.latest_delivered_messages?.length) return;
|
|
15164
15785
|
return await this.post(
|
|
15165
15786
|
this.baseURL + "/channels/delivered",
|
|
15166
15787
|
data ?? {}
|
|
15167
15788
|
);
|
|
15168
15789
|
}
|
|
15790
|
+
syncDeliveredCandidates(collections) {
|
|
15791
|
+
this.messageDeliveryReporter.syncDeliveredCandidates(collections);
|
|
15792
|
+
}
|
|
15169
15793
|
};
|
|
15170
15794
|
|
|
15171
15795
|
// src/events.ts
|
|
@@ -15202,6 +15826,7 @@ var EVENT_MAP = {
|
|
|
15202
15826
|
"notification.mark_unread": true,
|
|
15203
15827
|
"notification.message_new": true,
|
|
15204
15828
|
"notification.mutes_updated": true,
|
|
15829
|
+
"notification.reminder_due": true,
|
|
15205
15830
|
"notification.removed_from_channel": true,
|
|
15206
15831
|
"notification.thread_message_new": true,
|
|
15207
15832
|
"poll.closed": true,
|
|
@@ -15212,6 +15837,9 @@ var EVENT_MAP = {
|
|
|
15212
15837
|
"reaction.deleted": true,
|
|
15213
15838
|
"reaction.new": true,
|
|
15214
15839
|
"reaction.updated": true,
|
|
15840
|
+
"reminder.created": true,
|
|
15841
|
+
"reminder.deleted": true,
|
|
15842
|
+
"reminder.updated": true,
|
|
15215
15843
|
"thread.updated": true,
|
|
15216
15844
|
"typing.start": true,
|
|
15217
15845
|
"typing.stop": true,
|
|
@@ -15236,12 +15864,7 @@ var EVENT_MAP = {
|
|
|
15236
15864
|
"transport.changed": true,
|
|
15237
15865
|
"capabilities.changed": true,
|
|
15238
15866
|
"live_location_sharing.started": true,
|
|
15239
|
-
"live_location_sharing.stopped": true
|
|
15240
|
-
// Reminder events
|
|
15241
|
-
"reminder.created": true,
|
|
15242
|
-
"reminder.updated": true,
|
|
15243
|
-
"reminder.deleted": true,
|
|
15244
|
-
"notification.reminder_due": true
|
|
15867
|
+
"live_location_sharing.stopped": true
|
|
15245
15868
|
};
|
|
15246
15869
|
|
|
15247
15870
|
// src/permissions.ts
|