@amityco/ts-sdk 7.29.0 → 7.29.1-0cb2e19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/@types/core/errors.d.ts +2 -0
  2. package/dist/@types/core/errors.d.ts.map +1 -1
  3. package/dist/@types/domains/channel.d.ts +13 -1
  4. package/dist/@types/domains/channel.d.ts.map +1 -1
  5. package/dist/@types/domains/content.d.ts +11 -0
  6. package/dist/@types/domains/content.d.ts.map +1 -1
  7. package/dist/@types/domains/user.d.ts +31 -0
  8. package/dist/@types/domains/user.d.ts.map +1 -1
  9. package/dist/channelRepository/observers/getChannels/ChannelLiveCollectionController.d.ts.map +1 -1
  10. package/dist/channelRepository/observers/searchChannels/SearchChannelLiveCollectionController.d.ts.map +1 -1
  11. package/dist/channelRepository/observers/tests/integrations/getChannelsSortByLastMessage.integration.test.d.ts +2 -0
  12. package/dist/channelRepository/observers/tests/integrations/getChannelsSortByLastMessage.integration.test.d.ts.map +1 -0
  13. package/dist/channelRepository/utils/channelActivityOnLoginHandler.d.ts +16 -0
  14. package/dist/channelRepository/utils/channelActivityOnLoginHandler.d.ts.map +1 -0
  15. package/dist/channelRepository/utils/tests/integrations/channelActivity.integration.test.d.ts +2 -0
  16. package/dist/channelRepository/utils/tests/integrations/channelActivity.integration.test.d.ts.map +1 -0
  17. package/dist/channelRepository/utils/tests/updateChannelActivityTimestamps.test.d.ts +2 -0
  18. package/dist/channelRepository/utils/tests/updateChannelActivityTimestamps.test.d.ts.map +1 -0
  19. package/dist/channelRepository/utils/updateChannelActivityTimestamps.d.ts +42 -0
  20. package/dist/channelRepository/utils/updateChannelActivityTimestamps.d.ts.map +1 -0
  21. package/dist/client/api/resumeSession.d.ts.map +1 -1
  22. package/dist/client/api/setupLoginSubscriptions.d.ts.map +1 -1
  23. package/dist/commentRepository/api/flagComment.d.ts +72 -5
  24. package/dist/commentRepository/api/flagComment.d.ts.map +1 -1
  25. package/dist/commentRepository/internalApi/flagCommentWithOptions.d.ts +13 -0
  26. package/dist/commentRepository/internalApi/flagCommentWithOptions.d.ts.map +1 -0
  27. package/dist/commentRepository/internalApi/flagCommentWithReasonString.d.ts +17 -0
  28. package/dist/commentRepository/internalApi/flagCommentWithReasonString.d.ts.map +1 -0
  29. package/dist/commentRepository/internalApi/index.d.ts +2 -0
  30. package/dist/commentRepository/internalApi/index.d.ts.map +1 -1
  31. package/dist/commentRepository/internalApi/tests/integration/flagCommentWithOptions.integration.test.d.ts +2 -0
  32. package/dist/commentRepository/internalApi/tests/integration/flagCommentWithOptions.integration.test.d.ts.map +1 -0
  33. package/dist/commentRepository/internalApi/tests/integration/flagCommentWithReasonString.integration.test.d.ts +2 -0
  34. package/dist/commentRepository/internalApi/tests/integration/flagCommentWithReasonString.integration.test.d.ts.map +1 -0
  35. package/dist/core/microtasks.d.ts.map +1 -1
  36. package/dist/core/query/sorting.d.ts +10 -0
  37. package/dist/core/query/sorting.d.ts.map +1 -1
  38. package/dist/core/query/tests/sorting.test.d.ts +2 -0
  39. package/dist/core/query/tests/sorting.test.d.ts.map +1 -0
  40. package/dist/core/tests/microtasks.test.d.ts +2 -0
  41. package/dist/core/tests/microtasks.test.d.ts.map +1 -0
  42. package/dist/core/transports/tests/integration/serverErrorCodes.integration.test.d.ts +2 -0
  43. package/dist/core/transports/tests/integration/serverErrorCodes.integration.test.d.ts.map +1 -0
  44. package/dist/index.cjs.js +630 -68
  45. package/dist/index.esm.js +630 -69
  46. package/dist/index.umd.js +2 -2
  47. package/dist/messagePreview/utils/getChannelMessagePreviewWithUser.d.ts +1 -0
  48. package/dist/messagePreview/utils/getChannelMessagePreviewWithUser.d.ts.map +1 -1
  49. package/dist/messageRepository/api/createMessage.d.ts.map +1 -1
  50. package/dist/messageRepository/api/flagMessage.d.ts +73 -6
  51. package/dist/messageRepository/api/flagMessage.d.ts.map +1 -1
  52. package/dist/messageRepository/api/tests/integration/flagMessage.overloads.integration.test.d.ts +2 -0
  53. package/dist/messageRepository/api/tests/integration/flagMessage.overloads.integration.test.d.ts.map +1 -0
  54. package/dist/messageRepository/internalApi/flagMessageWithOptions.d.ts +13 -0
  55. package/dist/messageRepository/internalApi/flagMessageWithOptions.d.ts.map +1 -0
  56. package/dist/messageRepository/internalApi/flagMessageWithReasonString.d.ts +18 -0
  57. package/dist/messageRepository/internalApi/flagMessageWithReasonString.d.ts.map +1 -0
  58. package/dist/messageRepository/internalApi/tests/integration/flagMessageWithOptions.integration.test.d.ts +2 -0
  59. package/dist/messageRepository/internalApi/tests/integration/flagMessageWithOptions.integration.test.d.ts.map +1 -0
  60. package/dist/messageRepository/internalApi/tests/integration/flagMessageWithReasonString.integration.test.d.ts +2 -0
  61. package/dist/messageRepository/internalApi/tests/integration/flagMessageWithReasonString.integration.test.d.ts.map +1 -0
  62. package/dist/postRepository/api/flagPost.d.ts +73 -6
  63. package/dist/postRepository/api/flagPost.d.ts.map +1 -1
  64. package/dist/postRepository/api/tests/integration/flagPost.overloads.integration.test.d.ts +2 -0
  65. package/dist/postRepository/api/tests/integration/flagPost.overloads.integration.test.d.ts.map +1 -0
  66. package/dist/postRepository/internalApi/flagPostWithOptions.d.ts +13 -0
  67. package/dist/postRepository/internalApi/flagPostWithOptions.d.ts.map +1 -0
  68. package/dist/postRepository/internalApi/flagPostWithReasonString.d.ts +17 -0
  69. package/dist/postRepository/internalApi/flagPostWithReasonString.d.ts.map +1 -0
  70. package/dist/postRepository/internalApi/index.d.ts +2 -0
  71. package/dist/postRepository/internalApi/index.d.ts.map +1 -1
  72. package/dist/postRepository/internalApi/tests/integration/flagPostWithOptions.integration.test.d.ts +2 -0
  73. package/dist/postRepository/internalApi/tests/integration/flagPostWithOptions.integration.test.d.ts.map +1 -0
  74. package/dist/postRepository/internalApi/tests/integration/flagPostWithReasonString.integration.test.d.ts +2 -0
  75. package/dist/postRepository/internalApi/tests/integration/flagPostWithReasonString.integration.test.d.ts.map +1 -0
  76. package/dist/userRepository/api/flagUser.d.ts +50 -9
  77. package/dist/userRepository/api/flagUser.d.ts.map +1 -1
  78. package/dist/userRepository/api/flagUserBehavior.d.ts +29 -0
  79. package/dist/userRepository/api/flagUserBehavior.d.ts.map +1 -0
  80. package/dist/userRepository/api/flagUserProfile.d.ts +29 -0
  81. package/dist/userRepository/api/flagUserProfile.d.ts.map +1 -0
  82. package/dist/userRepository/api/index.d.ts +2 -0
  83. package/dist/userRepository/api/index.d.ts.map +1 -1
  84. package/dist/userRepository/api/tests/integration/flagUser.overloads.integration.test.d.ts +2 -0
  85. package/dist/userRepository/api/tests/integration/flagUser.overloads.integration.test.d.ts.map +1 -0
  86. package/dist/userRepository/api/tests/integration/flagUserBehavior.integration.test.d.ts +2 -0
  87. package/dist/userRepository/api/tests/integration/flagUserBehavior.integration.test.d.ts.map +1 -0
  88. package/dist/userRepository/api/tests/integration/flagUserProfile.integration.test.d.ts +2 -0
  89. package/dist/userRepository/api/tests/integration/flagUserProfile.integration.test.d.ts.map +1 -0
  90. package/dist/userRepository/utils/submitUserReport.d.ts +10 -0
  91. package/dist/userRepository/utils/submitUserReport.d.ts.map +1 -0
  92. package/dist/userRepository/utils/userReport.d.ts +35 -0
  93. package/dist/userRepository/utils/userReport.d.ts.map +1 -0
  94. package/dist/utils/contentFlag.d.ts +28 -0
  95. package/dist/utils/contentFlag.d.ts.map +1 -0
  96. package/dist/utils/tests/dummy/channel.d.ts +4 -0
  97. package/dist/utils/tests/dummy/channel.d.ts.map +1 -1
  98. package/package.json +1 -1
