stream-chat 9.21.0 → 9.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.browser.js +654 -37
- 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 +654 -37
- 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/cjs/index.node.js
CHANGED
|
@@ -81,7 +81,9 @@ __export(index_exports, {
|
|
|
81
81
|
MergedStateStore: () => MergedStateStore,
|
|
82
82
|
MessageComposer: () => MessageComposer,
|
|
83
83
|
MessageComposerMiddlewareExecutor: () => MessageComposerMiddlewareExecutor,
|
|
84
|
+
MessageDeliveryReporter: () => MessageDeliveryReporter,
|
|
84
85
|
MessageDraftComposerMiddlewareExecutor: () => MessageDraftComposerMiddlewareExecutor,
|
|
86
|
+
MessageReceiptsTracker: () => MessageReceiptsTracker,
|
|
85
87
|
MessageSearchSource: () => MessageSearchSource,
|
|
86
88
|
MiddlewareExecutor: () => MiddlewareExecutor,
|
|
87
89
|
MinPriority: () => MinPriority,
|
|
@@ -1132,6 +1134,21 @@ var runDetached = (callback, options) => {
|
|
|
1132
1134
|
};
|
|
1133
1135
|
|
|
1134
1136
|
// src/channel_state.ts
|
|
1137
|
+
var messageSetBounds = (a, b) => ({
|
|
1138
|
+
newestMessageA: new Date(a[0]?.created_at ?? 0),
|
|
1139
|
+
oldestMessageA: new Date(a.slice(-1)[0]?.created_at ?? 0),
|
|
1140
|
+
newestMessageB: new Date(b[0]?.created_at ?? 0),
|
|
1141
|
+
oldestMessageB: new Date(b.slice(-1)[0]?.created_at ?? 0)
|
|
1142
|
+
});
|
|
1143
|
+
var aContainsOrEqualsB = (a, b) => {
|
|
1144
|
+
const { newestMessageA, newestMessageB, oldestMessageA, oldestMessageB } = messageSetBounds(a, b);
|
|
1145
|
+
return newestMessageA >= newestMessageB && oldestMessageB >= oldestMessageA;
|
|
1146
|
+
};
|
|
1147
|
+
var aOverlapsB = (a, b) => {
|
|
1148
|
+
const { newestMessageA, newestMessageB, oldestMessageA, oldestMessageB } = messageSetBounds(a, b);
|
|
1149
|
+
return oldestMessageA < oldestMessageB && oldestMessageB < newestMessageA && newestMessageA < newestMessageB;
|
|
1150
|
+
};
|
|
1151
|
+
var messageSetsOverlapByTimestamp = (a, b) => aContainsOrEqualsB(a, b) || aContainsOrEqualsB(b, a) || aOverlapsB(a, b) || aOverlapsB(b, a);
|
|
1135
1152
|
var ChannelState = class {
|
|
1136
1153
|
constructor(channel) {
|
|
1137
1154
|
/**
|
|
@@ -1210,6 +1227,22 @@ var ChannelState = class {
|
|
|
1210
1227
|
deletedAt: deletedAt ?? null
|
|
1211
1228
|
});
|
|
1212
1229
|
};
|
|
1230
|
+
/**
|
|
1231
|
+
* Identifies the set index into which a message set would pertain if its first item's creation date corresponded to oldestTimestampMs.
|
|
1232
|
+
* @param oldestTimestampMs
|
|
1233
|
+
*/
|
|
1234
|
+
this.findMessageSetByOldestTimestamp = (oldestTimestampMs) => {
|
|
1235
|
+
let lo = 0, hi = this.messageSets.length;
|
|
1236
|
+
while (lo < hi) {
|
|
1237
|
+
const mid = lo + hi >>> 1;
|
|
1238
|
+
const msgSet = this.messageSets[mid];
|
|
1239
|
+
if (msgSet.messages.length === 0) return -1;
|
|
1240
|
+
const oldestMessageTimestampInSet = msgSet.messages[0].created_at.getTime();
|
|
1241
|
+
if (oldestMessageTimestampInSet <= oldestTimestampMs) hi = mid;
|
|
1242
|
+
else lo = mid + 1;
|
|
1243
|
+
}
|
|
1244
|
+
return lo;
|
|
1245
|
+
};
|
|
1213
1246
|
this._channel = channel;
|
|
1214
1247
|
this.watcher_count = 0;
|
|
1215
1248
|
this.typing = {};
|
|
@@ -1735,6 +1768,25 @@ var ChannelState = class {
|
|
|
1735
1768
|
}
|
|
1736
1769
|
return this.messageSets[messageSetIndex].messages.find((m) => m.id === messageId);
|
|
1737
1770
|
}
|
|
1771
|
+
findMessageByTimestamp(timestampMs, parentMessageId, exactTsMatch = false) {
|
|
1772
|
+
if (parentMessageId && !this.threads[parentMessageId] || this.messageSets.length === 0)
|
|
1773
|
+
return null;
|
|
1774
|
+
const setIndex = this.findMessageSetByOldestTimestamp(timestampMs);
|
|
1775
|
+
const targetMsgSet = this.messageSets[setIndex]?.messages;
|
|
1776
|
+
if (!targetMsgSet?.length) return null;
|
|
1777
|
+
const firstMsgTimestamp = targetMsgSet[0].created_at.getTime();
|
|
1778
|
+
const lastMsgTimestamp = targetMsgSet.slice(-1)[0].created_at.getTime();
|
|
1779
|
+
const isOutOfBound = timestampMs < firstMsgTimestamp || lastMsgTimestamp < timestampMs;
|
|
1780
|
+
if (isOutOfBound && exactTsMatch) return null;
|
|
1781
|
+
let msgIndex = 0, hi = targetMsgSet.length - 1;
|
|
1782
|
+
while (msgIndex < hi) {
|
|
1783
|
+
const mid = msgIndex + hi >>> 1;
|
|
1784
|
+
if (timestampMs <= targetMsgSet[mid].created_at.getTime()) hi = mid;
|
|
1785
|
+
else msgIndex = mid + 1;
|
|
1786
|
+
}
|
|
1787
|
+
const foundMessage = targetMsgSet[msgIndex];
|
|
1788
|
+
return !exactTsMatch ? foundMessage : foundMessage.created_at.getTime() === timestampMs ? foundMessage : null;
|
|
1789
|
+
}
|
|
1738
1790
|
switchToMessageSet(index) {
|
|
1739
1791
|
const currentMessages = this.messageSets.find((s) => s.isCurrent);
|
|
1740
1792
|
if (!currentMessages) {
|
|
@@ -1754,35 +1806,77 @@ var ChannelState = class {
|
|
|
1754
1806
|
findTargetMessageSet(newMessages, addIfDoesNotExist = true, messageSetToAddToIfDoesNotExist = "current") {
|
|
1755
1807
|
let messagesToAdd = newMessages;
|
|
1756
1808
|
let targetMessageSetIndex;
|
|
1809
|
+
if (newMessages.length === 0)
|
|
1810
|
+
return { targetMessageSetIndex: 0, messagesToAdd: newMessages };
|
|
1757
1811
|
if (addIfDoesNotExist) {
|
|
1758
|
-
const
|
|
1812
|
+
const overlappingMessageSetIndicesByMsgIds = this.messageSets.map((_, i) => i).filter(
|
|
1759
1813
|
(i) => this.areMessageSetsOverlap(this.messageSets[i].messages, newMessages)
|
|
1760
1814
|
);
|
|
1815
|
+
const overlappingMessageSetIndicesByTimestamp = this.messageSets.map((_, i) => i).filter(
|
|
1816
|
+
(i) => messageSetsOverlapByTimestamp(
|
|
1817
|
+
this.messageSets[i].messages,
|
|
1818
|
+
newMessages.map(formatMessage)
|
|
1819
|
+
)
|
|
1820
|
+
);
|
|
1761
1821
|
switch (messageSetToAddToIfDoesNotExist) {
|
|
1762
1822
|
case "new":
|
|
1763
|
-
if (
|
|
1764
|
-
targetMessageSetIndex =
|
|
1823
|
+
if (overlappingMessageSetIndicesByMsgIds.length > 0) {
|
|
1824
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByMsgIds[0];
|
|
1825
|
+
} else if (overlappingMessageSetIndicesByTimestamp.length > 0) {
|
|
1826
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByTimestamp[0];
|
|
1765
1827
|
} else if (newMessages.some((m) => !m.parent_id)) {
|
|
1766
|
-
this.
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1828
|
+
const setIngestIndex = this.findMessageSetByOldestTimestamp(
|
|
1829
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1830
|
+
new Date(newMessages[0].created_at).getTime()
|
|
1831
|
+
);
|
|
1832
|
+
if (setIngestIndex === -1) {
|
|
1833
|
+
this.messageSets.push({
|
|
1834
|
+
messages: [],
|
|
1835
|
+
isCurrent: false,
|
|
1836
|
+
isLatest: false,
|
|
1837
|
+
pagination: DEFAULT_MESSAGE_SET_PAGINATION
|
|
1838
|
+
});
|
|
1839
|
+
targetMessageSetIndex = this.messageSets.length - 1;
|
|
1840
|
+
} else {
|
|
1841
|
+
const isLatest = setIngestIndex === 0;
|
|
1842
|
+
this.messageSets.splice(setIngestIndex, 0, {
|
|
1843
|
+
messages: [],
|
|
1844
|
+
isCurrent: false,
|
|
1845
|
+
isLatest,
|
|
1846
|
+
pagination: DEFAULT_MESSAGE_SET_PAGINATION
|
|
1847
|
+
// fixme: it is problematic decide about pagination without having data
|
|
1848
|
+
});
|
|
1849
|
+
if (isLatest) {
|
|
1850
|
+
this.messageSets.slice(1).forEach((set) => {
|
|
1851
|
+
set.isLatest = false;
|
|
1852
|
+
});
|
|
1853
|
+
}
|
|
1854
|
+
targetMessageSetIndex = setIngestIndex;
|
|
1855
|
+
}
|
|
1773
1856
|
}
|
|
1774
1857
|
break;
|
|
1775
1858
|
case "current":
|
|
1776
|
-
|
|
1859
|
+
if (overlappingMessageSetIndicesByTimestamp.length > 0) {
|
|
1860
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByTimestamp[0];
|
|
1861
|
+
} else {
|
|
1862
|
+
targetMessageSetIndex = this.messageSets.findIndex((s) => s.isCurrent);
|
|
1863
|
+
}
|
|
1777
1864
|
break;
|
|
1778
1865
|
case "latest":
|
|
1779
|
-
|
|
1866
|
+
if (overlappingMessageSetIndicesByTimestamp.length > 0) {
|
|
1867
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByTimestamp[0];
|
|
1868
|
+
} else {
|
|
1869
|
+
targetMessageSetIndex = this.messageSets.findIndex((s) => s.isLatest);
|
|
1870
|
+
}
|
|
1780
1871
|
break;
|
|
1781
1872
|
default:
|
|
1782
1873
|
targetMessageSetIndex = -1;
|
|
1783
1874
|
}
|
|
1784
|
-
const mergeTargetMessageSetIndex =
|
|
1785
|
-
|
|
1875
|
+
const mergeTargetMessageSetIndex = overlappingMessageSetIndicesByMsgIds.splice(
|
|
1876
|
+
0,
|
|
1877
|
+
1
|
|
1878
|
+
)[0];
|
|
1879
|
+
const mergeSourceMessageSetIndices = [...overlappingMessageSetIndicesByMsgIds];
|
|
1786
1880
|
if (mergeTargetMessageSetIndex !== void 0 && mergeTargetMessageSetIndex !== targetMessageSetIndex) {
|
|
1787
1881
|
mergeSourceMessageSetIndices.push(targetMessageSetIndex);
|
|
1788
1882
|
}
|
|
@@ -6315,7 +6409,7 @@ var Thread = class extends WithSubscriptions {
|
|
|
6315
6409
|
if (this.ownUnreadCount === 0 && !force) {
|
|
6316
6410
|
return null;
|
|
6317
6411
|
}
|
|
6318
|
-
return await this.
|
|
6412
|
+
return await this.client.messageDeliveryReporter.markRead(this);
|
|
6319
6413
|
};
|
|
6320
6414
|
this.throttledMarkAsRead = throttle(
|
|
6321
6415
|
() => this.markAsRead(),
|
|
@@ -7045,6 +7139,444 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
|
|
|
7045
7139
|
_MessageComposer.generateId = generateUUIDv4;
|
|
7046
7140
|
var MessageComposer = _MessageComposer;
|
|
7047
7141
|
|
|
7142
|
+
// src/messageDelivery/MessageDeliveryReporter.ts
|
|
7143
|
+
var MAX_DELIVERED_MESSAGE_COUNT_IN_PAYLOAD = 100;
|
|
7144
|
+
var MARK_AS_DELIVERED_BUFFER_TIMEOUT = 1e3;
|
|
7145
|
+
var MARK_AS_READ_THROTTLE_TIMEOUT2 = 1e3;
|
|
7146
|
+
var isChannel = (item) => item instanceof Channel;
|
|
7147
|
+
var isThread = (item) => item instanceof Thread;
|
|
7148
|
+
var MessageDeliveryReporter = class {
|
|
7149
|
+
constructor({ client }) {
|
|
7150
|
+
this.deliveryReportCandidates = /* @__PURE__ */ new Map();
|
|
7151
|
+
this.nextDeliveryReportCandidates = /* @__PURE__ */ new Map();
|
|
7152
|
+
this.markDeliveredRequestPromise = null;
|
|
7153
|
+
this.markDeliveredTimeout = null;
|
|
7154
|
+
/**
|
|
7155
|
+
* Retrieve the reference to the latest message in the state that is nor read neither reported as delivered
|
|
7156
|
+
* @param collection
|
|
7157
|
+
*/
|
|
7158
|
+
this.getNextDeliveryReportCandidate = (collection) => {
|
|
7159
|
+
const ownUserId = this.client.user?.id;
|
|
7160
|
+
if (!ownUserId) return;
|
|
7161
|
+
let latestMessages = [];
|
|
7162
|
+
let lastDeliveredAt;
|
|
7163
|
+
let lastReadAt;
|
|
7164
|
+
let key = void 0;
|
|
7165
|
+
if (isChannel(collection)) {
|
|
7166
|
+
latestMessages = collection.state.latestMessages;
|
|
7167
|
+
const ownReadState = collection.state.read[ownUserId] ?? {};
|
|
7168
|
+
lastReadAt = ownReadState?.last_read;
|
|
7169
|
+
lastDeliveredAt = ownReadState?.last_delivered_at;
|
|
7170
|
+
key = collection.cid;
|
|
7171
|
+
} else if (isThread(collection)) {
|
|
7172
|
+
latestMessages = collection.state.getLatestValue().replies;
|
|
7173
|
+
const ownReadState = collection.state.getLatestValue().read[ownUserId] ?? {};
|
|
7174
|
+
lastReadAt = ownReadState?.lastReadAt;
|
|
7175
|
+
lastDeliveredAt = ownReadState?.lastDeliveredAt;
|
|
7176
|
+
key = `${collection.channel.cid}:${collection.id}`;
|
|
7177
|
+
return;
|
|
7178
|
+
} else {
|
|
7179
|
+
return;
|
|
7180
|
+
}
|
|
7181
|
+
if (!key) return;
|
|
7182
|
+
const [latestMessage] = latestMessages.slice(-1);
|
|
7183
|
+
const wholeCollectionIsRead = !latestMessage || lastReadAt >= latestMessage.created_at;
|
|
7184
|
+
if (wholeCollectionIsRead) return { key, id: null };
|
|
7185
|
+
const wholeCollectionIsMarkedDelivered = !latestMessage || (lastDeliveredAt ?? 0) >= latestMessage.created_at;
|
|
7186
|
+
if (wholeCollectionIsMarkedDelivered) return { key, id: null };
|
|
7187
|
+
return { key, id: latestMessage.id || null };
|
|
7188
|
+
};
|
|
7189
|
+
/**
|
|
7190
|
+
* Fires delivery announcement request followed by immediate delivery candidate buffer reset.
|
|
7191
|
+
* @param options
|
|
7192
|
+
*/
|
|
7193
|
+
this.announceDelivery = (options) => {
|
|
7194
|
+
if (this.markDeliveredRequestInFlight || !this.hasDeliveryCandidates) return;
|
|
7195
|
+
const { latest_delivered_messages, sendBuffer } = this.confirmationsFromDeliveryReportCandidates();
|
|
7196
|
+
if (!latest_delivered_messages.length) return;
|
|
7197
|
+
const payload = { ...options, latest_delivered_messages };
|
|
7198
|
+
const postFlightReconcile = () => {
|
|
7199
|
+
this.markDeliveredRequestPromise = null;
|
|
7200
|
+
for (const [k, v] of this.nextDeliveryReportCandidates.entries()) {
|
|
7201
|
+
this.deliveryReportCandidates.set(k, v);
|
|
7202
|
+
}
|
|
7203
|
+
this.nextDeliveryReportCandidates = /* @__PURE__ */ new Map();
|
|
7204
|
+
this.announceDeliveryBuffered(options);
|
|
7205
|
+
};
|
|
7206
|
+
const handleError = () => {
|
|
7207
|
+
for (const [k, v] of Object.entries(sendBuffer)) {
|
|
7208
|
+
if (!this.deliveryReportCandidates.has(k)) {
|
|
7209
|
+
this.deliveryReportCandidates.set(k, v);
|
|
7210
|
+
}
|
|
7211
|
+
}
|
|
7212
|
+
postFlightReconcile();
|
|
7213
|
+
};
|
|
7214
|
+
this.markDeliveredRequestPromise = this.client.markChannelsDelivered(payload).then(postFlightReconcile, handleError);
|
|
7215
|
+
};
|
|
7216
|
+
this.announceDeliveryBuffered = (options) => {
|
|
7217
|
+
if (this.hasTimer || this.markDeliveredRequestInFlight || !this.hasDeliveryCandidates)
|
|
7218
|
+
return;
|
|
7219
|
+
this.markDeliveredTimeout = setTimeout(() => {
|
|
7220
|
+
this.markDeliveredTimeout = null;
|
|
7221
|
+
this.announceDelivery(options);
|
|
7222
|
+
}, MARK_AS_DELIVERED_BUFFER_TIMEOUT);
|
|
7223
|
+
};
|
|
7224
|
+
/**
|
|
7225
|
+
* Delegates the mark-read call to the Channel or Thread instance
|
|
7226
|
+
* @param collection
|
|
7227
|
+
* @param options
|
|
7228
|
+
*/
|
|
7229
|
+
this.markRead = async (collection, options) => {
|
|
7230
|
+
let result = null;
|
|
7231
|
+
if (isChannel(collection)) {
|
|
7232
|
+
result = await collection.markAsReadRequest(options);
|
|
7233
|
+
} else if (isThread(collection)) {
|
|
7234
|
+
result = await collection.channel.markAsReadRequest({
|
|
7235
|
+
...options,
|
|
7236
|
+
thread_id: collection.id
|
|
7237
|
+
});
|
|
7238
|
+
}
|
|
7239
|
+
this.removeCandidateFor(collection);
|
|
7240
|
+
return result;
|
|
7241
|
+
};
|
|
7242
|
+
/**
|
|
7243
|
+
* Throttles the MessageDeliveryReporter.markRead call
|
|
7244
|
+
* @param collection
|
|
7245
|
+
* @param options
|
|
7246
|
+
*/
|
|
7247
|
+
this.throttledMarkRead = throttle(this.markRead, MARK_AS_READ_THROTTLE_TIMEOUT2, {
|
|
7248
|
+
leading: false,
|
|
7249
|
+
trailing: true
|
|
7250
|
+
});
|
|
7251
|
+
this.client = client;
|
|
7252
|
+
}
|
|
7253
|
+
get markDeliveredRequestInFlight() {
|
|
7254
|
+
return this.markDeliveredRequestPromise !== null;
|
|
7255
|
+
}
|
|
7256
|
+
get hasTimer() {
|
|
7257
|
+
return this.markDeliveredTimeout !== null;
|
|
7258
|
+
}
|
|
7259
|
+
get hasDeliveryCandidates() {
|
|
7260
|
+
return this.deliveryReportCandidates.size > 0;
|
|
7261
|
+
}
|
|
7262
|
+
/**
|
|
7263
|
+
* Build latest_delivered_messages payload from an arbitrary buffer (deliveryReportCandidates / nextDeliveryReportCandidates)
|
|
7264
|
+
*/
|
|
7265
|
+
confirmationsFrom(map2) {
|
|
7266
|
+
return Array.from(map2.entries()).map(([key, messageId]) => {
|
|
7267
|
+
const [type, id, parent_id] = key.split(":");
|
|
7268
|
+
return parent_id ? { cid: `${type}:${id}`, id: messageId, parent_id } : { cid: key, id: messageId };
|
|
7269
|
+
});
|
|
7270
|
+
}
|
|
7271
|
+
confirmationsFromDeliveryReportCandidates() {
|
|
7272
|
+
const entries = Array.from(this.deliveryReportCandidates);
|
|
7273
|
+
const sendBuffer = new Map(entries.slice(0, MAX_DELIVERED_MESSAGE_COUNT_IN_PAYLOAD));
|
|
7274
|
+
this.deliveryReportCandidates = new Map(
|
|
7275
|
+
entries.slice(MAX_DELIVERED_MESSAGE_COUNT_IN_PAYLOAD)
|
|
7276
|
+
);
|
|
7277
|
+
return { latest_delivered_messages: this.confirmationsFrom(sendBuffer), sendBuffer };
|
|
7278
|
+
}
|
|
7279
|
+
/**
|
|
7280
|
+
* Generate candidate key for storing in the candidates buffer
|
|
7281
|
+
* @param collection
|
|
7282
|
+
* @private
|
|
7283
|
+
*/
|
|
7284
|
+
candidateKeyFor(collection) {
|
|
7285
|
+
if (isChannel(collection)) return collection.cid;
|
|
7286
|
+
if (isThread(collection)) return `${collection.channel.cid}:${collection.id}`;
|
|
7287
|
+
}
|
|
7288
|
+
/**
|
|
7289
|
+
* Updates the delivery candidates buffer with the latest delivery candidates
|
|
7290
|
+
* @param collection
|
|
7291
|
+
*/
|
|
7292
|
+
trackDeliveredCandidate(collection) {
|
|
7293
|
+
if (isChannel(collection) && !collection.getConfig()?.read_events) return;
|
|
7294
|
+
if (isThread(collection) && !collection.channel.getConfig()?.read_events) return;
|
|
7295
|
+
const candidate = this.getNextDeliveryReportCandidate(collection);
|
|
7296
|
+
if (!candidate?.key) return;
|
|
7297
|
+
const buffer = this.markDeliveredRequestInFlight ? this.nextDeliveryReportCandidates : this.deliveryReportCandidates;
|
|
7298
|
+
if (candidate.id === null) buffer.delete(candidate.key);
|
|
7299
|
+
else buffer.set(candidate.key, candidate.id);
|
|
7300
|
+
}
|
|
7301
|
+
/**
|
|
7302
|
+
* Removes candidate from the delivery report buffer
|
|
7303
|
+
* @param collection
|
|
7304
|
+
* @private
|
|
7305
|
+
*/
|
|
7306
|
+
removeCandidateFor(collection) {
|
|
7307
|
+
const candidateKey = this.candidateKeyFor(collection);
|
|
7308
|
+
if (!candidateKey) return;
|
|
7309
|
+
this.deliveryReportCandidates.delete(candidateKey);
|
|
7310
|
+
this.nextDeliveryReportCandidates.delete(candidateKey);
|
|
7311
|
+
}
|
|
7312
|
+
/**
|
|
7313
|
+
* Records the latest message delivered for Channel or Thread instances and schedules the next report
|
|
7314
|
+
* if not already scheduled and candidates exist.
|
|
7315
|
+
* Should be used for WS handling (message.new) as well as for ingesting HTTP channel query results.
|
|
7316
|
+
* @param collections
|
|
7317
|
+
*/
|
|
7318
|
+
syncDeliveredCandidates(collections) {
|
|
7319
|
+
if (this.client.user?.privacy_settings?.delivery_receipts?.enabled === false) return;
|
|
7320
|
+
for (const c of collections) this.trackDeliveredCandidate(c);
|
|
7321
|
+
this.announceDeliveryBuffered();
|
|
7322
|
+
}
|
|
7323
|
+
};
|
|
7324
|
+
|
|
7325
|
+
// src/messageDelivery/MessageReceiptsTracker.ts
|
|
7326
|
+
var MIN_REF = { timestampMs: Number.NEGATIVE_INFINITY, msgId: "" };
|
|
7327
|
+
var compareRefsAsc = (a, b) => a.timestampMs !== b.timestampMs ? a.timestampMs - b.timestampMs : 0;
|
|
7328
|
+
var findIndex = (arr, target, keyOf) => {
|
|
7329
|
+
let lo = 0, hi = arr.length;
|
|
7330
|
+
while (lo < hi) {
|
|
7331
|
+
const mid = lo + hi >>> 1;
|
|
7332
|
+
if (compareRefsAsc(keyOf(arr[mid]), target) >= 0) hi = mid;
|
|
7333
|
+
else lo = mid + 1;
|
|
7334
|
+
}
|
|
7335
|
+
return lo;
|
|
7336
|
+
};
|
|
7337
|
+
var findUpperIndex = (arr, target, keyOf) => {
|
|
7338
|
+
let lo = 0, hi = arr.length;
|
|
7339
|
+
while (lo < hi) {
|
|
7340
|
+
const mid = lo + hi >>> 1;
|
|
7341
|
+
if (compareRefsAsc(keyOf(arr[mid]), target) > 0) hi = mid;
|
|
7342
|
+
else lo = mid + 1;
|
|
7343
|
+
}
|
|
7344
|
+
return lo;
|
|
7345
|
+
};
|
|
7346
|
+
var insertByKey = (arr, item, keyOf) => arr.splice(findUpperIndex(arr, keyOf(item), keyOf), 0, item);
|
|
7347
|
+
var removeByOldKey = (arr, item, oldKey, keyOf) => {
|
|
7348
|
+
let i = findIndex(arr, oldKey, keyOf);
|
|
7349
|
+
while (i < arr.length && compareRefsAsc(keyOf(arr[i]), oldKey) === 0) {
|
|
7350
|
+
if (arr[i].user.id === item.user.id) {
|
|
7351
|
+
arr.splice(i, 1);
|
|
7352
|
+
return;
|
|
7353
|
+
}
|
|
7354
|
+
i++;
|
|
7355
|
+
}
|
|
7356
|
+
};
|
|
7357
|
+
var MessageReceiptsTracker = class {
|
|
7358
|
+
constructor({ locateMessage }) {
|
|
7359
|
+
this.byUser = /* @__PURE__ */ new Map();
|
|
7360
|
+
this.readSorted = [];
|
|
7361
|
+
// asc by lastReadRef
|
|
7362
|
+
this.deliveredSorted = [];
|
|
7363
|
+
this.locateMessage = locateMessage;
|
|
7364
|
+
}
|
|
7365
|
+
/** Build initial state from server snapshots (single pass + sort). */
|
|
7366
|
+
ingestInitial(responses) {
|
|
7367
|
+
this.byUser.clear();
|
|
7368
|
+
this.readSorted = [];
|
|
7369
|
+
this.deliveredSorted = [];
|
|
7370
|
+
for (const r of responses) {
|
|
7371
|
+
const lastReadTimestamp = r.last_read ? new Date(r.last_read).getTime() : null;
|
|
7372
|
+
const lastDeliveredTimestamp = r.last_delivered_at ? new Date(r.last_delivered_at).getTime() : null;
|
|
7373
|
+
const lastReadRef = lastReadTimestamp ? this.locateMessage(lastReadTimestamp) ?? MIN_REF : MIN_REF;
|
|
7374
|
+
let lastDeliveredRef = lastDeliveredTimestamp ? this.locateMessage(lastDeliveredTimestamp) ?? MIN_REF : MIN_REF;
|
|
7375
|
+
const isReadAfterDelivered = compareRefsAsc(lastDeliveredRef, lastReadRef) < 0;
|
|
7376
|
+
if (isReadAfterDelivered) lastDeliveredRef = lastReadRef;
|
|
7377
|
+
const userProgress = { user: r.user, lastReadRef, lastDeliveredRef };
|
|
7378
|
+
this.byUser.set(r.user.id, userProgress);
|
|
7379
|
+
this.readSorted.splice(
|
|
7380
|
+
findIndex(this.readSorted, lastReadRef, (up) => up.lastReadRef),
|
|
7381
|
+
0,
|
|
7382
|
+
userProgress
|
|
7383
|
+
);
|
|
7384
|
+
this.deliveredSorted.splice(
|
|
7385
|
+
findIndex(this.deliveredSorted, lastDeliveredRef, (up) => up.lastDeliveredRef),
|
|
7386
|
+
0,
|
|
7387
|
+
userProgress
|
|
7388
|
+
);
|
|
7389
|
+
}
|
|
7390
|
+
}
|
|
7391
|
+
/** message.delivered — user device confirmed delivery up to and including messageId. */
|
|
7392
|
+
onMessageDelivered({
|
|
7393
|
+
user,
|
|
7394
|
+
deliveredAt,
|
|
7395
|
+
lastDeliveredMessageId
|
|
7396
|
+
}) {
|
|
7397
|
+
const timestampMs = new Date(deliveredAt).getTime();
|
|
7398
|
+
const msgRef = lastDeliveredMessageId ? { timestampMs, msgId: lastDeliveredMessageId } : this.locateMessage(new Date(deliveredAt).getTime());
|
|
7399
|
+
if (!msgRef) return;
|
|
7400
|
+
const userProgress = this.ensureUser(user);
|
|
7401
|
+
const newDelivered = compareRefsAsc(msgRef, userProgress.lastReadRef) < 0 ? userProgress.lastReadRef : msgRef;
|
|
7402
|
+
if (compareRefsAsc(newDelivered, userProgress.lastDeliveredRef) <= 0) return;
|
|
7403
|
+
removeByOldKey(
|
|
7404
|
+
this.deliveredSorted,
|
|
7405
|
+
userProgress,
|
|
7406
|
+
userProgress.lastDeliveredRef,
|
|
7407
|
+
(x) => x.lastDeliveredRef
|
|
7408
|
+
);
|
|
7409
|
+
userProgress.lastDeliveredRef = newDelivered;
|
|
7410
|
+
insertByKey(this.deliveredSorted, userProgress, (x) => x.lastDeliveredRef);
|
|
7411
|
+
}
|
|
7412
|
+
/** message.read — user read up to and including messageId. */
|
|
7413
|
+
onMessageRead({
|
|
7414
|
+
user,
|
|
7415
|
+
readAt,
|
|
7416
|
+
lastReadMessageId
|
|
7417
|
+
}) {
|
|
7418
|
+
const timestampMs = new Date(readAt).getTime();
|
|
7419
|
+
const msgRef = lastReadMessageId ? { timestampMs, msgId: lastReadMessageId } : this.locateMessage(timestampMs);
|
|
7420
|
+
if (!msgRef) return;
|
|
7421
|
+
const userProgress = this.ensureUser(user);
|
|
7422
|
+
if (compareRefsAsc(msgRef, userProgress.lastReadRef) <= 0) return;
|
|
7423
|
+
removeByOldKey(
|
|
7424
|
+
this.readSorted,
|
|
7425
|
+
userProgress,
|
|
7426
|
+
userProgress.lastReadRef,
|
|
7427
|
+
(x) => x.lastReadRef
|
|
7428
|
+
);
|
|
7429
|
+
userProgress.lastReadRef = msgRef;
|
|
7430
|
+
insertByKey(this.readSorted, userProgress, (x) => x.lastReadRef);
|
|
7431
|
+
if (compareRefsAsc(userProgress.lastDeliveredRef, userProgress.lastReadRef) < 0) {
|
|
7432
|
+
removeByOldKey(
|
|
7433
|
+
this.deliveredSorted,
|
|
7434
|
+
userProgress,
|
|
7435
|
+
userProgress.lastDeliveredRef,
|
|
7436
|
+
(x) => x.lastDeliveredRef
|
|
7437
|
+
);
|
|
7438
|
+
userProgress.lastDeliveredRef = userProgress.lastReadRef;
|
|
7439
|
+
insertByKey(this.deliveredSorted, userProgress, (x) => x.lastDeliveredRef);
|
|
7440
|
+
}
|
|
7441
|
+
}
|
|
7442
|
+
/** notification.mark_unread — user marked messages unread starting at `first_unread_message_id`.
|
|
7443
|
+
* Sets lastReadRef to the event’s last_read_* values. Delivery never moves backward.
|
|
7444
|
+
* The event is sent only to the user that triggered the action (own user), so we will never adjust read ref
|
|
7445
|
+
* for other users - we will not see changes in the UI for other users. However, this implementation does not
|
|
7446
|
+
* take into consideration this fact and is ready to handle the mark-unread event for any user.
|
|
7447
|
+
*/
|
|
7448
|
+
onNotificationMarkUnread({
|
|
7449
|
+
user,
|
|
7450
|
+
lastReadAt,
|
|
7451
|
+
lastReadMessageId
|
|
7452
|
+
}) {
|
|
7453
|
+
const userProgress = this.ensureUser(user);
|
|
7454
|
+
const newReadRef = lastReadAt ? { timestampMs: new Date(lastReadAt).getTime(), msgId: lastReadMessageId ?? "" } : { ...MIN_REF };
|
|
7455
|
+
if (compareRefsAsc(newReadRef, userProgress.lastReadRef) === 0 && newReadRef.msgId === userProgress.lastReadRef.msgId) {
|
|
7456
|
+
return;
|
|
7457
|
+
}
|
|
7458
|
+
removeByOldKey(
|
|
7459
|
+
this.readSorted,
|
|
7460
|
+
userProgress,
|
|
7461
|
+
userProgress.lastReadRef,
|
|
7462
|
+
(x) => x.lastReadRef
|
|
7463
|
+
);
|
|
7464
|
+
userProgress.lastReadRef = newReadRef;
|
|
7465
|
+
insertByKey(this.readSorted, userProgress, (x) => x.lastReadRef);
|
|
7466
|
+
if (compareRefsAsc(userProgress.lastDeliveredRef, userProgress.lastReadRef) < 0) {
|
|
7467
|
+
removeByOldKey(
|
|
7468
|
+
this.deliveredSorted,
|
|
7469
|
+
userProgress,
|
|
7470
|
+
userProgress.lastDeliveredRef,
|
|
7471
|
+
(x) => x.lastDeliveredRef
|
|
7472
|
+
);
|
|
7473
|
+
userProgress.lastDeliveredRef = userProgress.lastReadRef;
|
|
7474
|
+
insertByKey(this.deliveredSorted, userProgress, (x) => x.lastDeliveredRef);
|
|
7475
|
+
}
|
|
7476
|
+
}
|
|
7477
|
+
/** All users who READ this message. */
|
|
7478
|
+
readersForMessage(msgRef) {
|
|
7479
|
+
const index = findIndex(this.readSorted, msgRef, ({ lastReadRef }) => lastReadRef);
|
|
7480
|
+
return this.readSorted.slice(index).map((x) => x.user);
|
|
7481
|
+
}
|
|
7482
|
+
/** All users who have it DELIVERED (includes readers). */
|
|
7483
|
+
deliveredForMessage(msgRef) {
|
|
7484
|
+
const pos = findIndex(
|
|
7485
|
+
this.deliveredSorted,
|
|
7486
|
+
msgRef,
|
|
7487
|
+
({ lastDeliveredRef }) => lastDeliveredRef
|
|
7488
|
+
);
|
|
7489
|
+
return this.deliveredSorted.slice(pos).map((x) => x.user);
|
|
7490
|
+
}
|
|
7491
|
+
/** Users who delivered but have NOT read. */
|
|
7492
|
+
deliveredNotReadForMessage(msgRef) {
|
|
7493
|
+
const pos = findIndex(
|
|
7494
|
+
this.deliveredSorted,
|
|
7495
|
+
msgRef,
|
|
7496
|
+
({ lastDeliveredRef }) => lastDeliveredRef
|
|
7497
|
+
);
|
|
7498
|
+
const usersDeliveredNotRead = [];
|
|
7499
|
+
for (let i = pos; i < this.deliveredSorted.length; i++) {
|
|
7500
|
+
const userProgress = this.deliveredSorted[i];
|
|
7501
|
+
if (compareRefsAsc(userProgress.lastReadRef, msgRef) < 0)
|
|
7502
|
+
usersDeliveredNotRead.push(userProgress.user);
|
|
7503
|
+
}
|
|
7504
|
+
return usersDeliveredNotRead;
|
|
7505
|
+
}
|
|
7506
|
+
/** Users for whom `msgRef` is their *last read* (exact match). */
|
|
7507
|
+
usersWhoseLastReadIs(msgRef) {
|
|
7508
|
+
if (!msgRef.msgId) return [];
|
|
7509
|
+
const start = findIndex(this.readSorted, msgRef, (x) => x.lastReadRef);
|
|
7510
|
+
const end = findUpperIndex(this.readSorted, msgRef, (x) => x.lastReadRef);
|
|
7511
|
+
const users = [];
|
|
7512
|
+
for (let i = start; i < end; i++) {
|
|
7513
|
+
const up = this.readSorted[i];
|
|
7514
|
+
if (up.lastReadRef.msgId === msgRef.msgId) users.push(up.user);
|
|
7515
|
+
}
|
|
7516
|
+
return users;
|
|
7517
|
+
}
|
|
7518
|
+
/** Users for whom `msgRef` is their *last delivered* (exact match). */
|
|
7519
|
+
usersWhoseLastDeliveredIs(msgRef) {
|
|
7520
|
+
if (!msgRef.msgId) return [];
|
|
7521
|
+
const start = findIndex(this.deliveredSorted, msgRef, (x) => x.lastDeliveredRef);
|
|
7522
|
+
const end = findUpperIndex(this.deliveredSorted, msgRef, (x) => x.lastDeliveredRef);
|
|
7523
|
+
const users = [];
|
|
7524
|
+
for (let i = start; i < end; i++) {
|
|
7525
|
+
const up = this.deliveredSorted[i];
|
|
7526
|
+
if (up.lastDeliveredRef.msgId === msgRef.msgId) users.push(up.user);
|
|
7527
|
+
}
|
|
7528
|
+
return users;
|
|
7529
|
+
}
|
|
7530
|
+
// ---- queries: per-user status ----
|
|
7531
|
+
hasUserRead(msgRef, userId) {
|
|
7532
|
+
const up = this.byUser.get(userId);
|
|
7533
|
+
return !!up && compareRefsAsc(up.lastReadRef, msgRef) >= 0;
|
|
7534
|
+
}
|
|
7535
|
+
hasUserDelivered(msgRef, userId) {
|
|
7536
|
+
const up = this.byUser.get(userId);
|
|
7537
|
+
return !!up && compareRefsAsc(up.lastDeliveredRef, msgRef) >= 0;
|
|
7538
|
+
}
|
|
7539
|
+
getUserProgress(userId) {
|
|
7540
|
+
const userProgress = this.byUser.get(userId);
|
|
7541
|
+
if (!userProgress) return null;
|
|
7542
|
+
return userProgress;
|
|
7543
|
+
}
|
|
7544
|
+
groupUsersByLastReadMessage() {
|
|
7545
|
+
return Array.from(this.byUser.values()).reduce(
|
|
7546
|
+
(acc, userProgress) => {
|
|
7547
|
+
const msgId = userProgress.lastReadRef.msgId;
|
|
7548
|
+
if (!msgId) return acc;
|
|
7549
|
+
if (!acc[msgId]) acc[msgId] = [];
|
|
7550
|
+
acc[msgId].push(userProgress.user);
|
|
7551
|
+
return acc;
|
|
7552
|
+
},
|
|
7553
|
+
{}
|
|
7554
|
+
);
|
|
7555
|
+
}
|
|
7556
|
+
groupUsersByLastDeliveredMessage() {
|
|
7557
|
+
return Array.from(this.byUser.values()).reduce(
|
|
7558
|
+
(acc, userProgress) => {
|
|
7559
|
+
const msgId = userProgress.lastDeliveredRef.msgId;
|
|
7560
|
+
if (!msgId) return acc;
|
|
7561
|
+
if (!acc[msgId]) acc[msgId] = [];
|
|
7562
|
+
acc[msgId].push(userProgress.user);
|
|
7563
|
+
return acc;
|
|
7564
|
+
},
|
|
7565
|
+
{}
|
|
7566
|
+
);
|
|
7567
|
+
}
|
|
7568
|
+
ensureUser(user) {
|
|
7569
|
+
let up = this.byUser.get(user.id);
|
|
7570
|
+
if (!up) {
|
|
7571
|
+
up = { user, lastReadRef: MIN_REF, lastDeliveredRef: MIN_REF };
|
|
7572
|
+
this.byUser.set(user.id, up);
|
|
7573
|
+
insertByKey(this.readSorted, up, (x) => x.lastReadRef);
|
|
7574
|
+
insertByKey(this.deliveredSorted, up, (x) => x.lastDeliveredRef);
|
|
7575
|
+
}
|
|
7576
|
+
return up;
|
|
7577
|
+
}
|
|
7578
|
+
};
|
|
7579
|
+
|
|
7048
7580
|
// src/channel.ts
|
|
7049
7581
|
var Channel = class {
|
|
7050
7582
|
/**
|
|
@@ -7126,6 +7658,12 @@ var Channel = class {
|
|
|
7126
7658
|
client: this._client,
|
|
7127
7659
|
compositionContext: this
|
|
7128
7660
|
});
|
|
7661
|
+
this.messageReceiptsTracker = new MessageReceiptsTracker({
|
|
7662
|
+
locateMessage: (timestampMs) => {
|
|
7663
|
+
const msg = this.state.findMessageByTimestamp(timestampMs);
|
|
7664
|
+
return msg && { timestampMs, msgId: msg.id };
|
|
7665
|
+
}
|
|
7666
|
+
});
|
|
7129
7667
|
}
|
|
7130
7668
|
/**
|
|
7131
7669
|
* getClient - Get the chat client for this channel. If client.disconnect() was called, this function will error
|
|
@@ -7935,15 +8473,24 @@ var Channel = class {
|
|
|
7935
8473
|
return messageSlice[0];
|
|
7936
8474
|
}
|
|
7937
8475
|
/**
|
|
7938
|
-
* markRead - Send the mark read event for this user, only works if the `read_events` setting is enabled
|
|
8476
|
+
* 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.
|
|
7939
8477
|
*
|
|
7940
8478
|
* @param {MarkReadOptions} data
|
|
7941
8479
|
* @return {Promise<EventAPIResponse | null>} Description
|
|
7942
8480
|
*/
|
|
7943
8481
|
async markRead(data = {}) {
|
|
8482
|
+
return await this.getClient().messageDeliveryReporter.markRead(this, data);
|
|
8483
|
+
}
|
|
8484
|
+
/**
|
|
8485
|
+
* markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is enabled
|
|
8486
|
+
*
|
|
8487
|
+
* @param {MarkReadOptions} data
|
|
8488
|
+
* @return {Promise<EventAPIResponse | null>} Description
|
|
8489
|
+
*/
|
|
8490
|
+
async markAsReadRequest(data = {}) {
|
|
7944
8491
|
this._checkInitialized();
|
|
7945
8492
|
if (!this.getConfig()?.read_events && !this.getClient()._isUsingServerAuth()) {
|
|
7946
|
-
return
|
|
8493
|
+
return null;
|
|
7947
8494
|
}
|
|
7948
8495
|
return await this.getClient().post(this._channelURL() + "/read", {
|
|
7949
8496
|
...data
|
|
@@ -8251,6 +8798,7 @@ var Channel = class {
|
|
|
8251
8798
|
}),
|
|
8252
8799
|
{ method: "upsertChannels" }
|
|
8253
8800
|
);
|
|
8801
|
+
this.getClient().syncDeliveredCandidates([this]);
|
|
8254
8802
|
return state;
|
|
8255
8803
|
}
|
|
8256
8804
|
/**
|
|
@@ -8522,17 +9070,44 @@ var Channel = class {
|
|
|
8522
9070
|
break;
|
|
8523
9071
|
case "message.read":
|
|
8524
9072
|
if (event.user?.id && event.created_at) {
|
|
9073
|
+
const previousReadState = channelState.read[event.user.id];
|
|
8525
9074
|
channelState.read[event.user.id] = {
|
|
9075
|
+
// in case we already have delivery information
|
|
9076
|
+
...previousReadState,
|
|
8526
9077
|
last_read: new Date(event.created_at),
|
|
8527
9078
|
last_read_message_id: event.last_read_message_id,
|
|
8528
9079
|
user: event.user,
|
|
8529
9080
|
unread_messages: 0
|
|
8530
9081
|
};
|
|
8531
|
-
|
|
9082
|
+
this.messageReceiptsTracker.onMessageRead({
|
|
9083
|
+
user: event.user,
|
|
9084
|
+
readAt: event.created_at,
|
|
9085
|
+
lastReadMessageId: event.last_read_message_id
|
|
9086
|
+
});
|
|
9087
|
+
const client = this.getClient();
|
|
9088
|
+
const isOwnEvent = event.user?.id === client.user?.id;
|
|
9089
|
+
if (isOwnEvent) {
|
|
8532
9090
|
channelState.unreadCount = 0;
|
|
9091
|
+
client.syncDeliveredCandidates([this]);
|
|
8533
9092
|
}
|
|
8534
9093
|
}
|
|
8535
9094
|
break;
|
|
9095
|
+
case "message.delivered":
|
|
9096
|
+
if (event.user?.id && event.created_at) {
|
|
9097
|
+
const previousReadState = channelState.read[event.user.id];
|
|
9098
|
+
channelState.read[event.user.id] = {
|
|
9099
|
+
...previousReadState,
|
|
9100
|
+
last_delivered_at: event.last_delivered_at ? new Date(event.last_delivered_at) : void 0,
|
|
9101
|
+
last_delivered_message_id: event.last_delivered_message_id,
|
|
9102
|
+
user: event.user
|
|
9103
|
+
};
|
|
9104
|
+
this.messageReceiptsTracker.onMessageDelivered({
|
|
9105
|
+
user: event.user,
|
|
9106
|
+
deliveredAt: event.created_at,
|
|
9107
|
+
lastDeliveredMessageId: event.last_delivered_message_id
|
|
9108
|
+
});
|
|
9109
|
+
}
|
|
9110
|
+
break;
|
|
8536
9111
|
case "user.watching.start":
|
|
8537
9112
|
case "user.updated":
|
|
8538
9113
|
if (event.user?.id) {
|
|
@@ -8566,7 +9141,8 @@ var Channel = class {
|
|
|
8566
9141
|
break;
|
|
8567
9142
|
case "message.new":
|
|
8568
9143
|
if (event.message) {
|
|
8569
|
-
const
|
|
9144
|
+
const client = this.getClient();
|
|
9145
|
+
const ownMessage = event.user?.id === client.user?.id;
|
|
8570
9146
|
const isThreadMessage = event.message.parent_id && !event.message.show_in_channel;
|
|
8571
9147
|
if (this.state.isUpToDate || isThreadMessage) {
|
|
8572
9148
|
channelState.addMessageSorted(event.message, ownMessage);
|
|
@@ -8582,7 +9158,9 @@ var Channel = class {
|
|
|
8582
9158
|
channelState.read[event.user.id] = {
|
|
8583
9159
|
last_read: new Date(event.created_at),
|
|
8584
9160
|
user: event.user,
|
|
8585
|
-
unread_messages: 0
|
|
9161
|
+
unread_messages: 0,
|
|
9162
|
+
last_delivered_at: new Date(event.created_at),
|
|
9163
|
+
last_delivered_message_id: event.message.id
|
|
8586
9164
|
};
|
|
8587
9165
|
} else {
|
|
8588
9166
|
channelState.read[userId].unread_messages += 1;
|
|
@@ -8592,6 +9170,7 @@ var Channel = class {
|
|
|
8592
9170
|
if (this._countMessageAsUnread(event.message)) {
|
|
8593
9171
|
channelState.unreadCount = channelState.unreadCount + 1;
|
|
8594
9172
|
}
|
|
9173
|
+
client.syncDeliveredCandidates([this]);
|
|
8595
9174
|
}
|
|
8596
9175
|
break;
|
|
8597
9176
|
case "message.updated":
|
|
@@ -8674,9 +9253,12 @@ var Channel = class {
|
|
|
8674
9253
|
break;
|
|
8675
9254
|
case "notification.mark_unread": {
|
|
8676
9255
|
const ownMessage = event.user?.id === this.getClient().user?.id;
|
|
8677
|
-
if (!
|
|
9256
|
+
if (!ownMessage || !event.user) break;
|
|
8678
9257
|
const unreadCount = event.unread_messages ?? 0;
|
|
9258
|
+
const currentState = channelState.read[event.user.id];
|
|
8679
9259
|
channelState.read[event.user.id] = {
|
|
9260
|
+
// keep the message delivery info
|
|
9261
|
+
...currentState,
|
|
8680
9262
|
first_unread_message_id: event.first_unread_message_id,
|
|
8681
9263
|
last_read: new Date(event.last_read_at),
|
|
8682
9264
|
last_read_message_id: event.last_read_message_id,
|
|
@@ -8684,6 +9266,11 @@ var Channel = class {
|
|
|
8684
9266
|
unread_messages: unreadCount
|
|
8685
9267
|
};
|
|
8686
9268
|
channelState.unreadCount = unreadCount;
|
|
9269
|
+
this.messageReceiptsTracker.onNotificationMarkUnread({
|
|
9270
|
+
user: event.user,
|
|
9271
|
+
lastReadAt: event.last_read_at,
|
|
9272
|
+
lastReadMessageId: event.last_read_message_id
|
|
9273
|
+
});
|
|
8687
9274
|
break;
|
|
8688
9275
|
}
|
|
8689
9276
|
case "channel.updated":
|
|
@@ -8835,6 +9422,7 @@ var Channel = class {
|
|
|
8835
9422
|
this.state.unreadCount = this.state.read[read.user.id].unread_messages;
|
|
8836
9423
|
}
|
|
8837
9424
|
}
|
|
9425
|
+
this.messageReceiptsTracker.ingestInitial(state.read);
|
|
8838
9426
|
}
|
|
8839
9427
|
return {
|
|
8840
9428
|
messageSet
|
|
@@ -12385,6 +12973,7 @@ var StreamChat = class _StreamChat {
|
|
|
12385
12973
|
this.threads = new ThreadManager({ client: this });
|
|
12386
12974
|
this.polls = new PollManager({ client: this });
|
|
12387
12975
|
this.reminders = new ReminderManager({ client: this });
|
|
12976
|
+
this.messageDeliveryReporter = new MessageDeliveryReporter({ client: this });
|
|
12388
12977
|
}
|
|
12389
12978
|
static getInstance(key, secretOrOptions, options) {
|
|
12390
12979
|
if (!_StreamChat._instance) {
|
|
@@ -13055,6 +13644,7 @@ var StreamChat = class _StreamChat {
|
|
|
13055
13644
|
c.messageComposer.initStateFromChannelResponse(channelState);
|
|
13056
13645
|
channels.push(c);
|
|
13057
13646
|
}
|
|
13647
|
+
this.syncDeliveredCandidates(channels);
|
|
13058
13648
|
return channels;
|
|
13059
13649
|
}
|
|
13060
13650
|
/**
|
|
@@ -13819,10 +14409,26 @@ var StreamChat = class _StreamChat {
|
|
|
13819
14409
|
}
|
|
13820
14410
|
);
|
|
13821
14411
|
}
|
|
13822
|
-
|
|
14412
|
+
/**
|
|
14413
|
+
* deleteMessage - Delete a message
|
|
14414
|
+
*
|
|
14415
|
+
* @param {string} messageID The id of the message to delete
|
|
14416
|
+
* @param {boolean | DeleteMessageOptions | undefined} [optionsOrHardDelete]
|
|
14417
|
+
* @return {Promise<APIResponse & { message: MessageResponse }>} The API response
|
|
14418
|
+
*/
|
|
14419
|
+
// fixme: remove the signature with optionsOrHardDelete boolean with the next major release
|
|
14420
|
+
async deleteMessage(messageID, optionsOrHardDelete) {
|
|
14421
|
+
let options = {};
|
|
14422
|
+
if (typeof optionsOrHardDelete === "boolean") {
|
|
14423
|
+
options = optionsOrHardDelete ? { hardDelete: true } : {};
|
|
14424
|
+
} else if (optionsOrHardDelete?.deleteForMe) {
|
|
14425
|
+
options = { deleteForMe: true };
|
|
14426
|
+
} else if (optionsOrHardDelete?.hardDelete) {
|
|
14427
|
+
options = { hardDelete: true };
|
|
14428
|
+
}
|
|
13823
14429
|
try {
|
|
13824
14430
|
if (this.offlineDb) {
|
|
13825
|
-
if (hardDelete) {
|
|
14431
|
+
if (options.hardDelete) {
|
|
13826
14432
|
await this.offlineDb.hardDeleteMessage({ id: messageID });
|
|
13827
14433
|
} else {
|
|
13828
14434
|
await this.offlineDb.softDeleteMessage({ id: messageID });
|
|
@@ -13831,7 +14437,7 @@ var StreamChat = class _StreamChat {
|
|
|
13831
14437
|
{
|
|
13832
14438
|
task: {
|
|
13833
14439
|
messageId: messageID,
|
|
13834
|
-
payload: [messageID,
|
|
14440
|
+
payload: [messageID, options],
|
|
13835
14441
|
type: "delete-message"
|
|
13836
14442
|
}
|
|
13837
14443
|
}
|
|
@@ -13843,17 +14449,27 @@ var StreamChat = class _StreamChat {
|
|
|
13843
14449
|
error
|
|
13844
14450
|
});
|
|
13845
14451
|
}
|
|
13846
|
-
return this._deleteMessage(messageID,
|
|
14452
|
+
return this._deleteMessage(messageID, options);
|
|
13847
14453
|
}
|
|
13848
|
-
|
|
14454
|
+
// fixme: remove the signature with optionsOrHardDelete boolean with the next major release
|
|
14455
|
+
async _deleteMessage(messageID, optionsOrHardDelete) {
|
|
14456
|
+
const { deleteForMe, hardDelete } = typeof optionsOrHardDelete === "boolean" ? { hardDelete: optionsOrHardDelete } : optionsOrHardDelete ?? {};
|
|
13849
14457
|
let params = {};
|
|
13850
14458
|
if (hardDelete) {
|
|
13851
14459
|
params = { hard: true };
|
|
13852
14460
|
}
|
|
13853
|
-
|
|
14461
|
+
if (deleteForMe) {
|
|
14462
|
+
params = { ...params, delete_for_me: true };
|
|
14463
|
+
}
|
|
14464
|
+
const result = await this.delete(
|
|
13854
14465
|
this.baseURL + `/messages/${encodeURIComponent(messageID)}`,
|
|
13855
14466
|
params
|
|
13856
14467
|
);
|
|
14468
|
+
if (deleteForMe) {
|
|
14469
|
+
result.message.deleted_for_me = true;
|
|
14470
|
+
result.message.type = "deleted";
|
|
14471
|
+
}
|
|
14472
|
+
return result;
|
|
13857
14473
|
}
|
|
13858
14474
|
/**
|
|
13859
14475
|
* undeleteMessage - Undelete a message
|
|
@@ -13989,7 +14605,7 @@ var StreamChat = class _StreamChat {
|
|
|
13989
14605
|
if (this.userAgent) {
|
|
13990
14606
|
return this.userAgent;
|
|
13991
14607
|
}
|
|
13992
|
-
const version = "9.
|
|
14608
|
+
const version = "9.22.0";
|
|
13993
14609
|
const clientBundle = "node-cjs";
|
|
13994
14610
|
let userAgentString = "";
|
|
13995
14611
|
if (this.sdkIdentifier) {
|
|
@@ -15138,19 +15754,21 @@ var StreamChat = class _StreamChat {
|
|
|
15138
15754
|
return this.delete(`${this.baseURL}/uploads/image`, { url });
|
|
15139
15755
|
}
|
|
15140
15756
|
/**
|
|
15141
|
-
* Send the mark delivered event for this user
|
|
15757
|
+
* Send the mark delivered event for this user
|
|
15142
15758
|
*
|
|
15143
15759
|
* @param {MarkDeliveredOptions} data
|
|
15144
15760
|
* @return {Promise<EventAPIResponse | void>} Description
|
|
15145
15761
|
*/
|
|
15146
15762
|
async markChannelsDelivered(data) {
|
|
15147
|
-
|
|
15148
|
-
if (!deliveryReceiptsEnabled) return;
|
|
15763
|
+
if (!data?.latest_delivered_messages?.length) return;
|
|
15149
15764
|
return await this.post(
|
|
15150
15765
|
this.baseURL + "/channels/delivered",
|
|
15151
15766
|
data ?? {}
|
|
15152
15767
|
);
|
|
15153
15768
|
}
|
|
15769
|
+
syncDeliveredCandidates(collections) {
|
|
15770
|
+
this.messageDeliveryReporter.syncDeliveredCandidates(collections);
|
|
15771
|
+
}
|
|
15154
15772
|
};
|
|
15155
15773
|
|
|
15156
15774
|
// src/events.ts
|
|
@@ -15187,6 +15805,7 @@ var EVENT_MAP = {
|
|
|
15187
15805
|
"notification.mark_unread": true,
|
|
15188
15806
|
"notification.message_new": true,
|
|
15189
15807
|
"notification.mutes_updated": true,
|
|
15808
|
+
"notification.reminder_due": true,
|
|
15190
15809
|
"notification.removed_from_channel": true,
|
|
15191
15810
|
"notification.thread_message_new": true,
|
|
15192
15811
|
"poll.closed": true,
|
|
@@ -15197,6 +15816,9 @@ var EVENT_MAP = {
|
|
|
15197
15816
|
"reaction.deleted": true,
|
|
15198
15817
|
"reaction.new": true,
|
|
15199
15818
|
"reaction.updated": true,
|
|
15819
|
+
"reminder.created": true,
|
|
15820
|
+
"reminder.deleted": true,
|
|
15821
|
+
"reminder.updated": true,
|
|
15200
15822
|
"thread.updated": true,
|
|
15201
15823
|
"typing.start": true,
|
|
15202
15824
|
"typing.stop": true,
|
|
@@ -15221,12 +15843,7 @@ var EVENT_MAP = {
|
|
|
15221
15843
|
"transport.changed": true,
|
|
15222
15844
|
"capabilities.changed": true,
|
|
15223
15845
|
"live_location_sharing.started": true,
|
|
15224
|
-
"live_location_sharing.stopped": true
|
|
15225
|
-
// Reminder events
|
|
15226
|
-
"reminder.created": true,
|
|
15227
|
-
"reminder.updated": true,
|
|
15228
|
-
"reminder.deleted": true,
|
|
15229
|
-
"notification.reminder_due": true
|
|
15846
|
+
"live_location_sharing.stopped": true
|
|
15230
15847
|
};
|
|
15231
15848
|
|
|
15232
15849
|
// src/permissions.ts
|
|
@@ -16384,7 +17001,9 @@ var FixedSizeQueueCache = class {
|
|
|
16384
17001
|
MergedStateStore,
|
|
16385
17002
|
MessageComposer,
|
|
16386
17003
|
MessageComposerMiddlewareExecutor,
|
|
17004
|
+
MessageDeliveryReporter,
|
|
16387
17005
|
MessageDraftComposerMiddlewareExecutor,
|
|
17006
|
+
MessageReceiptsTracker,
|
|
16388
17007
|
MessageSearchSource,
|
|
16389
17008
|
MiddlewareExecutor,
|
|
16390
17009
|
MinPriority,
|