stream-chat 10.0.0-rc.12 → 10.0.0-rc.14
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 +801 -652
- package/dist/cjs/index.browser.js.map +4 -4
- package/dist/cjs/index.node.js +807 -653
- package/dist/cjs/index.node.js.map +4 -4
- package/dist/esm/index.mjs +801 -652
- package/dist/esm/index.mjs.map +4 -4
- package/dist/types/CooldownTimer.d.ts +4 -3
- package/dist/types/channel.d.ts +44 -81
- package/dist/types/channel_state.d.ts +5 -5
- package/dist/types/client.d.ts +14 -3
- package/dist/types/entityStore/EntityStore.d.ts +7 -0
- package/dist/types/entityStore/index.d.ts +0 -1
- package/dist/types/formatMessageMemo.d.ts +5 -0
- package/dist/types/gen/chat/ChatApi.d.ts +2 -2
- package/dist/types/gen/models/index.d.ts +492 -369
- package/dist/types/gen/models/timestamp-guard.d.ts +22 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/messageComposer/attachmentIdentity.d.ts +7 -0
- package/dist/types/messageComposer/attachmentUtils.d.ts +34 -0
- package/dist/types/messageComposer/configuration/types.d.ts +12 -0
- package/dist/types/messageComposer/index.d.ts +1 -0
- package/dist/types/messageComposer/linkPreviewsManager.d.ts +3 -3
- package/dist/types/messageComposer/messageComposer.d.ts +63 -19
- package/dist/types/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.d.ts +1 -1
- package/dist/types/messageComposer/middleware/messageComposer/MessageComposerMiddlewareExecutor.d.ts +1 -6
- package/dist/types/messageComposer/middleware/messageComposer/attachments.d.ts +7 -20
- package/dist/types/messageComposer/middleware/messageComposer/types.d.ts +1 -22
- package/dist/types/messageComposer/middleware/textComposer/commands.d.ts +2 -2
- package/dist/types/messageComposer/middleware/textComposer/mentions.d.ts +8 -0
- package/dist/types/messageDelivery/MessageReceiptsTracker.d.ts +42 -8
- package/dist/types/messageOperations/MessageOperations.d.ts +35 -4
- package/dist/types/messageOperations/WithMessageOperations.d.ts +35 -0
- package/dist/types/messageOperations/createMessageOperations.d.ts +11 -0
- package/dist/types/messageOperations/index.d.ts +6 -0
- package/dist/types/messageOperations/reflectReactionEvent.d.ts +24 -0
- package/dist/types/messageOperations/sendOrdering.d.ts +17 -0
- package/dist/types/messageOperations/settlePendingAttachmentUploads.d.ts +28 -0
- package/dist/types/messageOperations/types.d.ts +34 -0
- package/dist/types/notifications/types.d.ts +4 -0
- package/dist/types/offline-support/types.d.ts +2 -2
- package/dist/types/pagination/cursorDerivation/createdAtAroundPaginationFlags.d.ts +2 -1
- package/dist/types/pagination/cursorDerivation/linearPaginationFlags.d.ts +2 -1
- package/dist/types/pagination/paginators/BasePaginator.d.ts +22 -0
- package/dist/types/pagination/paginators/MessageIntervalPaginator.d.ts +10 -48
- package/dist/types/pagination/paginators/MessagePaginator.d.ts +5 -5
- package/dist/types/poll.d.ts +2 -2
- package/dist/types/reminders/Reminder.d.ts +6 -6
- package/dist/types/thread.d.ts +26 -46
- package/dist/types/types.d.ts +5 -5
- package/dist/types/uploadManager.d.ts +19 -1
- package/dist/types/utils/time.d.ts +23 -7
- package/dist/types/utils.d.ts +11 -13
- package/package.json +6 -4
- package/src/CooldownTimer.ts +5 -5
- package/src/api-client.ts +5 -2
- package/src/channel.ts +65 -300
- package/src/channel_state.ts +5 -4
- package/src/client.ts +18 -3
- package/src/configuration/shape.ts +12 -0
- package/src/entityStore/EntityStore.ts +22 -1
- package/src/entityStore/index.ts +0 -1
- package/src/formatMessageMemo.ts +23 -0
- package/src/gen/chat/ChatApi.ts +4 -2
- package/src/gen/models/index.ts +502 -3217
- package/src/gen/models/timestamp-guard.ts +27 -0
- package/src/gen/moderation/ModerationApi.ts +1 -0
- package/src/index.ts +6 -0
- package/src/messageComposer/attachmentIdentity.ts +12 -0
- package/src/messageComposer/attachmentUtils.ts +58 -0
- package/src/messageComposer/configuration/configuration.ts +2 -0
- package/src/messageComposer/configuration/types.ts +12 -0
- package/src/messageComposer/index.ts +1 -0
- package/src/messageComposer/messageComposer.ts +178 -28
- package/src/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.ts +1 -1
- package/src/messageComposer/middleware/messageComposer/MessageComposerMiddlewareExecutor.ts +0 -9
- package/src/messageComposer/middleware/messageComposer/attachments.ts +43 -56
- package/src/messageComposer/middleware/messageComposer/linkPreviews.ts +20 -10
- package/src/messageComposer/middleware/messageComposer/types.ts +1 -22
- package/src/messageDelivery/MessageDeliveryReporter.ts +3 -2
- package/src/messageDelivery/MessageReceiptsTracker.ts +83 -13
- package/src/messageOperations/MessageOperations.ts +140 -15
- package/src/messageOperations/WithMessageOperations.ts +60 -0
- package/src/messageOperations/createMessageOperations.ts +139 -0
- package/src/messageOperations/index.ts +6 -0
- package/src/messageOperations/optimistic.ts +1 -1
- package/src/messageOperations/reflectReactionEvent.ts +51 -0
- package/src/messageOperations/sendOrdering.ts +28 -0
- package/src/messageOperations/settlePendingAttachmentUploads.ts +110 -0
- package/src/messageOperations/types.ts +38 -0
- package/src/notifications/types.ts +4 -0
- package/src/offline-support/offline_support_api.ts +10 -1
- package/src/offline-support/types.ts +2 -1
- package/src/pagination/cursorDerivation/createdAtAroundPaginationFlags.ts +2 -1
- package/src/pagination/cursorDerivation/linearPaginationFlags.ts +2 -2
- package/src/pagination/paginators/BasePaginator.ts +73 -23
- package/src/pagination/paginators/MessageIntervalPaginator.ts +45 -102
- package/src/pagination/paginators/MessagePaginator.ts +7 -7
- package/src/poll.ts +6 -2
- package/src/reminders/Reminder.ts +10 -5
- package/src/thread.ts +24 -245
- package/src/types.ts +5 -4
- package/src/uploadManager.ts +37 -5
- package/src/utils/time.ts +27 -7
- package/src/utils.ts +12 -5
- /package/dist/types/{entityStore → messageOperations}/applyReactionLocally.d.ts +0 -0
- /package/src/{entityStore → messageOperations}/applyReactionLocally.ts +0 -0
|
@@ -137,6 +137,7 @@ __export(src_exports, {
|
|
|
137
137
|
WS_NETWORK_RECOVERY_RETRY_MS: () => WS_NETWORK_RECOVERY_RETRY_MS,
|
|
138
138
|
ZERO_PAGE_CURSOR: () => ZERO_PAGE_CURSOR,
|
|
139
139
|
accentsMap: () => accentsMap,
|
|
140
|
+
asTimestampNS: () => asTimestampNS,
|
|
140
141
|
browserNetworkStatusReporter: () => browserNetworkStatusReporter,
|
|
141
142
|
calculateLevenshtein: () => calculateLevenshtein,
|
|
142
143
|
chatCodes: () => chatCodes,
|
|
@@ -167,7 +168,6 @@ __export(src_exports, {
|
|
|
167
168
|
createPollComposerStateMiddleware: () => createPollComposerStateMiddleware,
|
|
168
169
|
createPostUploadAttachmentEnrichmentMiddleware: () => createPostUploadAttachmentEnrichmentMiddleware,
|
|
169
170
|
createPriorityOwnershipResolver: () => createPriorityOwnershipResolver,
|
|
170
|
-
createSendWithPendingUploadsAttachmentsMiddleware: () => createSendWithPendingUploadsAttachmentsMiddleware,
|
|
171
171
|
createSharedLocationCompositionMiddleware: () => createSharedLocationCompositionMiddleware,
|
|
172
172
|
createTextComposerCompositionMiddleware: () => createTextComposerCompositionMiddleware,
|
|
173
173
|
createTextComposerPreValidationMiddleware: () => createTextComposerPreValidationMiddleware,
|
|
@@ -186,6 +186,7 @@ __export(src_exports, {
|
|
|
186
186
|
formatMessage: () => formatMessage,
|
|
187
187
|
generateFileName: () => generateFileName,
|
|
188
188
|
getAllowedMentionTypesFromCapabilities: () => getAllowedMentionTypesFromCapabilities,
|
|
189
|
+
getAttachmentPreviewUrl: () => getAttachmentPreviewUrl,
|
|
189
190
|
getAttachmentTypeFromMimeType: () => getAttachmentTypeFromMimeType,
|
|
190
191
|
getCommandByName: () => getCommandByName,
|
|
191
192
|
getCompleteCommandInString: () => getCompleteCommandInString,
|
|
@@ -200,6 +201,7 @@ __export(src_exports, {
|
|
|
200
201
|
insertItemWithTrigger: () => insertItemWithTrigger,
|
|
201
202
|
isAudioAttachment: () => isAudioAttachment,
|
|
202
203
|
isBlobButNotFile: () => isBlobButNotFile,
|
|
204
|
+
isFailedUpload: () => isFailedUpload,
|
|
203
205
|
isFile: () => isFile,
|
|
204
206
|
isFileAttachment: () => isFileAttachment,
|
|
205
207
|
isFileList: () => isFileList,
|
|
@@ -225,6 +227,7 @@ __export(src_exports, {
|
|
|
225
227
|
isSharedLocationResponseData: () => isSharedLocationResponseData,
|
|
226
228
|
isTargetedOptionTextUpdate: () => isTargetedOptionTextUpdate,
|
|
227
229
|
isUploadCancellation: () => isUploadCancellation,
|
|
230
|
+
isUploadConfirmationPending: () => isUploadConfirmationPending,
|
|
228
231
|
isUploadedAttachment: () => isUploadedAttachment,
|
|
229
232
|
isVideoAttachment: () => isVideoAttachment,
|
|
230
233
|
isVoiceRecordingAttachment: () => isVoiceRecordingAttachment,
|
|
@@ -247,6 +250,8 @@ __export(src_exports, {
|
|
|
247
250
|
readFileAsArrayBuffer: () => readFileAsArrayBuffer,
|
|
248
251
|
removeDiacritics: () => removeDiacritics,
|
|
249
252
|
replaceWordWithEntity: () => replaceWordWithEntity,
|
|
253
|
+
resolveAttachmentFileSize: () => resolveAttachmentFileSize,
|
|
254
|
+
resolveAttachmentFullByteSize: () => resolveAttachmentFullByteSize,
|
|
250
255
|
settled: () => settled,
|
|
251
256
|
stripCommandFromText: () => stripCommandFromText,
|
|
252
257
|
stripMentionTokens: () => stripMentionTokens,
|
|
@@ -389,6 +394,13 @@ var LOCAL_MESSAGE_FIELDS = Object.freeze({ error: true });
|
|
|
389
394
|
var DEFAULT_QUERY_CHANNELS_RETRY_COUNT = 3;
|
|
390
395
|
var DEFAULT_QUERY_CHANNELS_MS_BETWEEN_RETRIES = 1e3;
|
|
391
396
|
|
|
397
|
+
// src/formatMessageMemo.ts
|
|
398
|
+
var memo = /* @__PURE__ */ new WeakMap();
|
|
399
|
+
var getMemoizedFormat = (source) => memo.get(source);
|
|
400
|
+
var memoizeFormat = (source, formatted) => {
|
|
401
|
+
memo.set(source, formatted);
|
|
402
|
+
};
|
|
403
|
+
|
|
392
404
|
// src/logger.ts
|
|
393
405
|
var scopedLogger = __toESM(require("@stream-io/logger"));
|
|
394
406
|
var import_logger = require("@stream-io/logger");
|
|
@@ -396,8 +408,9 @@ var chatLoggerSystem = scopedLogger.createLoggerSystem();
|
|
|
396
408
|
|
|
397
409
|
// src/utils/time.ts
|
|
398
410
|
var NS_PER_MS = 1e6;
|
|
411
|
+
var asTimestampNS = (ns) => ns;
|
|
399
412
|
var nsToMs = (ns) => Math.floor(ns / NS_PER_MS);
|
|
400
|
-
var msToNs = (ms) => ms * NS_PER_MS;
|
|
413
|
+
var msToNs = (ms) => asTimestampNS(ms * NS_PER_MS);
|
|
401
414
|
var nowNs = () => msToNs(Date.now());
|
|
402
415
|
var nsToDate = (ns) => new Date(nsToMs(ns));
|
|
403
416
|
var nsToRfc3339 = (ns) => {
|
|
@@ -594,6 +607,8 @@ var axiosParamsSerializer = (params) => {
|
|
|
594
607
|
return newParams.join("&");
|
|
595
608
|
};
|
|
596
609
|
function formatMessage(message) {
|
|
610
|
+
const memoized = getMemoizedFormat(message);
|
|
611
|
+
if (memoized) return memoized;
|
|
597
612
|
const toLocalMessageBase = (msg) => {
|
|
598
613
|
if (!msg) return null;
|
|
599
614
|
return {
|
|
@@ -613,11 +628,13 @@ function formatMessage(message) {
|
|
|
613
628
|
updated_at: msg.updated_at ?? nowNs()
|
|
614
629
|
};
|
|
615
630
|
};
|
|
616
|
-
|
|
631
|
+
const formatted = {
|
|
617
632
|
...toLocalMessageBase(message),
|
|
618
633
|
error: message.error ?? void 0,
|
|
619
634
|
quoted_message: toLocalMessageBase(message.quoted_message) ?? void 0
|
|
620
635
|
};
|
|
636
|
+
memoizeFormat(message, formatted);
|
|
637
|
+
return formatted;
|
|
621
638
|
}
|
|
622
639
|
function computeOwnReactions({
|
|
623
640
|
current,
|
|
@@ -1488,7 +1505,12 @@ var EntityStore = class {
|
|
|
1488
1505
|
return fn();
|
|
1489
1506
|
} finally {
|
|
1490
1507
|
this.transactionDepth -= 1;
|
|
1491
|
-
if (this.transactionDepth === 0)
|
|
1508
|
+
if (this.transactionDepth === 0) {
|
|
1509
|
+
this.flush();
|
|
1510
|
+
const ids = this.pendingFlushIds;
|
|
1511
|
+
this.pendingFlushIds = void 0;
|
|
1512
|
+
if (ids) for (const id of ids) this.flushSubscribersNow(id);
|
|
1513
|
+
}
|
|
1492
1514
|
}
|
|
1493
1515
|
}
|
|
1494
1516
|
/**
|
|
@@ -1496,8 +1518,19 @@ var EntityStore = class {
|
|
|
1496
1518
|
* {@link EntityStoreSubscriber.flushState}). Called after an optimistic (local-user) write to
|
|
1497
1519
|
* `id` so it renders without the throttle delay. Only that id's own subscribers are flushed — the
|
|
1498
1520
|
* write touched no other id — and flushing a subscriber with nothing pending is a no-op.
|
|
1521
|
+
*
|
|
1522
|
+
* Inside a transaction it is deferred to the outermost exit — inline there it would be a no-op,
|
|
1523
|
+
* since `markDirty` has not notified anyone yet. Getting this wrong costs the siblings (pinned
|
|
1524
|
+
* list, open thread) up to 500ms on an optimistic write, while the writer updates inline.
|
|
1499
1525
|
*/
|
|
1500
1526
|
flushSubscribers(id) {
|
|
1527
|
+
if (this.transactionDepth > 0) {
|
|
1528
|
+
(this.pendingFlushIds ?? (this.pendingFlushIds = /* @__PURE__ */ new Set())).add(id);
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
this.flushSubscribersNow(id);
|
|
1532
|
+
}
|
|
1533
|
+
flushSubscribersNow(id) {
|
|
1501
1534
|
const subscribers = this.subscribers.get(id);
|
|
1502
1535
|
if (!subscribers) return;
|
|
1503
1536
|
for (const subscriber of subscribers) subscriber.flushState?.();
|
|
@@ -1596,11 +1629,11 @@ var isClassInstance = (value) => {
|
|
|
1596
1629
|
if (proto === null || proto === Object.prototype) return false;
|
|
1597
1630
|
return value.constructor && value.constructor !== Object;
|
|
1598
1631
|
};
|
|
1599
|
-
function memoHasOrAdd(
|
|
1600
|
-
const set =
|
|
1632
|
+
function memoHasOrAdd(memo2, a, b) {
|
|
1633
|
+
const set = memo2.get(a);
|
|
1601
1634
|
if (set && set.has(b)) return true;
|
|
1602
1635
|
if (set) set.add(b);
|
|
1603
|
-
else
|
|
1636
|
+
else memo2.set(a, new WeakSet([b]));
|
|
1604
1637
|
return false;
|
|
1605
1638
|
}
|
|
1606
1639
|
var isEqual = (value1, value2, pairMemo = /* @__PURE__ */ new WeakMap()) => {
|
|
@@ -2671,6 +2704,41 @@ var BasePaginator = class {
|
|
|
2671
2704
|
scheduleWindowPublish() {
|
|
2672
2705
|
this._windowPublishThrottle?.throttledFn();
|
|
2673
2706
|
}
|
|
2707
|
+
/**
|
|
2708
|
+
* Run `fn` with this paginator's window publishes suspended, emitting once on exit.
|
|
2709
|
+
*
|
|
2710
|
+
* A replace is internally remove-then-insert and each half changes the window, so without this one
|
|
2711
|
+
* logical `ingestItem` emits twice.
|
|
2712
|
+
*
|
|
2713
|
+
* `exit` decides how the single emit leaves the scope: `auto` rides the throttle when there is one
|
|
2714
|
+
* (live ops, where successive events should coalesce across the trailing edge), `sync` always
|
|
2715
|
+
* flushes ({@link batch}, whose contract is that a one-shot operation settles in a single update).
|
|
2716
|
+
*
|
|
2717
|
+
* **`lockItemOrder` lists are excluded.** Their emit is not a re-projection but a republish of the
|
|
2718
|
+
* last-published array with the item spliced back at its original index, and exiting through
|
|
2719
|
+
* {@link flushWindowPublish} re-derives from the active interval — reordering the list and dropping
|
|
2720
|
+
* whatever the interval no longer holds, which is the whole point of locking the order.
|
|
2721
|
+
*
|
|
2722
|
+
* Re-entrant: only the outermost scope emits, so an `ingestItem` inside a coalescing `batch`
|
|
2723
|
+
* defers to the batch.
|
|
2724
|
+
*/
|
|
2725
|
+
withSingleWindowPublish(fn, { exit = "auto" } = {}) {
|
|
2726
|
+
if (this.config.lockItemOrder) {
|
|
2727
|
+
fn();
|
|
2728
|
+
return;
|
|
2729
|
+
}
|
|
2730
|
+
const isOutermost = this._windowPublishSuspendDepth === 0;
|
|
2731
|
+
this._windowPublishSuspendDepth += 1;
|
|
2732
|
+
try {
|
|
2733
|
+
fn();
|
|
2734
|
+
} finally {
|
|
2735
|
+
this._windowPublishSuspendDepth -= 1;
|
|
2736
|
+
}
|
|
2737
|
+
if (!isOutermost || !this._suspendedWindowDirty) return;
|
|
2738
|
+
this._suspendedWindowDirty = false;
|
|
2739
|
+
if (exit === "auto" && this.isStateThrottled) this.scheduleWindowPublish();
|
|
2740
|
+
else this.flushWindowPublish();
|
|
2741
|
+
}
|
|
2674
2742
|
/**
|
|
2675
2743
|
* Flush any pending throttled window + interval-view publishes immediately. No-op when nothing
|
|
2676
2744
|
* is pending or throttling is off.
|
|
@@ -2899,8 +2967,18 @@ var BasePaginator = class {
|
|
|
2899
2967
|
const activeInterval = this._itemIntervals.get(this._activeIntervalId);
|
|
2900
2968
|
if (!activeInterval) return;
|
|
2901
2969
|
if (this.isStateThrottled) {
|
|
2902
|
-
|
|
2903
|
-
|
|
2970
|
+
const currentItems2 = this.items;
|
|
2971
|
+
if (!currentItems2 || currentItems2.length !== activeInterval.itemIds.length) {
|
|
2972
|
+
if (this.intervalHoldsAnyChangedId(activeInterval, changedIds)) {
|
|
2973
|
+
this.scheduleWindowPublish();
|
|
2974
|
+
}
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
for (let i = 0; i < currentItems2.length; i++) {
|
|
2978
|
+
const id = this.getItemId(currentItems2[i]);
|
|
2979
|
+
if (!changedIds.has(id)) continue;
|
|
2980
|
+
const updated = this._itemIndex.get(id);
|
|
2981
|
+
if (!updated || updated !== currentItems2[i]) {
|
|
2904
2982
|
this.scheduleWindowPublish();
|
|
2905
2983
|
return;
|
|
2906
2984
|
}
|
|
@@ -2929,11 +3007,8 @@ var BasePaginator = class {
|
|
|
2929
3007
|
return;
|
|
2930
3008
|
}
|
|
2931
3009
|
}
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
this.state.partialNext({ items: this.intervalToItems(activeInterval) });
|
|
2935
|
-
return;
|
|
2936
|
-
}
|
|
3010
|
+
if (this.intervalHoldsAnyChangedId(activeInterval, changedIds)) {
|
|
3011
|
+
this.state.partialNext({ items: this.intervalToItems(activeInterval) });
|
|
2937
3012
|
}
|
|
2938
3013
|
}
|
|
2939
3014
|
// ---------------------------------------------------------------------------
|
|
@@ -3603,6 +3678,13 @@ var BasePaginator = class {
|
|
|
3603
3678
|
* - if this is the active interval, re-emit state.items from interval
|
|
3604
3679
|
*/
|
|
3605
3680
|
ingestItem(ingestedItem) {
|
|
3681
|
+
let ingested = false;
|
|
3682
|
+
this.withSingleWindowPublish(() => {
|
|
3683
|
+
ingested = this.applyItemIngestion(ingestedItem);
|
|
3684
|
+
});
|
|
3685
|
+
return ingested;
|
|
3686
|
+
}
|
|
3687
|
+
applyItemIngestion(ingestedItem) {
|
|
3606
3688
|
const id = this.getItemId(ingestedItem);
|
|
3607
3689
|
const previousItem = this._itemIndex.get(id);
|
|
3608
3690
|
const previousCoords = this.locateByItem(previousItem || ingestedItem);
|
|
@@ -3614,7 +3696,9 @@ var BasePaginator = class {
|
|
|
3614
3696
|
removedItemCoordinates = this.removeItemAtCoordinates(previousCoords);
|
|
3615
3697
|
}
|
|
3616
3698
|
const itemHasBeenRemoved = !!removedItemCoordinates?.state && removedItemCoordinates.state.currentIndex > -1;
|
|
3617
|
-
this._itemIndex.
|
|
3699
|
+
if (this._itemIndex.has(id) || this.matchesFilter(ingestedItem)) {
|
|
3700
|
+
this._itemIndex.setOne(ingestedItem);
|
|
3701
|
+
}
|
|
3618
3702
|
if (!this.matchesFilter(ingestedItem)) {
|
|
3619
3703
|
this.shrinkOffsetAfterRemoval(removedItemCoordinates);
|
|
3620
3704
|
if (!this.isWindowPublishSuspended && this.isStateThrottled && itemHasBeenRemoved)
|
|
@@ -3733,16 +3817,7 @@ var BasePaginator = class {
|
|
|
3733
3817
|
this._itemIndex.batch(fn);
|
|
3734
3818
|
return;
|
|
3735
3819
|
}
|
|
3736
|
-
this.
|
|
3737
|
-
try {
|
|
3738
|
-
this._itemIndex.batch(fn);
|
|
3739
|
-
} finally {
|
|
3740
|
-
this._windowPublishSuspendDepth -= 1;
|
|
3741
|
-
}
|
|
3742
|
-
if (this._windowPublishSuspendDepth === 0 && this._suspendedWindowDirty) {
|
|
3743
|
-
this._suspendedWindowDirty = false;
|
|
3744
|
-
this.flushWindowPublish();
|
|
3745
|
-
}
|
|
3820
|
+
this.withSingleWindowPublish(() => this._itemIndex.batch(fn), { exit: "sync" });
|
|
3746
3821
|
}
|
|
3747
3822
|
// ---------------------------------------------------------------------------
|
|
3748
3823
|
// Remove / contains
|
|
@@ -4359,6 +4434,10 @@ var CORE_NOTIFICATION_TYPE = {
|
|
|
4359
4434
|
messageJumpFailed: "api:message:jump:failed",
|
|
4360
4435
|
/** Jumping to the latest message failed. */
|
|
4361
4436
|
messageJumpToLatestFailed: "api:message:jumpToLatest:failed",
|
|
4437
|
+
/** Sending a composed message failed. The message stays in the list, marked failed. */
|
|
4438
|
+
messageSendFailed: "api:message:send:failed",
|
|
4439
|
+
/** Saving an edit failed. The edit is kept on the message, marked failed. */
|
|
4440
|
+
messageUpdateFailed: "api:message:update:failed",
|
|
4362
4441
|
pollCastVoteLimit: "validation:poll:castVote:limit",
|
|
4363
4442
|
pollCreateFailed: "api:poll:create:failed"
|
|
4364
4443
|
};
|
|
@@ -5021,34 +5100,37 @@ var MessageIntervalPaginator = class extends BasePaginator {
|
|
|
5021
5100
|
deletedAt
|
|
5022
5101
|
}) => {
|
|
5023
5102
|
const loadedMessages = this.items ?? [];
|
|
5024
|
-
this.batch(
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
if (
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5103
|
+
this.batch(
|
|
5104
|
+
() => {
|
|
5105
|
+
for (const message of loadedMessages) {
|
|
5106
|
+
if (message.user?.id === userId) {
|
|
5107
|
+
if (hardDelete) {
|
|
5108
|
+
this.removeItem({ id: message.id });
|
|
5109
|
+
} else {
|
|
5110
|
+
this.ingestItem(
|
|
5111
|
+
toDeletedMessage({
|
|
5112
|
+
message,
|
|
5113
|
+
hardDelete,
|
|
5114
|
+
deletedAt
|
|
5115
|
+
})
|
|
5116
|
+
);
|
|
5117
|
+
}
|
|
5118
|
+
continue;
|
|
5119
|
+
}
|
|
5120
|
+
if (message.quoted_message?.user?.id === userId && message.quoted_message.type !== "deleted") {
|
|
5121
|
+
this.ingestItem({
|
|
5122
|
+
...message,
|
|
5123
|
+
quoted_message: toDeletedMessage({
|
|
5124
|
+
message: formatMessage(message.quoted_message),
|
|
5033
5125
|
hardDelete,
|
|
5034
5126
|
deletedAt
|
|
5035
5127
|
})
|
|
5036
|
-
);
|
|
5128
|
+
});
|
|
5037
5129
|
}
|
|
5038
|
-
continue;
|
|
5039
|
-
}
|
|
5040
|
-
if (message.quoted_message?.user?.id === userId && message.quoted_message.type !== "deleted") {
|
|
5041
|
-
this.ingestItem({
|
|
5042
|
-
...message,
|
|
5043
|
-
quoted_message: toDeletedMessage({
|
|
5044
|
-
message: formatMessage(message.quoted_message),
|
|
5045
|
-
hardDelete,
|
|
5046
|
-
deletedAt
|
|
5047
|
-
})
|
|
5048
|
-
});
|
|
5049
5130
|
}
|
|
5050
|
-
}
|
|
5051
|
-
|
|
5131
|
+
},
|
|
5132
|
+
{ coalesce: true }
|
|
5133
|
+
);
|
|
5052
5134
|
};
|
|
5053
5135
|
/**
|
|
5054
5136
|
* Ensures quoted-message snapshots across loaded paginator cache are in sync
|
|
@@ -5084,6 +5166,7 @@ var MessageIntervalPaginator = class extends BasePaginator {
|
|
|
5084
5166
|
this.batch(() => {
|
|
5085
5167
|
for (const message of this._itemIndex.values()) {
|
|
5086
5168
|
if (message.user?.id !== user.id) continue;
|
|
5169
|
+
if (message.user === user) continue;
|
|
5087
5170
|
this._itemIndex.setOne({ ...message, user });
|
|
5088
5171
|
if (activeIds.has(this.getItemId(message))) activeAffected = true;
|
|
5089
5172
|
}
|
|
@@ -5094,58 +5177,6 @@ var MessageIntervalPaginator = class extends BasePaginator {
|
|
|
5094
5177
|
});
|
|
5095
5178
|
}
|
|
5096
5179
|
};
|
|
5097
|
-
/**
|
|
5098
|
-
* Apply a reaction WS event (`reaction.new` / `reaction.updated` / `reaction.deleted`) to the
|
|
5099
|
-
* cached message. The event's `message` already carries the server-updated
|
|
5100
|
-
* `reaction_groups` / `latest_reactions`; only `own_reactions` needs local preservation so a
|
|
5101
|
-
* cross-user reaction does not wipe the current user's reactions. This re-homes what
|
|
5102
|
-
* `ChannelState.addReaction` / `removeReaction` used to do off the now-removed
|
|
5103
|
-
* `channel.state.messages` / `channel.state.threads` caches (the same logic backs the thread
|
|
5104
|
-
* paginator via `Thread.messagePaginator`).
|
|
5105
|
-
*
|
|
5106
|
-
* `own_reactions` is seeded from the currently cached item (so another user's reaction keeps ours),
|
|
5107
|
-
* falling back to the event's own_reactions when the message is not loaded — matching the legacy
|
|
5108
|
-
* behavior where `_updateMessage` only mutated a message that existed locally.
|
|
5109
|
-
*
|
|
5110
|
-
* @param params - The reaction event payload.
|
|
5111
|
-
* @param params.message - The reaction event's message, carrying the
|
|
5112
|
-
* server-computed `reaction_groups` / `latest_reactions`. Ingested as-is except for `own_reactions`.
|
|
5113
|
-
* @param params.reaction - The reaction from the event. Only added to/removed from
|
|
5114
|
-
* `own_reactions` when its `user_id` is the current user; otherwise the current user's
|
|
5115
|
-
* `own_reactions` are left untouched.
|
|
5116
|
-
* @param [params.removed=false] - `true` for `reaction.deleted` (remove the reaction from
|
|
5117
|
-
* `own_reactions`); `false` for `reaction.new` / `reaction.updated` (add it).
|
|
5118
|
-
* @param [params.enforceUnique=false] - When adding, first clear the current user's existing
|
|
5119
|
-
* `own_reactions` so only the incoming one remains (used by `reaction.updated`, where a user's
|
|
5120
|
-
* reaction replaces their previous one).
|
|
5121
|
-
*
|
|
5122
|
-
* TODO(reactive-store): reflect reactions ONCE at the store level, not per-paginator. Both the
|
|
5123
|
-
* channel handler (channel.ts) and the thread handler (thread.ts) call this on every reaction.*
|
|
5124
|
-
* event, so a message held in more than one collection (a show_in_channel reply, or the thread
|
|
5125
|
-
* parent) is reflected TWICE: two writes to the same canonical slot, each fanning out to the
|
|
5126
|
-
* other holder (double re-projection) and minting a fresh ref that defeats the reconcile
|
|
5127
|
-
* ref-equality bail. Idempotent (counts come wholesale from the event) so the result is correct,
|
|
5128
|
-
* just wasteful. The store already fans out to every holder, so reflect once (by id, if held) and
|
|
5129
|
-
* retire the per-collection reflect calls + parent path + enforce_unique branch.
|
|
5130
|
-
*/
|
|
5131
|
-
this.reflectReaction = ({
|
|
5132
|
-
enforceUnique = false,
|
|
5133
|
-
message,
|
|
5134
|
-
reaction,
|
|
5135
|
-
removed = false
|
|
5136
|
-
}) => {
|
|
5137
|
-
const formatted = formatMessage(message);
|
|
5138
|
-
const existing = this.getItem(formatted.id);
|
|
5139
|
-
const baseOwnReactions = existing?.own_reactions ?? formatted.own_reactions ?? [];
|
|
5140
|
-
const own_reactions = computeOwnReactions({
|
|
5141
|
-
current: baseOwnReactions,
|
|
5142
|
-
enforceUnique,
|
|
5143
|
-
reaction,
|
|
5144
|
-
removed,
|
|
5145
|
-
userId: this.channel.getClient().userId
|
|
5146
|
-
});
|
|
5147
|
-
this.ingestItem({ ...formatted, own_reactions });
|
|
5148
|
-
};
|
|
5149
5180
|
/**
|
|
5150
5181
|
* Map a timestamp to a loaded message — the first message in the latest (head) window whose
|
|
5151
5182
|
* `created_at` is >= `timestamp` (mirrors the legacy `ChannelState.findMessageByTimestamp`
|
|
@@ -5883,6 +5914,7 @@ var PENDING_UPLOAD_STATES = [
|
|
|
5883
5914
|
];
|
|
5884
5915
|
var isPendingUpload = (attachment) => isLocalUploadAttachment(attachment) && PENDING_UPLOAD_STATES.includes(attachment.localMetadata.uploadState);
|
|
5885
5916
|
var isFinishedUpload = (attachment) => isLocalUploadAttachment(attachment) && attachment.localMetadata.uploadState === "finished";
|
|
5917
|
+
var isFailedUpload = (attachment) => isLocalUploadAttachment(attachment) && attachment.localMetadata.uploadState === "failed";
|
|
5886
5918
|
var isFileAttachment = (attachment, supportedVideoFormat = []) => attachment.type === "file" || !!(attachment.custom?.mime_type && supportedVideoFormat.indexOf(
|
|
5887
5919
|
attachment.custom?.mime_type
|
|
5888
5920
|
) === -1 && attachment.type !== "video");
|
|
@@ -5901,6 +5933,27 @@ var isUploadedAttachment = (attachment) => isAudioAttachment(attachment) || isFi
|
|
|
5901
5933
|
var isSharedLocationResponseData = (location) => !!location.latitude && !!location.longitude && !!location.channel_cid;
|
|
5902
5934
|
var isGiphyAttachment = (attachment) => attachment.type === "giphy";
|
|
5903
5935
|
|
|
5936
|
+
// src/messageComposer/attachmentUtils.ts
|
|
5937
|
+
var getAttachmentPreviewUrl = (attachment, ...urls) => {
|
|
5938
|
+
const resolved = urls.find(Boolean);
|
|
5939
|
+
if (resolved) return resolved;
|
|
5940
|
+
return isLocalUploadAttachment(attachment) ? attachment.localMetadata.previewUri : void 0;
|
|
5941
|
+
};
|
|
5942
|
+
var resolveAttachmentFileSize = (attachment) => attachment?.custom?.file_size ?? attachment?.file_size;
|
|
5943
|
+
var resolveAttachmentFullByteSize = (attachment) => {
|
|
5944
|
+
const fromFile = attachment?.localMetadata?.file?.size;
|
|
5945
|
+
if (typeof fromFile === "number" && Number.isFinite(fromFile) && fromFile >= 0) {
|
|
5946
|
+
return fromFile;
|
|
5947
|
+
}
|
|
5948
|
+
const raw = resolveAttachmentFileSize(attachment);
|
|
5949
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw >= 0) return raw;
|
|
5950
|
+
if (typeof raw === "string") {
|
|
5951
|
+
const parsed = parseFloat(raw);
|
|
5952
|
+
if (Number.isFinite(parsed) && parsed >= 0) return parsed;
|
|
5953
|
+
}
|
|
5954
|
+
return void 0;
|
|
5955
|
+
};
|
|
5956
|
+
|
|
5904
5957
|
// src/messageComposer/fileUtils.ts
|
|
5905
5958
|
var isFile = (fileLike) => !!fileLike.lastModified && !("uri" in fileLike);
|
|
5906
5959
|
var isFileList = (obj) => {
|
|
@@ -6167,6 +6220,12 @@ var import_axios = __toESM(require("axios"));
|
|
|
6167
6220
|
var import_state_store7 = require("@stream-io/state-store");
|
|
6168
6221
|
var logger3 = chatLoggerSystem.getLogger("upload-manager");
|
|
6169
6222
|
var isUploadCancellation = (error) => import_axios.default.isCancel(error) || error instanceof Error && error.name === "AbortError";
|
|
6223
|
+
var isUploadConfirmationPending = (localMetadata) => {
|
|
6224
|
+
if (!localMetadata || localMetadata.uploadState !== "uploading") return false;
|
|
6225
|
+
if (localMetadata.uploadConfirmationPending !== void 0)
|
|
6226
|
+
return localMetadata.uploadConfirmationPending;
|
|
6227
|
+
return localMetadata.uploadProgress !== void 0 && localMetadata.uploadProgress >= 100;
|
|
6228
|
+
};
|
|
6170
6229
|
var initState = () => ({ uploads: {} });
|
|
6171
6230
|
var upsertById = (uploads, record) => ({
|
|
6172
6231
|
...uploads,
|
|
@@ -6290,12 +6349,12 @@ var UploadManager = class {
|
|
|
6290
6349
|
this.state = new import_state_store7.StateStore(initState());
|
|
6291
6350
|
}
|
|
6292
6351
|
resolveAttachmentManager(channelCid) {
|
|
6293
|
-
const colon =
|
|
6294
|
-
|
|
6352
|
+
const colon = ":";
|
|
6353
|
+
const colonIndex = channelCid.indexOf(colon);
|
|
6354
|
+
if (colonIndex <= 0 || colonIndex === channelCid.length - 1) {
|
|
6295
6355
|
throw new Error(`Invalid channelCid: ${channelCid}`);
|
|
6296
6356
|
}
|
|
6297
|
-
const channelType = channelCid.
|
|
6298
|
-
const channelId = channelCid.slice(colon + 1);
|
|
6357
|
+
const [channelType, channelId] = channelCid.split(colon);
|
|
6299
6358
|
return this.client.channel(channelType, channelId).messageComposer.attachmentManager;
|
|
6300
6359
|
}
|
|
6301
6360
|
get uploads() {
|
|
@@ -7029,13 +7088,15 @@ var composeWithPendingUploads = ({
|
|
|
7029
7088
|
composer,
|
|
7030
7089
|
state
|
|
7031
7090
|
}) => {
|
|
7032
|
-
const composerIsKeptAsDraft =
|
|
7091
|
+
const composerIsKeptAsDraft = composer.retainsCompositionOnSubmit;
|
|
7033
7092
|
const relevantAttachments = composer.attachmentManager.attachments.filter(
|
|
7034
|
-
(attachment) => isFinishedUpload(attachment) || !composerIsKeptAsDraft && isPendingUpload(attachment)
|
|
7093
|
+
(attachment) => isFinishedUpload(attachment) || !composerIsKeptAsDraft && (isPendingUpload(attachment) || isFailedUpload(attachment))
|
|
7035
7094
|
);
|
|
7036
7095
|
const localAttachments = (state.localMessage.attachments ?? []).concat(
|
|
7096
|
+
// Only a finished upload has a URL of its own. Everything else keeps `localMetadata`, which
|
|
7097
|
+
// is what the message list joins to the live `uploadManager` record and what a retry needs.
|
|
7037
7098
|
relevantAttachments.map(
|
|
7038
|
-
(attachment) =>
|
|
7099
|
+
(attachment) => isFinishedUpload(attachment) ? localAttachmentToAttachment(attachment) : attachment
|
|
7039
7100
|
)
|
|
7040
7101
|
);
|
|
7041
7102
|
const messageAttachments = (state.message.attachments ?? []).concat(
|
|
@@ -7054,21 +7115,6 @@ var composeWithPendingUploads = ({
|
|
|
7054
7115
|
} : state.message
|
|
7055
7116
|
};
|
|
7056
7117
|
};
|
|
7057
|
-
var createSendWithPendingUploadsAttachmentsMiddleware = (composer) => ({
|
|
7058
|
-
allowsPendingUploads: true,
|
|
7059
|
-
id: "stream-io/message-composer-middleware/attachments",
|
|
7060
|
-
handlers: {
|
|
7061
|
-
compose: ({
|
|
7062
|
-
state,
|
|
7063
|
-
next,
|
|
7064
|
-
forward
|
|
7065
|
-
}) => {
|
|
7066
|
-
const { attachmentManager } = composer;
|
|
7067
|
-
if (!attachmentManager) return forward();
|
|
7068
|
-
return next(composeWithPendingUploads({ composer, state }));
|
|
7069
|
-
}
|
|
7070
|
-
}
|
|
7071
|
-
});
|
|
7072
7118
|
var createAttachmentsCompositionMiddleware = (composer) => ({
|
|
7073
7119
|
id: "stream-io/message-composer-middleware/attachments",
|
|
7074
7120
|
handlers: {
|
|
@@ -7080,6 +7126,9 @@ var createAttachmentsCompositionMiddleware = (composer) => ({
|
|
|
7080
7126
|
}) => {
|
|
7081
7127
|
const { attachmentManager } = composer;
|
|
7082
7128
|
if (!attachmentManager) return forward();
|
|
7129
|
+
if (attachmentManager.config.pendingUploadsEnabled) {
|
|
7130
|
+
return next(composeWithPendingUploads({ composer, state }));
|
|
7131
|
+
}
|
|
7083
7132
|
if (attachmentManager.uploadsInProgressCount > 0) {
|
|
7084
7133
|
composer.client.notifications.addWarning({
|
|
7085
7134
|
message: "Wait until all attachments have uploaded",
|
|
@@ -8616,7 +8665,7 @@ var MessagePaginator = class extends MessageIntervalPaginator {
|
|
|
8616
8665
|
const { lastMessage, seededLastMessageAt } = this.aggregateState.getLatestValue();
|
|
8617
8666
|
const fromMessage = getMessageCreatedAtTimestamp(lastMessage);
|
|
8618
8667
|
if (fromMessage !== null && seededLastMessageAt !== null) {
|
|
8619
|
-
return Math.max(fromMessage, seededLastMessageAt);
|
|
8668
|
+
return asTimestampNS(Math.max(fromMessage, seededLastMessageAt));
|
|
8620
8669
|
}
|
|
8621
8670
|
return fromMessage ?? seededLastMessageAt;
|
|
8622
8671
|
}
|
|
@@ -9805,10 +9854,11 @@ var createLinkPreviewsCompositionMiddleware = (composer) => ({
|
|
|
9805
9854
|
const linkPreviews = linkPreviewsManager.loadingPreviews.length > 0 ? [] : linkPreviewsManager.loadedPreviews.map(
|
|
9806
9855
|
(preview) => LinkPreviewsManager.getPreviewData(preview)
|
|
9807
9856
|
);
|
|
9808
|
-
const
|
|
9857
|
+
const messageAttachments = (state.message.attachments ?? []).concat(
|
|
9809
9858
|
linkPreviews
|
|
9810
9859
|
);
|
|
9811
|
-
|
|
9860
|
+
const localAttachments = (state.localMessage.attachments ?? []).concat(linkPreviews);
|
|
9861
|
+
if (!messageAttachments.length && !localAttachments.length) return forward();
|
|
9812
9862
|
const sendOptions = { ...state.sendOptions };
|
|
9813
9863
|
const skip_enrich_url = !someLinkPreviewsLoading && linkPreviews.length > 0 || someLinkPreviewsDismissed;
|
|
9814
9864
|
if (skip_enrich_url) {
|
|
@@ -9816,14 +9866,14 @@ var createLinkPreviewsCompositionMiddleware = (composer) => ({
|
|
|
9816
9866
|
}
|
|
9817
9867
|
return next({
|
|
9818
9868
|
...state,
|
|
9819
|
-
message: {
|
|
9869
|
+
message: messageAttachments.length ? {
|
|
9820
9870
|
...state.message,
|
|
9821
|
-
attachments
|
|
9822
|
-
},
|
|
9823
|
-
localMessage: {
|
|
9871
|
+
attachments: messageAttachments
|
|
9872
|
+
} : state.message,
|
|
9873
|
+
localMessage: localAttachments.length ? {
|
|
9824
9874
|
...state.localMessage,
|
|
9825
|
-
attachments
|
|
9826
|
-
},
|
|
9875
|
+
attachments: localAttachments
|
|
9876
|
+
} : state.localMessage,
|
|
9827
9877
|
sendOptions
|
|
9828
9878
|
});
|
|
9829
9879
|
}
|
|
@@ -10180,13 +10230,6 @@ var MessageComposerMiddlewareExecutor = class extends MiddlewareExecutor {
|
|
|
10180
10230
|
createCompositionDataCleanupMiddleware(composer)
|
|
10181
10231
|
]);
|
|
10182
10232
|
}
|
|
10183
|
-
/**
|
|
10184
|
-
* Narrows the base accessor to the composition middleware type, so callers can read the
|
|
10185
|
-
* declarations it carries (`allowsPendingUploads`) without casting.
|
|
10186
|
-
*/
|
|
10187
|
-
get installedMiddleware() {
|
|
10188
|
-
return super.installedMiddleware;
|
|
10189
|
-
}
|
|
10190
10233
|
};
|
|
10191
10234
|
var MessageDraftComposerMiddlewareExecutor = class extends MiddlewareExecutor {
|
|
10192
10235
|
constructor({ composer }) {
|
|
@@ -11335,6 +11378,7 @@ var DEFAULT_ATTACHMENT_MANAGER_CONFIG = {
|
|
|
11335
11378
|
enabled: true,
|
|
11336
11379
|
fileUploadFilter: () => true,
|
|
11337
11380
|
maxNumberOfFilesPerMessage: API_MAX_FILES_ALLOWED_PER_MESSAGE,
|
|
11381
|
+
pendingUploadsEnabled: false,
|
|
11338
11382
|
trackUploadProgress: true
|
|
11339
11383
|
};
|
|
11340
11384
|
var DEFAULT_TEXT_COMPOSER_CONFIG = {
|
|
@@ -11356,6 +11400,7 @@ var DEFAULT_COMPOSER_CONFIG = deepFreezeConfig({
|
|
|
11356
11400
|
linkPreviews: DEFAULT_LINK_PREVIEW_MANAGER_CONFIG,
|
|
11357
11401
|
location: DEFAULT_LOCATION_COMPOSER_CONFIG,
|
|
11358
11402
|
polls: DEFAULT_POLL_COMPOSER_CONFIG,
|
|
11403
|
+
retainCompositionOnSubmit: (composer) => !!composer.pollId,
|
|
11359
11404
|
text: DEFAULT_TEXT_COMPOSER_CONFIG
|
|
11360
11405
|
});
|
|
11361
11406
|
|
|
@@ -11918,6 +11963,123 @@ var mergeServerRestrictions = (requested, restrictions, upperBounds) => {
|
|
|
11918
11963
|
// src/thread.ts
|
|
11919
11964
|
var import_state_store18 = require("@stream-io/state-store");
|
|
11920
11965
|
|
|
11966
|
+
// src/messageOperations/applyReactionLocally.ts
|
|
11967
|
+
var applyReactionLocally = (client, {
|
|
11968
|
+
enforceUnique = false,
|
|
11969
|
+
messageId,
|
|
11970
|
+
reaction,
|
|
11971
|
+
removed = false
|
|
11972
|
+
}) => {
|
|
11973
|
+
const store = client.messageStore;
|
|
11974
|
+
const user = client.user;
|
|
11975
|
+
const existing = store.get(messageId);
|
|
11976
|
+
if (!user || !existing) return;
|
|
11977
|
+
const now = nowNs();
|
|
11978
|
+
const reactionResponse = {
|
|
11979
|
+
...reaction,
|
|
11980
|
+
created_at: reaction.created_at ?? now,
|
|
11981
|
+
custom: reaction.custom ?? {},
|
|
11982
|
+
message_id: messageId,
|
|
11983
|
+
score: reaction.score ?? 1,
|
|
11984
|
+
type: reaction.type,
|
|
11985
|
+
updated_at: reaction.updated_at ?? now,
|
|
11986
|
+
user,
|
|
11987
|
+
user_id: user.id
|
|
11988
|
+
};
|
|
11989
|
+
const removedReactions = removed ? existing.own_reactions?.filter((r) => r.type === reactionResponse.type) ?? [] : enforceUnique ? existing.own_reactions ?? [] : [];
|
|
11990
|
+
const withCounts = removed ? messageWithReactionRemoved(existing, reactionResponse) : messageWithReactionAdded(existing, reactionResponse, enforceUnique);
|
|
11991
|
+
const own_reactions = computeOwnReactions({
|
|
11992
|
+
current: existing.own_reactions ?? [],
|
|
11993
|
+
enforceUnique,
|
|
11994
|
+
reaction: reactionResponse,
|
|
11995
|
+
removed,
|
|
11996
|
+
userId: user.id
|
|
11997
|
+
});
|
|
11998
|
+
store.upsert({ ...withCounts, own_reactions });
|
|
11999
|
+
store.flushSubscribers(messageId);
|
|
12000
|
+
const persisted = store.get(messageId);
|
|
12001
|
+
if (persisted) {
|
|
12002
|
+
client.offlineDb?.executeQuerySafely(
|
|
12003
|
+
(db) => removed ? db.deleteReaction({ message: persisted, reaction: reactionResponse }) : enforceUnique ? db.updateReaction({ message: persisted, reaction: reactionResponse }) : db.insertReaction({ message: persisted, reaction: reactionResponse }),
|
|
12004
|
+
{ method: "applyReactionLocally" }
|
|
12005
|
+
);
|
|
12006
|
+
}
|
|
12007
|
+
return () => {
|
|
12008
|
+
if (!removed) {
|
|
12009
|
+
applyReactionLocally(client, {
|
|
12010
|
+
messageId,
|
|
12011
|
+
reaction: reactionResponse,
|
|
12012
|
+
removed: true
|
|
12013
|
+
});
|
|
12014
|
+
}
|
|
12015
|
+
for (const removedReaction of removedReactions) {
|
|
12016
|
+
applyReactionLocally(client, {
|
|
12017
|
+
messageId,
|
|
12018
|
+
reaction: removedReaction,
|
|
12019
|
+
removed: false
|
|
12020
|
+
});
|
|
12021
|
+
}
|
|
12022
|
+
};
|
|
12023
|
+
};
|
|
12024
|
+
|
|
12025
|
+
// src/messageOperations/optimistic.ts
|
|
12026
|
+
var isQueuedForReplay = async (client, messageId, types) => {
|
|
12027
|
+
const { offlineDb } = client;
|
|
12028
|
+
if (!offlineDb?.state.getLatestValue().initialized) return false;
|
|
12029
|
+
const pending = await offlineDb.getPendingTasks({ messageId });
|
|
12030
|
+
return !!pending?.some((task) => types.includes(task.type));
|
|
12031
|
+
};
|
|
12032
|
+
var reconcileHeldMessage = (channel, message) => {
|
|
12033
|
+
if (!message) return;
|
|
12034
|
+
const { messageStore } = channel.getClient();
|
|
12035
|
+
if (!messageStore.has(message.id)) return;
|
|
12036
|
+
messageStore.upsert(formatMessage(message));
|
|
12037
|
+
};
|
|
12038
|
+
var addReactionOptimistically = async ({
|
|
12039
|
+
channel,
|
|
12040
|
+
messageId,
|
|
12041
|
+
options,
|
|
12042
|
+
reaction
|
|
12043
|
+
}) => {
|
|
12044
|
+
const client = channel.getClient();
|
|
12045
|
+
const { created_at, updated_at, ...restOfReaction } = reaction;
|
|
12046
|
+
const undo = applyReactionLocally(client, {
|
|
12047
|
+
enforceUnique: options?.enforce_unique ?? false,
|
|
12048
|
+
messageId,
|
|
12049
|
+
reaction: {
|
|
12050
|
+
...restOfReaction,
|
|
12051
|
+
...created_at ? { created_at: dateToNs(created_at) } : {},
|
|
12052
|
+
...updated_at ? { updated_at: dateToNs(updated_at) } : {}
|
|
12053
|
+
}
|
|
12054
|
+
});
|
|
12055
|
+
try {
|
|
12056
|
+
const response = await channel.sendReaction({ id: messageId, reaction, ...options });
|
|
12057
|
+
reconcileHeldMessage(channel, response?.message);
|
|
12058
|
+
} catch (error) {
|
|
12059
|
+
if (!await isQueuedForReplay(client, messageId, ["send-reaction"])) undo?.();
|
|
12060
|
+
throw error;
|
|
12061
|
+
}
|
|
12062
|
+
};
|
|
12063
|
+
var deleteReactionOptimistically = async ({
|
|
12064
|
+
channel,
|
|
12065
|
+
messageId,
|
|
12066
|
+
type
|
|
12067
|
+
}) => {
|
|
12068
|
+
const client = channel.getClient();
|
|
12069
|
+
const undo = applyReactionLocally(client, {
|
|
12070
|
+
messageId,
|
|
12071
|
+
reaction: { type },
|
|
12072
|
+
removed: true
|
|
12073
|
+
});
|
|
12074
|
+
try {
|
|
12075
|
+
const response = await channel.deleteReaction({ id: messageId, type });
|
|
12076
|
+
reconcileHeldMessage(channel, response?.message);
|
|
12077
|
+
} catch (error) {
|
|
12078
|
+
if (!await isQueuedForReplay(client, messageId, ["delete-reaction"])) undo?.();
|
|
12079
|
+
throw error;
|
|
12080
|
+
}
|
|
12081
|
+
};
|
|
12082
|
+
|
|
11921
12083
|
// src/messageOperations/MessageOperationStatePolicy.ts
|
|
11922
12084
|
var parseError = (error) => {
|
|
11923
12085
|
const stringError = JSON.stringify(error);
|
|
@@ -12172,15 +12334,57 @@ var MessageOperations = class {
|
|
|
12172
12334
|
});
|
|
12173
12335
|
}
|
|
12174
12336
|
/**
|
|
12175
|
-
*
|
|
12176
|
-
*
|
|
12177
|
-
|
|
12337
|
+
* Resolves any in-flight upload and folds the URLs back into both payloads. A no-op without
|
|
12338
|
+
* {@link MessageOperationsContext.settlePendingUploads} or a pending attachment.
|
|
12339
|
+
*/
|
|
12340
|
+
async settlePendingUploads(params) {
|
|
12341
|
+
const settle = this.ctx.settlePendingUploads;
|
|
12342
|
+
const attachments = params.localMessage.attachments;
|
|
12343
|
+
if (!settle || !attachments?.length) return { params };
|
|
12344
|
+
const { attachments: settledAttachments, failureReason } = await settle(attachments);
|
|
12345
|
+
if (settledAttachments === attachments) return { failureReason, params };
|
|
12346
|
+
const wirePayload = "message" in params && params.message ? { message: { ...params.message, attachments: settledAttachments } } : {};
|
|
12347
|
+
return {
|
|
12348
|
+
failureReason,
|
|
12349
|
+
params: {
|
|
12350
|
+
...params,
|
|
12351
|
+
...wirePayload,
|
|
12352
|
+
localMessage: { ...params.localMessage, attachments: settledAttachments }
|
|
12353
|
+
}
|
|
12354
|
+
};
|
|
12355
|
+
}
|
|
12356
|
+
/**
|
|
12357
|
+
* Applies the owner's request-ordering policy for this kind, if it supplied one.
|
|
12358
|
+
*/
|
|
12359
|
+
withRequestSequencing(kind, request) {
|
|
12360
|
+
return this.ctx.sequenceRequests ? this.ctx.sequenceRequests(kind, request) : request();
|
|
12361
|
+
}
|
|
12362
|
+
/**
|
|
12363
|
+
* The shared lifecycle: apply the optimistic state, settle any attachment upload still in flight,
|
|
12364
|
+
* fire the request, then reconcile or record the failure. `kind` is threaded through because the
|
|
12365
|
+
* three operations want materially different state transitions — see
|
|
12366
|
+
* {@link MessageOperationStatePolicy}.
|
|
12178
12367
|
*/
|
|
12179
12368
|
async run(kind, params, doRequest) {
|
|
12180
12369
|
const messageId = params.localMessage.id;
|
|
12181
12370
|
const optimistic = this.policy.optimistic(kind, params);
|
|
12371
|
+
let effective = params;
|
|
12182
12372
|
try {
|
|
12183
|
-
const response = await
|
|
12373
|
+
const response = await this.withRequestSequencing(kind, async () => {
|
|
12374
|
+
if (kind !== "delete") {
|
|
12375
|
+
const settled2 = await this.settlePendingUploads(params);
|
|
12376
|
+
effective = settled2.params;
|
|
12377
|
+
if (settled2.failureReason) throw settled2.failureReason;
|
|
12378
|
+
const settledAttachments = effective.localMessage.attachments;
|
|
12379
|
+
const held = settledAttachments === params.localMessage.attachments ? void 0 : this.ctx.get(messageId);
|
|
12380
|
+
if (held) {
|
|
12381
|
+
const withUrls = { ...held, attachments: settledAttachments };
|
|
12382
|
+
this.ctx.ingest(withUrls);
|
|
12383
|
+
this.ctx.persist(withUrls);
|
|
12384
|
+
}
|
|
12385
|
+
}
|
|
12386
|
+
return await doRequest(effective);
|
|
12387
|
+
});
|
|
12184
12388
|
this.policy.success({
|
|
12185
12389
|
kind,
|
|
12186
12390
|
messageFromResponse: response?.message,
|
|
@@ -12192,7 +12396,7 @@ var MessageOperations = class {
|
|
|
12192
12396
|
await this.policy.failure({
|
|
12193
12397
|
error: e,
|
|
12194
12398
|
kind,
|
|
12195
|
-
localMessage:
|
|
12399
|
+
localMessage: effective.localMessage,
|
|
12196
12400
|
messageId,
|
|
12197
12401
|
optimistic,
|
|
12198
12402
|
options: params.options
|
|
@@ -12200,6 +12404,13 @@ var MessageOperations = class {
|
|
|
12200
12404
|
throw e;
|
|
12201
12405
|
}
|
|
12202
12406
|
}
|
|
12407
|
+
/*
|
|
12408
|
+
* Each operation resolves its request as `requestFn ?? handlers.<kind> ?? defaults.<kind>`.
|
|
12409
|
+
*
|
|
12410
|
+
* `handlers` is the integrator seam (`ChannelConfig.requestHandlers`). `requestFn` is a direct
|
|
12411
|
+
* injection point for tests; it is not exposed on the public methods because it wins over
|
|
12412
|
+
* `handlers`, so a caller reaching for it would bypass whatever the host SDK registered.
|
|
12413
|
+
*/
|
|
12203
12414
|
async send(params, requestFn) {
|
|
12204
12415
|
const handlers = this.ctx.handlers();
|
|
12205
12416
|
const messageToSend = this.normalizeMessage(
|
|
@@ -12272,143 +12483,252 @@ var MessageOperations = class {
|
|
|
12272
12483
|
const doRequest = requestFn ?? handlers.delete ?? (async (p) => await this.ctx.defaults.delete(p.localMessage.id, p.options));
|
|
12273
12484
|
return await this.run("delete", params, doRequest);
|
|
12274
12485
|
}
|
|
12486
|
+
async retrySendWithLocalUpdate({
|
|
12487
|
+
localMessage,
|
|
12488
|
+
options
|
|
12489
|
+
}) {
|
|
12490
|
+
await this.retry({ localMessage: { ...localMessage, type: "regular" }, options });
|
|
12491
|
+
}
|
|
12492
|
+
/**
|
|
12493
|
+
* Adds a reaction with an optimistic local state update — see {@link addReactionOptimistically}.
|
|
12494
|
+
*
|
|
12495
|
+
* The request routes through the channel because reactions are channel-level, while the local write
|
|
12496
|
+
* is addressed by message id and so reaches a pure thread reply that no channel collection holds.
|
|
12497
|
+
*/
|
|
12498
|
+
async addReactionWithLocalUpdate(params) {
|
|
12499
|
+
await addReactionOptimistically({ channel: this.ctx.channel, ...params });
|
|
12500
|
+
}
|
|
12501
|
+
/**
|
|
12502
|
+
* Removes the current user's reaction with an optimistic local state update, mirroring
|
|
12503
|
+
* {@link MessageOperations.addReactionWithLocalUpdate}.
|
|
12504
|
+
*/
|
|
12505
|
+
async deleteReactionWithLocalUpdate(params) {
|
|
12506
|
+
await deleteReactionOptimistically({ channel: this.ctx.channel, ...params });
|
|
12507
|
+
}
|
|
12275
12508
|
};
|
|
12276
12509
|
|
|
12277
|
-
// src/
|
|
12278
|
-
var
|
|
12279
|
-
|
|
12280
|
-
|
|
12281
|
-
|
|
12282
|
-
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
const user = client.user;
|
|
12286
|
-
const existing = store.get(messageId);
|
|
12287
|
-
if (!user || !existing) return;
|
|
12288
|
-
const now = nowNs();
|
|
12289
|
-
const reactionResponse = {
|
|
12290
|
-
...reaction,
|
|
12291
|
-
created_at: reaction.created_at ?? now,
|
|
12292
|
-
custom: reaction.custom ?? {},
|
|
12293
|
-
message_id: messageId,
|
|
12294
|
-
score: reaction.score ?? 1,
|
|
12295
|
-
type: reaction.type,
|
|
12296
|
-
updated_at: reaction.updated_at ?? now,
|
|
12297
|
-
user,
|
|
12298
|
-
user_id: user.id
|
|
12299
|
-
};
|
|
12300
|
-
const removedReactions = removed ? existing.own_reactions?.filter((r) => r.type === reactionResponse.type) ?? [] : enforceUnique ? existing.own_reactions ?? [] : [];
|
|
12301
|
-
const withCounts = removed ? messageWithReactionRemoved(existing, reactionResponse) : messageWithReactionAdded(existing, reactionResponse, enforceUnique);
|
|
12302
|
-
const own_reactions = computeOwnReactions({
|
|
12303
|
-
current: existing.own_reactions ?? [],
|
|
12304
|
-
enforceUnique,
|
|
12305
|
-
reaction: reactionResponse,
|
|
12306
|
-
removed,
|
|
12307
|
-
userId: user.id
|
|
12308
|
-
});
|
|
12309
|
-
store.upsert({ ...withCounts, own_reactions });
|
|
12310
|
-
store.flushSubscribers(messageId);
|
|
12311
|
-
const persisted = store.get(messageId);
|
|
12312
|
-
if (persisted) {
|
|
12313
|
-
client.offlineDb?.executeQuerySafely(
|
|
12314
|
-
(db) => removed ? db.deleteReaction({ message: persisted, reaction: reactionResponse }) : enforceUnique ? db.updateReaction({ message: persisted, reaction: reactionResponse }) : db.insertReaction({ message: persisted, reaction: reactionResponse }),
|
|
12315
|
-
{ method: "applyReactionLocally" }
|
|
12510
|
+
// src/messageOperations/persistence.ts
|
|
12511
|
+
var createMessageOperationsPersistence = ({
|
|
12512
|
+
channel
|
|
12513
|
+
}) => ({
|
|
12514
|
+
persist: (message) => {
|
|
12515
|
+
channel.getClient().offlineDb?.executeQuerySafely(
|
|
12516
|
+
(db) => db.upsertMessageWithChannelGuard({ message: { ...message, cid: channel.cid } }),
|
|
12517
|
+
{ method: "messageOperations:persist" }
|
|
12316
12518
|
);
|
|
12519
|
+
},
|
|
12520
|
+
purge: (id) => {
|
|
12521
|
+
channel.getClient().offlineDb?.executeQuerySafely((db) => db.hardDeleteMessage({ id }), {
|
|
12522
|
+
method: "messageOperations:purge"
|
|
12523
|
+
});
|
|
12524
|
+
},
|
|
12525
|
+
isQueued: (messageId, types) => isQueuedForReplay(channel.getClient(), messageId, types)
|
|
12526
|
+
});
|
|
12527
|
+
|
|
12528
|
+
// src/messageOperations/sendOrdering.ts
|
|
12529
|
+
var keepSendOrderWhilePendingUploadsAllowed = ({
|
|
12530
|
+
channelCid,
|
|
12531
|
+
composer,
|
|
12532
|
+
kind,
|
|
12533
|
+
request
|
|
12534
|
+
}) => (kind === "send" || kind === "retry") && composer.allowsPendingUploads ? withoutConcurrency(`stream-chat/send-message/${channelCid}`, request) : request();
|
|
12535
|
+
|
|
12536
|
+
// src/messageOperations/settlePendingAttachmentUploads.ts
|
|
12537
|
+
var applyUploadResult = (attachment, response) => {
|
|
12538
|
+
const enriched = { ...attachment };
|
|
12539
|
+
if (isLocalImageAttachment(enriched)) {
|
|
12540
|
+
enriched.image_url = response.file;
|
|
12541
|
+
} else {
|
|
12542
|
+
enriched.asset_url = response.file;
|
|
12317
12543
|
}
|
|
12318
|
-
|
|
12319
|
-
|
|
12320
|
-
|
|
12321
|
-
|
|
12322
|
-
|
|
12323
|
-
|
|
12324
|
-
|
|
12544
|
+
if (response.thumb_url) {
|
|
12545
|
+
enriched.thumb_url = response.thumb_url;
|
|
12546
|
+
}
|
|
12547
|
+
const { previewUri } = attachment.localMetadata;
|
|
12548
|
+
if (previewUri?.startsWith("blob:")) URL.revokeObjectURL?.(previewUri);
|
|
12549
|
+
const { localMetadata: _localMetadata, ...rest } = enriched;
|
|
12550
|
+
return rest;
|
|
12551
|
+
};
|
|
12552
|
+
var settlePendingAttachmentUploads = async ({
|
|
12553
|
+
attachments,
|
|
12554
|
+
channelCid,
|
|
12555
|
+
client
|
|
12556
|
+
}) => {
|
|
12557
|
+
const unsettledIndexes = attachments.reduce((indexes, attachment, index) => {
|
|
12558
|
+
if (isPendingUpload(attachment) || isFailedUpload(attachment)) indexes.push(index);
|
|
12559
|
+
return indexes;
|
|
12560
|
+
}, []);
|
|
12561
|
+
if (!unsettledIndexes.length) return { attachments };
|
|
12562
|
+
const settled2 = await Promise.allSettled(
|
|
12563
|
+
unsettledIndexes.map((index) => {
|
|
12564
|
+
const { file, id } = attachments[index].localMetadata;
|
|
12565
|
+
return client.uploadManager.upload({ channelCid, file, id });
|
|
12566
|
+
})
|
|
12567
|
+
);
|
|
12568
|
+
const nextAttachments = [...attachments];
|
|
12569
|
+
let failureReason;
|
|
12570
|
+
settled2.forEach((result, position) => {
|
|
12571
|
+
const index = unsettledIndexes[position];
|
|
12572
|
+
const attachment = nextAttachments[index];
|
|
12573
|
+
if (result.status === "fulfilled" && result.value) {
|
|
12574
|
+
nextAttachments[index] = applyUploadResult(attachment, result.value);
|
|
12575
|
+
return;
|
|
12325
12576
|
}
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
messageId,
|
|
12329
|
-
reaction: removedReaction,
|
|
12330
|
-
removed: false
|
|
12331
|
-
});
|
|
12577
|
+
if (!failureReason) {
|
|
12578
|
+
failureReason = result.status === "rejected" ? result.reason : new Error("Attachment upload returned no result");
|
|
12332
12579
|
}
|
|
12333
|
-
};
|
|
12580
|
+
});
|
|
12581
|
+
return { attachments: nextAttachments, failureReason };
|
|
12334
12582
|
};
|
|
12335
12583
|
|
|
12336
|
-
// src/messageOperations/
|
|
12337
|
-
var
|
|
12338
|
-
const
|
|
12339
|
-
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12584
|
+
// src/messageOperations/createMessageOperations.ts
|
|
12585
|
+
var createMessageOperations = (collection) => {
|
|
12586
|
+
const paginator = collection.messagePaginator;
|
|
12587
|
+
const { channel, parentMessageId } = paginator;
|
|
12588
|
+
return new MessageOperations({
|
|
12589
|
+
sequenceRequests: (kind, request) => keepSendOrderWhilePendingUploadsAllowed({
|
|
12590
|
+
channelCid: channel.cid,
|
|
12591
|
+
composer: collection.messageComposer,
|
|
12592
|
+
kind,
|
|
12593
|
+
request
|
|
12594
|
+
}),
|
|
12595
|
+
settlePendingUploads: (attachments) => settlePendingAttachmentUploads({
|
|
12596
|
+
attachments,
|
|
12597
|
+
// A reply uploads against the channel the thread belongs to - there is no thread-scoped
|
|
12598
|
+
// upload target.
|
|
12599
|
+
channelCid: channel.cid,
|
|
12600
|
+
client: channel.getClient()
|
|
12601
|
+
}),
|
|
12602
|
+
...createMessageOperationsPersistence({ channel }),
|
|
12603
|
+
channel,
|
|
12604
|
+
ingest: (m) => {
|
|
12605
|
+
const store = channel.getClient().messageStore;
|
|
12606
|
+
if (paginator.matchesFilter(m)) {
|
|
12607
|
+
paginator.ingestItem(m);
|
|
12608
|
+
} else if (store.has(m.id)) {
|
|
12609
|
+
store.upsert(m);
|
|
12610
|
+
}
|
|
12611
|
+
store.flushSubscribers(m.id);
|
|
12612
|
+
},
|
|
12613
|
+
// Mirrors `ingest`'s routing: a message this paginator does not hold can still be held by the
|
|
12614
|
+
// client-global store (a thread parent, a message displayed by another collection), and the
|
|
12615
|
+
// policy uses this both for its freshness comparison and to decide whether there is anything to
|
|
12616
|
+
// update optimistically at all. Reading only the paginator would make those two disagree.
|
|
12617
|
+
get: (id) => paginator.getItem(id) ?? channel.getClient().messageStore.get(id),
|
|
12618
|
+
remove: (id) => {
|
|
12619
|
+
const client = channel.getClient();
|
|
12620
|
+
const parentId = paginator.getItem(id)?.parent_id ?? client.messageStore.get(id)?.parent_id;
|
|
12621
|
+
channel.messagePaginator.removeItem({ id });
|
|
12622
|
+
channel.pinnedMessagesPaginator.removeItem({ id });
|
|
12623
|
+
if (parentId) {
|
|
12624
|
+
client.threads.threadsById[parentId]?.messagePaginator.removeItem({ id });
|
|
12625
|
+
}
|
|
12626
|
+
},
|
|
12627
|
+
...parentMessageId ? {
|
|
12628
|
+
normalizeOutgoingMessage: (m) => ({ ...m, parent_id: parentMessageId })
|
|
12629
|
+
} : {},
|
|
12630
|
+
handlers: () => {
|
|
12631
|
+
const { requestHandlers } = channel.configState.getLatestValue();
|
|
12632
|
+
const deleteMessageRequest = requestHandlers?.deleteMessageRequest;
|
|
12633
|
+
const sendMessageRequest = requestHandlers?.sendMessageRequest;
|
|
12634
|
+
const retrySendMessageRequest = requestHandlers?.retrySendMessageRequest;
|
|
12635
|
+
const updateMessageRequest = requestHandlers?.updateMessageRequest;
|
|
12636
|
+
return {
|
|
12637
|
+
delete: deleteMessageRequest ? (p) => deleteMessageRequest({
|
|
12638
|
+
localMessage: p.localMessage,
|
|
12639
|
+
options: p.options
|
|
12640
|
+
}) : void 0,
|
|
12641
|
+
send: sendMessageRequest ? (p) => sendMessageRequest({
|
|
12642
|
+
localMessage: p.localMessage,
|
|
12643
|
+
message: p.message,
|
|
12644
|
+
options: p.options
|
|
12645
|
+
}) : void 0,
|
|
12646
|
+
retry: retrySendMessageRequest ? (p) => retrySendMessageRequest({
|
|
12647
|
+
localMessage: p.localMessage,
|
|
12648
|
+
message: p.message,
|
|
12649
|
+
options: p.options
|
|
12650
|
+
}) : void 0,
|
|
12651
|
+
update: updateMessageRequest ? (p) => updateMessageRequest({
|
|
12652
|
+
localMessage: p.localMessage,
|
|
12653
|
+
options: p.options
|
|
12654
|
+
}) : void 0
|
|
12655
|
+
};
|
|
12656
|
+
},
|
|
12657
|
+
defaults: {
|
|
12658
|
+
delete: async (id, o) => {
|
|
12659
|
+
const result = await channel.getClient().deleteMessage({ id, ...o });
|
|
12660
|
+
return { message: result.message };
|
|
12661
|
+
},
|
|
12662
|
+
send: async (m, o) => {
|
|
12663
|
+
const result = await channel.sendMessage({ message: m, ...o });
|
|
12664
|
+
return { message: result.message };
|
|
12665
|
+
},
|
|
12666
|
+
update: async (m, o) => {
|
|
12667
|
+
const result = await channel.getClient().updateMessage({
|
|
12668
|
+
id: m.id,
|
|
12669
|
+
message: localMessageToNewMessagePayload(m),
|
|
12670
|
+
...o
|
|
12671
|
+
});
|
|
12672
|
+
return { message: result.message };
|
|
12673
|
+
}
|
|
12674
|
+
}
|
|
12675
|
+
});
|
|
12348
12676
|
};
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12677
|
+
|
|
12678
|
+
// src/messageOperations/WithMessageOperations.ts
|
|
12679
|
+
var WithMessageOperations = (Base) => {
|
|
12680
|
+
class WithMessageOperationsMixin extends Base {
|
|
12681
|
+
/** Sends a message with optimistic local state update. */
|
|
12682
|
+
async sendMessageWithLocalUpdate(params) {
|
|
12683
|
+
await this.messageOperations.send(params);
|
|
12684
|
+
}
|
|
12685
|
+
/** Retry sending a failed message. */
|
|
12686
|
+
async retrySendMessageWithLocalUpdate(params) {
|
|
12687
|
+
await this.messageOperations.retrySendWithLocalUpdate(params);
|
|
12688
|
+
}
|
|
12689
|
+
/** Updates a message with optimistic local state update. */
|
|
12690
|
+
async updateMessageWithLocalUpdate(params) {
|
|
12691
|
+
await this.messageOperations.update(params);
|
|
12692
|
+
}
|
|
12693
|
+
/** Deletes a message with local state update. */
|
|
12694
|
+
async deleteMessageWithLocalUpdate(params) {
|
|
12695
|
+
await this.messageOperations.delete(params);
|
|
12696
|
+
}
|
|
12697
|
+
/** Adds a reaction with an optimistic local state update. */
|
|
12698
|
+
async addReactionWithLocalUpdate(params) {
|
|
12699
|
+
await this.messageOperations.addReactionWithLocalUpdate(params);
|
|
12700
|
+
}
|
|
12701
|
+
/** Removes the current user's reaction with an optimistic local state update. */
|
|
12702
|
+
async deleteReactionWithLocalUpdate(params) {
|
|
12703
|
+
await this.messageOperations.deleteReactionWithLocalUpdate(params);
|
|
12364
12704
|
}
|
|
12365
|
-
});
|
|
12366
|
-
try {
|
|
12367
|
-
const response = await channel.sendReaction({ id: messageId, reaction, ...options });
|
|
12368
|
-
reconcileHeldMessage(channel, response?.message);
|
|
12369
|
-
} catch (error) {
|
|
12370
|
-
if (!await isQueuedForReplay(client, messageId, ["send-reaction"])) undo?.();
|
|
12371
|
-
throw error;
|
|
12372
12705
|
}
|
|
12706
|
+
return WithMessageOperationsMixin;
|
|
12373
12707
|
};
|
|
12374
|
-
|
|
12375
|
-
|
|
12376
|
-
|
|
12377
|
-
|
|
12708
|
+
|
|
12709
|
+
// src/messageOperations/reflectReactionEvent.ts
|
|
12710
|
+
var reflectReactionEvent = (client, {
|
|
12711
|
+
enforceUnique = false,
|
|
12712
|
+
message,
|
|
12713
|
+
reaction,
|
|
12714
|
+
removed = false
|
|
12378
12715
|
}) => {
|
|
12379
|
-
const
|
|
12380
|
-
|
|
12381
|
-
|
|
12382
|
-
|
|
12383
|
-
|
|
12716
|
+
const store = client.messageStore;
|
|
12717
|
+
if (!store.has(message.id)) return;
|
|
12718
|
+
const formatted = formatMessage(message);
|
|
12719
|
+
const existing = store.get(message.id);
|
|
12720
|
+
store.upsert({
|
|
12721
|
+
...formatted,
|
|
12722
|
+
own_reactions: computeOwnReactions({
|
|
12723
|
+
current: existing?.own_reactions ?? formatted.own_reactions ?? [],
|
|
12724
|
+
enforceUnique,
|
|
12725
|
+
reaction,
|
|
12726
|
+
removed,
|
|
12727
|
+
userId: client.userId
|
|
12728
|
+
})
|
|
12384
12729
|
});
|
|
12385
|
-
try {
|
|
12386
|
-
const response = await channel.deleteReaction({ id: messageId, type });
|
|
12387
|
-
reconcileHeldMessage(channel, response?.message);
|
|
12388
|
-
} catch (error) {
|
|
12389
|
-
if (!await isQueuedForReplay(client, messageId, ["delete-reaction"])) undo?.();
|
|
12390
|
-
throw error;
|
|
12391
|
-
}
|
|
12392
12730
|
};
|
|
12393
12731
|
|
|
12394
|
-
// src/messageOperations/persistence.ts
|
|
12395
|
-
var createMessageOperationsPersistence = ({
|
|
12396
|
-
channel
|
|
12397
|
-
}) => ({
|
|
12398
|
-
persist: (message) => {
|
|
12399
|
-
channel.getClient().offlineDb?.executeQuerySafely(
|
|
12400
|
-
(db) => db.upsertMessageWithChannelGuard({ message: { ...message, cid: channel.cid } }),
|
|
12401
|
-
{ method: "messageOperations:persist" }
|
|
12402
|
-
);
|
|
12403
|
-
},
|
|
12404
|
-
purge: (id) => {
|
|
12405
|
-
channel.getClient().offlineDb?.executeQuerySafely((db) => db.hardDeleteMessage({ id }), {
|
|
12406
|
-
method: "messageOperations:purge"
|
|
12407
|
-
});
|
|
12408
|
-
},
|
|
12409
|
-
isQueued: (messageId, types) => isQueuedForReplay(channel.getClient(), messageId, types)
|
|
12410
|
-
});
|
|
12411
|
-
|
|
12412
12732
|
// src/configuration/utils/declarativeSlices.ts
|
|
12413
12733
|
var mergeDeclarativeSlice = (general, specific) => {
|
|
12414
12734
|
if (!general) return specific;
|
|
@@ -12434,7 +12754,7 @@ var DEFAULT_PAGE_LIMIT = 50;
|
|
|
12434
12754
|
var DEFAULT_SORT2 = [{ field: "created_at", direction: -1 }];
|
|
12435
12755
|
var DEFAULT_ITEM_ORDER = [{ field: "created_at", direction: 1 }];
|
|
12436
12756
|
var DEFAULT_THREAD_CONFIG = deepFreezeConfig({});
|
|
12437
|
-
var Thread = class extends WithSubscriptions {
|
|
12757
|
+
var Thread = class extends WithMessageOperations(WithSubscriptions) {
|
|
12438
12758
|
constructor({
|
|
12439
12759
|
client,
|
|
12440
12760
|
threadData,
|
|
@@ -12786,24 +13106,7 @@ var Thread = class extends WithSubscriptions {
|
|
|
12786
13106
|
const unsubscribeReactions = reactionTypes.map(
|
|
12787
13107
|
(eventType) => this.client.on(eventType, (event) => {
|
|
12788
13108
|
if (!event.message || !event.reaction) return;
|
|
12789
|
-
const { message
|
|
12790
|
-
if (message.parent_id === this.id) {
|
|
12791
|
-
this.messagePaginator.reflectReaction({
|
|
12792
|
-
enforceUnique: eventType === "reaction.updated",
|
|
12793
|
-
message,
|
|
12794
|
-
reaction,
|
|
12795
|
-
removed: eventType === "reaction.deleted"
|
|
12796
|
-
});
|
|
12797
|
-
} else if (!message.parent_id && message.id === this.id) {
|
|
12798
|
-
const own_reactions = computeOwnReactions({
|
|
12799
|
-
current: this.state.getLatestValue().parentMessage?.own_reactions ?? message.own_reactions ?? [],
|
|
12800
|
-
enforceUnique: eventType === "reaction.updated",
|
|
12801
|
-
reaction,
|
|
12802
|
-
removed: eventType === "reaction.deleted",
|
|
12803
|
-
userId: this.client.userId
|
|
12804
|
-
});
|
|
12805
|
-
this.updateParentMessageLocally({ message: { ...message, own_reactions } });
|
|
12806
|
-
}
|
|
13109
|
+
const { message } = event;
|
|
12807
13110
|
this.messagePaginator.reflectQuotedMessageUpdate(formatMessage(message));
|
|
12808
13111
|
}).unsubscribe
|
|
12809
13112
|
);
|
|
@@ -12869,6 +13172,7 @@ var Thread = class extends WithSubscriptions {
|
|
|
12869
13172
|
} else if (this.failedRepliesMap.has(message.id)) {
|
|
12870
13173
|
this.failedRepliesMap.delete(message.id);
|
|
12871
13174
|
}
|
|
13175
|
+
if (this.messagePaginator.getItem(formattedMessage.id) === formattedMessage) return;
|
|
12872
13176
|
this.messagePaginator.ingestItem(formattedMessage);
|
|
12873
13177
|
};
|
|
12874
13178
|
// todo: can be removed with the next breaking change and use MessagePaginator only
|
|
@@ -13008,77 +13312,7 @@ var Thread = class extends WithSubscriptions {
|
|
|
13008
13312
|
composition: threadData?.draft ?? draft,
|
|
13009
13313
|
compositionContext: this
|
|
13010
13314
|
});
|
|
13011
|
-
this.messageOperations =
|
|
13012
|
-
...createMessageOperationsPersistence({ channel: this.channel }),
|
|
13013
|
-
ingest: (m) => {
|
|
13014
|
-
const store = this.channel.getClient().messageStore;
|
|
13015
|
-
if (this.messagePaginator.matchesFilter(m)) {
|
|
13016
|
-
this.messagePaginator.ingestItem(m);
|
|
13017
|
-
} else if (store.has(m.id)) {
|
|
13018
|
-
store.upsert(m);
|
|
13019
|
-
}
|
|
13020
|
-
store.flushSubscribers(m.id);
|
|
13021
|
-
},
|
|
13022
|
-
// Mirrors `ingest`'s routing: a message this paginator does not hold can still be held by the
|
|
13023
|
-
// client-global store (a thread parent, a message displayed by another collection), and the
|
|
13024
|
-
// policy uses this both for its freshness comparison and to decide whether there is anything to
|
|
13025
|
-
// update optimistically at all. Reading only the paginator would make those two disagree.
|
|
13026
|
-
get: (id) => this.messagePaginator.getItem(id) ?? this.channel.getClient().messageStore.get(id),
|
|
13027
|
-
remove: (id) => {
|
|
13028
|
-
this.messagePaginator.removeItem({ id });
|
|
13029
|
-
this.channel.messagePaginator.removeItem({ id });
|
|
13030
|
-
this.channel.pinnedMessagesPaginator.removeItem({ id });
|
|
13031
|
-
},
|
|
13032
|
-
normalizeOutgoingMessage: (m) => ({
|
|
13033
|
-
...m,
|
|
13034
|
-
parent_id: this.id
|
|
13035
|
-
}),
|
|
13036
|
-
handlers: () => {
|
|
13037
|
-
const { requestHandlers } = this.channel.configState.getLatestValue();
|
|
13038
|
-
const deleteMessageRequest = requestHandlers?.deleteMessageRequest;
|
|
13039
|
-
const sendMessageRequest = requestHandlers?.sendMessageRequest;
|
|
13040
|
-
const retrySendMessageRequest = requestHandlers?.retrySendMessageRequest;
|
|
13041
|
-
const updateMessageRequest = requestHandlers?.updateMessageRequest;
|
|
13042
|
-
return {
|
|
13043
|
-
delete: deleteMessageRequest ? (p) => deleteMessageRequest({
|
|
13044
|
-
localMessage: p.localMessage,
|
|
13045
|
-
options: p.options
|
|
13046
|
-
}) : void 0,
|
|
13047
|
-
send: sendMessageRequest ? (p) => sendMessageRequest({
|
|
13048
|
-
localMessage: p.localMessage,
|
|
13049
|
-
message: p.message,
|
|
13050
|
-
options: p.options
|
|
13051
|
-
}) : void 0,
|
|
13052
|
-
retry: retrySendMessageRequest ? (p) => retrySendMessageRequest({
|
|
13053
|
-
localMessage: p.localMessage,
|
|
13054
|
-
message: p.message,
|
|
13055
|
-
options: p.options
|
|
13056
|
-
}) : void 0,
|
|
13057
|
-
update: updateMessageRequest ? (p) => updateMessageRequest({
|
|
13058
|
-
localMessage: p.localMessage,
|
|
13059
|
-
options: p.options
|
|
13060
|
-
}) : void 0
|
|
13061
|
-
};
|
|
13062
|
-
},
|
|
13063
|
-
defaults: {
|
|
13064
|
-
delete: async (id, o) => {
|
|
13065
|
-
const result = await this.channel.getClient().deleteMessage({ id, ...o });
|
|
13066
|
-
return { message: result.message };
|
|
13067
|
-
},
|
|
13068
|
-
send: async (m, o) => {
|
|
13069
|
-
const result = await this.channel.sendMessage({ message: m, ...o });
|
|
13070
|
-
return { message: result.message };
|
|
13071
|
-
},
|
|
13072
|
-
update: async (m, o) => {
|
|
13073
|
-
const result = await this.channel.getClient().updateMessage({
|
|
13074
|
-
id: m.id,
|
|
13075
|
-
message: localMessageToNewMessagePayload(m),
|
|
13076
|
-
...o
|
|
13077
|
-
});
|
|
13078
|
-
return { message: result.message };
|
|
13079
|
-
}
|
|
13080
|
-
}
|
|
13081
|
-
});
|
|
13315
|
+
this.messageOperations = createMessageOperations(this);
|
|
13082
13316
|
this.initializeConfig(declarativeConfig);
|
|
13083
13317
|
}
|
|
13084
13318
|
/**
|
|
@@ -13133,90 +13367,6 @@ var Thread = class extends WithSubscriptions {
|
|
|
13133
13367
|
get ownUnreadCount() {
|
|
13134
13368
|
return ownUnreadCountSelector(this.client.userId)(this.state.getLatestValue());
|
|
13135
13369
|
}
|
|
13136
|
-
/**
|
|
13137
|
-
* Sends a message with optimistic local state update.
|
|
13138
|
-
*/
|
|
13139
|
-
async sendMessageWithLocalUpdate({
|
|
13140
|
-
localMessage,
|
|
13141
|
-
message,
|
|
13142
|
-
options,
|
|
13143
|
-
sendMessageRequestFn
|
|
13144
|
-
}) {
|
|
13145
|
-
await this.messageOperations.send(
|
|
13146
|
-
{
|
|
13147
|
-
localMessage,
|
|
13148
|
-
message,
|
|
13149
|
-
options
|
|
13150
|
-
},
|
|
13151
|
-
sendMessageRequestFn
|
|
13152
|
-
);
|
|
13153
|
-
}
|
|
13154
|
-
/**
|
|
13155
|
-
* Retry sending a failed message.
|
|
13156
|
-
*/
|
|
13157
|
-
async retrySendMessageWithLocalUpdate(params) {
|
|
13158
|
-
await this.messageOperations.retry(
|
|
13159
|
-
{
|
|
13160
|
-
localMessage: { ...params.localMessage, type: "regular" },
|
|
13161
|
-
options: params.options
|
|
13162
|
-
},
|
|
13163
|
-
params.sendMessageRequestFn
|
|
13164
|
-
);
|
|
13165
|
-
}
|
|
13166
|
-
/**
|
|
13167
|
-
* Updates a message with optimistic local state update.
|
|
13168
|
-
*
|
|
13169
|
-
* The update flows through `messagePaginator`, which is the sole reply source.
|
|
13170
|
-
*/
|
|
13171
|
-
async updateMessageWithLocalUpdate(params) {
|
|
13172
|
-
await this.messageOperations.update(
|
|
13173
|
-
{
|
|
13174
|
-
localMessage: params.localMessage,
|
|
13175
|
-
options: params.options
|
|
13176
|
-
},
|
|
13177
|
-
params.updateMessageRequestFn
|
|
13178
|
-
);
|
|
13179
|
-
}
|
|
13180
|
-
/**
|
|
13181
|
-
* Deletes a message with local state update.
|
|
13182
|
-
*/
|
|
13183
|
-
async deleteMessageWithLocalUpdate(params) {
|
|
13184
|
-
await this.messageOperations.delete(
|
|
13185
|
-
{
|
|
13186
|
-
localMessage: params.localMessage,
|
|
13187
|
-
options: params.options
|
|
13188
|
-
},
|
|
13189
|
-
params.deleteMessageRequestFn
|
|
13190
|
-
);
|
|
13191
|
-
}
|
|
13192
|
-
/**
|
|
13193
|
-
* Adds a reaction to a reply with an optimistic local state update — see
|
|
13194
|
-
* {@link addReactionOptimistically}, which `Channel` shares. The request routes through the parent
|
|
13195
|
-
* channel because reactions are channel-level, while the local write is addressed by message id and
|
|
13196
|
-
* so reaches a pure reply no channel collection holds.
|
|
13197
|
-
*/
|
|
13198
|
-
async addReactionWithLocalUpdate({
|
|
13199
|
-
messageId,
|
|
13200
|
-
reaction,
|
|
13201
|
-
options
|
|
13202
|
-
}) {
|
|
13203
|
-
await addReactionOptimistically({
|
|
13204
|
-
channel: this.channel,
|
|
13205
|
-
messageId,
|
|
13206
|
-
options,
|
|
13207
|
-
reaction
|
|
13208
|
-
});
|
|
13209
|
-
}
|
|
13210
|
-
/**
|
|
13211
|
-
* Removes the current user's reaction from a reply with an optimistic local state update — see
|
|
13212
|
-
* {@link deleteReactionOptimistically}, which `Channel` shares.
|
|
13213
|
-
*/
|
|
13214
|
-
async deleteReactionWithLocalUpdate({
|
|
13215
|
-
messageId,
|
|
13216
|
-
type
|
|
13217
|
-
}) {
|
|
13218
|
-
await deleteReactionOptimistically({ channel: this.channel, messageId, type });
|
|
13219
|
-
}
|
|
13220
13370
|
};
|
|
13221
13371
|
var normalizeThreadParticipants = (participants, channelCid) => {
|
|
13222
13372
|
if (!participants) return void 0;
|
|
@@ -13808,6 +13958,99 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
|
|
|
13808
13958
|
throw error;
|
|
13809
13959
|
}
|
|
13810
13960
|
};
|
|
13961
|
+
/**
|
|
13962
|
+
* Lets go of what the submitted message took with it.
|
|
13963
|
+
*
|
|
13964
|
+
* A poll message carries no text or attachments of its own, so the composer keeps whatever else
|
|
13965
|
+
* was drafted and releases only the poll. Everything else is a full clear.
|
|
13966
|
+
*/
|
|
13967
|
+
this.releaseSubmittedComposition = () => {
|
|
13968
|
+
if (this.retainsCompositionOnSubmit) {
|
|
13969
|
+
this.state.partialNext({
|
|
13970
|
+
id: _MessageComposer.generateId(),
|
|
13971
|
+
// Unconditional, and safe whatever decided to retain: a poll that exists is always a poll
|
|
13972
|
+
// that just went out, because `createMessageComposerStateCompositionMiddleware` copies a
|
|
13973
|
+
// set `pollId` onto every composition - threads and edits included, where the poll-only
|
|
13974
|
+
// middleware forwards. There is no state where this detaches one the submission did not
|
|
13975
|
+
// carry.
|
|
13976
|
+
//
|
|
13977
|
+
// Detaching it is also not optional. While `pollId` is set, `hasSendableData` counts it as
|
|
13978
|
+
// content on its own and `createPollOnlyCompositionMiddleware` turns every composition
|
|
13979
|
+
// into a poll-only message, so a composer left bound to a spent poll nullifies whatever is
|
|
13980
|
+
// typed next and re-sends the same poll.
|
|
13981
|
+
pollId: null
|
|
13982
|
+
});
|
|
13983
|
+
return;
|
|
13984
|
+
}
|
|
13985
|
+
this.clear();
|
|
13986
|
+
};
|
|
13987
|
+
this.reportSubmitFailure = (type, message, error) => {
|
|
13988
|
+
this.client.notifications.addError({
|
|
13989
|
+
message,
|
|
13990
|
+
origin: {
|
|
13991
|
+
emitter: "MessageComposer",
|
|
13992
|
+
context: { composer: this }
|
|
13993
|
+
},
|
|
13994
|
+
options: {
|
|
13995
|
+
type,
|
|
13996
|
+
metadata: { reason: error.message },
|
|
13997
|
+
originalError: error instanceof Error ? error : void 0
|
|
13998
|
+
}
|
|
13999
|
+
});
|
|
14000
|
+
};
|
|
14001
|
+
/**
|
|
14002
|
+
* Composes the message and sends it, releasing the composer as it goes.
|
|
14003
|
+
*
|
|
14004
|
+
* Never rejects - see {@link MessageComposerSubmitResult} for what each outcome means and where
|
|
14005
|
+
* the content ends up. A failure is reported through `client.notifications`, and the message
|
|
14006
|
+
* stays in the list marked `failed`, which is where the user retries it. Callers rendering their
|
|
14007
|
+
* own post-send feedback have to check the result.
|
|
14008
|
+
*/
|
|
14009
|
+
this.send = async () => {
|
|
14010
|
+
const composition = await this.compose();
|
|
14011
|
+
if (!composition?.message) return "nothing-to-send";
|
|
14012
|
+
const { localMessage, message, sendOptions } = composition;
|
|
14013
|
+
this.releaseSubmittedComposition();
|
|
14014
|
+
try {
|
|
14015
|
+
await this.defaultSubmitTarget.sendMessageWithLocalUpdate({
|
|
14016
|
+
localMessage,
|
|
14017
|
+
message,
|
|
14018
|
+
options: sendOptions
|
|
14019
|
+
});
|
|
14020
|
+
return "sent";
|
|
14021
|
+
} catch (error) {
|
|
14022
|
+
this.reportSubmitFailure(
|
|
14023
|
+
CORE_NOTIFICATION_TYPE.messageSendFailed,
|
|
14024
|
+
"Send message request failed",
|
|
14025
|
+
error
|
|
14026
|
+
);
|
|
14027
|
+
return "failed";
|
|
14028
|
+
}
|
|
14029
|
+
};
|
|
14030
|
+
/**
|
|
14031
|
+
* Composes the edit and saves it, on the same terms as {@link send} - including releasing the
|
|
14032
|
+
* composer before the request rather than after it.
|
|
14033
|
+
*/
|
|
14034
|
+
this.update = async () => {
|
|
14035
|
+
const composition = await this.compose();
|
|
14036
|
+
if (!composition?.message) return "nothing-to-send";
|
|
14037
|
+
const { localMessage, sendOptions } = composition;
|
|
14038
|
+
this.releaseSubmittedComposition();
|
|
14039
|
+
try {
|
|
14040
|
+
await this.defaultSubmitTarget.updateMessageWithLocalUpdate({
|
|
14041
|
+
localMessage,
|
|
14042
|
+
options: sendOptions
|
|
14043
|
+
});
|
|
14044
|
+
return "sent";
|
|
14045
|
+
} catch (error) {
|
|
14046
|
+
this.reportSubmitFailure(
|
|
14047
|
+
CORE_NOTIFICATION_TYPE.messageUpdateFailed,
|
|
14048
|
+
"Edit message request failed",
|
|
14049
|
+
error
|
|
14050
|
+
);
|
|
14051
|
+
return "failed";
|
|
14052
|
+
}
|
|
14053
|
+
};
|
|
13811
14054
|
this.sendLocation = async () => {
|
|
13812
14055
|
const location = this.locationComposer.validLocation;
|
|
13813
14056
|
if (this.threadId || !location) return;
|
|
@@ -13900,9 +14143,6 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
|
|
|
13900
14143
|
if (compositionContext instanceof Thread) {
|
|
13901
14144
|
return "thread";
|
|
13902
14145
|
}
|
|
13903
|
-
if (typeof compositionContext.legacyThreadId === "string") {
|
|
13904
|
-
return "legacy_thread";
|
|
13905
|
-
}
|
|
13906
14146
|
return "message";
|
|
13907
14147
|
}
|
|
13908
14148
|
static constructTag(compositionContext) {
|
|
@@ -13938,9 +14178,6 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
|
|
|
13938
14178
|
if (this.compositionContext instanceof Thread) {
|
|
13939
14179
|
return this.compositionContext.id;
|
|
13940
14180
|
}
|
|
13941
|
-
if (typeof this.compositionContext.legacyThreadId === "string") {
|
|
13942
|
-
return this.compositionContext.legacyThreadId;
|
|
13943
|
-
}
|
|
13944
14181
|
if (typeof this.compositionContext.parent_id === "string") {
|
|
13945
14182
|
return this.compositionContext.parent_id;
|
|
13946
14183
|
}
|
|
@@ -13972,27 +14209,41 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
|
|
|
13972
14209
|
return !currentCommand || this.validateCommandSendability(currentCommand).ready;
|
|
13973
14210
|
}
|
|
13974
14211
|
/**
|
|
13975
|
-
* Whether
|
|
13976
|
-
*
|
|
14212
|
+
* Whether a message may be sent while its attachments are still uploading — see
|
|
14213
|
+
* {@link AttachmentManagerConfig.pendingUploadsEnabled}.
|
|
14214
|
+
*/
|
|
14215
|
+
get allowsPendingUploads() {
|
|
14216
|
+
return this.attachmentManager.config.pendingUploadsEnabled;
|
|
14217
|
+
}
|
|
14218
|
+
/**
|
|
14219
|
+
* Where a composed message goes: the thread when this composer belongs to one, the channel
|
|
14220
|
+
* otherwise.
|
|
13977
14221
|
*
|
|
13978
|
-
*
|
|
13979
|
-
*
|
|
13980
|
-
*
|
|
14222
|
+
* Thread replies live in `thread.messagePaginator`, which is independent of the channel's, so
|
|
14223
|
+
* dispatching to the wrong one leaves the reply invisible where it was written. An edit composer
|
|
14224
|
+
* is built from the message rather than from its thread, so it can only find the thread when the
|
|
14225
|
+
* manager has it loaded. No composer reachable from a UI SDK is built that way today.
|
|
14226
|
+
*/
|
|
14227
|
+
get defaultSubmitTarget() {
|
|
14228
|
+
if (this.compositionContext instanceof Thread) return this.compositionContext;
|
|
14229
|
+
const { threadId } = this;
|
|
14230
|
+
return threadId && this.client.threads.threadsById[threadId] || this.channel;
|
|
14231
|
+
}
|
|
14232
|
+
/**
|
|
14233
|
+
* Whether submitting keeps the composer's contents rather than clearing them - see
|
|
14234
|
+
* {@link MessageComposerConfig.retainCompositionOnSubmit}.
|
|
13981
14235
|
*
|
|
13982
|
-
*
|
|
13983
|
-
*
|
|
13984
|
-
* together with the middleware declaration it reads, is replaced by that config field.
|
|
14236
|
+
* Read by the attachments composition step too: content the composer keeps must not also ride
|
|
14237
|
+
* along on the submitted message, or one `localMetadata.id` ends up owned in two places.
|
|
13985
14238
|
*/
|
|
13986
|
-
get
|
|
13987
|
-
return this.
|
|
13988
|
-
(middleware) => middleware.allowsPendingUploads
|
|
13989
|
-
);
|
|
14239
|
+
get retainsCompositionOnSubmit() {
|
|
14240
|
+
return this.config.retainCompositionOnSubmit(this);
|
|
13990
14241
|
}
|
|
13991
14242
|
get hasSendableData() {
|
|
13992
14243
|
if (!this.isCommandSendable) return false;
|
|
13993
14244
|
if (this.allowsPendingUploads) {
|
|
13994
14245
|
const hasSendableAttachment = this.attachmentManager.attachments.some(
|
|
13995
|
-
(attachment) => isFinishedUpload(attachment) || isPendingUpload(attachment)
|
|
14246
|
+
(attachment) => isFinishedUpload(attachment) || isPendingUpload(attachment) || isFailedUpload(attachment)
|
|
13996
14247
|
);
|
|
13997
14248
|
return !!(!this.textComposer.textIsEmpty || hasSendableAttachment || this.pollId || this.locationComposer.validLocation);
|
|
13998
14249
|
}
|
|
@@ -14420,7 +14671,10 @@ var MessageDeliveryReporter = class _MessageDeliveryReporter {
|
|
|
14420
14671
|
|
|
14421
14672
|
// src/messageDelivery/MessageReceiptsTracker.ts
|
|
14422
14673
|
var import_state_store20 = require("@stream-io/state-store");
|
|
14423
|
-
var MIN_REF = {
|
|
14674
|
+
var MIN_REF = {
|
|
14675
|
+
timestamp: asTimestampNS(Number.NEGATIVE_INFINITY),
|
|
14676
|
+
msgId: ""
|
|
14677
|
+
};
|
|
14424
14678
|
var isValidReadState = (readState) => !!readState?.user && Number.isFinite(readState.last_read);
|
|
14425
14679
|
var compareRefsAsc = (a, b) => a.timestamp !== b.timestamp ? a.timestamp - b.timestamp : 0;
|
|
14426
14680
|
var findIndex = (arr, target, keyOf) => {
|
|
@@ -14462,7 +14716,9 @@ var MessageReceiptsTracker = class extends WithSubscriptions {
|
|
|
14462
14716
|
this.snapshotStore = new import_state_store20.StateStore({
|
|
14463
14717
|
revision: 0,
|
|
14464
14718
|
readersByMessageId: {},
|
|
14465
|
-
deliveredByMessageId: {}
|
|
14719
|
+
deliveredByMessageId: {},
|
|
14720
|
+
lastDeliveredRefByOthers: null,
|
|
14721
|
+
lastReadRefByOthers: null
|
|
14466
14722
|
});
|
|
14467
14723
|
this.registerSubscriptions = () => {
|
|
14468
14724
|
this.incrementRefCount();
|
|
@@ -14658,6 +14914,21 @@ var MessageReceiptsTracker = class extends WithSubscriptions {
|
|
|
14658
14914
|
);
|
|
14659
14915
|
return this.deliveredSorted.slice(pos).map((x) => x.user);
|
|
14660
14916
|
}
|
|
14917
|
+
/**
|
|
14918
|
+
* Whether at least one other member has this message delivered.
|
|
14919
|
+
*
|
|
14920
|
+
* Reads the single furthest cursor rather than asking who is past this message, so the answer is
|
|
14921
|
+
* monotonic by construction: an older message can never report less than a newer one.
|
|
14922
|
+
*/
|
|
14923
|
+
isDeliveredToOthers(msgRef) {
|
|
14924
|
+
const ref = this.snapshotStore.getLatestValue().lastDeliveredRefByOthers;
|
|
14925
|
+
return !!ref && compareRefsAsc(msgRef, ref) <= 0;
|
|
14926
|
+
}
|
|
14927
|
+
/** As {@link isDeliveredToOthers}, for reads. */
|
|
14928
|
+
isReadByOthers(msgRef) {
|
|
14929
|
+
const ref = this.snapshotStore.getLatestValue().lastReadRefByOthers;
|
|
14930
|
+
return !!ref && compareRefsAsc(msgRef, ref) <= 0;
|
|
14931
|
+
}
|
|
14661
14932
|
/** Users who delivered but have NOT read. */
|
|
14662
14933
|
deliveredNotReadForMessage(msgRef) {
|
|
14663
14934
|
const pos = findIndex(
|
|
@@ -14833,6 +15104,19 @@ var MessageReceiptsTracker = class extends WithSubscriptions {
|
|
|
14833
15104
|
[]
|
|
14834
15105
|
);
|
|
14835
15106
|
}
|
|
15107
|
+
/**
|
|
15108
|
+
* The furthest ref in an ascending-by-cursor array, ignoring this user's own progress - your own
|
|
15109
|
+
* cursor says nothing about whether anyone received the message.
|
|
15110
|
+
*/
|
|
15111
|
+
furthestRefByOthers(sorted, refOf) {
|
|
15112
|
+
const ownUserId = this.channel.getClient().userID;
|
|
15113
|
+
for (let i = sorted.length - 1; i >= 0; i -= 1) {
|
|
15114
|
+
if (sorted[i].user.id === ownUserId) continue;
|
|
15115
|
+
const ref = refOf(sorted[i]);
|
|
15116
|
+
return Number.isFinite(ref.timestamp) ? ref : null;
|
|
15117
|
+
}
|
|
15118
|
+
return null;
|
|
15119
|
+
}
|
|
14836
15120
|
emitSnapshot() {
|
|
14837
15121
|
const readersByMessageId = this.groupUsersByLastReadMessage();
|
|
14838
15122
|
const deliveredByMessageId = this.groupUsersByLastDeliveredMessage();
|
|
@@ -14840,7 +15124,15 @@ var MessageReceiptsTracker = class extends WithSubscriptions {
|
|
|
14840
15124
|
this.snapshotStore.next({
|
|
14841
15125
|
revision: currentSnapshot.revision + 1,
|
|
14842
15126
|
readersByMessageId,
|
|
14843
|
-
deliveredByMessageId
|
|
15127
|
+
deliveredByMessageId,
|
|
15128
|
+
lastDeliveredRefByOthers: this.furthestRefByOthers(
|
|
15129
|
+
this.deliveredSorted,
|
|
15130
|
+
({ lastDeliveredRef }) => lastDeliveredRef
|
|
15131
|
+
),
|
|
15132
|
+
lastReadRefByOthers: this.furthestRefByOthers(
|
|
15133
|
+
this.readSorted,
|
|
15134
|
+
({ lastReadRef }) => lastReadRef
|
|
15135
|
+
)
|
|
14844
15136
|
});
|
|
14845
15137
|
}
|
|
14846
15138
|
};
|
|
@@ -15164,7 +15456,7 @@ var DEFAULT_CHANNEL_CONFIG = deepFreezeConfig({
|
|
|
15164
15456
|
typingEvents: { enabled: true },
|
|
15165
15457
|
userMessageReminders: { enabled: true }
|
|
15166
15458
|
});
|
|
15167
|
-
var Channel = class extends ChannelApi {
|
|
15459
|
+
var Channel = class extends WithMessageOperations(ChannelApi) {
|
|
15168
15460
|
/**
|
|
15169
15461
|
* Creates a `Channel` instance bound to the given chat client.
|
|
15170
15462
|
*
|
|
@@ -15277,78 +15569,7 @@ var Channel = class extends ChannelApi {
|
|
|
15277
15569
|
this.messageReceiptsTracker.registerSubscriptions();
|
|
15278
15570
|
this.cooldownTimer = new CooldownTimer({ channel: this });
|
|
15279
15571
|
this.cooldownTimer.registerSubscriptions();
|
|
15280
|
-
this.messageOperations =
|
|
15281
|
-
...createMessageOperationsPersistence({ channel: this }),
|
|
15282
|
-
ingest: (m) => {
|
|
15283
|
-
const store = this.getClient().messageStore;
|
|
15284
|
-
if (this.messagePaginator.matchesFilter(m)) {
|
|
15285
|
-
this.messagePaginator.ingestItem(m);
|
|
15286
|
-
} else if (store.has(m.id)) {
|
|
15287
|
-
store.upsert(m);
|
|
15288
|
-
}
|
|
15289
|
-
store.flushSubscribers(m.id);
|
|
15290
|
-
},
|
|
15291
|
-
// Mirrors `ingest`'s routing: a message this paginator does not hold can still be held by the
|
|
15292
|
-
// client-global store (a thread parent, a message displayed by another collection), and the
|
|
15293
|
-
// policy uses this both for its freshness comparison and to decide whether there is anything to
|
|
15294
|
-
// update optimistically at all. Reading only the paginator would make those two disagree.
|
|
15295
|
-
get: (id2) => this.messagePaginator.getItem(id2) ?? this.getClient().messageStore.get(id2),
|
|
15296
|
-
remove: (id2) => {
|
|
15297
|
-
const parentId = this.messagePaginator.getItem(id2)?.parent_id ?? this.getClient().messageStore.get(id2)?.parent_id;
|
|
15298
|
-
this.messagePaginator.removeItem({ id: id2 });
|
|
15299
|
-
this.pinnedMessagesPaginator.removeItem({ id: id2 });
|
|
15300
|
-
if (parentId) {
|
|
15301
|
-
this.getClient().threads.threadsById[parentId]?.messagePaginator.removeItem({
|
|
15302
|
-
id: id2
|
|
15303
|
-
});
|
|
15304
|
-
}
|
|
15305
|
-
},
|
|
15306
|
-
handlers: () => {
|
|
15307
|
-
const { requestHandlers } = this.configState.getLatestValue();
|
|
15308
|
-
const deleteMessageRequest = requestHandlers?.deleteMessageRequest;
|
|
15309
|
-
const sendMessageRequest = requestHandlers?.sendMessageRequest;
|
|
15310
|
-
const retrySendMessageRequest = requestHandlers?.retrySendMessageRequest;
|
|
15311
|
-
const updateMessageRequest = requestHandlers?.updateMessageRequest;
|
|
15312
|
-
return {
|
|
15313
|
-
delete: deleteMessageRequest ? (p) => deleteMessageRequest({
|
|
15314
|
-
localMessage: p.localMessage,
|
|
15315
|
-
options: p.options
|
|
15316
|
-
}) : void 0,
|
|
15317
|
-
send: sendMessageRequest ? (p) => sendMessageRequest({
|
|
15318
|
-
localMessage: p.localMessage,
|
|
15319
|
-
message: p.message,
|
|
15320
|
-
options: p.options
|
|
15321
|
-
}) : void 0,
|
|
15322
|
-
retry: retrySendMessageRequest ? (p) => retrySendMessageRequest({
|
|
15323
|
-
localMessage: p.localMessage,
|
|
15324
|
-
message: p.message,
|
|
15325
|
-
options: p.options
|
|
15326
|
-
}) : void 0,
|
|
15327
|
-
update: updateMessageRequest ? (p) => updateMessageRequest({
|
|
15328
|
-
localMessage: p.localMessage,
|
|
15329
|
-
options: p.options
|
|
15330
|
-
}) : void 0
|
|
15331
|
-
};
|
|
15332
|
-
},
|
|
15333
|
-
defaults: {
|
|
15334
|
-
delete: async (id2, o) => {
|
|
15335
|
-
const result = await this.getClient().deleteMessage({ id: id2, ...o });
|
|
15336
|
-
return { message: result.message };
|
|
15337
|
-
},
|
|
15338
|
-
send: async (m, o) => {
|
|
15339
|
-
const result = await this.sendMessage({ message: m, ...o });
|
|
15340
|
-
return { message: result.message };
|
|
15341
|
-
},
|
|
15342
|
-
update: async (m, o) => {
|
|
15343
|
-
const result = await this.getClient().updateMessage({
|
|
15344
|
-
id: m.id,
|
|
15345
|
-
message: localMessageToNewMessagePayload(m),
|
|
15346
|
-
...o
|
|
15347
|
-
});
|
|
15348
|
-
return { message: result.message };
|
|
15349
|
-
}
|
|
15350
|
-
}
|
|
15351
|
-
});
|
|
15572
|
+
this.messageOperations = createMessageOperations(this);
|
|
15352
15573
|
this._syncMuteStatus();
|
|
15353
15574
|
this.configController = new ConfigController({
|
|
15354
15575
|
defaults: DEFAULT_CHANNEL_CONFIG,
|
|
@@ -15529,74 +15750,16 @@ var Channel = class extends ChannelApi {
|
|
|
15529
15750
|
});
|
|
15530
15751
|
}
|
|
15531
15752
|
/**
|
|
15532
|
-
* Sends a message with optimistic local state update.
|
|
15753
|
+
* Sends a message with optimistic local state update, stopping the typing indicator first.
|
|
15754
|
+
*
|
|
15755
|
+
* The only collection-specific part of the optimistic API: typing is a channel concern and a
|
|
15756
|
+
* `Thread` has no counterpart, so it is an override rather than part of the mixin.
|
|
15533
15757
|
*/
|
|
15534
15758
|
async sendMessageWithLocalUpdate(params) {
|
|
15535
|
-
|
|
15536
|
-
|
|
15537
|
-
|
|
15538
|
-
|
|
15539
|
-
options: params.options
|
|
15540
|
-
},
|
|
15541
|
-
params.sendMessageRequestFn
|
|
15542
|
-
);
|
|
15543
|
-
if (this.messageComposer.config.text.publishTypingEvents) await this.stopTyping();
|
|
15544
|
-
}
|
|
15545
|
-
/**
|
|
15546
|
-
* Retry sending a failed message.
|
|
15547
|
-
*/
|
|
15548
|
-
async retrySendMessageWithLocalUpdate(params) {
|
|
15549
|
-
await this.messageOperations.retry(
|
|
15550
|
-
{
|
|
15551
|
-
localMessage: { ...params.localMessage, type: "regular" },
|
|
15552
|
-
options: params.options
|
|
15553
|
-
},
|
|
15554
|
-
params.sendMessageRequestFn
|
|
15555
|
-
);
|
|
15556
|
-
}
|
|
15557
|
-
/**
|
|
15558
|
-
* Updates a message with optimistic local state update.
|
|
15559
|
-
*/
|
|
15560
|
-
async updateMessageWithLocalUpdate(params) {
|
|
15561
|
-
await this.messageOperations.update(
|
|
15562
|
-
{
|
|
15563
|
-
localMessage: params.localMessage,
|
|
15564
|
-
options: params.options
|
|
15565
|
-
},
|
|
15566
|
-
params.updateMessageRequestFn
|
|
15567
|
-
);
|
|
15568
|
-
}
|
|
15569
|
-
/**
|
|
15570
|
-
* Deletes a message with local state update.
|
|
15571
|
-
*/
|
|
15572
|
-
async deleteMessageWithLocalUpdate(params) {
|
|
15573
|
-
await this.messageOperations.delete(
|
|
15574
|
-
{
|
|
15575
|
-
localMessage: params.localMessage,
|
|
15576
|
-
options: params.options
|
|
15577
|
-
},
|
|
15578
|
-
params.deleteMessageRequestFn
|
|
15579
|
-
);
|
|
15580
|
-
}
|
|
15581
|
-
/**
|
|
15582
|
-
* Adds a reaction with an optimistic local state update - see {@link addReactionOptimistically}.
|
|
15583
|
-
*/
|
|
15584
|
-
async addReactionWithLocalUpdate({
|
|
15585
|
-
messageId,
|
|
15586
|
-
reaction,
|
|
15587
|
-
options
|
|
15588
|
-
}) {
|
|
15589
|
-
await addReactionOptimistically({ channel: this, messageId, options, reaction });
|
|
15590
|
-
}
|
|
15591
|
-
/**
|
|
15592
|
-
* Removes the current user's reaction with an optimistic local state update - see
|
|
15593
|
-
* {@link deleteReactionOptimistically}.
|
|
15594
|
-
*/
|
|
15595
|
-
async deleteReactionWithLocalUpdate({
|
|
15596
|
-
messageId,
|
|
15597
|
-
type
|
|
15598
|
-
}) {
|
|
15599
|
-
await deleteReactionOptimistically({ channel: this, messageId, type });
|
|
15759
|
+
if (this.messageComposer.config.text.publishTypingEvents) {
|
|
15760
|
+
this.stopTyping().catch(() => void 0);
|
|
15761
|
+
}
|
|
15762
|
+
await super.sendMessageWithLocalUpdate(params);
|
|
15600
15763
|
}
|
|
15601
15764
|
/**
|
|
15602
15765
|
* Upload a file to this channel's file endpoint (multipart).
|
|
@@ -16042,7 +16205,7 @@ var Channel = class extends ChannelApi {
|
|
|
16042
16205
|
/**
|
|
16043
16206
|
* Returns the mute status for the current channel.
|
|
16044
16207
|
*
|
|
16045
|
-
* @returns An object of the form `{ muted: true | false, createdAt:
|
|
16208
|
+
* @returns An object of the form `{ muted: true | false, createdAt: TimestampNS | null, expiresAt: TimestampNS | null }`,
|
|
16046
16209
|
* where the timestamps are unix nanoseconds as the API sends them.
|
|
16047
16210
|
*/
|
|
16048
16211
|
muteStatus() {
|
|
@@ -16927,8 +17090,8 @@ var Channel = class extends ChannelApi {
|
|
|
16927
17090
|
this.messagePaginator.removeItem({ id: event.message.id });
|
|
16928
17091
|
this.pinnedMessagesPaginator.removeItem({ id: event.message.id });
|
|
16929
17092
|
} else {
|
|
16930
|
-
this.
|
|
16931
|
-
|
|
17093
|
+
const store = this.getClient().messageStore;
|
|
17094
|
+
if (store.has(event.message.id)) store.upsert(formattedMessage);
|
|
16932
17095
|
}
|
|
16933
17096
|
}
|
|
16934
17097
|
this.messagePaginator.reflectQuotedMessageUpdate(formattedMessage);
|
|
@@ -16957,8 +17120,9 @@ var Channel = class extends ChannelApi {
|
|
|
16957
17120
|
const ownMessage = event.user?.id === client.user?.id;
|
|
16958
17121
|
const isThreadMessage = event.message.parent_id && !event.message.show_in_channel;
|
|
16959
17122
|
if (!isThreadMessage) {
|
|
16960
|
-
|
|
16961
|
-
this.
|
|
17123
|
+
const formattedMessage = formatMessage(event.message);
|
|
17124
|
+
this.messagePaginator.ingestItem(formattedMessage);
|
|
17125
|
+
this.pinnedMessagesPaginator.ingestItem(formattedMessage);
|
|
16962
17126
|
}
|
|
16963
17127
|
const preventUnreadCountUpdate = ownMessage || isThreadMessage;
|
|
16964
17128
|
if (preventUnreadCountUpdate) break;
|
|
@@ -16994,7 +17158,7 @@ var Channel = class extends ChannelApi {
|
|
|
16994
17158
|
}
|
|
16995
17159
|
if (ownUserId && countsAsOwnUnread && !currentReadState[ownUserId]) {
|
|
16996
17160
|
nextReadState[ownUserId] = {
|
|
16997
|
-
last_read: 0,
|
|
17161
|
+
last_read: asTimestampNS(0),
|
|
16998
17162
|
unread_messages: 1,
|
|
16999
17163
|
user: client.user ?? { id: ownUserId }
|
|
17000
17164
|
};
|
|
@@ -17038,8 +17202,9 @@ var Channel = class extends ChannelApi {
|
|
|
17038
17202
|
this.pinnedMessagesPaginator.clearStateAndCache();
|
|
17039
17203
|
}
|
|
17040
17204
|
if (event.message) {
|
|
17041
|
-
|
|
17042
|
-
this.
|
|
17205
|
+
const formattedMessage = formatMessage(event.message);
|
|
17206
|
+
this.messagePaginator.ingestItem(formattedMessage);
|
|
17207
|
+
this.pinnedMessagesPaginator.ingestItem(formattedMessage);
|
|
17043
17208
|
}
|
|
17044
17209
|
break;
|
|
17045
17210
|
}
|
|
@@ -17120,49 +17285,16 @@ var Channel = class extends ChannelApi {
|
|
|
17120
17285
|
}
|
|
17121
17286
|
break;
|
|
17122
17287
|
case "reaction.new":
|
|
17123
|
-
if (event.message && event.reaction) {
|
|
17124
|
-
const { reaction } = event;
|
|
17125
|
-
if (!event.message?.parent_id || event.message.show_in_channel) {
|
|
17126
|
-
this.messagePaginator.reflectReaction({ message: event.message, reaction });
|
|
17127
|
-
this.pinnedMessagesPaginator.reflectReaction({
|
|
17128
|
-
message: event.message,
|
|
17129
|
-
reaction
|
|
17130
|
-
});
|
|
17131
|
-
}
|
|
17132
|
-
}
|
|
17133
|
-
break;
|
|
17134
|
-
case "reaction.deleted":
|
|
17135
|
-
if (event.message && event.reaction) {
|
|
17136
|
-
const { reaction } = event;
|
|
17137
|
-
if (event.message && (!event.message.parent_id || event.message.show_in_channel)) {
|
|
17138
|
-
this.messagePaginator.reflectReaction({
|
|
17139
|
-
message: event.message,
|
|
17140
|
-
reaction,
|
|
17141
|
-
removed: true
|
|
17142
|
-
});
|
|
17143
|
-
this.pinnedMessagesPaginator.reflectReaction({
|
|
17144
|
-
message: event.message,
|
|
17145
|
-
reaction,
|
|
17146
|
-
removed: true
|
|
17147
|
-
});
|
|
17148
|
-
}
|
|
17149
|
-
}
|
|
17150
|
-
break;
|
|
17151
17288
|
case "reaction.updated":
|
|
17289
|
+
case "reaction.deleted":
|
|
17152
17290
|
if (event.message && event.reaction) {
|
|
17153
|
-
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
|
|
17157
|
-
|
|
17158
|
-
|
|
17159
|
-
|
|
17160
|
-
this.pinnedMessagesPaginator.reflectReaction({
|
|
17161
|
-
enforceUnique: true,
|
|
17162
|
-
message: event.message,
|
|
17163
|
-
reaction
|
|
17164
|
-
});
|
|
17165
|
-
}
|
|
17291
|
+
reflectReactionEvent(this.getClient(), {
|
|
17292
|
+
// reaction.updated is only sent when enforce_unique is set
|
|
17293
|
+
enforceUnique: event.type === "reaction.updated",
|
|
17294
|
+
message: event.message,
|
|
17295
|
+
reaction: event.reaction,
|
|
17296
|
+
removed: event.type === "reaction.deleted"
|
|
17297
|
+
});
|
|
17166
17298
|
}
|
|
17167
17299
|
break;
|
|
17168
17300
|
case "channel.hidden": {
|
|
@@ -17642,8 +17774,9 @@ var ApiClient = class {
|
|
|
17642
17774
|
if (limit) rateLimit.rate_limit = parseInt(limit, 10);
|
|
17643
17775
|
const remaining = headers["x-ratelimit-remaining"];
|
|
17644
17776
|
if (remaining) rateLimit.rate_limit_remaining = parseInt(remaining, 10);
|
|
17645
|
-
const reset = headers["x-ratelimit-reset"];
|
|
17646
|
-
if (reset)
|
|
17777
|
+
const reset = Number(headers["x-ratelimit-reset"]);
|
|
17778
|
+
if (Number.isFinite(reset) && reset > 0)
|
|
17779
|
+
rateLimit.rate_limit_reset = new Date(reset * 1e3);
|
|
17647
17780
|
return {
|
|
17648
17781
|
response_headers: headers,
|
|
17649
17782
|
rate_limit: rateLimit,
|
|
@@ -17844,6 +17977,7 @@ var ModerationApi = class {
|
|
|
17844
17977
|
appeal_reason: request?.appeal_reason,
|
|
17845
17978
|
entity_id: request?.entity_id,
|
|
17846
17979
|
entity_type: request?.entity_type,
|
|
17980
|
+
channel_cid: request?.channel_cid,
|
|
17847
17981
|
review_queue_item_id: request?.review_queue_item_id,
|
|
17848
17982
|
attachments: request?.attachments
|
|
17849
17983
|
};
|
|
@@ -23190,6 +23324,7 @@ var ChatApi = class {
|
|
|
23190
23324
|
id: request?.id,
|
|
23191
23325
|
is_closed: request?.is_closed,
|
|
23192
23326
|
max_votes_allowed: request?.max_votes_allowed,
|
|
23327
|
+
team: request?.team,
|
|
23193
23328
|
voting_visibility: request?.voting_visibility,
|
|
23194
23329
|
options: request?.options,
|
|
23195
23330
|
custom: request?.custom
|
|
@@ -24277,7 +24412,7 @@ var StreamChat = class _StreamChat extends ChatApi {
|
|
|
24277
24412
|
return this.userAgent;
|
|
24278
24413
|
}
|
|
24279
24414
|
if (!this.cachedUserAgent) {
|
|
24280
|
-
const version = "10.0.0-rc.
|
|
24415
|
+
const version = "10.0.0-rc.14";
|
|
24281
24416
|
const { name: sdkName, version: sdkVersion } = this.sdkIdentifier ?? {};
|
|
24282
24417
|
const { name: appName, version: appVersion } = this.appIdentifier ?? {};
|
|
24283
24418
|
const { os, model: deviceModel } = this.deviceIdentifier ?? {};
|
|
@@ -25095,6 +25230,10 @@ var StreamChat = class _StreamChat extends ChatApi {
|
|
|
25095
25230
|
/**
|
|
25096
25231
|
* Pins the message.
|
|
25097
25232
|
*
|
|
25233
|
+
* A `number` is a relative offset in SECONDS, so a server-sent timestamp (`message.pinned_at`,
|
|
25234
|
+
* `message.pin_expires`) is rejected at compile time rather than read as ~1.79e18 seconds from now.
|
|
25235
|
+
* Hand one back as a date with `nsToDate(timestamp)`.
|
|
25236
|
+
*
|
|
25098
25237
|
* @param messageOrMessageId - MessageRequest object or message ID.
|
|
25099
25238
|
* @param timeoutOrExpirationDate - Expiration date or timeout. Use `number` to set the timeout
|
|
25100
25239
|
* in seconds, `string` or `Date` to set the exact expiration date (optional).
|
|
@@ -25473,6 +25612,11 @@ var ATTACHMENTS_FIELDS = {
|
|
|
25473
25612
|
kind: "value",
|
|
25474
25613
|
type: "number"
|
|
25475
25614
|
},
|
|
25615
|
+
pendingUploadsEnabled: {
|
|
25616
|
+
description: "Lets a message be sent while its attachments are still uploading. The message appears immediately and the request waits for the uploads; one failed upload fails the message.",
|
|
25617
|
+
kind: "value",
|
|
25618
|
+
type: "boolean"
|
|
25619
|
+
},
|
|
25476
25620
|
trackUploadProgress: {
|
|
25477
25621
|
description: "Reports upload progress on each attachment. Turning it off skips the progress bookkeeping.",
|
|
25478
25622
|
kind: "value",
|
|
@@ -25593,6 +25737,11 @@ var MESSAGE_COMPOSER_FIELDS = {
|
|
|
25593
25737
|
fields: POLLS_FIELDS,
|
|
25594
25738
|
kind: "group"
|
|
25595
25739
|
},
|
|
25740
|
+
retainCompositionOnSubmit: {
|
|
25741
|
+
description: "Decides whether submitting keeps the composer's contents instead of clearing them. Retains for a poll message by default.",
|
|
25742
|
+
kind: "value",
|
|
25743
|
+
type: "function"
|
|
25744
|
+
},
|
|
25596
25745
|
text: { description: "The text input itself.", fields: TEXT_FIELDS, kind: "group" }
|
|
25597
25746
|
};
|
|
25598
25747
|
var MESSAGE_DELIVERY_FIELDS = {
|
|
@@ -26538,13 +26687,13 @@ var AbstractOfflineDB = class {
|
|
|
26538
26687
|
execute = true
|
|
26539
26688
|
}) => {
|
|
26540
26689
|
const {
|
|
26541
|
-
received_at: last_read = nowNs(),
|
|
26542
26690
|
last_read_message_id,
|
|
26543
26691
|
// @ts-expect-error property missing
|
|
26544
26692
|
unread_messages = 0,
|
|
26545
26693
|
user,
|
|
26546
26694
|
cid
|
|
26547
26695
|
} = event;
|
|
26696
|
+
const last_read = (event.type === "notification.mark_unread" ? event.last_read_at : void 0) ?? event.created_at ?? event.received_at ?? nowNs();
|
|
26548
26697
|
const overriddenUnreadMessages = unreadMessages ?? unread_messages;
|
|
26549
26698
|
if (user?.id && cid) {
|
|
26550
26699
|
return await this.queriesWithChannelGuard(
|