package/dist/index.cjs.js CHANGED
@@ -58,6 +58,18 @@ exports.UserReportTypeEnum = void 0;
58
58
  UserReportTypeEnum["AVATAR"] = "avatarFileId";
59
59
  UserReportTypeEnum["BEHAVIOR"] = "behavior";
60
60
  })(exports.UserReportTypeEnum || (exports.UserReportTypeEnum = {}));
61
+ /**
62
+ * The profile field a user is reported for in `UserRepository.flagUserProfile`.
63
+ * Each member is the backend `reportType` wire value. There is deliberately no
64
+ * behavior member: behavior reports need a reason and go through
65
+ * `UserRepository.flagUserBehavior`.
66
+ */
67
+ exports.UserProfileFieldEnum = void 0;
68
+ (function (UserProfileFieldEnum) {
69
+ UserProfileFieldEnum["DISPLAY_NAME"] = "displayName";
70
+ UserProfileFieldEnum["DESCRIPTION"] = "description";
71
+ UserProfileFieldEnum["AVATAR"] = "avatarFileId";
72
+ })(exports.UserProfileFieldEnum || (exports.UserProfileFieldEnum = {}));
61
73
 
62
74
  const FileType = Object.freeze({
63
75
  FILE: 'file',
@@ -834,6 +846,18 @@ class AmityModuleNotEntitledError extends ASCApiError {
834
846
  this.type = 'AmityModuleNotEntitledError';
835
847
  }
836
848
  }
849
+ /**
850
+ * Input sanitization related error
851
+ * @category Errors
852
+ */
853
+ class ASCInvalidParameterError extends ASCError {
854
+ /**
855
+ * @param message A custom error message
856
+ */
857
+ constructor(message) {
858
+ super(message, 800110 /* Amity.ClientError.INVALID_PARAMETERS */, "error" /* Amity.ErrorLevel.ERROR */);
859
+ }
860
+ }
837
861
 
838
862
  let activeClient = null;
839
863
  /**
@@ -1607,6 +1631,24 @@ const sortByLastUpdated = ({ updatedAt: a = 0 }, { updatedAt: b = 0 }) => new Da
1607
1631
  * Sorting a collection by the items with most recent activity
1608
1632
  */
1609
1633
  const sortByLastActivity = ({ lastActivity: a }, { lastActivity: b }) => new Date(b).valueOf() - new Date(a).valueOf();
1634
+ /**
1635
+ * Sorting a channel collection by the time of its newest message (newest first).
1636
+ * Channels whose `lastMessageAt` is `null` sort last, ordered by `createdAt`
1637
+ * (newest first) among themselves, matching the server's `sortBy=lastMessage`
1638
+ * order so the local sort agrees with the page order.
1639
+ */
1640
+ const sortByLastMessage = (a, b) => {
1641
+ var _a, _b;
1642
+ const aAt = (_a = a.lastMessageAt) !== null && _a !== void 0 ? _a : null;
1643
+ const bAt = (_b = b.lastMessageAt) !== null && _b !== void 0 ? _b : null;
1644
+ if (aAt !== null && bAt !== null)
1645
+ return new Date(bAt).valueOf() - new Date(aAt).valueOf();
1646
+ if (aAt !== null)
1647
+ return -1;
1648
+ if (bAt !== null)
1649
+ return 1;
1650
+ return new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf();
1651
+ };
1610
1652
  /**
1611
1653
  * Filter items by untilAt timestamp based on sort direction
1612
1654
  * - For 'lastCreated' (newest first): untilAt acts as lower bound, excludes items older than boundary
@@ -1691,8 +1733,13 @@ const scheduleTask = (task) => {
1691
1733
  clearTimeout(timer);
1692
1734
  tasks.push(task);
1693
1735
  timer = setTimeout(() => {
1694
- tasks.forEach(fn => fn());
1736
+ // Swap the queue out before running it. A task that schedules another task
1737
+ // while the batch runs (an event handler that fires an event) must land in
1738
+ // a fresh queue and its own timer, not in the array being drained and then
1739
+ // discarded.
1740
+ const batch = tasks;
1695
1741
  tasks = [];
1742
+ batch.forEach(fn => fn());
1696
1743
  }, 0);
1697
1744
  };
1698
1745
 
@@ -8042,6 +8089,140 @@ const postCommentCountEngineOnLoginHandler = () => onSessionStateChange(state =>
8042
8089
  }
8043
8090
  });
8044
8091
 
8092
+ const BOTH_FIELDS = { lastActivity: true, lastMessageAt: true };
8093
+ /**
8094
+ * The channel cache is keyed by `channelInternalId`, but a message names its
8095
+ * channel by whichever id the producer had: the raw MQTT payload carries the
8096
+ * internal id, a converted `Amity.InternalMessage` carries the public id, and
8097
+ * the optimistic message carries the sub-channel's `channelId`. Try the direct
8098
+ * key first, then match any of the three ids.
8099
+ */
8100
+ const findCachedChannel = (channelId) => {
8101
+ var _a, _b, _c;
8102
+ const direct = (_a = pullFromCache(['channel', 'get', channelId])) === null || _a === void 0 ? void 0 : _a.data;
8103
+ if (direct)
8104
+ return direct;
8105
+ return (_c = (_b = queryCache(['channel', 'get'])) === null || _b === void 0 ? void 0 : _b.find(({ data }) => data.channelInternalId === channelId ||
8106
+ data.channelId === channelId ||
8107
+ data.channelPublicId === channelId)) === null || _c === void 0 ? void 0 : _c.data;
8108
+ };
8109
+ const isNewer = (stored, at) => !stored || convertDateStringToTimestamp(stored) < convertDateStringToTimestamp(at);
8110
+ const isSameInstant = (stored, at) => !!stored && convertDateStringToTimestamp(stored) === convertDateStringToTimestamp(at);
8111
+ /** Pushes the changed fields in one write and emits one `local.channel.updated`. */
8112
+ const commit = (channel, changes) => {
8113
+ if (Object.keys(changes).length === 0)
8114
+ return false;
8115
+ const updated = shallowClone(channel, changes);
8116
+ // Write back under the key the cache actually uses (the resolver key), not
8117
+ // under `channel.channelId`, which can differ from `channelInternalId`.
8118
+ pushToCache(['channel', 'get', getResolver('channel')(updated)], updated);
8119
+ fireEvent('local.channel.updated', {
8120
+ channels: [updated],
8121
+ channelUsers: [],
8122
+ users: [],
8123
+ files: [],
8124
+ });
8125
+ return true;
8126
+ };
8127
+ /**
8128
+ * Single owner of every local write to a channel's ordering timestamps,
8129
+ * `lastActivity` and `lastMessageAt` (ChannelActivityService v1).
8130
+ *
8131
+ * - Monotonic per field: a field moves only when `at` is newer than the stored
8132
+ * value (or the stored value is `null`), so a late or replayed event never
8133
+ * moves a channel down the list.
8134
+ * - One write: both fields are read, compared and pushed in a single cache
8135
+ * update, followed by one `local.channel.updated` event so every open channel
8136
+ * collection re-sorts once.
8137
+ * - Server channel payloads never go through here; they overwrite unguarded.
8138
+ *
8139
+ * @returns `true` when at least one field moved
8140
+ * @category private
8141
+ */
8142
+ const updateChannelActivityTimestamps = (channelId, at, fields = BOTH_FIELDS) => {
8143
+ const client = getActiveClient();
8144
+ if (!client.cache || !channelId || !at)
8145
+ return false;
8146
+ const channel = findCachedChannel(channelId);
8147
+ if (!channel)
8148
+ return false;
8149
+ const changes = {};
8150
+ if (fields.lastActivity && isNewer(channel.lastActivity, at))
8151
+ changes.lastActivity = at;
8152
+ if (fields.lastMessageAt && isNewer(channel.lastMessageAt, at))
8153
+ changes.lastMessageAt = at;
8154
+ return commit(channel, changes);
8155
+ };
8156
+ /**
8157
+ * Local correction for the current user's own send (ChannelActivityService v1
8158
+ * REQ-009, Trigger Matrix row 1b). Once the server confirms the message, each
8159
+ * field that still holds the device-clock `localAt` written at send time is
8160
+ * replaced by the server's `serverAt`, even when that is older. A field a newer
8161
+ * message has already moved is left alone. One write, one change notification,
8162
+ * nothing when neither field still holds `localAt`.
8163
+ *
8164
+ * @returns `true` when at least one field was replaced
8165
+ * @category private
8166
+ */
8167
+ const confirmOwnMessage = (channelId, localAt, serverAt) => {
8168
+ const client = getActiveClient();
8169
+ if (!client.cache || !channelId || !localAt || !serverAt)
8170
+ return false;
8171
+ if (isSameInstant(localAt, serverAt))
8172
+ return false;
8173
+ const channel = findCachedChannel(channelId);
8174
+ if (!channel)
8175
+ return false;
8176
+ const changes = {};
8177
+ if (isSameInstant(channel.lastActivity, localAt))
8178
+ changes.lastActivity = serverAt;
8179
+ if (isSameInstant(channel.lastMessageAt, localAt))
8180
+ changes.lastMessageAt = serverAt;
8181
+ return commit(channel, changes);
8182
+ };
8183
+
8184
+ /**
8185
+ * Session-wide owner of the local `lastActivity` / `lastMessageAt` updates
8186
+ * (ChannelActivityService v1, Web migration rows W1 – W5).
8187
+ *
8188
+ * Registered once per session from `setupLoginSubscriptions` and
8189
+ * `resumeSession`, so the update runs whether or not a channel collection is
8190
+ * open, and exactly once per message however many collections are open.
8191
+ *
8192
+ * It listens to the raw events rather than `onMessageCreatedMqtt`, so it does
8193
+ * not duplicate that subscriber's side effects (mark-read, unread recount).
8194
+ *
8195
+ * @category private
8196
+ */
8197
+ const channelActivityOnLoginHandler = () => {
8198
+ const client = getActiveClient();
8199
+ const disposers = [
8200
+ // Any user's new message, in any message feed of the channel. The raw
8201
+ // payload's `channelId` is the channel's internal id.
8202
+ createEventSubscriber(client, 'channelActivity', 'message.created', payload => {
8203
+ payload.messages.forEach(message => {
8204
+ updateChannelActivityTimestamps(message.channelId, message.createdAt);
8205
+ });
8206
+ }),
8207
+ // The current user's own send fires this three times: with the optimistic
8208
+ // message at send time (Syncing), on the create response (Synced), and on
8209
+ // failure (Error). The device-clock time of the Syncing / Error fires was
8210
+ // already applied synchronously in createMessageOptimistic, and replaying it
8211
+ // here could undo the server correction made on the response
8212
+ // (confirmOwnMessage). Only the server-confirmed fire goes through the guard.
8213
+ createEventSubscriber(client, 'channelActivity', 'local.message.created', payload => {
8214
+ payload.messages.forEach(message => {
8215
+ if (message.syncState !== "synced" /* Amity.SyncState.Synced */)
8216
+ return;
8217
+ updateChannelActivityTimestamps(message.channelId, message.createdAt);
8218
+ });
8219
+ }),
8220
+ ];
8221
+ return () => {
8222
+ disposers.forEach(fn => fn());
8223
+ };
8224
+ };
8225
+
8045
8226
  const EVENTS = [
8046
8227
  'disconnected',
8047
8228
  'error',
@@ -8231,7 +8412,7 @@ const setupLoginSubscriptions = (unsubWatcher) => {
8231
8412
  // NOTE: This is a temporary solution to handle the channel marker when the user is forced to leave
8232
8413
  // the channel because currently backend can't handle this, so every time a user is banned from
8233
8414
  // a channel or the channel is deleted the channel's unread count will not be reset to zero
8234
- onChannelDeleted(removeChannelMarkerCache), onChannelMemberBanned(removeChannelMarkerCache), markReadEngineOnLoginHandler(), analyticsEngineOnLoginHandler(), objectResolverEngineOnLoginHandler(), reactionSyncEngineOnLoginHandler(), blockedUserSyncEngineOnLoginHandler(), blockingUserSyncEngineOnLoginHandler(), blockRealtimeOnLoginHandler(), resetAutoSubscriptionsOnLogout(), CursorController.clearOnLogout(), postCommentCountEngineOnLoginHandler());
8415
+ onChannelDeleted(removeChannelMarkerCache), onChannelMemberBanned(removeChannelMarkerCache), markReadEngineOnLoginHandler(), analyticsEngineOnLoginHandler(), objectResolverEngineOnLoginHandler(), reactionSyncEngineOnLoginHandler(), blockedUserSyncEngineOnLoginHandler(), blockingUserSyncEngineOnLoginHandler(), blockRealtimeOnLoginHandler(), resetAutoSubscriptionsOnLogout(), CursorController.clearOnLogout(), postCommentCountEngineOnLoginHandler(), channelActivityOnLoginHandler());
8235
8416
  if (client.useLegacyUnreadCount) {
8236
8417
  subscriptions.push(readReceiptSyncEngineOnLoginHandler());
8237
8418
  }
@@ -9492,7 +9673,7 @@ const resumeSession = async (params, sessionHandler, config) => {
9492
9673
  // NOTE: This is a temporary solution to handle the channel marker when the user is forced to leave
9493
9674
  // the channel because currently backend can't handle this, so every time a user is banned from
9494
9675
  // a channel or the channel is deleted the channel's unread count will not be reset to zero
9495
- onChannelDeleted(removeChannelMarkerCache), onChannelMemberBanned(removeChannelMarkerCache), markReadEngineOnLoginHandler(), analyticsEngineOnLoginHandler(), objectResolverEngineOnLoginHandler());
9676
+ onChannelDeleted(removeChannelMarkerCache), onChannelMemberBanned(removeChannelMarkerCache), markReadEngineOnLoginHandler(), analyticsEngineOnLoginHandler(), objectResolverEngineOnLoginHandler(), channelActivityOnLoginHandler());
9496
9677
  if (client.useLegacyUnreadCount) {
9497
9678
  subscriptions$1.push(readReceiptSyncEngineOnLoginHandler());
9498
9679
  }
