@amityco/ts-sdk-react-native 7.27.2-60f0571.0 → 7.28.1-ea5533e.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 (195) hide show
  1. package/dist/@types/core/events.d.ts +5 -0
  2. package/dist/@types/core/events.d.ts.map +1 -1
  3. package/dist/@types/core/payload.d.ts +26 -0
  4. package/dist/@types/core/payload.d.ts.map +1 -1
  5. package/dist/@types/core/permissions.d.ts +10 -4
  6. package/dist/@types/core/permissions.d.ts.map +1 -1
  7. package/dist/@types/domains/analytics.d.ts +3 -1
  8. package/dist/@types/domains/analytics.d.ts.map +1 -1
  9. package/dist/@types/domains/channel.d.ts +5 -0
  10. package/dist/@types/domains/channel.d.ts.map +1 -1
  11. package/dist/@types/domains/client.d.ts +18 -0
  12. package/dist/@types/domains/client.d.ts.map +1 -1
  13. package/dist/@types/domains/comment.d.ts +27 -0
  14. package/dist/@types/domains/comment.d.ts.map +1 -1
  15. package/dist/@types/domains/community.d.ts +1 -0
  16. package/dist/@types/domains/community.d.ts.map +1 -1
  17. package/dist/@types/domains/curatedContent.d.ts +25 -0
  18. package/dist/@types/domains/curatedContent.d.ts.map +1 -0
  19. package/dist/@types/domains/pinnedMessage.d.ts +43 -0
  20. package/dist/@types/domains/pinnedMessage.d.ts.map +1 -0
  21. package/dist/@types/domains/post.d.ts +11 -0
  22. package/dist/@types/domains/post.d.ts.map +1 -1
  23. package/dist/@types/index.d.ts +3 -1
  24. package/dist/@types/index.d.ts.map +1 -1
  25. package/dist/analytic/service/analytic/AnalyticsEngine.d.ts +3 -0
  26. package/dist/analytic/service/analytic/AnalyticsEngine.d.ts.map +1 -1
  27. package/dist/analytic/service/analytic/AnalyticsEventCapturer.d.ts +3 -0
  28. package/dist/analytic/service/analytic/AnalyticsEventCapturer.d.ts.map +1 -1
  29. package/dist/channelRepository/api/createChannel.d.ts.map +1 -1
  30. package/dist/channelRepository/events/index.d.ts +2 -0
  31. package/dist/channelRepository/events/index.d.ts.map +1 -1
  32. package/dist/channelRepository/events/onChannelMemberRoleAdded.d.ts +22 -0
  33. package/dist/channelRepository/events/onChannelMemberRoleAdded.d.ts.map +1 -1
  34. package/dist/channelRepository/events/onChannelMemberRoleRemoved.d.ts +22 -0
  35. package/dist/channelRepository/events/onChannelMemberRoleRemoved.d.ts.map +1 -1
  36. package/dist/channelRepository/events/onChannelMessagePinned.d.ts +24 -0
  37. package/dist/channelRepository/events/onChannelMessagePinned.d.ts.map +1 -0
  38. package/dist/channelRepository/events/onChannelMessageUnpinned.d.ts +21 -0
  39. package/dist/channelRepository/events/onChannelMessageUnpinned.d.ts.map +1 -0
  40. package/dist/channelRepository/events/onChannelPinnedMessageDeleted.d.ts +12 -0
  41. package/dist/channelRepository/events/onChannelPinnedMessageDeleted.d.ts.map +1 -0
  42. package/dist/channelRepository/events/tests/onChannelMemberRoleAdded.test.d.ts +2 -0
  43. package/dist/channelRepository/events/tests/onChannelMemberRoleAdded.test.d.ts.map +1 -0
  44. package/dist/channelRepository/events/tests/onChannelMemberRoleRemoved.test.d.ts +2 -0
  45. package/dist/channelRepository/events/tests/onChannelMemberRoleRemoved.test.d.ts.map +1 -0
  46. package/dist/channelRepository/events/tests/onChannelMessagePinned.test.d.ts +2 -0
  47. package/dist/channelRepository/events/tests/onChannelMessagePinned.test.d.ts.map +1 -0
  48. package/dist/channelRepository/observers/getChannel.d.ts.map +1 -1
  49. package/dist/channelRepository/observers/getChannels/ChannelLiveCollectionController.d.ts.map +1 -1
  50. package/dist/channelRepository/observers/getMyMembership.d.ts.map +1 -1
  51. package/dist/channelRepository/observers/searchChannels/SearchChannelLiveCollectionController.d.ts.map +1 -1
  52. package/dist/channelRepository/utils/applyChannelRolePayload.d.ts +21 -0
  53. package/dist/channelRepository/utils/applyChannelRolePayload.d.ts.map +1 -0
  54. package/dist/channelRepository/utils/index.d.ts +2 -0
  55. package/dist/channelRepository/utils/index.d.ts.map +1 -1
  56. package/dist/channelRepository/utils/tests/pinnedMessage.test.d.ts +2 -0
  57. package/dist/channelRepository/utils/tests/pinnedMessage.test.d.ts.map +1 -0
  58. package/dist/channelRepository/utils/updatePinnedMessageCache.d.ts +24 -0
  59. package/dist/channelRepository/utils/updatePinnedMessageCache.d.ts.map +1 -0
  60. package/dist/client/api/createClient.d.ts +5 -1
  61. package/dist/client/api/createClient.d.ts.map +1 -1
  62. package/dist/client/api/getSocialSettings.d.ts.map +1 -1
  63. package/dist/client/api/setupLoginSubscriptions.d.ts.map +1 -1
  64. package/dist/client/api/tests/getSocialSettings.test.d.ts +2 -0
  65. package/dist/client/api/tests/getSocialSettings.test.d.ts.map +1 -0
  66. package/dist/commentRepository/api/approveComment.d.ts +17 -0
  67. package/dist/commentRepository/api/approveComment.d.ts.map +1 -0
  68. package/dist/commentRepository/api/createComment.d.ts +12 -1
  69. package/dist/commentRepository/api/createComment.d.ts.map +1 -1
  70. package/dist/commentRepository/api/declineComment.d.ts +18 -0
  71. package/dist/commentRepository/api/declineComment.d.ts.map +1 -0
  72. package/dist/commentRepository/api/deleteComment.d.ts.map +1 -1
  73. package/dist/commentRepository/api/index.d.ts +2 -0
  74. package/dist/commentRepository/api/index.d.ts.map +1 -1
  75. package/dist/commentRepository/api/tests/approveComment.test.d.ts +2 -0
  76. package/dist/commentRepository/api/tests/approveComment.test.d.ts.map +1 -0
  77. package/dist/commentRepository/api/tests/createComment.optimistic.test.d.ts +2 -0
  78. package/dist/commentRepository/api/tests/createComment.optimistic.test.d.ts.map +1 -0
  79. package/dist/commentRepository/api/tests/declineComment.test.d.ts +2 -0
  80. package/dist/commentRepository/api/tests/declineComment.test.d.ts.map +1 -0
  81. package/dist/commentRepository/api/updateComment.d.ts.map +1 -1
  82. package/dist/commentRepository/events/index.d.ts +1 -0
  83. package/dist/commentRepository/events/index.d.ts.map +1 -1
  84. package/dist/commentRepository/events/onCommentApproved.d.ts +17 -0
  85. package/dist/commentRepository/events/onCommentApproved.d.ts.map +1 -0
  86. package/dist/commentRepository/events/tests/onCommentApproved.test.d.ts +2 -0
  87. package/dist/commentRepository/events/tests/onCommentApproved.test.d.ts.map +1 -0
  88. package/dist/commentRepository/events/tests/onCommentCreated.count.test.d.ts +2 -0
  89. package/dist/commentRepository/events/tests/onCommentCreated.count.test.d.ts.map +1 -0
  90. package/dist/commentRepository/events/utils.d.ts.map +1 -1
  91. package/dist/commentRepository/internalApi/getComment.d.ts.map +1 -1
  92. package/dist/commentRepository/observers/getComment.d.ts.map +1 -1
  93. package/dist/commentRepository/observers/getCommentReviewQueue/LiveCollectionController.d.ts +13 -0
  94. package/dist/commentRepository/observers/getCommentReviewQueue/LiveCollectionController.d.ts.map +1 -0
  95. package/dist/commentRepository/observers/getCommentReviewQueue/PaginationController.d.ts +5 -0
  96. package/dist/commentRepository/observers/getCommentReviewQueue/PaginationController.d.ts.map +1 -0
  97. package/dist/commentRepository/observers/getCommentReviewQueue/QueryStreamController.d.ts +15 -0
  98. package/dist/commentRepository/observers/getCommentReviewQueue/QueryStreamController.d.ts.map +1 -0
  99. package/dist/commentRepository/observers/getCommentReviewQueue.d.ts +22 -0
  100. package/dist/commentRepository/observers/getCommentReviewQueue.d.ts.map +1 -0
  101. package/dist/commentRepository/observers/getComments/CommentLiveCollectionController.d.ts +1 -1
  102. package/dist/commentRepository/observers/getComments/CommentLiveCollectionController.d.ts.map +1 -1
  103. package/dist/commentRepository/observers/getComments/CommentQueryStreamController.d.ts.map +1 -1
  104. package/dist/commentRepository/observers/getComments/enums.d.ts +1 -0
  105. package/dist/commentRepository/observers/getComments/enums.d.ts.map +1 -1
  106. package/dist/commentRepository/observers/index.d.ts +1 -0
  107. package/dist/commentRepository/observers/index.d.ts.map +1 -1
  108. package/dist/commentRepository/observers/tests/integrations/getCommentReviewQueue.integration.test.d.ts +2 -0
  109. package/dist/commentRepository/observers/tests/integrations/getCommentReviewQueue.integration.test.d.ts.map +1 -0
  110. package/dist/commentRepository/utils/approval.d.ts +49 -0
  111. package/dist/commentRepository/utils/approval.d.ts.map +1 -0
  112. package/dist/commentRepository/utils/payload.d.ts +6 -0
  113. package/dist/commentRepository/utils/payload.d.ts.map +1 -1
  114. package/dist/commentRepository/utils/tests/approval.test.d.ts +2 -0
  115. package/dist/commentRepository/utils/tests/approval.test.d.ts.map +1 -0
  116. package/dist/commentRepository/utils/tests/payload.test.d.ts +2 -0
  117. package/dist/commentRepository/utils/tests/payload.test.d.ts.map +1 -0
  118. package/dist/communityRepository/api/createCommunity.d.ts +1 -1
  119. package/dist/communityRepository/api/createCommunity.d.ts.map +1 -1
  120. package/dist/communityRepository/api/updateCommunity.d.ts +1 -1
  121. package/dist/communityRepository/api/updateCommunity.d.ts.map +1 -1
  122. package/dist/core/device.d.ts +1 -1
  123. package/dist/core/device.d.ts.map +1 -1
  124. package/dist/core/events.d.ts +3 -3
  125. package/dist/core/events.d.ts.map +1 -1
  126. package/dist/core/tests/device.test.d.ts +2 -0
  127. package/dist/core/tests/device.test.d.ts.map +1 -0
  128. package/dist/core/transports/http.d.ts.map +1 -1
  129. package/dist/core/transports/utils.d.ts +8 -0
  130. package/dist/core/transports/utils.d.ts.map +1 -1
  131. package/dist/curatedContentRepository/api/getPool.d.ts +2 -0
  132. package/dist/curatedContentRepository/api/getPool.d.ts.map +1 -0
  133. package/dist/curatedContentRepository/api/index.d.ts +2 -0
  134. package/dist/curatedContentRepository/api/index.d.ts.map +1 -0
  135. package/dist/curatedContentRepository/api/tests/getPool.test.d.ts +2 -0
  136. package/dist/curatedContentRepository/api/tests/getPool.test.d.ts.map +1 -0
  137. package/dist/curatedContentRepository/api/tests/integration/getPool.integration.test.d.ts +2 -0
  138. package/dist/curatedContentRepository/api/tests/integration/getPool.integration.test.d.ts.map +1 -0
  139. package/dist/curatedContentRepository/index.d.ts +2 -0
  140. package/dist/curatedContentRepository/index.d.ts.map +1 -0
  141. package/dist/index.cjs.js +1743 -478
  142. package/dist/index.d.ts +1 -0
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.esm.js +1718 -454
  145. package/dist/index.umd.js +1 -1
  146. package/dist/messagePreview/utils/getChannelMessagePreviewWithUser.d.ts +1 -0
  147. package/dist/messagePreview/utils/getChannelMessagePreviewWithUser.d.ts.map +1 -1
  148. package/dist/messageRepository/api/index.d.ts +2 -0
  149. package/dist/messageRepository/api/index.d.ts.map +1 -1
  150. package/dist/messageRepository/api/pinMessage.d.ts +29 -0
  151. package/dist/messageRepository/api/pinMessage.d.ts.map +1 -0
  152. package/dist/messageRepository/api/tests/pinMessage.test.d.ts +2 -0
  153. package/dist/messageRepository/api/tests/pinMessage.test.d.ts.map +1 -0
  154. package/dist/messageRepository/api/tests/unpinMessage.test.d.ts +2 -0
  155. package/dist/messageRepository/api/tests/unpinMessage.test.d.ts.map +1 -0
  156. package/dist/messageRepository/api/unpinMessage.d.ts +28 -0
  157. package/dist/messageRepository/api/unpinMessage.d.ts.map +1 -0
  158. package/dist/messageRepository/utils/refreshChannelPermissionsForMessage.d.ts +13 -0
  159. package/dist/messageRepository/utils/refreshChannelPermissionsForMessage.d.ts.map +1 -0
  160. package/dist/postRepository/utils/PostCommentCountEngine/ApproveTask.d.ts +16 -0
  161. package/dist/postRepository/utils/PostCommentCountEngine/ApproveTask.d.ts.map +1 -0
  162. package/dist/postRepository/utils/PostCommentCountEngine/PostCommentCountEngine.d.ts +25 -1
  163. package/dist/postRepository/utils/PostCommentCountEngine/PostCommentCountEngine.d.ts.map +1 -1
  164. package/dist/postRepository/utils/PostCommentCountEngine/postCommentCountEngineOnLoginHandler.d.ts +9 -0
  165. package/dist/postRepository/utils/PostCommentCountEngine/postCommentCountEngineOnLoginHandler.d.ts.map +1 -0
  166. package/dist/postRepository/utils/PostCommentCountEngine/tests/getLocalCommentCount.test.d.ts +2 -0
  167. package/dist/postRepository/utils/PostCommentCountEngine/tests/getLocalCommentCount.test.d.ts.map +1 -0
  168. package/dist/postRepository/utils/PostCommentCountEngine/tests/pendingAuthoredComments.test.d.ts +2 -0
  169. package/dist/postRepository/utils/PostCommentCountEngine/tests/pendingAuthoredComments.test.d.ts.map +1 -0
  170. package/dist/postRepository/utils/latestComments.d.ts +10 -0
  171. package/dist/postRepository/utils/latestComments.d.ts.map +1 -0
  172. package/dist/postRepository/utils/payload.d.ts.map +1 -1
  173. package/dist/postRepository/utils/tests/latestComments.test.d.ts +2 -0
  174. package/dist/postRepository/utils/tests/latestComments.test.d.ts.map +1 -0
  175. package/dist/postRepository/utils/tests/payload.test.d.ts +2 -0
  176. package/dist/postRepository/utils/tests/payload.test.d.ts.map +1 -0
  177. package/dist/utils/constants.d.ts +4 -0
  178. package/dist/utils/constants.d.ts.map +1 -1
  179. package/dist/utils/linkedObject/commentLinkedObject.d.ts.map +1 -1
  180. package/dist/utils/linkedObject/curatedContentTopicLinkedObject.d.ts +2 -0
  181. package/dist/utils/linkedObject/curatedContentTopicLinkedObject.d.ts.map +1 -0
  182. package/dist/utils/linkedObject/index.d.ts +1 -0
  183. package/dist/utils/linkedObject/index.d.ts.map +1 -1
  184. package/dist/utils/linkedObject/messageLinkedObject.d.ts.map +1 -1
  185. package/dist/utils/linkedObject/postLinkedObject.d.ts.map +1 -1
  186. package/dist/utils/linkedObject/tests/postLinkedObject.test.d.ts +2 -0
  187. package/dist/utils/linkedObject/tests/postLinkedObject.test.d.ts.map +1 -0
  188. package/dist/utils/tests/dummy/comment.d.ts +1 -0
  189. package/dist/utils/tests/dummy/comment.d.ts.map +1 -1
  190. package/dist/utils/tests/dummy/index.d.ts +1 -0
  191. package/dist/utils/tests/dummy/index.d.ts.map +1 -1
  192. package/dist/utils/tests/dummy/pinnedMessage.d.ts +2 -0
  193. package/dist/utils/tests/dummy/pinnedMessage.d.ts.map +1 -0
  194. package/dist/utils/tests/dummy/post.d.ts.map +1 -1
  195. package/package.json +3 -2
package/dist/index.esm.js CHANGED
@@ -3,9 +3,10 @@ import mitt from 'mitt';
3
3
  import debug from 'debug';
4
4
  import axios from 'axios';
5
5
  import HttpAgent, { HttpsAgent } from 'agentkeepalive';
6
+ import uuid$1 from 'react-native-uuid';
6
7
  import mqtt from 'mqtt';
8
+ import { UAParser } from 'ua-parser-js';
7
9
  import AsyncStorage from '@react-native-async-storage/async-storage';
8
- import uuid$1 from 'react-native-uuid';
9
10
  import jwtDecode from 'jwt-decode';
10
11
  import { Platform } from 'react-native';
11
12
  import hash from 'object-hash';
@@ -317,8 +318,8 @@ var AmityLiveViewerCountMode;
317
318
 
