@amityco/ts-sdk 7.29.0 → 7.29.1-c2a29dc.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 (103) hide show
  1. package/dist/@types/core/errors.d.ts +4 -1
  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 +75 -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 +726 -81
  45. package/dist/index.esm.js +726 -82
  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/report/api/isReportedByMe.d.ts.map +1 -1
  77. package/dist/userRepository/api/flagUser.d.ts +50 -9
  78. package/dist/userRepository/api/flagUser.d.ts.map +1 -1
  79. package/dist/userRepository/api/flagUserBehavior.d.ts +29 -0
  80. package/dist/userRepository/api/flagUserBehavior.d.ts.map +1 -0
  81. package/dist/userRepository/api/flagUserProfile.d.ts +29 -0
  82. package/dist/userRepository/api/flagUserProfile.d.ts.map +1 -0
  83. package/dist/userRepository/api/index.d.ts +2 -0
  84. package/dist/userRepository/api/index.d.ts.map +1 -1
  85. package/dist/userRepository/api/isUserFlaggedByMe.d.ts +22 -4
  86. package/dist/userRepository/api/isUserFlaggedByMe.d.ts.map +1 -1
  87. package/dist/userRepository/api/tests/integration/flagUser.overloads.integration.test.d.ts +2 -0
  88. package/dist/userRepository/api/tests/integration/flagUser.overloads.integration.test.d.ts.map +1 -0
  89. package/dist/userRepository/api/tests/integration/flagUserBehavior.integration.test.d.ts +2 -0
  90. package/dist/userRepository/api/tests/integration/flagUserBehavior.integration.test.d.ts.map +1 -0
  91. package/dist/userRepository/api/tests/integration/flagUserProfile.integration.test.d.ts +2 -0
  92. package/dist/userRepository/api/tests/integration/flagUserProfile.integration.test.d.ts.map +1 -0
  93. package/dist/userRepository/api/unflagUser.d.ts +26 -4
  94. package/dist/userRepository/api/unflagUser.d.ts.map +1 -1
  95. package/dist/userRepository/utils/submitUserReport.d.ts +10 -0
  96. package/dist/userRepository/utils/submitUserReport.d.ts.map +1 -0
  97. package/dist/userRepository/utils/userReport.d.ts +50 -0
  98. package/dist/userRepository/utils/userReport.d.ts.map +1 -0
  99. package/dist/utils/contentFlag.d.ts +28 -0
  100. package/dist/utils/contentFlag.d.ts.map +1 -0
  101. package/dist/utils/tests/dummy/channel.d.ts +4 -0
  102. package/dist/utils/tests/dummy/channel.d.ts.map +1 -1
  103. package/package.json +1 -1
package/dist/index.esm.js CHANGED
@@ -42,6 +42,33 @@ var UserReportTypeEnum;
42
42
  UserReportTypeEnum["AVATAR"] = "avatarFileId";
43
43
  UserReportTypeEnum["BEHAVIOR"] = "behavior";
44
44
  })(UserReportTypeEnum || (UserReportTypeEnum = {}));
45
+ /**
46
+ * The profile field a user is reported for in `UserRepository.flagUserProfile`.
47
+ * Each member is the backend `reportType` wire value. There is deliberately no
48
+ * behavior member: behavior reports need a reason and go through
49
+ * `UserRepository.flagUserBehavior`.
50
+ */
51
+ var UserProfileFieldEnum;
52
+ (function (UserProfileFieldEnum) {
53
+ UserProfileFieldEnum["DISPLAY_NAME"] = "displayName";
54
+ UserProfileFieldEnum["DESCRIPTION"] = "description";
55
+ UserProfileFieldEnum["AVATAR"] = "avatarFileId";
56
+ })(UserProfileFieldEnum || (UserProfileFieldEnum = {}));
57
+ /**
58
+ * Selects which of the caller's reports `UserRepository.unflagUser` withdraws, sent as the
59
+ * `reportType` query parameter. The four typed members are the backend `reportType` wire
60
+ * values and match `UserReportTypeEnum`. `UNTYPED` selects the typeless (legacy) report,
61
+ * which `isUserFlaggedByMe` returns as `reportType: null`; it is a selector only, never
62
+ * stored, and the flag endpoint refuses it.
63
+ */
64
+ var UserReportTypeFilterEnum;
65
+ (function (UserReportTypeFilterEnum) {
66
+ UserReportTypeFilterEnum["DISPLAY_NAME"] = "displayName";
67
+ UserReportTypeFilterEnum["DESCRIPTION"] = "description";
68
+ UserReportTypeFilterEnum["AVATAR"] = "avatarFileId";
69
+ UserReportTypeFilterEnum["BEHAVIOR"] = "behavior";
70
+ UserReportTypeFilterEnum["UNTYPED"] = "untyped";
71
+ })(UserReportTypeFilterEnum || (UserReportTypeFilterEnum = {}));
45
72
 