@@ -13451,7 +13632,7 @@ const getWatchSessionStorage = () => {
13451
13632
  return storageInstance;
13452
13633
  };
13453
13634
 
13454
- const privateKey = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDHo80SecH7FuF2\nhFYnb+l26/VN8UMLXAQFLnxciNTEwkGVFMpdezlH8rU2HtUJL4RETogbAOLVY0XM\njs6sPn8G1nALmh9qeDpUtVqFOVtBHxEZ910TLOtQiunjqJKO5nWdqZ71EC3OFluR\niGQkO84BiIFbv37ub7xl3S8XarbtKoLcyVpkDHi+1wx1pgCAn6gtBUgckPL5NR8j\nLseabl3HAXQfhTCKo4tmOFM2Dxwl1IUMmIJrJg/aIU/U0tj/1Eoo7mG0JcNWX19l\nW3EecCbi0ncCJOrkUdwlBrcjaMayaX/ubEwyUeTGiLdyc4L3GRLHjyK8xgVNXRMH\nbZWJ2a5NAgMBAAECggEASxuE+35zTFO/XydKgmvIGcWL9FbgMlXb7Vcf0nBoG945\nbiz0NVc2paraIhJXc608xbYF3qLmtAE1MVBI0ORyRdBHNxY024l/6H6SH60Ed+uI\nM4ysp5ourY6Vj+DLwpdRiI9YDjqYAQDIUmhNxJP7XPhOMoZI6st+xZQBM34ic/bv\nAMSJm9OZphSp3+qXVkFZztr2mxD2EZSJJLYxi8BCdgM2qhazalbcJ6zDKHCZWVWm\n8RRxDGldyMb/237JxETzP40tAlzOZDmBAbUgEnurDJ93RVDIE3rbZUshwgeQd18a\nem096mWgvB1AIKYgsTAR3pw+V19YWAjq/glP6fz8wQKBgQD/oQq+ukKF0PRgBeM5\ngeTjSwsdGppQLmf5ndujvoiz/TpdjDEPu6R8kigQr1rG2t4K/yfdZoI8RdmJD1al\n3Q7N9hofooSy4rj6E3txzWZCHJjHad2cnCp/O26HiReGAl7wTcfTmNdiFHhZQzm5\nJBkvWAiwuvQMNfEbnXxw6/vIDwKBgQDH7fX8gsc77JLvAWgp1MaQN/sbqVb6JeT1\nFQfR8E/WFCSmzQBtNzd5KgYuCeelwr/8DyYytvN2BzCYZXp73gI1jF3YlW5jVn74\nOY6TwQ095digwo6Z0yuxopdIOApKgAkL9PRKgNrqAf3NAyMua6lOGifzjDojC3KU\nfylQmxMn4wKBgHp2B9O/H0dEBw5JQ8W0+JX6yWQz7mEjGiR2/1W+XXb8hQ1zr709\nw1r6Gb+EghRpnZ3fBpYGGbYOMFx8wKHM+N6qW3F0ReX8v2juFGE8aRSa5oYBrWzt\nU16Idjbv8hj84cZ1PJmdyvDtpYn9rpWHOZl4rxEbPvbqkIsOMyNVqdT5AoGAOSge\nmwIIU2le2FVeohbibXiToWTYKMuMmURZ5/r72AgKMmWJKbAPe+Q3wBG01/7FRBpQ\noU8Ma0HC8s6QJbliiEyIx9JwrJWd1vkdecBHONrtA4ibm/5zD2WcOllLF+FitLhi\n3qnX6+6F0IaFGFBPJrTzlv0P4dTz/OAdv52V7GECgYEA2TttOKBAqWllgOaZOkql\nLVMJVmgR7s6tLi1+cEP8ZcapV9aRbRzTAKXm4f8AEhtlG9F9kCOvHYCYGi6JaiWJ\nZkHjeex3T+eE6Di6y5Bm/Ift5jtVhJ4jCVwHOKTMej79NPUFTJfv8hCo29haBDv6\nRXFrv+T21KCcw8k3sJeJWWQ=\n-----END PRIVATE KEY-----";
13635
+ const privateKey = "MIIEpQIBAAKCAQEAwAEc/oZgYIvKSUG/C3mONYLR4ZPgAjMEX4bJ+xqqakUDRtqlNO+eZs2blQ1Ko0DBkqPExyQezvjibH5W2UZBV5RaBTlTcNVKTToMBEGesAfaEcM3qUyQHxdbFYZv6P4sb14dcwxTQ8usmaV8ooiR1Fcaso5ZWYcZ8Hb46FbQ7OoVumsBtPWwfZ4f003o5VCl6AIM6lcLv9UDLlFVYhE+PeXpRHtfWlGqxMvqC9oinlwhL6nWv6VjQXW4nhcib72dPBzfHT7k/PMKto2SxALYdb68ENiAGuJLWi3AUHSyYCJK2w7wIlWfJUAI0v26ub10IpExr6D5QuW2577jjP93iwIDAQABAoIBAFWfqXhwIIatkFY+9Z1+ZcbDQimgsmMIsUiQaX6Lk7e0cxOj6czDlxYtVtaPiNtow2pLkjNkjkCqiP7tEHnwdK9DvylZOTa2R15NJpK3WLcTqVIGhsn/FL5owfvFah6zSsmXZParZm5zY9NZE03ALZhOB9/cz0e3kf/EbpfeL2mW7MApyiUt5i09ycchroOpcWp73ipIxvgigtZyUGFmsQicWhUs28F0D7w4Qfk76yG3nqXeb+BAMhCaIaa/k/aAxhiZG/ygEQWQrcC8gfe+jyicMAQPDEVS9YuUMGsLjIjKuVLZzp2xirQnhc2i2zVNEIvG6soprPOBEMQugzrtX5ECgYEA3b7KAbBIbDl1e4ZSCWhHdHkiWVZHaopsR/LhqDDNhXjWjq3AesgV6k0j9EdziMn/HmmOso0bz99GTV3JZf4A9ztTLumJlkHbdVtlgOqSjrFLj12rH9KXTheyIhWSpUmm8+WB1xasFbqpvJaGo7F3pd2Fqj1XR4mp5BO7c/t7LJ0CgYEA3aouEzXQ9THRKYocdfY69EI1Il1t/d/RSqqd9BxEjxBgxkM13ZiYIn/R4WW/nCUrlmhxG44Aa2Gob4Ahfsui2xKTg/g/3Zk/rAxAEGkfOLGoenaJMD41fH4wUq3FRYwkvnaMb9Hd6f/TlBHslIRa2NN58bSBGJCyBP2b59+2+EcCgYEAixDVRXvV37GlYUOa/XVdosk5Zoe6oDGRuQm0xbNdoUBoZvDHDvme7ONWEiQha/8qtVsD+CyQ7awcPfb8kK9c0bBt+bTS6d4BkTcxkEkMgtrkBVR8Nqfu5jXsLH4VCv4G61zbMhZw8+ut+az5YX2yCN7Frj9sFlxapMRPQmzMEe0CgYEAumsAzM8ZqNv4mAK65Mnr0rhLj1cbxcKRdUYACOgtEFQpzxN/HZnTeFAe5nx3pI3uFlRHq3DFEYnT6dHMWaJQmAULYpVIwMi9L6gtyJ9fzoI6uqMtxRDMUqKdaSsTGOY/kJ6KhQ/unXi1K3XXjR+yd1+C0q+HUm1+CYxvrZYLfskCgYEArsEy+IQOiqniJ0NE2vVUF+UK/IRZaic9YKcpov5Ot7Vvzm/MnnW4N1ljVskocETBWMmPUvNSExVjPebi+rxd8fa5kY8BJScPTzMFbunZn/wjtGdcM10qdlVQ9doG61A/9P3ezFKCfS4AvF/H/59LcSx2Bh28fp3/efiVIOpVd4Y=";
13455
13636
  /*
13456
13637
  * The crypto algorithm used for importing key and signing string
13457
13638
  */