318
319
  function getVersion() {
319
320
  try {
320
- // the string ''v7.27.1-esm'' should be replaced by actual value by @rollup/plugin-replace
321
- return 'v7.27.1-esm';
321
+ // the string ''v7.28.0-esm'' should be replaced by actual value by @rollup/plugin-replace
322
+ return 'v7.28.0-esm';
322
323
  }
323
324
  catch (error) {
324
325
  return '__dev__';
@@ -327,6 +328,7 @@ function getVersion() {
327
328
  const VERSION = getVersion();
328
329
 
329
330
  const COLLECTION_DEFAULT_PAGINATION_LIMIT = 5;
331
+ const LATEST_COMMENTS_LIMIT = 5;
330
332
  const COLLECTION_DEFAULT_PAGINATION_SIZE = 20;
331
333
  const COLLECTION_DEFAULT_CACHING_POLICY = 'cache_then_server';
332
334
  const ENABLE_CACHE_MESSAGE = 'For using Live Collection feature you need to enable Cache!';
@@ -342,7 +344,11 @@ const YEAR = 365 * DAY;
342
344
  const ACCESS_TOKEN_WATCHER_INTERVAL = 10 * MINUTE;
343
345
  const API_ENDPOINTS = {
344
346
  forYouFeed: '/api/v1/feeds/for-you',
347
+ commentReviewQueue: (communityId) => `/api/v3/communities/${encodeURIComponent(communityId)}/comments/review-queue`,
345
348
  };
349
+ // The review-queue route defaults to 10, not the SDK-wide page size, and caps at 100
350
+ const COMMENT_REVIEW_QUEUE_DEFAULT_PAGINATION_SIZE = 10;
351
+ const COMMENT_REVIEW_QUEUE_MAX_PAGINATION_SIZE = 100;
346
352
 
347
353
  // cache constants
348
354
  const CACHE_KEY_GET = 'get';
@@ -1951,6 +1957,8 @@ function computeUrl(type, region) {
1951
1957
  return URLS[type].replace('{region}', region);
1952
1958
  }
1953
1959
 
1960
+ const uuid = () => uuid$1.v4().toString();
1961
+
1954
1962
  /**
1955
1963
  * Unwraps v3 server payload (for error handling and type extraction)
1956
1964
  *
@@ -1975,6 +1983,24 @@ const unwrapPayload = (response) => {
1975
1983
  // unjust, but helps force uncast "T | undefined" to allow deconstruction without warnings
1976
1984
  return undefined;
1977
1985
  };
1986
+ /**
1987
+ * Builds a hex string of `bytes` length from the SDK's uuid helper (a v4 UUID is
1988
+ * 16 bytes / 32 hex chars once hyphens are removed) — avoids a new crypto dependency.
1989
+ */
1990
+ const randomHex = (bytes) => {
1991
+ let hex = '';
1992
+ while (hex.length < bytes * 2)
1993
+ hex += uuid().replace(/-/g, '');
1994
+ return hex.slice(0, bytes * 2);
1995
+ };
1996
+ /**
1997
+ * W3C Trace Context `traceparent`: `version-<16-byte trace-id>-<8-byte span-id>-<flags>`.
1998
+ * The SDK is the trace originator, so it mints a fresh trace + span id per request; flags `01` = sampled.
1999
+ *
2000
+ * @category Transport
2001
+ * @hidden
2002
+ */
2003
+ const generateTraceparent = () => `00-${randomHex(16)}-${randomHex(8)}-01`;
1978
2004
 
1979
2005
  class NetworkActivitiesWatcher {
1980
2006
  constructor() {
@@ -2099,6 +2125,10 @@ const createHttpTransport = (endpoint) => {
2099
2125
  });
2100
2126
  instance.defaults.withCredentials = false;
2101
2127
  instance.interceptors.request.use(config => {
2128
+ // Request tracing — set on every outbound request (login included), never persisted.
2129
+ // Must run before the session-url early-return below so /api/v5/sessions carries them too.
2130
+ config.headers.set('x-request-id', uuid());
2131
+ config.headers.set('traceparent', generateTraceparent());
2102
2132
  // do not check expiration for token creation url
2103
2133
  if (config.url === '/api/v5/sessions') {
2104
2134
  return config;
@@ -2142,20 +2172,28 @@ const createHttpTransport = (endpoint) => {
2142
2172
  });
2143
2173
  return response;
2144
2174
  }, error => {
2145
- var _a, _b, _c;
2175
+ var _a, _b, _c, _d;
2146
2176
  const { response } = error;
2147
2177
  // handle unauthorized request
2148
2178
  if ((response === null || response === void 0 ? void 0 : response.data.code) === 400100 /* Amity.ServerError.UNAUTHORIZED */) {
2149
2179
  fireEvent('tokenTerminated', "terminated" /* Amity.SessionStates.TERMINATED */);
2150
2180
  }
2181
+ // handle globally banned user. The realtime `user.didGlobalBan` event can be missed
2182
+ // (e.g. MQTT dropped before delivery), so mirror the 400100 handling above and fire
2183
+ // the same virtual event the login subscriptions already listen to. That terminates
2184
+ // the session with `TokenTerminationReason.GLOBAL_BAN` and notifies `onClientBanned`
2185
+ // consumers, instead of leaving the session `established` with a revoked token.
2186
+ if (((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.code) === 400312 /* Amity.ServerError.GLOBAL_BAN */) {
2187
+ fireEvent('user.didGlobalBan', { users: [], files: [] });
2188
+ }
2151
2189
  // handle visitor daily usage limit exceeded
2152
- if (((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.code) === 400323 /* Amity.ServerError.VISITOR_USAGE_LIMIT_EXCEEDED */) {
2190
+ if (((_b = response === null || response === void 0 ? void 0 : response.data) === null || _b === void 0 ? void 0 : _b.code) === 400323 /* Amity.ServerError.VISITOR_USAGE_LIMIT_EXCEEDED */) {
2153
2191
  throttledFireVisitorUsageLimit();
2154
2192
  throw new ASCError('Visitor usage limit exceeded', 400323 /* Amity.ServerError.VISITOR_USAGE_LIMIT_EXCEEDED */, "error" /* Amity.ErrorLevel.ERROR */);
2155
2193
  }
2156
2194
  // if it's an error with status in the response payload,
2157
2195
  // then it's an expected error.
2158
- if ((_b = response === null || response === void 0 ? void 0 : response.data) === null || _b === void 0 ? void 0 : _b.status) {
2196
+ if ((_c = response === null || response === void 0 ? void 0 : response.data) === null || _c === void 0 ? void 0 : _c.status) {
2159
2197
  unwrapPayload(response === null || response === void 0 ? void 0 : response.data);
2160
2198
  }
2161
2199
  // as on request cancellation error is returned
@@ -2163,7 +2201,7 @@ const createHttpTransport = (endpoint) => {
2163
2201
  throw error;
2164
2202
  }
2165
2203
  // unexpected error.
2166
- throw new Error((_c = response === null || response === void 0 ? void 0 : response.data) !== null && _c !== void 0 ? _c : error);
2204
+ throw new Error((_d = response === null || response === void 0 ? void 0 : response.data) !== null && _d !== void 0 ? _d : error);
2167
2205
  });
2168
2206
  instance.defaults.paramsSerializer = {
2169
2207
  encode: params => encodeURIComponent(params),
@@ -2369,8 +2407,6 @@ const getItem = async (key) => {
2369
2407
  });
2370
2408
  };
2371
2409
 
2372
- const uuid = () => uuid$1.v4().toString();
2373
-
2374
2410
  // TODO: find a way to uniquely identify the device
2375
2411
  let tabIndex = 0;
2376
2412
  // This logic is running on browser only
@@ -2430,22 +2466,60 @@ const getMQTTClientId = async (userId) => {
2430
2466
  };
2431
2467
  /** @hidden */
2432
2468
  const getDeviceModel = () => {
2433
- var _a, _b;
2469
+ var _a;
2434
2470
  if ((_a = safeProcess.versions) === null || _a === void 0 ? void 0 : _a.node)
2435
2471
  return safeProcess.versions.node;
2436
- if (navigator)
2437
- return `${navigator.product.toLowerCase()}#${(_b = navigator.userAgent) !== null && _b !== void 0 ? _b : 'unknown_agent'}`;
2438
- return 'unknown_model';
2472
+ return undefined;
2473
+ };
2474
+ const hasNavigator = () => typeof navigator !== 'undefined' && !!navigator;
2475
+ const getLocale = () => (hasNavigator() ? navigator.language : undefined);
2476
+ const getTimezone = () => {
2477
+ try {
2478
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
2479
+ }
2480
+ catch (_a) {
2481
+ return undefined;
2482
+ }
2483
+ };
2484
+ const getNetworkType = () => {
2485
+ var _a;
2486
+ if (!hasNavigator())
2487
+ return undefined;
2488
+ return (_a = navigator.connection) === null || _a === void 0 ? void 0 : _a.effectiveType;
2489
+ };
2490
+ const isNode = () => { var _a; return !!((_a = safeProcess.versions) === null || _a === void 0 ? void 0 : _a.node); };
2491
+ const parseUserAgent = () => {
2492
+ if (!hasNavigator() || !navigator.userAgent)
2493
+ return {};
2494
+ const { os, browser, device } = new UAParser(navigator.userAgent).getResult();
2495
+ const result = {};
2496
+ if (os.name)
2497
+ result.osName = os.name;
2498
+ if (os.version)
2499
+ result.osVersion = os.version;
2500
+ if (browser.name)
2501
+ result.browserName = browser.name;
2502
+ if (browser.version)
2503
+ result.browserVersion = browser.version;
2504
+ if (device.type === 'tablet')
2505
+ result.deviceType = 'tablet';
2506
+ else if (device.type === 'mobile')
2507
+ result.deviceType = 'mobile';
2508
+ else
2509
+ result.deviceType = 'desktop';
2510
+ return result;
2439
2511
  };
2440
2512
  /** @hidden */
2441
2513
  const getDeviceInfo = () => {
2442
- const model = getDeviceModel();
2443
- return {
2444
- // TODO: replace with: kind: process.versions ? 'node' : 'web', when backend has fixed this
2445
- kind: 'web',
2446
- model,
2447
- sdkVersion: VERSION,
2448
- };
2514
+ var _a;
2515
+ let client;
2516
+ try {
2517
+ client = getActiveClient();
2518
+ }
2519
+ catch (_b) {
2520
+ client = undefined;
2521
+ }
2522
+ return Object.assign({ kind: isNode() ? 'node' : 'web', model: getDeviceModel(), sdkVersion: VERSION, product: (_a = client === null || client === void 0 ? void 0 : client.product) !== null && _a !== void 0 ? _a : 'sdk', uikitVersion: client === null || client === void 0 ? void 0 : client.uikitVersion, appId: client === null || client === void 0 ? void 0 : client.appId, appVersion: client === null || client === void 0 ? void 0 : client.appVersion, locale: getLocale(), timezone: getTimezone(), networkType: getNetworkType() }, parseUserAgent());
2449
2523
  };
2450
2524
 
2451
2525
  /* eslint-disable no-console */
@@ -3902,6 +3976,95 @@ const prepareChannelPayload = async (rawPayload, options = { isMessagePreviewUpd
3902
3976
  channelUsers });
3903
3977
  };
3904
3978
 
3979
+ const channelCacheKey = (channel) => [
3980
+ 'channel',
3981
+ 'get',
3982
+ getResolver('channel')(channel),
3983
+ ];
3984
+ /**
3985
+ * Find a cached channel by any of its ids (internal, public, or `channelId`).
3986
+ *
3987
+ * The channel cache is keyed by the channel internal id (see `idResolvers.channel`), but
3988
+ * realtime payloads reference channels by different ids: pin events carry the **public**
3989
+ * id, messages carry `channelId`. Try the cheap direct lookup first, then scan.
3990
+ */
3991
+ const findCachedChannelByAnyId = (id) => {
3992
+ var _a, _b, _c;
3993
+ if (!id)
3994
+ return undefined;
3995
+ const direct = (_a = pullFromCache(['channel', 'get', id])) === null || _a === void 0 ? void 0 : _a.data;
3996
+ if (direct)
3997
+ return direct;
3998
+ return (_c = (_b = queryCache(['channel', 'get'])) === null || _b === void 0 ? void 0 : _b.find(({ data }) => data.channelInternalId === id || data.channelPublicId === id || data.channelId === id)) === null || _c === void 0 ? void 0 : _c.data;
3999
+ };
4000
+ /**
4001
+ * Resolve the cached channel a `channel.messagePinned` / `channel.messageUnpinned` event
4002
+ * refers to. `data.channelId` is the channel public id; a pinned message body (present on
4003
+ * `messagePinned` only) also carries the internal id, which is the direct cache key.
4004
+ */
4005
+ const findCachedChannelForPin = (payload) => {
4006
+ var _a, _b;
4007
+ return (_b = findCachedChannelByAnyId((_a = payload.pinnedMessage) === null || _a === void 0 ? void 0 : _a.channelId)) !== null && _b !== void 0 ? _b : findCachedChannelByAnyId(payload.channelId);
4008
+ };
4009
+ /**
4010
+ * Write a new pin snapshot (or `null` to clear) on a cached channel and ingest any
4011
+ * users delivered with it (the pin author and the message author), so the banner can
4012
+ * render a byline without a second fetch.
4013
+ *
4014
+ * Every `messagePinned` payload is a full snapshot — "this is the pin now" — so the last
4015
+ * one received always wins; there is no merging with the previous pin.
4016
+ */
4017
+ const updatePinnedMessageCache = (channel, pinnedMessage, users) => {
4018
+ if (users && users.length > 0) {
4019
+ ingestInCache({ users: users.map(convertRawUserToInternalUser) });
4020
+ }
4021
+ const updated = Object.assign(Object.assign({}, channel), { pinnedMessage });
4022
+ pushToCache(channelCacheKey(channel), updated);
4023
+ return updated;
4024
+ };
4025
+
4026
+ /**
4027
+ * Applies a `channel.roleAdded` / `channel.roleRemoved` realtime payload to the cache.
4028
+ *
4029
+ * The event names every member the role request affected together with the
4030
+ * roles and channel-scoped permissions each of them is left holding. That is a
4031
+ * full state, not a delta, so it is written straight over the cached membership.
4032
+ *
4033
+ * Members the SDK has not fetched yet are skipped: the payload does not carry
4034
+ * enough to build a full {@link Amity.Membership} and the live collections only
4035
+ * emit rows that already exist in cache.
4036
+ *
4037
+ * @param payload the `data` of the realtime event
4038
+ * @returns the refreshed channel (when resolvable) and the updated cached memberships
4039
+ *
4040
+ * @hidden
4041
+ */
4042
+ const applyChannelRolePayload = (payload) => {
4043
+ var _a, _b, _c;
4044
+ const { channelId, users = [], channels = [] } = payload;
4045
+ const rawChannel = (_a = channels.find(channel => channel.channelId === channelId)) !== null && _a !== void 0 ? _a : channels[0];
4046
+ let channel;
4047
+ if (rawChannel) {
4048
+ const channelKey = ['channel', 'get', getResolver('channel')(rawChannel)];
4049
+ // the role event does not touch message previews, keep whatever preview is cached
4050
+ const converted = convertFromRaw(rawChannel, { isMessagePreviewUpdated: false });
4051
+ upsertInCache(channelKey, converted);
4052
+ channel = (_c = (_b = pullFromCache(channelKey)) === null || _b === void 0 ? void 0 : _b.data) !== null && _c !== void 0 ? _c : converted;
4053
+ }
4054
+ const members = [];
4055
+ users.forEach(({ userId, roles, permissions }) => {
4056
+ var _a, _b, _c;
4057
+ const memberKey = ['channelUsers', 'get', getResolver('channelUsers')({ channelId, userId })];
4058
+ const cached = (_a = pullFromCache(memberKey)) === null || _a === void 0 ? void 0 : _a.data;
4059
+ if (!cached)
4060
+ return;
4061
+ const updated = Object.assign(Object.assign({}, cached), { roles, permissions });
4062
+ pushToCache(memberKey, updated);
4063
+ members.push((_c = (_b = pullFromCache(memberKey)) === null || _b === void 0 ? void 0 : _b.data) !== null && _c !== void 0 ? _c : updated);
4064
+ });
4065
+ return { channel, members };
4066
+ };
4067
+
3905
4068
  /**
3906
4069
  * ```js
3907
4070
  * import { getSubChannelMarkers } from '@amityco/ts-sdk-react-native'
@@ -4087,32 +4250,6 @@ const channelMemberLinkedObject = (channelMember) => {
4087
4250
  } });
4088
4251
  };
4089
4252
 
4090
- /**
4091
- *
4092
- * get current user's membership data
4093
- *
4094
- * @returns the {@link Amity.Membership<'channel'>} objects
4095
- *
4096
- * @category Channel API
4097
- * @async
4098
- */
4099
- const getMyMembership$1 = (channelId) => {
4100
- const client = getActiveClient();
4101
- client.log('channel/getMyMembership.locally');
4102
- const cacheChannelUser = pullFromCache([
4103
- 'channelUsers',
4104
- 'get',
4105
- `${channelId}#${client.userId}`,
4106
- ]);
4107
- if (!cacheChannelUser) {
4108
- throw new ASCApiError('Item not found!', 400400 /* Amity.ServerError.ITEM_NOT_FOUND */, "error" /* Amity.ErrorLevel.ERROR */);
4109
- }
4110
- return {
4111
- data: cacheChannelUser.data,
4112
- cachedAt: cacheChannelUser.cachedAt,
4113
- };
4114
- };
4115
-
4116
4253
  /**
4117
4254
  * ```js
4118
4255
  * import { isEqual } from '~/utils/isEqual'
@@ -4183,55 +4320,81 @@ function isEqual(x, y) {
4183
4320
  }));
4184
4321
  }
4185
4322
 
4186
- const callbacks$c = [];
4323
+ /**
4324
+ *
4325
+ * get current user's membership data
4326
+ *
4327
+ * @returns the {@link Amity.Membership<'channel'>} objects
4328
+ *
4329
+ * @category Channel API
4330
+ * @async
4331
+ */
4332
+ const getMyMembership$1 = (channelId) => {
4333
+ const client = getActiveClient();
4334
+ client.log('channel/getMyMembership.locally');
4335
+ const cacheChannelUser = pullFromCache([
4336
+ 'channelUsers',
4337
+ 'get',
4338
+ `${channelId}#${client.userId}`,
4339
+ ]);
4340
+ if (!cacheChannelUser) {
4341
+ throw new ASCApiError('Item not found!', 400400 /* Amity.ServerError.ITEM_NOT_FOUND */, "error" /* Amity.ErrorLevel.ERROR */);
4342
+ }
4343
+ return {
4344
+ data: cacheChannelUser.data,
4345
+ cachedAt: cacheChannelUser.cachedAt,
4346
+ };
4347
+ };
4348
+
4349
+ const callbacks$e = [];
4187
4350
  let mainDisposer$b = null;
4188
- const dispose$c = (cb) => {
4189
- const index = callbacks$c.indexOf(cb);
4351
+ const dispose$e = (cb) => {
4352
+ const index = callbacks$e.indexOf(cb);
4190
4353
  if (index > -1) {
4191
- callbacks$c.splice(index, 1);
4354
+ callbacks$e.splice(index, 1);
4192
4355
  }
4193
- if (callbacks$c.length === 0) {
4356
+ if (callbacks$e.length === 0) {
4194
4357
  mainDisposer$b === null || mainDisposer$b === void 0 ? void 0 : mainDisposer$b();
4195
4358
  }
4196
4359
  };
4197
4360
  const onChannelCreated = (callback) => {
4198
- if (callbacks$c.length === 0) {
4361
+ if (callbacks$e.length === 0) {
4199
4362
  const client = getActiveClient();
4200
4363
  const filter = async (payload) => {
4201
4364
  const data = await prepareChannelPayload(payload);
4202
4365
  ingestInCache(data);
4203
- callbacks$c.forEach(cb => cb(data.channels[0]));
4366
+ callbacks$e.forEach(cb => cb(data.channels[0]));
4204
4367
  };
4205
4368
  mainDisposer$b = createEventSubscriber(client, 'onChannelCreated', 'channel.created', filter);
4206
4369
  }
4207
- callbacks$c.push(callback);
4208
- return () => dispose$c(callback);
4370
+ callbacks$e.push(callback);
4371
+ return () => dispose$e(callback);
4209
4372
  };
4210
4373
 
4211
- const callbacks$b = [];
4212
- const mainDisposers = [];
4213
- const dispose$b = (cb) => {
4214
- const index = callbacks$b.indexOf(cb);
4374
+ const callbacks$d = [];
4375
+ const mainDisposers$2 = [];
4376
+ const dispose$d = (cb) => {
4377
+ const index = callbacks$d.indexOf(cb);
4215
4378
  if (index > -1) {
4216
- callbacks$b.splice(index, 1);
4379
+ callbacks$d.splice(index, 1);
4217
4380
  }
4218
- if (callbacks$b.length === 0) {
4219
- mainDisposers.forEach(fn => fn());
4381
+ if (callbacks$d.length === 0) {
4382
+ mainDisposers$2.forEach(fn => fn());
4220
4383
  }
4221
4384
  };
4222
4385
  const onChannelUpdated = (callback) => {
4223
4386
  const client = getActiveClient();
4224
- if (callbacks$b.length === 0) {
4387
+ if (callbacks$d.length === 0) {
4225
4388
  const filter = async (payload) => {
4226
4389
  const data = await prepareChannelPayload(payload);
4227
4390
  ingestInCache(data);
4228
- callbacks$b.forEach(cb => cb(data.channels[0]));
4391
+ callbacks$d.forEach(cb => cb(data.channels[0]));
4229
4392
  };
4230
- mainDisposers.push(createEventSubscriber(client, 'onChannelUpdated', 'channel.updated', filter));
4231
- mainDisposers.push(createEventSubscriber(client, 'onChannelUpdated', 'local.channel.updated', payload => callbacks$b.forEach(cb => cb(payload.channels[0]))));
4393
+ mainDisposers$2.push(createEventSubscriber(client, 'onChannelUpdated', 'channel.updated', filter));
4394
+ mainDisposers$2.push(createEventSubscriber(client, 'onChannelUpdated', 'local.channel.updated', payload => callbacks$d.forEach(cb => cb(payload.channels[0]))));
4232
4395
  }
4233
- callbacks$b.push(callback);
4234
- return () => dispose$b(callback);
4396
+ callbacks$d.push(callback);
4397
+ return () => dispose$d(callback);
4235
4398
  };
4236
4399
 
4237
4400
  const addFlagIsDeletedSubChannelUnreadByChannelId = (channelId) => {
@@ -4254,19 +4417,19 @@ const deleteChannelUnreadByChannelId = (channelId) => {
4254
4417
  }
4255
4418
  };
4256
4419
 
4257
- const callbacks$a = [];
4420
+ const callbacks$c = [];
4258
4421
  let mainDisposer$a = null;
4259
- const dispose$a = (cb) => {
4260
- const index = callbacks$a.indexOf(cb);
4422
+ const dispose$c = (cb) => {
4423
+ const index = callbacks$c.indexOf(cb);
4261
4424
  if (index > -1) {
4262
- callbacks$a.splice(index, 1);
4425
+ callbacks$c.splice(index, 1);
4263
4426
  }
4264
- if (callbacks$a.length === 0) {
4427
+ if (callbacks$c.length === 0) {
4265
4428
  mainDisposer$a === null || mainDisposer$a === void 0 ? void 0 : mainDisposer$a();
4266
4429
  }
4267
4430
  };
4268
4431
  const onChannelDeleted = (callback) => {
4269
- if (callbacks$a.length === 0) {
4432
+ if (callbacks$c.length === 0) {
4270
4433
  const client = getActiveClient();
4271
4434
  const filter = async (payload) => {
4272
4435
  const data = await prepareChannelPayload(payload);
@@ -4286,53 +4449,53 @@ const onChannelDeleted = (callback) => {
4286
4449
  }
4287
4450
  });
4288
4451
  ingestInCache(data);
4289
- callbacks$a.forEach(cb => cb(data.channels[0]));
4452
+ callbacks$c.forEach(cb => cb(data.channels[0]));
4290
4453
  };
4291
4454
  mainDisposer$a = createEventSubscriber(client, 'onChannelDeleted', 'channel.deleted', filter);
4292
4455
  }
4293
- callbacks$a.push(callback);
4294
- return () => dispose$a(callback);
4456
+ callbacks$c.push(callback);
4457
+ return () => dispose$c(callback);
4295
4458
  };
4296
4459
 
4297
- const callbacks$9 = [];
4460
+ const callbacks$b = [];
4298
4461
  let mainDisposer$9 = null;
4299
- const dispose$9 = (cb) => {
4300
- const index = callbacks$9.indexOf(cb);
4462
+ const dispose$b = (cb) => {
4463
+ const index = callbacks$b.indexOf(cb);
4301
4464
  if (index > -1) {
4302
- callbacks$9.splice(index, 1);
4465
+ callbacks$b.splice(index, 1);
4303
4466
  }
4304
- if (callbacks$9.length === 0) {
4467
+ if (callbacks$b.length === 0) {
4305
4468
  mainDisposer$9 === null || mainDisposer$9 === void 0 ? void 0 : mainDisposer$9();
4306
4469
  }
4307
4470
  };
4308
4471
  const onChannelJoined = (callback) => {
4309
- if (callbacks$9.length === 0) {
4472
+ if (callbacks$b.length === 0) {
4310
4473
  const client = getActiveClient();
4311
4474
  const filter = async (payload) => {
4312
4475
  const preparedPayload = await prepareChannelPayload(payload);
4313
4476
  const { channels, channelUsers } = preparedPayload;
4314
4477
  ingestInCache(preparedPayload);
4315
- callbacks$9.forEach(cb => cb(channels[0], channelUsers[0]));
4478
+ callbacks$b.forEach(cb => cb(channels[0], channelUsers[0]));
4316
4479
  };
4317
4480
  mainDisposer$9 = createEventSubscriber(client, 'onChannelJoined', 'channel.joined', filter);
4318
4481
  }
4319
- callbacks$9.push(callback);
4320
- return () => dispose$9(callback);
4482
+ callbacks$b.push(callback);
4483
+ return () => dispose$b(callback);
4321
4484
  };
4322
4485
 
4323
- const callbacks$8 = [];
4486
+ const callbacks$a = [];
4324
4487
  let mainDisposer$8 = null;
4325
- const dispose$8 = (cb) => {
4326
- const index = callbacks$8.indexOf(cb);
4488
+ const dispose$a = (cb) => {
4489
+ const index = callbacks$a.indexOf(cb);
4327
4490
  if (index > -1) {
4328
- callbacks$8.splice(index, 1);
4491
+ callbacks$a.splice(index, 1);
4329
4492
  }
4330
- if (callbacks$8.length === 0) {
4493
+ if (callbacks$a.length === 0) {
4331
4494
  mainDisposer$8 === null || mainDisposer$8 === void 0 ? void 0 : mainDisposer$8();
4332
4495
  }
4333
4496
  };
4334
4497
  const onChannelLeft = (callback) => {
4335
- if (callbacks$8.length === 0) {
4498
+ if (callbacks$a.length === 0) {
4336
4499
  const client = getActiveClient();
4337
4500
  const filter = async (payload) => {
4338
4501
  const { userId } = getActiveClient();
@@ -4356,27 +4519,27 @@ const onChannelLeft = (callback) => {
4356
4519
  }
4357
4520
  const { channels, channelUsers } = preparedPayload;
4358
4521
  ingestInCache(preparedPayload);
4359
- callbacks$8.forEach(cb => cb(channels[0], channelUsers[0]));
4522
+ callbacks$a.forEach(cb => cb(channels[0], channelUsers[0]));
4360
4523
  };
4361
4524
  mainDisposer$8 = createEventSubscriber(client, 'onChannelLeft', 'channel.left', filter);
4362
4525
  }
4363
- callbacks$8.push(callback);
4364
- return () => dispose$8(callback);
4526
+ callbacks$a.push(callback);
4527
+ return () => dispose$a(callback);
4365
4528
  };
4366
4529
 
4367
- const callbacks$7 = [];
4530
+ const callbacks$9 = [];
4368
4531
  let mainDisposer$7 = null;
4369
- const dispose$7 = (cb) => {
4370
- const index = callbacks$7.indexOf(cb);
4532
+ const dispose$9 = (cb) => {
4533
+ const index = callbacks$9.indexOf(cb);
4371
4534
  if (index > -1) {
4372
- callbacks$7.splice(index, 1);
4535
+ callbacks$9.splice(index, 1);
4373
4536
  }
4374
- if (callbacks$7.length === 0) {
4537
+ if (callbacks$9.length === 0) {
4375
4538
  mainDisposer$7 === null || mainDisposer$7 === void 0 ? void 0 : mainDisposer$7();
4376
4539
  }
4377
4540
  };
4378
4541
  const onChannelSetMuted = (callback) => {
4379
- if (callbacks$7.length === 0) {
4542
+ if (callbacks$9.length === 0) {
4380
4543
  const client = getActiveClient();
4381
4544
  const filter = async (payload) => {
4382
4545
  var _a;
@@ -4391,24 +4554,124 @@ const onChannelSetMuted = (callback) => {
4391
4554
  payload.channelId,
4392
4555
  ])) === null || _a === void 0 ? void 0 : _a.data;
4393
4556
  if (channel) {
4394
- callbacks$7.forEach(cb => cb(channel));
4557
+ callbacks$9.forEach(cb => cb(channel));
4395
4558
  }
4396
4559
  };
4397
4560
  mainDisposer$7 = createEventSubscriber(client, 'onChannelMute', 'channel.setMuted', filter);
4398
4561
  }
4562
+ callbacks$9.push(callback);
4563
+ return () => dispose$9(callback);
4564
+ };
4565
+
4566
+ const callbacks$8 = [];
4567
+ let mainDisposer$6 = null;
4568
+ const dispose$8 = (cb) => {
4569
+ const index = callbacks$8.indexOf(cb);
4570
+ if (index > -1) {
4571
+ callbacks$8.splice(index, 1);
4572
+ }
4573
+ if (callbacks$8.length === 0) {
4574
+ mainDisposer$6 === null || mainDisposer$6 === void 0 ? void 0 : mainDisposer$6();
4575
+ }
4576
+ };
4577
+ /* begin_public_function
4578
+ id: channel.event.message_pinned
4579
+ */
4580
+ /**
4581
+ * ```js
4582
+ * import { onChannelMessagePinned } from '@amityco/ts-sdk'
4583
+ * const dispose = onChannelMessagePinned(channel => {
4584
+ * // channel.pinnedMessage is the new pin
4585
+ * })
4586
+ * ```
4587
+ *
4588
+ * Fired when a message is pinned in a channel (the `channel.messagePinned` realtime
4589
+ * event). Pinning a different message while one is already pinned emits a single
4590
+ * `messagePinned` carrying the replacement — treat every payload as a full snapshot.
4591
+ *
4592
+ * The updated channel is written to cache before the callback runs, so
4593
+ * `ChannelRepository.getChannel` observers see `channel.pinnedMessage` change.
4594
+ *
4595
+ * @param callback The function to call when the event was fired
4596
+ * @returns an {@link Amity.Unsubscriber} function to run when willing to stop listening
4597
+ *
4598
+ * @category Channel Events
4599
+ */
4600
+ const onChannelMessagePinned = (callback) => {
4601
+ if (callbacks$8.length === 0) {
4602
+ const client = getActiveClient();
4603
+ const filter = (payload) => {
4604
+ var _a;
4605
+ const channel = findCachedChannelForPin(payload);
4606
+ if (!channel)
4607
+ return;
4608
+ const updated = updatePinnedMessageCache(channel, (_a = payload.pinnedMessage) !== null && _a !== void 0 ? _a : null, payload.users);
4609
+ callbacks$8.forEach(cb => cb(updated));
4610
+ };
4611
+ mainDisposer$6 = createEventSubscriber(client, 'onChannelMessagePinned', 'channel.messagePinned', filter);
4612
+ }
4613
+ callbacks$8.push(callback);
4614
+ return () => dispose$8(callback);
4615
+ };
4616
+ /* end_public_function */
4617
+
4618
+ const callbacks$7 = [];
4619
+ let mainDisposer$5 = null;
4620
+ const dispose$7 = (cb) => {
4621
+ const index = callbacks$7.indexOf(cb);
4622
+ if (index > -1) {
4623
+ callbacks$7.splice(index, 1);
4624
+ }
4625
+ if (callbacks$7.length === 0) {
4626
+ mainDisposer$5 === null || mainDisposer$5 === void 0 ? void 0 : mainDisposer$5();
4627
+ }
4628
+ };
4629
+ /* begin_public_function
4630
+ id: channel.event.message_unpinned
4631
+ */
4632
+ /**
4633
+ * ```js
4634
+ * import { onChannelMessageUnpinned } from '@amityco/ts-sdk'
4635
+ * const dispose = onChannelMessageUnpinned(channel => {
4636
+ * // channel.pinnedMessage is now null
4637
+ * })
4638
+ * ```
4639
+ *
4640
+ * Fired when a channel's pinned message is removed (the `channel.messageUnpinned`
4641
+ * realtime event) — either by an explicit unpin or automatically by the server when the
4642
+ * pinned message is deleted or its author is banned from the channel / globally.
4643
+ *
4644
+ * @param callback The function to call when the event was fired
4645
+ * @returns an {@link Amity.Unsubscriber} function to run when willing to stop listening
4646
+ *
4647
+ * @category Channel Events
4648
+ */
4649
+ const onChannelMessageUnpinned = (callback) => {
4650
+ if (callbacks$7.length === 0) {
4651
+ const client = getActiveClient();
4652
+ const filter = (payload) => {
4653
+ const channel = findCachedChannelForPin(payload);
4654
+ if (!channel)
4655
+ return;
4656
+ const updated = updatePinnedMessageCache(channel, null);
4657
+ callbacks$7.forEach(cb => cb(updated));
4658
+ };
4659
+ mainDisposer$5 = createEventSubscriber(client, 'onChannelMessageUnpinned', 'channel.messageUnpinned', filter);
4660
+ }
4399
4661
  callbacks$7.push(callback);
4400
4662
  return () => dispose$7(callback);
4401
4663
  };
4664
+ /* end_public_function */
4402
4665
 
4403
4666
  const callbacks$6 = [];
4404
- let mainDisposer$6 = null;
4667
+ let mainDisposer$4 = null;
4405
4668
  const dispose$6 = (cb) => {
4406
4669
  const index = callbacks$6.indexOf(cb);
4407
4670
  if (index > -1) {
4408
4671
  callbacks$6.splice(index, 1);
4409
4672
  }
4410
4673
  if (callbacks$6.length === 0) {
4411
- mainDisposer$6 === null || mainDisposer$6 === void 0 ? void 0 : mainDisposer$6();
4674
+ mainDisposer$4 === null || mainDisposer$4 === void 0 ? void 0 : mainDisposer$4();
4412
4675
  }
4413
4676
  };
4414
4677
  const onChannelMemberAdded = (callback) => {
@@ -4420,21 +4683,21 @@ const onChannelMemberAdded = (callback) => {
4420
4683
  ingestInCache(preparedPayload);
4421
4684
  callbacks$6.forEach(cb => cb(channels[0], channelUsers.find(cu => cu.membership === 'member')));
4422
4685
  };
4423
- mainDisposer$6 = createEventSubscriber(client, 'onChannelMemberAdded', 'channel.membersAdded', filter);
4686
+ mainDisposer$4 = createEventSubscriber(client, 'onChannelMemberAdded', 'channel.membersAdded', filter);
4424
4687
  }
4425
4688
  callbacks$6.push(callback);
4426
4689
  return () => dispose$6(callback);
4427
4690
  };
4428
4691
 
4429
4692
  const callbacks$5 = [];
4430
- let mainDisposer$5 = null;
4693
+ let mainDisposer$3 = null;
4431
4694
  const dispose$5 = (cb) => {
4432
4695
  const index = callbacks$5.indexOf(cb);
4433
4696
  if (index > -1) {
4434
4697
  callbacks$5.splice(index, 1);
4435
4698
  }
4436
4699
  if (callbacks$5.length === 0) {
4437
- mainDisposer$5 === null || mainDisposer$5 === void 0 ? void 0 : mainDisposer$5();
4700
+ mainDisposer$3 === null || mainDisposer$3 === void 0 ? void 0 : mainDisposer$3();
4438
4701
  }
4439
4702
  };
4440
4703
  const onChannelMemberRemoved = (callback) => {
@@ -4446,21 +4709,21 @@ const onChannelMemberRemoved = (callback) => {
4446
4709
  ingestInCache(preparedPayload);
4447
4710
  callbacks$5.forEach(cb => cb(channels[0], channelUsers[0]));
4448
4711
  };
4449
- mainDisposer$5 = createEventSubscriber(client, 'onChannelMemberRemoved', 'channel.membersRemoved', filter);
4712
+ mainDisposer$3 = createEventSubscriber(client, 'onChannelMemberRemoved', 'channel.membersRemoved', filter);
4450
4713
  }
4451
4714
  callbacks$5.push(callback);
4452
4715
  return () => dispose$5(callback);
4453
4716
  };
4454
4717
 
4455
4718
  const callbacks$4 = [];
4456
- let mainDisposer$4 = null;
4719
+ let mainDisposer$2 = null;
4457
4720
  const dispose$4 = (cb) => {
4458
4721
  const index = callbacks$4.indexOf(cb);
4459
4722
  if (index > -1) {
4460
4723
  callbacks$4.splice(index, 1);
4461
4724
  }
4462
4725
  if (callbacks$4.length === 0) {
4463
- mainDisposer$4 === null || mainDisposer$4 === void 0 ? void 0 : mainDisposer$4();
4726
+ mainDisposer$2 === null || mainDisposer$2 === void 0 ? void 0 : mainDisposer$2();
4464
4727
  }
4465
4728
  };
4466
4729
  const onChannelMemberBanned = (callback) => {
@@ -4480,20 +4743,20 @@ const onChannelMemberBanned = (callback) => {
4480
4743
  ingestInCache(preparedPayload);
4481
4744
  callbacks$4.forEach(cb => cb(channels[0], channelUsers.find(cu => cu.membership === 'banned')));
4482
4745
  };
4483
- mainDisposer$4 = createEventSubscriber(client, 'onChannelMemberBanned', 'channel.banned', filter);
4746
+ mainDisposer$2 = createEventSubscriber(client, 'onChannelMemberBanned', 'channel.banned', filter);
4484
4747
  callbacks$4.push(callback);
4485
4748
  return () => dispose$4(callback);
4486
4749
  };
4487
4750
 
4488
4751
  const callbacks$3 = [];
4489
- let mainDisposer$3 = null;
4752
+ let mainDisposer$1 = null;
4490
4753
  const dispose$3 = (cb) => {
4491
4754
  const index = callbacks$3.indexOf(cb);
4492
4755
  if (index > -1) {
4493
4756
  callbacks$3.splice(index, 1);
4494
4757
  }
4495
4758
  if (callbacks$3.length === 0) {
4496
- mainDisposer$3 === null || mainDisposer$3 === void 0 ? void 0 : mainDisposer$3();
4759
+ mainDisposer$1 === null || mainDisposer$1 === void 0 ? void 0 : mainDisposer$1();
4497
4760
  }
4498
4761
  };
4499
4762
  const onChannelMemberUnbanned = (callback) => {
@@ -4505,59 +4768,127 @@ const onChannelMemberUnbanned = (callback) => {
4505
4768
  ingestInCache(preparedPayload);
4506
4769
  callbacks$3.forEach(cb => cb(channels[0], channelUsers.find(cu => cu.membership === 'none')));
4507
4770
  };
4508
- mainDisposer$3 = createEventSubscriber(client, 'onChannelMemberUnbanned', 'channel.unbanned', filter);
4771
+ mainDisposer$1 = createEventSubscriber(client, 'onChannelMemberUnbanned', 'channel.unbanned', filter);
4509
4772
  }
4510
4773
  callbacks$3.push(callback);
4511
4774
  return () => dispose$3(callback);
4512
4775
  };
4513
4776
 
4514
4777
  const callbacks$2 = [];
4515
- let mainDisposer$2 = null;
4778
+ const mainDisposers$1 = [];
4516
4779
  const dispose$2 = (cb) => {
4517
4780
  const index = callbacks$2.indexOf(cb);
4518
4781
  if (index > -1) {
4519
4782
  callbacks$2.splice(index, 1);
4520
4783
  }
4521
4784
  if (callbacks$2.length === 0) {
4522
- mainDisposer$2 === null || mainDisposer$2 === void 0 ? void 0 : mainDisposer$2();
4785
+ mainDisposers$1.forEach(fn => fn());
4786
+ mainDisposers$1.length = 0;
4523
4787
  }
4524
4788
  };
4789
+ /* begin_public_function
4790
+ id: channel.event.member_role_added
4791
+ */
4792
+ /**
4793
+ * ```js
4794
+ * import { onChannelMemberRoleAdded } from '@amityco/ts-sdk'
4795
+ * const dispose = onChannelMemberRoleAdded((channel, member) => {
4796
+ * // member.roles and member.permissions are the member's resulting state
4797
+ * })
4798
+ * ```
4799
+ *
4800
+ * Fired when a role is added to a member of a {@link Amity.Channel}, either by the
4801
+ * local `ChannelRepository.Moderation.addRole` call or by the `channel.roleAdded` realtime
4802
+ * event coming from any client or the admin console.
4803
+ *
4804
+ * The realtime event reaches every subscriber of the channel topic (see `getChannelTopic`),
4805
+ * not only the actor or the affected members. It carries the affected members' resulting
4806
+ * channel roles and channel-scoped permissions, which are written to cache before the
4807
+ * callback runs. The callback is invoked once per affected member already held in cache.
4808
+ *
4809
+ * @param callback The function to call when the event was fired
4810
+ * @returns an {@link Amity.Unsubscriber} function to run when willing to stop listening
4811
+ *
4812
+ * @category Channel Events
4813
+ */
4525
4814
  const onChannelMemberRoleAdded = (callback) => {
4526
4815
  if (callbacks$2.length === 0) {
4527
4816
  const client = getActiveClient();
4528
- const filter = async (payload) => {
4817
+ const onLocal = async (payload) => {
4529
4818
  const { channels, channelUsers } = payload;
4530
4819
  callbacks$2.forEach(cb => cb(channels[0], channelUsers.find(channelUser => channelUser.membership === 'member')));
4531
4820
  };
4532
- mainDisposer$2 = createEventSubscriber(client, 'onChannelMemberRoleAdded', 'local.channel-moderator.role-added', filter);
4821
+ const onRealtime = (payload) => {
4822
+ const { channel, members } = applyChannelRolePayload(payload);
4823
+ if (!channel)
4824
+ return;
4825
+ members.forEach(member => callbacks$2.forEach(cb => cb(channel, member)));
4826
+ };
4827
+ mainDisposers$1.push(createEventSubscriber(client, 'onChannelMemberRoleAdded', 'local.channel-moderator.role-added', onLocal));
4828
+ mainDisposers$1.push(createEventSubscriber(client, 'onChannelMemberRoleAdded', 'channel.roleAdded', onRealtime));
4533
4829
  }
4534
4830
  callbacks$2.push(callback);
4535
4831
  return () => dispose$2(callback);
4536
4832
  };
4833
+ /* end_public_function */
4537
4834
 
4538
4835
  const callbacks$1 = [];
4539
- let mainDisposer$1 = null;
4836
+ const mainDisposers = [];
4540
4837
  const dispose$1 = (cb) => {
4541
4838
  const index = callbacks$1.indexOf(cb);
4542
4839
  if (index > -1) {
4543
4840
  callbacks$1.splice(index, 1);
4544
4841
  }
4545
4842
  if (callbacks$1.length === 0) {
4546
- mainDisposer$1 === null || mainDisposer$1 === void 0 ? void 0 : mainDisposer$1();
4843
+ mainDisposers.forEach(fn => fn());
4844
+ mainDisposers.length = 0;
4547
4845
  }
4548
4846
  };
4847
+ /* begin_public_function
4848
+ id: channel.event.member_role_removed
4849
+ */
4850
+ /**
4851
+ * ```js
4852
+ * import { onChannelMemberRoleRemoved } from '@amityco/ts-sdk'
4853
+ * const dispose = onChannelMemberRoleRemoved((channel, member) => {
4854
+ * // member.roles and member.permissions are the member's resulting state
4855
+ * })
4856
+ * ```
4857
+ *
4858
+ * Fired when a role is removed from a member of a {@link Amity.Channel}, either by the
4859
+ * local `ChannelRepository.Moderation.removeRole` call or by the `channel.roleRemoved` realtime
4860
+ * event coming from any client or the admin console.
4861
+ *
4862
+ * The realtime event reaches every subscriber of the channel topic (see `getChannelTopic`),
4863
+ * not only the actor or the affected members. It carries the affected members' resulting
4864
+ * channel roles and channel-scoped permissions, which are written to cache before the
4865
+ * callback runs. The callback is invoked once per affected member already held in cache.
4866
+ *
4867
+ * @param callback The function to call when the event was fired
4868
+ * @returns an {@link Amity.Unsubscriber} function to run when willing to stop listening
4869
+ *
4870
+ * @category Channel Events
4871
+ */
4549
4872
  const onChannelMemberRoleRemoved = (callback) => {
4550
4873
  if (callbacks$1.length === 0) {
4551
4874
  const client = getActiveClient();
4552
- const filter = async (payload) => {
4875
+ const onLocal = async (payload) => {
4553
4876
  const { channels, channelUsers } = payload;
4554
4877
  callbacks$1.forEach(cb => cb(channels[0], channelUsers.find(channelUser => channelUser.membership === 'member')));
4555
4878
  };
4556
- mainDisposer$1 = createEventSubscriber(client, 'onChannelMemberRoleRemoved', 'local.channel-moderator.role-removed', filter);
4879
+ const onRealtime = (payload) => {
4880
+ const { channel, members } = applyChannelRolePayload(payload);
4881
+ if (!channel)
4882
+ return;
4883
+ members.forEach(member => callbacks$1.forEach(cb => cb(channel, member)));
4884
+ };
4885
+ mainDisposers.push(createEventSubscriber(client, 'onChannelMemberRoleRemoved', 'local.channel-moderator.role-removed', onLocal));
4886
+ mainDisposers.push(createEventSubscriber(client, 'onChannelMemberRoleRemoved', 'channel.roleRemoved', onRealtime));
4557
4887
  }
4558
4888
  callbacks$1.push(callback);
4559
4889
  return () => dispose$1(callback);
4560
4890
  };
4891
+ /* end_public_function */
4561
4892
 
4562
4893
  /**
4563
4894
  * Fired when a {@link Amity.Channel} has been archived by the active user.
@@ -4681,6 +5012,8 @@ const getMyMembership = (channelId, callback) => {
4681
5012
  disposers.push(onChannelMemberBanned(realtimeRouter));
4682
5013
  disposers.push(onChannelMemberUnbanned(realtimeRouter));
4683
5014
  disposers.push(onChannelSetUserMuted(realtimeRouter));
5015
+ disposers.push(onChannelMemberRoleAdded(realtimeRouter));
5016
+ disposers.push(onChannelMemberRoleRemoved(realtimeRouter));
4684
5017
  onFetch();
4685
5018
  return () => {
4686
5019
  disposers.forEach(fn => fn());
@@ -5074,7 +5407,9 @@ const initializeMessagePreviewSetting = async () => {
5074
5407
  const getSocialSettings = async () => {
5075
5408
  const client = getActiveClient();
5076
5409
  const { data } = await client.http.get(`/api/v3/network-settings/social`);
5077
- return data.socialNetworkSetting;
5410
+ return Object.assign(Object.assign({}, data.socialNetworkSetting), {
5411
+ // Networks that never set the comment edit switch allow the edit
5412
+ isAllowEditCommentWhenReviewingEnabled: data.socialNetworkSetting.isAllowEditCommentWhenReviewingEnabled });
5078
5413
  };
5079
5414
 
5080
5415
  /**
@@ -5760,6 +6095,32 @@ class AnalyticsEventCapturer {
5760
6095
  activityType: "view" /* Amity.AnalyticEventActivityType.View */,
5761
6096
  });
5762
6097
  }
6098
+ markCuratedContentWidgetAsViewed(topicId) {
6099
+ this.markAs({
6100
+ uniqueId: `curatedContentWidget.${topicId}`,
6101
+ contentId: topicId,
6102
+ contentType: "curated_content_widget" /* Amity.AnalyticEventContentType.CuratedContentWidget */,
6103
+ activityType: "view" /* Amity.AnalyticEventActivityType.View */,
6104
+ });
6105
+ }
6106
+ markCuratedContentPostAsViewed(postId, topicId) {
6107
+ this.markAs({
6108
+ uniqueId: `curatedContentPost.${postId}.view`,
6109
+ contentId: postId,
6110
+ contentType: "curated_content_post" /* Amity.AnalyticEventContentType.CuratedContentPost */,
6111
+ activityType: "view" /* Amity.AnalyticEventActivityType.View */,
6112
+ metadata: { topicId },
6113
+ });
6114
+ }
6115
+ markCuratedContentPostAsClicked(postId, topicId) {
6116
+ this.markAs({
6117
+ uniqueId: `curatedContentPost.${postId}.click`,
6118
+ contentId: postId,
6119
+ contentType: "curated_content_post" /* Amity.AnalyticEventContentType.CuratedContentPost */,
6120
+ activityType: "linkClicked" /* Amity.AnalyticEventActivityType.Click */,
6121
+ metadata: { topicId },
6122
+ });
6123
+ }
5763
6124
  markPostAsMeaningfullyViewed(postId, feedRenderPosition) {
5764
6125
  var _a;
5765
6126
  const cached = (_a = pullFromCache(['feedMetadata', 'get', postId])) === null || _a === void 0 ? void 0 : _a.data;
@@ -5923,6 +6284,24 @@ class AnalyticsEngine {
5923
6284
  this._eventCapturer.markPostAsViewed(postId);
5924
6285
  }
5925
6286
  }
6287
+ markCuratedContentWidgetAsViewed(topicId) {
6288
+ if (this._client.sessionState === "established" /* Amity.SessionStates.ESTABLISHED */ ||
6289
+ this._client.sessionState === "tokenExpired" /* Amity.SessionStates.TOKEN_EXPIRED */) {
6290
+ this._eventCapturer.markCuratedContentWidgetAsViewed(topicId);
6291
+ }
6292
+ }
6293
+ markCuratedContentPostAsViewed(postId, topicId) {
6294
+ if (this._client.sessionState === "established" /* Amity.SessionStates.ESTABLISHED */ ||
6295
+ this._client.sessionState === "tokenExpired" /* Amity.SessionStates.TOKEN_EXPIRED */) {
6296
+ this._eventCapturer.markCuratedContentPostAsViewed(postId, topicId);
6297
+ }
6298
+ }
6299
+ markCuratedContentPostAsClicked(postId, topicId) {
6300
+ if (this._client.sessionState === "established" /* Amity.SessionStates.ESTABLISHED */ ||
6301
+ this._client.sessionState === "tokenExpired" /* Amity.SessionStates.TOKEN_EXPIRED */) {
6302
+ this._eventCapturer.markCuratedContentPostAsClicked(postId, topicId);
6303
+ }
6304
+ }
5926
6305
  markPostAsMeaningfullyViewed(postId, feedRenderPosition) {
5927
6306
  if (this._client.sessionState === "established" /* Amity.SessionStates.ESTABLISHED */ ||
5928
6307
  this._client.sessionState === "tokenExpired" /* Amity.SessionStates.TOKEN_EXPIRED */) {
@@ -7349,6 +7728,265 @@ class CursorController {
7349
7728
  }
7350
7729
  }
7351
7730
 
7731
+ class ResetTask {
7732
+ constructor(postId, latestCreatedAt, serverCommentCount) {
7733
+ this.postId = postId;
7734
+ this.latestCreatedAt = latestCreatedAt;
7735
+ this.serverCommentCount = serverCommentCount;
7736
+ }
7737
+ }
7738
+
7739
+ // Task to track comment creation
7740
+ class CreateTask {
7741
+ constructor(postId, commentId, createdAt) {
7742
+ this.postId = postId;
7743
+ this.commentId = commentId;
7744
+ this.createdAt = createdAt;
7745
+ }
7746
+ }
7747
+
7748
+ /**
7749
+ * A pending comment was approved and now counts publicly. Unlike `CreateTask` it carries no
7750
+ * `createdAt` and bypasses the age guard on purpose: the comment was written before the last post
7751
+ * payload (so its `createdAt` is old) but entered the server count only now. Same rule as iOS.
7752
+ *
7753
+ * Accepted trade-off (plan 40 §9): if a `ResetTask` that already includes this comment lands
7754
+ * between the approve response and the late `comment.approved` event, the count reads +1 until
7755
+ * the next server sync — the same self-correcting drift accepted for stale deletes.
7756
+ */
7757
+ class ApproveTask {
7758
+ constructor(postId, commentId) {
7759
+ this.postId = postId;
7760
+ this.commentId = commentId;
7761
+ }
7762
+ }
7763
+
7764
+ // Task to track comment deletion
7765
+ class DeleteTask {
7766
+ constructor(postId, commentId) {
7767
+ this.postId = postId;
7768
+ this.commentId = commentId;
7769
+ }
7770
+ }
7771
+
7772
+ class CommentChange {
7773
+ constructor(latestCreatedAt, serverCommentCount) {
7774
+ this.latestCommentCreatedAt = latestCreatedAt;
7775
+ this.serverCommentCount = serverCommentCount;
7776
+ this.createdCommentIds = new Set();
7777
+ this.deletedCommentIds = new Set();
7778
+ }
7779
+ }
7780
+
7781
+ class PostCommentCountEngine {
7782
+ constructor() {
7783
+ this.isProcessing = false;
7784
+ this.tasks = [];
7785
+ this.commentChangeTracker = new Map();
7786
+ // Ids of the LOGGED-IN USER's own pending comments, per post. Survives ResetTask, so the
7787
+ // author's +1 does not vanish on the next post refresh; emptied only when the comment leaves
7788
+ // the pending state (approval hands it to createdCommentIds, decline/delete drops it).
7789
+ this.pendingAuthoredCommentIds = new Map();
7790
+ }
7791
+ queueCommentChangeTask(task) {
7792
+ this.tasks.push(task);
7793
+ if (!this.isProcessing) {
7794
+ this.processCommentChangeTask();
7795
+ }
7796
+ }
7797
+ addPendingAuthoredComment(postId, commentId, { publish = true } = {}) {
7798
+ var _a;
7799
+ const ids = (_a = this.pendingAuthoredCommentIds.get(postId)) !== null && _a !== void 0 ? _a : new Set();
7800
+ if (ids.has(commentId))
7801
+ return;
7802
+ ids.add(commentId);
7803
+ this.pendingAuthoredCommentIds.set(postId, ids);
7804
+ if (publish)
7805
+ this.publishIfTracked(postId);
7806
+ }
7807
+ removePendingAuthoredComment(postId, commentId, { publish = true } = {}) {
7808
+ const ids = this.pendingAuthoredCommentIds.get(postId);
7809
+ if (!(ids === null || ids === void 0 ? void 0 : ids.delete(commentId)))
7810
+ return false;
7811
+ if (ids.size === 0)
7812
+ this.pendingAuthoredCommentIds.delete(postId);
7813
+ if (publish)
7814
+ this.publishIfTracked(postId);
7815
+ return true;
7816
+ }
7817
+ /** Whether a post payload has seeded this post; without one every count is `0`. */
7818
+ hasTracker(postId) {
7819
+ return this.commentChangeTracker.has(postId);
7820
+ }
7821
+ isPendingAuthoredComment(postId, commentId) {
7822
+ var _a, _b;
7823
+ return (_b = (_a = this.pendingAuthoredCommentIds.get(postId)) === null || _a === void 0 ? void 0 : _a.has(commentId)) !== null && _b !== void 0 ? _b : false;
7824
+ }
7825
+ // Drops every per-post state. Called on logout / user switch.
7826
+ reset() {
7827
+ this.tasks = [];
7828
+ this.commentChangeTracker.clear();
7829
+ this.pendingAuthoredCommentIds.clear();
7830
+ }
7831
+ publishIfTracked(postId) {
7832
+ // Without a tracker there is no server figure to add to; the next ResetTask picks it up.
7833
+ if (!this.commentChangeTracker.has(postId))
7834
+ return;
7835
+ PostCommentCountEngine.publishUpdate(postId, this.getLocalCommentCount(postId, true));
7836
+ }
7837
+ processCommentChangeTask() {
7838
+ if (this.isProcessing) {
7839
+ return;
7840
+ }
7841
+ this.isProcessing = true;
7842
+ if (this.tasks.length === 0) {
7843
+ this.isProcessing = false;
7844
+ return;
7845
+ }
7846
+ // Process in capped batches, coalescing updates
7847
+ const batch = this.tasks.splice(0, PostCommentCountEngine.BATCH_SIZE);
7848
+ const modifiedPostIds = new Set();
7849
+ batch.forEach(task => {
7850
+ let modified = false;
7851
+ if (task instanceof ResetTask) {
7852
+ modified = this.processResetTaskInternal(task);
7853
+ }
7854
+ else if (task instanceof CreateTask) {
7855
+ modified = this.processCreateTaskInternal(task);
7856
+ }
7857
+ else if (task instanceof ApproveTask) {
7858
+ modified = this.processApproveTaskInternal(task);
7859
+ }
7860
+ else if (task instanceof DeleteTask) {
7861
+ modified = this.processDeleteTaskInternal(task);
7862
+ }
7863
+ if (modified) {
7864
+ modifiedPostIds.add(task.postId);
7865
+ }
7866
+ });
7867
+ // Publish one update per modified post
7868
+ modifiedPostIds.forEach(postId => {
7869
+ PostCommentCountEngine.publishUpdate(postId, this.getLocalCommentCount(postId, true));
7870
+ });
7871
+ this.isProcessing = false;
7872
+ // Recurse if more tasks remain
7873
+ if (this.tasks.length > 0) {
7874
+ this.processCommentChangeTask();
7875
+ }
7876
+ }
7877
+ processResetTaskInternal(task) {
7878
+ // Always creates/overwrites tracker
7879
+ this.commentChangeTracker.set(task.postId, new CommentChange(task.latestCreatedAt, task.serverCommentCount));
7880
+ return true;
7881
+ }
7882
+ processCreateTaskInternal(task) {
7883
+ // The comment is public now: the author-local +1 hands over to the normal path, so the
7884
+ // count stays put instead of moving twice.
7885
+ const wasPendingAuthored = this.removePendingAuthoredComment(task.postId, task.commentId, {
7886
+ publish: false,
7887
+ });
7888
+ const tracker = this.commentChangeTracker.get(task.postId);
7889
+ if (!tracker)
7890
+ return false; // No tracker, skip
7891
+ if (tracker.deletedCommentIds.delete(task.commentId))
7892
+ return true; // Re-approval cancels the earlier removal
7893
+ if (tracker.createdCommentIds.has(task.commentId))
7894
+ return wasPendingAuthored; // Deduplication
7895
+ if (task.createdAt <= tracker.latestCommentCreatedAt)
7896
+ return wasPendingAuthored; // Timestamp filtering
7897
+ tracker.createdCommentIds.add(task.commentId);
7898
+ return true;
7899
+ }
7900
+ processApproveTaskInternal(task) {
7901
+ // The comment is public now: the author-local +1 hands over to the normal path, so the
7902
+ // count stays put instead of moving twice.
7903
+ const wasPendingAuthored = this.removePendingAuthoredComment(task.postId, task.commentId, {
7904
+ publish: false,
7905
+ });
7906
+ const tracker = this.commentChangeTracker.get(task.postId);
7907
+ if (!tracker)
7908
+ return false; // No tracker, skip
7909
+ if (tracker.deletedCommentIds.delete(task.commentId))
7910
+ return true; // Re-approval cancels the earlier re-review removal
7911
+ if (tracker.createdCommentIds.has(task.commentId))
7912
+ return wasPendingAuthored; // Approve response and comment.approved event dedupe by id
7913
+ // No timestamp filtering: approval is the moment the comment enters the count, not createdAt
7914
+ tracker.createdCommentIds.add(task.commentId);
7915
+ return true;
7916
+ }
7917
+ processDeleteTaskInternal(task) {
7918
+ const tracker = this.commentChangeTracker.get(task.postId);
7919
+ if (!tracker)
7920
+ return false; // No tracker, skip
7921
+ if (tracker.deletedCommentIds.has(task.commentId))
7922
+ return false; // Deduplication
7923
+ tracker.deletedCommentIds.add(task.commentId);
7924
+ return true;
7925
+ }
7926
+ /**
7927
+ * The count as this session sees it: the last server figure plus the local delta.
7928
+ *
7929
+ * `includePending` adds the logged-in user's own pending comments — the author's view. A
7930
+ * pending id is added only while the public delta does not already count it, so a comment
7931
+ * present in both sets counts once: a `comment.created` that arrives pending after the
7932
+ * `comment.approved` hand-over adds nothing, while an approved comment sent back to review
7933
+ * (created, deleted and pending all hold its id) keeps the author's +1.
7934
+ *
7935
+ * `0` for a post no payload has seeded yet; callers fall back to the post's own figure.
7936
+ */
7937
+ getLocalCommentCount(postId, includePending = false) {
7938
+ var _a;
7939
+ const tracker = this.commentChangeTracker.get(postId);
7940
+ if (!tracker)
7941
+ return 0;
7942
+ const publicDelta = tracker.createdCommentIds.size - tracker.deletedCommentIds.size;
7943
+ if (!includePending)
7944
+ return Math.max(0, tracker.serverCommentCount + publicDelta);
7945
+ let pendingExtra = 0;
7946
+ (_a = this.pendingAuthoredCommentIds.get(postId)) === null || _a === void 0 ? void 0 : _a.forEach(commentId => {
7947
+ const countedPublicly = tracker.createdCommentIds.has(commentId) && !tracker.deletedCommentIds.has(commentId);
7948
+ if (!countedPublicly)
7949
+ pendingExtra += 1;
7950
+ });
7951
+ return Math.max(0, tracker.serverCommentCount + publicDelta + pendingExtra);
7952
+ }
7953
+ static publishUpdate(postId, newCount) {
7954
+ var _a;
7955
+ const queryKey = ['post', 'get', postId];
7956
+ mergeInCache(queryKey, {
7957
+ localCommentCount: newCount,
7958
+ });
7959
+ const postPayload = (_a = pullFromCache(queryKey)) === null || _a === void 0 ? void 0 : _a.data;
7960
+ if (!postPayload)
7961
+ return;
7962
+ fireEvent('local.post.updated', {
7963
+ posts: [postPayload],
7964
+ });
7965
+ }
7966
+ }
7967
+ PostCommentCountEngine.BATCH_SIZE = 50;
7968
+ let instance$1;
7969
+ var PostCommentCountEngine$1 = {
7970
+ getInstance: () => {
7971
+ if (!instance$1) {
7972
+ instance$1 = new PostCommentCountEngine();
7973
+ }
7974
+ return instance$1;
7975
+ },
7976
+ };
7977
+
7978
+ /**
7979
+ * Comment-count state is per user — in particular the ids of the logged-in user's own pending
7980
+ * comments — so it must not survive a logout or a user switch.
7981
+ *
7982
+ * @category private
7983
+ */
7984
+ const postCommentCountEngineOnLoginHandler = () => onSessionStateChange(state => {
7985
+ if (state === "notLoggedIn" /* Amity.SessionStates.NOT_LOGGED_IN */ || state === "terminated" /* Amity.SessionStates.TERMINATED */) {
7986
+ PostCommentCountEngine$1.getInstance().reset();
7987
+ }
7988
+ });
7989
+
7352
7990
  const EVENTS = [
7353
7991
  'disconnected',
7354
7992
  'error',
@@ -7538,7 +8176,7 @@ const setupLoginSubscriptions = (unsubWatcher) => {
7538
8176
  // NOTE: This is a temporary solution to handle the channel marker when the user is forced to leave
7539
8177
  // the channel because currently backend can't handle this, so every time a user is banned from
7540
8178
  // a channel or the channel is deleted the channel's unread count will not be reset to zero
7541
- onChannelDeleted(removeChannelMarkerCache), onChannelMemberBanned(removeChannelMarkerCache), markReadEngineOnLoginHandler(), analyticsEngineOnLoginHandler(), objectResolverEngineOnLoginHandler(), reactionSyncEngineOnLoginHandler(), blockedUserSyncEngineOnLoginHandler(), blockingUserSyncEngineOnLoginHandler(), blockRealtimeOnLoginHandler(), resetAutoSubscriptionsOnLogout(), CursorController.clearOnLogout());
8179
+ onChannelDeleted(removeChannelMarkerCache), onChannelMemberBanned(removeChannelMarkerCache), markReadEngineOnLoginHandler(), analyticsEngineOnLoginHandler(), objectResolverEngineOnLoginHandler(), reactionSyncEngineOnLoginHandler(), blockedUserSyncEngineOnLoginHandler(), blockingUserSyncEngineOnLoginHandler(), blockRealtimeOnLoginHandler(), resetAutoSubscriptionsOnLogout(), CursorController.clearOnLogout(), postCommentCountEngineOnLoginHandler());
7542
8180
  if (client.useLegacyUnreadCount) {
7543
8181
  subscriptions.push(readReceiptSyncEngineOnLoginHandler());
7544
8182
  }
@@ -8594,7 +9232,7 @@ const DEFAULT_DEBUG_SESSION = 'amity';
8594
9232
  *
8595
9233
  * @category Client API
8596
9234
  * */
8597
- const createClient = (apiKey, apiRegion = API_REGIONS.SG, { debugSession = DEFAULT_DEBUG_SESSION, apiEndpoint, prefixDeviceIdKey, rteEnabled = true, } = {}) => {
9235
+ const createClient = (apiKey, apiRegion = API_REGIONS.SG, { debugSession = DEFAULT_DEBUG_SESSION, apiEndpoint, prefixDeviceIdKey, rteEnabled = true, product, uikitVersion, appId, appVersion, } = {}) => {
8598
9236
  var _a, _b, _c;
8599
9237
  const log = createLogger(debugSession);
8600
9238
  log('client/api/createClient', {
@@ -8623,6 +9261,10 @@ const createClient = (apiKey, apiRegion = API_REGIONS.SG, { debugSession = DEFAU
8623
9261
  const useLegacyUnreadCount = true;
8624
9262
  const client = {
8625
9263
  version: `${VERSION}`,
9264
+ product,
9265
+ uikitVersion,
9266
+ appId,
9267
+ appVersion,
8626
9268
  apiKey,
8627
9269
  /*
8628
9270
  * SDK Components
@@ -8884,13 +9526,13 @@ class GlobalFileAccessType {
8884
9526
  }
8885
9527
  }
8886
9528
  _GlobalFileAccessType_fileAccessType = new WeakMap();
8887
- let instance$1;
9529
+ let instance;
8888
9530
  var GlobalFileAccessType$1 = {
8889
9531
  getInstance: () => {
8890
- if (!instance$1) {
8891
- instance$1 = new GlobalFileAccessType();
9532
+ if (!instance) {
9533
+ instance = new GlobalFileAccessType();
8892
9534
  }
8893
- return instance$1;
9535
+ return instance;
8894
9536
  },
8895
9537
  };
8896
9538
 
@@ -9749,7 +10391,7 @@ const getUserUnread = (callback) => {
9749
10391
  };
9750
10392
  };
9751
10393
 
9752
- var index$s = /*#__PURE__*/Object.freeze({
10394
+ var index$t = /*#__PURE__*/Object.freeze({
9753
10395
  __proto__: null,
9754
10396
  getActiveClient: getActiveClient,
9755
10397
  getActiveUser: getActiveUser,
@@ -10978,7 +11620,7 @@ const getMyFollowInfo = (callback) => {
10978
11620
  };
10979
11621
  /* end_public_function */
10980
11622
 
10981
- var index$r = /*#__PURE__*/Object.freeze({
11623
+ var index$s = /*#__PURE__*/Object.freeze({
10982
11624
  __proto__: null,
10983
11625
  blockUser: blockUser,
10984
11626
  unBlockUser: unBlockUser,
@@ -12319,6 +12961,16 @@ const createChannel = async (bundle) => {
12319
12961
  /* c8 ignore next */
12320
12962
  isPublic: (bundle === null || bundle === void 0 ? void 0 : bundle.type) === 'community' ? isPublic : undefined }));
12321
12963
  }
12964
+ // Fire the event locally, the same way createCommunity and createPost do.
12965
+ // Without it a running getChannels collection never inserts the new channel:
12966
+ // its query stream controller inserts an id only on OnCreate / OnJoin /
12967
+ // OnResolveChannel / OnUnarchived, and the first sent message arrives as an
12968
+ // OnUpdate, which only re-notifies. Reusing the MQTT event name means the
12969
+ // already-wired onChannelCreated -> OnCreate path does the insert, so the
12970
+ // list updates without a re-query. onChannelCreated re-runs
12971
+ // prepareChannelPayload + ingestInCache itself, so this is idempotent with
12972
+ // the ingest below.
12973
+ fireEvent('channel.created', payload.data);
12322
12974
  const data = await prepareChannelPayload(payload.data);
12323
12975
  const cachedAt = client.cache && Date.now();
12324
12976
  if (client.cache)
@@ -12490,8 +13142,32 @@ const streamLinkedObject = (stream) => {
12490
13142
  } });
12491
13143
  };
12492
13144
 
13145
+ const KNOWN_APPROVAL_STATUSES = [
13146
+ "pending" /* Amity.CommentApprovalStatusEnum.Pending */,
13147
+ "approved" /* Amity.CommentApprovalStatusEnum.Approved */,
13148
+ "rejected" /* Amity.CommentApprovalStatusEnum.Rejected */,
13149
+ ];
13150
+ /**
13151
+ * The server sends `approvalStatus` only while a comment is held for review or after it was
13152
+ * rejected. Absent, `null`, or an unrecognised value all resolve to `approved` — never to
13153
+ * `pending`, which would mark every comment written before the feature as unpublished.
13154
+ */
13155
+ const resolveCommentApprovalStatus = (approvalStatus) => {
13156
+ if (approvalStatus && KNOWN_APPROVAL_STATUSES.includes(approvalStatus)) {
13157
+ return approvalStatus;
13158
+ }
13159
+ return "approved" /* Amity.CommentApprovalStatusEnum.Approved */;
13160
+ };
13161
+ const prepareComment = (comment) => (Object.assign(Object.assign(Object.assign({}, (comment.hasOwnProperty('myReactions') ? {} : { myReactions: [] })), comment), { approvalStatus: resolveCommentApprovalStatus(comment.approvalStatus) }));
13162
+ function prepareCommentPayload(commentPayload) {
13163
+ const { comments, commentChildren } = commentPayload;
13164
+ return Object.assign(Object.assign(Object.assign({}, commentPayload), { comments: comments.map(prepareComment) }), (commentChildren ? { commentChildren: commentChildren.map(prepareComment) } : {}));
13165
+ }
13166
+
12493
13167
  const commentLinkedObject = (comment) => {
12494
- return Object.assign(Object.assign({}, comment), { get target() {
13168
+ return Object.assign(Object.assign({}, comment), {
13169
+ // Non-optional on the public model: absent on the wire means approved
13170
+ approvalStatus: resolveCommentApprovalStatus(comment.approvalStatus), get target() {
12495
13171
  const commentTypes = {
12496
13172
  type: comment.targetType,
12497
13173
  };
@@ -13142,6 +13818,122 @@ const fileLinkedObject = (file) => {
13142
13818
  return file;
13143
13819
  };
13144
13820
 
13821
+ const isCommentPending = (comment) => resolveCommentApprovalStatus(comment.approvalStatus) === "pending" /* Amity.CommentApprovalStatusEnum.Pending */;
13822
+ const isCommentApproved = (comment) => resolveCommentApprovalStatus(comment.approvalStatus) === "approved" /* Amity.CommentApprovalStatusEnum.Approved */;
13823
+ // Only post and content references feed PostCommentCountEngine; story counts live on the story.
13824
+ const isCountedReference = (comment) => ['post', 'content'].includes(comment.referenceType);
13825
+ const isAuthoredByCurrentUser = (comment) => comment.userId === getActiveClient().userId;
13826
+ /**
13827
+ * Whether a comment on this reference would be held for review, read from the cached post's
13828
+ * community. `true` when that cannot be known — the post or its community is not cached yet.
13829
+ * A pending guess that turns out approved is corrected by the response and the comment then
13830
+ * enters the list; an approved guess that turns out pending has already been shown in a public
13831
+ * list with nothing to take it back out. Only shapes the optimistic object — the server
13832
+ * response is the authority on the actual status.
13833
+ */
13834
+ const isCommentApprovalRequired = (bundle) => {
13835
+ var _a, _b;
13836
+ // Only comments on community posts are gated; every other reference is known-off
13837
+ if (bundle.referenceType !== 'post')
13838
+ return false;
13839
+ const post = (_a = pullFromCache(['post', 'get', bundle.referenceId])) === null || _a === void 0 ? void 0 : _a.data;
13840
+ if (!post)
13841
+ return true;
13842
+ // A user-feed post is known-off
13843
+ if (post.targetType !== 'community')
13844
+ return false;
13845
+ const community = (_b = pullFromCache([
13846
+ 'community',
13847
+ 'get',
13848
+ post.targetId,
13849
+ ])) === null || _b === void 0 ? void 0 : _b.data;
13850
+ if (!community)
13851
+ return true;
13852
+ return community.needApprovalOnCommentCreation === true;
13853
+ };
13854
+ /**
13855
+ * An approved comment arrived over realtime: one `CreateTask` carrying its own `createdAt`, so
13856
+ * the timestamp filter skips anything the last post payload already counted. The author's own
13857
+ * comment reaches here as an echo too; the engine de-duplicates it by id against the task the
13858
+ * create response queued.
13859
+ */
13860
+ const applyCommentCreatedCount = (comment) => {
13861
+ if (!isCountedReference(comment))
13862
+ return;
13863
+ PostCommentCountEngine$1.getInstance().queueCommentChangeTask(new CreateTask(comment.referenceId, comment.commentId, comment.createdAt));
13864
+ };
13865
+ /**
13866
+ * The author's own pending comment counts in their view only. Recorded as separate engine state
13867
+ * so it survives a post refresh (`ResetTask`).
13868
+ */
13869
+ const recordPendingAuthoredComment = (comment, options) => {
13870
+ if (!isCountedReference(comment) || !isAuthoredByCurrentUser(comment))
13871
+ return;
13872
+ PostCommentCountEngine$1.getInstance().addPendingAuthoredComment(comment.referenceId, comment.commentId, options);
13873
+ };
13874
+ /**
13875
+ * A pending comment was declined or deleted: it was never in the server count, so no
13876
+ * `DeleteTask` — only the author-local +1 goes away.
13877
+ */
13878
+ const releasePendingAuthoredComment = (comment) => {
13879
+ if (!isCountedReference(comment))
13880
+ return;
13881
+ PostCommentCountEngine$1.getInstance().removePendingAuthoredComment(comment.referenceId, comment.commentId);
13882
+ };
13883
+ /**
13884
+ * Reveal: exactly one `ApproveTask`. The engine de-duplicates by comment id, so the approve
13885
+ * response and the `comment.approved` event may both call this. `ApproveTask` bypasses the age
13886
+ * guard on purpose — the comment was written before the last server sync but enters the public
13887
+ * count now — matching the iOS `ApproveTask` (plan 40 §9, guard off for approve).
13888
+ */
13889
+ const applyCommentApprovedCount = (comment) => {
13890
+ if (!isCountedReference(comment))
13891
+ return;
13892
+ PostCommentCountEngine$1.getInstance().queueCommentChangeTask(new ApproveTask(comment.referenceId, comment.commentId));
13893
+ };
13894
+ /**
13895
+ * Re-review: an approved comment edited back to pending leaves the public count once. The
13896
+ * author keeps their own +1 through the pending set, so their count does not move.
13897
+ */
13898
+ const applyCommentReReviewCount = (comment) => {
13899
+ if (!isCountedReference(comment))
13900
+ return;
13901
+ recordPendingAuthoredComment(comment, { publish: false });
13902
+ PostCommentCountEngine$1.getInstance().queueCommentChangeTask(new DeleteTask(comment.referenceId, comment.commentId));
13903
+ };
13904
+ /** Adds a reply to its cached parent's `children` (and count) once. */
13905
+ const linkCommentToParent = (comment) => {
13906
+ var _a;
13907
+ if (!comment.parentId)
13908
+ return;
13909
+ const parentKey = ['comment', 'get', comment.parentId];
13910
+ const parent = (_a = pullFromCache(parentKey)) === null || _a === void 0 ? void 0 : _a.data;
13911
+ if (!parent || parent.children.includes(comment.commentId))
13912
+ return;
13913
+ pushToCache(parentKey, Object.assign(Object.assign({}, parent), { childrenNumber: parent.childrenNumber + 1, children: [...new Set([...parent.children, comment.commentId])] }));
13914
+ };
13915
+ /** Removes a reply from its cached parent's `children` (and count) once. */
13916
+ const unlinkCommentFromParent = (comment) => {
13917
+ var _a;
13918
+ if (!comment.parentId)
13919
+ return;
13920
+ const parentKey = ['comment', 'get', comment.parentId];
13921
+ const parent = (_a = pullFromCache(parentKey)) === null || _a === void 0 ? void 0 : _a.data;
13922
+ if (!parent || !parent.children.includes(comment.commentId))
13923
+ return;
13924
+ pushToCache(parentKey, Object.assign(Object.assign({}, parent), { childrenNumber: parent.childrenNumber - 1, children: parent.children.filter(id => id !== comment.commentId) }));
13925
+ };
13926
+
13927
+ /**
13928
+ * The server's list is oldest → newest, so the newest ids are the last ones. Kept unique so the
13929
+ * create response, the local event and the realtime echo cannot each add the same comment.
13930
+ */
13931
+ const capLatestCommentIds = (commentIds) => [...new Set(commentIds)].slice(-LATEST_COMMENTS_LIMIT);
13932
+ /** A comment belongs in the post preview only while it is cached, not deleted and public. */
13933
+ const isPreviewableComment = (comment) => comment !== undefined && !comment.isDeleted && isCommentApproved(comment);
13934
+ /** The post's preview list after one of the user's own top-level comments is created. */
13935
+ const appendLatestCommentId = (post, commentId) => { var _a; return capLatestCommentIds([...((_a = post.comments) !== null && _a !== void 0 ? _a : []), commentId]); };
13936
+
13145
13937
  const postLinkedObject = (post) => {
13146
13938
  return shallowClone(post, {
13147
13939
  childrenPosts: post.children
@@ -13165,19 +13957,13 @@ const postLinkedObject = (post) => {
13165
13957
  get latestComments() {
13166
13958
  if (!post.comments)
13167
13959
  return [];
13168
- return (post.comments
13169
- .map(commentId => {
13170
- var _a;
13171
- const commentCached = (_a = pullFromCache([
13172
- 'comment',
13173
- 'get',
13174
- commentId,
13175
- ])) === null || _a === void 0 ? void 0 : _a.data;
13176
- if (!commentCached)
13177
- return null;
13178
- return commentLinkedObject(commentCached);
13179
- })
13180
- .filter(Boolean) || []);
13960
+ // Same rule as the server's own list and as iOS: deleted and non-public comments are
13961
+ // dropped first, then the newest LATEST_COMMENTS_LIMIT remain
13962
+ return post.comments
13963
+ .map(commentId => { var _a; return (_a = pullFromCache(['comment', 'get', commentId])) === null || _a === void 0 ? void 0 : _a.data; })
13964
+ .filter(isPreviewableComment)
13965
+ .slice(-LATEST_COMMENTS_LIMIT)
13966
+ .map(commentLinkedObject);
13181
13967
  },
13182
13968
  get creator() {
13183
13969
  // The id is checked for a value, not just for presence: a payload can
@@ -13190,6 +13976,15 @@ const postLinkedObject = (post) => {
13190
13976
  return undefined;
13191
13977
  return userLinkedObject(cacheData.data);
13192
13978
  },
13979
+ getLocalCommentCount(includePending = false) {
13980
+ var _a;
13981
+ const engine = PostCommentCountEngine$1.getInstance();
13982
+ // Before any payload has seeded the engine for this post there is no tracker; the
13983
+ // post's own figure is the best answer then.
13984
+ return engine.hasTracker(post.postId)
13985
+ ? engine.getLocalCommentCount(post.postId, includePending)
13986
+ : (_a = post.localCommentCount) !== null && _a !== void 0 ? _a : post.commentsCount;
13987
+ },
13193
13988
  get targetCommunity() {
13194
13989
  var _a;
13195
13990
  if (!post.targetId)
@@ -13554,7 +14349,8 @@ const messageLinkedObject = (message) => {
13554
14349
  },
13555
14350
  get creator() {
13556
14351
  var _a;
13557
- return (_a = pullFromCache(['user', 'get', message.creatorId])) === null || _a === void 0 ? void 0 : _a.data;
14352
+ const creator = (_a = pullFromCache(['user', 'get', message.creatorId])) === null || _a === void 0 ? void 0 : _a.data;
14353
+ return creator ? userLinkedObject(creator) : undefined;
13558
14354
  }, markRead: () => markReadMessage(message), addReaction: (reactionName) => addReactionToMessage(reactionName, message), removeReaction: (reactionName) => removeReactionToMessage(reactionName, message) });
13559
14355
  };
13560
14356
 
@@ -14215,6 +15011,22 @@ const eventLinkedObject = (event) => {
14215
15011
  } });
14216
15012
  };
14217
15013
 
15014
+ const curatedContentTopicLinkedObject = (topic) => {
15015
+ return shallowClone(topic, {
15016
+ analytics: {
15017
+ markAsViewed: () => {
15018
+ AnalyticsEngine$1.getInstance().markCuratedContentWidgetAsViewed(topic.topicId);
15019
+ },
15020
+ markPostAsViewed: (postId) => {
15021
+ AnalyticsEngine$1.getInstance().markCuratedContentPostAsViewed(postId, topic.topicId);
15022
+ },
15023
+ markPostClick: (postId) => {
15024
+ AnalyticsEngine$1.getInstance().markCuratedContentPostAsClicked(postId, topic.topicId);
15025
+ },
15026
+ },
15027
+ });
15028
+ };
15029
+
14218
15030
  const LinkedObject = {
14219
15031
  file: fileLinkedObject,
14220
15032
  ad: adLinkedObject,
@@ -14239,6 +15051,7 @@ const LinkedObject = {
14239
15051
  eventResponse: eventResponseLinkedObject,
14240
15052
  product: productLinkedObject,
14241
15053
  poll: pollLinkedObject,
15054
+ curatedContentTopic: curatedContentTopicLinkedObject,
14242
15055
  };
14243
15056
 
14244
15057
  /* begin_public_function
@@ -15561,9 +16374,9 @@ var AmityUserSearchMatchType;
15561
16374
  AmityUserSearchMatchType["PARTIAL"] = "partial";
15562
16375
  })(AmityUserSearchMatchType || (AmityUserSearchMatchType = {}));
15563
16376
 
15564
- var index$q = /*#__PURE__*/Object.freeze({
16377
+ var index$r = /*#__PURE__*/Object.freeze({
15565
16378
  __proto__: null,
15566
- Relationship: index$r,
16379
+ Relationship: index$s,
15567
16380
  getUserByIds: getUserByIds,
15568
16381
  updateUser: updateUser,
15569
16382
  flagUser: flagUser,
@@ -15971,7 +16784,7 @@ const uploadAudio = async (formData, onProgress) => {
15971
16784
  };
15972
16785
  /* end_public_function */
15973
16786
 
15974
- var index$p = /*#__PURE__*/Object.freeze({
16787
+ var index$q = /*#__PURE__*/Object.freeze({
15975
16788
  __proto__: null,
15976
16789
  getFile: getFile,
15977
16790
  uploadFile: uploadFile,
@@ -16164,147 +16977,6 @@ getMessage$1.locally = (messageId) => {
16164
16977
  };
16165
16978
  };
16166
16979
 
16167
- class ResetTask {
16168
- constructor(postId, latestCreatedAt, serverCommentCount) {
16169
- this.postId = postId;
16170
- this.latestCreatedAt = latestCreatedAt;
16171
- this.serverCommentCount = serverCommentCount;
16172
- }
16173
- }
16174
-
16175
- // Task to track comment creation
16176
- class CreateTask {
16177
- constructor(postId, commentId, createdAt) {
16178
- this.postId = postId;
16179
- this.commentId = commentId;
16180
- this.createdAt = createdAt;
16181
- }
16182
- }
16183
-
16184
- // Task to track comment deletion
16185
- class DeleteTask {
16186
- constructor(postId, commentId) {
16187
- this.postId = postId;
16188
- this.commentId = commentId;
16189
- }
16190
- }
16191
-
16192
- class CommentChange {
16193
- constructor(latestCreatedAt, serverCommentCount) {
16194
- this.latestCommentCreatedAt = latestCreatedAt;
16195
- this.serverCommentCount = serverCommentCount;
16196
- this.createdCommentIds = new Set();
16197
- this.deletedCommentIds = new Set();
16198
- }
16199
- }
16200
-
16201
- class PostCommentCountEngine {
16202
- constructor() {
16203
- this.isProcessing = false;
16204
- this.tasks = [];
16205
- this.commentChangeTracker = new Map();
16206
- }
16207
- queueCommentChangeTask(task) {
16208
- this.tasks.push(task);
16209
- if (!this.isProcessing) {
16210
- this.processCommentChangeTask();
16211
- }
16212
- }
16213
- processCommentChangeTask() {
16214
- if (this.isProcessing) {
16215
- return;
16216
- }
16217
- this.isProcessing = true;
16218
- if (this.tasks.length === 0) {
16219
- this.isProcessing = false;
16220
- return;
16221
- }
16222
- // Process in capped batches, coalescing updates
16223
- const batch = this.tasks.splice(0, PostCommentCountEngine.BATCH_SIZE);
16224
- const modifiedPostIds = new Set();
16225
- batch.forEach(task => {
16226
- let modified = false;
16227
- if (task instanceof ResetTask) {
16228
- modified = this.processResetTaskInternal(task);
16229
- }
16230
- else if (task instanceof CreateTask) {
16231
- modified = this.processCreateTaskInternal(task);
16232
- }
16233
- else if (task instanceof DeleteTask) {
16234
- modified = this.processDeleteTaskInternal(task);
16235
- }
16236
- if (modified) {
16237
- modifiedPostIds.add(task.postId);
16238
- }
16239
- });
16240
- // Publish one update per modified post
16241
- modifiedPostIds.forEach(postId => {
16242
- const count = this.computeCommentCount(postId);
16243
- PostCommentCountEngine.publishUpdate(postId, count);
16244
- });
16245
- this.isProcessing = false;
16246
- // Recurse if more tasks remain
16247
- if (this.tasks.length > 0) {
16248
- this.processCommentChangeTask();
16249
- }
16250
- }
16251
- processResetTaskInternal(task) {
16252
- // Always creates/overwrites tracker
16253
- this.commentChangeTracker.set(task.postId, new CommentChange(task.latestCreatedAt, task.serverCommentCount));
16254
- return true;
16255
- }
16256
- processCreateTaskInternal(task) {
16257
- const tracker = this.commentChangeTracker.get(task.postId);
16258
- if (!tracker)
16259
- return false; // No tracker, skip
16260
- if (tracker.createdCommentIds.has(task.commentId))
16261
- return false; // Deduplication
16262
- if (task.createdAt <= tracker.latestCommentCreatedAt)
16263
- return false; // Timestamp filtering
16264
- tracker.createdCommentIds.add(task.commentId);
16265
- return true;
16266
- }
16267
- processDeleteTaskInternal(task) {
16268
- const tracker = this.commentChangeTracker.get(task.postId);
16269
- if (!tracker)
16270
- return false; // No tracker, skip
16271
- if (tracker.deletedCommentIds.has(task.commentId))
16272
- return false; // Deduplication
16273
- tracker.deletedCommentIds.add(task.commentId);
16274
- return true;
16275
- }
16276
- computeCommentCount(postId) {
16277
- const tracker = this.commentChangeTracker.get(postId);
16278
- if (!tracker)
16279
- return 0;
16280
- const count = tracker.serverCommentCount + tracker.createdCommentIds.size - tracker.deletedCommentIds.size;
16281
- return Math.max(0, count);
16282
- }
16283
- static publishUpdate(postId, newCount) {
16284
- var _a;
16285
- const queryKey = ['post', 'get', postId];
16286
- mergeInCache(queryKey, {
16287
- localCommentCount: newCount,
16288
- });
16289
- const postPayload = (_a = pullFromCache(queryKey)) === null || _a === void 0 ? void 0 : _a.data;
16290
- if (!postPayload)
16291
- return;
16292
- fireEvent('local.post.updated', {
16293
- posts: [postPayload],
16294
- });
16295
- }
16296
- }
16297
- PostCommentCountEngine.BATCH_SIZE = 50;
16298
- let instance;
16299
- var PostCommentCountEngine$1 = {
16300
- getInstance: () => {
16301
- if (!instance) {
16302
- instance = new PostCommentCountEngine();
16303
- }
16304
- return instance;
16305
- },
16306
- };
16307
-
16308
16980
  const updateStreamReferences = (streams, streamId, postId) => {
16309
16981
  if (!streamId)
16310
16982
  return streams;
@@ -16324,7 +16996,7 @@ const preparePostPayload = (payload) => {
16324
16996
  let mappedNewStream = [];
16325
16997
  // feed type
16326
16998
  const posts = postsData.map(post => {
16327
- var _a, _b, _c, _d;
16999
+ var _a, _b, _c;
16328
17000
  const feedType = (_a = postPayload.feeds.find(feed => feed.feedId === post.feedId)) === null || _a === void 0 ? void 0 : _a.feedType;
16329
17001
  const childPosts = payload.postChildren.filter(children => children.parentPostId === post.postId);
16330
17002
  if (childPosts.length > 0 && isAmityLivestreamPost(childPosts[0])) {
@@ -16343,7 +17015,11 @@ const preparePostPayload = (payload) => {
16343
17015
  // Queue ResetTask for this post
16344
17016
  PostCommentCountEngine$1.getInstance().queueCommentChangeTask(new ResetTask(post.postId, latestCreatedAt, (_c = post.commentsCount) !== null && _c !== void 0 ? _c : 0));
16345
17017
  return Object.assign(Object.assign({}, post), { childPosts,
16346
- feedType, localCommentCount: (_d = post.localCommentCount) !== null && _d !== void 0 ? _d : post.commentsCount, pinnedProductId: post === null || post === void 0 ? void 0 : post.pinnedProductId });
17018
+ feedType,
17019
+ // The ResetTask above has just seeded the engine, so this is the server figure plus the
17020
+ // author's own pending comments. Reading `post.commentsCount` here instead would overwrite
17021
+ // that +1 in the cache on every refresh, since the ingest that follows merges this object.
17022
+ localCommentCount: PostCommentCountEngine$1.getInstance().getLocalCommentCount(post.postId, true), pinnedProductId: post === null || post === void 0 ? void 0 : post.pinnedProductId });
16347
17023
  });
16348
17024
  return Object.assign(Object.assign({}, postPayload), { postChildren, videoStreamings: mappedNewStream, posts, communities: communityWithMembershipStatus, communityUsers: mappedCommunityUsers });
16349
17025
  };
@@ -17096,62 +17772,67 @@ const onPostReactionRemoved = (callback) => {
17096
17772
  return createEventSubscriber(client, 'post.removeReaction', 'post.removeReaction', filter);
17097
17773
  };
17098
17774
 
17775
+ /**
17776
+ * `comment.created` fires while a comment is still pending, so delivery is not visibility. The
17777
+ * two side effects that make a comment public — the parent link that feeds the reply list and
17778
+ * the comment-count task — are gated here; `comment.approved` is the only reveal signal, and a
17779
+ * decline arrives as `comment.deleted`.
17780
+ */
17781
+ const applyApprovalSideEffects = (event, comment, previous) => {
17782
+ switch (event) {
17783
+ case 'comment.created':
17784
+ if (isCommentPending(comment)) {
17785
+ recordPendingAuthoredComment(comment);
17786
+ }
17787
+ else {
17788
+ linkCommentToParent(comment);
17789
+ applyCommentCreatedCount(comment);
17790
+ }
17791
+ break;
17792
+ case 'comment.approved':
17793
+ linkCommentToParent(comment);
17794
+ applyCommentApprovedCount(comment);
17795
+ break;
17796
+ case 'comment.updated':
17797
+ // An effective edit in a gated community sends an approved comment back for review
17798
+ if (isCommentPending(comment) && previous && isCommentApproved(previous)) {
17799
+ unlinkCommentFromParent(comment);
17800
+ applyCommentReReviewCount(comment);
17801
+ }
17802
+ break;
17803
+ case 'comment.deleted':
17804
+ unlinkCommentFromParent(comment);
17805
+ // A pending comment was never counted, so its deletion (or decline) moves no count
17806
+ if (previous && isCommentPending(previous))
17807
+ releasePendingAuthoredComment(comment);
17808
+ break;
17809
+ }
17810
+ };
17099
17811
  const createCommentEventSubscriber = (event, callback) => {
17100
17812
  const client = getActiveClient();
17101
17813
  const filter = (payload) => {
17102
- var _a;
17814
+ var _a, _b;
17103
17815
  if (!client.cache) {
17104
17816
  // TODO: here we are missing specific properties here!
17105
17817
  callback(LinkedObject.comment(payload.comments[0]));
17106
17818
  }
17107
17819
  else {
17108
- const processed = ['comment.flagged', 'comment.unflagged'].includes(event)
17820
+ const flagged = ['comment.flagged', 'comment.unflagged'].includes(event)
17109
17821
  ? prepareCommentFromFlaggedEvent(payload)
17110
17822
  : payload;
17111
- // NOTE: The event data should be merge with existing cache rather than replace it
17112
- ingestInCache(processed, undefined, false);
17823
+ // Resolve `approvalStatus` before the merge: an absent value means approved and has to
17824
+ // overwrite a cached `pending`, which a plain merge would leave in place.
17825
+ const processed = prepareCommentPayload(flagged);
17113
17826
  const { comments } = processed;
17114
17827
  if (comments.length > 0) {
17115
- const comment = pullFromCache([
17116
- 'comment',
17117
- 'get',
17118
- comments[0].commentId,
17119
- ]);
17120
- if (['comment.created'].includes(event)) {
17121
- if (comments[0].parentId) {
17122
- const parentComment = pullFromCache([
17123
- 'comment',
17124
- 'get',
17125
- comments[0].parentId,
17126
- ]);
17127
- if (parentComment === null || parentComment === void 0 ? void 0 : parentComment.data) {
17128
- // Skip to update parent childComment if current comment already exists
17129
- if (!parentComment.data.children.includes(comments[0].commentId)) {
17130
- pushToCache(['comment', 'get', comments[0].parentId], Object.assign(Object.assign({}, parentComment.data), { childrenNumber: parentComment.data.childrenNumber + 1, children: [...new Set([...parentComment.data.children, comments[0].commentId])] }));
17131
- }
17132
- }
17133
- }
17134
- }
17828
+ const commentKey = ['comment', 'get', comments[0].commentId];
17829
+ const previous = (_a = pullFromCache(commentKey)) === null || _a === void 0 ? void 0 : _a.data;
17830
+ // NOTE: The event data should be merge with existing cache rather than replace it
17831
+ ingestInCache(processed, undefined, false);
17832
+ const comment = pullFromCache(commentKey);
17833
+ applyApprovalSideEffects(event, comment.data, previous);
17135
17834
  if (['comment.deleted'].includes(event)) {
17136
- if (comments[0].parentId) {
17137
- const parentComment = pullFromCache([
17138
- 'comment',
17139
- 'get',
17140
- comments[0].parentId,
17141
- ]);
17142
- if (parentComment === null || parentComment === void 0 ? void 0 : parentComment.data) {
17143
- // Remove deleted comment in parent childComment if still exists
17144
- if (parentComment.data.children.includes(comments[0].commentId)) {
17145
- const newParentComment = Object.assign(Object.assign({}, parentComment.data), { childrenNumber: parentComment.data.childrenNumber - 1, children: [
17146
- ...new Set([
17147
- ...parentComment.data.children.filter(id => id !== comments[0].commentId),
17148
- ]),
17149
- ] });
17150
- pushToCache(['comment', 'get', comments[0].parentId], newParentComment);
17151
- }
17152
- }
17153
- }
17154
- const queries = (_a = queryCache(['comment', 'query'])) === null || _a === void 0 ? void 0 : _a.filter(({ key }) => { var _a; return ((_a = key[2]) === null || _a === void 0 ? void 0 : _a.referenceId) === comment.data.referenceId; });
17835
+ const queries = (_b = queryCache(['comment', 'query'])) === null || _b === void 0 ? void 0 : _b.filter(({ key }) => { var _a; return ((_a = key[2]) === null || _a === void 0 ? void 0 : _a.referenceId) === comment.data.referenceId; });
17155
17836
  queries === null || queries === void 0 ? void 0 : queries.map(({ key, data }) => upsertInCache(key, data, { cachedAt: -1 }));
17156
17837
  }
17157
17838
  callback(LinkedObject.comment(comment.data));
@@ -17163,13 +17844,13 @@ const createCommentEventSubscriber = (event, callback) => {
17163
17844
  const createLocalCommentEventSubscriber = (event, callback) => {
17164
17845
  const client = getActiveClient();
17165
17846
  const filter = (payload) => {
17166
- var _a, _b, _c, _d, _e;
17847
+ var _a, _b, _c, _d;
17167
17848
  if (!client.cache) {
17168
17849
  // TODO: here we are missing specific properties here!
17169
17850
  callback(LinkedObject.comment(payload.comments[0]));
17170
17851
  }
17171
17852
  else {
17172
- const processed = payload;
17853
+ const processed = prepareCommentPayload(payload);
17173
17854
  ingestInCache(processed);
17174
17855
  const { comments } = processed;
17175
17856
  if (comments.length > 0) {
@@ -17178,7 +17859,9 @@ const createLocalCommentEventSubscriber = (event, callback) => {
17178
17859
  'get',
17179
17860
  comments[0].commentId,
17180
17861
  ]);
17181
- if (['local.comment.created'].includes(event)) {
17862
+ // A pending comment is persisted for its author but never linked into the public
17863
+ // surfaces (parent reply list, post comment list)
17864
+ if (['local.comment.created'].includes(event) && isCommentApproved(comments[0])) {
17182
17865
  if (comments[0].parentId) {
17183
17866
  const parentComment = pullFromCache([
17184
17867
  'comment',
@@ -17207,11 +17890,11 @@ const createLocalCommentEventSubscriber = (event, callback) => {
17207
17890
  const postCacheKey = ['post', 'get', comments[0].referenceId];
17208
17891
  const postCache = (_a = pullFromCache(postCacheKey)) === null || _a === void 0 ? void 0 : _a.data;
17209
17892
  if (postCache) {
17210
- const updatedPostCache = Object.assign(Object.assign({}, postCache), { comments: [...postCache.comments, (_b = comments[0]) === null || _b === void 0 ? void 0 : _b.commentId] });
17893
+ const updatedPostCache = Object.assign(Object.assign({}, postCache), { comments: appendLatestCommentId(postCache, comments[0].commentId) });
17211
17894
  pushToCache(postCacheKey, updatedPostCache);
17212
17895
  }
17213
17896
  }
17214
- const queries = (_c = queryCache(['comment', 'query'])) === null || _c === void 0 ? void 0 : _c.filter(({ key }) => { var _a; return ((_a = key[2]) === null || _a === void 0 ? void 0 : _a.referenceId) === comment.data.referenceId; });
17897
+ const queries = (_b = queryCache(['comment', 'query'])) === null || _b === void 0 ? void 0 : _b.filter(({ key }) => { var _a; return ((_a = key[2]) === null || _a === void 0 ? void 0 : _a.referenceId) === comment.data.referenceId; });
17215
17898
  queries === null || queries === void 0 ? void 0 : queries.map(({ key, data }) => upsertInCache(key, data, { cachedAt: -1 }));
17216
17899
  }
17217
17900
  if (['local.comment.deleted'].includes(event)) {
@@ -17245,13 +17928,13 @@ const createLocalCommentEventSubscriber = (event, callback) => {
17245
17928
  }
17246
17929
  else {
17247
17930
  const postCacheKey = ['post', 'get', comments[0].referenceId];
17248
- const postCache = (_d = pullFromCache(postCacheKey)) === null || _d === void 0 ? void 0 : _d.data;
17931
+ const postCache = (_c = pullFromCache(postCacheKey)) === null || _c === void 0 ? void 0 : _c.data;
17249
17932
  if (postCache) {
17250
17933
  const updatedPost = Object.assign(Object.assign({}, postCache), { comments: postCache === null || postCache === void 0 ? void 0 : postCache.comments.filter(commentId => { var _a; return commentId !== ((_a = comments[0]) === null || _a === void 0 ? void 0 : _a.commentId); }) });
17251
17934
  pushToCache(postCacheKey, updatedPost);
17252
17935
  }
17253
17936
  }
17254
- const queries = (_e = queryCache(['comment', 'query'])) === null || _e === void 0 ? void 0 : _e.filter(({ key }) => { var _a; return ((_a = key[2]) === null || _a === void 0 ? void 0 : _a.referenceId) === comment.data.referenceId; });
17937
+ const queries = (_d = queryCache(['comment', 'query'])) === null || _d === void 0 ? void 0 : _d.filter(({ key }) => { var _a; return ((_a = key[2]) === null || _a === void 0 ? void 0 : _a.referenceId) === comment.data.referenceId; });
17255
17938
  queries === null || queries === void 0 ? void 0 : queries.map(({ key, data }) => upsertInCache(key, data, { cachedAt: -1 }));
17256
17939
  }
17257
17940
  callback(LinkedObject.comment(comment.data));
@@ -17312,6 +17995,23 @@ const onCommentUpdated = (callback) => createCommentEventSubscriber('comment.upd
17312
17995
  */
17313
17996
  const onCommentDeleted = (callback) => createCommentEventSubscriber('comment.deleted', callback);
17314
17997
 
17998
+ /**
17999
+ * ```js
18000
+ * import { onCommentApproved } from '@amityco/ts-sdk'
18001
+ * const dispose = onCommentApproved(comment => {
18002
+ * // ...
18003
+ * })
18004
+ * ```
18005
+ *
18006
+ * Fired when a {@link Amity.InternalComment} held for review has been approved
18007
+ *
18008
+ * @param callback The function to call when the event was fired
18009
+ * @returns an {@link Amity.Unsubscriber} function to stop listening
18010
+ *
18011
+ * @category Comment Events
18012
+ */
18013
+ const onCommentApproved = (callback) => createCommentEventSubscriber('comment.approved', callback);
18014
+
17315
18015
  /**
17316
18016
  * ```js
17317
18017
  * import { onCommentFlagged } from '@amityco/ts-sdk-react-native'
@@ -17944,7 +18644,7 @@ const getReactions$1 = (params, callback, config) => {
17944
18644
  };
17945
18645
  /* end_public_function */
17946
18646
 
17947
- var index$o = /*#__PURE__*/Object.freeze({
18647
+ var index$p = /*#__PURE__*/Object.freeze({
17948
18648
  __proto__: null,
17949
18649
  addReaction: addReaction,
17950
18650
  removeReaction: removeReaction,
@@ -17955,17 +18655,6 @@ var index$o = /*#__PURE__*/Object.freeze({
17955
18655
  getReactions: getReactions$1
17956
18656
  });
17957
18657
 
17958
- function prepareCommentPayload(commentPayload) {
17959
- const { comments } = commentPayload;
17960
- return Object.assign(Object.assign({}, commentPayload), { comments: comments.map(comment => {
17961
- if (comment.hasOwnProperty('myReactions'))
17962
- return comment;
17963
- // Sometimes `myReactions` field will not come with BE response because that field is empty
17964
- // We need to put it with an empty array manually to make it show up in client side
17965
- return Object.assign({ myReactions: [] }, comment);
17966
- }) });
17967
- }
17968
-
17969
18658
  const createMessageReport = async ({ client, referenceId, }) => {
17970
18659
  const { data: payload } = await client.http.post(`/api/v5/messages/${encodeURIComponent(referenceId)}/flags`);
17971
18660
  if (client.cache) {
@@ -18429,20 +19118,20 @@ const getArchivedChannelIds = async () => {
18429
19118
 
18430
19119
  /**
18431
19120
  * ```js
18432
- * import { onMessageUpdated } from '@amityco/ts-sdk-react-native'
18433
- * const dispose = onMessageUpdated(message => {
19121
+ * import { onMessageDeleted } from '@amityco/ts-sdk-react-native'
19122
+ * const dispose = onMessageDeleted(message => {
18434
19123
  * // ...
18435
19124
  * })
18436
19125
  * ```
18437
19126
  *
18438
- * Fired when an {@link Amity.Message} has been updated
19127
+ * Fired when an {@link Amity.Message} was deleted
18439
19128
  *
18440
19129
  * @param callback The function to call when the event was fired
18441
19130
  * @returns an {@link Amity.Unsubscriber} function to stop listening
18442
19131
  *
18443
19132
  * @category Message Events
18444
19133
  */
18445
- const onMessageUpdated = (callback) => {
19134
+ const onMessageDeleted = (callback) => {
18446
19135
  const client = getActiveClient();
18447
19136
  const filter = async (rawPayload) => {
18448
19137
  const payload = await prepareMessagePayload(rawPayload);
@@ -18450,30 +19139,48 @@ const onMessageUpdated = (callback) => {
18450
19139
  callback(payload.messages[0]);
18451
19140
  };
18452
19141
  const disposers = [
18453
- createEventSubscriber(client, 'onMessageUpdated', 'message.updated', filter),
18454
- createEventSubscriber(client, 'onMessageUpdated', 'local.message.updated', payload => callback(payload.messages[0])),
19142
+ createEventSubscriber(client, 'message/onMessageDeleted', 'message.deleted', filter),
19143
+ createEventSubscriber(client, 'message/onMessageDeleted', 'local.message.deleted', payload => callback(payload.messages[0])),
18455
19144
  ];
18456
19145
  return () => {
18457
19146
  disposers.forEach(fn => fn());
18458
19147
  };
18459
19148
  };
18460
19149
 
19150
+ /**
19151
+ * Defensive clear of `channel.pinnedMessage` when the pinned message itself is deleted.
19152
+ *
19153
+ * The server also emits `channel.messageUnpinned` in this case, but realtime events are
19154
+ * fire-and-forget and their order is not guaranteed. Clearing on `message.deleted`
19155
+ * (MQTT or local) makes the banner disappear even if the unpin event is lost; the
19156
+ * server's own `messageUnpinned` then becomes a no-op.
19157
+ *
19158
+ * Internal — wired into the `getChannel` live object; not exported publicly.
19159
+ */
19160
+ const onChannelPinnedMessageDeleted = (callback) => onMessageDeleted(message => {
19161
+ var _a;
19162
+ const channel = findCachedChannelByAnyId(message.channelId);
19163
+ if (!channel || ((_a = channel.pinnedMessage) === null || _a === void 0 ? void 0 : _a.messageId) !== message.messageId)
19164
+ return;
19165
+ callback(updatePinnedMessageCache(channel, null));
19166
+ });
19167
+
18461
19168
  /**
18462
19169
  * ```js
18463
- * import { onMessageDeleted } from '@amityco/ts-sdk-react-native'
18464
- * const dispose = onMessageDeleted(message => {
19170
+ * import { onMessageUpdated } from '@amityco/ts-sdk-react-native'
19171
+ * const dispose = onMessageUpdated(message => {
18465
19172
  * // ...
18466
19173
  * })
18467
19174
  * ```
18468
19175
  *
18469
- * Fired when an {@link Amity.Message} was deleted
19176
+ * Fired when an {@link Amity.Message} has been updated
18470
19177
  *
18471
19178
  * @param callback The function to call when the event was fired
18472
19179
  * @returns an {@link Amity.Unsubscriber} function to stop listening
18473
19180
  *
18474
19181
  * @category Message Events
18475
19182
  */
18476
- const onMessageDeleted = (callback) => {
19183
+ const onMessageUpdated = (callback) => {
18477
19184
  const client = getActiveClient();
18478
19185
  const filter = async (rawPayload) => {
18479
19186
  const payload = await prepareMessagePayload(rawPayload);
@@ -18481,8 +19188,8 @@ const onMessageDeleted = (callback) => {
18481
19188
  callback(payload.messages[0]);
18482
19189
  };
18483
19190
  const disposers = [
18484
- createEventSubscriber(client, 'message/onMessageDeleted', 'message.deleted', filter),
18485
- createEventSubscriber(client, 'message/onMessageDeleted', 'local.message.deleted', payload => callback(payload.messages[0])),
19191
+ createEventSubscriber(client, 'onMessageUpdated', 'message.updated', filter),
19192
+ createEventSubscriber(client, 'onMessageUpdated', 'local.message.updated', payload => callback(payload.messages[0])),
18486
19193
  ];
18487
19194
  return () => {
18488
19195
  disposers.forEach(fn => fn());
@@ -19063,6 +19770,132 @@ softDeleteMessage.optimistically = (messageId) => {
19063
19770
  };
19064
19771
  /* end_public_function */
19065
19772
 
19773
+ /**
19774
+ * Fire-and-forget refresh of `channelUsers[...].permissions` for the channel a
19775
+ * message belongs to. Used by `pinMessage` / `unpinMessage` on `403` so the next
19776
+ * `hasPermission(PIN_MESSAGE).channel(channelId)` reflects a revoked role
19777
+ * ([`pinMessage` REQ-010](../../@types/../..) / [`unpinMessage` REQ-008](../../@types/../..)).
19778
+ *
19779
+ * The API only receives a `messageId`, so the channel is resolved through the
19780
+ * local cache: first the message row, then any cached channel whose current pin
19781
+ * is that message. When nothing matches the refresh is skipped — the next
19782
+ * channel read picks up the fresh permissions anyway.
19783
+ */
19784
+ const refreshChannelPermissionsForMessage = (messageId) => {
19785
+ var _a, _b, _c;
19786
+ const cachedMessage = (_a = getMessage$1.locally(messageId)) === null || _a === void 0 ? void 0 : _a.data;
19787
+ let channelId = cachedMessage === null || cachedMessage === void 0 ? void 0 : cachedMessage.channelId;
19788
+ if (!channelId) {
19789
+ channelId = (_c = (_b = queryCache(['channel', 'get'])) === null || _b === void 0 ? void 0 : _b.find(({ data }) => { var _a; return ((_a = data.pinnedMessage) === null || _a === void 0 ? void 0 : _a.messageId) === messageId; })) === null || _c === void 0 ? void 0 : _c.data.channelId;
19790
+ }
19791
+ if (!channelId)
19792
+ return;
19793
+ getChannel$1(channelId).catch(() => {
19794
+ /* silent per spec; UIKit renders no error toast */
19795
+ });
19796
+ };
19797
+
19798
+ /* begin_public_function
19799
+ id: message.pin
19800
+ */
19801
+ /**
19802
+ * ```js
19803
+ * import { MessageRepository } from '@amityco/ts-sdk'
19804
+ * const channel = await MessageRepository.pinMessage(messageId)
19805
+ * console.log(channel.pinnedMessage)
19806
+ * ```
19807
+ *
19808
+ * Pins a {@link Amity.Message} in its livestream (`live`) channel. A channel holds at
19809
+ * most one pinned message: pinning a different message replaces the current pin in a
19810
+ * single step — no unpin is needed first. Pinning the message that is already pinned is
19811
+ * rejected by the server (400).
19812
+ *
19813
+ * The caller must hold the `PIN_MESSAGE` permission in the channel (or be an admin with
19814
+ * `ModerateChannel`). The server also rejects pinning a deleted message, a message whose
19815
+ * author is banned from the channel or globally banned, and pinning in any channel type
19816
+ * other than `live`. On any error the pin state is left unchanged.
19817
+ *
19818
+ * The response is the affected channel; it is ingested into cache and fires
19819
+ * `local.channel.updated`, so `ChannelRepository.getChannel` observers update without
19820
+ * waiting for the `channel.messagePinned` realtime event.
19821
+ *
19822
+ * @param messageId the **internal** ID of the {@link Amity.Message} to pin
19823
+ * @returns the updated {@link Amity.Channel}, with `pinnedMessage` set
19824
+ *
19825
+ * @category Message API
19826
+ * @async
19827
+ */
19828
+ const pinMessage = async (messageId) => {
19829
+ const client = getActiveClient();
19830
+ client.log('message/pinMessage', messageId);
19831
+ let payload;
19832
+ try {
19833
+ ({ data: payload } = await client.http.post(`/api/v5/messages/${encodeURIComponent(messageId)}/pin`));
19834
+ }
19835
+ catch (error) {
19836
+ if ((error === null || error === void 0 ? void 0 : error.code) === 400301 /* Amity.ServerError.PERMISSION_DENIED */) {
19837
+ refreshChannelPermissionsForMessage(messageId);
19838
+ }
19839
+ throw error;
19840
+ }
19841
+ const data = await prepareChannelPayload(payload);
19842
+ if (client.cache)
19843
+ ingestInCache(data);
19844
+ fireEvent('local.channel.updated', data);
19845
+ return constructChannelObject(data.channels[0]);
19846
+ };
19847
+ /* end_public_function */
19848
+
19849
+ /* begin_public_function
19850
+ id: message.unpin
19851
+ */
19852
+ /**
19853
+ * ```js
19854
+ * import { MessageRepository } from '@amityco/ts-sdk'
19855
+ * const channel = await MessageRepository.unpinMessage(messageId)
19856
+ * console.log(channel.pinnedMessage) // null
19857
+ * ```
19858
+ *
19859
+ * Removes the pin from a {@link Amity.Message}. `messageId` must be the message that is
19860
+ * **currently** pinned in its channel; otherwise the server answers 400 — this is
19861
+ * deliberate, so a stale client cannot clear a pin that someone else has since replaced.
19862
+ * On that error, re-read the channel (or rely on the `channel.messagePinned` event) to
19863
+ * get the current pin.
19864
+ *
19865
+ * The caller must hold the `PIN_MESSAGE` permission in the channel (or be an admin with
19866
+ * `ModerateChannel`). Unpinning is allowed regardless of channel type and stream state.
19867
+ *
19868
+ * The response is the affected channel; it is ingested into cache and fires
19869
+ * `local.channel.updated`, so `ChannelRepository.getChannel` observers update without
19870
+ * waiting for the `channel.messageUnpinned` realtime event.
19871
+ *
19872
+ * @param messageId the **internal** ID of the currently pinned {@link Amity.Message}
19873
+ * @returns the updated {@link Amity.Channel}, with `pinnedMessage` cleared
19874
+ *
19875
+ * @category Message API
19876
+ * @async
19877
+ */
19878
+ const unpinMessage = async (messageId) => {
19879
+ const client = getActiveClient();
19880
+ client.log('message/unpinMessage', messageId);
19881
+ let payload;
19882
+ try {
19883
+ ({ data: payload } = await client.http.delete(`/api/v5/messages/${encodeURIComponent(messageId)}/pin`));
19884
+ }
19885
+ catch (error) {
19886
+ if ((error === null || error === void 0 ? void 0 : error.code) === 400301 /* Amity.ServerError.PERMISSION_DENIED */) {
19887
+ refreshChannelPermissionsForMessage(messageId);
19888
+ }
19889
+ throw error;
19890
+ }
19891
+ const data = await prepareChannelPayload(payload);
19892
+ if (client.cache)
19893
+ ingestInCache(data);
19894
+ fireEvent('local.channel.updated', data);
19895
+ return constructChannelObject(data.channels[0]);
19896
+ };
19897
+ /* end_public_function */
19898
+
19066
19899
  /**
19067
19900
  * ```js
19068
19901
  * import { MessageRepository } from '@amityco/ts-sdk-react-native'
@@ -19959,13 +20792,15 @@ const searchMessage = (params, callback) => {
19959
20792
  };
19960
20793
  /* end_public_function */
19961
20794
 
19962
- var index$n = /*#__PURE__*/Object.freeze({
20795
+ var index$o = /*#__PURE__*/Object.freeze({
19963
20796
  __proto__: null,
19964
20797
  createMessage: createMessage,
19965
20798
  updateMessage: updateMessage,
19966
20799
  editMessage: editMessage,
19967
20800
  deleteMessage: deleteMessage,
19968
20801
  softDeleteMessage: softDeleteMessage,
20802
+ pinMessage: pinMessage,
20803
+ unpinMessage: unpinMessage,
19969
20804
  markAsDelivered: markAsDelivered,
19970
20805
  getReadUsers: getReadUsers,
19971
20806
  getDeliveredUsers: getDeliveredUsers,
@@ -20495,7 +21330,7 @@ const stopMessageReceiptSync = (subChannelId) => {
20495
21330
  };
20496
21331
  /* end_public_function */
20497
21332
 
20498
- var index$m = /*#__PURE__*/Object.freeze({
21333
+ var index$n = /*#__PURE__*/Object.freeze({
20499
21334
  __proto__: null,
20500
21335
  getSubChannelByIds: getSubChannels$1,
20501
21336
  createSubChannel: createSubChannel,
@@ -20602,7 +21437,12 @@ const getChannel = (channelId, callback) => {
20602
21437
  onChannelMemberRemoved,
20603
21438
  onChannelMemberBanned,
20604
21439
  onChannelMemberUnbanned,
21440
+ onChannelMemberRoleAdded,
21441
+ onChannelMemberRoleRemoved,
20605
21442
  onChannelSetMuted,
21443
+ onChannelMessagePinned,
21444
+ onChannelMessageUnpinned,
21445
+ onChannelPinnedMessageDeleted,
20606
21446
  convertEventPayload(onChannelMarkerFetched, 'entityId', 'channel'),
20607
21447
  convertEventPayload(onChannelMarkerUpdated, 'entityId', 'channel'),
20608
21448
  convertEventPayload((callback) => {
@@ -21228,6 +22068,14 @@ class ChannelLiveCollectionController extends LiveCollectionController {
21228
22068
  return;
21229
22069
  if (messagePreviewSetting ===
21230
22070
  "message-preview-not-include-deleted" /* Amity.MessagePreviewSetting.MESSAGE_PREVIEW_NOT_INCLUDE_DELETED */) {
22071
+ // The replacement preview the server sends back is an OLDER
22072
+ // message than the one just deleted, and
22073
+ // updateChannelMessagePreviewCache drops any preview that is not
22074
+ // newer than what is cached — so without clearing the entry
22075
+ // first the refetch is a no-op and the channel keeps showing the
22076
+ // deleted message. getChannel's own live object already does
22077
+ // this; the live collections did not.
22078
+ dropFromCache(['messagePreviewChannel', 'get', message.channelId]);
21231
22079
  await getChannel$1(message.channelId);
21232
22080
  }
21233
22081
  callback(message);
@@ -21952,6 +22800,14 @@ class SearchChannelLiveCollectionController extends LiveCollectionController {
21952
22800
  return;
21953
22801
  if (messagePreviewSetting ===
21954
22802
  "message-preview-not-include-deleted" /* Amity.MessagePreviewSetting.MESSAGE_PREVIEW_NOT_INCLUDE_DELETED */) {
22803
+ // The replacement preview the server sends back is an OLDER
22804
+ // message than the one just deleted, and
22805
+ // updateChannelMessagePreviewCache drops any preview that is not
22806
+ // newer than what is cached — so without clearing the entry
22807
+ // first the refetch is a no-op and the channel keeps showing the
22808
+ // deleted message. getChannel's own live object already does
22809
+ // this; the live collections did not.
22810
+ dropFromCache(['messagePreviewChannel', 'get', message.channelId]);
21955
22811
  await getChannel$1(message.channelId);
21956
22812
  }
21957
22813
  callback(message);
@@ -22446,7 +23302,7 @@ const searchMembers$1 = (params, callback, config) => {
22446
23302
  };
22447
23303
  /* end_public_function */
22448
23304
 
22449
- var index$l = /*#__PURE__*/Object.freeze({
23305
+ var index$m = /*#__PURE__*/Object.freeze({
22450
23306
  __proto__: null,
22451
23307
  addMembers: addMembers$1,
22452
23308
  removeMembers: removeMembers$1,
@@ -22668,7 +23524,7 @@ const unmuteMembers = async (channelId, userIds) => {
22668
23524
  };
22669
23525
  /* end_public_function */
22670
23526
 
22671
- var index$k = /*#__PURE__*/Object.freeze({
23527
+ var index$l = /*#__PURE__*/Object.freeze({
22672
23528
  __proto__: null,
22673
23529
  addRole: addRole,
22674
23530
  removeRole: removeRole,
@@ -22678,10 +23534,10 @@ var index$k = /*#__PURE__*/Object.freeze({
22678
23534
  unmuteMembers: unmuteMembers
22679
23535
  });
22680
23536
 
22681
- var index$j = /*#__PURE__*/Object.freeze({
23537
+ var index$k = /*#__PURE__*/Object.freeze({
22682
23538
  __proto__: null,
22683
- Membership: index$l,
22684
- Moderation: index$k,
23539
+ Membership: index$m,
23540
+ Moderation: index$l,
22685
23541
  getChannelByIds: getChannelByIds$1,
22686
23542
  createChannel: createChannel,
22687
23543
  updateChannel: updateChannel,
@@ -22699,6 +23555,8 @@ var index$j = /*#__PURE__*/Object.freeze({
22699
23555
  onChannelJoined: onChannelJoined,
22700
23556
  onChannelLeft: onChannelLeft,
22701
23557
  onChannelSetMuted: onChannelSetMuted,
23558
+ onChannelMessagePinned: onChannelMessagePinned,
23559
+ onChannelMessageUnpinned: onChannelMessageUnpinned,
22702
23560
  onChannelMemberAdded: onChannelMemberAdded,
22703
23561
  onChannelMemberRemoved: onChannelMemberRemoved,
22704
23562
  onChannelMemberBanned: onChannelMemberBanned,
@@ -22716,7 +23574,11 @@ var index$j = /*#__PURE__*/Object.freeze({
22716
23574
  isUnreadCountSupport: isUnreadCountSupport,
22717
23575
  convertFromRaw: convertFromRaw,
22718
23576
  preUpdateChannelCache: preUpdateChannelCache,
22719
- prepareChannelPayload: prepareChannelPayload
23577
+ prepareChannelPayload: prepareChannelPayload,
23578
+ findCachedChannelByAnyId: findCachedChannelByAnyId,
23579
+ findCachedChannelForPin: findCachedChannelForPin,
23580
+ updatePinnedMessageCache: updatePinnedMessageCache,
23581
+ applyChannelRolePayload: applyChannelRolePayload
22720
23582
  });
22721
23583
 
22722
23584
  const saveCommunityUsers = (communities, communityUsers) => {
@@ -24110,7 +24972,7 @@ const searchMembers = (params, callback, config) => {
24110
24972
  };
24111
24973
  /* end_public_function */
24112
24974
 
24113
- var index$i = /*#__PURE__*/Object.freeze({
24975
+ var index$j = /*#__PURE__*/Object.freeze({
24114
24976
  __proto__: null,
24115
24977
  addMembers: addMembers,
24116
24978
  removeMembers: removeMembers,
@@ -25361,7 +26223,7 @@ const unbanMembers = async (communityId, userIds) => {
25361
26223
  };
25362
26224
  /* end_public_function */
25363
26225
 
25364
- var index$h = /*#__PURE__*/Object.freeze({
26226
+ var index$i = /*#__PURE__*/Object.freeze({
25365
26227
  __proto__: null,
25366
26228
  addRoles: addRoles,
25367
26229
  removeRoles: removeRoles,
@@ -25369,10 +26231,10 @@ var index$h = /*#__PURE__*/Object.freeze({
25369
26231
  unbanMembers: unbanMembers
25370
26232
  });
25371
26233
 
25372
- var index$g = /*#__PURE__*/Object.freeze({
26234
+ var index$h = /*#__PURE__*/Object.freeze({
25373
26235
  __proto__: null,
25374
- Moderation: index$h,
25375
- Membership: index$i,
26236
+ Moderation: index$i,
26237
+ Membership: index$j,
25376
26238
  getCommunityByIds: getCommunities$1,
25377
26239
  createCommunity: createCommunity,
25378
26240
  updateCommunity: updateCommunity,
@@ -25609,7 +26471,7 @@ const getCategories = (params, callback, config) => {
25609
26471
  };
25610
26472
  /* end_public_function */
25611
26473
 
25612
- var index$f = /*#__PURE__*/Object.freeze({
26474
+ var index$g = /*#__PURE__*/Object.freeze({
25613
26475
  __proto__: null,
25614
26476
  getCategory: getCategory,
25615
26477
  getCategories: getCategories
@@ -25908,6 +26770,11 @@ const createCommentOptimistic = (bundle) => {
25908
26770
  targetType: 'community',
25909
26771
  dataType: 'text',
25910
26772
  syncState: "syncing" /* Amity.SyncState.Syncing */,
26773
+ // In a gated community the optimistic object stays out of public collections until the
26774
+ // response settles the status; the author's own view still updates through the local event
26775
+ approvalStatus: isCommentApprovalRequired(bundle)
26776
+ ? "pending" /* Amity.CommentApprovalStatusEnum.Pending */
26777
+ : "approved" /* Amity.CommentApprovalStatusEnum.Approved */,
25911
26778
  };
25912
26779
  const cachedAt = UNSYNCED_OBJECT_CACHED_AT_VALUE;
25913
26780
  pushToCache(['comment', 'get', comment.commentId], comment, { cachedAt });
@@ -25924,32 +26791,57 @@ const createCommentOptimistic = (bundle) => {
25924
26791
  * ```js
25925
26792
  * import { CommentRepository } from '@amityco/ts-sdk-react-native'
25926
26793
  * const newComment = await CommentRepository.createComment(bundle)
26794
+ *
26795
+ * // Server-confirmed only: nothing is shown until the request succeeds
26796
+ * const confirmed = await CommentRepository.createComment(bundle, false)
25927
26797
  * ```
25928
26798
  *
25929
26799
  * Creates an {@link Amity.Comment}
25930
26800
  *
26801
+ * With `isOptimistic` (the default) a local comment appears at once with `syncState: 'syncing'`,
26802
+ * moves to `'synced'` when the server confirms it and to `'error'` if the request fails, so the
26803
+ * caller can offer a retry. With `isOptimistic: false` nothing is created locally until the
26804
+ * server answers: on success the comment arrives already `'synced'`; on failure no comment
26805
+ * entity exists anywhere and the promise rejects.
26806
+ *
25931
26807
  * @param bundle The data necessary to create a new {@link Amity.Comment}
26808
+ * @param isOptimistic Show a local comment before the server confirms it. Default `true`.
26809
+ * Ignored when the client cache is disabled, since there is nowhere to hold the local comment.
25932
26810
  * @returns The newly created {@link Amity.Comment}
25933
26811
  *
25934
26812
  * @category Comment API
25935
26813
  * @async
25936
26814
  */
25937
- const createComment = async (bundle) => {
26815
+ const createComment = async (bundle, isOptimistic = true) => {
25938
26816
  const client = getActiveClient();
25939
- client.log('comment/createComment', bundle);
25940
- const optimisticData = createCommentOptimistic(bundle);
26817
+ client.log('comment/createComment', Object.assign(Object.assign({}, bundle), { isOptimistic }));
26818
+ // Without a local comment the server assigns the id; with one, the same id travels through the
26819
+ // optimistic object, the response and every realtime event so each step recognises it.
26820
+ const optimisticData = isOptimistic ? createCommentOptimistic(bundle) : undefined;
25941
26821
  const commentId = optimisticData === null || optimisticData === void 0 ? void 0 : optimisticData.commentId;
25942
26822
  try {
25943
- const { data } = await client.http.post('/api/v3/comments', Object.assign(Object.assign({}, bundle), { commentId }));
25944
- const { comments } = data;
26823
+ const { data: payload } = await client.http.post('/api/v3/comments', Object.assign(Object.assign({}, bundle), { commentId }));
25945
26824
  // BE always returns an array of comments If it got record 0 from BE it might have a problem on creation logic
25946
- if (comments.length === 0)
26825
+ if (payload.comments.length === 0)
25947
26826
  throw new Error('Comment not created');
26827
+ // Absent `approvalStatus` means approved; the result is the only source of the status.
26828
+ // The confirmed comment is written as `synced` so the cache itself tells the two states apart
26829
+ // (a local-event subscriber is not guaranteed to be listening).
26830
+ const prepared = prepareCommentPayload(payload);
26831
+ const data = Object.assign(Object.assign({}, prepared), { comments: prepared.comments.map(comment => comment.commentId === (commentId !== null && commentId !== void 0 ? commentId : comment.commentId)
26832
+ ? Object.assign(Object.assign({}, comment), { syncState: "synced" /* Amity.SyncState.Synced */ }) : comment) });
26833
+ const { comments } = data;
25948
26834
  const cachedAt = client.cache && Date.now();
25949
26835
  if (client.cache)
25950
26836
  ingestInCache(data, { cachedAt });
25951
26837
  if (['post', 'content'].includes(bundle.referenceType)) {
25952
- PostCommentCountEngine$1.getInstance().queueCommentChangeTask(new CreateTask(bundle.referenceId, comments[0].commentId, data.comments[0].createdAt));
26838
+ if (isCommentPending(comments[0])) {
26839
+ // Not counted for anyone else until approved; the author sees their own +1
26840
+ recordPendingAuthoredComment(comments[0]);
26841
+ }
26842
+ else {
26843
+ PostCommentCountEngine$1.getInstance().queueCommentChangeTask(new CreateTask(bundle.referenceId, comments[0].commentId, comments[0].createdAt));
26844
+ }
25953
26845
  }
25954
26846
  else if (bundle.referenceType === 'story') {
25955
26847
  const storyIndex = pullFromCache([
@@ -25970,9 +26862,9 @@ const createComment = async (bundle) => {
25970
26862
  categories: [],
25971
26863
  comments,
25972
26864
  communities: [],
25973
- communityUsers: data.communityUsers,
25974
- files: data.files,
25975
- users: data.users,
26865
+ communityUsers: payload.communityUsers,
26866
+ files: payload.files,
26867
+ users: payload.users,
25976
26868
  });
25977
26869
  }
25978
26870
  }
@@ -25980,9 +26872,9 @@ const createComment = async (bundle) => {
25980
26872
  fireEvent('local.comment.created', {
25981
26873
  comments: [Object.assign(Object.assign({}, comments[0]), { syncState: "synced" /* Amity.SyncState.Synced */ })],
25982
26874
  commentChildren: data.commentChildren,
25983
- users: data.users,
25984
- files: data.files,
25985
- communityUsers: data.communityUsers,
26875
+ users: payload.users,
26876
+ files: payload.files,
26877
+ communityUsers: payload.communityUsers,
25986
26878
  });
25987
26879
  return {
25988
26880
  data: LinkedObject.comment(comments[0]),
@@ -25990,9 +26882,17 @@ const createComment = async (bundle) => {
25990
26882
  };
25991
26883
  }
25992
26884
  catch (e) {
26885
+ // Only a local comment has anything to mark as failed; a server-confirmed-only create leaves
26886
+ // no entity behind
25993
26887
  if (optimisticData) {
26888
+ const failed = Object.assign(Object.assign({}, optimisticData), { syncState: "error" /* Amity.SyncState.Error */ });
26889
+ // Write the failure to the cache directly: the local event below only reaches pages that
26890
+ // observe comments, and the caller's retry UI reads the cached state
26891
+ pushToCache(['comment', 'get', failed.commentId], failed, {
26892
+ cachedAt: UNSYNCED_OBJECT_CACHED_AT_VALUE,
26893
+ });
25994
26894
  fireEvent('local.comment.created', {
25995
- comments: [Object.assign(Object.assign({}, optimisticData), { syncState: "error" /* Amity.SyncState.Error */ })],
26895
+ comments: [failed],
25996
26896
  commentChildren: [],
25997
26897
  users: [],
25998
26898
  files: [],
@@ -26025,16 +26925,26 @@ const createComment = async (bundle) => {
26025
26925
  * @async
26026
26926
  */
26027
26927
  const updateComment = async (commentId, patch) => {
26928
+ var _a;
26028
26929
  const client = getActiveClient();
26029
26930
  client.log('user/updateComment', patch);
26030
- const { data } = await client.http.put(`/api/v3/comments/${encodeURIComponent(commentId)}`, patch);
26931
+ const previous = (_a = pullFromCache(['comment', 'get', commentId])) === null || _a === void 0 ? void 0 : _a.data;
26932
+ const { data: payload } = await client.http.put(`/api/v3/comments/${encodeURIComponent(commentId)}`, patch);
26933
+ const data = prepareCommentPayload(payload);
26934
+ const updated = data.comments.find(comment => comment.commentId === commentId);
26031
26935
  const cachedAt = client.cache && Date.now();
26032
26936
  if (client.cache)
26033
26937
  ingestInCache(data, { cachedAt });
26034
- fireEvent('comment.updated', data);
26035
- const { comments } = data;
26938
+ // An effective edit in a gated community sends the comment back for review: it leaves the
26939
+ // public reply list and the public count once. The `comment.updated` event below removes it
26940
+ // from the comment collections; the realtime echo finds it already pending and does nothing.
26941
+ if (isCommentPending(updated) && previous && isCommentApproved(previous)) {
26942
+ unlinkCommentFromParent(updated);
26943
+ applyCommentReReviewCount(updated);
26944
+ }
26945
+ fireEvent('comment.updated', payload);
26036
26946
  return {
26037
- data: LinkedObject.comment(comments.find(comment => comment.commentId === commentId)),
26947
+ data: LinkedObject.comment(updated),
26038
26948
  cachedAt,
26039
26949
  };
26040
26950
  };
@@ -26174,7 +27084,13 @@ const deleteComment = async (commentId, permanent = false) => {
26174
27084
  const post = (_b = pullFromCache(['post', 'get', comment.data.referenceId])) === null || _b === void 0 ? void 0 : _b.data;
26175
27085
  if (post) {
26176
27086
  const engine = PostCommentCountEngine$1.getInstance();
26177
- engine.queueCommentChangeTask(new DeleteTask(post.postId, commentId));
27087
+ if (isCommentPending(comment.data)) {
27088
+ // Never counted for anyone else; only the author's own +1 goes away
27089
+ engine.removePendingAuthoredComment(post.postId, commentId);
27090
+ }
27091
+ else {
27092
+ engine.queueCommentChangeTask(new DeleteTask(post.postId, commentId));
27093
+ }
26178
27094
  if (!deleted.parentId && ((_c = deleted.children /* c8 ignore next */) === null || _c === void 0 ? void 0 : _c.length) > 0) {
26179
27095
  // NOTE: delete the parent comment will also remove all children comments
26180
27096
  deleted.children.forEach((childCommentId) => {
@@ -26244,6 +27160,109 @@ const hardDeleteComment = async (commentId) => {
26244
27160
  };
26245
27161
  /* end_public_function */
26246
27162
 
27163
+ /* begin_public_function
27164
+ id: comment.approve
27165
+ */
27166
+ /**
27167
+ * ```js
27168
+ * import { CommentRepository } from '@amityco/ts-sdk'
27169
+ *
27170
+ * const { data: comment } = await CommentRepository.approveComment('commentId')
27171
+ * ```
27172
+ *
27173
+ * Approves a {@link Amity.Comment} held for review, making it visible to everyone
27174
+ *
27175
+ * @param commentId The {@link Amity.Comment} ID to be approved
27176
+ * @return The {@link Amity.Comment} that was approved
27177
+ *
27178
+ * @category Comment API
27179
+ * @async
27180
+ */
27181
+ const approveComment = async (commentId) => {
27182
+ const client = getActiveClient();
27183
+ client.log('comment/approveComment', commentId);
27184
+ // No request body, and nothing is revealed before the server confirms
27185
+ const { data: payload } = await client.http.post(`/api/v3/comments/${encodeURIComponent(commentId)}/approve`);
27186
+ const data = prepareCommentPayload(prepareMembershipPayload(payload, 'communityUsers'));
27187
+ const approved = data.comments.find(comment => comment.commentId === commentId);
27188
+ const cachedAt = client.cache && Date.now();
27189
+ if (client.cache)
27190
+ ingestInCache(data, { cachedAt });
27191
+ // The reveal path: parent link and exactly one count task. Applied here so it does not depend
27192
+ // on an observer being open; the event below and the realtime `comment.approved` that follows
27193
+ // find the work done and reconcile by comment id (collection insert, queue removal).
27194
+ linkCommentToParent(approved);
27195
+ applyCommentApprovedCount(approved);
27196
+ fireEvent('comment.approved', payload);
27197
+ return {
27198
+ data: LinkedObject.comment(approved),
27199
+ cachedAt,
27200
+ };
27201
+ };
27202
+ /* end_public_function */
27203
+
27204
+ // Rejected is terminal and implies deleted
27205
+ const markRejected = (comment) => (Object.assign(Object.assign({}, comment), { approvalStatus: "rejected" /* Amity.CommentApprovalStatusEnum.Rejected */, isDeleted: true }));
27206
+ /* begin_public_function
27207
+ id: comment.decline
27208
+ */
27209
+ /**
27210
+ * ```js
27211
+ * import { CommentRepository } from '@amityco/ts-sdk'
27212
+ *
27213
+ * const { data: comment } = await CommentRepository.declineComment('commentId', 'Off-topic')
27214
+ * ```
27215
+ *
27216
+ * Declines a {@link Amity.Comment} held for review. A declined comment is also a deleted comment
27217
+ *
27218
+ * @param commentId The {@link Amity.Comment} ID to be declined
27219
+ * @param reason Optional reviewer's note, at most 250 characters, kept for the moderation record only
27220
+ * @return The {@link Amity.Comment} that was declined
27221
+ *
27222
+ * @category Comment API
27223
+ * @async
27224
+ */
27225
+ const declineComment = async (commentId, reason) => {
27226
+ var _a, _b, _c, _d, _e, _f;
27227
+ const client = getActiveClient();
27228
+ client.log('comment/declineComment', commentId, reason);
27229
+ // Read before the request: the fallback when the response carries no comment
27230
+ const cached = (_a = pullFromCache(['comment', 'get', commentId])) === null || _a === void 0 ? void 0 : _a.data;
27231
+ // `reason` is surfaced to nobody; the key is absent from the body when the caller gives none
27232
+ const { data: payload } = await client.http.post(`/api/v3/comments/${encodeURIComponent(commentId)}/decline`, reason ? { reason } : undefined);
27233
+ // The BE contract (PDT-5135 §3) states the response body for approve only; for decline the
27234
+ // standard comment payload is assumed. Ingest whatever came back, and when the body carries no
27235
+ // comment fall back to the cached one so the delete path below still runs.
27236
+ const returned = (_b = payload === null || payload === void 0 ? void 0 : payload.comments) === null || _b === void 0 ? void 0 : _b.some(comment => comment.commentId === commentId);
27237
+ if (!returned && !cached)
27238
+ throw new Error('Comment not found after decline');
27239
+ const comments = returned
27240
+ ? payload.comments.map(comment => comment.commentId === commentId ? markRejected(comment) : comment)
27241
+ : [markRejected(cached)];
27242
+ const declined = {
27243
+ comments,
27244
+ commentChildren: (_c = payload === null || payload === void 0 ? void 0 : payload.commentChildren) !== null && _c !== void 0 ? _c : [],
27245
+ users: (_d = payload === null || payload === void 0 ? void 0 : payload.users) !== null && _d !== void 0 ? _d : [],
27246
+ files: (_e = payload === null || payload === void 0 ? void 0 : payload.files) !== null && _e !== void 0 ? _e : [],
27247
+ communityUsers: (_f = payload === null || payload === void 0 ? void 0 : payload.communityUsers) !== null && _f !== void 0 ? _f : [],
27248
+ };
27249
+ // There is no `comment.declined` event: the existing delete path drops the comment from every
27250
+ // collection and the review queue, and moves no count because a pending comment was never counted.
27251
+ const data = prepareCommentPayload(prepareMembershipPayload(declined, 'communityUsers'));
27252
+ const rejected = data.comments.find(comment => comment.commentId === commentId);
27253
+ const cachedAt = client.cache && Date.now();
27254
+ if (client.cache)
27255
+ ingestInCache(data, { cachedAt });
27256
+ unlinkCommentFromParent(rejected);
27257
+ releasePendingAuthoredComment(rejected);
27258
+ fireEvent('comment.deleted', declined);
27259
+ return {
27260
+ data: LinkedObject.comment(rejected),
27261
+ cachedAt,
27262
+ };
27263
+ };
27264
+ /* end_public_function */
27265
+
26247
27266
  /* begin_public_function
26248
27267
  id: comment.flag
26249
27268
  */
@@ -26329,6 +27348,7 @@ const isCommentFlaggedByMe = async (commentId) => {
26329
27348
  /* end_public_function */
26330
27349
 
26331
27350
  const getComment$1 = async (commentId) => {
27351
+ var _a;
26332
27352
  const client = getActiveClient();
26333
27353
  client.log('comment/getComment', commentId);
26334
27354
  isInTombstone('comment', commentId);
@@ -26339,7 +27359,11 @@ const getComment$1 = async (commentId) => {
26339
27359
  data = response.data;
26340
27360
  }
26341
27361
  catch (error) {
26342
- if (checkIfShouldGoesToTombstone(error === null || error === void 0 ? void 0 : error.code)) {
27362
+ // A pending comment answers 404 to anyone but its author and reviewers. That is a
27363
+ // visibility answer, not a deletion — never tombstone a comment known to be pending.
27364
+ const cached = (_a = pullFromCache(['comment', 'get', commentId])) === null || _a === void 0 ? void 0 : _a.data;
27365
+ const isKnownPending = !!cached && isCommentPending(cached);
27366
+ if (!isKnownPending && checkIfShouldGoesToTombstone(error === null || error === void 0 ? void 0 : error.code)) {
26343
27367
  pushToTombstone('comment', commentId);
26344
27368
  }
26345
27369
  throw error;
@@ -26466,6 +27490,7 @@ const onLocalCommentReactionRemoved = (callback) => {
26466
27490
  */
26467
27491
  const getComment = (commentId, callback) => {
26468
27492
  return liveObject(commentId, callback, 'commentId', getComment$1, [
27493
+ onCommentApproved,
26469
27494
  onCommentDeleteLocal,
26470
27495
  onCommentDeleted,
26471
27496
  onCommentFlagged,
@@ -26479,28 +27504,31 @@ const getComment = (commentId, callback) => {
26479
27504
  };
26480
27505
  /* end_public_function */
26481
27506
 
26482
- class CommentPaginationController extends PaginationController {
26483
- async getRequest(queryParams, token) {
26484
- var _a;
26485
- const { pageSize, limit, includeDeleted } = queryParams, params = __rest(queryParams, ["pageSize", "limit", "includeDeleted"]);
26486
- const effectiveLimit = (_a = pageSize !== null && pageSize !== void 0 ? pageSize : limit) !== null && _a !== void 0 ? _a : COLLECTION_DEFAULT_PAGINATION_LIMIT;
26487
- const baseOptions = {
26488
- type: params.sortBy || queryParams.limit || queryParams.pageSize ? 'pagination' : undefined,
26489
- };
26490
- const options = token
26491
- ? Object.assign(Object.assign({}, baseOptions), { token }) : Object.assign(Object.assign({}, baseOptions), { pageSize: effectiveLimit, limit: effectiveLimit });
26492
- const { data: queryResponse } = await this.http.get(`/api/v3/comments`, {
26493
- params: Object.assign(Object.assign({}, params), { isDeleted: inferIsDeleted(includeDeleted), options }),
26494
- });
26495
- return queryResponse;
26496
- }
26497
- }
27507
+ /**
27508
+ * ```js
27509
+ * import { onCommentCreated } from '@amityco/ts-sdk-react-native'
27510
+ * const dispose = onCommentCreated(comment => {
27511
+ * // ...
27512
+ * })
27513
+ * ```
27514
+ *
27515
+ * Fired when a {@link Amity.InternalComment} has been created
27516
+ *
27517
+ * @param callback The function to call when the event was fired
27518
+ * @returns an {@link Amity.Unsubscriber} function to stop listening
27519
+ *
27520
+ * @category Comment Events
27521
+ */
27522
+ const onCommentCreatedLocal = (callback) => {
27523
+ return createLocalCommentEventSubscriber('local.comment.created', callback);
27524
+ };
26498
27525
 
26499
27526
  var EnumCommentActions;
26500
27527
  (function (EnumCommentActions) {
26501
27528
  EnumCommentActions["OnCommentCreated"] = "onCommentCreated";
26502
27529
  EnumCommentActions["OnCommentUpdated"] = "onCommentUpdated";
26503
27530
  EnumCommentActions["OnCommentDeleted"] = "onCommentDeleted";
27531
+ EnumCommentActions["OnCommentApproved"] = "onCommentApproved";
26504
27532
  EnumCommentActions["OnCommentFlagged"] = "onCommentFlagged";
26505
27533
  EnumCommentActions["OnCommentUnflagged"] = "onCommentUnflagged";
26506
27534
  EnumCommentActions["OnCommentReactionAdded"] = "onCommentReactionAdded";
@@ -26523,15 +27551,17 @@ class CommentQueryStreamController extends QueryStreamController {
26523
27551
  }
26524
27552
  appendToQueryStream(response, direction, refresh = false) {
26525
27553
  var _a, _b;
27554
+ // The public list is approved-only even when the server widens the read for a reviewer
27555
+ const commentIds = response.comments.filter(isCommentApproved).map(getResolver('comment'));
26526
27556
  if (refresh) {
26527
27557
  pushToCache(this.cacheKey, {
26528
- data: response.comments.map(getResolver('comment')),
27558
+ data: commentIds,
26529
27559
  });
26530
27560
  }
26531
27561
  else {
26532
27562
  const collection = (_a = pullFromCache(this.cacheKey)) === null || _a === void 0 ? void 0 : _a.data;
26533
27563
  const comments = (_b = collection === null || collection === void 0 ? void 0 : collection.data) !== null && _b !== void 0 ? _b : [];
26534
- pushToCache(this.cacheKey, Object.assign(Object.assign({}, collection), { data: [...new Set([...comments, ...response.comments.map(getResolver('comment'))])] }));
27564
+ pushToCache(this.cacheKey, Object.assign(Object.assign({}, collection), { data: [...new Set([...comments, ...commentIds])] }));
26535
27565
  }
26536
27566
  }
26537
27567
  reactor(action) {
@@ -26550,8 +27580,19 @@ class CommentQueryStreamController extends QueryStreamController {
26550
27580
  return;
26551
27581
  }
26552
27582
  if (action === EnumCommentActions.OnCommentCreated) {
27583
+ // A pending comment reaches clients over realtime before anyone may see it: persist
27584
+ // only, no insert and no emit. `comment.approved` is the reveal signal.
27585
+ if (!isCommentApproved(comment))
27586
+ return;
26553
27587
  collection.data = [...new Set([comment.commentId, ...collection.data])];
26554
27588
  }
27589
+ if (action === EnumCommentActions.OnCommentApproved) {
27590
+ collection.data = [...new Set([comment.commentId, ...collection.data])];
27591
+ }
27592
+ // An effective edit in a gated community returns the comment to review: it leaves the list
27593
+ if (action === EnumCommentActions.OnCommentUpdated && isCommentPending(comment)) {
27594
+ collection.data = collection.data.filter(commentId => commentId !== comment.commentId);
27595
+ }
26555
27596
  pushToCache(this.cacheKey, collection);
26556
27597
  this.notifyChange({ origin: "event" /* Amity.LiveDataOrigin.EVENT */, loading: false });
26557
27598
  };
@@ -26561,24 +27602,22 @@ class CommentQueryStreamController extends QueryStreamController {
26561
27602
  }
26562
27603
  }
26563
27604
 
26564
- /**
26565
- * ```js
26566
- * import { onCommentCreated } from '@amityco/ts-sdk-react-native'
26567
- * const dispose = onCommentCreated(comment => {
26568
- * // ...
26569
- * })
26570
- * ```
26571
- *
26572
- * Fired when a {@link Amity.InternalComment} has been created
26573
- *
26574
- * @param callback The function to call when the event was fired
26575
- * @returns an {@link Amity.Unsubscriber} function to stop listening
26576
- *
26577
- * @category Comment Events
26578
- */
26579
- const onCommentCreatedLocal = (callback) => {
26580
- return createLocalCommentEventSubscriber('local.comment.created', callback);
26581
- };
27605
+ class CommentPaginationController extends PaginationController {
27606
+ async getRequest(queryParams, token) {
27607
+ var _a;
27608
+ const { pageSize, limit, includeDeleted } = queryParams, params = __rest(queryParams, ["pageSize", "limit", "includeDeleted"]);
27609
+ const effectiveLimit = (_a = pageSize !== null && pageSize !== void 0 ? pageSize : limit) !== null && _a !== void 0 ? _a : COLLECTION_DEFAULT_PAGINATION_LIMIT;
27610
+ const baseOptions = {
27611
+ type: params.sortBy || queryParams.limit || queryParams.pageSize ? 'pagination' : undefined,
27612
+ };
27613
+ const options = token
27614
+ ? Object.assign(Object.assign({}, baseOptions), { token }) : Object.assign(Object.assign({}, baseOptions), { pageSize: effectiveLimit, limit: effectiveLimit });
27615
+ const { data: queryResponse } = await this.http.get(`/api/v3/comments`, {
27616
+ params: Object.assign(Object.assign({}, params), { isDeleted: inferIsDeleted(includeDeleted), options }),
27617
+ });
27618
+ return queryResponse;
27619
+ }
27620
+ }
26582
27621
 
26583
27622
  class CommentLiveCollectionController extends LiveCollectionController {
26584
27623
  constructor(query, callback) {
@@ -26614,6 +27653,7 @@ class CommentLiveCollectionController extends LiveCollectionController {
26614
27653
  { fn: onCommentCreated, action: EnumCommentActions.OnCommentCreated },
26615
27654
  { fn: onCommentUpdated, action: EnumCommentActions.OnCommentUpdated },
26616
27655
  { fn: onCommentDeleted, action: EnumCommentActions.OnCommentDeleted },
27656
+ { fn: onCommentApproved, action: EnumCommentActions.OnCommentApproved },
26617
27657
  { fn: onCommentFlagged, action: EnumCommentActions.OnCommentFlagged },
26618
27658
  { fn: onCommentUnflagged, action: EnumCommentActions.OnCommentUnflagged },
26619
27659
  { fn: onCommentReactionAdded, action: EnumCommentActions.OnCommentReactionAdded },
@@ -26647,7 +27687,8 @@ class CommentLiveCollectionController extends LiveCollectionController {
26647
27687
  });
26648
27688
  }
26649
27689
  applyFilter(data) {
26650
- let comments = data;
27690
+ // Approved-only, for every caller including the author of a pending comment
27691
+ let comments = data.filter(isCommentApproved);
26651
27692
  if (!this.query.includeDeleted) {
26652
27693
  comments = filterByPropEquality(comments, 'isDeleted', false);
26653
27694
  }
@@ -26735,7 +27776,202 @@ const getComments = (params, callback, config) => {
26735
27776
  };
26736
27777
  /* end_public_function */
26737
27778
 
26738
- var index$e = /*#__PURE__*/Object.freeze({
27779
+ class CommentReviewQueuePaginationController extends PaginationController {
27780
+ async getRequest(queryParams, token) {
27781
+ const { communityId, limit } = queryParams, params = __rest(queryParams, ["communityId", "limit"]);
27782
+ // The route defaults to 10, not the SDK-wide 20, and rejects anything above 100
27783
+ const effectiveLimit = Math.min(limit !== null && limit !== void 0 ? limit : COMMENT_REVIEW_QUEUE_DEFAULT_PAGINATION_SIZE, COMMENT_REVIEW_QUEUE_MAX_PAGINATION_SIZE);
27784
+ // Token paging only. The route also accepts options[skip], but offset paging over a queue that
27785
+ // drains from the front skips items, so the SDK never sends it.
27786
+ const options = token ? { token } : { limit: effectiveLimit };
27787
+ const { data: queryResponse } = await this.http.get(API_ENDPOINTS.commentReviewQueue(communityId), {
27788
+ params: Object.assign(Object.assign({}, params), { options }),
27789
+ });
27790
+ return queryResponse;
27791
+ }
27792
+ }
27793
+
27794
+ class CommentReviewQueueQueryStreamController extends QueryStreamController {
27795
+ constructor(query, cacheKey, notifyChange) {
27796
+ super(query, cacheKey);
27797
+ this.notifyChange = notifyChange;
27798
+ }
27799
+ async saveToMainDB(response) {
27800
+ const processedPayload = prepareCommentPayload(response);
27801
+ const client = getActiveClient();
27802
+ const cachedAt = client.cache && Date.now();
27803
+ if (client.cache) {
27804
+ ingestInCache(processedPayload, { cachedAt });
27805
+ }
27806
+ }
27807
+ appendToQueryStream(response, direction, refresh = false) {
27808
+ var _a, _b;
27809
+ // Server order is queue-entry order, oldest first. Keep it: later pages append, nothing
27810
+ // re-sorts by createdAt (the two diverge on re-review).
27811
+ const commentIds = response.comments.map(getResolver('comment'));
27812
+ if (refresh) {
27813
+ pushToCache(this.cacheKey, {
27814
+ data: commentIds,
27815
+ });
27816
+ }
27817
+ else {
27818
+ const collection = (_a = pullFromCache(this.cacheKey)) === null || _a === void 0 ? void 0 : _a.data;
27819
+ const comments = (_b = collection === null || collection === void 0 ? void 0 : collection.data) !== null && _b !== void 0 ? _b : [];
27820
+ pushToCache(this.cacheKey, Object.assign(Object.assign({}, collection), { data: [...new Set([...comments, ...commentIds])] }));
27821
+ }
27822
+ }
27823
+ matchesQuery(comment) {
27824
+ return (comment.targetType === 'community' &&
27825
+ comment.targetId === this.query.communityId &&
27826
+ (!this.query.postId || comment.referenceId === this.query.postId));
27827
+ }
27828
+ reactor(action) {
27829
+ return (comment) => {
27830
+ var _a;
27831
+ const collection = (_a = pullFromCache(this.cacheKey)) === null || _a === void 0 ? void 0 : _a.data;
27832
+ if (!collection || !this.matchesQuery(comment))
27833
+ return;
27834
+ const isQueued = collection.data.includes(comment.commentId);
27835
+ // A date-filtered queue is the server's to shape; live events only remove from it
27836
+ const acceptsLiveInserts = !this.query.startDate && !this.query.endDate;
27837
+ switch (action) {
27838
+ case EnumCommentActions.OnCommentCreated:
27839
+ case EnumCommentActions.OnCommentUpdated:
27840
+ if (isCommentPending(comment) && !comment.isDeleted) {
27841
+ if (isQueued || !acceptsLiveInserts)
27842
+ return;
27843
+ // Enters (or, after an edit, re-enters) the queue at the end
27844
+ collection.data = [...collection.data, comment.commentId];
27845
+ }
27846
+ else {
27847
+ if (!isQueued)
27848
+ return;
27849
+ collection.data = collection.data.filter(commentId => commentId !== comment.commentId);
27850
+ }
27851
+ break;
27852
+ case EnumCommentActions.OnCommentApproved:
27853
+ case EnumCommentActions.OnCommentDeleted:
27854
+ if (!isQueued)
27855
+ return;
27856
+ collection.data = collection.data.filter(commentId => commentId !== comment.commentId);
27857
+ break;
27858
+ default:
27859
+ return;
27860
+ }
27861
+ pushToCache(this.cacheKey, collection);
27862
+ this.notifyChange({ origin: "event" /* Amity.LiveDataOrigin.EVENT */, loading: false });
27863
+ };
27864
+ }
27865
+ subscribeRTE(createSubscriber) {
27866
+ return createSubscriber.map(subscriber => subscriber.fn(this.reactor(subscriber.action)));
27867
+ }
27868
+ }
27869
+
27870
+ class CommentReviewQueueLiveCollectionController extends LiveCollectionController {
27871
+ constructor(query, callback) {
27872
+ const queryStreamId = hash(query);
27873
+ const cacheKey = ['commentReviewQueue', 'collection', queryStreamId];
27874
+ const paginationController = new CommentReviewQueuePaginationController(query);
27875
+ super(paginationController, queryStreamId, cacheKey, callback);
27876
+ this.query = query;
27877
+ this.queryStreamController = new CommentReviewQueueQueryStreamController(this.query, this.cacheKey, this.notifyChange.bind(this));
27878
+ this.callback = callback.bind(this);
27879
+ this.loadPage({ initial: true });
27880
+ }
27881
+ setup() {
27882
+ var _a;
27883
+ const collection = (_a = pullFromCache(this.cacheKey)) === null || _a === void 0 ? void 0 : _a.data;
27884
+ if (!collection) {
27885
+ pushToCache(this.cacheKey, {
27886
+ data: [],
27887
+ params: {},
27888
+ });
27889
+ }
27890
+ }
27891
+ async persistModel(queryPayload) {
27892
+ await this.queryStreamController.saveToMainDB(queryPayload);
27893
+ }
27894
+ persistQueryStream({ response, direction, refresh, }) {
27895
+ this.queryStreamController.appendToQueryStream(response, direction, refresh);
27896
+ }
27897
+ startSubscription() {
27898
+ // No `comment.declined` exists: a decline arrives as `comment.deleted`
27899
+ return this.queryStreamController.subscribeRTE([
27900
+ { fn: onCommentCreated, action: EnumCommentActions.OnCommentCreated },
27901
+ { fn: onCommentUpdated, action: EnumCommentActions.OnCommentUpdated },
27902
+ { fn: onCommentApproved, action: EnumCommentActions.OnCommentApproved },
27903
+ { fn: onCommentDeleted, action: EnumCommentActions.OnCommentDeleted },
27904
+ { fn: onCommentDeleteLocal, action: EnumCommentActions.OnCommentDeleted },
27905
+ ]);
27906
+ }
27907
+ notifyChange({ origin, loading, error }) {
27908
+ var _a;
27909
+ // A caller who lost REVIEW_COMMUNITY_COMMENT must not keep reading stale pending content
27910
+ if ((error === null || error === void 0 ? void 0 : error.code) === 400301 /* Amity.ServerError.PERMISSION_DENIED */) {
27911
+ pushToCache(this.cacheKey, { data: [], params: {} });
27912
+ }
27913
+ const collection = (_a = pullFromCache(this.cacheKey)) === null || _a === void 0 ? void 0 : _a.data;
27914
+ if (!collection)
27915
+ return;
27916
+ const data = collection.data
27917
+ .map(id => pullFromCache(['comment', 'get', id]))
27918
+ .filter(isNonNullable)
27919
+ .map(({ data }) => data)
27920
+ .filter(comment => isCommentPending(comment) && !comment.isDeleted)
27921
+ .map(LinkedObject.comment);
27922
+ if (!this.shouldNotify(data) && origin === 'event')
27923
+ return;
27924
+ this.callback({
27925
+ onNextPage: () => this.loadPage({ direction: "next" /* Amity.LiveCollectionPageDirection.NEXT */ }),
27926
+ data,
27927
+ hasNextPage: !!this.paginationController.getNextToken(),
27928
+ loading,
27929
+ error,
27930
+ });
27931
+ }
27932
+ }
27933
+
27934
+ /* begin_public_function
27935
+ id: comment.review_queue
27936
+ */
27937
+ /**
27938
+ * ```js
27939
+ * import { CommentRepository } from '@amityco/ts-sdk'
27940
+ *
27941
+ * const unsub = CommentRepository.getCommentReviewQueue(
27942
+ * { communityId: 'communityId' },
27943
+ * ({ data: comments, hasNextPage, onNextPage }) => {
27944
+ * // every comment is awaiting review
27945
+ * },
27946
+ * )
27947
+ * ```
27948
+ *
27949
+ * Observe the {@link Amity.Comment} list awaiting moderator review in a community, oldest first
27950
+ *
27951
+ * @param params the community to read the queue of, optionally narrowed to one post and a date range
27952
+ * @param callback the function to call when new data are available
27953
+ * @returns An {@link Amity.Unsubscriber} function to run when willing to stop observing the queue
27954
+ *
27955
+ * @category Comments Live Collection
27956
+ */
27957
+ const getCommentReviewQueue = (params, callback) => {
27958
+ const { log, cache } = getActiveClient();
27959
+ if (!cache)
27960
+ console.log(ENABLE_CACHE_MESSAGE);
27961
+ const timestamp = Date.now();
27962
+ log(`getCommentReviewQueue(tmpid: ${timestamp}) > listen`);
27963
+ const commentReviewQueueLiveCollection = new CommentReviewQueueLiveCollectionController(params, callback);
27964
+ const disposers = commentReviewQueueLiveCollection.startSubscription();
27965
+ const cacheKey = commentReviewQueueLiveCollection.getCacheKey();
27966
+ disposers.push(() => dropFromCache(cacheKey));
27967
+ return () => {
27968
+ log(`getCommentReviewQueue(tmpid: ${timestamp}) > dispose`);
27969
+ disposers.forEach(fn => fn());
27970
+ };
27971
+ };
27972
+ /* end_public_function */
27973
+
27974
+ var index$f = /*#__PURE__*/Object.freeze({
26739
27975
  __proto__: null,
26740
27976
  getCommentByIds: getCommentByIds,
26741
27977
  createComment: createComment,
@@ -26743,18 +27979,22 @@ var index$e = /*#__PURE__*/Object.freeze({
26743
27979
  deleteComment: deleteComment,
26744
27980
  softDeleteComment: softDeleteComment,
26745
27981
  hardDeleteComment: hardDeleteComment,
27982
+ approveComment: approveComment,
27983
+ declineComment: declineComment,
26746
27984
  flagComment: flagComment,
26747
27985
  unflagComment: unflagComment,
26748
27986
  isCommentFlaggedByMe: isCommentFlaggedByMe,
26749
27987
  onCommentCreated: onCommentCreated,
26750
27988
  onCommentUpdated: onCommentUpdated,
26751
27989
  onCommentDeleted: onCommentDeleted,
27990
+ onCommentApproved: onCommentApproved,
26752
27991
  onCommentFlagged: onCommentFlagged,
26753
27992
  onCommentUnflagged: onCommentUnflagged,
26754
27993
  onCommentReactionAdded: onCommentReactionAdded,
26755
27994
  onCommentReactionRemoved: onCommentReactionRemoved,
26756
27995
  getComment: getComment,
26757
- getComments: getComments
27996
+ getComments: getComments,
27997
+ getCommentReviewQueue: getCommentReviewQueue
26758
27998
  });
26759
27999
 
26760
28000
  /**
@@ -28004,7 +29244,7 @@ const getForYouFeed = (callback) => {
28004
29244
  };
28005
29245
  /* end_public_function */
28006
29246
 
28007
- var index$d = /*#__PURE__*/Object.freeze({
29247
+ var index$e = /*#__PURE__*/Object.freeze({
28008
29248
  __proto__: null,
28009
29249
  queryGlobalFeed: queryGlobalFeed,
28010
29250
  getCustomRankingGlobalFeed: getCustomRankingGlobalFeed,
@@ -31149,7 +32389,7 @@ const getRooms = (params, callback, config) => {
31149
32389
  };
31150
32390
  /* end_public_function */
31151
32391
 
31152
- var index$c = /*#__PURE__*/Object.freeze({
32392
+ var index$d = /*#__PURE__*/Object.freeze({
31153
32393
  __proto__: null,
31154
32394
  createRoom: createRoom,
31155
32395
  updateRoom: updateRoom,
@@ -31448,7 +32688,7 @@ const getCommunityLiveRoomPosts = (params, callback, config) => {
31448
32688
  };
31449
32689
  /* end_public_function */
31450
32690
 
31451
- var index$b = /*#__PURE__*/Object.freeze({
32691
+ var index$c = /*#__PURE__*/Object.freeze({
31452
32692
  __proto__: null,
31453
32693
  getPostByIds: getPostByIds,
31454
32694
  createPost: createPost,
@@ -32056,7 +33296,7 @@ const getStreams = (params, callback, config) => {
32056
33296
  };
32057
33297
  /* end_public_function */
32058
33298
 
32059
- var index$a = /*#__PURE__*/Object.freeze({
33299
+ var index$b = /*#__PURE__*/Object.freeze({
32060
33300
  __proto__: null,
32061
33301
  createStream: createStream,
32062
33302
  updateStream: updateStream,
@@ -32359,7 +33599,7 @@ const stopHeartbeat = (roomId) => {
32359
33599
  };
32360
33600
  /* end_public_function */
32361
33601
 
32362
- var index$9 = /*#__PURE__*/Object.freeze({
33602
+ var index$a = /*#__PURE__*/Object.freeze({
32363
33603
  __proto__: null,
32364
33604
  getRoomOnlineUsers: getRoomOnlineUsers,
32365
33605
  getRoomUserCount: getRoomUserCount,
@@ -32670,7 +33910,7 @@ const getPoll = (pollId, callback) => {
32670
33910
  };
32671
33911
  /* end_public_function */
32672
33912
 
32673
- var index$8 = /*#__PURE__*/Object.freeze({
33913
+ var index$9 = /*#__PURE__*/Object.freeze({
32674
33914
  __proto__: null,
32675
33915
  createPoll: createPoll,
32676
33916
  closePoll: closePoll,
@@ -32974,7 +34214,7 @@ const getPlayer = async (parameters) => {
32974
34214
  return video;
32975
34215
  };
32976
34216
 
32977
- var index$7 = /*#__PURE__*/Object.freeze({
34217
+ var index$8 = /*#__PURE__*/Object.freeze({
32978
34218
  __proto__: null,
32979
34219
  getPlayer: getPlayer
32980
34220
  });
@@ -34146,7 +35386,7 @@ const getGlobalStoryTargets = (params, callback, config) => {
34146
35386
  };
34147
35387
  };
34148
35388
 
34149
- var index$6 = /*#__PURE__*/Object.freeze({
35389
+ var index$7 = /*#__PURE__*/Object.freeze({
34150
35390
  __proto__: null,
34151
35391
  createImageStory: createImageStory,
34152
35392
  createVideoStory: createVideoStory,
@@ -34183,7 +35423,7 @@ const getNetworkAds = async () => {
34183
35423
  };
34184
35424
  };
34185
35425
 
34186
- var index$5 = /*#__PURE__*/Object.freeze({
35426
+ var index$6 = /*#__PURE__*/Object.freeze({
34187
35427
  __proto__: null,
34188
35428
  getNetworkAds: getNetworkAds
34189
35429
  });
@@ -34574,7 +35814,7 @@ const markTraySeen = async (lastSeenAt) => {
34574
35814
  };
34575
35815
  /* end_public_function */
34576
35816
 
34577
- var index$4 = /*#__PURE__*/Object.freeze({
35817
+ var index$5 = /*#__PURE__*/Object.freeze({
34578
35818
  __proto__: null,
34579
35819
  getNotificationTraySeen: getNotificationTraySeen,
34580
35820
  getNotificationTrayItems: getNotificationTrayItems,
@@ -34876,7 +36116,7 @@ const getInvitations = (params, callback) => {
34876
36116
  };
34877
36117
  };
34878
36118
 
34879
- var index$3 = /*#__PURE__*/Object.freeze({
36119
+ var index$4 = /*#__PURE__*/Object.freeze({
34880
36120
  __proto__: null,
34881
36121
  cancelInvitation: cancelInvitation,
34882
36122
  onLocalInvitationCreated: onLocalInvitationCreated,
@@ -35025,7 +36265,7 @@ const getReactions = (postId, callback) => {
35025
36265
  };
35026
36266
  };
35027
36267
 
35028
- var index$2 = /*#__PURE__*/Object.freeze({
36268
+ var index$3 = /*#__PURE__*/Object.freeze({
35029
36269
  __proto__: null,
35030
36270
  createReaction: createReaction,
35031
36271
  onLiveReactionCreated: onLiveReactionCreated,
@@ -35508,7 +36748,7 @@ const getMyEvents = (params, callback, config) => {
35508
36748
  };
35509
36749
  };
35510
36750
 
35511
- var index$1 = /*#__PURE__*/Object.freeze({
36751
+ var index$2 = /*#__PURE__*/Object.freeze({
35512
36752
  __proto__: null,
35513
36753
  createEvent: createEvent,
35514
36754
  updateEvent: updateEvent,
@@ -35735,12 +36975,36 @@ const searchProducts = (params, callback, config) => {
35735
36975
  };
35736
36976
  /* end_public_function */
35737
36977
 
35738
- var index = /*#__PURE__*/Object.freeze({
36978
+ var index$1 = /*#__PURE__*/Object.freeze({
35739
36979
  __proto__: null,
35740
36980
  getProduct: getProduct,
35741
36981
  searchProducts: searchProducts
35742
36982
  });
35743
36983
 
36984
+ const getPool = async (topicId, limit) => {
36985
+ const client = getActiveClient();
36986
+ if (!topicId)
36987
+ throw new ASCError('topicId must not be empty', 800110 /* Amity.ClientError.INVALID_PARAMETERS */, "error" /* Amity.ErrorLevel.ERROR */);
36988
+ if (limit !== undefined && (limit < 1 || limit > 100))
36989
+ throw new ASCError('limit must be between 1 and 100', 800110 /* Amity.ClientError.INVALID_PARAMETERS */, "error" /* Amity.ErrorLevel.ERROR */);
36990
+ const { data } = await client.http.get('/api/v5/curated-content/pool', { params: { topicId, limit } });
36991
+ const { topic } = data, payload = __rest(data, ["topic"]);
36992
+ const processedPayload = preparePostPayload(payload);
36993
+ const cachedAt = client.cache && Date.now();
36994
+ if (client.cache) {
36995
+ ingestInCache(processedPayload, { cachedAt });
36996
+ }
36997
+ return {
36998
+ topic: LinkedObject.curatedContentTopic(topic),
36999
+ posts: processedPayload.posts.map(LinkedObject.post),
37000
+ };
37001
+ };
37002
+
37003
+ var index = /*#__PURE__*/Object.freeze({
37004
+ __proto__: null,
37005
+ getPool: getPool
37006
+ });
37007
+
35744
37008
  /**
35745
37009
  * Helper class for managing product tags per media attachment.
35746
37010
  *
@@ -35788,4 +37052,4 @@ class AmityAttachmentProductTags {
35788
37052
  }
35789
37053
  }
35790
37054
 
35791
- export { API_REGIONS, index$5 as AdRepository, AmityAttachmentProductTags, AmityAutoSubscription, AmityChannelNotificationModeEnum, AmityCommunityType, AmityEventOrderOption, AmityEventOriginType, AmityEventResponseStatus, AmityEventSortOption, AmityEventStatus, AmityEventType, AmityLiveViewerCountMode, AmitySharableContentType, AnalyticsSourceTypeEnum, index$f as CategoryRepository, index$j as ChannelRepository, index$s as Client, index$e as CommentRepository, CommunityNotificationEventNameEnum, CommunityPostSettingMaps, CommunityPostSettings, index$g as CommunityRepository, ContentFeedType, ContentFlagReasonEnum, DefaultCommunityPostSetting, index$1 as EventRepository, FeedDataTypeEnum, index$d as FeedRepository, FeedSortByEnum, FeedSourceEnum, FeedTypeEnum, FileAccessTypeEnum, index$p as FileRepository, FileType, GET_WATCHER_URLS, index$3 as InvitationRepository, InvitationSortByEnum, InvitationStatusEnum, InvitationTargetTypeEnum, InvitationTypeEnum, JoinRequestStatusEnum, JoinResultStatusEnum, index$2 as LiveReactionRepository, index$7 as LiveStreamPlayer, MembershipAcceptanceTypeEnum, MessageContentType, index$n as MessageRepository, NotificationRolesFilterTypeEnum, NotificationSettingsLevelEnum, index$8 as PollRepository, PostContentType, index$b as PostRepository, PostStructureType, index as ProductRepository, index$o as ReactionRepository, index$9 as RoomPresenceRepository, index$c as RoomRepository, SearchUsersByEnum, index$6 as StoryRepository, index$a as StreamRepository, index$m as SubChannelRepository, SubscriptionLevels, UserNotificationModuleNameEnum, UserReportTypeEnum, index$q 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$4 as notificationTray, onChannelMarkerFetched, onFeedMarkerFetched, onFeedMarkerUpdated, onMessageMarked, onMessageMarkerFetched, onSubChannelMarkerFetched, onSubChannelMarkerUpdated, onUserMarkerFetched, onUserMarkerFetchedLegacy, pullFromCache, pushToCache, queryCache, queryOptions, queryRoles, restoreCache, runQuery, sortByChannelSegment, sortByDisplayName, sortByFirstCreated, sortByFirstUpdated, sortByLastActivity, sortByLastCreated, sortByLastUpdated, sortByLocalSortingDate, sortByName, sortBySegmentNumber, subscribeTopic, toPage, toPageRaw, toToken, upsertInCache, validateUntilAt, wipeCache };
37055
+ export { API_REGIONS, index$6 as AdRepository, AmityAttachmentProductTags, AmityAutoSubscription, AmityChannelNotificationModeEnum, AmityCommunityType, AmityEventOrderOption, AmityEventOriginType, AmityEventResponseStatus, AmityEventSortOption, AmityEventStatus, AmityEventType, AmityLiveViewerCountMode, 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, restoreCache, runQuery, sortByChannelSegment, sortByDisplayName, sortByFirstCreated, sortByFirstUpdated, sortByLastActivity, sortByLastCreated, sortByLastUpdated, sortByLocalSortingDate, sortByName, sortBySegmentNumber, subscribeTopic, toPage, toPageRaw, toToken, upsertInCache, validateUntilAt, wipeCache };