46
73
  const FileType = Object.freeze({
47
74
  FILE: 'file',
@@ -818,6 +845,18 @@ class AmityModuleNotEntitledError extends ASCApiError {
818
845
  this.type = 'AmityModuleNotEntitledError';
819
846
  }
820
847
  }
848
+ /**
849
+ * Input sanitization related error
850
+ * @category Errors
851
+ */
852
+ class ASCInvalidParameterError extends ASCError {
853
+ /**
854
+ * @param message A custom error message
855
+ */
856
+ constructor(message) {
857
+ super(message, 800110 /* Amity.ClientError.INVALID_PARAMETERS */, "error" /* Amity.ErrorLevel.ERROR */);
858
+ }
859
+ }
821
860
 
822
861
  let activeClient = null;
823
862
  /**
@@ -1591,6 +1630,24 @@ const sortByLastUpdated = ({ updatedAt: a = 0 }, { updatedAt: b = 0 }) => new Da
1591
1630
  * Sorting a collection by the items with most recent activity
1592
1631
  */
1593
1632
  const sortByLastActivity = ({ lastActivity: a }, { lastActivity: b }) => new Date(b).valueOf() - new Date(a).valueOf();
1633
+ /**
1634
+ * Sorting a channel collection by the time of its newest message (newest first).
1635
+ * Channels whose `lastMessageAt` is `null` sort last, ordered by `createdAt`
1636
+ * (newest first) among themselves, matching the server's `sortBy=lastMessage`
1637
+ * order so the local sort agrees with the page order.
1638
+ */
1639
+ const sortByLastMessage = (a, b) => {
1640
+ var _a, _b;
1641
+ const aAt = (_a = a.lastMessageAt) !== null && _a !== void 0 ? _a : null;
1642
+ const bAt = (_b = b.lastMessageAt) !== null && _b !== void 0 ? _b : null;
1643
+ if (aAt !== null && bAt !== null)
1644
+ return new Date(bAt).valueOf() - new Date(aAt).valueOf();
1645
+ if (aAt !== null)
1646
+ return -1;
1647
+ if (bAt !== null)
1648
+ return 1;
1649
+ return new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf();
1650
+ };
1594
1651
  /**
1595
1652
  * Filter items by untilAt timestamp based on sort direction
1596
1653
  * - For 'lastCreated' (newest first): untilAt acts as lower bound, excludes items older than boundary
@@ -1675,8 +1732,13 @@ const scheduleTask = (task) => {
1675
1732
  clearTimeout(timer);
1676
1733
  tasks.push(task);
1677
1734
  timer = setTimeout(() => {
1678
- tasks.forEach(fn => fn());
1735
+ // Swap the queue out before running it. A task that schedules another task
1736
+ // while the batch runs (an event handler that fires an event) must land in
1737
+ // a fresh queue and its own timer, not in the array being drained and then
1738
+ // discarded.
1739
+ const batch = tasks;
1679
1740
  tasks = [];
1741
+ batch.forEach(fn => fn());
1680
1742
  }, 0);
1681
1743
  };
1682
1744
 
@@ -8042,6 +8104,140 @@ const postCommentCountEngineOnLoginHandler = () => onSessionStateChange(state =>
8042
8104
  }
8043
8105
  });
8044
8106
 
8107
+ const BOTH_FIELDS = { lastActivity: true, lastMessageAt: true };
8108
+ /**
8109
+ * The channel cache is keyed by `channelInternalId`, but a message names its
8110
+ * channel by whichever id the producer had: the raw MQTT payload carries the
8111
+ * internal id, a converted `Amity.InternalMessage` carries the public id, and
8112
+ * the optimistic message carries the sub-channel's `channelId`. Try the direct
8113
+ * key first, then match any of the three ids.
8114
+ */
8115
+ const findCachedChannel = (channelId) => {
8116
+ var _a, _b, _c;
8117
+ const direct = (_a = pullFromCache(['channel', 'get', channelId])) === null || _a === void 0 ? void 0 : _a.data;
8118
+ if (direct)
8119
+ return direct;
8120
+ return (_c = (_b = queryCache(['channel', 'get'])) === null || _b === void 0 ? void 0 : _b.find(({ data }) => data.channelInternalId === channelId ||
8121
+ data.channelId === channelId ||
8122
+ data.channelPublicId === channelId)) === null || _c === void 0 ? void 0 : _c.data;
8123
+ };
8124
+ const isNewer = (stored, at) => !stored || convertDateStringToTimestamp(stored) < convertDateStringToTimestamp(at);
8125
+ const isSameInstant = (stored, at) => !!stored && convertDateStringToTimestamp(stored) === convertDateStringToTimestamp(at);
8126
+ /** Pushes the changed fields in one write and emits one `local.channel.updated`. */
8127
+ const commit = (channel, changes) => {
8128
+ if (Object.keys(changes).length === 0)
8129
+ return false;
8130
+ const updated = shallowClone(channel, changes);
8131
+ // Write back under the key the cache actually uses (the resolver key), not
8132
+ // under `channel.channelId`, which can differ from `channelInternalId`.
8133
+ pushToCache(['channel', 'get', getResolver('channel')(updated)], updated);
8134
+ fireEvent('local.channel.updated', {
8135
+ channels: [updated],
8136
+ channelUsers: [],
8137
+ users: [],
8138
+ files: [],
8139
+ });
8140
+ return true;
8141
+ };
8142
+ /**
8143
+ * Single owner of every local write to a channel's ordering timestamps,
8144
+ * `lastActivity` and `lastMessageAt` (ChannelActivityService v1).
8145
+ *
8146
+ * - Monotonic per field: a field moves only when `at` is newer than the stored
8147
+ * value (or the stored value is `null`), so a late or replayed event never
8148
+ * moves a channel down the list.
8149
+ * - One write: both fields are read, compared and pushed in a single cache
8150
+ * update, followed by one `local.channel.updated` event so every open channel
8151
+ * collection re-sorts once.
8152
+ * - Server channel payloads never go through here; they overwrite unguarded.
8153
+ *
8154
+ * @returns `true` when at least one field moved
8155
+ * @category private
8156
+ */
8157
+ const updateChannelActivityTimestamps = (channelId, at, fields = BOTH_FIELDS) => {
8158
+ const client = getActiveClient();
8159
+ if (!client.cache || !channelId || !at)
8160
+ return false;
8161
+ const channel = findCachedChannel(channelId);
8162
+ if (!channel)
8163
+ return false;
8164
+ const changes = {};
8165
+ if (fields.lastActivity && isNewer(channel.lastActivity, at))
8166
+ changes.lastActivity = at;
8167
+ if (fields.lastMessageAt && isNewer(channel.lastMessageAt, at))
8168
+ changes.lastMessageAt = at;
8169
+ return commit(channel, changes);
8170
+ };
8171
+ /**
8172
+ * Local correction for the current user's own send (ChannelActivityService v1
8173
+ * REQ-009, Trigger Matrix row 1b). Once the server confirms the message, each
8174
+ * field that still holds the device-clock `localAt` written at send time is
8175
+ * replaced by the server's `serverAt`, even when that is older. A field a newer
8176
+ * message has already moved is left alone. One write, one change notification,
8177
+ * nothing when neither field still holds `localAt`.
8178
+ *
8179
+ * @returns `true` when at least one field was replaced
8180
+ * @category private
8181
+ */
8182
+ const confirmOwnMessage = (channelId, localAt, serverAt) => {
8183
+ const client = getActiveClient();
8184
+ if (!client.cache || !channelId || !localAt || !serverAt)
8185
+ return false;
8186
+ if (isSameInstant(localAt, serverAt))
8187
+ return false;
8188
+ const channel = findCachedChannel(channelId);
8189
+ if (!channel)
8190
+ return false;
8191
+ const changes = {};
8192
+ if (isSameInstant(channel.lastActivity, localAt))
8193
+ changes.lastActivity = serverAt;
8194
+ if (isSameInstant(channel.lastMessageAt, localAt))
8195
+ changes.lastMessageAt = serverAt;
8196
+ return commit(channel, changes);
8197
+ };
8198
+
8199
+ /**
8200
+ * Session-wide owner of the local `lastActivity` / `lastMessageAt` updates
8201
+ * (ChannelActivityService v1, Web migration rows W1 – W5).
8202
+ *
8203
+ * Registered once per session from `setupLoginSubscriptions` and
8204
+ * `resumeSession`, so the update runs whether or not a channel collection is
8205
+ * open, and exactly once per message however many collections are open.
8206
+ *
8207
+ * It listens to the raw events rather than `onMessageCreatedMqtt`, so it does
8208
+ * not duplicate that subscriber's side effects (mark-read, unread recount).
8209
+ *
8210
+ * @category private
8211
+ */
8212
+ const channelActivityOnLoginHandler = () => {
8213
+ const client = getActiveClient();
8214
+ const disposers = [
8215
+ // Any user's new message, in any message feed of the channel. The raw
8216
+ // payload's `channelId` is the channel's internal id.
8217
+ createEventSubscriber(client, 'channelActivity', 'message.created', payload => {
8218
+ payload.messages.forEach(message => {
8219
+ updateChannelActivityTimestamps(message.channelId, message.createdAt);
8220
+ });
8221
+ }),
8222
+ // The current user's own send fires this three times: with the optimistic
8223
+ // message at send time (Syncing), on the create response (Synced), and on
8224
+ // failure (Error). The device-clock time of the Syncing / Error fires was
8225
+ // already applied synchronously in createMessageOptimistic, and replaying it
8226
+ // here could undo the server correction made on the response
8227
+ // (confirmOwnMessage). Only the server-confirmed fire goes through the guard.
8228
+ createEventSubscriber(client, 'channelActivity', 'local.message.created', payload => {
8229
+ payload.messages.forEach(message => {
8230
+ if (message.syncState !== "synced" /* Amity.SyncState.Synced */)
8231
+ return;
8232
+ updateChannelActivityTimestamps(message.channelId, message.createdAt);
8233
+ });
8234
+ }),
8235
+ ];
8236
+ return () => {
8237
+ disposers.forEach(fn => fn());
8238
+ };
8239
+ };
8240
+
8045
8241
  const EVENTS = [
8046
8242
  'disconnected',
8047
8243
  'error',
@@ -8231,7 +8427,7 @@ const setupLoginSubscriptions = (unsubWatcher) => {
8231
8427
  // NOTE: This is a temporary solution to handle the channel marker when the user is forced to leave
8232
8428
  // the channel because currently backend can't handle this, so every time a user is banned from
8233
8429
  // 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());
8430
+ onChannelDeleted(removeChannelMarkerCache), onChannelMemberBanned(removeChannelMarkerCache), markReadEngineOnLoginHandler(), analyticsEngineOnLoginHandler(), objectResolverEngineOnLoginHandler(), reactionSyncEngineOnLoginHandler(), blockedUserSyncEngineOnLoginHandler(), blockingUserSyncEngineOnLoginHandler(), blockRealtimeOnLoginHandler(), resetAutoSubscriptionsOnLogout(), CursorController.clearOnLogout(), postCommentCountEngineOnLoginHandler(), channelActivityOnLoginHandler());
8235
8431
  if (client.useLegacyUnreadCount) {
8236
8432
  subscriptions.push(readReceiptSyncEngineOnLoginHandler());
8237
8433
  }
@@ -9492,7 +9688,7 @@ const resumeSession = async (params, sessionHandler, config) => {
9492
9688
  // NOTE: This is a temporary solution to handle the channel marker when the user is forced to leave
9493
9689
  // the channel because currently backend can't handle this, so every time a user is banned from
9494
9690
  // 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());
9691
+ onChannelDeleted(removeChannelMarkerCache), onChannelMemberBanned(removeChannelMarkerCache), markReadEngineOnLoginHandler(), analyticsEngineOnLoginHandler(), objectResolverEngineOnLoginHandler(), channelActivityOnLoginHandler());
9496
9692
  if (client.useLegacyUnreadCount) {
9497
9693
  subscriptions$1.push(readReceiptSyncEngineOnLoginHandler());
9498
9694
  }
@@ -13451,7 +13647,7 @@ const getWatchSessionStorage = () => {
13451
13647
  return storageInstance;
13452
13648
  };
13453
13649
 
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-----";
13650
+ 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
13651
  /*
13456
13652
  * The crypto algorithm used for importing key and signing string
13457
13653
  */
@@ -15250,18 +15446,18 @@ const buildFlagUserBody = (options) => {
15250
15446
  /**
15251
15447
  * ```js
15252
15448
  * import { UserRepository } from '@amityco/ts-sdk'
15449
+ *
15450
+ * // Typeless report (supported).
15253
15451
  * 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
15452
  * ```
15260
15453
  *
15454
+ * Flags a user. Two call shapes are accepted (see the overloads): id-only, which
15455
+ * sends no body and stays supported, and the deprecated structured options object,
15456
+ * whose request mapping is unchanged. New code reporting a profile field or behavior
15457
+ * should use `flagUserProfile` or `flagUserBehavior`.
15458
+ *
15261
15459
  * @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.
15460
+ * @param options Deprecated report metadata; omit for a typeless report
15265
15461
  * @returns the flagged {@link Amity.User}
15266
15462
  *
15267
15463
  * @category User API
@@ -15282,25 +15478,224 @@ const flagUser = async (userId, options) => {
15282
15478
  };
15283
15479
  /* end_public_function */
15284
15480
 
15481
+ /**
15482
+ * Sends a validated split user report (`flagUserProfile` / `flagUserBehavior`) to the
15483
+ * same endpoint as `flagUser`, then persists the returned user and fires `user.flagged`
15484
+ * exactly as `flagUser` does, so all three calls share return, cache and event behaviour.
15485
+ *
15486
+ * Not exported from the public API.
15487
+ */
15488
+ const submitUserReport = async (apiName, userId, body) => {
15489
+ var _a;
15490
+ const client = getActiveClient();
15491
+ client.log(`user/${apiName}`, { userId, body });
15492
+ const { data } = await client.http.post(`/api/v4/me/flags/${encodeURIComponent(userId)}`, body);
15493
+ const payload = prepareUserPayload(data);
15494
+ if (client.cache) {
15495
+ ingestInCache(payload);
15496
+ }
15497
+ fireEvent('user.flagged', data);
15498
+ return (_a = payload.users.find(user => user.userId === userId)) !== null && _a !== void 0 ? _a : payload.users[0];
15499
+ };
15500
+
15501
+ /** Maximum `comment` length, in string length units, after trimming. */
15502
+ const USER_REPORT_COMMENT_MAX_LENGTH = 300;
15503
+ const PROFILE_FIELDS = Object.values(UserProfileFieldEnum);
15504
+ const BEHAVIOR_REASONS = Object.values(ContentFlagReasonEnum);
15505
+ const REPORT_TYPE_FILTERS = Object.values(UserReportTypeFilterEnum);
15506
+ const assertOptionsObject = (apiName, options, shape) => {
15507
+ if (typeof options !== 'object' || options === null || Array.isArray(options)) {
15508
+ throw new ASCInvalidParameterError(`${apiName}: expected an options object \`${shape}\``);
15509
+ }
15510
+ return options;
15511
+ };
15512
+ /**
15513
+ * Normalizes an optional reporter `comment`: it must be a string when supplied (`null`
15514
+ * is not a valid supplied comment); it is trimmed; a blank result is omitted (the
15515
+ * backend rejects `""`); a result longer than 300 characters is rejected before any
15516
+ * request. Otherwise the trimmed text is returned unchanged.
15517
+ *
15518
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` for a non-string or over-length comment
15519
+ */
15520
+ const normalizeUserReportComment = (apiName, comment) => {
15521
+ if (comment === undefined)
15522
+ return undefined;
15523
+ if (typeof comment !== 'string') {
15524
+ throw new ASCInvalidParameterError(`${apiName}: \`comment\` must be a string when supplied`);
15525
+ }
15526
+ const trimmed = comment.trim();
15527
+ if (trimmed.length === 0)
15528
+ return undefined;
15529
+ if (trimmed.length > USER_REPORT_COMMENT_MAX_LENGTH) {
15530
+ throw new ASCInvalidParameterError(`${apiName}: \`comment\` must be at most ${USER_REPORT_COMMENT_MAX_LENGTH} characters after trimming`);
15531
+ }
15532
+ return trimmed;
15533
+ };
15534
+ const withComment = (body, comment) => comment === undefined ? body : Object.assign(Object.assign({}, body), { comment });
15535
+ /**
15536
+ * Validates `flagUserProfile` options and builds its request body. Untyped callers can
15537
+ * pass anything at runtime, so nothing is trusted from the declared type. Any `reason`
15538
+ * key is ignored and never sent.
15539
+ *
15540
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when the options are malformed
15541
+ */
15542
+ const toUserProfileReportBody = (options) => {
15543
+ const apiName = 'flagUserProfile';
15544
+ const { field, comment } = assertOptionsObject(apiName, options, '{ field, comment? }');
15545
+ if (typeof field !== 'string' || !PROFILE_FIELDS.includes(field)) {
15546
+ throw new ASCInvalidParameterError(`${apiName}: \`field\` must be a \`UserProfileFieldEnum\` value`);
15547
+ }
15548
+ return withComment({ reportType: field }, normalizeUserReportComment(apiName, comment));
15549
+ };
15550
+ /**
15551
+ * Validates `flagUserBehavior` options and builds its request body. `reason` must be one
15552
+ * of the nine `ContentFlagReasonEnum` values and is sent as that canonical label;
15553
+ * `reportType` is always `behavior`.
15554
+ *
15555
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when the options are malformed
15556
+ */
15557
+ const toUserBehaviorReportBody = (options) => {
15558
+ const apiName = 'flagUserBehavior';
15559
+ const { reason, comment } = assertOptionsObject(apiName, options, '{ reason, comment? }');
15560
+ if (typeof reason !== 'string' || !BEHAVIOR_REASONS.includes(reason)) {
15561
+ throw new ASCInvalidParameterError(`${apiName}: \`reason\` must be a \`ContentFlagReasonEnum\` value`);
15562
+ }
15563
+ return withComment({ reportType: 'behavior', reason }, normalizeUserReportComment(apiName, comment));
15564
+ };
15565
+ /**
15566
+ * Validates the optional `{ reportType? }` of `unflagUser` and builds its query parameters.
15567
+ * Returns `undefined` when no `reportType` is given, so the request carries no query string
15568
+ * and withdraws every report the caller filed on the user. The server validates the value
15569
+ * too (422); this local check only saves the round-trip for the string alias, which admits
15570
+ * raw strings. Untyped callers can pass anything at runtime, so nothing is trusted from the
15571
+ * declared type.
15572
+ *
15573
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when the options are malformed or `reportType` is unknown
15574
+ */
15575
+ const toUserReportTypeFilter = (options) => {
15576
+ if (options === undefined)
15577
+ return undefined;
15578
+ const apiName = 'unflagUser';
15579
+ const { reportType } = assertOptionsObject(apiName, options, '{ reportType? }');
15580
+ if (reportType === undefined)
15581
+ return undefined;
15582
+ if (typeof reportType !== 'string' || !REPORT_TYPE_FILTERS.includes(reportType)) {
15583
+ throw new ASCInvalidParameterError(`${apiName}: \`reportType\` must be a \`UserReportTypeFilterEnum\` value`);
15584
+ }
15585
+ return { reportType: reportType };
15586
+ };
15587
+
15588
+ /* begin_public_function
15589
+ id: user.flag_profile
15590
+ */
15591
+ /**
15592
+ * ```js
15593
+ * import { UserRepository, UserProfileFieldEnum } from '@amityco/ts-sdk'
15594
+ * const flagged = await UserRepository.flagUserProfile('userId', {
15595
+ * field: UserProfileFieldEnum.AVATAR,
15596
+ * comment: 'Nudity in profile photo',
15597
+ * })
15598
+ * ```
15599
+ *
15600
+ * Reports one of a user's profile fields (display name, description or avatar). The
15601
+ * call has no reason: profile reports never carry one, so the backend's reason rules
15602
+ * cannot be violated from here. A `reason` key passed at runtime is ignored and never sent.
15603
+ *
15604
+ * `comment` is trimmed; a blank comment is omitted from the request; a comment longer
15605
+ * than 300 characters after trimming is rejected before any request. Otherwise the
15606
+ * trimmed text is sent unchanged.
15607
+ *
15608
+ * Sends `POST /api/v4/me/flags/:userId` with `{ reportType: <field>, comment? }`.
15609
+ *
15610
+ * @param userId The ID of the user to be reported
15611
+ * @param options `field` is a `UserProfileFieldEnum` value; `comment` is optional free text
15612
+ * @returns the flagged {@link Amity.User}
15613
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when `options` is missing or malformed, or `comment` is over 300 characters, before any request is made
15614
+ *
15615
+ * @category User API
15616
+ * @async
15617
+ * */
15618
+ const flagUserProfile = async (userId, options) => submitUserReport('flagUserProfile', userId, toUserProfileReportBody(options));
15619
+ /* end_public_function */
15620
+
15621
+ /* begin_public_function
15622
+ id: user.flag_behavior
15623
+ */
15624
+ /**
15625
+ * ```js
15626
+ * import { UserRepository, ContentFlagReasonEnum } from '@amityco/ts-sdk'
15627
+ * const flagged = await UserRepository.flagUserBehavior('userId', {
15628
+ * reason: ContentFlagReasonEnum.Others,
15629
+ * comment: 'Impersonating our brand',
15630
+ * })
15631
+ * ```
15632
+ *
15633
+ * Reports a user's behavior. `reason` is required and is one of the nine
15634
+ * `ContentFlagReasonEnum` values, sent as its canonical label. Reporter text, including
15635
+ * the explanation for `Others`, always travels in `comment`; the reason never carries text.
15636
+ *
15637
+ * `comment` is trimmed; a blank comment is omitted from the request; a comment longer
15638
+ * than 300 characters after trimming is rejected before any request. Otherwise the
15639
+ * trimmed text is sent unchanged.
15640
+ *
15641
+ * Sends `POST /api/v4/me/flags/:userId` with `{ reportType: 'behavior', reason, comment? }`.
15642
+ *
15643
+ * @param userId The ID of the user to be reported
15644
+ * @param options `reason` is a `ContentFlagReasonEnum` value; `comment` is optional free text
15645
+ * @returns the flagged {@link Amity.User}
15646
+ * @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
15647
+ *
15648
+ * @category User API
15649
+ * @async
15650
+ * */
15651
+ const flagUserBehavior = async (userId, options) => submitUserReport('flagUserBehavior', userId, toUserBehaviorReportBody(options));
15652
+ /* end_public_function */
15653
+
15285
15654
  /* begin_public_function
15286
15655
  id: user.unflag
15287
15656
  */
15288
15657
  /**
15289
15658
  * ```js
15290
- * import { UserRepository } from '@amityco/ts-sdk'
15291
- * const unflagged = await UserRepository.unflagUser('post', postId)
15659
+ * import { UserRepository, UserReportTypeFilterEnum } from '@amityco/ts-sdk'
15660
+ *
15661
+ * // Withdraw every report I filed on the user.
15662
+ * const unflagged = await UserRepository.unflagUser('userId')
15663
+ *
15664
+ * // Withdraw only my behavior report; my other reports on the user stay.
15665
+ * await UserRepository.unflagUser('userId', { reportType: UserReportTypeFilterEnum.BEHAVIOR })
15666
+ *
15667
+ * // Withdraw only my typeless (legacy) report.
15668
+ * await UserRepository.unflagUser('userId', { reportType: UserReportTypeFilterEnum.UNTYPED })
15292
15669
  * ```
15293
15670
  *
15671
+ * Withdraws the caller's reports on a user. Without `reportType` every report the caller
15672
+ * filed on that user is withdrawn, as before. With `reportType` only the caller's report
15673
+ * of that type is withdrawn; the caller's other reports and other users' reports are
15674
+ * untouched. Withdrawing a report that does not exist succeeds and changes nothing.
15675
+ *
15676
+ * The returned user is persisted to the cache, so its `hashFlag` says whether the caller
15677
+ * still holds any report on the user. Do not flip a local "flagged by me" flag to `false`
15678
+ * after a per-type withdrawal; read the fresh user, or call `isUserFlaggedByMe` for the
15679
+ * remaining per-type state (`flags`).
15680
+ *
15681
+ * Sends `DELETE /api/v4/me/flags/:userId`, with `?reportType=<type>` when one is given.
15682
+ *
15294
15683
  * @param userId The ID of the user to unflag
15295
- * @returns the deleted report result
15684
+ * @param options Optional; `reportType` selects a single report to withdraw. For a report that `isUserFlaggedByMe` lists with `reportType: null`, pass `UserReportTypeFilterEnum.UNTYPED`
15685
+ * @returns `true` once the server has processed the withdrawal
15686
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when `reportType` is not a `UserReportTypeFilterEnum` value, before any request is made
15296
15687
  *
15297
15688
  * @category User API
15298
15689
  * @async
15299
15690
  * */
15300
- const unflagUser = async (userId) => {
15301
- const client = getActiveClient();
15302
- client.log('user/unflag', userId);
15303
- const { data } = await client.http.delete(`/api/v4/me/flags/${encodeURIComponent(userId)}`);
15691
+ const unflagUser = async (userId, options) => {
15692
+ const client = getActiveClient();
15693
+ client.log('user/unflag', { userId, options });
15694
+ const params = toUserReportTypeFilter(options);
15695
+ const url = `/api/v4/me/flags/${encodeURIComponent(userId)}`;
15696
+ const { data } = params
15697
+ ? await client.http.delete(url, { params })
15698
+ : await client.http.delete(url);
15304
15699
  const payload = prepareUserPayload(data);
15305
15700
  if (client.cache) {
15306
15701
  ingestInCache(payload);
@@ -15316,11 +15711,29 @@ const unflagUser = async (userId) => {
15316
15711
  /**
15317
15712
  * ```js
15318
15713
  * import { UserRepository } from '@amityco/ts-sdk'
15319
- * const isFlagged = await UserRepository.isUserFlaggedByMe(postId)
15714
+ *
15715
+ * const { isFlagByMe, flags } = await UserRepository.isUserFlaggedByMe('userId')
15716
+ *
15717
+ * // Grey out the report options already used; null marks the typeless report.
15718
+ * const used = new Set(flags.map(flag => flag.reportType))
15320
15719
  * ```
15321
15720
  *
15322
- * @param userId The ID of the thing to check a report to.
15323
- * @returns `true` if the report is created by me, `false` if doesn't.
15721
+ * Returns whether the caller has reported a user, with each of the caller's own reports
15722
+ * on that user, exactly as `GET /api/v4/users/:userId/isFlagByMe` answers. Each report
15723
+ * carries `reportType`, `reason`, `comment` and `createdAt`; a field the report does not
15724
+ * have is `null`. `reportType: null` is the typeless (legacy) report, which `unflagUser`
15725
+ * withdraws with `UserReportTypeFilterEnum.UNTYPED`. Only the caller's reports come back.
15726
+ *
15727
+ * This is the only source of per-type state. Nothing is filtered, cached or persisted:
15728
+ * the call reads the server every time and does not consult `hashFlag`, which is a
15729
+ * type-blind bloom filter. Call it again after a flag or unflag when the UI needs the
15730
+ * updated per-type state.
15731
+ *
15732
+ * Breaking change: earlier versions returned a boolean from the v3 endpoint, which the
15733
+ * backend has deprecated. Read `isFlagByMe` on the result for the same answer.
15734
+ *
15735
+ * @param userId The ID of the user to check
15736
+ * @returns the caller's {@link Amity.UserFlagByMe} on that user
15324
15737
  *
15325
15738
  * @category Report API
15326
15739
  * @async
@@ -15328,8 +15741,8 @@ const unflagUser = async (userId) => {
15328
15741
  const isUserFlaggedByMe = async (userId) => {
15329
15742
  const client = getActiveClient();
15330
15743
  client.log('user/isUserFlaggedByMe', userId);
15331
- const { data: { isFlagByMe }, } = await client.http.get(`/api/v3/users/${userId}/isFlagByMe`);
15332
- return isFlagByMe;
15744
+ const { data } = await client.http.get(`/api/v4/users/${encodeURIComponent(userId)}/isFlagByMe`);
15745
+ return data;
15333
15746
  };
15334
15747
  /* end_public_function */
15335
15748
 
@@ -16456,6 +16869,8 @@ var index$r = /*#__PURE__*/Object.freeze({
16456
16869
  getUserByIds: getUserByIds,
16457
16870
  updateUser: updateUser,
16458
16871
  flagUser: flagUser,
16872
+ flagUserProfile: flagUserProfile,
16873
+ flagUserBehavior: flagUserBehavior,
16459
16874
  unflagUser: unflagUser,
16460
16875
  isUserFlaggedByMe: isUserFlaggedByMe,
16461
16876
  getAllBlockedUsers: getAllBlockedUsers,
@@ -18877,7 +19292,8 @@ const getPostReport = async ({ client, referenceId, }) => {
18877
19292
  };
18878
19293
  const getUserReport = async ({ client, referenceId, }) => {
18879
19294
  var _a;
18880
- const { data } = await client.http.get(`/api/v3/users/${referenceId}/isflagbyme`);
19295
+ // v3 isFlagByMe is deprecated by the backend; v4 answers the same boolean plus the caller's flags.
19296
+ const { data } = await client.http.get(`/api/v4/users/${encodeURIComponent(referenceId)}/isFlagByMe`);
18881
19297
  const { result, isFlagByMe } = data !== null && data !== void 0 ? data : {};
18882
19298
  return (_a = result !== null && result !== void 0 ? result : isFlagByMe) !== null && _a !== void 0 ? _a : false;
18883
19299
  };
@@ -19494,7 +19910,7 @@ const getLocalId = () => `LOCAL_${uuid()}`;
19494
19910
  // FIXME: temp solution
19495
19911
  let uniqueId;
19496
19912
  const createMessageOptimistic = (bundle) => {
19497
- var _a, _b;
19913
+ var _a, _b, _c, _d;
19498
19914
  const client = getActiveClient();
19499
19915
  if (!client.cache)
19500
19916
  return;
@@ -19525,9 +19941,20 @@ const createMessageOptimistic = (bundle) => {
19525
19941
  }
19526
19942
  // as reused to update created and updated time, which should be the same
19527
19943
  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);
19944
+ const message = Object.assign({
19945
+ // The optimistic message must name its channel (public id) so the own-send
19946
+ // update can move lastActivity / lastMessageAt at send time. Take it from the
19947
+ // cached sub-channel; when that is not cached, resolve the sub-channel id
19948
+ // through the channel cache, which for a channel's default sub-channel is
19949
+ // the channel itself (ChannelActivityService migration row I2). A thread or
19950
+ // unknown channel resolves to undefined, as before.
19951
+ 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
19952
  const cachedAt = UNSYNCED_OBJECT_CACHED_AT_VALUE;
19530
19953
  pushToCache(['message', 'get', message.messageId], message, { cachedAt });
19954
+ // Own-send update (ChannelActivityService row 1): move the channel at send
19955
+ // time, synchronously, so the confirmation on the send response always sees
19956
+ // this value rather than racing the scheduled local event below.
19957
+ updateChannelActivityTimestamps(message.channelId, message.createdAt);
19531
19958
  fireEvent('local.message.created', { messages: [message] });
19532
19959
  return message;
19533
19960
  };
@@ -19567,6 +19994,9 @@ const createMessage = async (bundle) => {
19567
19994
  if (client.cache) {
19568
19995
  ingestInCache(data, { cachedAt });
19569
19996
  }
19997
+ // The own-send update wrote the device-clock createdAt; replace it with the
19998
+ // server's now that the message is confirmed (ChannelActivityService REQ-009).
19999
+ confirmOwnMessage(optimisticData === null || optimisticData === void 0 ? void 0 : optimisticData.channelId, optimisticData === null || optimisticData === void 0 ? void 0 : optimisticData.createdAt, messages[0].createdAt);
19570
20000
  fireEvent('local.message.created', {
19571
20001
  messages: [Object.assign(Object.assign({}, messages[0]), { syncState: "synced" /* Amity.SyncState.Synced */ })],
19572
20002
  });
@@ -20160,23 +20590,85 @@ getDeliveredUsers.locally = (query) => {
20160
20590
  return { data: users, cachedAt, prevPage, nextPage };
20161
20591
  };
20162
20592
 
20163
- /* begin_public_function
20164
- id: message.flag
20165
- */
20593
+ const CONTENT_FLAG_REASONS = Object.values(ContentFlagReasonEnum);
20166
20594
  /**
20167
- * ```js
20168
- * import { MessageRepository } from '@amityco/ts-sdk'
20169
- * const flagged = await MessageRepository.flagMessage(messageId, reason)
20170
- * ```
20595
+ * True when a public `flagX(id, input)` argument is the legacy shape (a reason string
20596
+ * or nothing). Anything else is routed to the `Amity.ContentFlagOptions` path, whose
20597
+ * validation rejects malformed values before any request is made.
20598
+ */
20599
+ const isLegacyContentFlagReason = (input) => input === undefined || typeof input === 'string';
20600
+ /**
20601
+ * Validates an `Amity.ContentFlagOptions` argument. Untyped (plain JS) callers can pass
20602
+ * anything at runtime, so the checks do not trust the declared type: the value must be a
20603
+ * plain object, `reason` must be one of the nine `ContentFlagReasonEnum` values and
20604
+ * `detail`, when supplied, must be a string (`null` is not a valid supplied detail).
20171
20605
  *
20172
- * @param messageId of the message to flag
20173
- * @param reason the reason to flag the message
20174
- * @returns the created report result
20606
+ * @param apiName used in the error message, e.g. `flagPost`
20607
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when the options are malformed
20608
+ */
20609
+ const validateContentFlagOptions = (apiName, options) => {
20610
+ if (typeof options !== 'object' || options === null || Array.isArray(options)) {
20611
+ throw new ASCInvalidParameterError(`${apiName}: expected an options object \`{ reason, detail? }\``);
20612
+ }
20613
+ const { reason, detail } = options;
20614
+ if (typeof reason !== 'string' || !CONTENT_FLAG_REASONS.includes(reason)) {
20615
+ throw new ASCInvalidParameterError(`${apiName}: \`reason\` must be a \`ContentFlagReasonEnum\` value`);
20616
+ }
20617
+ if (detail !== undefined && typeof detail !== 'string') {
20618
+ throw new ASCInvalidParameterError(`${apiName}: \`detail\` must be a string when supplied`);
20619
+ }
20620
+ return { reason: reason, detail };
20621
+ };
20622
+ /**
20623
+ * Builds the request body for validated options. `detail` is forwarded unchanged (empty
20624
+ * and whitespace-only strings included) and omitted from the body when `undefined`; the
20625
+ * backend enforces the 300-character limit.
20626
+ */
20627
+ const toContentFlagBody = ({ reason, detail }) => detail === undefined ? { reason } : { reason, detail };
20628
+
20629
+ /**
20630
+ * New `flagMessage(messageId, { reason, detail? })` implementation. Any category,
20631
+ * including `Others`, may carry a `detail`. `detail` is forwarded unchanged (empty and
20632
+ * whitespace-only strings included) and omitted from the request body when `undefined`;
20633
+ * the backend enforces the 300-character limit.
20175
20634
  *
20176
- * @category Message API
20177
- * @async
20178
- * */
20179
- const flagMessage = async (messageId, reason) => {
20635
+ * Not exported from the public API. It is reached through the
20636
+ * `MessageRepository.flagMessage(messageId, options)` overload; see `api/flagMessage.ts`.
20637
+ *
20638
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when `options` is malformed, before any request is made
20639
+ */
20640
+ const flagMessageWithOptions = async (messageId, options) => {
20641
+ // Validate first: an input error must surface as INVALID_PARAMETERS regardless of
20642
+ // whether a client is active, and validation needs nothing from the client.
20643
+ const body = toContentFlagBody(validateContentFlagOptions('flagMessage', options));
20644
+ const client = getActiveClient();
20645
+ client.log('message/flag', messageId);
20646
+ const { data: payload } = await client.http.post(`/api/v5/messages/${encodeURIComponent(messageId)}/flags`, body);
20647
+ if (client.cache) {
20648
+ const messagePayload = await prepareMessagePayload(payload);
20649
+ ingestInCache(messagePayload);
20650
+ }
20651
+ fireEvent('message.flagged', payload);
20652
+ return !!payload;
20653
+ };
20654
+
20655
+ /**
20656
+ * Legacy `flagMessage(messageId, reason?)` implementation, kept verbatim so existing
20657
+ * callers keep the exact request body they had before the options contract existed:
20658
+ *
20659
+ * - predefined `ContentFlagReasonEnum` value → `{ reason: <value>, detail: '' }`
20660
+ * - any other non-empty string (including the literal `'Others'`) → `{ reason: 'Others', detail: <string> }`
20661
+ * - `undefined` or `''` → `{ reason: 'Others', detail: '' }`
20662
+ * (for `undefined` the message body carries `detail: undefined`, which JSON serialization omits)
20663
+ *
20664
+ * Not exported from the public API. It is reached through the deprecated
20665
+ * `MessageRepository.flagMessage(messageId, reason)` overload; see `api/flagMessage.ts`.
20666
+ *
20667
+ * Not itself deprecated: it implements the deprecated legacy-string overload of the public
20668
+ * `flagMessage`, which carries the deprecation tag. New code should use `flagMessageWithOptions`
20669
+ * (the `{ reason, detail? }` contract). This path stays available throughout the current major version.
20670
+ */
20671
+ const flagMessageWithReasonString = async (messageId, reason) => {
20180
20672
  const client = getActiveClient();
20181
20673
  client.log('message/flag', messageId);
20182
20674
  const isPredefinedReason = reason &&
@@ -20193,6 +20685,42 @@ const flagMessage = async (messageId, reason) => {
20193
20685
  fireEvent('message.flagged', payload);
20194
20686
  return !!payload;
20195
20687
  };
20688
+
20689
+ /* begin_public_function
20690
+ id: message.flag
20691
+ */
20692
+ /**
20693
+ * ```js
20694
+ * import { MessageRepository, ContentFlagReasonEnum } from '@amityco/ts-sdk'
20695
+ *
20696
+ * // No reason.
20697
+ * await MessageRepository.flagMessage(messageId)
20698
+ *
20699
+ * // Category plus optional reporter context (any category, including Others).
20700
+ * await MessageRepository.flagMessage(messageId, {
20701
+ * reason: ContentFlagReasonEnum.SpamOrScams,
20702
+ * detail: 'Repeated promotional links',
20703
+ * })
20704
+ * ```
20705
+ *
20706
+ * Reports a message to moderation. Three call shapes are accepted (see the overloads):
20707
+ * id-only, an options object, and the deprecated legacy reason string. The public
20708
+ * name only dispatches on the input shape; the two implementations are kept separate
20709
+ * so the legacy path is untouched by the new contract:
20710
+ *
20711
+ * - `undefined` or a string → `flagMessageWithReasonString` (unchanged legacy mapping)
20712
+ * - anything else → `flagMessageWithOptions` (validated `{ reason, detail? }`)
20713
+ *
20714
+ * @param messageId The ID of the message to flag
20715
+ * @param input an options object `{ reason, detail? }`, a legacy reason string, or nothing
20716
+ * @returns `true` when the report was created
20717
+ *
20718
+ * @category Message API
20719
+ * @async
20720
+ * */
20721
+ const flagMessage = (messageId, input) => isLegacyContentFlagReason(input)
20722
+ ? flagMessageWithReasonString(messageId, input)
20723
+ : flagMessageWithOptions(messageId, input);
20196
20724
  /* end_public_function */
20197
20725
 
20198
20726
  /* begin_public_function
@@ -21912,6 +22440,9 @@ class ChannelLiveCollectionController extends LiveCollectionController {
21912
22440
  case 'lastCreated':
21913
22441
  channels = channels.sort(sortByLastCreated);
21914
22442
  break;
22443
+ case 'lastMessage':
22444
+ channels = channels.sort(sortByLastMessage);
22445
+ break;
21915
22446
  case 'displayName':
21916
22447
  /*
21917
22448
  * The server returns channels with empty | null displayName's first before
@@ -21972,9 +22503,8 @@ class ChannelLiveCollectionController extends LiveCollectionController {
21972
22503
  objectResolverEngine.resolve(message.channelId, "channel" /* Amity.ReferenceType.CHANNEL */);
21973
22504
  }
21974
22505
  else {
21975
- updateChannelCache(channelCache, {
21976
- lastActivity: message.createdAt,
21977
- });
22506
+ // lastActivity / lastMessageAt are moved by channelActivityOnLoginHandler
22507
+ // (session-wide, monotonic, one write). This listener only re-sorts.
21978
22508
  callback(message);
21979
22509
  }
21980
22510
  });
@@ -21992,9 +22522,7 @@ class ChannelLiveCollectionController extends LiveCollectionController {
21992
22522
  ])) === null || _a === void 0 ? void 0 : _a.data;
21993
22523
  if (!cacheData)
21994
22524
  return;
21995
- updateChannelCache(cacheData, {
21996
- lastActivity: message.createdAt,
21997
- });
22525
+ // Timestamps are moved by channelActivityOnLoginHandler; only re-sort here.
21998
22526
  callback(message);
21999
22527
  });
22000
22528
  }, 'channelId', 'channel'),
@@ -22732,9 +23260,7 @@ class SearchChannelLiveCollectionController extends LiveCollectionController {
22732
23260
  ])) === null || _a === void 0 ? void 0 : _a.data;
22733
23261
  if (!cacheData)
22734
23262
  return;
22735
- updateChannelCache(cacheData, {
22736
- lastActivity: message.createdAt,
22737
- });
23263
+ // Timestamps are moved by channelActivityOnLoginHandler; only re-sort here.
22738
23264
  callback(message);
22739
23265
  });