@@ -15250,18 +15431,18 @@ const buildFlagUserBody = (options) => {
15250
15431
  /**
15251
15432
  * ```js
15252
15433
  * import { UserRepository } from '@amityco/ts-sdk'
15434
+ *
15435
+ * // Typeless report (supported).
15253
15436
  * const flagged = await UserRepository.flagUser('userId')
15254
- * const flaggedBehavior = await UserRepository.flagUser('userId', {
15255
- * reportType: 'behavior',
15256
- * reason: 'Harassment or bullying',
15257
- * comment: 'optional free-text',
15258
- * })
15259
15437
  * ```
15260
15438
  *
15439
+ * Flags a user. Two call shapes are accepted (see the overloads): id-only, which
15440
+ * sends no body and stays supported, and the deprecated structured options object,
15441
+ * whose request mapping is unchanged. New code reporting a profile field or behavior
15442
+ * should use `flagUserProfile` or `flagUserBehavior`.
15443
+ *
15261
15444
  * @param userId The ID of the user to be flagged
15262
- * @param options Optional report metadata forwarded to the server as the request
15263
- * body: `reportType`, `reason`, `comment`. Only non-null fields are included.
15264
- * All validation is server-side; a bodyless call produces a typeless report.
15445
+ * @param options Deprecated report metadata; omit for a typeless report
15265
15446
  * @returns the flagged {@link Amity.User}
15266
15447
  *
15267
15448
  * @category User API
@@ -15282,6 +15463,156 @@ const flagUser = async (userId, options) => {
15282
15463
  };
15283
15464
  /* end_public_function */
15284
15465
 
15466
+ /**
15467
+ * Sends a validated split user report (`flagUserProfile` / `flagUserBehavior`) to the
15468
+ * same endpoint as `flagUser`, then persists the returned user and fires `user.flagged`
15469
+ * exactly as `flagUser` does, so all three calls share return, cache and event behaviour.
15470
+ *
15471
+ * Not exported from the public API.
15472
+ */
15473
+ const submitUserReport = async (apiName, userId, body) => {
15474
+ var _a;
15475
+ const client = getActiveClient();
15476
+ client.log(`user/${apiName}`, { userId, body });
15477
+ const { data } = await client.http.post(`/api/v4/me/flags/${encodeURIComponent(userId)}`, body);
15478
+ const payload = prepareUserPayload(data);
15479
+ if (client.cache) {
15480
+ ingestInCache(payload);
15481
+ }
15482
+ fireEvent('user.flagged', data);
15483
+ return (_a = payload.users.find(user => user.userId === userId)) !== null && _a !== void 0 ? _a : payload.users[0];
15484
+ };
15485
+
15486
+ /** Maximum `comment` length, in string length units, after trimming. */
15487
+ const USER_REPORT_COMMENT_MAX_LENGTH = 300;
15488
+ const PROFILE_FIELDS = Object.values(exports.UserProfileFieldEnum);
15489
+ const BEHAVIOR_REASONS = Object.values(exports.ContentFlagReasonEnum);
15490
+ const assertOptionsObject = (apiName, options, shape) => {
15491
+ if (typeof options !== 'object' || options === null || Array.isArray(options)) {
15492
+ throw new ASCInvalidParameterError(`${apiName}: expected an options object \`${shape}\``);
15493
+ }
15494
+ return options;
15495
+ };
15496
+ /**
15497
+ * Normalizes an optional reporter `comment`: it must be a string when supplied (`null`
15498
+ * is not a valid supplied comment); it is trimmed; a blank result is omitted (the
15499
+ * backend rejects `""`); a result longer than 300 characters is rejected before any
15500
+ * request. Otherwise the trimmed text is returned unchanged.
15501
+ *
15502
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` for a non-string or over-length comment
15503
+ */
15504
+ const normalizeUserReportComment = (apiName, comment) => {
15505
+ if (comment === undefined)
15506
+ return undefined;
15507
+ if (typeof comment !== 'string') {
15508
+ throw new ASCInvalidParameterError(`${apiName}: \`comment\` must be a string when supplied`);
15509
+ }
15510
+ const trimmed = comment.trim();
15511
+ if (trimmed.length === 0)
15512
+ return undefined;
15513
+ if (trimmed.length > USER_REPORT_COMMENT_MAX_LENGTH) {
15514
+ throw new ASCInvalidParameterError(`${apiName}: \`comment\` must be at most ${USER_REPORT_COMMENT_MAX_LENGTH} characters after trimming`);
15515
+ }
15516
+ return trimmed;
15517
+ };
15518
+ const withComment = (body, comment) => comment === undefined ? body : Object.assign(Object.assign({}, body), { comment });
15519
+ /**
15520
+ * Validates `flagUserProfile` options and builds its request body. Untyped callers can
15521
+ * pass anything at runtime, so nothing is trusted from the declared type. Any `reason`
15522
+ * key is ignored and never sent.
15523
+ *
15524
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when the options are malformed
15525
+ */
15526
+ const toUserProfileReportBody = (options) => {
15527
+ const apiName = 'flagUserProfile';
15528
+ const { field, comment } = assertOptionsObject(apiName, options, '{ field, comment? }');
15529
+ if (typeof field !== 'string' || !PROFILE_FIELDS.includes(field)) {
15530
+ throw new ASCInvalidParameterError(`${apiName}: \`field\` must be a \`UserProfileFieldEnum\` value`);
15531
+ }
15532
+ return withComment({ reportType: field }, normalizeUserReportComment(apiName, comment));
15533
+ };
15534
+ /**
15535
+ * Validates `flagUserBehavior` options and builds its request body. `reason` must be one
15536
+ * of the nine `ContentFlagReasonEnum` values and is sent as that canonical label;
15537
+ * `reportType` is always `behavior`.
15538
+ *
15539
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when the options are malformed
15540
+ */
15541
+ const toUserBehaviorReportBody = (options) => {
15542
+ const apiName = 'flagUserBehavior';
15543
+ const { reason, comment } = assertOptionsObject(apiName, options, '{ reason, comment? }');
15544
+ if (typeof reason !== 'string' || !BEHAVIOR_REASONS.includes(reason)) {
15545
+ throw new ASCInvalidParameterError(`${apiName}: \`reason\` must be a \`ContentFlagReasonEnum\` value`);
15546
+ }
15547
+ return withComment({ reportType: 'behavior', reason }, normalizeUserReportComment(apiName, comment));
15548
+ };
15549
+
15550
+ /* begin_public_function
15551
+ id: user.flag_profile
15552
+ */
15553
+ /**
15554
+ * ```js
15555
+ * import { UserRepository, UserProfileFieldEnum } from '@amityco/ts-sdk'
15556
+ * const flagged = await UserRepository.flagUserProfile('userId', {
15557
+ * field: UserProfileFieldEnum.AVATAR,
15558
+ * comment: 'Nudity in profile photo',
15559
+ * })
15560
+ * ```
15561
+ *
15562
+ * Reports one of a user's profile fields (display name, description or avatar). The
15563
+ * call has no reason: profile reports never carry one, so the backend's reason rules
15564
+ * cannot be violated from here. A `reason` key passed at runtime is ignored and never sent.
15565
+ *
15566
+ * `comment` is trimmed; a blank comment is omitted from the request; a comment longer
15567
+ * than 300 characters after trimming is rejected before any request. Otherwise the
15568
+ * trimmed text is sent unchanged.
15569
+ *
15570
+ * Sends `POST /api/v4/me/flags/:userId` with `{ reportType: <field>, comment? }`.
15571
+ *
15572
+ * @param userId The ID of the user to be reported
15573
+ * @param options `field` is a `UserProfileFieldEnum` value; `comment` is optional free text
15574
+ * @returns the flagged {@link Amity.User}
15575
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when `options` is missing or malformed, or `comment` is over 300 characters, before any request is made
15576
+ *
15577
+ * @category User API
15578
+ * @async
15579
+ * */
15580
+ const flagUserProfile = async (userId, options) => submitUserReport('flagUserProfile', userId, toUserProfileReportBody(options));
15581
+ /* end_public_function */
15582
+
15583
+ /* begin_public_function
15584
+ id: user.flag_behavior
15585
+ */
15586
+ /**
15587
+ * ```js
15588
+ * import { UserRepository, ContentFlagReasonEnum } from '@amityco/ts-sdk'
15589
+ * const flagged = await UserRepository.flagUserBehavior('userId', {
15590
+ * reason: ContentFlagReasonEnum.Others,
15591
+ * comment: 'Impersonating our brand',
15592
+ * })
15593
+ * ```
15594
+ *
15595
+ * Reports a user's behavior. `reason` is required and is one of the nine
15596
+ * `ContentFlagReasonEnum` values, sent as its canonical label. Reporter text, including
15597
+ * the explanation for `Others`, always travels in `comment`; the reason never carries text.
15598
+ *
15599
+ * `comment` is trimmed; a blank comment is omitted from the request; a comment longer
15600
+ * than 300 characters after trimming is rejected before any request. Otherwise the
15601
+ * trimmed text is sent unchanged.
15602
+ *
15603
+ * Sends `POST /api/v4/me/flags/:userId` with `{ reportType: 'behavior', reason, comment? }`.
15604
+ *
15605
+ * @param userId The ID of the user to be reported
15606
+ * @param options `reason` is a `ContentFlagReasonEnum` value; `comment` is optional free text
15607
+ * @returns the flagged {@link Amity.User}
15608
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when `options` is missing or malformed, `reason` is missing or unknown, or `comment` is over 300 characters, before any request is made
15609
+ *
15610
+ * @category User API
15611
+ * @async
15612
+ * */
15613
+ const flagUserBehavior = async (userId, options) => submitUserReport('flagUserBehavior', userId, toUserBehaviorReportBody(options));
15614
+ /* end_public_function */
15615
+
15285
15616
  /* begin_public_function
15286
15617
  id: user.unflag
15287
15618
  */
@@ -16456,6 +16787,8 @@ var index$r = /*#__PURE__*/Object.freeze({
16456
16787
  getUserByIds: getUserByIds,
16457
16788
  updateUser: updateUser,
16458
16789
  flagUser: flagUser,
16790
+ flagUserProfile: flagUserProfile,
16791
+ flagUserBehavior: flagUserBehavior,
16459
16792
  unflagUser: unflagUser,
16460
16793
  isUserFlaggedByMe: isUserFlaggedByMe,
16461
16794
  getAllBlockedUsers: getAllBlockedUsers,
@@ -19494,7 +19827,7 @@ const getLocalId = () => `LOCAL_${uuid()}`;
19494
19827
  // FIXME: temp solution
19495
19828
  let uniqueId;
19496
19829
  const createMessageOptimistic = (bundle) => {
19497
- var _a, _b;
19830
+ var _a, _b, _c, _d;
19498
19831
  const client = getActiveClient();
19499
19832
  if (!client.cache)
19500
19833
  return;
@@ -19525,9 +19858,20 @@ const createMessageOptimistic = (bundle) => {
19525
19858
  }
19526
19859
  // as reused to update created and updated time, which should be the same
19527
19860
  const createdTime = new Date().toISOString();
19528
- const message = Object.assign({ creatorId: client.userId, creatorPrivateId: getActiveUser()._id, channelSegment: ((_b = subChannel === null || subChannel === void 0 ? void 0 : subChannel.data.messageCount) !== null && _b !== void 0 ? _b : 0) + 1, childrenNumber: 0, createdAt: createdTime, updatedAt: createdTime, syncState: "syncing" /* Amity.SyncState.Syncing */, isDeleted: false }, bundleWithMessageId);
19861
+ const message = Object.assign({
19862
+ // The optimistic message must name its channel (public id) so the own-send
19863
+ // update can move lastActivity / lastMessageAt at send time. Take it from the
19864
+ // cached sub-channel; when that is not cached, resolve the sub-channel id
19865
+ // through the channel cache, which for a channel's default sub-channel is
19866
+ // the channel itself (ChannelActivityService migration row I2). A thread or
19867
+ // unknown channel resolves to undefined, as before.
19868
+ channelId: (_b = subChannel === null || subChannel === void 0 ? void 0 : subChannel.data.channelId) !== null && _b !== void 0 ? _b : (_c = findCachedChannel(bundle.subChannelId)) === null || _c === void 0 ? void 0 : _c.channelId, creatorId: client.userId, creatorPrivateId: getActiveUser()._id, channelSegment: ((_d = subChannel === null || subChannel === void 0 ? void 0 : subChannel.data.messageCount) !== null && _d !== void 0 ? _d : 0) + 1, childrenNumber: 0, createdAt: createdTime, updatedAt: createdTime, syncState: "syncing" /* Amity.SyncState.Syncing */, isDeleted: false }, bundleWithMessageId);
19529
19869
  const cachedAt = UNSYNCED_OBJECT_CACHED_AT_VALUE;
19530
19870
  pushToCache(['message', 'get', message.messageId], message, { cachedAt });
19871
+ // Own-send update (ChannelActivityService row 1): move the channel at send
19872
+ // time, synchronously, so the confirmation on the send response always sees
19873
+ // this value rather than racing the scheduled local event below.
19874
+ updateChannelActivityTimestamps(message.channelId, message.createdAt);
19531
19875
  fireEvent('local.message.created', { messages: [message] });
19532
19876
  return message;
19533
19877
  };
@@ -19567,6 +19911,9 @@ const createMessage = async (bundle) => {
19567
19911
  if (client.cache) {
19568
19912
  ingestInCache(data, { cachedAt });
19569
19913
  }
19914
+ // The own-send update wrote the device-clock createdAt; replace it with the
19915
+ // server's now that the message is confirmed (ChannelActivityService REQ-009).
19916
+ confirmOwnMessage(optimisticData === null || optimisticData === void 0 ? void 0 : optimisticData.channelId, optimisticData === null || optimisticData === void 0 ? void 0 : optimisticData.createdAt, messages[0].createdAt);
19570
19917
  fireEvent('local.message.created', {
19571
19918
  messages: [Object.assign(Object.assign({}, messages[0]), { syncState: "synced" /* Amity.SyncState.Synced */ })],
19572
19919
  });
@@ -20160,23 +20507,85 @@ getDeliveredUsers.locally = (query) => {
20160
20507
  return { data: users, cachedAt, prevPage, nextPage };
20161
20508
  };
20162
20509
 
20163
- /* begin_public_function
20164
- id: message.flag
20165
- */
20510
+ const CONTENT_FLAG_REASONS = Object.values(exports.ContentFlagReasonEnum);
20166
20511
  /**
20167
- * ```js
20168
- * import { MessageRepository } from '@amityco/ts-sdk'
20169
- * const flagged = await MessageRepository.flagMessage(messageId, reason)
20170
- * ```
20512
+ * True when a public `flagX(id, input)` argument is the legacy shape (a reason string
20513
+ * or nothing). Anything else is routed to the `Amity.ContentFlagOptions` path, whose
20514
+ * validation rejects malformed values before any request is made.
20515
+ */
20516
+ const isLegacyContentFlagReason = (input) => input === undefined || typeof input === 'string';
20517
+ /**
20518
+ * Validates an `Amity.ContentFlagOptions` argument. Untyped (plain JS) callers can pass
20519
+ * anything at runtime, so the checks do not trust the declared type: the value must be a
20520
+ * plain object, `reason` must be one of the nine `ContentFlagReasonEnum` values and
20521
+ * `detail`, when supplied, must be a string (`null` is not a valid supplied detail).
20171
20522
  *
20172
- * @param messageId of the message to flag
20173
- * @param reason the reason to flag the message
20174
- * @returns the created report result
20523
+ * @param apiName used in the error message, e.g. `flagPost`
20524
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when the options are malformed
20525
+ */
20526
+ const validateContentFlagOptions = (apiName, options) => {
20527
+ if (typeof options !== 'object' || options === null || Array.isArray(options)) {
20528
+ throw new ASCInvalidParameterError(`${apiName}: expected an options object \`{ reason, detail? }\``);
20529
+ }
20530
+ const { reason, detail } = options;
20531
+ if (typeof reason !== 'string' || !CONTENT_FLAG_REASONS.includes(reason)) {
20532
+ throw new ASCInvalidParameterError(`${apiName}: \`reason\` must be a \`ContentFlagReasonEnum\` value`);
20533
+ }
20534
+ if (detail !== undefined && typeof detail !== 'string') {
20535
+ throw new ASCInvalidParameterError(`${apiName}: \`detail\` must be a string when supplied`);
20536
+ }
20537
+ return { reason: reason, detail };
20538
+ };
20539
+ /**
20540
+ * Builds the request body for validated options. `detail` is forwarded unchanged (empty
20541
+ * and whitespace-only strings included) and omitted from the body when `undefined`; the
20542
+ * backend enforces the 300-character limit.
20543
+ */
20544
+ const toContentFlagBody = ({ reason, detail }) => detail === undefined ? { reason } : { reason, detail };
20545
+
20546
+ /**
20547
+ * New `flagMessage(messageId, { reason, detail? })` implementation. Any category,
20548
+ * including `Others`, may carry a `detail`. `detail` is forwarded unchanged (empty and
20549
+ * whitespace-only strings included) and omitted from the request body when `undefined`;
20550
+ * the backend enforces the 300-character limit.
20175
20551
  *
20176
- * @category Message API
20177
- * @async
20178
- * */
20179
- const flagMessage = async (messageId, reason) => {
20552
+ * Not exported from the public API. It is reached through the
20553
+ * `MessageRepository.flagMessage(messageId, options)` overload; see `api/flagMessage.ts`.
20554
+ *
20555
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when `options` is malformed, before any request is made
20556
+ */
20557
+ const flagMessageWithOptions = async (messageId, options) => {
20558
+ // Validate first: an input error must surface as INVALID_PARAMETERS regardless of
20559
+ // whether a client is active, and validation needs nothing from the client.
20560
+ const body = toContentFlagBody(validateContentFlagOptions('flagMessage', options));
20561
+ const client = getActiveClient();
20562
+ client.log('message/flag', messageId);
20563
+ const { data: payload } = await client.http.post(`/api/v5/messages/${encodeURIComponent(messageId)}/flags`, body);
20564
+ if (client.cache) {
20565
+ const messagePayload = await prepareMessagePayload(payload);
20566
+ ingestInCache(messagePayload);
20567
+ }
20568
+ fireEvent('message.flagged', payload);
20569
+ return !!payload;
20570
+ };
20571
+
20572
+ /**
20573
+ * Legacy `flagMessage(messageId, reason?)` implementation, kept verbatim so existing
20574
+ * callers keep the exact request body they had before the options contract existed:
20575
+ *
20576
+ * - predefined `ContentFlagReasonEnum` value → `{ reason: <value>, detail: '' }`
20577
+ * - any other non-empty string (including the literal `'Others'`) → `{ reason: 'Others', detail: <string> }`
20578
+ * - `undefined` or `''` → `{ reason: 'Others', detail: '' }`
20579
+ * (for `undefined` the message body carries `detail: undefined`, which JSON serialization omits)
20580
+ *
20581
+ * Not exported from the public API. It is reached through the deprecated
20582
+ * `MessageRepository.flagMessage(messageId, reason)` overload; see `api/flagMessage.ts`.
20583
+ *
20584
+ * Not itself deprecated: it implements the deprecated legacy-string overload of the public
20585
+ * `flagMessage`, which carries the deprecation tag. New code should use `flagMessageWithOptions`
20586
+ * (the `{ reason, detail? }` contract). This path stays available throughout the current major version.
20587
+ */
20588
+ const flagMessageWithReasonString = async (messageId, reason) => {
20180
20589
  const client = getActiveClient();
20181
20590
  client.log('message/flag', messageId);
20182
20591
  const isPredefinedReason = reason &&
@@ -20193,6 +20602,42 @@ const flagMessage = async (messageId, reason) => {
20193
20602
  fireEvent('message.flagged', payload);
20194
20603
  return !!payload;
20195
20604
  };
20605
+
20606
+ /* begin_public_function
20607
+ id: message.flag
20608
+ */
20609
+ /**
20610
+ * ```js
20611
+ * import { MessageRepository, ContentFlagReasonEnum } from '@amityco/ts-sdk'
20612
+ *
20613
+ * // No reason.
20614
+ * await MessageRepository.flagMessage(messageId)
20615
+ *
20616
+ * // Category plus optional reporter context (any category, including Others).
20617
+ * await MessageRepository.flagMessage(messageId, {
20618
+ * reason: ContentFlagReasonEnum.SpamOrScams,
20619
+ * detail: 'Repeated promotional links',
20620
+ * })
20621
+ * ```
20622
+ *
20623
+ * Reports a message to moderation. Three call shapes are accepted (see the overloads):
20624
+ * id-only, an options object, and the deprecated legacy reason string. The public
20625
+ * name only dispatches on the input shape; the two implementations are kept separate
20626
+ * so the legacy path is untouched by the new contract:
20627
+ *
20628
+ * - `undefined` or a string → `flagMessageWithReasonString` (unchanged legacy mapping)
20629
+ * - anything else → `flagMessageWithOptions` (validated `{ reason, detail? }`)
20630
+ *
20631
+ * @param messageId The ID of the message to flag
20632
+ * @param input an options object `{ reason, detail? }`, a legacy reason string, or nothing
20633
+ * @returns `true` when the report was created
20634
+ *
20635
+ * @category Message API
20636
+ * @async
20637
+ * */
20638
+ const flagMessage = (messageId, input) => isLegacyContentFlagReason(input)
20639
+ ? flagMessageWithReasonString(messageId, input)
20640
+ : flagMessageWithOptions(messageId, input);
20196
20641
  /* end_public_function */
20197
20642
 
20198
20643
  /* begin_public_function
@@ -21912,6 +22357,9 @@ class ChannelLiveCollectionController extends LiveCollectionController {
21912
22357
  case 'lastCreated':
21913
22358
  channels = channels.sort(sortByLastCreated);
21914
22359
  break;
22360
+ case 'lastMessage':
22361
+ channels = channels.sort(sortByLastMessage);
22362
+ break;
21915
22363
  case 'displayName':
21916
22364
  /*
21917
22365
  * The server returns channels with empty | null displayName's first before
@@ -21972,9 +22420,8 @@ class ChannelLiveCollectionController extends LiveCollectionController {
21972
22420
  objectResolverEngine.resolve(message.channelId, "channel" /* Amity.ReferenceType.CHANNEL */);
21973
22421
  }
21974
22422
  else {
21975
- updateChannelCache(channelCache, {
21976
- lastActivity: message.createdAt,
21977
- });
22423
+ // lastActivity / lastMessageAt are moved by channelActivityOnLoginHandler
22424
+ // (session-wide, monotonic, one write). This listener only re-sorts.
21978
22425
  callback(message);
21979
22426
  }
21980
22427
  });
@@ -21992,9 +22439,7 @@ class ChannelLiveCollectionController extends LiveCollectionController {
21992
22439
  ])) === null || _a === void 0 ? void 0 : _a.data;
