stream-chat 10.0.0-rc.12 → 10.0.0-rc.13
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 +775 -638
- package/dist/cjs/index.browser.js.map +4 -4
- package/dist/cjs/index.node.js +780 -639
- package/dist/cjs/index.node.js.map +4 -4
- package/dist/esm/index.mjs +775 -638
- package/dist/esm/index.mjs.map +4 -4
- package/dist/types/channel.d.ts +39 -76
- 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/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 +60 -16
- 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/messageDelivery/MessageReceiptsTracker.d.ts +34 -0
- 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/pagination/paginators/BasePaginator.d.ts +22 -0
- package/dist/types/pagination/paginators/MessageIntervalPaginator.d.ts +5 -43
- package/dist/types/thread.d.ts +22 -42
- package/dist/types/uploadManager.d.ts +19 -1
- package/dist/types/utils.d.ts +2 -4
- package/package.json +1 -1
- package/src/channel.ts +60 -296
- 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/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 +173 -27
- 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/MessageReceiptsTracker.ts +66 -0
- 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/pagination/paginators/BasePaginator.ts +73 -23
- package/src/pagination/paginators/MessageIntervalPaginator.ts +39 -97
- package/src/thread.ts +15 -240
- package/src/uploadManager.ts +37 -5
- package/src/utils.ts +12 -5
- /package/dist/types/{entityStore → messageOperations}/applyReactionLocally.d.ts +0 -0
- /package/src/{entityStore → messageOperations}/applyReactionLocally.ts +0 -0
|
@@ -167,7 +167,6 @@ __export(src_exports, {
|
|
|
167
167
|
createPollComposerStateMiddleware: () => createPollComposerStateMiddleware,
|
|
168
168
|
createPostUploadAttachmentEnrichmentMiddleware: () => createPostUploadAttachmentEnrichmentMiddleware,
|
|
169
169
|
createPriorityOwnershipResolver: () => createPriorityOwnershipResolver,
|
|
170
|
-
createSendWithPendingUploadsAttachmentsMiddleware: () => createSendWithPendingUploadsAttachmentsMiddleware,
|
|
171
170
|
createSharedLocationCompositionMiddleware: () => createSharedLocationCompositionMiddleware,
|
|
172
171
|
createTextComposerCompositionMiddleware: () => createTextComposerCompositionMiddleware,
|
|
173
172
|
createTextComposerPreValidationMiddleware: () => createTextComposerPreValidationMiddleware,
|
|
@@ -186,6 +185,7 @@ __export(src_exports, {
|
|
|
186
185
|
formatMessage: () => formatMessage,
|
|
187
186
|
generateFileName: () => generateFileName,
|
|
188
187
|
getAllowedMentionTypesFromCapabilities: () => getAllowedMentionTypesFromCapabilities,
|
|
188
|
+
getAttachmentPreviewUrl: () => getAttachmentPreviewUrl,
|
|
189
189
|
getAttachmentTypeFromMimeType: () => getAttachmentTypeFromMimeType,
|
|
190
190
|
getCommandByName: () => getCommandByName,
|
|
191
191
|
getCompleteCommandInString: () => getCompleteCommandInString,
|
|
@@ -200,6 +200,7 @@ __export(src_exports, {
|
|
|
200
200
|
insertItemWithTrigger: () => insertItemWithTrigger,
|
|
201
201
|
isAudioAttachment: () => isAudioAttachment,
|
|
202
202
|
isBlobButNotFile: () => isBlobButNotFile,
|
|
203
|
+
isFailedUpload: () => isFailedUpload,
|
|
203
204
|
isFile: () => isFile,
|
|
204
205
|
isFileAttachment: () => isFileAttachment,
|
|
205
206
|
isFileList: () => isFileList,
|
|
@@ -225,6 +226,7 @@ __export(src_exports, {
|
|
|
225
226
|
isSharedLocationResponseData: () => isSharedLocationResponseData,
|
|
226
227
|
isTargetedOptionTextUpdate: () => isTargetedOptionTextUpdate,
|
|
227
228
|
isUploadCancellation: () => isUploadCancellation,
|
|
229
|
+
isUploadConfirmationPending: () => isUploadConfirmationPending,
|
|
228
230
|
isUploadedAttachment: () => isUploadedAttachment,
|
|
229
231
|
isVideoAttachment: () => isVideoAttachment,
|
|
230
232
|
isVoiceRecordingAttachment: () => isVoiceRecordingAttachment,
|
|
@@ -247,6 +249,8 @@ __export(src_exports, {
|
|
|
247
249
|
readFileAsArrayBuffer: () => readFileAsArrayBuffer,
|
|
248
250
|
removeDiacritics: () => removeDiacritics,
|
|
249
251
|
replaceWordWithEntity: () => replaceWordWithEntity,
|
|
252
|
+
resolveAttachmentFileSize: () => resolveAttachmentFileSize,
|
|
253
|
+
resolveAttachmentFullByteSize: () => resolveAttachmentFullByteSize,
|
|
250
254
|
settled: () => settled,
|
|
251
255
|
stripCommandFromText: () => stripCommandFromText,
|
|
252
256
|
stripMentionTokens: () => stripMentionTokens,
|
|
@@ -389,6 +393,13 @@ var LOCAL_MESSAGE_FIELDS = Object.freeze({ error: true });
|
|
|
389
393
|
var DEFAULT_QUERY_CHANNELS_RETRY_COUNT = 3;
|
|
390
394
|
var DEFAULT_QUERY_CHANNELS_MS_BETWEEN_RETRIES = 1e3;
|
|
391
395
|
|
|
396
|
+
// src/formatMessageMemo.ts
|
|
397
|
+
var memo = /* @__PURE__ */ new WeakMap();
|
|
398
|
+
var getMemoizedFormat = (source) => memo.get(source);
|
|
399
|
+
var memoizeFormat = (source, formatted) => {
|
|
400
|
+
memo.set(source, formatted);
|
|
401
|
+
};
|
|
402
|
+
|
|
392
403
|
// src/logger.ts
|
|
393
404
|
var scopedLogger = __toESM(require("@stream-io/logger"));
|
|
394
405
|
var import_logger = require("@stream-io/logger");
|
|
@@ -594,6 +605,8 @@ var axiosParamsSerializer = (params) => {
|
|
|
594
605
|
return newParams.join("&");
|
|
595
606
|
};
|
|
596
607
|
function formatMessage(message) {
|
|
608
|
+
const memoized = getMemoizedFormat(message);
|
|
609
|
+
if (memoized) return memoized;
|
|
597
610
|
const toLocalMessageBase = (msg) => {
|
|
598
611
|
if (!msg) return null;
|
|
599
612
|
return {
|
|
@@ -613,11 +626,13 @@ function formatMessage(message) {
|
|
|
613
626
|
updated_at: msg.updated_at ?? nowNs()
|
|
614
627
|
};
|
|
615
628
|
};
|
|
616
|
-
|
|
629
|
+
const formatted = {
|
|
617
630
|
...toLocalMessageBase(message),
|
|
618
631
|
error: message.error ?? void 0,
|
|
619
632
|
quoted_message: toLocalMessageBase(message.quoted_message) ?? void 0
|
|
620
633
|
};
|
|
634
|
+
memoizeFormat(message, formatted);
|
|
635
|
+
return formatted;
|
|
621
636
|
}
|
|
622
637
|
function computeOwnReactions({
|
|
623
638
|
current,
|
|
@@ -1488,7 +1503,12 @@ var EntityStore = class {
|
|
|
1488
1503
|
return fn();
|
|
1489
1504
|
} finally {
|
|
1490
1505
|
this.transactionDepth -= 1;
|
|
1491
|
-
if (this.transactionDepth === 0)
|
|
1506
|
+
if (this.transactionDepth === 0) {
|
|
1507
|
+
this.flush();
|
|
1508
|
+
const ids = this.pendingFlushIds;
|
|
1509
|
+
this.pendingFlushIds = void 0;
|
|
1510
|
+
if (ids) for (const id of ids) this.flushSubscribersNow(id);
|
|
1511
|
+
}
|
|
1492
1512
|
}
|
|
1493
1513
|
}
|
|
1494
1514
|
/**
|
|
@@ -1496,8 +1516,19 @@ var EntityStore = class {
|
|
|
1496
1516
|
* {@link EntityStoreSubscriber.flushState}). Called after an optimistic (local-user) write to
|
|
1497
1517
|
* `id` so it renders without the throttle delay. Only that id's own subscribers are flushed — the
|
|
1498
1518
|
* write touched no other id — and flushing a subscriber with nothing pending is a no-op.
|
|
1519
|
+
*
|
|
1520
|
+
* Inside a transaction it is deferred to the outermost exit — inline there it would be a no-op,
|
|
1521
|
+
* since `markDirty` has not notified anyone yet. Getting this wrong costs the siblings (pinned
|
|
1522
|
+
* list, open thread) up to 500ms on an optimistic write, while the writer updates inline.
|
|
1499
1523
|
*/
|
|
1500
1524
|
flushSubscribers(id) {
|
|
1525
|
+
if (this.transactionDepth > 0) {
|
|
1526
|
+
(this.pendingFlushIds ?? (this.pendingFlushIds = /* @__PURE__ */ new Set())).add(id);
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
this.flushSubscribersNow(id);
|
|
1530
|
+
}
|
|
1531
|
+
flushSubscribersNow(id) {
|
|
1501
1532
|
const subscribers = this.subscribers.get(id);
|
|
1502
1533
|
if (!subscribers) return;
|
|
1503
1534
|
for (const subscriber of subscribers) subscriber.flushState?.();
|
|
@@ -1596,11 +1627,11 @@ var isClassInstance = (value) => {
|
|
|
1596
1627
|
if (proto === null || proto === Object.prototype) return false;
|
|
1597
1628
|
return value.constructor && value.constructor !== Object;
|
|
1598
1629
|
};
|
|
1599
|
-
function memoHasOrAdd(
|
|
1600
|
-
const set =
|
|
1630
|
+
function memoHasOrAdd(memo2, a, b) {
|
|
1631
|
+
const set = memo2.get(a);
|
|
1601
1632
|
if (set && set.has(b)) return true;
|
|
1602
1633
|
if (set) set.add(b);
|
|
1603
|
-
else
|
|
1634
|
+
else memo2.set(a, new WeakSet([b]));
|
|
1604
1635
|
return false;
|
|
1605
1636
|
}
|
|
1606
1637
|
var isEqual = (value1, value2, pairMemo = /* @__PURE__ */ new WeakMap()) => {
|
|
@@ -2671,6 +2702,41 @@ var BasePaginator = class {
|
|
|
2671
2702
|
scheduleWindowPublish() {
|
|
2672
2703
|
this._windowPublishThrottle?.throttledFn();
|
|
2673
2704
|
}
|
|
2705
|
+
/**
|
|
2706
|
+
* Run `fn` with this paginator's window publishes suspended, emitting once on exit.
|
|
2707
|
+
*
|
|
2708
|
+
* A replace is internally remove-then-insert and each half changes the window, so without this one
|
|
2709
|
+
* logical `ingestItem` emits twice.
|
|
2710
|
+
*
|
|
2711
|
+
* `exit` decides how the single emit leaves the scope: `auto` rides the throttle when there is one
|
|
2712
|
+
* (live ops, where successive events should coalesce across the trailing edge), `sync` always
|
|
2713
|
+
* flushes ({@link batch}, whose contract is that a one-shot operation settles in a single update).
|
|
2714
|
+
*
|
|
2715
|
+
* **`lockItemOrder` lists are excluded.** Their emit is not a re-projection but a republish of the
|
|
2716
|
+
* last-published array with the item spliced back at its original index, and exiting through
|
|
2717
|
+
* {@link flushWindowPublish} re-derives from the active interval — reordering the list and dropping
|
|
2718
|
+
* whatever the interval no longer holds, which is the whole point of locking the order.
|
|
2719
|
+
*
|
|
2720
|
+
* Re-entrant: only the outermost scope emits, so an `ingestItem` inside a coalescing `batch`
|
|
2721
|
+
* defers to the batch.
|
|
2722
|
+
*/
|
|
2723
|
+
withSingleWindowPublish(fn, { exit = "auto" } = {}) {
|
|
2724
|
+
if (this.config.lockItemOrder) {
|
|
2725
|
+
fn();
|
|
2726
|
+
return;
|
|
2727
|
+
}
|
|
2728
|
+
const isOutermost = this._windowPublishSuspendDepth === 0;
|
|
2729
|
+
this._windowPublishSuspendDepth += 1;
|
|
2730
|
+
try {
|
|
2731
|
+
fn();
|
|
2732
|
+
} finally {
|
|
2733
|
+
this._windowPublishSuspendDepth -= 1;
|
|
2734
|
+
}
|
|
2735
|
+
if (!isOutermost || !this._suspendedWindowDirty) return;
|
|
2736
|
+
this._suspendedWindowDirty = false;
|
|
2737
|
+
if (exit === "auto" && this.isStateThrottled) this.scheduleWindowPublish();
|
|
2738
|
+
else this.flushWindowPublish();
|
|
2739
|
+
}
|
|
2674
2740
|
/**
|
|
2675
2741
|
* Flush any pending throttled window + interval-view publishes immediately. No-op when nothing
|
|
2676
2742
|
* is pending or throttling is off.
|
|
@@ -2899,8 +2965,18 @@ var BasePaginator = class {
|
|
|
2899
2965
|
const activeInterval = this._itemIntervals.get(this._activeIntervalId);
|
|
2900
2966
|
if (!activeInterval) return;
|
|
2901
2967
|
if (this.isStateThrottled) {
|
|
2902
|
-
|
|
2903
|
-
|
|
2968
|
+
const currentItems2 = this.items;
|
|
2969
|
+
if (!currentItems2 || currentItems2.length !== activeInterval.itemIds.length) {
|
|
2970
|
+
if (this.intervalHoldsAnyChangedId(activeInterval, changedIds)) {
|
|
2971
|
+
this.scheduleWindowPublish();
|
|
2972
|
+
}
|
|
2973
|
+
return;
|
|
2974
|
+
}
|
|
2975
|
+
for (let i = 0; i < currentItems2.length; i++) {
|
|
2976
|
+
const id = this.getItemId(currentItems2[i]);
|
|
2977
|
+
if (!changedIds.has(id)) continue;
|
|
2978
|
+
const updated = this._itemIndex.get(id);
|
|
2979
|
+
if (!updated || updated !== currentItems2[i]) {
|
|
2904
2980
|
this.scheduleWindowPublish();
|
|
2905
2981
|
return;
|
|
2906
2982
|
}
|
|
@@ -2929,11 +3005,8 @@ var BasePaginator = class {
|
|
|
2929
3005
|
return;
|
|
2930
3006
|
}
|
|
2931
3007
|
}
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
this.state.partialNext({ items: this.intervalToItems(activeInterval) });
|
|
2935
|
-
return;
|
|
2936
|
-
}
|
|
3008
|
+
if (this.intervalHoldsAnyChangedId(activeInterval, changedIds)) {
|
|
3009
|
+
this.state.partialNext({ items: this.intervalToItems(activeInterval) });
|
|
2937
3010
|
}
|
|
2938
3011
|
}
|
|
2939
3012
|
// ---------------------------------------------------------------------------
|
|
@@ -3603,6 +3676,13 @@ var BasePaginator = class {
|
|
|
3603
3676
|
* - if this is the active interval, re-emit state.items from interval
|
|
3604
3677
|
*/
|
|
3605
3678
|
ingestItem(ingestedItem) {
|
|
3679
|
+
let ingested = false;
|
|
3680
|
+
this.withSingleWindowPublish(() => {
|
|
3681
|
+
ingested = this.applyItemIngestion(ingestedItem);
|
|
3682
|
+
});
|
|
3683
|
+
return ingested;
|
|
3684
|
+
}
|
|
3685
|
+
applyItemIngestion(ingestedItem) {
|
|
3606
3686
|
const id = this.getItemId(ingestedItem);
|
|
3607
3687
|
const previousItem = this._itemIndex.get(id);
|
|
3608
3688
|
const previousCoords = this.locateByItem(previousItem || ingestedItem);
|
|
@@ -3614,7 +3694,9 @@ var BasePaginator = class {
|
|
|
3614
3694
|
removedItemCoordinates = this.removeItemAtCoordinates(previousCoords);
|
|
3615
3695
|
}
|
|
3616
3696
|
const itemHasBeenRemoved = !!removedItemCoordinates?.state && removedItemCoordinates.state.currentIndex > -1;
|
|
3617
|
-
this._itemIndex.
|
|
3697
|
+
if (this._itemIndex.has(id) || this.matchesFilter(ingestedItem)) {
|
|
3698
|
+
this._itemIndex.setOne(ingestedItem);
|
|
3699
|
+
}
|
|
3618
3700
|
if (!this.matchesFilter(ingestedItem)) {
|
|
3619
3701
|
this.shrinkOffsetAfterRemoval(removedItemCoordinates);
|
|
3620
3702
|
if (!this.isWindowPublishSuspended && this.isStateThrottled && itemHasBeenRemoved)
|
|
@@ -3733,16 +3815,7 @@ var BasePaginator = class {
|
|
|
3733
3815
|
this._itemIndex.batch(fn);
|
|
3734
3816
|
return;
|
|
3735
3817
|
}
|
|
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
|
-
}
|
|
3818
|
+
this.withSingleWindowPublish(() => this._itemIndex.batch(fn), { exit: "sync" });
|
|
3746
3819
|
}
|
|
3747
3820
|
// ---------------------------------------------------------------------------
|
|
3748
3821
|
// Remove / contains
|
|
@@ -4359,6 +4432,10 @@ var CORE_NOTIFICATION_TYPE = {
|
|
|
4359
4432
|
messageJumpFailed: "api:message:jump:failed",
|
|
4360
4433
|
/** Jumping to the latest message failed. */
|
|
4361
4434
|
messageJumpToLatestFailed: "api:message:jumpToLatest:failed",
|
|
4435
|
+
/** Sending a composed message failed. The message stays in the list, marked failed. */
|
|
4436
|
+
messageSendFailed: "api:message:send:failed",
|
|
4437
|
+
/** Saving an edit failed. The edit is kept on the message, marked failed. */
|
|
4438
|
+
messageUpdateFailed: "api:message:update:failed",
|
|
4362
4439
|
pollCastVoteLimit: "validation:poll:castVote:limit",
|
|
4363
4440
|
pollCreateFailed: "api:poll:create:failed"
|
|
4364
4441
|
};
|
|
@@ -5021,34 +5098,37 @@ var MessageIntervalPaginator = class extends BasePaginator {
|
|
|
5021
5098
|
deletedAt
|
|
5022
5099
|
}) => {
|
|
5023
5100
|
const loadedMessages = this.items ?? [];
|
|
5024
|
-
this.batch(
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
if (
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5101
|
+
this.batch(
|
|
5102
|
+
() => {
|
|
5103
|
+
for (const message of loadedMessages) {
|
|
5104
|
+
if (message.user?.id === userId) {
|
|
5105
|
+
if (hardDelete) {
|
|
5106
|
+
this.removeItem({ id: message.id });
|
|
5107
|
+
} else {
|
|
5108
|
+
this.ingestItem(
|
|
5109
|
+
toDeletedMessage({
|
|
5110
|
+
message,
|
|
5111
|
+
hardDelete,
|
|
5112
|
+
deletedAt
|
|
5113
|
+
})
|
|
5114
|
+
);
|
|
5115
|
+
}
|
|
5116
|
+
continue;
|
|
5117
|
+
}
|
|
5118
|
+
if (message.quoted_message?.user?.id === userId && message.quoted_message.type !== "deleted") {
|
|
5119
|
+
this.ingestItem({
|
|
5120
|
+
...message,
|
|
5121
|
+
quoted_message: toDeletedMessage({
|
|
5122
|
+
message: formatMessage(message.quoted_message),
|
|
5033
5123
|
hardDelete,
|
|
5034
5124
|
deletedAt
|
|
5035
5125
|
})
|
|
5036
|
-
);
|
|
5126
|
+
});
|
|
5037
5127
|
}
|
|
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
5128
|
}
|
|
5050
|
-
}
|
|
5051
|
-
|
|
5129
|
+
},
|
|
5130
|
+
{ coalesce: true }
|
|
5131
|
+
);
|
|
5052
5132
|
};
|
|
5053
5133
|
/**
|
|
5054
5134
|
* Ensures quoted-message snapshots across loaded paginator cache are in sync
|
|
@@ -5084,6 +5164,7 @@ var MessageIntervalPaginator = class extends BasePaginator {
|
|
|
5084
5164
|
this.batch(() => {
|
|
5085
5165
|
for (const message of this._itemIndex.values()) {
|
|
5086
5166
|
if (message.user?.id !== user.id) continue;
|
|
5167
|
+
if (message.user === user) continue;
|
|
5087
5168
|
this._itemIndex.setOne({ ...message, user });
|
|
5088
5169
|
if (activeIds.has(this.getItemId(message))) activeAffected = true;
|
|
5089
5170
|
}
|
|
@@ -5094,58 +5175,6 @@ var MessageIntervalPaginator = class extends BasePaginator {
|
|
|
5094
5175
|
});
|
|
5095
5176
|
}
|
|
5096
5177
|
};
|
|
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
5178
|
/**
|
|
5150
5179
|
* Map a timestamp to a loaded message — the first message in the latest (head) window whose
|
|
5151
5180
|
* `created_at` is >= `timestamp` (mirrors the legacy `ChannelState.findMessageByTimestamp`
|
|
@@ -5883,6 +5912,7 @@ var PENDING_UPLOAD_STATES = [
|
|
|
5883
5912
|
];
|
|
5884
5913
|
var isPendingUpload = (attachment) => isLocalUploadAttachment(attachment) && PENDING_UPLOAD_STATES.includes(attachment.localMetadata.uploadState);
|
|
5885
5914
|
var isFinishedUpload = (attachment) => isLocalUploadAttachment(attachment) && attachment.localMetadata.uploadState === "finished";
|
|
5915
|
+
var isFailedUpload = (attachment) => isLocalUploadAttachment(attachment) && attachment.localMetadata.uploadState === "failed";
|
|
5886
5916
|
var isFileAttachment = (attachment, supportedVideoFormat = []) => attachment.type === "file" || !!(attachment.custom?.mime_type && supportedVideoFormat.indexOf(
|
|
5887
5917
|
attachment.custom?.mime_type
|
|
5888
5918
|
) === -1 && attachment.type !== "video");
|
|
@@ -5901,6 +5931,27 @@ var isUploadedAttachment = (attachment) => isAudioAttachment(attachment) || isFi
|
|
|
5901
5931
|
var isSharedLocationResponseData = (location) => !!location.latitude && !!location.longitude && !!location.channel_cid;
|
|
5902
5932
|
var isGiphyAttachment = (attachment) => attachment.type === "giphy";
|
|
5903
5933
|
|
|
5934
|
+
// src/messageComposer/attachmentUtils.ts
|
|
5935
|
+
var getAttachmentPreviewUrl = (attachment, ...urls) => {
|
|
5936
|
+
const resolved = urls.find(Boolean);
|
|
5937
|
+
if (resolved) return resolved;
|
|
5938
|
+
return isLocalUploadAttachment(attachment) ? attachment.localMetadata.previewUri : void 0;
|
|
5939
|
+
};
|
|
5940
|
+
var resolveAttachmentFileSize = (attachment) => attachment?.custom?.file_size ?? attachment?.file_size;
|
|
5941
|
+
var resolveAttachmentFullByteSize = (attachment) => {
|
|
5942
|
+
const fromFile = attachment?.localMetadata?.file?.size;
|
|
5943
|
+
if (typeof fromFile === "number" && Number.isFinite(fromFile) && fromFile >= 0) {
|
|
5944
|
+
return fromFile;
|
|
5945
|
+
}
|
|
5946
|
+
const raw = resolveAttachmentFileSize(attachment);
|
|
5947
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw >= 0) return raw;
|
|
5948
|
+
if (typeof raw === "string") {
|
|
5949
|
+
const parsed = parseFloat(raw);
|
|
5950
|
+
if (Number.isFinite(parsed) && parsed >= 0) return parsed;
|
|
5951
|
+
}
|
|
5952
|
+
return void 0;
|
|
5953
|
+
};
|
|
5954
|
+
|
|
5904
5955
|
// src/messageComposer/fileUtils.ts
|
|
5905
5956
|
var isFile = (fileLike) => !!fileLike.lastModified && !("uri" in fileLike);
|
|
5906
5957
|
var isFileList = (obj) => {
|
|
@@ -6167,6 +6218,12 @@ var import_axios = __toESM(require("axios"));
|
|
|
6167
6218
|
var import_state_store7 = require("@stream-io/state-store");
|
|
6168
6219
|
var logger3 = chatLoggerSystem.getLogger("upload-manager");
|
|
6169
6220
|
var isUploadCancellation = (error) => import_axios.default.isCancel(error) || error instanceof Error && error.name === "AbortError";
|
|
6221
|
+
var isUploadConfirmationPending = (localMetadata) => {
|
|
6222
|
+
if (!localMetadata || localMetadata.uploadState !== "uploading") return false;
|
|
6223
|
+
if (localMetadata.uploadConfirmationPending !== void 0)
|
|
6224
|
+
return localMetadata.uploadConfirmationPending;
|
|
6225
|
+
return localMetadata.uploadProgress !== void 0 && localMetadata.uploadProgress >= 100;
|
|
6226
|
+
};
|
|
6170
6227
|
var initState = () => ({ uploads: {} });
|
|
6171
6228
|
var upsertById = (uploads, record) => ({
|
|
6172
6229
|
...uploads,
|
|
@@ -6290,12 +6347,12 @@ var UploadManager = class {
|
|
|
6290
6347
|
this.state = new import_state_store7.StateStore(initState());
|
|
6291
6348
|
}
|
|
6292
6349
|
resolveAttachmentManager(channelCid) {
|
|
6293
|
-
const colon =
|
|
6294
|
-
|
|
6350
|
+
const colon = ":";
|
|
6351
|
+
const colonIndex = channelCid.indexOf(colon);
|
|
6352
|
+
if (colonIndex <= 0 || colonIndex === channelCid.length - 1) {
|
|
6295
6353
|
throw new Error(`Invalid channelCid: ${channelCid}`);
|
|
6296
6354
|
}
|
|
6297
|
-
const channelType = channelCid.
|
|
6298
|
-
const channelId = channelCid.slice(colon + 1);
|
|
6355
|
+
const [channelType, channelId] = channelCid.split(colon);
|
|
6299
6356
|
return this.client.channel(channelType, channelId).messageComposer.attachmentManager;
|
|
6300
6357
|
}
|
|
6301
6358
|
get uploads() {
|
|
@@ -7029,13 +7086,15 @@ var composeWithPendingUploads = ({
|
|
|
7029
7086
|
composer,
|
|
7030
7087
|
state
|
|
7031
7088
|
}) => {
|
|
7032
|
-
const composerIsKeptAsDraft =
|
|
7089
|
+
const composerIsKeptAsDraft = composer.retainsCompositionOnSubmit;
|
|
7033
7090
|
const relevantAttachments = composer.attachmentManager.attachments.filter(
|
|
7034
|
-
(attachment) => isFinishedUpload(attachment) || !composerIsKeptAsDraft && isPendingUpload(attachment)
|
|
7091
|
+
(attachment) => isFinishedUpload(attachment) || !composerIsKeptAsDraft && (isPendingUpload(attachment) || isFailedUpload(attachment))
|
|
7035
7092
|
);
|
|
7036
7093
|
const localAttachments = (state.localMessage.attachments ?? []).concat(
|
|
7094
|
+
// Only a finished upload has a URL of its own. Everything else keeps `localMetadata`, which
|
|
7095
|
+
// is what the message list joins to the live `uploadManager` record and what a retry needs.
|
|
7037
7096
|
relevantAttachments.map(
|
|
7038
|
-
(attachment) =>
|
|
7097
|
+
(attachment) => isFinishedUpload(attachment) ? localAttachmentToAttachment(attachment) : attachment
|
|
7039
7098
|
)
|
|
7040
7099
|
);
|
|
7041
7100
|
const messageAttachments = (state.message.attachments ?? []).concat(
|
|
@@ -7054,21 +7113,6 @@ var composeWithPendingUploads = ({
|
|
|
7054
7113
|
} : state.message
|
|
7055
7114
|
};
|
|
7056
7115
|
};
|
|
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
7116
|
var createAttachmentsCompositionMiddleware = (composer) => ({
|
|
7073
7117
|
id: "stream-io/message-composer-middleware/attachments",
|
|
7074
7118
|
handlers: {
|
|
@@ -7080,6 +7124,9 @@ var createAttachmentsCompositionMiddleware = (composer) => ({
|
|
|
7080
7124
|
}) => {
|
|
7081
7125
|
const { attachmentManager } = composer;
|
|
7082
7126
|
if (!attachmentManager) return forward();
|
|
7127
|
+
if (attachmentManager.config.pendingUploadsEnabled) {
|
|
7128
|
+
return next(composeWithPendingUploads({ composer, state }));
|
|
7129
|
+
}
|
|
7083
7130
|
if (attachmentManager.uploadsInProgressCount > 0) {
|
|
7084
7131
|
composer.client.notifications.addWarning({
|
|
7085
7132
|
message: "Wait until all attachments have uploaded",
|
|
@@ -9805,10 +9852,11 @@ var createLinkPreviewsCompositionMiddleware = (composer) => ({
|
|
|
9805
9852
|
const linkPreviews = linkPreviewsManager.loadingPreviews.length > 0 ? [] : linkPreviewsManager.loadedPreviews.map(
|
|
9806
9853
|
(preview) => LinkPreviewsManager.getPreviewData(preview)
|
|
9807
9854
|
);
|
|
9808
|
-
const
|
|
9855
|
+
const messageAttachments = (state.message.attachments ?? []).concat(
|
|
9809
9856
|
linkPreviews
|
|
9810
9857
|
);
|
|
9811
|
-
|
|
9858
|
+
const localAttachments = (state.localMessage.attachments ?? []).concat(linkPreviews);
|
|
9859
|
+
if (!messageAttachments.length && !localAttachments.length) return forward();
|
|
9812
9860
|
const sendOptions = { ...state.sendOptions };
|
|
9813
9861
|
const skip_enrich_url = !someLinkPreviewsLoading && linkPreviews.length > 0 || someLinkPreviewsDismissed;
|
|
9814
9862
|
if (skip_enrich_url) {
|
|
@@ -9816,14 +9864,14 @@ var createLinkPreviewsCompositionMiddleware = (composer) => ({
|
|
|
9816
9864
|
}
|
|
9817
9865
|
return next({
|
|
9818
9866
|
...state,
|
|
9819
|
-
message: {
|
|
9867
|
+
message: messageAttachments.length ? {
|
|
9820
9868
|
...state.message,
|
|
9821
|
-
attachments
|
|
9822
|
-
},
|
|
9823
|
-
localMessage: {
|
|
9869
|
+
attachments: messageAttachments
|
|
9870
|
+
} : state.message,
|
|
9871
|
+
localMessage: localAttachments.length ? {
|
|
9824
9872
|
...state.localMessage,
|
|
9825
|
-
attachments
|
|
9826
|
-
},
|
|
9873
|
+
attachments: localAttachments
|
|
9874
|
+
} : state.localMessage,
|
|
9827
9875
|
sendOptions
|
|
9828
9876
|
});
|
|
9829
9877
|
}
|
|
@@ -10180,13 +10228,6 @@ var MessageComposerMiddlewareExecutor = class extends MiddlewareExecutor {
|
|
|
10180
10228
|
createCompositionDataCleanupMiddleware(composer)
|
|
10181
10229
|
]);
|
|
10182
10230
|
}
|
|
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
10231
|
};
|
|
10191
10232
|
var MessageDraftComposerMiddlewareExecutor = class extends MiddlewareExecutor {
|
|
10192
10233
|
constructor({ composer }) {
|
|
@@ -11335,6 +11376,7 @@ var DEFAULT_ATTACHMENT_MANAGER_CONFIG = {
|
|
|
11335
11376
|
enabled: true,
|
|
11336
11377
|
fileUploadFilter: () => true,
|
|
11337
11378
|
maxNumberOfFilesPerMessage: API_MAX_FILES_ALLOWED_PER_MESSAGE,
|
|
11379
|
+
pendingUploadsEnabled: false,
|
|
11338
11380
|
trackUploadProgress: true
|
|
11339
11381
|
};
|
|
11340
11382
|
var DEFAULT_TEXT_COMPOSER_CONFIG = {
|
|
@@ -11356,6 +11398,7 @@ var DEFAULT_COMPOSER_CONFIG = deepFreezeConfig({
|
|
|
11356
11398
|
linkPreviews: DEFAULT_LINK_PREVIEW_MANAGER_CONFIG,
|
|
11357
11399
|
location: DEFAULT_LOCATION_COMPOSER_CONFIG,
|
|
11358
11400
|
polls: DEFAULT_POLL_COMPOSER_CONFIG,
|
|
11401
|
+
retainCompositionOnSubmit: (composer) => !!composer.pollId,
|
|
11359
11402
|
text: DEFAULT_TEXT_COMPOSER_CONFIG
|
|
11360
11403
|
});
|
|
11361
11404
|
|
|
@@ -11918,6 +11961,123 @@ var mergeServerRestrictions = (requested, restrictions, upperBounds) => {
|
|
|
11918
11961
|
// src/thread.ts
|
|
11919
11962
|
var import_state_store18 = require("@stream-io/state-store");
|
|
11920
11963
|
|
|
11964
|
+
// src/messageOperations/applyReactionLocally.ts
|
|
11965
|
+
var applyReactionLocally = (client, {
|
|
11966
|
+
enforceUnique = false,
|
|
11967
|
+
messageId,
|
|
11968
|
+
reaction,
|
|
11969
|
+
removed = false
|
|
11970
|
+
}) => {
|
|
11971
|
+
const store = client.messageStore;
|
|
11972
|
+
const user = client.user;
|
|
11973
|
+
const existing = store.get(messageId);
|
|
11974
|
+
if (!user || !existing) return;
|
|
11975
|
+
const now = nowNs();
|
|
11976
|
+
const reactionResponse = {
|
|
11977
|
+
...reaction,
|
|
11978
|
+
created_at: reaction.created_at ?? now,
|
|
11979
|
+
custom: reaction.custom ?? {},
|
|
11980
|
+
message_id: messageId,
|
|
11981
|
+
score: reaction.score ?? 1,
|
|
11982
|
+
type: reaction.type,
|
|
11983
|
+
updated_at: reaction.updated_at ?? now,
|
|
11984
|
+
user,
|
|
11985
|
+
user_id: user.id
|
|
11986
|
+
};
|
|
11987
|
+
const removedReactions = removed ? existing.own_reactions?.filter((r) => r.type === reactionResponse.type) ?? [] : enforceUnique ? existing.own_reactions ?? [] : [];
|
|
11988
|
+
const withCounts = removed ? messageWithReactionRemoved(existing, reactionResponse) : messageWithReactionAdded(existing, reactionResponse, enforceUnique);
|
|
11989
|
+
const own_reactions = computeOwnReactions({
|
|
11990
|
+
current: existing.own_reactions ?? [],
|
|
11991
|
+
enforceUnique,
|
|
11992
|
+
reaction: reactionResponse,
|
|
11993
|
+
removed,
|
|
11994
|
+
userId: user.id
|
|
11995
|
+
});
|
|
11996
|
+
store.upsert({ ...withCounts, own_reactions });
|
|
11997
|
+
store.flushSubscribers(messageId);
|
|
11998
|
+
const persisted = store.get(messageId);
|
|
11999
|
+
if (persisted) {
|
|
12000
|
+
client.offlineDb?.executeQuerySafely(
|
|
12001
|
+
(db) => removed ? db.deleteReaction({ message: persisted, reaction: reactionResponse }) : enforceUnique ? db.updateReaction({ message: persisted, reaction: reactionResponse }) : db.insertReaction({ message: persisted, reaction: reactionResponse }),
|
|
12002
|
+
{ method: "applyReactionLocally" }
|
|
12003
|
+
);
|
|
12004
|
+
}
|
|
12005
|
+
return () => {
|
|
12006
|
+
if (!removed) {
|
|
12007
|
+
applyReactionLocally(client, {
|
|
12008
|
+
messageId,
|
|
12009
|
+
reaction: reactionResponse,
|
|
12010
|
+
removed: true
|
|
12011
|
+
});
|
|
12012
|
+
}
|
|
12013
|
+
for (const removedReaction of removedReactions) {
|
|
12014
|
+
applyReactionLocally(client, {
|
|
12015
|
+
messageId,
|
|
12016
|
+
reaction: removedReaction,
|
|
12017
|
+
removed: false
|
|
12018
|
+
});
|
|
12019
|
+
}
|
|
12020
|
+
};
|
|
12021
|
+
};
|
|
12022
|
+
|
|
12023
|
+
// src/messageOperations/optimistic.ts
|
|
12024
|
+
var isQueuedForReplay = async (client, messageId, types) => {
|
|
12025
|
+
const { offlineDb } = client;
|
|
12026
|
+
if (!offlineDb?.state.getLatestValue().initialized) return false;
|
|
12027
|
+
const pending = await offlineDb.getPendingTasks({ messageId });
|
|
12028
|
+
return !!pending?.some((task) => types.includes(task.type));
|
|
12029
|
+
};
|
|
12030
|
+
var reconcileHeldMessage = (channel, message) => {
|
|
12031
|
+
if (!message) return;
|
|
12032
|
+
const { messageStore } = channel.getClient();
|
|
12033
|
+
if (!messageStore.has(message.id)) return;
|
|
12034
|
+
messageStore.upsert(formatMessage(message));
|
|
12035
|
+
};
|
|
12036
|
+
var addReactionOptimistically = async ({
|
|
12037
|
+
channel,
|
|
12038
|
+
messageId,
|
|
12039
|
+
options,
|
|
12040
|
+
reaction
|
|
12041
|
+
}) => {
|
|
12042
|
+
const client = channel.getClient();
|
|
12043
|
+
const { created_at, updated_at, ...restOfReaction } = reaction;
|
|
12044
|
+
const undo = applyReactionLocally(client, {
|
|
12045
|
+
enforceUnique: options?.enforce_unique ?? false,
|
|
12046
|
+
messageId,
|
|
12047
|
+
reaction: {
|
|
12048
|
+
...restOfReaction,
|
|
12049
|
+
...created_at ? { created_at: dateToNs(created_at) } : {},
|
|
12050
|
+
...updated_at ? { updated_at: dateToNs(updated_at) } : {}
|
|
12051
|
+
}
|
|
12052
|
+
});
|
|
12053
|
+
try {
|
|
12054
|
+
const response = await channel.sendReaction({ id: messageId, reaction, ...options });
|
|
12055
|
+
reconcileHeldMessage(channel, response?.message);
|
|
12056
|
+
} catch (error) {
|
|
12057
|
+
if (!await isQueuedForReplay(client, messageId, ["send-reaction"])) undo?.();
|
|
12058
|
+
throw error;
|
|
12059
|
+
}
|
|
12060
|
+
};
|
|
12061
|
+
var deleteReactionOptimistically = async ({
|
|
12062
|
+
channel,
|
|
12063
|
+
messageId,
|
|
12064
|
+
type
|
|
12065
|
+
}) => {
|
|
12066
|
+
const client = channel.getClient();
|
|
12067
|
+
const undo = applyReactionLocally(client, {
|
|
12068
|
+
messageId,
|
|
12069
|
+
reaction: { type },
|
|
12070
|
+
removed: true
|
|
12071
|
+
});
|
|
12072
|
+
try {
|
|
12073
|
+
const response = await channel.deleteReaction({ id: messageId, type });
|
|
12074
|
+
reconcileHeldMessage(channel, response?.message);
|
|
12075
|
+
} catch (error) {
|
|
12076
|
+
if (!await isQueuedForReplay(client, messageId, ["delete-reaction"])) undo?.();
|
|
12077
|
+
throw error;
|
|
12078
|
+
}
|
|
12079
|
+
};
|
|
12080
|
+
|
|
11921
12081
|
// src/messageOperations/MessageOperationStatePolicy.ts
|
|
11922
12082
|
var parseError = (error) => {
|
|
11923
12083
|
const stringError = JSON.stringify(error);
|
|
@@ -12172,15 +12332,57 @@ var MessageOperations = class {
|
|
|
12172
12332
|
});
|
|
12173
12333
|
}
|
|
12174
12334
|
/**
|
|
12175
|
-
*
|
|
12176
|
-
*
|
|
12177
|
-
|
|
12335
|
+
* Resolves any in-flight upload and folds the URLs back into both payloads. A no-op without
|
|
12336
|
+
* {@link MessageOperationsContext.settlePendingUploads} or a pending attachment.
|
|
12337
|
+
*/
|
|
12338
|
+
async settlePendingUploads(params) {
|
|
12339
|
+
const settle = this.ctx.settlePendingUploads;
|
|
12340
|
+
const attachments = params.localMessage.attachments;
|
|
12341
|
+
if (!settle || !attachments?.length) return { params };
|
|
12342
|
+
const { attachments: settledAttachments, failureReason } = await settle(attachments);
|
|
12343
|
+
if (settledAttachments === attachments) return { failureReason, params };
|
|
12344
|
+
const wirePayload = "message" in params && params.message ? { message: { ...params.message, attachments: settledAttachments } } : {};
|
|
12345
|
+
return {
|
|
12346
|
+
failureReason,
|
|
12347
|
+
params: {
|
|
12348
|
+
...params,
|
|
12349
|
+
...wirePayload,
|
|
12350
|
+
localMessage: { ...params.localMessage, attachments: settledAttachments }
|
|
12351
|
+
}
|
|
12352
|
+
};
|
|
12353
|
+
}
|
|
12354
|
+
/**
|
|
12355
|
+
* Applies the owner's request-ordering policy for this kind, if it supplied one.
|
|
12356
|
+
*/
|
|
12357
|
+
withRequestSequencing(kind, request) {
|
|
12358
|
+
return this.ctx.sequenceRequests ? this.ctx.sequenceRequests(kind, request) : request();
|
|
12359
|
+
}
|
|
12360
|
+
/**
|
|
12361
|
+
* The shared lifecycle: apply the optimistic state, settle any attachment upload still in flight,
|
|
12362
|
+
* fire the request, then reconcile or record the failure. `kind` is threaded through because the
|
|
12363
|
+
* three operations want materially different state transitions — see
|
|
12364
|
+
* {@link MessageOperationStatePolicy}.
|
|
12178
12365
|
*/
|
|
12179
12366
|
async run(kind, params, doRequest) {
|
|
12180
12367
|
const messageId = params.localMessage.id;
|
|
12181
12368
|
const optimistic = this.policy.optimistic(kind, params);
|
|
12369
|
+
let effective = params;
|
|
12182
12370
|
try {
|
|
12183
|
-
const response = await
|
|
12371
|
+
const response = await this.withRequestSequencing(kind, async () => {
|
|
12372
|
+
if (kind !== "delete") {
|
|
12373
|
+
const settled2 = await this.settlePendingUploads(params);
|
|
12374
|
+
effective = settled2.params;
|
|
12375
|
+
if (settled2.failureReason) throw settled2.failureReason;
|
|
12376
|
+
const settledAttachments = effective.localMessage.attachments;
|
|
12377
|
+
const held = settledAttachments === params.localMessage.attachments ? void 0 : this.ctx.get(messageId);
|
|
12378
|
+
if (held) {
|
|
12379
|
+
const withUrls = { ...held, attachments: settledAttachments };
|
|
12380
|
+
this.ctx.ingest(withUrls);
|
|
12381
|
+
this.ctx.persist(withUrls);
|
|
12382
|
+
}
|
|
12383
|
+
}
|
|
12384
|
+
return await doRequest(effective);
|
|
12385
|
+
});
|
|
12184
12386
|
this.policy.success({
|
|
12185
12387
|
kind,
|
|
12186
12388
|
messageFromResponse: response?.message,
|
|
@@ -12192,7 +12394,7 @@ var MessageOperations = class {
|
|
|
12192
12394
|
await this.policy.failure({
|
|
12193
12395
|
error: e,
|
|
12194
12396
|
kind,
|
|
12195
|
-
localMessage:
|
|
12397
|
+
localMessage: effective.localMessage,
|
|
12196
12398
|
messageId,
|
|
12197
12399
|
optimistic,
|
|
12198
12400
|
options: params.options
|
|
@@ -12200,6 +12402,13 @@ var MessageOperations = class {
|
|
|
12200
12402
|
throw e;
|
|
12201
12403
|
}
|
|
12202
12404
|
}
|
|
12405
|
+
/*
|
|
12406
|
+
* Each operation resolves its request as `requestFn ?? handlers.<kind> ?? defaults.<kind>`.
|
|
12407
|
+
*
|
|
12408
|
+
* `handlers` is the integrator seam (`ChannelConfig.requestHandlers`). `requestFn` is a direct
|
|
12409
|
+
* injection point for tests; it is not exposed on the public methods because it wins over
|
|
12410
|
+
* `handlers`, so a caller reaching for it would bypass whatever the host SDK registered.
|
|
12411
|
+
*/
|
|
12203
12412
|
async send(params, requestFn) {
|
|
12204
12413
|
const handlers = this.ctx.handlers();
|
|
12205
12414
|
const messageToSend = this.normalizeMessage(
|
|
@@ -12272,122 +12481,27 @@ var MessageOperations = class {
|
|
|
12272
12481
|
const doRequest = requestFn ?? handlers.delete ?? (async (p) => await this.ctx.defaults.delete(p.localMessage.id, p.options));
|
|
12273
12482
|
return await this.run("delete", params, doRequest);
|
|
12274
12483
|
}
|
|
12275
|
-
|
|
12276
|
-
|
|
12277
|
-
|
|
12278
|
-
|
|
12279
|
-
|
|
12280
|
-
messageId,
|
|
12281
|
-
reaction,
|
|
12282
|
-
removed = false
|
|
12283
|
-
}) => {
|
|
12284
|
-
const store = client.messageStore;
|
|
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" }
|
|
12316
|
-
);
|
|
12317
|
-
}
|
|
12318
|
-
return () => {
|
|
12319
|
-
if (!removed) {
|
|
12320
|
-
applyReactionLocally(client, {
|
|
12321
|
-
messageId,
|
|
12322
|
-
reaction: reactionResponse,
|
|
12323
|
-
removed: true
|
|
12324
|
-
});
|
|
12325
|
-
}
|
|
12326
|
-
for (const removedReaction of removedReactions) {
|
|
12327
|
-
applyReactionLocally(client, {
|
|
12328
|
-
messageId,
|
|
12329
|
-
reaction: removedReaction,
|
|
12330
|
-
removed: false
|
|
12331
|
-
});
|
|
12332
|
-
}
|
|
12333
|
-
};
|
|
12334
|
-
};
|
|
12335
|
-
|
|
12336
|
-
// src/messageOperations/optimistic.ts
|
|
12337
|
-
var isQueuedForReplay = async (client, messageId, types) => {
|
|
12338
|
-
const { offlineDb } = client;
|
|
12339
|
-
if (!offlineDb?.state.getLatestValue().initialized) return false;
|
|
12340
|
-
const pending = await offlineDb.getPendingTasks({ messageId });
|
|
12341
|
-
return !!pending?.some((task) => types.includes(task.type));
|
|
12342
|
-
};
|
|
12343
|
-
var reconcileHeldMessage = (channel, message) => {
|
|
12344
|
-
if (!message) return;
|
|
12345
|
-
const { messageStore } = channel.getClient();
|
|
12346
|
-
if (!messageStore.has(message.id)) return;
|
|
12347
|
-
messageStore.upsert(formatMessage(message));
|
|
12348
|
-
};
|
|
12349
|
-
var addReactionOptimistically = async ({
|
|
12350
|
-
channel,
|
|
12351
|
-
messageId,
|
|
12352
|
-
options,
|
|
12353
|
-
reaction
|
|
12354
|
-
}) => {
|
|
12355
|
-
const client = channel.getClient();
|
|
12356
|
-
const { created_at, updated_at, ...restOfReaction } = reaction;
|
|
12357
|
-
const undo = applyReactionLocally(client, {
|
|
12358
|
-
enforceUnique: options?.enforce_unique ?? false,
|
|
12359
|
-
messageId,
|
|
12360
|
-
reaction: {
|
|
12361
|
-
...restOfReaction,
|
|
12362
|
-
...created_at ? { created_at: dateToNs(created_at) } : {},
|
|
12363
|
-
...updated_at ? { updated_at: dateToNs(updated_at) } : {}
|
|
12364
|
-
}
|
|
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;
|
|
12484
|
+
async retrySendWithLocalUpdate({
|
|
12485
|
+
localMessage,
|
|
12486
|
+
options
|
|
12487
|
+
}) {
|
|
12488
|
+
await this.retry({ localMessage: { ...localMessage, type: "regular" }, options });
|
|
12372
12489
|
}
|
|
12373
|
-
|
|
12374
|
-
|
|
12375
|
-
|
|
12376
|
-
|
|
12377
|
-
|
|
12378
|
-
|
|
12379
|
-
|
|
12380
|
-
|
|
12381
|
-
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
|
|
12388
|
-
} catch (error) {
|
|
12389
|
-
if (!await isQueuedForReplay(client, messageId, ["delete-reaction"])) undo?.();
|
|
12390
|
-
throw error;
|
|
12490
|
+
/**
|
|
12491
|
+
* Adds a reaction with an optimistic local state update — see {@link addReactionOptimistically}.
|
|
12492
|
+
*
|
|
12493
|
+
* The request routes through the channel because reactions are channel-level, while the local write
|
|
12494
|
+
* is addressed by message id and so reaches a pure thread reply that no channel collection holds.
|
|
12495
|
+
*/
|
|
12496
|
+
async addReactionWithLocalUpdate(params) {
|
|
12497
|
+
await addReactionOptimistically({ channel: this.ctx.channel, ...params });
|
|
12498
|
+
}
|
|
12499
|
+
/**
|
|
12500
|
+
* Removes the current user's reaction with an optimistic local state update, mirroring
|
|
12501
|
+
* {@link MessageOperations.addReactionWithLocalUpdate}.
|
|
12502
|
+
*/
|
|
12503
|
+
async deleteReactionWithLocalUpdate(params) {
|
|
12504
|
+
await deleteReactionOptimistically({ channel: this.ctx.channel, ...params });
|
|
12391
12505
|
}
|
|
12392
12506
|
};
|
|
12393
12507
|
|
|
@@ -12409,6 +12523,210 @@ var createMessageOperationsPersistence = ({
|
|
|
12409
12523
|
isQueued: (messageId, types) => isQueuedForReplay(channel.getClient(), messageId, types)
|
|
12410
12524
|
});
|
|
12411
12525
|
|
|
12526
|
+
// src/messageOperations/sendOrdering.ts
|
|
12527
|
+
var keepSendOrderWhilePendingUploadsAllowed = ({
|
|
12528
|
+
channelCid,
|
|
12529
|
+
composer,
|
|
12530
|
+
kind,
|
|
12531
|
+
request
|
|
12532
|
+
}) => (kind === "send" || kind === "retry") && composer.allowsPendingUploads ? withoutConcurrency(`stream-chat/send-message/${channelCid}`, request) : request();
|
|
12533
|
+
|
|
12534
|
+
// src/messageOperations/settlePendingAttachmentUploads.ts
|
|
12535
|
+
var applyUploadResult = (attachment, response) => {
|
|
12536
|
+
const enriched = { ...attachment };
|
|
12537
|
+
if (isLocalImageAttachment(enriched)) {
|
|
12538
|
+
enriched.image_url = response.file;
|
|
12539
|
+
} else {
|
|
12540
|
+
enriched.asset_url = response.file;
|
|
12541
|
+
}
|
|
12542
|
+
if (response.thumb_url) {
|
|
12543
|
+
enriched.thumb_url = response.thumb_url;
|
|
12544
|
+
}
|
|
12545
|
+
const { previewUri } = attachment.localMetadata;
|
|
12546
|
+
if (previewUri?.startsWith("blob:")) URL.revokeObjectURL?.(previewUri);
|
|
12547
|
+
const { localMetadata: _localMetadata, ...rest } = enriched;
|
|
12548
|
+
return rest;
|
|
12549
|
+
};
|
|
12550
|
+
var settlePendingAttachmentUploads = async ({
|
|
12551
|
+
attachments,
|
|
12552
|
+
channelCid,
|
|
12553
|
+
client
|
|
12554
|
+
}) => {
|
|
12555
|
+
const unsettledIndexes = attachments.reduce((indexes, attachment, index) => {
|
|
12556
|
+
if (isPendingUpload(attachment) || isFailedUpload(attachment)) indexes.push(index);
|
|
12557
|
+
return indexes;
|
|
12558
|
+
}, []);
|
|
12559
|
+
if (!unsettledIndexes.length) return { attachments };
|
|
12560
|
+
const settled2 = await Promise.allSettled(
|
|
12561
|
+
unsettledIndexes.map((index) => {
|
|
12562
|
+
const { file, id } = attachments[index].localMetadata;
|
|
12563
|
+
return client.uploadManager.upload({ channelCid, file, id });
|
|
12564
|
+
})
|
|
12565
|
+
);
|
|
12566
|
+
const nextAttachments = [...attachments];
|
|
12567
|
+
let failureReason;
|
|
12568
|
+
settled2.forEach((result, position) => {
|
|
12569
|
+
const index = unsettledIndexes[position];
|
|
12570
|
+
const attachment = nextAttachments[index];
|
|
12571
|
+
if (result.status === "fulfilled" && result.value) {
|
|
12572
|
+
nextAttachments[index] = applyUploadResult(attachment, result.value);
|
|
12573
|
+
return;
|
|
12574
|
+
}
|
|
12575
|
+
if (!failureReason) {
|
|
12576
|
+
failureReason = result.status === "rejected" ? result.reason : new Error("Attachment upload returned no result");
|
|
12577
|
+
}
|
|
12578
|
+
});
|
|
12579
|
+
return { attachments: nextAttachments, failureReason };
|
|
12580
|
+
};
|
|
12581
|
+
|
|
12582
|
+
// src/messageOperations/createMessageOperations.ts
|
|
12583
|
+
var createMessageOperations = (collection) => {
|
|
12584
|
+
const paginator = collection.messagePaginator;
|
|
12585
|
+
const { channel, parentMessageId } = paginator;
|
|
12586
|
+
return new MessageOperations({
|
|
12587
|
+
sequenceRequests: (kind, request) => keepSendOrderWhilePendingUploadsAllowed({
|
|
12588
|
+
channelCid: channel.cid,
|
|
12589
|
+
composer: collection.messageComposer,
|
|
12590
|
+
kind,
|
|
12591
|
+
request
|
|
12592
|
+
}),
|
|
12593
|
+
settlePendingUploads: (attachments) => settlePendingAttachmentUploads({
|
|
12594
|
+
attachments,
|
|
12595
|
+
// A reply uploads against the channel the thread belongs to - there is no thread-scoped
|
|
12596
|
+
// upload target.
|
|
12597
|
+
channelCid: channel.cid,
|
|
12598
|
+
client: channel.getClient()
|
|
12599
|
+
}),
|
|
12600
|
+
...createMessageOperationsPersistence({ channel }),
|
|
12601
|
+
channel,
|
|
12602
|
+
ingest: (m) => {
|
|
12603
|
+
const store = channel.getClient().messageStore;
|
|
12604
|
+
if (paginator.matchesFilter(m)) {
|
|
12605
|
+
paginator.ingestItem(m);
|
|
12606
|
+
} else if (store.has(m.id)) {
|
|
12607
|
+
store.upsert(m);
|
|
12608
|
+
}
|
|
12609
|
+
store.flushSubscribers(m.id);
|
|
12610
|
+
},
|
|
12611
|
+
// Mirrors `ingest`'s routing: a message this paginator does not hold can still be held by the
|
|
12612
|
+
// client-global store (a thread parent, a message displayed by another collection), and the
|
|
12613
|
+
// policy uses this both for its freshness comparison and to decide whether there is anything to
|
|
12614
|
+
// update optimistically at all. Reading only the paginator would make those two disagree.
|
|
12615
|
+
get: (id) => paginator.getItem(id) ?? channel.getClient().messageStore.get(id),
|
|
12616
|
+
remove: (id) => {
|
|
12617
|
+
const client = channel.getClient();
|
|
12618
|
+
const parentId = paginator.getItem(id)?.parent_id ?? client.messageStore.get(id)?.parent_id;
|
|
12619
|
+
channel.messagePaginator.removeItem({ id });
|
|
12620
|
+
channel.pinnedMessagesPaginator.removeItem({ id });
|
|
12621
|
+
if (parentId) {
|
|
12622
|
+
client.threads.threadsById[parentId]?.messagePaginator.removeItem({ id });
|
|
12623
|
+
}
|
|
12624
|
+
},
|
|
12625
|
+
...parentMessageId ? {
|
|
12626
|
+
normalizeOutgoingMessage: (m) => ({ ...m, parent_id: parentMessageId })
|
|
12627
|
+
} : {},
|
|
12628
|
+
handlers: () => {
|
|
12629
|
+
const { requestHandlers } = channel.configState.getLatestValue();
|
|
12630
|
+
const deleteMessageRequest = requestHandlers?.deleteMessageRequest;
|
|
12631
|
+
const sendMessageRequest = requestHandlers?.sendMessageRequest;
|
|
12632
|
+
const retrySendMessageRequest = requestHandlers?.retrySendMessageRequest;
|
|
12633
|
+
const updateMessageRequest = requestHandlers?.updateMessageRequest;
|
|
12634
|
+
return {
|
|
12635
|
+
delete: deleteMessageRequest ? (p) => deleteMessageRequest({
|
|
12636
|
+
localMessage: p.localMessage,
|
|
12637
|
+
options: p.options
|
|
12638
|
+
}) : void 0,
|
|
12639
|
+
send: sendMessageRequest ? (p) => sendMessageRequest({
|
|
12640
|
+
localMessage: p.localMessage,
|
|
12641
|
+
message: p.message,
|
|
12642
|
+
options: p.options
|
|
12643
|
+
}) : void 0,
|
|
12644
|
+
retry: retrySendMessageRequest ? (p) => retrySendMessageRequest({
|
|
12645
|
+
localMessage: p.localMessage,
|
|
12646
|
+
message: p.message,
|
|
12647
|
+
options: p.options
|
|
12648
|
+
}) : void 0,
|
|
12649
|
+
update: updateMessageRequest ? (p) => updateMessageRequest({
|
|
12650
|
+
localMessage: p.localMessage,
|
|
12651
|
+
options: p.options
|
|
12652
|
+
}) : void 0
|
|
12653
|
+
};
|
|
12654
|
+
},
|
|
12655
|
+
defaults: {
|
|
12656
|
+
delete: async (id, o) => {
|
|
12657
|
+
const result = await channel.getClient().deleteMessage({ id, ...o });
|
|
12658
|
+
return { message: result.message };
|
|
12659
|
+
},
|
|
12660
|
+
send: async (m, o) => {
|
|
12661
|
+
const result = await channel.sendMessage({ message: m, ...o });
|
|
12662
|
+
return { message: result.message };
|
|
12663
|
+
},
|
|
12664
|
+
update: async (m, o) => {
|
|
12665
|
+
const result = await channel.getClient().updateMessage({
|
|
12666
|
+
id: m.id,
|
|
12667
|
+
message: localMessageToNewMessagePayload(m),
|
|
12668
|
+
...o
|
|
12669
|
+
});
|
|
12670
|
+
return { message: result.message };
|
|
12671
|
+
}
|
|
12672
|
+
}
|
|
12673
|
+
});
|
|
12674
|
+
};
|
|
12675
|
+
|
|
12676
|
+
// src/messageOperations/WithMessageOperations.ts
|
|
12677
|
+
var WithMessageOperations = (Base) => {
|
|
12678
|
+
class WithMessageOperationsMixin extends Base {
|
|
12679
|
+
/** Sends a message with optimistic local state update. */
|
|
12680
|
+
async sendMessageWithLocalUpdate(params) {
|
|
12681
|
+
await this.messageOperations.send(params);
|
|
12682
|
+
}
|
|
12683
|
+
/** Retry sending a failed message. */
|
|
12684
|
+
async retrySendMessageWithLocalUpdate(params) {
|
|
12685
|
+
await this.messageOperations.retrySendWithLocalUpdate(params);
|
|
12686
|
+
}
|
|
12687
|
+
/** Updates a message with optimistic local state update. */
|
|
12688
|
+
async updateMessageWithLocalUpdate(params) {
|
|
12689
|
+
await this.messageOperations.update(params);
|
|
12690
|
+
}
|
|
12691
|
+
/** Deletes a message with local state update. */
|
|
12692
|
+
async deleteMessageWithLocalUpdate(params) {
|
|
12693
|
+
await this.messageOperations.delete(params);
|
|
12694
|
+
}
|
|
12695
|
+
/** Adds a reaction with an optimistic local state update. */
|
|
12696
|
+
async addReactionWithLocalUpdate(params) {
|
|
12697
|
+
await this.messageOperations.addReactionWithLocalUpdate(params);
|
|
12698
|
+
}
|
|
12699
|
+
/** Removes the current user's reaction with an optimistic local state update. */
|
|
12700
|
+
async deleteReactionWithLocalUpdate(params) {
|
|
12701
|
+
await this.messageOperations.deleteReactionWithLocalUpdate(params);
|
|
12702
|
+
}
|
|
12703
|
+
}
|
|
12704
|
+
return WithMessageOperationsMixin;
|
|
12705
|
+
};
|
|
12706
|
+
|
|
12707
|
+
// src/messageOperations/reflectReactionEvent.ts
|
|
12708
|
+
var reflectReactionEvent = (client, {
|
|
12709
|
+
enforceUnique = false,
|
|
12710
|
+
message,
|
|
12711
|
+
reaction,
|
|
12712
|
+
removed = false
|
|
12713
|
+
}) => {
|
|
12714
|
+
const store = client.messageStore;
|
|
12715
|
+
if (!store.has(message.id)) return;
|
|
12716
|
+
const formatted = formatMessage(message);
|
|
12717
|
+
const existing = store.get(message.id);
|
|
12718
|
+
store.upsert({
|
|
12719
|
+
...formatted,
|
|
12720
|
+
own_reactions: computeOwnReactions({
|
|
12721
|
+
current: existing?.own_reactions ?? formatted.own_reactions ?? [],
|
|
12722
|
+
enforceUnique,
|
|
12723
|
+
reaction,
|
|
12724
|
+
removed,
|
|
12725
|
+
userId: client.userId
|
|
12726
|
+
})
|
|
12727
|
+
});
|
|
12728
|
+
};
|
|
12729
|
+
|
|
12412
12730
|
// src/configuration/utils/declarativeSlices.ts
|
|
12413
12731
|
var mergeDeclarativeSlice = (general, specific) => {
|
|
12414
12732
|
if (!general) return specific;
|
|
@@ -12434,7 +12752,7 @@ var DEFAULT_PAGE_LIMIT = 50;
|
|
|
12434
12752
|
var DEFAULT_SORT2 = [{ field: "created_at", direction: -1 }];
|
|
12435
12753
|
var DEFAULT_ITEM_ORDER = [{ field: "created_at", direction: 1 }];
|
|
12436
12754
|
var DEFAULT_THREAD_CONFIG = deepFreezeConfig({});
|
|
12437
|
-
var Thread = class extends WithSubscriptions {
|
|
12755
|
+
var Thread = class extends WithMessageOperations(WithSubscriptions) {
|
|
12438
12756
|
constructor({
|
|
12439
12757
|
client,
|
|
12440
12758
|
threadData,
|
|
@@ -12786,24 +13104,7 @@ var Thread = class extends WithSubscriptions {
|
|
|
12786
13104
|
const unsubscribeReactions = reactionTypes.map(
|
|
12787
13105
|
(eventType) => this.client.on(eventType, (event) => {
|
|
12788
13106
|
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
|
-
}
|
|
13107
|
+
const { message } = event;
|
|
12807
13108
|
this.messagePaginator.reflectQuotedMessageUpdate(formatMessage(message));
|
|
12808
13109
|
}).unsubscribe
|
|
12809
13110
|
);
|
|
@@ -12869,6 +13170,7 @@ var Thread = class extends WithSubscriptions {
|
|
|
12869
13170
|
} else if (this.failedRepliesMap.has(message.id)) {
|
|
12870
13171
|
this.failedRepliesMap.delete(message.id);
|
|
12871
13172
|
}
|
|
13173
|
+
if (this.messagePaginator.getItem(formattedMessage.id) === formattedMessage) return;
|
|
12872
13174
|
this.messagePaginator.ingestItem(formattedMessage);
|
|
12873
13175
|
};
|
|
12874
13176
|
// todo: can be removed with the next breaking change and use MessagePaginator only
|
|
@@ -13008,77 +13310,7 @@ var Thread = class extends WithSubscriptions {
|
|
|
13008
13310
|
composition: threadData?.draft ?? draft,
|
|
13009
13311
|
compositionContext: this
|
|
13010
13312
|
});
|
|
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
|
-
});
|
|
13313
|
+
this.messageOperations = createMessageOperations(this);
|
|
13082
13314
|
this.initializeConfig(declarativeConfig);
|
|
13083
13315
|
}
|
|
13084
13316
|
/**
|
|
@@ -13133,90 +13365,6 @@ var Thread = class extends WithSubscriptions {
|
|
|
13133
13365
|
get ownUnreadCount() {
|
|
13134
13366
|
return ownUnreadCountSelector(this.client.userId)(this.state.getLatestValue());
|
|
13135
13367
|
}
|
|
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
13368
|
};
|
|
13221
13369
|
var normalizeThreadParticipants = (participants, channelCid) => {
|
|
13222
13370
|
if (!participants) return void 0;
|
|
@@ -13808,6 +13956,99 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
|
|
|
13808
13956
|
throw error;
|
|
13809
13957
|
}
|
|
13810
13958
|
};
|
|
13959
|
+
/**
|
|
13960
|
+
* Lets go of what the submitted message took with it.
|
|
13961
|
+
*
|
|
13962
|
+
* A poll message carries no text or attachments of its own, so the composer keeps whatever else
|
|
13963
|
+
* was drafted and releases only the poll. Everything else is a full clear.
|
|
13964
|
+
*/
|
|
13965
|
+
this.releaseSubmittedComposition = () => {
|
|
13966
|
+
if (this.retainsCompositionOnSubmit) {
|
|
13967
|
+
this.state.partialNext({
|
|
13968
|
+
id: _MessageComposer.generateId(),
|
|
13969
|
+
// Unconditional, and safe whatever decided to retain: a poll that exists is always a poll
|
|
13970
|
+
// that just went out, because `createMessageComposerStateCompositionMiddleware` copies a
|
|
13971
|
+
// set `pollId` onto every composition - threads and edits included, where the poll-only
|
|
13972
|
+
// middleware forwards. There is no state where this detaches one the submission did not
|
|
13973
|
+
// carry.
|
|
13974
|
+
//
|
|
13975
|
+
// Detaching it is also not optional. While `pollId` is set, `hasSendableData` counts it as
|
|
13976
|
+
// content on its own and `createPollOnlyCompositionMiddleware` turns every composition
|
|
13977
|
+
// into a poll-only message, so a composer left bound to a spent poll nullifies whatever is
|
|
13978
|
+
// typed next and re-sends the same poll.
|
|
13979
|
+
pollId: null
|
|
13980
|
+
});
|
|
13981
|
+
return;
|
|
13982
|
+
}
|
|
13983
|
+
this.clear();
|
|
13984
|
+
};
|
|
13985
|
+
this.reportSubmitFailure = (type, message, error) => {
|
|
13986
|
+
this.client.notifications.addError({
|
|
13987
|
+
message,
|
|
13988
|
+
origin: {
|
|
13989
|
+
emitter: "MessageComposer",
|
|
13990
|
+
context: { composer: this }
|
|
13991
|
+
},
|
|
13992
|
+
options: {
|
|
13993
|
+
type,
|
|
13994
|
+
metadata: { reason: error.message },
|
|
13995
|
+
originalError: error instanceof Error ? error : void 0
|
|
13996
|
+
}
|
|
13997
|
+
});
|
|
13998
|
+
};
|
|
13999
|
+
/**
|
|
14000
|
+
* Composes the message and sends it, releasing the composer as it goes.
|
|
14001
|
+
*
|
|
14002
|
+
* Never rejects - see {@link MessageComposerSubmitResult} for what each outcome means and where
|
|
14003
|
+
* the content ends up. A failure is reported through `client.notifications`, and the message
|
|
14004
|
+
* stays in the list marked `failed`, which is where the user retries it. Callers rendering their
|
|
14005
|
+
* own post-send feedback have to check the result.
|
|
14006
|
+
*/
|
|
14007
|
+
this.send = async () => {
|
|
14008
|
+
const composition = await this.compose();
|
|
14009
|
+
if (!composition?.message) return "nothing-to-send";
|
|
14010
|
+
const { localMessage, message, sendOptions } = composition;
|
|
14011
|
+
this.releaseSubmittedComposition();
|
|
14012
|
+
try {
|
|
14013
|
+
await this.defaultSubmitTarget.sendMessageWithLocalUpdate({
|
|
14014
|
+
localMessage,
|
|
14015
|
+
message,
|
|
14016
|
+
options: sendOptions
|
|
14017
|
+
});
|
|
14018
|
+
return "sent";
|
|
14019
|
+
} catch (error) {
|
|
14020
|
+
this.reportSubmitFailure(
|
|
14021
|
+
CORE_NOTIFICATION_TYPE.messageSendFailed,
|
|
14022
|
+
"Send message request failed",
|
|
14023
|
+
error
|
|
14024
|
+
);
|
|
14025
|
+
return "failed";
|
|
14026
|
+
}
|
|
14027
|
+
};
|
|
14028
|
+
/**
|
|
14029
|
+
* Composes the edit and saves it, on the same terms as {@link send} - including releasing the
|
|
14030
|
+
* composer before the request rather than after it.
|
|
14031
|
+
*/
|
|
14032
|
+
this.update = async () => {
|
|
14033
|
+
const composition = await this.compose();
|
|
14034
|
+
if (!composition?.message) return "nothing-to-send";
|
|
14035
|
+
const { localMessage, sendOptions } = composition;
|
|
14036
|
+
this.releaseSubmittedComposition();
|
|
14037
|
+
try {
|
|
14038
|
+
await this.defaultSubmitTarget.updateMessageWithLocalUpdate({
|
|
14039
|
+
localMessage,
|
|
14040
|
+
options: sendOptions
|
|
14041
|
+
});
|
|
14042
|
+
return "sent";
|
|
14043
|
+
} catch (error) {
|
|
14044
|
+
this.reportSubmitFailure(
|
|
14045
|
+
CORE_NOTIFICATION_TYPE.messageUpdateFailed,
|
|
14046
|
+
"Edit message request failed",
|
|
14047
|
+
error
|
|
14048
|
+
);
|
|
14049
|
+
return "failed";
|
|
14050
|
+
}
|
|
14051
|
+
};
|
|
13811
14052
|
this.sendLocation = async () => {
|
|
13812
14053
|
const location = this.locationComposer.validLocation;
|
|
13813
14054
|
if (this.threadId || !location) return;
|
|
@@ -13900,9 +14141,6 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
|
|
|
13900
14141
|
if (compositionContext instanceof Thread) {
|
|
13901
14142
|
return "thread";
|
|
13902
14143
|
}
|
|
13903
|
-
if (typeof compositionContext.legacyThreadId === "string") {
|
|
13904
|
-
return "legacy_thread";
|
|
13905
|
-
}
|
|
13906
14144
|
return "message";
|
|
13907
14145
|
}
|
|
13908
14146
|
static constructTag(compositionContext) {
|
|
@@ -13938,9 +14176,6 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
|
|
|
13938
14176
|
if (this.compositionContext instanceof Thread) {
|
|
13939
14177
|
return this.compositionContext.id;
|
|
13940
14178
|
}
|
|
13941
|
-
if (typeof this.compositionContext.legacyThreadId === "string") {
|
|
13942
|
-
return this.compositionContext.legacyThreadId;
|
|
13943
|
-
}
|
|
13944
14179
|
if (typeof this.compositionContext.parent_id === "string") {
|
|
13945
14180
|
return this.compositionContext.parent_id;
|
|
13946
14181
|
}
|
|
@@ -13972,27 +14207,41 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
|
|
|
13972
14207
|
return !currentCommand || this.validateCommandSendability(currentCommand).ready;
|
|
13973
14208
|
}
|
|
13974
14209
|
/**
|
|
13975
|
-
* Whether
|
|
13976
|
-
*
|
|
14210
|
+
* Whether a message may be sent while its attachments are still uploading — see
|
|
14211
|
+
* {@link AttachmentManagerConfig.pendingUploadsEnabled}.
|
|
14212
|
+
*/
|
|
14213
|
+
get allowsPendingUploads() {
|
|
14214
|
+
return this.attachmentManager.config.pendingUploadsEnabled;
|
|
14215
|
+
}
|
|
14216
|
+
/**
|
|
14217
|
+
* Where a composed message goes: the thread when this composer belongs to one, the channel
|
|
14218
|
+
* otherwise.
|
|
13977
14219
|
*
|
|
13978
|
-
*
|
|
13979
|
-
*
|
|
13980
|
-
*
|
|
14220
|
+
* Thread replies live in `thread.messagePaginator`, which is independent of the channel's, so
|
|
14221
|
+
* dispatching to the wrong one leaves the reply invisible where it was written. An edit composer
|
|
14222
|
+
* is built from the message rather than from its thread, so it can only find the thread when the
|
|
14223
|
+
* manager has it loaded. No composer reachable from a UI SDK is built that way today.
|
|
14224
|
+
*/
|
|
14225
|
+
get defaultSubmitTarget() {
|
|
14226
|
+
if (this.compositionContext instanceof Thread) return this.compositionContext;
|
|
14227
|
+
const { threadId } = this;
|
|
14228
|
+
return threadId && this.client.threads.threadsById[threadId] || this.channel;
|
|
14229
|
+
}
|
|
14230
|
+
/**
|
|
14231
|
+
* Whether submitting keeps the composer's contents rather than clearing them - see
|
|
14232
|
+
* {@link MessageComposerConfig.retainCompositionOnSubmit}.
|
|
13981
14233
|
*
|
|
13982
|
-
*
|
|
13983
|
-
*
|
|
13984
|
-
* together with the middleware declaration it reads, is replaced by that config field.
|
|
14234
|
+
* Read by the attachments composition step too: content the composer keeps must not also ride
|
|
14235
|
+
* along on the submitted message, or one `localMetadata.id` ends up owned in two places.
|
|
13985
14236
|
*/
|
|
13986
|
-
get
|
|
13987
|
-
return this.
|
|
13988
|
-
(middleware) => middleware.allowsPendingUploads
|
|
13989
|
-
);
|
|
14237
|
+
get retainsCompositionOnSubmit() {
|
|
14238
|
+
return this.config.retainCompositionOnSubmit(this);
|
|
13990
14239
|
}
|
|
13991
14240
|
get hasSendableData() {
|
|
13992
14241
|
if (!this.isCommandSendable) return false;
|
|
13993
14242
|
if (this.allowsPendingUploads) {
|
|
13994
14243
|
const hasSendableAttachment = this.attachmentManager.attachments.some(
|
|
13995
|
-
(attachment) => isFinishedUpload(attachment) || isPendingUpload(attachment)
|
|
14244
|
+
(attachment) => isFinishedUpload(attachment) || isPendingUpload(attachment) || isFailedUpload(attachment)
|
|
13996
14245
|
);
|
|
13997
14246
|
return !!(!this.textComposer.textIsEmpty || hasSendableAttachment || this.pollId || this.locationComposer.validLocation);
|
|
13998
14247
|
}
|
|
@@ -14462,7 +14711,9 @@ var MessageReceiptsTracker = class extends WithSubscriptions {
|
|
|
14462
14711
|
this.snapshotStore = new import_state_store20.StateStore({
|
|
14463
14712
|
revision: 0,
|
|
14464
14713
|
readersByMessageId: {},
|
|
14465
|
-
deliveredByMessageId: {}
|
|
14714
|
+
deliveredByMessageId: {},
|
|
14715
|
+
lastDeliveredRefByOthers: null,
|
|
14716
|
+
lastReadRefByOthers: null
|
|
14466
14717
|
});
|
|
14467
14718
|
this.registerSubscriptions = () => {
|
|
14468
14719
|
this.incrementRefCount();
|
|
@@ -14658,6 +14909,21 @@ var MessageReceiptsTracker = class extends WithSubscriptions {
|
|
|
14658
14909
|
);
|
|
14659
14910
|
return this.deliveredSorted.slice(pos).map((x) => x.user);
|
|
14660
14911
|
}
|
|
14912
|
+
/**
|
|
14913
|
+
* Whether at least one other member has this message delivered.
|
|
14914
|
+
*
|
|
14915
|
+
* Reads the single furthest cursor rather than asking who is past this message, so the answer is
|
|
14916
|
+
* monotonic by construction: an older message can never report less than a newer one.
|
|
14917
|
+
*/
|
|
14918
|
+
isDeliveredToOthers(msgRef) {
|
|
14919
|
+
const ref = this.snapshotStore.getLatestValue().lastDeliveredRefByOthers;
|
|
14920
|
+
return !!ref && compareRefsAsc(msgRef, ref) <= 0;
|
|
14921
|
+
}
|
|
14922
|
+
/** As {@link isDeliveredToOthers}, for reads. */
|
|
14923
|
+
isReadByOthers(msgRef) {
|
|
14924
|
+
const ref = this.snapshotStore.getLatestValue().lastReadRefByOthers;
|
|
14925
|
+
return !!ref && compareRefsAsc(msgRef, ref) <= 0;
|
|
14926
|
+
}
|
|
14661
14927
|
/** Users who delivered but have NOT read. */
|
|
14662
14928
|
deliveredNotReadForMessage(msgRef) {
|
|
14663
14929
|
const pos = findIndex(
|
|
@@ -14833,6 +15099,19 @@ var MessageReceiptsTracker = class extends WithSubscriptions {
|
|
|
14833
15099
|
[]
|
|
14834
15100
|
);
|
|
14835
15101
|
}
|
|
15102
|
+
/**
|
|
15103
|
+
* The furthest ref in an ascending-by-cursor array, ignoring this user's own progress - your own
|
|
15104
|
+
* cursor says nothing about whether anyone received the message.
|
|
15105
|
+
*/
|
|
15106
|
+
furthestRefByOthers(sorted, refOf) {
|
|
15107
|
+
const ownUserId = this.channel.getClient().userID;
|
|
15108
|
+
for (let i = sorted.length - 1; i >= 0; i -= 1) {
|
|
15109
|
+
if (sorted[i].user.id === ownUserId) continue;
|
|
15110
|
+
const ref = refOf(sorted[i]);
|
|
15111
|
+
return Number.isFinite(ref.timestamp) ? ref : null;
|
|
15112
|
+
}
|
|
15113
|
+
return null;
|
|
15114
|
+
}
|
|
14836
15115
|
emitSnapshot() {
|
|
14837
15116
|
const readersByMessageId = this.groupUsersByLastReadMessage();
|
|
14838
15117
|
const deliveredByMessageId = this.groupUsersByLastDeliveredMessage();
|
|
@@ -14840,7 +15119,15 @@ var MessageReceiptsTracker = class extends WithSubscriptions {
|
|
|
14840
15119
|
this.snapshotStore.next({
|
|
14841
15120
|
revision: currentSnapshot.revision + 1,
|
|
14842
15121
|
readersByMessageId,
|
|
14843
|
-
deliveredByMessageId
|
|
15122
|
+
deliveredByMessageId,
|
|
15123
|
+
lastDeliveredRefByOthers: this.furthestRefByOthers(
|
|
15124
|
+
this.deliveredSorted,
|
|
15125
|
+
({ lastDeliveredRef }) => lastDeliveredRef
|
|
15126
|
+
),
|
|
15127
|
+
lastReadRefByOthers: this.furthestRefByOthers(
|
|
15128
|
+
this.readSorted,
|
|
15129
|
+
({ lastReadRef }) => lastReadRef
|
|
15130
|
+
)
|
|
14844
15131
|
});
|
|
14845
15132
|
}
|
|
14846
15133
|
};
|
|
@@ -15164,7 +15451,7 @@ var DEFAULT_CHANNEL_CONFIG = deepFreezeConfig({
|
|
|
15164
15451
|
typingEvents: { enabled: true },
|
|
15165
15452
|
userMessageReminders: { enabled: true }
|
|
15166
15453
|
});
|
|
15167
|
-
var Channel = class extends ChannelApi {
|
|
15454
|
+
var Channel = class extends WithMessageOperations(ChannelApi) {
|
|
15168
15455
|
/**
|
|
15169
15456
|
* Creates a `Channel` instance bound to the given chat client.
|
|
15170
15457
|
*
|
|
@@ -15277,78 +15564,7 @@ var Channel = class extends ChannelApi {
|
|
|
15277
15564
|
this.messageReceiptsTracker.registerSubscriptions();
|
|
15278
15565
|
this.cooldownTimer = new CooldownTimer({ channel: this });
|
|
15279
15566
|
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
|
-
});
|
|
15567
|
+
this.messageOperations = createMessageOperations(this);
|
|
15352
15568
|
this._syncMuteStatus();
|
|
15353
15569
|
this.configController = new ConfigController({
|
|
15354
15570
|
defaults: DEFAULT_CHANNEL_CONFIG,
|
|
@@ -15529,74 +15745,16 @@ var Channel = class extends ChannelApi {
|
|
|
15529
15745
|
});
|
|
15530
15746
|
}
|
|
15531
15747
|
/**
|
|
15532
|
-
* Sends a message with optimistic local state update.
|
|
15748
|
+
* Sends a message with optimistic local state update, stopping the typing indicator first.
|
|
15749
|
+
*
|
|
15750
|
+
* The only collection-specific part of the optimistic API: typing is a channel concern and a
|
|
15751
|
+
* `Thread` has no counterpart, so it is an override rather than part of the mixin.
|
|
15533
15752
|
*/
|
|
15534
15753
|
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 });
|
|
15754
|
+
if (this.messageComposer.config.text.publishTypingEvents) {
|
|
15755
|
+
this.stopTyping().catch(() => void 0);
|
|
15756
|
+
}
|
|
15757
|
+
await super.sendMessageWithLocalUpdate(params);
|
|
15600
15758
|
}
|
|
15601
15759
|
/**
|
|
15602
15760
|
* Upload a file to this channel's file endpoint (multipart).
|
|
@@ -16927,8 +17085,8 @@ var Channel = class extends ChannelApi {
|
|
|
16927
17085
|
this.messagePaginator.removeItem({ id: event.message.id });
|
|
16928
17086
|
this.pinnedMessagesPaginator.removeItem({ id: event.message.id });
|
|
16929
17087
|
} else {
|
|
16930
|
-
this.
|
|
16931
|
-
|
|
17088
|
+
const store = this.getClient().messageStore;
|
|
17089
|
+
if (store.has(event.message.id)) store.upsert(formattedMessage);
|
|
16932
17090
|
}
|
|
16933
17091
|
}
|
|
16934
17092
|
this.messagePaginator.reflectQuotedMessageUpdate(formattedMessage);
|
|
@@ -16957,8 +17115,9 @@ var Channel = class extends ChannelApi {
|
|
|
16957
17115
|
const ownMessage = event.user?.id === client.user?.id;
|
|
16958
17116
|
const isThreadMessage = event.message.parent_id && !event.message.show_in_channel;
|
|
16959
17117
|
if (!isThreadMessage) {
|
|
16960
|
-
|
|
16961
|
-
this.
|
|
17118
|
+
const formattedMessage = formatMessage(event.message);
|
|
17119
|
+
this.messagePaginator.ingestItem(formattedMessage);
|
|
17120
|
+
this.pinnedMessagesPaginator.ingestItem(formattedMessage);
|
|
16962
17121
|
}
|
|
16963
17122
|
const preventUnreadCountUpdate = ownMessage || isThreadMessage;
|
|
16964
17123
|
if (preventUnreadCountUpdate) break;
|
|
@@ -17038,8 +17197,9 @@ var Channel = class extends ChannelApi {
|
|
|
17038
17197
|
this.pinnedMessagesPaginator.clearStateAndCache();
|
|
17039
17198
|
}
|
|
17040
17199
|
if (event.message) {
|
|
17041
|
-
|
|
17042
|
-
this.
|
|
17200
|
+
const formattedMessage = formatMessage(event.message);
|
|
17201
|
+
this.messagePaginator.ingestItem(formattedMessage);
|
|
17202
|
+
this.pinnedMessagesPaginator.ingestItem(formattedMessage);
|
|
17043
17203
|
}
|
|
17044
17204
|
break;
|
|
17045
17205
|
}
|
|
@@ -17120,49 +17280,16 @@ var Channel = class extends ChannelApi {
|
|
|
17120
17280
|
}
|
|
17121
17281
|
break;
|
|
17122
17282
|
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
17283
|
case "reaction.updated":
|
|
17284
|
+
case "reaction.deleted":
|
|
17152
17285
|
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
|
-
}
|
|
17286
|
+
reflectReactionEvent(this.getClient(), {
|
|
17287
|
+
// reaction.updated is only sent when enforce_unique is set
|
|
17288
|
+
enforceUnique: event.type === "reaction.updated",
|
|
17289
|
+
message: event.message,
|
|
17290
|
+
reaction: event.reaction,
|
|
17291
|
+
removed: event.type === "reaction.deleted"
|
|
17292
|
+
});
|
|
17166
17293
|
}
|
|
17167
17294
|
break;
|
|
17168
17295
|
case "channel.hidden": {
|
|
@@ -24277,7 +24404,7 @@ var StreamChat = class _StreamChat extends ChatApi {
|
|
|
24277
24404
|
return this.userAgent;
|
|
24278
24405
|
}
|
|
24279
24406
|
if (!this.cachedUserAgent) {
|
|
24280
|
-
const version = "10.0.0-rc.
|
|
24407
|
+
const version = "10.0.0-rc.13";
|
|
24281
24408
|
const { name: sdkName, version: sdkVersion } = this.sdkIdentifier ?? {};
|
|
24282
24409
|
const { name: appName, version: appVersion } = this.appIdentifier ?? {};
|
|
24283
24410
|
const { os, model: deviceModel } = this.deviceIdentifier ?? {};
|
|
@@ -25473,6 +25600,11 @@ var ATTACHMENTS_FIELDS = {
|
|
|
25473
25600
|
kind: "value",
|
|
25474
25601
|
type: "number"
|
|
25475
25602
|
},
|
|
25603
|
+
pendingUploadsEnabled: {
|
|
25604
|
+
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.",
|
|
25605
|
+
kind: "value",
|
|
25606
|
+
type: "boolean"
|
|
25607
|
+
},
|
|
25476
25608
|
trackUploadProgress: {
|
|
25477
25609
|
description: "Reports upload progress on each attachment. Turning it off skips the progress bookkeeping.",
|
|
25478
25610
|
kind: "value",
|
|
@@ -25593,6 +25725,11 @@ var MESSAGE_COMPOSER_FIELDS = {
|
|
|
25593
25725
|
fields: POLLS_FIELDS,
|
|
25594
25726
|
kind: "group"
|
|
25595
25727
|
},
|
|
25728
|
+
retainCompositionOnSubmit: {
|
|
25729
|
+
description: "Decides whether submitting keeps the composer's contents instead of clearing them. Retains for a poll message by default.",
|
|
25730
|
+
kind: "value",
|
|
25731
|
+
type: "function"
|
|
25732
|
+
},
|
|
25596
25733
|
text: { description: "The text input itself.", fields: TEXT_FIELDS, kind: "group" }
|
|
25597
25734
|
};
|
|
25598
25735
|
var MESSAGE_DELIVERY_FIELDS = {
|