22740
23266
  }, 'channelId', 'channel'),
@@ -22751,9 +23277,7 @@ class SearchChannelLiveCollectionController extends LiveCollectionController {
22751
23277
  ])) === null || _a === void 0 ? void 0 : _a.data;
22752
23278
  if (!cacheData)
22753
23279
  return;
22754
- updateChannelCache(cacheData, {
22755
- lastActivity: message.createdAt,
22756
- });
23280
+ // Timestamps are moved by channelActivityOnLoginHandler; only re-sort here.
22757
23281
  callback(message);
22758
23282
  });
22759
23283
  }, 'channelId', 'channel'),
@@ -27338,23 +27862,22 @@ const declineComment = async (commentId, reason) => {
27338
27862
  };
27339
27863
  /* end_public_function */
27340
27864
 
27341
- /* begin_public_function
27342
- id: comment.flag
27343
- */
27344
27865
  /**
27345
- * ```js
27346
- * import { CommentRepository } from '@amityco/ts-sdk'
27347
- * const flagged = await CommentRepository.flagComment(commentId, reason)
27348
- * ```
27866
+ * Legacy `flagComment(commentId, reason?)` implementation, kept verbatim so existing
27867
+ * callers keep the exact request body they had before the options contract existed:
27349
27868
  *
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
27869
+ * - predefined `ContentFlagReasonEnum` value → `{ reason: <value>, detail: '' }`
27870
+ * - any other non-empty string (including the literal `'Others'`) → `{ reason: 'Others', detail: <string> }`
27871
+ * - `undefined` or `''` → `{ reason: 'Others', detail: '' }`
27353
27872
  *
27354
- * @category Comment API
27355
- * @async
27356
- * */
27357
- const flagComment = async (commentId, reason) => {
27873
+ * Not exported from the public API. It is reached through the deprecated
27874
+ * `CommentRepository.flagComment(commentId, reason)` overload; see `api/flagComment.ts`.
27875
+ *
27876
+ * Not itself deprecated: it implements the deprecated legacy-string overload of the public
27877
+ * `flagComment`, which carries the deprecation tag. New code should use `flagCommentWithOptions`
27878
+ * (the `{ reason, detail? }` contract). This path stays available throughout the current major version.
27879
+ */
27880
+ const flagCommentWithReasonString = async (commentId, reason) => {
27358
27881
  const client = getActiveClient();
27359
27882
  client.log('comment/flagComment', commentId);
27360
27883
  const isPredefinedReason = reason &&
@@ -27370,6 +27893,67 @@ const flagComment = async (commentId, reason) => {
27370
27893
  fireEvent('comment.flagged', payload);
27371
27894
  return !!payload;
27372
27895
  };
27896
+
27897
+ /**
27898
+ * New `flagComment(commentId, { reason, detail? })` implementation. Any category,
27899
+ * including `Others`, may carry a `detail`. `detail` is forwarded unchanged (empty and
27900
+ * whitespace-only strings included) and omitted from the request body when `undefined`;
27901
+ * the backend enforces the 300-character limit.
27902
+ *
27903
+ * Not exported from the public API. It is reached through the
27904
+ * `CommentRepository.flagComment(commentId, options)` overload; see `api/flagComment.ts`.
27905
+ *
27906
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when `options` is malformed, before any request is made
27907
+ */
27908
+ const flagCommentWithOptions = async (commentId, options) => {
27909
+ // Validate first: an input error must surface as INVALID_PARAMETERS regardless of
27910
+ // whether a client is active, and validation needs nothing from the client.
27911
+ const body = toContentFlagBody(validateContentFlagOptions('flagComment', options));
27912
+ const client = getActiveClient();
27913
+ client.log('comment/flagComment', commentId);
27914
+ const { data: payload } = await client.http.post(`/api/v3/comments/${encodeURIComponent(commentId)}/flag`, body);
27915
+ if (client.cache) {
27916
+ ingestInCache(payload);
27917
+ }
27918
+ fireEvent('comment.flagged', payload);
27919
+ return !!payload;
27920
+ };
27921
+
27922
+ /* begin_public_function
27923
+ id: comment.flag
27924
+ */
27925
+ /**
27926
+ * ```js
27927
+ * import { CommentRepository, ContentFlagReasonEnum } from '@amityco/ts-sdk'
27928
+ *
27929
+ * // No reason: sent as Others with an empty detail.
27930
+ * await CommentRepository.flagComment(commentId)
27931
+ *
27932
+ * // Category plus optional reporter context (any category, including Others).
27933
+ * await CommentRepository.flagComment(commentId, {
27934
+ * reason: ContentFlagReasonEnum.SpamOrScams,
27935
+ * detail: 'Repeated promotional links',
27936
+ * })
27937
+ * ```
27938
+ *
27939
+ * Reports a comment to moderation. Three call shapes are accepted (see the overloads):
27940
+ * id-only, an options object, and the deprecated legacy reason string. The public
27941
+ * name only dispatches on the input shape; the two implementations are kept separate
27942
+ * so the legacy path is untouched by the new contract:
27943
+ *
27944
+ * - `undefined` or a string → `flagCommentWithReasonString` (unchanged legacy mapping)
27945
+ * - anything else → `flagCommentWithOptions` (validated `{ reason, detail? }`)
27946
+ *
27947
+ * @param commentId The ID of the comment to flag
27948
+ * @param input an options object `{ reason, detail? }`, a legacy reason string, or nothing
27949
+ * @returns `true` when the report was created
27950
+ *
27951
+ * @category Comment API
27952
+ * @async
27953
+ * */
27954
+ const flagComment = (commentId, input) => isLegacyContentFlagReason(input)
27955
+ ? flagCommentWithReasonString(commentId, input)
27956
+ : flagCommentWithOptions(commentId, input);
27373
27957
  /* end_public_function */
27374
27958
 
27375
27959
  /* begin_public_function
@@ -29723,23 +30307,47 @@ const declinePost = async (postId) => {
29723
30307
  };
29724
30308
  /* end_public_function */
29725
30309
 
29726
- /* begin_public_function
29727
- id: post.flag
29728
- */
29729
30310
  /**
29730
- * ```js
29731
- * import { PostRepository } from '@amityco/ts-sdk'
29732
- * const flagged = await PostRepository.flagPost(postId, reason)
29733
- * ```
30311
+ * New `flagPost(postId, { reason, detail? })` implementation. Any category, including
30312
+ * `Others`, may carry a `detail`. `detail` is forwarded unchanged (empty and
30313
+ * whitespace-only strings included) and omitted from the request body when `undefined`;
30314
+ * the backend enforces the 300-character limit.
29734
30315
  *
29735
- * @param postId of the post to flag
29736
- * @param reason the reason to flag the post
29737
- * @returns a boolean
30316
+ * Not exported from the public API. It is reached through the
30317
+ * `PostRepository.flagPost(postId, options)` overload; see `api/flagPost.ts`.
29738
30318
  *
29739
- * @category Post API
29740
- * @async
29741
- * */
29742
- const flagPost = async (postId, reason) => {
30319
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when `options` is malformed, before any request is made
30320
+ */
30321
+ const flagPostWithOptions = async (postId, options) => {
30322
+ // Validate first: an input error must surface as INVALID_PARAMETERS regardless of
30323
+ // whether a client is active, and validation needs nothing from the client.
30324
+ const body = toContentFlagBody(validateContentFlagOptions('flagPost', options));
30325
+ const client = getActiveClient();
30326
+ client.log('post/flagPost', postId);
30327
+ const { data: payload } = await client.http.post(`/api/v3/posts/${encodeURIComponent(postId)}/flag`, body);
30328
+ if (client.cache) {
30329
+ ingestInCache(prepareMembershipPayload(payload, 'communityUsers'));
30330
+ }
30331
+ fireEvent('post.flagged', payload);
30332
+ return !!payload;
30333
+ };
30334
+
30335
+ /**
30336
+ * Legacy `flagPost(postId, reason?)` implementation, kept verbatim so existing
30337
+ * callers keep the exact request body they had before the options contract existed:
30338
+ *
30339
+ * - predefined `ContentFlagReasonEnum` value → `{ reason: <value>, detail: '' }`
30340
+ * - any other non-empty string (including the literal `'Others'`) → `{ reason: 'Others', detail: <string> }`
30341
+ * - `undefined` or `''` → `{ reason: 'Others', detail: '' }`
30342
+ *
30343
+ * Not exported from the public API. It is reached through the deprecated
30344
+ * `PostRepository.flagPost(postId, reason)` overload; see `api/flagPost.ts`.
30345
+ *
30346
+ * Not itself deprecated: it implements the deprecated legacy-string overload of the public
30347
+ * `flagPost`, which carries the deprecation tag. New code should use `flagPostWithOptions`
30348
+ * (the `{ reason, detail? }` contract). This path stays available throughout the current major version.
30349
+ */
30350
+ const flagPostWithReasonString = async (postId, reason) => {
29743
30351
  const client = getActiveClient();
29744
30352
  client.log('post/flagPost', postId);
29745
30353
  const isPredefinedReason = reason &&
@@ -29755,6 +30363,42 @@ const flagPost = async (postId, reason) => {
29755
30363
  fireEvent('post.flagged', payload);
29756
30364
  return !!payload;
29757
30365
  };
30366
+
30367
+ /* begin_public_function
30368
+ id: post.flag
30369
+ */
30370
+ /**
30371
+ * ```js
30372
+ * import { PostRepository, ContentFlagReasonEnum } from '@amityco/ts-sdk'
30373
+ *
30374
+ * // No reason.
30375
+ * await PostRepository.flagPost(postId)
30376
+ *
30377
+ * // Category plus optional reporter context (any category, including Others).
30378
+ * await PostRepository.flagPost(postId, {
30379
+ * reason: ContentFlagReasonEnum.SpamOrScams,
30380
+ * detail: 'Repeated promotional links',
30381
+ * })
30382
+ * ```
30383
+ *
30384
+ * Reports a post to moderation. Three call shapes are accepted (see the overloads):
30385
+ * id-only, an options object, and the deprecated legacy reason string. The public
30386
+ * name only dispatches on the input shape; the two implementations are kept separate
30387
+ * so the legacy path is untouched by the new contract:
30388
+ *
30389
+ * - `undefined` or a string → `flagPostWithReasonString` (unchanged legacy mapping)
30390
+ * - anything else → `flagPostWithOptions` (validated `{ reason, detail? }`)
30391
+ *
30392
+ * @param postId The ID of the post to flag
30393
+ * @param input an options object `{ reason, detail? }`, a legacy reason string, or nothing
30394
+ * @returns `true` when the report was created
30395
+ *
30396
+ * @category Post API
30397
+ * @async
30398
+ * */
30399
+ const flagPost = (postId, input) => isLegacyContentFlagReason(input)
30400
+ ? flagPostWithReasonString(postId, input)
30401
+ : flagPostWithOptions(postId, input);
29758
30402
  /* end_public_function */
29759
30403
 
29760
30404
  /* begin_public_function
@@ -37135,4 +37779,4 @@ class AmityAttachmentProductTags {
37135
37779
  }
37136
37780
  }
37137
37781
 
37138
- export { API_REGIONS, index$6 as AdRepository, AmityAttachmentProductTags, AmityAutoSubscription, AmityChannelNotificationModeEnum, AmityCommunityType, AmityEventOrderOption, AmityEventOriginType, AmityEventResponseStatus, AmityEventSortOption, AmityEventStatus, AmityEventType, AmityLiveViewerCountMode, AmityModuleNotEntitledError, AmitySharableContentType, AnalyticsSourceTypeEnum, index$g as CategoryRepository, index$k as ChannelRepository, index$t as Client, index$f as CommentRepository, CommunityNotificationEventNameEnum, CommunityPostSettingMaps, CommunityPostSettings, index$h as CommunityRepository, ContentFeedType, ContentFlagReasonEnum, index as CuratedContentRepository, DefaultCommunityPostSetting, index$2 as EventRepository, FeedDataTypeEnum, index$e as FeedRepository, FeedSortByEnum, FeedSourceEnum, FeedTypeEnum, FileAccessTypeEnum, index$q as FileRepository, FileType, GET_WATCHER_URLS, index$4 as InvitationRepository, InvitationSortByEnum, InvitationStatusEnum, InvitationTargetTypeEnum, InvitationTypeEnum, JoinRequestStatusEnum, JoinResultStatusEnum, index$3 as LiveReactionRepository, index$8 as LiveStreamPlayer, MembershipAcceptanceTypeEnum, MessageContentType, index$o as MessageRepository, NotificationRolesFilterTypeEnum, NotificationSettingsLevelEnum, index$9 as PollRepository, PostContentType, index$c as PostRepository, PostStructureType, index$1 as ProductRepository, index$p as ReactionRepository, index$a as RoomPresenceRepository, index$d as RoomRepository, SearchUsersByEnum, index$7 as StoryRepository, index$b as StreamRepository, index$n as SubChannelRepository, SubscriptionLevels, UserNotificationModuleNameEnum, UserReportTypeEnum, index$r as UserRepository, UserTypeEnum, VERSION, VideoResolution, VideoSize, VideoTranscodingStatus, backupCache, createQuery, createReport, createUserToken, deleteReport, disableCache, dropFromCache, enableCache, exceedsUntilAtBoundary, filterByChannelMembership, filterByCommunityMembership, filterByFeedType, filterByPostDataTypes, filterByPropEquality, filterByPropInclusion, filterByPropIntersection, filterBySearchTerm, filterByStringComparePartially, filterByUntilAt, filterCommentsByBlockUsers, filterPostsByBlockUsers, getBlockUsersId, getChannelTopic, getCommentTopic, getCommunityStoriesTopic, getCommunityTopic, getLiveReactionTopic, getLiveStreamTopic, getMarkedMessageTopic, getMarkerUserFeedTopic, getMessageTopic, getMyFollowersTopic, getMyFollowingsTopic, getNetworkId, getNetworkTopic, getPostTopic, getRole, getRoomStreamerTopic, getRoomWatcherTopic, getSmartFeedChannelTopic, getSmartFeedMessageTopic, getSmartFeedSubChannelTopic, getStoryTopic, getSubChannelTopic, getUserTopic, isAfterBefore, isAfterBeforeRaw, isCachable, isFetcher, isFresh, isLocal, isMutator, isOffline, isPaged, isReportedByMe, isSkip, mergeInCache, index$5 as notificationTray, onChannelMarkerFetched, onFeedMarkerFetched, onFeedMarkerUpdated, onMessageMarked, onMessageMarkerFetched, onSubChannelMarkerFetched, onSubChannelMarkerUpdated, onUserMarkerFetched, onUserMarkerFetchedLegacy, pullFromCache, pushToCache, queryCache, queryOptions, queryRoles, resolveModuleAvailability, restoreCache, runQuery, sortByChannelSegment, sortByDisplayName, sortByFirstCreated, sortByFirstUpdated, sortByLastActivity, sortByLastCreated, sortByLastUpdated, sortByLocalSortingDate, sortByName, sortBySegmentNumber, subscribeTopic, toPage, toPageRaw, toToken, upsertInCache, validateUntilAt, wipeCache };
37782
+ export { API_REGIONS, index$6 as AdRepository, AmityAttachmentProductTags, AmityAutoSubscription, AmityChannelNotificationModeEnum, AmityCommunityType, AmityEventOrderOption, AmityEventOriginType, AmityEventResponseStatus, AmityEventSortOption, AmityEventStatus, AmityEventType, AmityLiveViewerCountMode, AmityModuleNotEntitledError, AmitySharableContentType, AnalyticsSourceTypeEnum, index$g as CategoryRepository, index$k as ChannelRepository, index$t as Client, index$f as CommentRepository, CommunityNotificationEventNameEnum, CommunityPostSettingMaps, CommunityPostSettings, index$h as CommunityRepository, ContentFeedType, ContentFlagReasonEnum, index as CuratedContentRepository, DefaultCommunityPostSetting, index$2 as EventRepository, FeedDataTypeEnum, index$e as FeedRepository, FeedSortByEnum, FeedSourceEnum, FeedTypeEnum, FileAccessTypeEnum, index$q as FileRepository, FileType, GET_WATCHER_URLS, index$4 as InvitationRepository, InvitationSortByEnum, InvitationStatusEnum, InvitationTargetTypeEnum, InvitationTypeEnum, JoinRequestStatusEnum, JoinResultStatusEnum, index$3 as LiveReactionRepository, index$8 as LiveStreamPlayer, MembershipAcceptanceTypeEnum, MessageContentType, index$o as MessageRepository, NotificationRolesFilterTypeEnum, NotificationSettingsLevelEnum, index$9 as PollRepository, PostContentType, index$c as PostRepository, PostStructureType, index$1 as ProductRepository, index$p as ReactionRepository, index$a as RoomPresenceRepository, index$d as RoomRepository, SearchUsersByEnum, index$7 as StoryRepository, index$b as StreamRepository, index$n as SubChannelRepository, SubscriptionLevels, UserNotificationModuleNameEnum, UserProfileFieldEnum, UserReportTypeEnum, UserReportTypeFilterEnum, index$r as UserRepository, UserTypeEnum, VERSION, VideoResolution, VideoSize, VideoTranscodingStatus, backupCache, createQuery, createReport, createUserToken, deleteReport, disableCache, dropFromCache, enableCache, exceedsUntilAtBoundary, filterByChannelMembership, filterByCommunityMembership, filterByFeedType, filterByPostDataTypes, filterByPropEquality, filterByPropInclusion, filterByPropIntersection, filterBySearchTerm, filterByStringComparePartially, filterByUntilAt, filterCommentsByBlockUsers, filterPostsByBlockUsers, getBlockUsersId, getChannelTopic, getCommentTopic, getCommunityStoriesTopic, getCommunityTopic, getLiveReactionTopic, getLiveStreamTopic, getMarkedMessageTopic, getMarkerUserFeedTopic, getMessageTopic, getMyFollowersTopic, getMyFollowingsTopic, getNetworkId, getNetworkTopic, getPostTopic, getRole, getRoomStreamerTopic, getRoomWatcherTopic, getSmartFeedChannelTopic, getSmartFeedMessageTopic, getSmartFeedSubChannelTopic, getStoryTopic, getSubChannelTopic, getUserTopic, isAfterBefore, isAfterBeforeRaw, isCachable, isFetcher, isFresh, isLocal, isMutator, isOffline, isPaged, isReportedByMe, isSkip, mergeInCache, index$5 as notificationTray, onChannelMarkerFetched, onFeedMarkerFetched, onFeedMarkerUpdated, onMessageMarked, onMessageMarkerFetched, onSubChannelMarkerFetched, onSubChannelMarkerUpdated, onUserMarkerFetched, onUserMarkerFetchedLegacy, pullFromCache, pushToCache, queryCache, queryOptions, queryRoles, resolveModuleAvailability, restoreCache, runQuery, sortByChannelSegment, sortByDisplayName, sortByFirstCreated, sortByFirstUpdated, sortByLastActivity, sortByLastCreated, sortByLastMessage, sortByLastUpdated, sortByLocalSortingDate, sortByName, sortBySegmentNumber, subscribeTopic, toPage, toPageRaw, toToken, upsertInCache, validateUntilAt, wipeCache };