21993
22440
  if (!cacheData)
21994
22441
  return;
21995
- updateChannelCache(cacheData, {
21996
- lastActivity: message.createdAt,
21997
- });
22442
+ // Timestamps are moved by channelActivityOnLoginHandler; only re-sort here.
21998
22443
  callback(message);
21999
22444
  });
22000
22445
  }, 'channelId', 'channel'),
@@ -22732,9 +23177,7 @@ class SearchChannelLiveCollectionController extends LiveCollectionController {
22732
23177
  ])) === null || _a === void 0 ? void 0 : _a.data;
22733
23178
  if (!cacheData)
22734
23179
  return;
22735
- updateChannelCache(cacheData, {
22736
- lastActivity: message.createdAt,
22737
- });
23180
+ // Timestamps are moved by channelActivityOnLoginHandler; only re-sort here.
22738
23181
  callback(message);
22739
23182
  });
22740
23183
  }, 'channelId', 'channel'),
@@ -22751,9 +23194,7 @@ class SearchChannelLiveCollectionController extends LiveCollectionController {
22751
23194
  ])) === null || _a === void 0 ? void 0 : _a.data;
22752
23195
  if (!cacheData)
22753
23196
  return;
22754
- updateChannelCache(cacheData, {
22755
- lastActivity: message.createdAt,
22756
- });
23197
+ // Timestamps are moved by channelActivityOnLoginHandler; only re-sort here.
22757
23198
  callback(message);
