stream-chat 9.20.3 → 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 +674 -42
- package/dist/cjs/index.browser.js.map +4 -4
- package/dist/cjs/index.node.js +676 -42
- package/dist/cjs/index.node.js.map +4 -4
- package/dist/esm/index.mjs +674 -42
- package/dist/esm/index.mjs.map +4 -4
- package/dist/types/channel.d.ts +11 -2
- package/dist/types/channel_state.d.ts +8 -0
- package/dist/types/client.d.ts +22 -3
- package/dist/types/events.d.ts +5 -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 +31 -4
- package/dist/types/utils.d.ts +2 -1
- package/package.json +1 -1
- package/src/channel.ts +77 -7
- package/src/channel_state.ts +149 -14
- package/src/client.ts +73 -8
- package/src/events.ts +5 -6
- package/src/index.ts +1 -0
- package/src/messageComposer/messageComposer.ts +3 -6
- package/src/messageComposer/textComposer.ts +9 -3
- 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 +36 -5
- package/src/utils.ts +2 -1
|
@@ -96,7 +96,9 @@ __export(index_exports, {
|
|
|
96
96
|
MergedStateStore: () => MergedStateStore,
|
|
97
97
|
MessageComposer: () => MessageComposer,
|
|
98
98
|
MessageComposerMiddlewareExecutor: () => MessageComposerMiddlewareExecutor,
|
|
99
|
+
MessageDeliveryReporter: () => MessageDeliveryReporter,
|
|
99
100
|
MessageDraftComposerMiddlewareExecutor: () => MessageDraftComposerMiddlewareExecutor,
|
|
101
|
+
MessageReceiptsTracker: () => MessageReceiptsTracker,
|
|
100
102
|
MessageSearchSource: () => MessageSearchSource,
|
|
101
103
|
MiddlewareExecutor: () => MiddlewareExecutor,
|
|
102
104
|
MinPriority: () => MinPriority,
|
|
@@ -1147,6 +1149,21 @@ var runDetached = (callback, options) => {
|
|
|
1147
1149
|
};
|
|
1148
1150
|
|
|
1149
1151
|
// src/channel_state.ts
|
|
1152
|
+
var messageSetBounds = (a, b) => ({
|
|
1153
|
+
newestMessageA: new Date(a[0]?.created_at ?? 0),
|
|
1154
|
+
oldestMessageA: new Date(a.slice(-1)[0]?.created_at ?? 0),
|
|
1155
|
+
newestMessageB: new Date(b[0]?.created_at ?? 0),
|
|
1156
|
+
oldestMessageB: new Date(b.slice(-1)[0]?.created_at ?? 0)
|
|
1157
|
+
});
|
|
1158
|
+
var aContainsOrEqualsB = (a, b) => {
|
|
1159
|
+
const { newestMessageA, newestMessageB, oldestMessageA, oldestMessageB } = messageSetBounds(a, b);
|
|
1160
|
+
return newestMessageA >= newestMessageB && oldestMessageB >= oldestMessageA;
|
|
1161
|
+
};
|
|
1162
|
+
var aOverlapsB = (a, b) => {
|
|
1163
|
+
const { newestMessageA, newestMessageB, oldestMessageA, oldestMessageB } = messageSetBounds(a, b);
|
|
1164
|
+
return oldestMessageA < oldestMessageB && oldestMessageB < newestMessageA && newestMessageA < newestMessageB;
|
|
1165
|
+
};
|
|
1166
|
+
var messageSetsOverlapByTimestamp = (a, b) => aContainsOrEqualsB(a, b) || aContainsOrEqualsB(b, a) || aOverlapsB(a, b) || aOverlapsB(b, a);
|
|
1150
1167
|
var ChannelState = class {
|
|
1151
1168
|
constructor(channel) {
|
|
1152
1169
|
/**
|
|
@@ -1225,6 +1242,22 @@ var ChannelState = class {
|
|
|
1225
1242
|
deletedAt: deletedAt ?? null
|
|
1226
1243
|
});
|
|
1227
1244
|
};
|
|
1245
|
+
/**
|
|
1246
|
+
* Identifies the set index into which a message set would pertain if its first item's creation date corresponded to oldestTimestampMs.
|
|
1247
|
+
* @param oldestTimestampMs
|
|
1248
|
+
*/
|
|
1249
|
+
this.findMessageSetByOldestTimestamp = (oldestTimestampMs) => {
|
|
1250
|
+
let lo = 0, hi = this.messageSets.length;
|
|
1251
|
+
while (lo < hi) {
|
|
1252
|
+
const mid = lo + hi >>> 1;
|
|
1253
|
+
const msgSet = this.messageSets[mid];
|
|
1254
|
+
if (msgSet.messages.length === 0) return -1;
|
|
1255
|
+
const oldestMessageTimestampInSet = msgSet.messages[0].created_at.getTime();
|
|
1256
|
+
if (oldestMessageTimestampInSet <= oldestTimestampMs) hi = mid;
|
|
1257
|
+
else lo = mid + 1;
|
|
1258
|
+
}
|
|
1259
|
+
return lo;
|
|
1260
|
+
};
|
|
1228
1261
|
this._channel = channel;
|
|
1229
1262
|
this.watcher_count = 0;
|
|
1230
1263
|
this.typing = {};
|
|
@@ -1750,6 +1783,25 @@ var ChannelState = class {
|
|
|
1750
1783
|
}
|
|
1751
1784
|
return this.messageSets[messageSetIndex].messages.find((m) => m.id === messageId);
|
|
1752
1785
|
}
|
|
1786
|
+
findMessageByTimestamp(timestampMs, parentMessageId, exactTsMatch = false) {
|
|
1787
|
+
if (parentMessageId && !this.threads[parentMessageId] || this.messageSets.length === 0)
|
|
1788
|
+
return null;
|
|
1789
|
+
const setIndex = this.findMessageSetByOldestTimestamp(timestampMs);
|
|
1790
|
+
const targetMsgSet = this.messageSets[setIndex]?.messages;
|
|
1791
|
+
if (!targetMsgSet?.length) return null;
|
|
1792
|
+
const firstMsgTimestamp = targetMsgSet[0].created_at.getTime();
|
|
1793
|
+
const lastMsgTimestamp = targetMsgSet.slice(-1)[0].created_at.getTime();
|
|
1794
|
+
const isOutOfBound = timestampMs < firstMsgTimestamp || lastMsgTimestamp < timestampMs;
|
|
1795
|
+
if (isOutOfBound && exactTsMatch) return null;
|
|
1796
|
+
let msgIndex = 0, hi = targetMsgSet.length - 1;
|
|
1797
|
+
while (msgIndex < hi) {
|
|
1798
|
+
const mid = msgIndex + hi >>> 1;
|
|
1799
|
+
if (timestampMs <= targetMsgSet[mid].created_at.getTime()) hi = mid;
|
|
1800
|
+
else msgIndex = mid + 1;
|
|
1801
|
+
}
|
|
1802
|
+
const foundMessage = targetMsgSet[msgIndex];
|
|
1803
|
+
return !exactTsMatch ? foundMessage : foundMessage.created_at.getTime() === timestampMs ? foundMessage : null;
|
|
1804
|
+
}
|
|
1753
1805
|
switchToMessageSet(index) {
|
|
1754
1806
|
const currentMessages = this.messageSets.find((s) => s.isCurrent);
|
|
1755
1807
|
if (!currentMessages) {
|
|
@@ -1769,35 +1821,77 @@ var ChannelState = class {
|
|
|
1769
1821
|
findTargetMessageSet(newMessages, addIfDoesNotExist = true, messageSetToAddToIfDoesNotExist = "current") {
|
|
1770
1822
|
let messagesToAdd = newMessages;
|
|
1771
1823
|
let targetMessageSetIndex;
|
|
1824
|
+
if (newMessages.length === 0)
|
|
1825
|
+
return { targetMessageSetIndex: 0, messagesToAdd: newMessages };
|
|
1772
1826
|
if (addIfDoesNotExist) {
|
|
1773
|
-
const
|
|
1827
|
+
const overlappingMessageSetIndicesByMsgIds = this.messageSets.map((_, i) => i).filter(
|
|
1774
1828
|
(i) => this.areMessageSetsOverlap(this.messageSets[i].messages, newMessages)
|
|
1775
1829
|
);
|
|
1830
|
+
const overlappingMessageSetIndicesByTimestamp = this.messageSets.map((_, i) => i).filter(
|
|
1831
|
+
(i) => messageSetsOverlapByTimestamp(
|
|
1832
|
+
this.messageSets[i].messages,
|
|
1833
|
+
newMessages.map(formatMessage)
|
|
1834
|
+
)
|
|
1835
|
+
);
|
|
1776
1836
|
switch (messageSetToAddToIfDoesNotExist) {
|
|
1777
1837
|
case "new":
|
|
1778
|
-
if (
|
|
1779
|
-
targetMessageSetIndex =
|
|
1838
|
+
if (overlappingMessageSetIndicesByMsgIds.length > 0) {
|
|
1839
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByMsgIds[0];
|
|
1840
|
+
} else if (overlappingMessageSetIndicesByTimestamp.length > 0) {
|
|
1841
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByTimestamp[0];
|
|
1780
1842
|
} else if (newMessages.some((m) => !m.parent_id)) {
|
|
1781
|
-
this.
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1843
|
+
const setIngestIndex = this.findMessageSetByOldestTimestamp(
|
|
1844
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1845
|
+
new Date(newMessages[0].created_at).getTime()
|
|
1846
|
+
);
|
|
1847
|
+
if (setIngestIndex === -1) {
|
|
1848
|
+
this.messageSets.push({
|
|
1849
|
+
messages: [],
|
|
1850
|
+
isCurrent: false,
|
|
1851
|
+
isLatest: false,
|
|
1852
|
+
pagination: DEFAULT_MESSAGE_SET_PAGINATION
|
|
1853
|
+
});
|
|
1854
|
+
targetMessageSetIndex = this.messageSets.length - 1;
|
|
1855
|
+
} else {
|
|
1856
|
+
const isLatest = setIngestIndex === 0;
|
|
1857
|
+
this.messageSets.splice(setIngestIndex, 0, {
|
|
1858
|
+
messages: [],
|
|
1859
|
+
isCurrent: false,
|
|
1860
|
+
isLatest,
|
|
1861
|
+
pagination: DEFAULT_MESSAGE_SET_PAGINATION
|
|
1862
|
+
// fixme: it is problematic decide about pagination without having data
|
|
1863
|
+
});
|
|
1864
|
+
if (isLatest) {
|
|
1865
|
+
this.messageSets.slice(1).forEach((set) => {
|
|
1866
|
+
set.isLatest = false;
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
targetMessageSetIndex = setIngestIndex;
|
|
1870
|
+
}
|
|
1788
1871
|
}
|
|
1789
1872
|
break;
|
|
1790
1873
|
case "current":
|
|
1791
|
-
|
|
1874
|
+
if (overlappingMessageSetIndicesByTimestamp.length > 0) {
|
|
1875
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByTimestamp[0];
|
|
1876
|
+
} else {
|
|
1877
|
+
targetMessageSetIndex = this.messageSets.findIndex((s) => s.isCurrent);
|
|
1878
|
+
}
|
|
1792
1879
|
break;
|
|
1793
1880
|
case "latest":
|
|
1794
|
-
|
|
1881
|
+
if (overlappingMessageSetIndicesByTimestamp.length > 0) {
|
|
1882
|
+
targetMessageSetIndex = overlappingMessageSetIndicesByTimestamp[0];
|
|
1883
|
+
} else {
|
|
1884
|
+
targetMessageSetIndex = this.messageSets.findIndex((s) => s.isLatest);
|
|
1885
|
+
}
|
|
1795
1886
|
break;
|
|
1796
1887
|
default:
|
|
1797
1888
|
targetMessageSetIndex = -1;
|
|
1798
1889
|
}
|
|
1799
|
-
const mergeTargetMessageSetIndex =
|
|
1800
|
-
|
|
1890
|
+
const mergeTargetMessageSetIndex = overlappingMessageSetIndicesByMsgIds.splice(
|
|
1891
|
+
0,
|
|
1892
|
+
1
|
|
1893
|
+
)[0];
|
|
1894
|
+
const mergeSourceMessageSetIndices = [...overlappingMessageSetIndicesByMsgIds];
|
|
1801
1895
|
if (mergeTargetMessageSetIndex !== void 0 && mergeTargetMessageSetIndex !== targetMessageSetIndex) {
|
|
1802
1896
|
mergeSourceMessageSetIndices.push(targetMessageSetIndex);
|
|
1803
1897
|
}
|
|
@@ -5811,19 +5905,20 @@ var TextComposer = class {
|
|
|
5811
5905
|
selection
|
|
5812
5906
|
}) => {
|
|
5813
5907
|
if (!this.enabled) return;
|
|
5908
|
+
const normalizedText = text.replace(/\r\n/g, "\n");
|
|
5814
5909
|
const finalSelection = selection ?? this.selection;
|
|
5815
5910
|
const { maxLengthOnEdit } = this.composer.config.text ?? {};
|
|
5816
5911
|
const currentText = this.text;
|
|
5817
5912
|
const textBeforeTrim = [
|
|
5818
5913
|
currentText.slice(0, finalSelection.start),
|
|
5819
|
-
|
|
5914
|
+
normalizedText,
|
|
5820
5915
|
currentText.slice(finalSelection.end)
|
|
5821
5916
|
].join("");
|
|
5822
5917
|
const finalText = textBeforeTrim.slice(
|
|
5823
5918
|
0,
|
|
5824
5919
|
typeof maxLengthOnEdit === "number" ? maxLengthOnEdit : textBeforeTrim.length
|
|
5825
5920
|
);
|
|
5826
|
-
const expectedCursorPosition = finalSelection.start +
|
|
5921
|
+
const expectedCursorPosition = finalSelection.start + normalizedText.length;
|
|
5827
5922
|
const cursorPosition = Math.min(expectedCursorPosition, finalText.length);
|
|
5828
5923
|
await this.handleChange({
|
|
5829
5924
|
text: finalText,
|
|
@@ -6329,7 +6424,7 @@ var Thread = class extends WithSubscriptions {
|
|
|
6329
6424
|
if (this.ownUnreadCount === 0 && !force) {
|
|
6330
6425
|
return null;
|
|
6331
6426
|
}
|
|
6332
|
-
return await this.
|
|
6427
|
+
return await this.client.messageDeliveryReporter.markRead(this);
|
|
6333
6428
|
};
|
|
6334
6429
|
this.throttledMarkAsRead = throttle(
|
|
6335
6430
|
() => this.markAsRead(),
|
|
@@ -6857,12 +6952,9 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
|
|
|
6857
6952
|
);
|
|
6858
6953
|
this.initState({ composition: draft });
|
|
6859
6954
|
} catch (error) {
|
|
6860
|
-
this.client.
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
emitter: "MessageComposer",
|
|
6864
|
-
context: { composer: this }
|
|
6865
|
-
}
|
|
6955
|
+
this.client.logger("error", `messageComposer:getDraft`, {
|
|
6956
|
+
tags: ["channel", "messageComposer"],
|
|
6957
|
+
error
|
|
6866
6958
|
});
|
|
6867
6959
|
}
|
|
6868
6960
|
};
|
|
@@ -7062,6 +7154,444 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
|
|
|
7062
7154
|
_MessageComposer.generateId = generateUUIDv4;
|
|
7063
7155
|
var MessageComposer = _MessageComposer;
|
|
7064
7156
|
|
|
7157
|
+
// src/messageDelivery/MessageDeliveryReporter.ts
|
|
7158
|
+
var MAX_DELIVERED_MESSAGE_COUNT_IN_PAYLOAD = 100;
|
|
7159
|
+
var MARK_AS_DELIVERED_BUFFER_TIMEOUT = 1e3;
|
|
7160
|
+
var MARK_AS_READ_THROTTLE_TIMEOUT2 = 1e3;
|
|
7161
|
+
var isChannel = (item) => item instanceof Channel;
|
|
7162
|
+
var isThread = (item) => item instanceof Thread;
|
|
7163
|
+
var MessageDeliveryReporter = class {
|
|
7164
|
+
constructor({ client }) {
|
|
7165
|
+
this.deliveryReportCandidates = /* @__PURE__ */ new Map();
|
|
7166
|
+
this.nextDeliveryReportCandidates = /* @__PURE__ */ new Map();
|
|
7167
|
+
this.markDeliveredRequestPromise = null;
|
|
7168
|
+
this.markDeliveredTimeout = null;
|
|
7169
|
+
/**
|
|
7170
|
+
* Retrieve the reference to the latest message in the state that is nor read neither reported as delivered
|
|
7171
|
+
* @param collection
|
|
7172
|
+
*/
|
|
7173
|
+
this.getNextDeliveryReportCandidate = (collection) => {
|
|
7174
|
+
const ownUserId = this.client.user?.id;
|
|
7175
|
+
if (!ownUserId) return;
|
|
7176
|
+
let latestMessages = [];
|
|
7177
|
+
let lastDeliveredAt;
|
|
7178
|
+
let lastReadAt;
|
|
7179
|
+
let key = void 0;
|
|
7180
|
+
if (isChannel(collection)) {
|
|
7181
|
+
latestMessages = collection.state.latestMessages;
|
|
7182
|
+
const ownReadState = collection.state.read[ownUserId] ?? {};
|
|
7183
|
+
lastReadAt = ownReadState?.last_read;
|
|
7184
|
+
lastDeliveredAt = ownReadState?.last_delivered_at;
|
|
7185
|
+
key = collection.cid;
|
|
7186
|
+
} else if (isThread(collection)) {
|
|
7187
|
+
latestMessages = collection.state.getLatestValue().replies;
|
|
7188
|
+
const ownReadState = collection.state.getLatestValue().read[ownUserId] ?? {};
|
|
7189
|
+
lastReadAt = ownReadState?.lastReadAt;
|
|
7190
|
+
lastDeliveredAt = ownReadState?.lastDeliveredAt;
|
|
7191
|
+
key = `${collection.channel.cid}:${collection.id}`;
|
|
7192
|
+
return;
|
|
7193
|
+
} else {
|
|
7194
|
+
return;
|
|
7195
|
+
}
|
|
7196
|
+
if (!key) return;
|
|
7197
|
+
const [latestMessage] = latestMessages.slice(-1);
|
|
7198
|
+
const wholeCollectionIsRead = !latestMessage || lastReadAt >= latestMessage.created_at;
|
|
7199
|
+
if (wholeCollectionIsRead) return { key, id: null };
|
|
7200
|
+
const wholeCollectionIsMarkedDelivered = !latestMessage || (lastDeliveredAt ?? 0) >= latestMessage.created_at;
|
|
7201
|
+
if (wholeCollectionIsMarkedDelivered) return { key, id: null };
|
|
7202
|
+
return { key, id: latestMessage.id || null };
|
|
7203
|
+
};
|
|
7204
|
+
/**
|
|
7205
|
+
* Fires delivery announcement request followed by immediate delivery candidate buffer reset.
|
|
7206
|
+
* @param options
|
|
7207
|
+
*/
|
|
7208
|
+
this.announceDelivery = (options) => {
|
|
7209
|
+
if (this.markDeliveredRequestInFlight || !this.hasDeliveryCandidates) return;
|
|
7210
|
+
const { latest_delivered_messages, sendBuffer } = this.confirmationsFromDeliveryReportCandidates();
|
|
7211
|
+
if (!latest_delivered_messages.length) return;
|
|
7212
|
+
const payload = { ...options, latest_delivered_messages };
|
|
7213
|
+
const postFlightReconcile = () => {
|
|
7214
|
+
this.markDeliveredRequestPromise = null;
|
|
7215
|
+
for (const [k, v] of this.nextDeliveryReportCandidates.entries()) {
|
|
7216
|
+
this.deliveryReportCandidates.set(k, v);
|
|
7217
|
+
}
|
|
7218
|
+
this.nextDeliveryReportCandidates = /* @__PURE__ */ new Map();
|
|
7219
|
+
this.announceDeliveryBuffered(options);
|
|
7220
|
+
};
|
|
7221
|
+
const handleError = () => {
|
|
7222
|
+
for (const [k, v] of Object.entries(sendBuffer)) {
|
|
7223
|
+
if (!this.deliveryReportCandidates.has(k)) {
|
|
7224
|
+
this.deliveryReportCandidates.set(k, v);
|
|
7225
|
+
}
|
|
7226
|
+
}
|
|
7227
|
+
postFlightReconcile();
|
|
7228
|
+
};
|
|
7229
|
+
this.markDeliveredRequestPromise = this.client.markChannelsDelivered(payload).then(postFlightReconcile, handleError);
|
|
7230
|
+
};
|
|
7231
|
+
this.announceDeliveryBuffered = (options) => {
|
|
7232
|
+
if (this.hasTimer || this.markDeliveredRequestInFlight || !this.hasDeliveryCandidates)
|
|
7233
|
+
return;
|
|
7234
|
+
this.markDeliveredTimeout = setTimeout(() => {
|
|
7235
|
+
this.markDeliveredTimeout = null;
|
|
7236
|
+
this.announceDelivery(options);
|
|
7237
|
+
}, MARK_AS_DELIVERED_BUFFER_TIMEOUT);
|
|
7238
|
+
};
|
|
7239
|
+
/**
|
|
7240
|
+
* Delegates the mark-read call to the Channel or Thread instance
|
|
7241
|
+
* @param collection
|
|
7242
|
+
* @param options
|
|
7243
|
+
*/
|
|
7244
|
+
this.markRead = async (collection, options) => {
|
|
7245
|
+
let result = null;
|
|
7246
|
+
if (isChannel(collection)) {
|
|
7247
|
+
result = await collection.markAsReadRequest(options);
|
|
7248
|
+
} else if (isThread(collection)) {
|
|
7249
|
+
result = await collection.channel.markAsReadRequest({
|
|
7250
|
+
...options,
|
|
7251
|
+
thread_id: collection.id
|
|
7252
|
+
});
|
|
7253
|
+
}
|
|
7254
|
+
this.removeCandidateFor(collection);
|
|
7255
|
+
return result;
|
|
7256
|
+
};
|
|
7257
|
+
/**
|
|
7258
|
+
* Throttles the MessageDeliveryReporter.markRead call
|
|
7259
|
+
* @param collection
|
|
7260
|
+
* @param options
|
|
7261
|
+
*/
|
|
7262
|
+
this.throttledMarkRead = throttle(this.markRead, MARK_AS_READ_THROTTLE_TIMEOUT2, {
|
|
7263
|
+
leading: false,
|
|
7264
|
+
trailing: true
|
|
7265
|
+
});
|
|
7266
|
+
this.client = client;
|
|
7267
|
+
}
|
|
7268
|
+
get markDeliveredRequestInFlight() {
|
|
7269
|
+
return this.markDeliveredRequestPromise !== null;
|
|
7270
|
+
}
|
|
7271
|
+
get hasTimer() {
|
|
7272
|
+
return this.markDeliveredTimeout !== null;
|
|
7273
|
+
}
|
|
7274
|
+
get hasDeliveryCandidates() {
|
|
7275
|
+
return this.deliveryReportCandidates.size > 0;
|
|
7276
|
+
}
|
|
7277
|
+
/**
|
|
7278
|
+
* Build latest_delivered_messages payload from an arbitrary buffer (deliveryReportCandidates / nextDeliveryReportCandidates)
|
|
7279
|
+
*/
|
|
7280
|
+
confirmationsFrom(map2) {
|
|
7281
|
+
return Array.from(map2.entries()).map(([key, messageId]) => {
|
|
7282
|
+
const [type, id, parent_id] = key.split(":");
|
|
7283
|
+
return parent_id ? { cid: `${type}:${id}`, id: messageId, parent_id } : { cid: key, id: messageId };
|
|
7284
|
+
});
|
|
7285
|
+
}
|
|
7286
|
+
confirmationsFromDeliveryReportCandidates() {
|
|
7287
|
+
const entries = Array.from(this.deliveryReportCandidates);
|
|
7288
|
+
const sendBuffer = new Map(entries.slice(0, MAX_DELIVERED_MESSAGE_COUNT_IN_PAYLOAD));
|
|
7289
|
+
this.deliveryReportCandidates = new Map(
|
|
7290
|
+
entries.slice(MAX_DELIVERED_MESSAGE_COUNT_IN_PAYLOAD)
|
|
7291
|
+
);
|
|
7292
|
+
return { latest_delivered_messages: this.confirmationsFrom(sendBuffer), sendBuffer };
|
|
7293
|
+
}
|
|
7294
|
+
/**
|
|
7295
|
+
* Generate candidate key for storing in the candidates buffer
|
|
7296
|
+
* @param collection
|
|
7297
|
+
* @private
|
|
7298
|
+
*/
|
|
7299
|
+
candidateKeyFor(collection) {
|
|
7300
|
+
if (isChannel(collection)) return collection.cid;
|
|
7301
|
+
if (isThread(collection)) return `${collection.channel.cid}:${collection.id}`;
|
|
7302
|
+
}
|
|
7303
|
+
/**
|
|
7304
|
+
* Updates the delivery candidates buffer with the latest delivery candidates
|
|
7305
|
+
* @param collection
|
|
7306
|
+
*/
|
|
7307
|
+
trackDeliveredCandidate(collection) {
|
|
7308
|
+
if (isChannel(collection) && !collection.getConfig()?.read_events) return;
|
|
7309
|
+
if (isThread(collection) && !collection.channel.getConfig()?.read_events) return;
|
|
7310
|
+
const candidate = this.getNextDeliveryReportCandidate(collection);
|
|
7311
|
+
if (!candidate?.key) return;
|
|
7312
|
+
const buffer = this.markDeliveredRequestInFlight ? this.nextDeliveryReportCandidates : this.deliveryReportCandidates;
|
|
7313
|
+
if (candidate.id === null) buffer.delete(candidate.key);
|
|
7314
|
+
else buffer.set(candidate.key, candidate.id);
|
|
7315
|
+
}
|
|
7316
|
+
/**
|
|
7317
|
+
* Removes candidate from the delivery report buffer
|
|
7318
|
+
* @param collection
|
|
7319
|
+
* @private
|
|
7320
|
+
*/
|
|
7321
|
+
removeCandidateFor(collection) {
|
|
7322
|
+
const candidateKey = this.candidateKeyFor(collection);
|
|
7323
|
+
if (!candidateKey) return;
|
|
7324
|
+
this.deliveryReportCandidates.delete(candidateKey);
|
|
7325
|
+
this.nextDeliveryReportCandidates.delete(candidateKey);
|
|
7326
|
+
}
|
|
7327
|
+
/**
|
|
7328
|
+
* Records the latest message delivered for Channel or Thread instances and schedules the next report
|
|
7329
|
+
* if not already scheduled and candidates exist.
|
|
7330
|
+
* Should be used for WS handling (message.new) as well as for ingesting HTTP channel query results.
|
|
7331
|
+
* @param collections
|
|
7332
|
+
*/
|
|
7333
|
+
syncDeliveredCandidates(collections) {
|
|
7334
|
+
if (this.client.user?.privacy_settings?.delivery_receipts?.enabled === false) return;
|
|
7335
|
+
for (const c of collections) this.trackDeliveredCandidate(c);
|
|
7336
|
+
this.announceDeliveryBuffered();
|
|
7337
|
+
}
|
|
7338
|
+
};
|
|
7339
|
+
|
|
7340
|
+
// src/messageDelivery/MessageReceiptsTracker.ts
|
|
7341
|
+
var MIN_REF = { timestampMs: Number.NEGATIVE_INFINITY, msgId: "" };
|
|
7342
|
+
var compareRefsAsc = (a, b) => a.timestampMs !== b.timestampMs ? a.timestampMs - b.timestampMs : 0;
|
|
7343
|
+
var findIndex = (arr, target, keyOf) => {
|
|
7344
|
+
let lo = 0, hi = arr.length;
|
|
7345
|
+
while (lo < hi) {
|
|
7346
|
+
const mid = lo + hi >>> 1;
|
|
7347
|
+
if (compareRefsAsc(keyOf(arr[mid]), target) >= 0) hi = mid;
|
|
7348
|
+
else lo = mid + 1;
|
|
7349
|
+
}
|
|
7350
|
+
return lo;
|
|
7351
|
+
};
|
|
7352
|
+
var findUpperIndex = (arr, target, keyOf) => {
|
|
7353
|
+
let lo = 0, hi = arr.length;
|
|
7354
|
+
while (lo < hi) {
|
|
7355
|
+
const mid = lo + hi >>> 1;
|
|
7356
|
+
if (compareRefsAsc(keyOf(arr[mid]), target) > 0) hi = mid;
|
|
7357
|
+
else lo = mid + 1;
|
|
7358
|
+
}
|
|
7359
|
+
return lo;
|
|
7360
|
+
};
|
|
7361
|
+
var insertByKey = (arr, item, keyOf) => arr.splice(findUpperIndex(arr, keyOf(item), keyOf), 0, item);
|
|
7362
|
+
var removeByOldKey = (arr, item, oldKey, keyOf) => {
|
|
7363
|
+
let i = findIndex(arr, oldKey, keyOf);
|
|
7364
|
+
while (i < arr.length && compareRefsAsc(keyOf(arr[i]), oldKey) === 0) {
|
|
7365
|
+
if (arr[i].user.id === item.user.id) {
|
|
7366
|
+
arr.splice(i, 1);
|
|
7367
|
+
return;
|
|
7368
|
+
}
|
|
7369
|
+
i++;
|
|
7370
|
+
}
|
|
7371
|
+
};
|
|
7372
|
+
var MessageReceiptsTracker = class {
|
|
7373
|
+
constructor({ locateMessage }) {
|
|
7374
|
+
this.byUser = /* @__PURE__ */ new Map();
|
|
7375
|
+
this.readSorted = [];
|
|
7376
|
+
// asc by lastReadRef
|
|
7377
|
+
this.deliveredSorted = [];
|
|
7378
|
+
this.locateMessage = locateMessage;
|
|
7379
|
+
}
|
|
7380
|
+
/** Build initial state from server snapshots (single pass + sort). */
|
|
7381
|
+
ingestInitial(responses) {
|
|
7382
|
+
this.byUser.clear();
|
|
7383
|
+
this.readSorted = [];
|
|
7384
|
+
this.deliveredSorted = [];
|
|
7385
|
+
for (const r of responses) {
|
|
7386
|
+
const lastReadTimestamp = r.last_read ? new Date(r.last_read).getTime() : null;
|
|
7387
|
+
const lastDeliveredTimestamp = r.last_delivered_at ? new Date(r.last_delivered_at).getTime() : null;
|
|
7388
|
+
const lastReadRef = lastReadTimestamp ? this.locateMessage(lastReadTimestamp) ?? MIN_REF : MIN_REF;
|
|
7389
|
+
let lastDeliveredRef = lastDeliveredTimestamp ? this.locateMessage(lastDeliveredTimestamp) ?? MIN_REF : MIN_REF;
|
|
7390
|
+
const isReadAfterDelivered = compareRefsAsc(lastDeliveredRef, lastReadRef) < 0;
|
|
7391
|
+
if (isReadAfterDelivered) lastDeliveredRef = lastReadRef;
|
|
7392
|
+
const userProgress = { user: r.user, lastReadRef, lastDeliveredRef };
|
|
7393
|
+
this.byUser.set(r.user.id, userProgress);
|
|
7394
|
+
this.readSorted.splice(
|
|
7395
|
+
findIndex(this.readSorted, lastReadRef, (up) => up.lastReadRef),
|
|
7396
|
+
0,
|
|
7397
|
+
userProgress
|
|
7398
|
+
);
|
|
7399
|
+
this.deliveredSorted.splice(
|
|
7400
|
+
findIndex(this.deliveredSorted, lastDeliveredRef, (up) => up.lastDeliveredRef),
|
|
7401
|
+
0,
|
|
7402
|
+
userProgress
|
|
7403
|
+
);
|
|
7404
|
+
}
|
|
7405
|
+
}
|
|
7406
|
+
/** message.delivered — user device confirmed delivery up to and including messageId. */
|
|
7407
|
+
onMessageDelivered({
|
|
7408
|
+
user,
|
|
7409
|
+
deliveredAt,
|
|
7410
|
+
lastDeliveredMessageId
|
|
7411
|
+
}) {
|
|
7412
|
+
const timestampMs = new Date(deliveredAt).getTime();
|
|
7413
|
+
const msgRef = lastDeliveredMessageId ? { timestampMs, msgId: lastDeliveredMessageId } : this.locateMessage(new Date(deliveredAt).getTime());
|
|
7414
|
+
if (!msgRef) return;
|
|
7415
|
+
const userProgress = this.ensureUser(user);
|
|
7416
|
+
const newDelivered = compareRefsAsc(msgRef, userProgress.lastReadRef) < 0 ? userProgress.lastReadRef : msgRef;
|
|
7417
|
+
if (compareRefsAsc(newDelivered, userProgress.lastDeliveredRef) <= 0) return;
|
|
7418
|
+
removeByOldKey(
|
|
7419
|
+
this.deliveredSorted,
|
|
7420
|
+
userProgress,
|
|
7421
|
+
userProgress.lastDeliveredRef,
|
|
7422
|
+
(x) => x.lastDeliveredRef
|
|
7423
|
+
);
|
|
7424
|
+
userProgress.lastDeliveredRef = newDelivered;
|
|
7425
|
+
insertByKey(this.deliveredSorted, userProgress, (x) => x.lastDeliveredRef);
|
|
7426
|
+
}
|
|
7427
|
+
/** message.read — user read up to and including messageId. */
|
|
7428
|
+
onMessageRead({
|
|
7429
|
+
user,
|
|
7430
|
+
readAt,
|
|
7431
|
+
lastReadMessageId
|
|
7432
|
+
}) {
|
|
7433
|
+
const timestampMs = new Date(readAt).getTime();
|
|
7434
|
+
const msgRef = lastReadMessageId ? { timestampMs, msgId: lastReadMessageId } : this.locateMessage(timestampMs);
|
|
7435
|
+
if (!msgRef) return;
|
|
7436
|
+
const userProgress = this.ensureUser(user);
|
|
7437
|
+
if (compareRefsAsc(msgRef, userProgress.lastReadRef) <= 0) return;
|
|
7438
|
+
removeByOldKey(
|
|
7439
|
+
this.readSorted,
|
|
7440
|
+
userProgress,
|
|
7441
|
+
userProgress.lastReadRef,
|
|
7442
|
+
(x) => x.lastReadRef
|
|
7443
|
+
);
|
|
7444
|
+
userProgress.lastReadRef = msgRef;
|
|
7445
|
+
insertByKey(this.readSorted, userProgress, (x) => x.lastReadRef);
|
|
7446
|
+
if (compareRefsAsc(userProgress.lastDeliveredRef, userProgress.lastReadRef) < 0) {
|
|
7447
|
+
removeByOldKey(
|
|
7448
|
+
this.deliveredSorted,
|
|
7449
|
+
userProgress,
|
|
7450
|
+
userProgress.lastDeliveredRef,
|
|
7451
|
+
(x) => x.lastDeliveredRef
|
|
7452
|
+
);
|
|
7453
|
+
userProgress.lastDeliveredRef = userProgress.lastReadRef;
|
|
7454
|
+
insertByKey(this.deliveredSorted, userProgress, (x) => x.lastDeliveredRef);
|
|
7455
|
+
}
|
|
7456
|
+
}
|
|
7457
|
+
/** notification.mark_unread — user marked messages unread starting at `first_unread_message_id`.
|
|
7458
|
+
* Sets lastReadRef to the event’s last_read_* values. Delivery never moves backward.
|
|
7459
|
+
* The event is sent only to the user that triggered the action (own user), so we will never adjust read ref
|
|
7460
|
+
* for other users - we will not see changes in the UI for other users. However, this implementation does not
|
|
7461
|
+
* take into consideration this fact and is ready to handle the mark-unread event for any user.
|
|
7462
|
+
*/
|
|
7463
|
+
onNotificationMarkUnread({
|
|
7464
|
+
user,
|
|
7465
|
+
lastReadAt,
|
|
7466
|
+
lastReadMessageId
|
|
7467
|
+
}) {
|
|
7468
|
+
const userProgress = this.ensureUser(user);
|
|
7469
|
+
const newReadRef = lastReadAt ? { timestampMs: new Date(lastReadAt).getTime(), msgId: lastReadMessageId ?? "" } : { ...MIN_REF };
|
|
7470
|
+
if (compareRefsAsc(newReadRef, userProgress.lastReadRef) === 0 && newReadRef.msgId === userProgress.lastReadRef.msgId) {
|
|
7471
|
+
return;
|
|
7472
|
+
}
|
|
7473
|
+
removeByOldKey(
|
|
7474
|
+
this.readSorted,
|
|
7475
|
+
userProgress,
|
|
7476
|
+
userProgress.lastReadRef,
|
|
7477
|
+
(x) => x.lastReadRef
|
|
7478
|
+
);
|
|
7479
|
+
userProgress.lastReadRef = newReadRef;
|
|
7480
|
+
insertByKey(this.readSorted, userProgress, (x) => x.lastReadRef);
|
|
7481
|
+
if (compareRefsAsc(userProgress.lastDeliveredRef, userProgress.lastReadRef) < 0) {
|
|
7482
|
+
removeByOldKey(
|
|
7483
|
+
this.deliveredSorted,
|
|
7484
|
+
userProgress,
|
|
7485
|
+
userProgress.lastDeliveredRef,
|
|
7486
|
+
(x) => x.lastDeliveredRef
|
|
7487
|
+
);
|
|
7488
|
+
userProgress.lastDeliveredRef = userProgress.lastReadRef;
|
|
7489
|
+
insertByKey(this.deliveredSorted, userProgress, (x) => x.lastDeliveredRef);
|
|
7490
|
+
}
|
|
7491
|
+
}
|
|
7492
|
+
/** All users who READ this message. */
|
|
7493
|
+
readersForMessage(msgRef) {
|
|
7494
|
+
const index = findIndex(this.readSorted, msgRef, ({ lastReadRef }) => lastReadRef);
|
|
7495
|
+
return this.readSorted.slice(index).map((x) => x.user);
|
|
7496
|
+
}
|
|
7497
|
+
/** All users who have it DELIVERED (includes readers). */
|
|
7498
|
+
deliveredForMessage(msgRef) {
|
|
7499
|
+
const pos = findIndex(
|
|
7500
|
+
this.deliveredSorted,
|
|
7501
|
+
msgRef,
|
|
7502
|
+
({ lastDeliveredRef }) => lastDeliveredRef
|
|
7503
|
+
);
|
|
7504
|
+
return this.deliveredSorted.slice(pos).map((x) => x.user);
|
|
7505
|
+
}
|
|
7506
|
+
/** Users who delivered but have NOT read. */
|
|
7507
|
+
deliveredNotReadForMessage(msgRef) {
|
|
7508
|
+
const pos = findIndex(
|
|
7509
|
+
this.deliveredSorted,
|
|
7510
|
+
msgRef,
|
|
7511
|
+
({ lastDeliveredRef }) => lastDeliveredRef
|
|
7512
|
+
);
|
|
7513
|
+
const usersDeliveredNotRead = [];
|
|
7514
|
+
for (let i = pos; i < this.deliveredSorted.length; i++) {
|
|
7515
|
+
const userProgress = this.deliveredSorted[i];
|
|
7516
|
+
if (compareRefsAsc(userProgress.lastReadRef, msgRef) < 0)
|
|
7517
|
+
usersDeliveredNotRead.push(userProgress.user);
|
|
7518
|
+
}
|
|
7519
|
+
return usersDeliveredNotRead;
|
|
7520
|
+
}
|
|
7521
|
+
/** Users for whom `msgRef` is their *last read* (exact match). */
|
|
7522
|
+
usersWhoseLastReadIs(msgRef) {
|
|
7523
|
+
if (!msgRef.msgId) return [];
|
|
7524
|
+
const start = findIndex(this.readSorted, msgRef, (x) => x.lastReadRef);
|
|
7525
|
+
const end = findUpperIndex(this.readSorted, msgRef, (x) => x.lastReadRef);
|
|
7526
|
+
const users = [];
|
|
7527
|
+
for (let i = start; i < end; i++) {
|
|
7528
|
+
const up = this.readSorted[i];
|
|
7529
|
+
if (up.lastReadRef.msgId === msgRef.msgId) users.push(up.user);
|
|
7530
|
+
}
|
|
7531
|
+
return users;
|
|
7532
|
+
}
|
|
7533
|
+
/** Users for whom `msgRef` is their *last delivered* (exact match). */
|
|
7534
|
+
usersWhoseLastDeliveredIs(msgRef) {
|
|
7535
|
+
if (!msgRef.msgId) return [];
|
|
7536
|
+
const start = findIndex(this.deliveredSorted, msgRef, (x) => x.lastDeliveredRef);
|
|
7537
|
+
const end = findUpperIndex(this.deliveredSorted, msgRef, (x) => x.lastDeliveredRef);
|
|
7538
|
+
const users = [];
|
|
7539
|
+
for (let i = start; i < end; i++) {
|
|
7540
|
+
const up = this.deliveredSorted[i];
|
|
7541
|
+
if (up.lastDeliveredRef.msgId === msgRef.msgId) users.push(up.user);
|
|
7542
|
+
}
|
|
7543
|
+
return users;
|
|
7544
|
+
}
|
|
7545
|
+
// ---- queries: per-user status ----
|
|
7546
|
+
hasUserRead(msgRef, userId) {
|
|
7547
|
+
const up = this.byUser.get(userId);
|
|
7548
|
+
return !!up && compareRefsAsc(up.lastReadRef, msgRef) >= 0;
|
|
7549
|
+
}
|
|
7550
|
+
hasUserDelivered(msgRef, userId) {
|
|
7551
|
+
const up = this.byUser.get(userId);
|
|
7552
|
+
return !!up && compareRefsAsc(up.lastDeliveredRef, msgRef) >= 0;
|
|
7553
|
+
}
|
|
7554
|
+
getUserProgress(userId) {
|
|
7555
|
+
const userProgress = this.byUser.get(userId);
|
|
7556
|
+
if (!userProgress) return null;
|
|
7557
|
+
return userProgress;
|
|
7558
|
+
}
|
|
7559
|
+
groupUsersByLastReadMessage() {
|
|
7560
|
+
return Array.from(this.byUser.values()).reduce(
|
|
7561
|
+
(acc, userProgress) => {
|
|
7562
|
+
const msgId = userProgress.lastReadRef.msgId;
|
|
7563
|
+
if (!msgId) return acc;
|
|
7564
|
+
if (!acc[msgId]) acc[msgId] = [];
|
|
7565
|
+
acc[msgId].push(userProgress.user);
|
|
7566
|
+
return acc;
|
|
7567
|
+
},
|
|
7568
|
+
{}
|
|
7569
|
+
);
|
|
7570
|
+
}
|
|
7571
|
+
groupUsersByLastDeliveredMessage() {
|
|
7572
|
+
return Array.from(this.byUser.values()).reduce(
|
|
7573
|
+
(acc, userProgress) => {
|
|
7574
|
+
const msgId = userProgress.lastDeliveredRef.msgId;
|
|
7575
|
+
if (!msgId) return acc;
|
|
7576
|
+
if (!acc[msgId]) acc[msgId] = [];
|
|
7577
|
+
acc[msgId].push(userProgress.user);
|
|
7578
|
+
return acc;
|
|
7579
|
+
},
|
|
7580
|
+
{}
|
|
7581
|
+
);
|
|
7582
|
+
}
|
|
7583
|
+
ensureUser(user) {
|
|
7584
|
+
let up = this.byUser.get(user.id);
|
|
7585
|
+
if (!up) {
|
|
7586
|
+
up = { user, lastReadRef: MIN_REF, lastDeliveredRef: MIN_REF };
|
|
7587
|
+
this.byUser.set(user.id, up);
|
|
7588
|
+
insertByKey(this.readSorted, up, (x) => x.lastReadRef);
|
|
7589
|
+
insertByKey(this.deliveredSorted, up, (x) => x.lastDeliveredRef);
|
|
7590
|
+
}
|
|
7591
|
+
return up;
|
|
7592
|
+
}
|
|
7593
|
+
};
|
|
7594
|
+
|
|
7065
7595
|
// src/channel.ts
|
|
7066
7596
|
var Channel = class {
|
|
7067
7597
|
/**
|
|
@@ -7143,6 +7673,12 @@ var Channel = class {
|
|
|
7143
7673
|
client: this._client,
|
|
7144
7674
|
compositionContext: this
|
|
7145
7675
|
});
|
|
7676
|
+
this.messageReceiptsTracker = new MessageReceiptsTracker({
|
|
7677
|
+
locateMessage: (timestampMs) => {
|
|
7678
|
+
const msg = this.state.findMessageByTimestamp(timestampMs);
|
|
7679
|
+
return msg && { timestampMs, msgId: msg.id };
|
|
7680
|
+
}
|
|
7681
|
+
});
|
|
7146
7682
|
}
|
|
7147
7683
|
/**
|
|
7148
7684
|
* getClient - Get the chat client for this channel. If client.disconnect() was called, this function will error
|
|
@@ -7952,15 +8488,24 @@ var Channel = class {
|
|
|
7952
8488
|
return messageSlice[0];
|
|
7953
8489
|
}
|
|
7954
8490
|
/**
|
|
7955
|
-
* markRead - Send the mark read event for this user, only works if the `read_events` setting is enabled
|
|
8491
|
+
* 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.
|
|
7956
8492
|
*
|
|
7957
8493
|
* @param {MarkReadOptions} data
|
|
7958
8494
|
* @return {Promise<EventAPIResponse | null>} Description
|
|
7959
8495
|
*/
|
|
7960
8496
|
async markRead(data = {}) {
|
|
8497
|
+
return await this.getClient().messageDeliveryReporter.markRead(this, data);
|
|
8498
|
+
}
|
|
8499
|
+
/**
|
|
8500
|
+
* markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is enabled
|
|
8501
|
+
*
|
|
8502
|
+
* @param {MarkReadOptions} data
|
|
8503
|
+
* @return {Promise<EventAPIResponse | null>} Description
|
|
8504
|
+
*/
|
|
8505
|
+
async markAsReadRequest(data = {}) {
|
|
7961
8506
|
this._checkInitialized();
|
|
7962
8507
|
if (!this.getConfig()?.read_events && !this.getClient()._isUsingServerAuth()) {
|
|
7963
|
-
return
|
|
8508
|
+
return null;
|
|
7964
8509
|
}
|
|
7965
8510
|
return await this.getClient().post(this._channelURL() + "/read", {
|
|
7966
8511
|
...data
|
|
@@ -8268,6 +8813,7 @@ var Channel = class {
|
|
|
8268
8813
|
}),
|
|
8269
8814
|
{ method: "upsertChannels" }
|
|
8270
8815
|
);
|
|
8816
|
+
this.getClient().syncDeliveredCandidates([this]);
|
|
8271
8817
|
return state;
|
|
8272
8818
|
}
|
|
8273
8819
|
/**
|
|
@@ -8539,17 +9085,44 @@ var Channel = class {
|
|
|
8539
9085
|
break;
|
|
8540
9086
|
case "message.read":
|
|
8541
9087
|
if (event.user?.id && event.created_at) {
|
|
9088
|
+
const previousReadState = channelState.read[event.user.id];
|
|
8542
9089
|
channelState.read[event.user.id] = {
|
|
9090
|
+
// in case we already have delivery information
|
|
9091
|
+
...previousReadState,
|
|
8543
9092
|
last_read: new Date(event.created_at),
|
|
8544
9093
|
last_read_message_id: event.last_read_message_id,
|
|
8545
9094
|
user: event.user,
|
|
8546
9095
|
unread_messages: 0
|
|
8547
9096
|
};
|
|
8548
|
-
|
|
9097
|
+
this.messageReceiptsTracker.onMessageRead({
|
|
9098
|
+
user: event.user,
|
|
9099
|
+
readAt: event.created_at,
|
|
9100
|
+
lastReadMessageId: event.last_read_message_id
|
|
9101
|
+
});
|
|
9102
|
+
const client = this.getClient();
|
|
9103
|
+
const isOwnEvent = event.user?.id === client.user?.id;
|
|
9104
|
+
if (isOwnEvent) {
|
|
8549
9105
|
channelState.unreadCount = 0;
|
|
9106
|
+
client.syncDeliveredCandidates([this]);
|
|
8550
9107
|
}
|
|
8551
9108
|
}
|
|
8552
9109
|
break;
|
|
9110
|
+
case "message.delivered":
|
|
9111
|
+
if (event.user?.id && event.created_at) {
|
|
9112
|
+
const previousReadState = channelState.read[event.user.id];
|
|
9113
|
+
channelState.read[event.user.id] = {
|
|
9114
|
+
...previousReadState,
|
|
9115
|
+
last_delivered_at: event.last_delivered_at ? new Date(event.last_delivered_at) : void 0,
|
|
9116
|
+
last_delivered_message_id: event.last_delivered_message_id,
|
|
9117
|
+
user: event.user
|
|
9118
|
+
};
|
|
9119
|
+
this.messageReceiptsTracker.onMessageDelivered({
|
|
9120
|
+
user: event.user,
|
|
9121
|
+
deliveredAt: event.created_at,
|
|
9122
|
+
lastDeliveredMessageId: event.last_delivered_message_id
|
|
9123
|
+
});
|
|
9124
|
+
}
|
|
9125
|
+
break;
|
|
8553
9126
|
case "user.watching.start":
|
|
8554
9127
|
case "user.updated":
|
|
8555
9128
|
if (event.user?.id) {
|
|
@@ -8583,7 +9156,8 @@ var Channel = class {
|
|
|
8583
9156
|
break;
|
|
8584
9157
|
case "message.new":
|
|
8585
9158
|
if (event.message) {
|
|
8586
|
-
const
|
|
9159
|
+
const client = this.getClient();
|
|
9160
|
+
const ownMessage = event.user?.id === client.user?.id;
|
|
8587
9161
|
const isThreadMessage = event.message.parent_id && !event.message.show_in_channel;
|
|
8588
9162
|
if (this.state.isUpToDate || isThreadMessage) {
|
|
8589
9163
|
channelState.addMessageSorted(event.message, ownMessage);
|
|
@@ -8599,7 +9173,9 @@ var Channel = class {
|
|
|
8599
9173
|
channelState.read[event.user.id] = {
|
|
8600
9174
|
last_read: new Date(event.created_at),
|
|
8601
9175
|
user: event.user,
|
|
8602
|
-
unread_messages: 0
|
|
9176
|
+
unread_messages: 0,
|
|
9177
|
+
last_delivered_at: new Date(event.created_at),
|
|
9178
|
+
last_delivered_message_id: event.message.id
|
|
8603
9179
|
};
|
|
8604
9180
|
} else {
|
|
8605
9181
|
channelState.read[userId].unread_messages += 1;
|
|
@@ -8609,6 +9185,7 @@ var Channel = class {
|
|
|
8609
9185
|
if (this._countMessageAsUnread(event.message)) {
|
|
8610
9186
|
channelState.unreadCount = channelState.unreadCount + 1;
|
|
8611
9187
|
}
|
|
9188
|
+
client.syncDeliveredCandidates([this]);
|
|
8612
9189
|
}
|
|
8613
9190
|
break;
|
|
8614
9191
|
case "message.updated":
|
|
@@ -8691,9 +9268,12 @@ var Channel = class {
|
|
|
8691
9268
|
break;
|
|
8692
9269
|
case "notification.mark_unread": {
|
|
8693
9270
|
const ownMessage = event.user?.id === this.getClient().user?.id;
|
|
8694
|
-
if (!
|
|
9271
|
+
if (!ownMessage || !event.user) break;
|
|
8695
9272
|
const unreadCount = event.unread_messages ?? 0;
|
|
9273
|
+
const currentState = channelState.read[event.user.id];
|
|
8696
9274
|
channelState.read[event.user.id] = {
|
|
9275
|
+
// keep the message delivery info
|
|
9276
|
+
...currentState,
|
|
8697
9277
|
first_unread_message_id: event.first_unread_message_id,
|
|
8698
9278
|
last_read: new Date(event.last_read_at),
|
|
8699
9279
|
last_read_message_id: event.last_read_message_id,
|
|
@@ -8701,6 +9281,11 @@ var Channel = class {
|
|
|
8701
9281
|
unread_messages: unreadCount
|
|
8702
9282
|
};
|
|
8703
9283
|
channelState.unreadCount = unreadCount;
|
|
9284
|
+
this.messageReceiptsTracker.onNotificationMarkUnread({
|
|
9285
|
+
user: event.user,
|
|
9286
|
+
lastReadAt: event.last_read_at,
|
|
9287
|
+
lastReadMessageId: event.last_read_message_id
|
|
9288
|
+
});
|
|
8704
9289
|
break;
|
|
8705
9290
|
}
|
|
8706
9291
|
case "channel.updated":
|
|
@@ -8841,6 +9426,8 @@ var Channel = class {
|
|
|
8841
9426
|
if (state.read) {
|
|
8842
9427
|
for (const read of state.read) {
|
|
8843
9428
|
this.state.read[read.user.id] = {
|
|
9429
|
+
last_delivered_at: read.last_delivered_at ? new Date(read.last_delivered_at) : void 0,
|
|
9430
|
+
last_delivered_message_id: read.last_delivered_message_id,
|
|
8844
9431
|
last_read: new Date(read.last_read),
|
|
8845
9432
|
last_read_message_id: read.last_read_message_id,
|
|
8846
9433
|
unread_messages: read.unread_messages ?? 0,
|
|
@@ -8850,6 +9437,7 @@ var Channel = class {
|
|
|
8850
9437
|
this.state.unreadCount = this.state.read[read.user.id].unread_messages;
|
|
8851
9438
|
}
|
|
8852
9439
|
}
|
|
9440
|
+
this.messageReceiptsTracker.ingestInitial(state.read);
|
|
8853
9441
|
}
|
|
8854
9442
|
return {
|
|
8855
9443
|
messageSet
|
|
@@ -12400,6 +12988,7 @@ var StreamChat = class _StreamChat {
|
|
|
12400
12988
|
this.threads = new ThreadManager({ client: this });
|
|
12401
12989
|
this.polls = new PollManager({ client: this });
|
|
12402
12990
|
this.reminders = new ReminderManager({ client: this });
|
|
12991
|
+
this.messageDeliveryReporter = new MessageDeliveryReporter({ client: this });
|
|
12403
12992
|
}
|
|
12404
12993
|
static getInstance(key, secretOrOptions, options) {
|
|
12405
12994
|
if (!_StreamChat._instance) {
|
|
@@ -13070,6 +13659,7 @@ var StreamChat = class _StreamChat {
|
|
|
13070
13659
|
c.messageComposer.initStateFromChannelResponse(channelState);
|
|
13071
13660
|
channels.push(c);
|
|
13072
13661
|
}
|
|
13662
|
+
this.syncDeliveredCandidates(channels);
|
|
13073
13663
|
return channels;
|
|
13074
13664
|
}
|
|
13075
13665
|
/**
|
|
@@ -13834,10 +14424,26 @@ var StreamChat = class _StreamChat {
|
|
|
13834
14424
|
}
|
|
13835
14425
|
);
|
|
13836
14426
|
}
|
|
13837
|
-
|
|
14427
|
+
/**
|
|
14428
|
+
* deleteMessage - Delete a message
|
|
14429
|
+
*
|
|
14430
|
+
* @param {string} messageID The id of the message to delete
|
|
14431
|
+
* @param {boolean | DeleteMessageOptions | undefined} [optionsOrHardDelete]
|
|
14432
|
+
* @return {Promise<APIResponse & { message: MessageResponse }>} The API response
|
|
14433
|
+
*/
|
|
14434
|
+
// fixme: remove the signature with optionsOrHardDelete boolean with the next major release
|
|
14435
|
+
async deleteMessage(messageID, optionsOrHardDelete) {
|
|
14436
|
+
let options = {};
|
|
14437
|
+
if (typeof optionsOrHardDelete === "boolean") {
|
|
14438
|
+
options = optionsOrHardDelete ? { hardDelete: true } : {};
|
|
14439
|
+
} else if (optionsOrHardDelete?.deleteForMe) {
|
|
14440
|
+
options = { deleteForMe: true };
|
|
14441
|
+
} else if (optionsOrHardDelete?.hardDelete) {
|
|
14442
|
+
options = { hardDelete: true };
|
|
14443
|
+
}
|
|
13838
14444
|
try {
|
|
13839
14445
|
if (this.offlineDb) {
|
|
13840
|
-
if (hardDelete) {
|
|
14446
|
+
if (options.hardDelete) {
|
|
13841
14447
|
await this.offlineDb.hardDeleteMessage({ id: messageID });
|
|
13842
14448
|
} else {
|
|
13843
14449
|
await this.offlineDb.softDeleteMessage({ id: messageID });
|
|
@@ -13846,7 +14452,7 @@ var StreamChat = class _StreamChat {
|
|
|
13846
14452
|
{
|
|
13847
14453
|
task: {
|
|
13848
14454
|
messageId: messageID,
|
|
13849
|
-
payload: [messageID,
|
|
14455
|
+
payload: [messageID, options],
|
|
13850
14456
|
type: "delete-message"
|
|
13851
14457
|
}
|
|
13852
14458
|
}
|
|
@@ -13858,17 +14464,27 @@ var StreamChat = class _StreamChat {
|
|
|
13858
14464
|
error
|
|
13859
14465
|
});
|
|
13860
14466
|
}
|
|
13861
|
-
return this._deleteMessage(messageID,
|
|
14467
|
+
return this._deleteMessage(messageID, options);
|
|
13862
14468
|
}
|
|
13863
|
-
|
|
14469
|
+
// fixme: remove the signature with optionsOrHardDelete boolean with the next major release
|
|
14470
|
+
async _deleteMessage(messageID, optionsOrHardDelete) {
|
|
14471
|
+
const { deleteForMe, hardDelete } = typeof optionsOrHardDelete === "boolean" ? { hardDelete: optionsOrHardDelete } : optionsOrHardDelete ?? {};
|
|
13864
14472
|
let params = {};
|
|
13865
14473
|
if (hardDelete) {
|
|
13866
14474
|
params = { hard: true };
|
|
13867
14475
|
}
|
|
13868
|
-
|
|
14476
|
+
if (deleteForMe) {
|
|
14477
|
+
params = { ...params, delete_for_me: true };
|
|
14478
|
+
}
|
|
14479
|
+
const result = await this.delete(
|
|
13869
14480
|
this.baseURL + `/messages/${encodeURIComponent(messageID)}`,
|
|
13870
14481
|
params
|
|
13871
14482
|
);
|
|
14483
|
+
if (deleteForMe) {
|
|
14484
|
+
result.message.deleted_for_me = true;
|
|
14485
|
+
result.message.type = "deleted";
|
|
14486
|
+
}
|
|
14487
|
+
return result;
|
|
13872
14488
|
}
|
|
13873
14489
|
/**
|
|
13874
14490
|
* undeleteMessage - Undelete a message
|
|
@@ -14004,7 +14620,7 @@ var StreamChat = class _StreamChat {
|
|
|
14004
14620
|
if (this.userAgent) {
|
|
14005
14621
|
return this.userAgent;
|
|
14006
14622
|
}
|
|
14007
|
-
const version = "9.
|
|
14623
|
+
const version = "9.22.0";
|
|
14008
14624
|
const clientBundle = "browser-cjs";
|
|
14009
14625
|
let userAgentString = "";
|
|
14010
14626
|
if (this.sdkIdentifier) {
|
|
@@ -15152,6 +15768,22 @@ var StreamChat = class _StreamChat {
|
|
|
15152
15768
|
deleteImage(url) {
|
|
15153
15769
|
return this.delete(`${this.baseURL}/uploads/image`, { url });
|
|
15154
15770
|
}
|
|
15771
|
+
/**
|
|
15772
|
+
* Send the mark delivered event for this user
|
|
15773
|
+
*
|
|
15774
|
+
* @param {MarkDeliveredOptions} data
|
|
15775
|
+
* @return {Promise<EventAPIResponse | void>} Description
|
|
15776
|
+
*/
|
|
15777
|
+
async markChannelsDelivered(data) {
|
|
15778
|
+
if (!data?.latest_delivered_messages?.length) return;
|
|
15779
|
+
return await this.post(
|
|
15780
|
+
this.baseURL + "/channels/delivered",
|
|
15781
|
+
data ?? {}
|
|
15782
|
+
);
|
|
15783
|
+
}
|
|
15784
|
+
syncDeliveredCandidates(collections) {
|
|
15785
|
+
this.messageDeliveryReporter.syncDeliveredCandidates(collections);
|
|
15786
|
+
}
|
|
15155
15787
|
};
|
|
15156
15788
|
|
|
15157
15789
|
// src/events.ts
|
|
@@ -15178,6 +15810,7 @@ var EVENT_MAP = {
|
|
|
15178
15810
|
"message.undeleted": true,
|
|
15179
15811
|
"notification.added_to_channel": true,
|
|
15180
15812
|
"notification.channel_deleted": true,
|
|
15813
|
+
"message.delivered": true,
|
|
15181
15814
|
"notification.channel_mutes_updated": true,
|
|
15182
15815
|
"notification.channel_truncated": true,
|
|
15183
15816
|
"notification.invite_accepted": true,
|
|
@@ -15187,6 +15820,7 @@ var EVENT_MAP = {
|
|
|
15187
15820
|
"notification.mark_unread": true,
|
|
15188
15821
|
"notification.message_new": true,
|
|
15189
15822
|
"notification.mutes_updated": true,
|
|
15823
|
+
"notification.reminder_due": true,
|
|
15190
15824
|
"notification.removed_from_channel": true,
|
|
15191
15825
|
"notification.thread_message_new": true,
|
|
15192
15826
|
"poll.closed": true,
|
|
@@ -15197,6 +15831,9 @@ var EVENT_MAP = {
|
|
|
15197
15831
|
"reaction.deleted": true,
|
|
15198
15832
|
"reaction.new": true,
|
|
15199
15833
|
"reaction.updated": true,
|
|
15834
|
+
"reminder.created": true,
|
|
15835
|
+
"reminder.deleted": true,
|
|
15836
|
+
"reminder.updated": true,
|
|
15200
15837
|
"thread.updated": true,
|
|
15201
15838
|
"typing.start": true,
|
|
15202
15839
|
"typing.stop": true,
|
|
@@ -15221,12 +15858,7 @@ var EVENT_MAP = {
|
|
|
15221
15858
|
"transport.changed": true,
|
|
15222
15859
|
"capabilities.changed": true,
|
|
15223
15860
|
"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
|
|
15861
|
+
"live_location_sharing.stopped": true
|
|
15230
15862
|
};
|
|
15231
15863
|
|
|
15232
15864
|
// src/permissions.ts
|