22758
23199
  });
22759
23200
  }, 'channelId', 'channel'),
@@ -27338,23 +27779,22 @@ const declineComment = async (commentId, reason) => {
27338
27779
  };
27339
27780
  /* end_public_function */
27340
27781
 
27341
- /* begin_public_function
27342
- id: comment.flag
27343
- */
27344
27782
  /**
27345
- * ```js
27346
- * import { CommentRepository } from '@amityco/ts-sdk'
27347
- * const flagged = await CommentRepository.flagComment(commentId, reason)
27348
- * ```
27783
+ * Legacy `flagComment(commentId, reason?)` implementation, kept verbatim so existing
27784
+ * callers keep the exact request body they had before the options contract existed:
27349
27785
  *
27350
- * @param commentId The ID of the comment to flag
27351
- * @param reason the reason to flag the comment
27352
- * @returns the created report result
27786
+ * - predefined `ContentFlagReasonEnum` value → `{ reason: <value>, detail: '' }`
27787
+ * - any other non-empty string (including the literal `'Others'`) → `{ reason: 'Others', detail: <string> }`
27788
+ * - `undefined` or `''` → `{ reason: 'Others', detail: '' }`
27353
27789
  *
27354
- * @category Comment API
27355
- * @async
27356
- * */
27357
- const flagComment = async (commentId, reason) => {
27790
+ * Not exported from the public API. It is reached through the deprecated
27791
+ * `CommentRepository.flagComment(commentId, reason)` overload; see `api/flagComment.ts`.
27792
+ *
27793
+ * Not itself deprecated: it implements the deprecated legacy-string overload of the public
27794
+ * `flagComment`, which carries the deprecation tag. New code should use `flagCommentWithOptions`
27795
+ * (the `{ reason, detail? }` contract). This path stays available throughout the current major version.
27796
+ */
27797
+ const flagCommentWithReasonString = async (commentId, reason) => {
27358
27798
  const client = getActiveClient();
27359
27799
  client.log('comment/flagComment', commentId);
27360
27800
  const isPredefinedReason = reason &&
@@ -27370,6 +27810,67 @@ const flagComment = async (commentId, reason) => {
27370
27810
  fireEvent('comment.flagged', payload);
27371
27811
  return !!payload;
27372
27812
  };
27813
+
27814
+ /**
27815
+ * New `flagComment(commentId, { reason, detail? })` implementation. Any category,
27816
+ * including `Others`, may carry a `detail`. `detail` is forwarded unchanged (empty and
27817
+ * whitespace-only strings included) and omitted from the request body when `undefined`;
27818
+ * the backend enforces the 300-character limit.
27819
+ *
27820
+ * Not exported from the public API. It is reached through the
27821
+ * `CommentRepository.flagComment(commentId, options)` overload; see `api/flagComment.ts`.
27822
+ *
27823
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when `options` is malformed, before any request is made
27824
+ */
27825
+ const flagCommentWithOptions = async (commentId, options) => {
27826
+ // Validate first: an input error must surface as INVALID_PARAMETERS regardless of
27827
+ // whether a client is active, and validation needs nothing from the client.
27828
+ const body = toContentFlagBody(validateContentFlagOptions('flagComment', options));
27829
+ const client = getActiveClient();
27830
+ client.log('comment/flagComment', commentId);
27831
+ const { data: payload } = await client.http.post(`/api/v3/comments/${encodeURIComponent(commentId)}/flag`, body);
27832
+ if (client.cache) {
27833
+ ingestInCache(payload);
27834
+ }
27835
+ fireEvent('comment.flagged', payload);
27836
+ return !!payload;
27837
+ };
27838
+
27839
+ /* begin_public_function
27840
+ id: comment.flag
27841
+ */
27842
+ /**
27843
+ * ```js
27844
+ * import { CommentRepository, ContentFlagReasonEnum } from '@amityco/ts-sdk'
27845
+ *
27846
+ * // No reason: sent as Others with an empty detail.
27847
+ * await CommentRepository.flagComment(commentId)
27848
+ *
27849
+ * // Category plus optional reporter context (any category, including Others).
27850
+ * await CommentRepository.flagComment(commentId, {
27851
+ * reason: ContentFlagReasonEnum.SpamOrScams,
27852
+ * detail: 'Repeated promotional links',
27853
+ * })
27854
+ * ```
27855
+ *
27856
+ * Reports a comment to moderation. Three call shapes are accepted (see the overloads):
27857
+ * id-only, an options object, and the deprecated legacy reason string. The public
27858
+ * name only dispatches on the input shape; the two implementations are kept separate
27859
+ * so the legacy path is untouched by the new contract:
27860
+ *
27861
+ * - `undefined` or a string → `flagCommentWithReasonString` (unchanged legacy mapping)
27862
+ * - anything else → `flagCommentWithOptions` (validated `{ reason, detail? }`)
27863
+ *
27864
+ * @param commentId The ID of the comment to flag
27865
+ * @param input an options object `{ reason, detail? }`, a legacy reason string, or nothing
27866
+ * @returns `true` when the report was created
27867
+ *
27868
+ * @category Comment API
27869
+ * @async
27870
+ * */
27871
+ const flagComment = (commentId, input) => isLegacyContentFlagReason(input)
27872
+ ? flagCommentWithReasonString(commentId, input)
27873
+ : flagCommentWithOptions(commentId, input);
27373
27874
  /* end_public_function */
27374
27875
 
27375
27876
  /* begin_public_function
@@ -29723,23 +30224,47 @@ const declinePost = async (postId) => {
29723
30224
  };
29724
30225
  /* end_public_function */
29725
30226
 
29726
- /* begin_public_function
29727
- id: post.flag
29728
- */
29729
30227
  /**
29730
- * ```js
29731
- * import { PostRepository } from '@amityco/ts-sdk'
29732
- * const flagged = await PostRepository.flagPost(postId, reason)
29733
- * ```
30228
+ * New `flagPost(postId, { reason, detail? })` implementation. Any category, including
30229
+ * `Others`, may carry a `detail`. `detail` is forwarded unchanged (empty and
30230
+ * whitespace-only strings included) and omitted from the request body when `undefined`;
30231
+ * the backend enforces the 300-character limit.
29734
30232
  *
29735
- * @param postId of the post to flag
29736
- * @param reason the reason to flag the post
29737
- * @returns a boolean
30233
+ * Not exported from the public API. It is reached through the
30234
+ * `PostRepository.flagPost(postId, options)` overload; see `api/flagPost.ts`.
29738
30235
  *
29739
- * @category Post API
29740
- * @async
29741
- * */
29742
- const flagPost = async (postId, reason) => {
30236
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when `options` is malformed, before any request is made
30237
+ */
30238
+ const flagPostWithOptions = async (postId, options) => {
30239
+ // Validate first: an input error must surface as INVALID_PARAMETERS regardless of
30240
+ // whether a client is active, and validation needs nothing from the client.
30241
+ const body = toContentFlagBody(validateContentFlagOptions('flagPost', options));
30242
+ const client = getActiveClient();
30243
+ client.log('post/flagPost', postId);
30244
+ const { data: payload } = await client.http.post(`/api/v3/posts/${encodeURIComponent(postId)}/flag`, body);
30245
+ if (client.cache) {
30246
+ ingestInCache(prepareMembershipPayload(payload, 'communityUsers'));
30247
+ }
30248
+ fireEvent('post.flagged', payload);
30249
+ return !!payload;
30250
+ };
30251
+
30252
+ /**
30253
+ * Legacy `flagPost(postId, reason?)` implementation, kept verbatim so existing
30254
+ * callers keep the exact request body they had before the options contract existed:
30255
+ *
30256
+ * - predefined `ContentFlagReasonEnum` value → `{ reason: <value>, detail: '' }`
30257
+ * - any other non-empty string (including the literal `'Others'`) → `{ reason: 'Others', detail: <string> }`
30258
+ * - `undefined` or `''` → `{ reason: 'Others', detail: '' }`
30259
+ *
30260
+ * Not exported from the public API. It is reached through the deprecated
30261
+ * `PostRepository.flagPost(postId, reason)` overload; see `api/flagPost.ts`.
30262
+ *
30263
+ * Not itself deprecated: it implements the deprecated legacy-string overload of the public
30264
+ * `flagPost`, which carries the deprecation tag. New code should use `flagPostWithOptions`
30265
+ * (the `{ reason, detail? }` contract). This path stays available throughout the current major version.
30266
+ */
30267
+ const flagPostWithReasonString = async (postId, reason) => {
29743
30268
  const client = getActiveClient();
29744
30269
  client.log('post/flagPost', postId);
29745
30270
  const isPredefinedReason = reason &&
@@ -29755,6 +30280,42 @@ const flagPost = async (postId, reason) => {
29755
30280
  fireEvent('post.flagged', payload);
29756
30281
  return !!payload;
29757
30282
  };
30283
+
30284
+ /* begin_public_function
30285
+ id: post.flag
30286
+ */
30287
+ /**
30288
+ * ```js
30289
+ * import { PostRepository, ContentFlagReasonEnum } from '@amityco/ts-sdk'
30290
+ *
30291
+ * // No reason.
30292
+ * await PostRepository.flagPost(postId)
30293
+ *
30294
+ * // Category plus optional reporter context (any category, including Others).
30295
+ * await PostRepository.flagPost(postId, {
30296
+ * reason: ContentFlagReasonEnum.SpamOrScams,
30297
+ * detail: 'Repeated promotional links',
30298
+ * })
30299
+ * ```
30300
+ *
30301
+ * Reports a post to moderation. Three call shapes are accepted (see the overloads):
30302
+ * id-only, an options object, and the deprecated legacy reason string. The public
30303
+ * name only dispatches on the input shape; the two implementations are kept separate
30304
+ * so the legacy path is untouched by the new contract:
30305
+ *
30306
+ * - `undefined` or a string → `flagPostWithReasonString` (unchanged legacy mapping)
30307
+ * - anything else → `flagPostWithOptions` (validated `{ reason, detail? }`)
30308
+ *
30309
+ * @param postId The ID of the post to flag
30310
+ * @param input an options object `{ reason, detail? }`, a legacy reason string, or nothing
30311
+ * @returns `true` when the report was created
30312
+ *
30313
+ * @category Post API
30314
+ * @async
30315
+ * */
30316
+ const flagPost = (postId, input) => isLegacyContentFlagReason(input)
30317
+ ? flagPostWithReasonString(postId, input)
30318
+ : flagPostWithOptions(postId, input);
29758
30319
  /* end_public_function */
29759
30320
 
29760
30321
  /* begin_public_function
@@ -37256,6 +37817,7 @@ exports.sortByFirstCreated = sortByFirstCreated;
37256
37817
  exports.sortByFirstUpdated = sortByFirstUpdated;
37257
37818
  exports.sortByLastActivity = sortByLastActivity;
37258
37819
  exports.sortByLastCreated = sortByLastCreated;
37820
+ exports.sortByLastMessage = sortByLastMessage;
37259
37821
  exports.sortByLastUpdated = sortByLastUpdated;
37260
37822
  exports.sortByLocalSortingDate = sortByLocalSortingDate;
37261
37823
  exports.sortByName = sortByName;