@amityco/ts-sdk 7.29.1-cc0fba8.0 → 7.30.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 (99) hide show
  1. package/dist/@types/core/errors.d.ts +2 -1
  2. package/dist/@types/core/errors.d.ts.map +1 -1
  3. package/dist/@types/core/events.d.ts +4 -0
  4. package/dist/@types/core/events.d.ts.map +1 -1
  5. package/dist/@types/domains/channel.d.ts +13 -1
  6. package/dist/@types/domains/channel.d.ts.map +1 -1
  7. package/dist/@types/domains/event.d.ts +1 -1
  8. package/dist/@types/domains/event.d.ts.map +1 -1
  9. package/dist/@types/domains/group.d.ts +2 -2
  10. package/dist/@types/domains/group.d.ts.map +1 -1
  11. package/dist/@types/domains/user.d.ts +70 -0
  12. package/dist/@types/domains/user.d.ts.map +1 -1
  13. package/dist/channelRepository/events/onUserDeleted.d.ts.map +1 -1
  14. package/dist/channelRepository/observers/getChannels/ChannelLiveCollectionController.d.ts.map +1 -1
  15. package/dist/channelRepository/observers/searchChannels/SearchChannelLiveCollectionController.d.ts.map +1 -1
  16. package/dist/channelRepository/observers/tests/integrations/getChannelsSortByLastMessage.integration.test.d.ts +2 -0
  17. package/dist/channelRepository/observers/tests/integrations/getChannelsSortByLastMessage.integration.test.d.ts.map +1 -0
  18. package/dist/channelRepository/utils/channelActivityOnLoginHandler.d.ts +16 -0
  19. package/dist/channelRepository/utils/channelActivityOnLoginHandler.d.ts.map +1 -0
  20. package/dist/channelRepository/utils/tests/integrations/channelActivity.integration.test.d.ts +2 -0
  21. package/dist/channelRepository/utils/tests/integrations/channelActivity.integration.test.d.ts.map +1 -0
  22. package/dist/channelRepository/utils/tests/updateChannelActivityTimestamps.test.d.ts +2 -0
  23. package/dist/channelRepository/utils/tests/updateChannelActivityTimestamps.test.d.ts.map +1 -0
  24. package/dist/channelRepository/utils/updateChannelActivityTimestamps.d.ts +42 -0
  25. package/dist/channelRepository/utils/updateChannelActivityTimestamps.d.ts.map +1 -0
  26. package/dist/client/api/resumeSession.d.ts.map +1 -1
  27. package/dist/client/api/setupLoginSubscriptions.d.ts.map +1 -1
  28. package/dist/communityRepository/communityMembership/events/onUserDeleted.d.ts.map +1 -1
  29. package/dist/communityRepository/utils/payload.d.ts.map +1 -1
  30. package/dist/core/events.d.ts +3 -3
  31. package/dist/core/events.d.ts.map +1 -1
  32. package/dist/core/microtasks.d.ts.map +1 -1
  33. package/dist/core/query/sorting.d.ts +10 -0
  34. package/dist/core/query/sorting.d.ts.map +1 -1
  35. package/dist/core/query/tests/sorting.test.d.ts +2 -0
  36. package/dist/core/query/tests/sorting.test.d.ts.map +1 -0
  37. package/dist/core/tests/microtasks.test.d.ts +2 -0
  38. package/dist/core/tests/microtasks.test.d.ts.map +1 -0
  39. package/dist/group/utils/withUser.d.ts +3 -3
  40. package/dist/group/utils/withUser.d.ts.map +1 -1
  41. package/dist/index.cjs.js +826 -167
  42. package/dist/index.esm.js +827 -169
  43. package/dist/index.umd.js +2 -2
  44. package/dist/messagePreview/utils/getChannelMessagePreviewWithUser.d.ts +1 -0
  45. package/dist/messagePreview/utils/getChannelMessagePreviewWithUser.d.ts.map +1 -1
  46. package/dist/messagePreview/utils/getSubChannelMessagePreviewWithUser.d.ts +1 -1
  47. package/dist/messagePreview/utils/getSubChannelMessagePreviewWithUser.d.ts.map +1 -1
  48. package/dist/messageRepository/api/createMessage.d.ts.map +1 -1
  49. package/dist/postRepository/api/tests/integration/setup.d.ts +3 -3
  50. package/dist/postRepository/utils/payload.d.ts.map +1 -1
  51. package/dist/report/api/isReportedByMe.d.ts.map +1 -1
  52. package/dist/userRepository/api/flagUser.d.ts.map +1 -1
  53. package/dist/userRepository/api/isUserFlaggedByMe.d.ts +11 -3
  54. package/dist/userRepository/api/isUserFlaggedByMe.d.ts.map +1 -1
  55. package/dist/userRepository/api/queryBlockedUsers.d.ts.map +1 -1
  56. package/dist/userRepository/api/unflagUser.d.ts +27 -4
  57. package/dist/userRepository/api/unflagUser.d.ts.map +1 -1
  58. package/dist/userRepository/api/updateUser.d.ts.map +1 -1
  59. package/dist/userRepository/internalApi/getFlagsByMe.d.ts +22 -0
  60. package/dist/userRepository/internalApi/getFlagsByMe.d.ts.map +1 -0
  61. package/dist/userRepository/internalApi/tests/getFlagsByMe.test.d.ts +2 -0
  62. package/dist/userRepository/internalApi/tests/getFlagsByMe.test.d.ts.map +1 -0
  63. package/dist/userRepository/observers/getBlockedUsers/BlockedUserLiveCollectionController.d.ts +2 -2
  64. package/dist/userRepository/observers/getBlockedUsers/BlockedUserLiveCollectionController.d.ts.map +1 -1
  65. package/dist/userRepository/observers/getBlockingUsers/LiveCollectionController.d.ts +1 -1
  66. package/dist/userRepository/observers/getBlockingUsers/LiveCollectionController.d.ts.map +1 -1
  67. package/dist/userRepository/observers/getUser.d.ts.map +1 -1
  68. package/dist/userRepository/observers/getUsers/UserLiveCollectionController.d.ts +2 -2
  69. package/dist/userRepository/observers/getUsers/UserLiveCollectionController.d.ts.map +1 -1
  70. package/dist/userRepository/observers/searchUserByDisplayName/SearchUserLiveCollectionController.d.ts +2 -2
  71. package/dist/userRepository/observers/searchUserByDisplayName/SearchUserLiveCollectionController.d.ts.map +1 -1
  72. package/dist/userRepository/observers/searchUserByDisplayName.d.ts +1 -1
  73. package/dist/userRepository/observers/searchUserByDisplayName.d.ts.map +1 -1
  74. package/dist/userRepository/observers/searchUsers.d.ts +1 -1
  75. package/dist/userRepository/observers/searchUsers.d.ts.map +1 -1
  76. package/dist/userRepository/utils/submitUserReport.d.ts.map +1 -1
  77. package/dist/userRepository/utils/userFlagByMe.d.ts +68 -0
  78. package/dist/userRepository/utils/userFlagByMe.d.ts.map +1 -0
  79. package/dist/userRepository/utils/userReport.d.ts +16 -1
  80. package/dist/userRepository/utils/userReport.d.ts.map +1 -1
  81. package/dist/utils/bloomFilter.d.ts +14 -0
  82. package/dist/utils/bloomFilter.d.ts.map +1 -0
  83. package/dist/utils/linkedObject/tests/userLinkedObject.test.d.ts +2 -0
  84. package/dist/utils/linkedObject/tests/userLinkedObject.test.d.ts.map +1 -0
  85. package/dist/utils/linkedObject/userLinkedObject.d.ts.map +1 -1
  86. package/dist/utils/tests/bloomFilter.test.d.ts +2 -0
  87. package/dist/utils/tests/bloomFilter.test.d.ts.map +1 -0
  88. package/dist/utils/tests/client.d.ts +1 -1
  89. package/dist/utils/tests/client.d.ts.map +1 -1
  90. package/dist/utils/tests/dummy/channel.d.ts +31 -10
  91. package/dist/utils/tests/dummy/channel.d.ts.map +1 -1
  92. package/dist/utils/tests/dummy/comment.d.ts +1 -1
  93. package/dist/utils/tests/dummy/community.d.ts +2 -2
  94. package/dist/utils/tests/dummy/community.d.ts.map +1 -1
  95. package/dist/utils/tests/dummy/message.d.ts +1 -1
  96. package/dist/utils/tests/dummy/token.d.ts +1 -1
  97. package/dist/utils/tests/dummy/user.d.ts +10 -12
  98. package/dist/utils/tests/dummy/user.d.ts.map +1 -1
  99. package/package.json +1 -1
package/dist/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import { encode, decode, btoa, atob } from 'js-base64';
1
+ import { encode, decode, toUint8Array, btoa, atob } from 'js-base64';
2
2
  import mitt from 'mitt';
3
3
  import debug from 'debug';
4
4
  import axios from 'axios';
@@ -54,6 +54,21 @@ var UserProfileFieldEnum;
54
54
  UserProfileFieldEnum["DESCRIPTION"] = "description";
55
55
  UserProfileFieldEnum["AVATAR"] = "avatarFileId";
56
56
  })(UserProfileFieldEnum || (UserProfileFieldEnum = {}));
57
+ /**
58
+ * Selects which of the caller's reports `UserRepository.unflagUser` withdraws, sent as the
59
+ * `reportType` query parameter. The four typed members are the backend `reportType` wire
60
+ * values and match `UserReportTypeEnum`. `UNTYPED` selects the typeless (legacy) report,
61
+ * which `user.getUserFlagsByMe()` returns as `reportType: null`; it is a selector only, never
62
+ * stored, and the flag endpoint refuses it.
63
+ */
64
+ var UserReportTypeFilterEnum;
65
+ (function (UserReportTypeFilterEnum) {
66
+ UserReportTypeFilterEnum["DISPLAY_NAME"] = "displayName";
67
+ UserReportTypeFilterEnum["DESCRIPTION"] = "description";
68
+ UserReportTypeFilterEnum["AVATAR"] = "avatarFileId";
69
+ UserReportTypeFilterEnum["BEHAVIOR"] = "behavior";
70
+ UserReportTypeFilterEnum["UNTYPED"] = "untyped";
71
+ })(UserReportTypeFilterEnum || (UserReportTypeFilterEnum = {}));
57
72
 
58
73
  const FileType = Object.freeze({
59
74
  FILE: 'file',
@@ -328,8 +343,8 @@ var AmityLiveViewerCountMode;
328
343
 
329
344
  function getVersion() {
330
345
  try {
331
- // the string ''v7.29.0-esm'' should be replaced by actual value by @rollup/plugin-replace
332
- return 'v7.29.0-esm';
346
+ // the string ''v7.30.0-esm'' should be replaced by actual value by @rollup/plugin-replace
347
+ return 'v7.30.0-esm';
333
348
  }
334
349
  catch (error) {
335
350
  return '__dev__';
@@ -1615,6 +1630,24 @@ const sortByLastUpdated = ({ updatedAt: a = 0 }, { updatedAt: b = 0 }) => new Da
1615
1630
  * Sorting a collection by the items with most recent activity
1616
1631
  */
1617
1632
  const sortByLastActivity = ({ lastActivity: a }, { lastActivity: b }) => new Date(b).valueOf() - new Date(a).valueOf();
1633
+ /**
1634
+ * Sorting a channel collection by the time of its newest message (newest first).
1635
+ * Channels whose `lastMessageAt` is `null` sort last, ordered by `createdAt`
1636
+ * (newest first) among themselves, matching the server's `sortBy=lastMessage`
1637
+ * order so the local sort agrees with the page order.
1638
+ */
1639
+ const sortByLastMessage = (a, b) => {
1640
+ var _a, _b;
1641
+ const aAt = (_a = a.lastMessageAt) !== null && _a !== void 0 ? _a : null;
1642
+ const bAt = (_b = b.lastMessageAt) !== null && _b !== void 0 ? _b : null;
1643
+ if (aAt !== null && bAt !== null)
1644
+ return new Date(bAt).valueOf() - new Date(aAt).valueOf();
1645
+ if (aAt !== null)
1646
+ return -1;
1647
+ if (bAt !== null)
1648
+ return 1;
1649
+ return new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf();
1650
+ };
1618
1651
  /**
1619
1652
  * Filter items by untilAt timestamp based on sort direction
1620
1653
  * - For 'lastCreated' (newest first): untilAt acts as lower bound, excludes items older than boundary
@@ -1699,8 +1732,13 @@ const scheduleTask = (task) => {
1699
1732
  clearTimeout(timer);
1700
1733
  tasks.push(task);
1701
1734
  timer = setTimeout(() => {
1702
- tasks.forEach(fn => fn());
1735
+ // Swap the queue out before running it. A task that schedules another task
1736
+ // while the batch runs (an event handler that fires an event) must land in
1737
+ // a fresh queue and its own timer, not in the array being drained and then
1738
+ // discarded.
1739
+ const batch = tasks;
1703
1740
  tasks = [];
1741
+ batch.forEach(fn => fn());
1704
1742
  }, 0);
1705
1743
  };
1706
1744
 
@@ -1798,18 +1836,434 @@ async function modifyMqttConnection() {
1798
1836
  }
1799
1837
  }
1800
1838
 
1839
+ class SessionWatcher {
1840
+ constructor() {
1841
+ this._sessionState = "notLoggedIn" /* Amity.SessionStates.NOT_LOGGED_IN */;
1842
+ this._listener = new Map();
1843
+ }
1844
+ onSessionStateChange(callback) {
1845
+ this._listener.set(callback, callback);
1846
+ return () => {
1847
+ this._listener.delete(callback);
1848
+ };
1849
+ }
1850
+ setSessionState(state, reason) {
1851
+ if (this._sessionState === state)
1852
+ return;
1853
+ this._sessionState = state;
1854
+ this._listener.forEach(cb => cb(state, reason));
1855
+ }
1856
+ destroy() {
1857
+ this._listener.clear();
1858
+ }
1859
+ }
1860
+ let instance$b;
1861
+ var SessionWatcher$1 = {
1862
+ getInstance: () => {
1863
+ if (!instance$b) {
1864
+ instance$b = new SessionWatcher();
1865
+ }
1866
+ return instance$b;
1867
+ },
1868
+ };
1869
+
1870
+ /**
1871
+ * ```js
1872
+ * import { onSessionStateChange } from '@amityco/ts-sdk'
1873
+ * const dispose = onSessionStateChange((state: Amity.SessionStates) => {
1874
+ * // ...
1875
+ * })
1876
+ * ```
1877
+ *
1878
+ * Fired when any {@link Amity.Client} has a session state change
1879
+ *
1880
+ * @param callback The function to call when the event was fired
1881
+ * @returns an {@link Amity.Unsubscriber} function to stop listening
1882
+ *
1883
+ * @category Client Events
1884
+ */
1885
+ const onSessionStateChange = (callback) => SessionWatcher$1.getInstance().onSessionStateChange(callback);
1886
+
1887
+ /**
1888
+ * ```js
1889
+ * import { getFlagsByMe } from '~/userRepository/internalApi/getFlagsByMe'
1890
+ * const { isFlagByMe, flags } = await getFlagsByMe('userId')
1891
+ * ```
1892
+ *
1893
+ * The v4 read behind `user.getUserFlagsByMe()`, the background confirm of
1894
+ * `user.isFlaggedByMe`, and the deprecated `UserRepository.isUserFlaggedByMe`:
1895
+ * `GET /api/v4/users/:userId/isFlagByMe`, returned as received. Nothing is filtered, cached or
1896
+ * persisted here, and `hashFlag` is not consulted.
1897
+ * This is the Web counterpart of iOS `ASCUserService.getFlagsByMe` and Android
1898
+ * `UserRepository.getFlagsByMe`, kept apart from the deprecated public function so the
1899
+ * user-object read does not change if that function ever does.
1900
+ *
1901
+ * @param userId The ID of the user whose reports by the caller are read
1902
+ * @returns the caller's {@link Amity.UserFlagByMe} on that user
1903
+ *
1904
+ * @category Private
1905
+ * @async
1906
+ */
1907
+ const getFlagsByMe = async (userId) => {
1908
+ const client = getActiveClient();
1909
+ client.log('user/getFlagsByMe', userId);
1910
+ const { data } = await client.http.get(`/api/v4/users/${encodeURIComponent(userId)}/isFlagByMe`);
1911
+ return data;
1912
+ };
1913
+
1914
+ /* eslint-disable no-bitwise */
1915
+ /**
1916
+ * Client-side test of the `hashFlag` Bloom filter the backend attaches to flaggable objects.
1917
+ *
1918
+ * The filter holds the ids of every user who flagged the object: `bits` is its size, `hashes`
1919
+ * the number of hash functions per id and `hash` the bit array, base64-encoded. This is a port
1920
+ * of the test iOS (`_BloomFilter`) and Android (`EkoBloomFilter`) already run: FNV-1a 32-bit
1921
+ * over the UTF-8 bytes of the id with seeds `0` and `1576284489`, combined by double hashing
1922
+ * (`(a + i·b) mod bits`). A Bloom filter has no false negatives, so `false` is exact; `true`
1923
+ * means "probably", because another id's hashes may have set the same bits. The filter knows
1924
+ * nothing about report types.
1925
+ */
1926
+ const FNV_OFFSET_BASIS = 0x811c9dc5;
1927
+ const FNV_PRIME = 16777619;
1928
+ const SECOND_SEED = 1576284489;
1929
+ /** FNV-1a with the same mixing step and signed-byte handling as the native SDKs. */
1930
+ const fnv1a = (bytes, seed) => {
1931
+ let a = (FNV_OFFSET_BASIS ^ seed) >>> 0;
1932
+ for (let i = 0; i < bytes.length; i += 1) {
1933
+ // Bytes are `char` on iOS and `byte` on Android: both signed, so 0x80..0xff sign-extend.
1934
+ const byte = bytes[i] > 127 ? bytes[i] - 256 : bytes[i];
1935
+ a = Math.imul((a ^ byte) >>> 0, FNV_PRIME) >>> 0;
1936
+ }
1937
+ a = (a + (a << 13)) >>> 0;
1938
+ a = (a ^ (a >>> 7)) >>> 0;
1939
+ a = (a + (a << 3)) >>> 0;
1940
+ a = (a ^ (a >>> 17)) >>> 0;
1941
+ a = (a + (a << 5)) >>> 0;
1942
+ return a;
1943
+ };
1944
+ /** Bit positions for `value`, reproducing the native SDKs' signed 32-bit arithmetic. */
1945
+ const locationsFor = (value, bits, hashes) => {
1946
+ const bytes = toUint8Array(encode(value)); // UTF-8 bytes of the id
1947
+ const a = fnv1a(bytes, 0) | 0;
1948
+ const b = fnv1a(bytes, SECOND_SEED) | 0;
1949
+ const locations = [];
1950
+ let x = a % bits;
1951
+ for (let i = 0; i < hashes; i += 1) {
1952
+ locations.push(x < 0 ? x + bits : x);
1953
+ x = ((x + b) | 0) % bits;
1954
+ }
1955
+ return locations;
1956
+ };
1957
+ /** Standard or URL-safe base64, padding optional. Anything else is not a filter we can read. */
1958
+ const BASE64 = /^[A-Za-z0-9+/_-]*={0,2}$/;
1959
+ /**
1960
+ * Whether `value` is probably in the filter. `false` is exact; `true` means "maybe", which may be
1961
+ * a false positive. `null` (the server's value when nobody has flagged the object), a missing
1962
+ * filter or a missing value answer `false`.
1963
+ *
1964
+ * A filter that cannot be read answers `true` ("maybe") and never throws, so the caller asks the
1965
+ * server instead of trusting a "definitely not" it cannot back up (REQ-017): `bits` or `hashes`
1966
+ * not a positive integer, a `hash` that is not base64, or a `hash` shorter than `ceil(bits / 8)`
1967
+ * bytes.
1968
+ */
1969
+ const testBloomFilter = (hashFlag, value) => {
1970
+ if (!hashFlag || !value)
1971
+ return false;
1972
+ const { bits, hashes, hash } = hashFlag;
1973
+ if (!Number.isInteger(bits) || bits <= 0)
1974
+ return true;
1975
+ if (!Number.isInteger(hashes) || hashes <= 0)
1976
+ return true;
1977
+ if (typeof hash !== 'string' || !BASE64.test(hash))
1978
+ return true;
1979
+ let buckets;
1980
+ try {
1981
+ buckets = toUint8Array(hash);
1982
+ }
1983
+ catch (_a) {
1984
+ return true;
1985
+ }
1986
+ if (buckets.length < Math.ceil(bits / 8))
1987
+ return true;
1988
+ const locations = locationsFor(value, bits, hashes);
1989
+ for (let i = 0; i < locations.length; i += 1) {
1990
+ const index = Math.floor(locations[i] / 8);
1991
+ const bit = 1 << locations[i] % 8;
1992
+ if ((buckets[index] & bit) === 0)
1993
+ return false;
1994
+ }
1995
+ return true;
1996
+ };
1997
+
1998
+ /**
1999
+ * Flag state of the current user on a target user, behind `Amity.User.isFlaggedByMe` and
2000
+ * `Amity.User.getUserFlagsByMe()` (`user.check_flag_by_me` v1, REQ-011, REQ-012, REQ-016).
2001
+ *
2002
+ * `hashFlag` is a Bloom filter of reporter ids. "Definitely not" is exact. "Maybe" can be a
2003
+ * false positive caused by other reporters, so the SDK confirms it with the server once per
2004
+ * `hashFlag` value and records the answer in the cache under its own key, never on the user's
2005
+ * cache row (REQ-015). Observers are told through `local.user.flagConfirmed` when a recorded
2006
+ * answer changes what a user object would show.
2007
+ *
2008
+ * Not exported from the public API.
2009
+ */
2010
+ /** Confirms for the same user are at least this far apart, as Android's `EkoFlag.expired`. */
2011
+ const FLAG_CONFIRM_INTERVAL = 1000;
2012
+ const cacheKey = (userId) => ['userFlagByMe', 'get', userId];
2013
+ const lastConfirmAt = new Map();
2014
+ /**
2015
+ * Bumped by the current user's own flag and unflag. A confirm or exact read that started before
2016
+ * one of them must not record its answer, because the write may have changed it while the
2017
+ * `hashFlag` stayed the same.
2018
+ */
2019
+ const generations = new Map();
2020
+ const generationOf = (userId) => { var _a; return (_a = generations.get(userId)) !== null && _a !== void 0 ? _a : 0; };
2021
+ const bumpGeneration = (userId) => generations.set(userId, generationOf(userId) + 1);
2022
+ /** The current user's id, or `undefined` when there is no active client. Never throws. */
2023
+ const currentUserId = () => {
2024
+ try {
2025
+ return getActiveClient().userId;
2026
+ }
2027
+ catch (_a) {
2028
+ return undefined;
2029
+ }
2030
+ };
2031
+ const readRecord = (userId) => {
2032
+ var _a;
2033
+ try {
2034
+ return (_a = pullFromCache(cacheKey(userId))) === null || _a === void 0 ? void 0 : _a.data;
2035
+ }
2036
+ catch (_b) {
2037
+ return undefined;
2038
+ }
2039
+ };
2040
+ /**
2041
+ * The server's answer recorded for this user and this exact `hashFlag`, or `undefined` when there
2042
+ * is none for the current user. Never throws.
2043
+ */
2044
+ const readConfirmedFlag = (userId, hashFlag) => {
2045
+ const me = currentUserId();
2046
+ if (!me || !hashFlag || typeof hashFlag.hash !== 'string')
2047
+ return undefined;
2048
+ const record = readRecord(userId);
2049
+ if (!record || record.reporterId !== me || record.hash !== hashFlag.hash)
2050
+ return undefined;
2051
+ return record.isFlagByMe;
2052
+ };
2053
+ /**
2054
+ * The v4 request in flight per user, with the generation it started in. A confirm and an exact
2055
+ * read of the same user share one request; a request that started before the current user's own
2056
+ * flag or unflag is not joined.
2057
+ */
2058
+ const inFlight = new Map();
2059
+ const requestFlagsByMe = (userId) => {
2060
+ const generation = generationOf(userId);
2061
+ const pending = inFlight.get(userId);
2062
+ if (pending && pending.generation === generation)
2063
+ return pending.promise;
2064
+ const promise = getFlagsByMe(userId).finally(() => {
2065
+ var _a;
2066
+ if (((_a = inFlight.get(userId)) === null || _a === void 0 ? void 0 : _a.promise) === promise)
2067
+ inFlight.delete(userId);
2068
+ });
2069
+ inFlight.set(userId, { generation, promise });
2070
+ return promise;
2071
+ };
2072
+ const notifyObservers = (userId) => fireEvent('local.user.flagConfirmed', { userId });
2073
+ /**
2074
+ * Records `isFlagByMe` for `hashFlag`. A "maybe" without a record shows `true`, so observers are
2075
+ * told only when the shown value changes.
2076
+ */
2077
+ const recordFlag = (userId, hashFlag, isFlagByMe) => {
2078
+ var _a;
2079
+ const me = currentUserId();
2080
+ if (!me || typeof hashFlag.hash !== 'string')
2081
+ return;
2082
+ const shown = (_a = readConfirmedFlag(userId, hashFlag)) !== null && _a !== void 0 ? _a : true;
2083
+ const record = { reporterId: me, hash: hashFlag.hash, isFlagByMe };
2084
+ pushToCache(cacheKey(userId), record);
2085
+ if (shown !== isFlagByMe)
2086
+ notifyObservers(userId);
2087
+ };
2088
+ /**
2089
+ * Starts one background confirm of a "maybe" (REQ-016): at most one in flight per user, and
2090
+ * confirms for the same user at least {@link FLAG_CONFIRM_INTERVAL} apart. A failed confirm
2091
+ * records nothing, surfaces no error, and a later read tries again.
2092
+ */
2093
+ const confirmFlagByMe = (userId, hashFlag) => {
2094
+ if (inFlight.has(userId))
2095
+ return;
2096
+ const now = Date.now();
2097
+ const last = lastConfirmAt.get(userId);
2098
+ if (last !== undefined && now - last < FLAG_CONFIRM_INTERVAL)
2099
+ return;
2100
+ const me = currentUserId();
2101
+ const generation = generationOf(userId);
2102
+ lastConfirmAt.set(userId, now);
2103
+ requestFlagsByMe(userId)
2104
+ .then(({ isFlagByMe }) => {
2105
+ if (currentUserId() !== me || generationOf(userId) !== generation)
2106
+ return;
2107
+ recordFlag(userId, hashFlag, isFlagByMe);
2108
+ })
2109
+ .catch(() => {
2110
+ // Not an answer: isFlaggedByMe stays true until a later read confirms again.
2111
+ });
2112
+ };
2113
+ /**
2114
+ * The value of `Amity.User.isFlaggedByMe` for a user object whose confirmed answer was captured
2115
+ * (`confirmed`) when the object was linked, so a held object never changes (REQ-014).
2116
+ *
2117
+ * - No current user, `hashFlag: null` or a missing `hashFlag`, or "definitely not": `false`.
2118
+ * - "Maybe" with a confirmed answer: that answer, no request.
2119
+ * - "Maybe" without one: `true`, and one background confirm (REQ-016).
2120
+ *
2121
+ * The linked object declares this as a getter inside an object spread, which the build compiles
2122
+ * to `Object.assign`, so it runs once when the user is linked and the object keeps that value.
2123
+ */
2124
+ const isFlaggedByMeFor = (user, confirmed) => {
2125
+ const me = currentUserId();
2126
+ if (!me || !user.hashFlag || !testBloomFilter(user.hashFlag, me))
2127
+ return false;
2128
+ if (confirmed !== undefined)
2129
+ return confirmed;
2130
+ confirmFlagByMe(user.userId, user.hashFlag);
2131
+ return true;
2132
+ };
2133
+ const NOT_FLAGGED = { isFlagByMe: false, flags: [] };
2134
+ const isNotOlder = (next, current) => {
2135
+ var _a, _b;
2136
+ const nextAt = Date.parse((_a = next.updatedAt) !== null && _a !== void 0 ? _a : '');
2137
+ const currentAt = Date.parse((_b = current.updatedAt) !== null && _b !== void 0 ? _b : '');
2138
+ return Number.isNaN(nextAt) || Number.isNaN(currentAt) || nextAt >= currentAt;
2139
+ };
2140
+ /**
2141
+ * One `getUserFlagsByMe` closure per userId, remembering the newest user object linked for that
2142
+ * id. Live objects and collections compare snapshots with `isEqual`, which compares functions by
2143
+ * reference, so a fresh closure per linked object would re-dispatch unchanged snapshots
2144
+ * (REQ-015). An older snapshot linked later does not replace a newer one.
2145
+ */
2146
+ const flagsByMeEntries = new Map();
2147
+ /**
2148
+ * `Amity.User.getUserFlagsByMe()` (REQ-012): `{ isFlagByMe: false, flags: [] }` with no request
2149
+ * when the newest linked user says "not flagged", including a confirmed `false`. Otherwise the
2150
+ * v4 read, returned as is; a confirm already in flight for the user is joined instead of sending
2151
+ * a second request. Its `isFlagByMe` is recorded as the confirmed answer for the
2152
+ * `hashFlag` it was read for; `flags[]` is never stored (REQ-009). A missing `hashFlag` is
2153
+ * unknown, so the server is asked.
2154
+ */
2155
+ const readFlagsByMe = async (userId) => {
2156
+ var _a, _b;
2157
+ const me = currentUserId();
2158
+ const { hashFlag } = (_b = (_a = flagsByMeEntries.get(userId)) === null || _a === void 0 ? void 0 : _a.latest) !== null && _b !== void 0 ? _b : {};
2159
+ if (me && hashFlag !== undefined) {
2160
+ if (!hashFlag || !testBloomFilter(hashFlag, me))
2161
+ return Object.assign(Object.assign({}, NOT_FLAGGED), { flags: [] });
2162
+ if (readConfirmedFlag(userId, hashFlag) === false)
2163
+ return Object.assign(Object.assign({}, NOT_FLAGGED), { flags: [] });
2164
+ }
2165
+ const generation = generationOf(userId);
2166
+ const result = await requestFlagsByMe(userId);
2167
+ if (hashFlag && currentUserId() === me && generationOf(userId) === generation) {
2168
+ recordFlag(userId, hashFlag, result.isFlagByMe);
2169
+ }
2170
+ return result;
2171
+ };
2172
+ const getUserFlagsByMeFor = (user) => {
2173
+ let entry = flagsByMeEntries.get(user.userId);
2174
+ if (!entry) {
2175
+ entry = { latest: user, getUserFlagsByMe: () => readFlagsByMe(user.userId) };
2176
+ flagsByMeEntries.set(user.userId, entry);
2177
+ }
2178
+ else if (isNotOlder(user, entry.latest)) {
2179
+ entry.latest = user;
2180
+ }
2181
+ return entry.getUserFlagsByMe;
2182
+ };
2183
+ /**
2184
+ * The current user's own flag succeeded (REQ-016). Their report may leave `hashFlag` unchanged
2185
+ * when other reporters had already set all of their bits, so the answer is known: record `true`
2186
+ * for the `hashFlag` the response carries, or drop the answer when it carries none.
2187
+ */
2188
+ const onFlaggedByMe = (user, userId) => {
2189
+ bumpGeneration(userId);
2190
+ if (user === null || user === void 0 ? void 0 : user.hashFlag) {
2191
+ recordFlag(userId, user.hashFlag, true);
2192
+ return;
2193
+ }
2194
+ const dropped = readRecord(userId);
2195
+ dropFromCache(cacheKey(userId), true);
2196
+ if ((dropped === null || dropped === void 0 ? void 0 : dropped.isFlagByMe) === false)
2197
+ notifyObservers(userId);
2198
+ };
2199
+ /**
2200
+ * The current user's own unflag succeeded (`user.unflag` REQ-010, REQ-016). A per-type unflag can
2201
+ * leave other reports in place, so the answer is dropped, never set to `false`; the next "maybe"
2202
+ * is confirmed again.
2203
+ */
2204
+ const onUnflaggedByMe = (userId) => {
2205
+ bumpGeneration(userId);
2206
+ const dropped = readRecord(userId);
2207
+ dropFromCache(cacheKey(userId), true);
2208
+ if ((dropped === null || dropped === void 0 ? void 0 : dropped.isFlagByMe) === false)
2209
+ notifyObservers(userId);
2210
+ };
2211
+ /** Forgets every in-memory trace of the current user's flag state. Records live in the cache. */
2212
+ const resetUserFlagByMe = () => {
2213
+ flagsByMeEntries.clear();
2214
+ inFlight.clear();
2215
+ lastConfirmAt.clear();
2216
+ generations.clear();
2217
+ };
2218
+ /**
2219
+ * Registered through `setupLoginSubscriptions`. Every confirmed answer belongs to the current
2220
+ * user, so the state is cleared on logout; the records themselves go with the cache, and each
2221
+ * also carries the reporter's id.
2222
+ */
2223
+ const userFlagByMeOnLoginHandler = () => {
2224
+ const unsubscribe = onSessionStateChange(state => {
2225
+ if (state === "notLoggedIn" /* Amity.SessionStates.NOT_LOGGED_IN */ || state === "terminated" /* Amity.SessionStates.TERMINATED */) {
2226
+ resetUserFlagByMe();
2227
+ }
2228
+ });
2229
+ return () => {
2230
+ unsubscribe();
2231
+ resetUserFlagByMe();
2232
+ };
2233
+ };
2234
+ /**
2235
+ * Called with the cached user when a recorded answer changes what that user would show, so live
2236
+ * objects and live collections deliver it again (REQ-016, delivery).
2237
+ */
2238
+ const onUserFlagConfirmed = (callback) => createEventSubscriber(getActiveClient(), 'user/onUserFlagConfirmed', 'local.user.flagConfirmed', ({ userId }) => {
2239
+ var _a;
2240
+ const user = (_a = pullFromCache(['user', 'get', userId])) === null || _a === void 0 ? void 0 : _a.data;
2241
+ if (user)
2242
+ callback(user);
2243
+ });
2244
+
1801
2245
  const userLinkedObject = (user) => {
2246
+ // Captured now, so a user object the app holds never changes after a confirm (REQ-014).
2247
+ const confirmedFlagByMe = readConfirmedFlag(user.userId, user.hashFlag);
1802
2248
  return Object.assign(Object.assign({}, user), { get avatar() {
1803
2249
  var _a;
1804
2250
  if (!user.avatarFileId)
1805
- return undefined;
1806
- const avatar = (_a = pullFromCache([
1807
- 'file',
1808
- 'get',
1809
- `${user.avatarFileId}`,
1810
- ])) === null || _a === void 0 ? void 0 : _a.data;
2251
+ return null;
2252
+ const avatar = (_a = pullFromCache(['file', 'get', user.avatarFileId])) === null || _a === void 0 ? void 0 : _a.data;
2253
+ if (!avatar)
2254
+ return null;
1811
2255
  return avatar;
1812
- } });
2256
+ },
2257
+ get isFlaggedByMe() {
2258
+ // Bloom test on this object's hashFlag: "definitely not" is exact and sends nothing.
2259
+ // "Maybe" returns the answer confirmed for this hashFlag, or true while one background
2260
+ // confirm runs (REQ-011, REQ-016). Never throws.
2261
+ return isFlaggedByMeFor(user, confirmedFlagByMe);
2262
+ },
2263
+ /* begin_public_function
2264
+ id: user.get_flags_by_me
2265
+ */
2266
+ getUserFlagsByMe: getUserFlagsByMeFor(user) });
1813
2267
  };
1814
2268
 
1815
2269
  /* begin_public_function
@@ -2076,13 +2530,13 @@ class NetworkActivitiesWatcher {
2076
2530
  this._listener.clear();
2077
2531
  }
2078
2532
  }
2079
- let instance$b;
2533
+ let instance$a;
2080
2534
  var NetworkActivitiesWatcher$1 = {
2081
2535
  getInstance: () => {
2082
- if (!instance$b) {
2083
- instance$b = new NetworkActivitiesWatcher();
2536
+ if (!instance$a) {
2537
+ instance$a = new NetworkActivitiesWatcher();
2084
2538
  }
2085
- return instance$b;
2539
+ return instance$a;
2086
2540
  },
2087
2541
  };
2088
2542
 
@@ -2266,54 +2720,6 @@ const createHttpTransport = (endpoint) => {
2266
2720
  return instance;
2267
2721
  };
2268
2722
 
2269
- class SessionWatcher {
2270
- constructor() {
2271
- this._sessionState = "notLoggedIn" /* Amity.SessionStates.NOT_LOGGED_IN */;
2272
- this._listener = new Map();
2273
- }
2274
- onSessionStateChange(callback) {
2275
- this._listener.set(callback, callback);
2276
- return () => {
2277
- this._listener.delete(callback);
2278
- };
2279
- }
2280
- setSessionState(state, reason) {
2281
- if (this._sessionState === state)
2282
- return;
2283
- this._sessionState = state;
2284
- this._listener.forEach(cb => cb(state, reason));
2285
- }
2286
- destroy() {
2287
- this._listener.clear();
2288
- }
2289
- }
2290
- let instance$a;
2291
- var SessionWatcher$1 = {
2292
- getInstance: () => {
2293
- if (!instance$a) {
2294
- instance$a = new SessionWatcher();
2295
- }
2296
- return instance$a;
2297
- },
2298
- };
2299
-
2300
- /**
2301
- * ```js
2302
- * import { onSessionStateChange } from '@amityco/ts-sdk'
2303
- * const dispose = onSessionStateChange((state: Amity.SessionStates) => {
2304
- * // ...
2305
- * })
2306
- * ```
2307
- *
2308
- * Fired when any {@link Amity.Client} has a session state change
2309
- *
2310
- * @param callback The function to call when the event was fired
2311
- * @returns an {@link Amity.Unsubscriber} function to stop listening
2312
- *
2313
- * @category Client Events
2314
- */
2315
- const onSessionStateChange = (callback) => SessionWatcher$1.getInstance().onSessionStateChange(callback);
2316
-
2317
2723
  const TOPICS = {
2318
2724
  [AmityAutoSubscription.NETWORK]: [getNetworkTopic],
2319
2725
  [AmityAutoSubscription.CHAT]: [
@@ -2886,7 +3292,8 @@ const convertRawMembershipToMembership = (member) => {
2886
3292
  if (!userCache) {
2887
3293
  return undefined;
2888
3294
  }
2889
- return userCache;
3295
+ // Every user the SDK hands out is a linked object (isFlaggedByMe, getUserFlagsByMe, avatar).
3296
+ return userLinkedObject(userCache);
2890
3297
  } });
2891
3298
  };
2892
3299
  /**
@@ -2920,7 +3327,7 @@ const withUser = (member, userIdProp = 'userId', userProp = 'user') => {
2920
3327
  if (!userCache) {
2921
3328
  return undefined;
2922
3329
  }
2923
- return userCache;
3330
+ return userLinkedObject(userCache);
2924
3331
  } }, member);
2925
3332
  };
2926
3333
  /**
@@ -3074,12 +3481,19 @@ const getSubChannelMessagePreview = (subChannelId) => {
3074
3481
  };
3075
3482
 
3076
3483
  const getSubChannelMessagePreviewWithUser = (subChannel) => {
3077
- var _a;
3078
3484
  const messagePreview = subChannel.messagePreviewId
3079
3485
  ? getSubChannelMessagePreview(subChannel.subChannelId)
3080
3486
  : null;
3081
3487
  const messagePreviewWithUser = messagePreview
3082
- ? Object.assign(Object.assign({}, messagePreview), { user: (_a = pullFromCache(['user', 'get', messagePreview === null || messagePreview === void 0 ? void 0 : messagePreview.creatorId])) === null || _a === void 0 ? void 0 : _a.data }) : null;
3488
+ ? Object.assign(Object.assign({}, messagePreview), { user: (() => {
3489
+ var _a;
3490
+ const internalUser = (_a = pullFromCache([
3491
+ 'user',
3492
+ 'get',
3493
+ messagePreview === null || messagePreview === void 0 ? void 0 : messagePreview.creatorId,
3494
+ ])) === null || _a === void 0 ? void 0 : _a.data;
3495
+ return internalUser ? userLinkedObject(internalUser) : undefined;
3496
+ })() }) : null;
3083
3497
  return Object.assign(Object.assign({}, subChannel), { messagePreview: messagePreviewWithUser });
3084
3498
  };
3085
3499
 
@@ -8066,6 +8480,140 @@ const postCommentCountEngineOnLoginHandler = () => onSessionStateChange(state =>
8066
8480
  }
8067
8481
  });
8068
8482
 
8483
+ const BOTH_FIELDS = { lastActivity: true, lastMessageAt: true };
8484
+ /**
8485
+ * The channel cache is keyed by `channelInternalId`, but a message names its
8486
+ * channel by whichever id the producer had: the raw MQTT payload carries the
8487
+ * internal id, a converted `Amity.InternalMessage` carries the public id, and
8488
+ * the optimistic message carries the sub-channel's `channelId`. Try the direct
8489
+ * key first, then match any of the three ids.
8490
+ */
8491
+ const findCachedChannel = (channelId) => {
8492
+ var _a, _b, _c;
8493
+ const direct = (_a = pullFromCache(['channel', 'get', channelId])) === null || _a === void 0 ? void 0 : _a.data;
8494
+ if (direct)
8495
+ return direct;
8496
+ return (_c = (_b = queryCache(['channel', 'get'])) === null || _b === void 0 ? void 0 : _b.find(({ data }) => data.channelInternalId === channelId ||
8497
+ data.channelId === channelId ||
8498
+ data.channelPublicId === channelId)) === null || _c === void 0 ? void 0 : _c.data;
8499
+ };
8500
+ const isNewer = (stored, at) => !stored || convertDateStringToTimestamp(stored) < convertDateStringToTimestamp(at);
8501
+ const isSameInstant = (stored, at) => !!stored && convertDateStringToTimestamp(stored) === convertDateStringToTimestamp(at);
8502
+ /** Pushes the changed fields in one write and emits one `local.channel.updated`. */
8503
+ const commit = (channel, changes) => {
8504
+ if (Object.keys(changes).length === 0)
8505
+ return false;
8506
+ const updated = shallowClone(channel, changes);
8507
+ // Write back under the key the cache actually uses (the resolver key), not
8508
+ // under `channel.channelId`, which can differ from `channelInternalId`.
8509
+ pushToCache(['channel', 'get', getResolver('channel')(updated)], updated);
8510
+ fireEvent('local.channel.updated', {
8511
+ channels: [updated],
8512
+ channelUsers: [],
8513
+ users: [],
8514
+ files: [],
8515
+ });
8516
+ return true;
8517
+ };
8518
+ /**
8519
+ * Single owner of every local write to a channel's ordering timestamps,
8520
+ * `lastActivity` and `lastMessageAt` (ChannelActivityService v1).
8521
+ *
8522
+ * - Monotonic per field: a field moves only when `at` is newer than the stored
8523
+ * value (or the stored value is `null`), so a late or replayed event never
8524
+ * moves a channel down the list.
8525
+ * - One write: both fields are read, compared and pushed in a single cache
8526
+ * update, followed by one `local.channel.updated` event so every open channel
8527
+ * collection re-sorts once.
8528
+ * - Server channel payloads never go through here; they overwrite unguarded.
8529
+ *
8530
+ * @returns `true` when at least one field moved
8531
+ * @category private
8532
+ */
8533
+ const updateChannelActivityTimestamps = (channelId, at, fields = BOTH_FIELDS) => {
8534
+ const client = getActiveClient();
8535
+ if (!client.cache || !channelId || !at)
8536
+ return false;
8537
+ const channel = findCachedChannel(channelId);
8538
+ if (!channel)
8539
+ return false;
8540
+ const changes = {};
8541
+ if (fields.lastActivity && isNewer(channel.lastActivity, at))
8542
+ changes.lastActivity = at;
8543
+ if (fields.lastMessageAt && isNewer(channel.lastMessageAt, at))
8544
+ changes.lastMessageAt = at;
8545
+ return commit(channel, changes);
8546
+ };
8547
+ /**
8548
+ * Local correction for the current user's own send (ChannelActivityService v1
8549
+ * REQ-009, Trigger Matrix row 1b). Once the server confirms the message, each
8550
+ * field that still holds the device-clock `localAt` written at send time is
8551
+ * replaced by the server's `serverAt`, even when that is older. A field a newer
8552
+ * message has already moved is left alone. One write, one change notification,
8553
+ * nothing when neither field still holds `localAt`.
8554
+ *
8555
+ * @returns `true` when at least one field was replaced
8556
+ * @category private
8557
+ */
8558
+ const confirmOwnMessage = (channelId, localAt, serverAt) => {
8559
+ const client = getActiveClient();
8560
+ if (!client.cache || !channelId || !localAt || !serverAt)
8561
+ return false;
8562
+ if (isSameInstant(localAt, serverAt))
8563
+ return false;
8564
+ const channel = findCachedChannel(channelId);
8565
+ if (!channel)
8566
+ return false;
8567
+ const changes = {};
8568
+ if (isSameInstant(channel.lastActivity, localAt))
8569
+ changes.lastActivity = serverAt;
8570
+ if (isSameInstant(channel.lastMessageAt, localAt))
8571
+ changes.lastMessageAt = serverAt;
8572
+ return commit(channel, changes);
8573
+ };
8574
+
8575
+ /**
8576
+ * Session-wide owner of the local `lastActivity` / `lastMessageAt` updates
8577
+ * (ChannelActivityService v1, Web migration rows W1 – W5).
8578
+ *
8579
+ * Registered once per session from `setupLoginSubscriptions` and
8580
+ * `resumeSession`, so the update runs whether or not a channel collection is
8581
+ * open, and exactly once per message however many collections are open.
8582
+ *
8583
+ * It listens to the raw events rather than `onMessageCreatedMqtt`, so it does
8584
+ * not duplicate that subscriber's side effects (mark-read, unread recount).
8585
+ *
8586
+ * @category private
8587
+ */
8588
+ const channelActivityOnLoginHandler = () => {
8589
+ const client = getActiveClient();
8590
+ const disposers = [
8591
+ // Any user's new message, in any message feed of the channel. The raw
8592
+ // payload's `channelId` is the channel's internal id.
8593
+ createEventSubscriber(client, 'channelActivity', 'message.created', payload => {
8594
+ payload.messages.forEach(message => {
8595
+ updateChannelActivityTimestamps(message.channelId, message.createdAt);
8596
+ });
8597
+ }),
8598
+ // The current user's own send fires this three times: with the optimistic
8599
+ // message at send time (Syncing), on the create response (Synced), and on
8600
+ // failure (Error). The device-clock time of the Syncing / Error fires was
8601
+ // already applied synchronously in createMessageOptimistic, and replaying it
8602
+ // here could undo the server correction made on the response
8603
+ // (confirmOwnMessage). Only the server-confirmed fire goes through the guard.
8604
+ createEventSubscriber(client, 'channelActivity', 'local.message.created', payload => {
8605
+ payload.messages.forEach(message => {
8606
+ if (message.syncState !== "synced" /* Amity.SyncState.Synced */)
8607
+ return;
8608
+ updateChannelActivityTimestamps(message.channelId, message.createdAt);
8609
+ });
8610
+ }),
8611
+ ];
8612
+ return () => {
8613
+ disposers.forEach(fn => fn());
8614
+ };
8615
+ };
8616
+
8069
8617
  const EVENTS = [
8070
8618
  'disconnected',
8071
8619
  'error',
@@ -8255,7 +8803,7 @@ const setupLoginSubscriptions = (unsubWatcher) => {
8255
8803
  // NOTE: This is a temporary solution to handle the channel marker when the user is forced to leave
8256
8804
  // the channel because currently backend can't handle this, so every time a user is banned from
8257
8805
  // a channel or the channel is deleted the channel's unread count will not be reset to zero
8258
- onChannelDeleted(removeChannelMarkerCache), onChannelMemberBanned(removeChannelMarkerCache), markReadEngineOnLoginHandler(), analyticsEngineOnLoginHandler(), objectResolverEngineOnLoginHandler(), reactionSyncEngineOnLoginHandler(), blockedUserSyncEngineOnLoginHandler(), blockingUserSyncEngineOnLoginHandler(), blockRealtimeOnLoginHandler(), resetAutoSubscriptionsOnLogout(), CursorController.clearOnLogout(), postCommentCountEngineOnLoginHandler());
8806
+ onChannelDeleted(removeChannelMarkerCache), onChannelMemberBanned(removeChannelMarkerCache), markReadEngineOnLoginHandler(), analyticsEngineOnLoginHandler(), objectResolverEngineOnLoginHandler(), reactionSyncEngineOnLoginHandler(), blockedUserSyncEngineOnLoginHandler(), blockingUserSyncEngineOnLoginHandler(), blockRealtimeOnLoginHandler(), resetAutoSubscriptionsOnLogout(), CursorController.clearOnLogout(), postCommentCountEngineOnLoginHandler(), channelActivityOnLoginHandler(), userFlagByMeOnLoginHandler());
8259
8807
  if (client.useLegacyUnreadCount) {
8260
8808
  subscriptions.push(readReceiptSyncEngineOnLoginHandler());
8261
8809
  }
@@ -9516,7 +10064,7 @@ const resumeSession = async (params, sessionHandler, config) => {
9516
10064
  // NOTE: This is a temporary solution to handle the channel marker when the user is forced to leave
9517
10065
  // the channel because currently backend can't handle this, so every time a user is banned from
9518
10066
  // a channel or the channel is deleted the channel's unread count will not be reset to zero
9519
- onChannelDeleted(removeChannelMarkerCache), onChannelMemberBanned(removeChannelMarkerCache), markReadEngineOnLoginHandler(), analyticsEngineOnLoginHandler(), objectResolverEngineOnLoginHandler());
10067
+ onChannelDeleted(removeChannelMarkerCache), onChannelMemberBanned(removeChannelMarkerCache), markReadEngineOnLoginHandler(), analyticsEngineOnLoginHandler(), objectResolverEngineOnLoginHandler(), channelActivityOnLoginHandler());
9520
10068
  if (client.useLegacyUnreadCount) {
9521
10069
  subscriptions$1.push(readReceiptSyncEngineOnLoginHandler());
9522
10070
  }
@@ -11808,7 +12356,7 @@ const prepareCommunityPayload = (rawPayload) => {
11808
12356
  // map users with community
11809
12357
  const mappedCommunityUsers = rawPayload.communityUsers.map(communityUser => {
11810
12358
  const user = internalUsers.find(user => user.userId === communityUser.userId);
11811
- return Object.assign(Object.assign({}, communityUser), { user });
12359
+ return Object.assign(Object.assign({}, communityUser), { user: user ? userLinkedObject(user) : undefined });
11812
12360
  });
11813
12361
  const communityWithMembershipStatus = updateMembershipStatus(communitiesWithPostSetting, mappedCommunityUsers);
11814
12362
  return Object.assign(Object.assign({}, rawPayload), { users: rawPayload.users.map(convertRawUserToInternalUser), communities: communityWithMembershipStatus, communityUsers: mappedCommunityUsers });
@@ -11825,7 +12373,7 @@ const prepareCommunityMembershipPayload = (rawPayload) => {
11825
12373
  // map users with community
11826
12374
  const mappedCommunityUsers = rawPayload.communityUsers.map(communityUser => {
11827
12375
  const user = rawPayload.users.find(user => user.userId === communityUser.userId);
11828
- return Object.assign(Object.assign({}, communityUser), { user });
12376
+ return Object.assign(Object.assign({}, communityUser), { user: user ? userLinkedObject(convertRawUserToInternalUser(user)) : undefined });
11829
12377
  });
11830
12378
  const communityWithMembershipStatus = updateMembershipStatus(communitiesWithPostSetting, mappedCommunityUsers);
11831
12379
  return Object.assign(Object.assign({}, rawPayload), { communities: communityWithMembershipStatus, communityUsers: mappedCommunityUsers });
@@ -13475,7 +14023,7 @@ const getWatchSessionStorage = () => {
13475
14023
  return storageInstance;
13476
14024
  };
13477
14025
 
13478
- const privateKey = "MIIEpQIBAAKCAQEAwAEc/oZgYIvKSUG/C3mONYLR4ZPgAjMEX4bJ+xqqakUDRtqlNO+eZs2blQ1Ko0DBkqPExyQezvjibH5W2UZBV5RaBTlTcNVKTToMBEGesAfaEcM3qUyQHxdbFYZv6P4sb14dcwxTQ8usmaV8ooiR1Fcaso5ZWYcZ8Hb46FbQ7OoVumsBtPWwfZ4f003o5VCl6AIM6lcLv9UDLlFVYhE+PeXpRHtfWlGqxMvqC9oinlwhL6nWv6VjQXW4nhcib72dPBzfHT7k/PMKto2SxALYdb68ENiAGuJLWi3AUHSyYCJK2w7wIlWfJUAI0v26ub10IpExr6D5QuW2577jjP93iwIDAQABAoIBAFWfqXhwIIatkFY+9Z1+ZcbDQimgsmMIsUiQaX6Lk7e0cxOj6czDlxYtVtaPiNtow2pLkjNkjkCqiP7tEHnwdK9DvylZOTa2R15NJpK3WLcTqVIGhsn/FL5owfvFah6zSsmXZParZm5zY9NZE03ALZhOB9/cz0e3kf/EbpfeL2mW7MApyiUt5i09ycchroOpcWp73ipIxvgigtZyUGFmsQicWhUs28F0D7w4Qfk76yG3nqXeb+BAMhCaIaa/k/aAxhiZG/ygEQWQrcC8gfe+jyicMAQPDEVS9YuUMGsLjIjKuVLZzp2xirQnhc2i2zVNEIvG6soprPOBEMQugzrtX5ECgYEA3b7KAbBIbDl1e4ZSCWhHdHkiWVZHaopsR/LhqDDNhXjWjq3AesgV6k0j9EdziMn/HmmOso0bz99GTV3JZf4A9ztTLumJlkHbdVtlgOqSjrFLj12rH9KXTheyIhWSpUmm8+WB1xasFbqpvJaGo7F3pd2Fqj1XR4mp5BO7c/t7LJ0CgYEA3aouEzXQ9THRKYocdfY69EI1Il1t/d/RSqqd9BxEjxBgxkM13ZiYIn/R4WW/nCUrlmhxG44Aa2Gob4Ahfsui2xKTg/g/3Zk/rAxAEGkfOLGoenaJMD41fH4wUq3FRYwkvnaMb9Hd6f/TlBHslIRa2NN58bSBGJCyBP2b59+2+EcCgYEAixDVRXvV37GlYUOa/XVdosk5Zoe6oDGRuQm0xbNdoUBoZvDHDvme7ONWEiQha/8qtVsD+CyQ7awcPfb8kK9c0bBt+bTS6d4BkTcxkEkMgtrkBVR8Nqfu5jXsLH4VCv4G61zbMhZw8+ut+az5YX2yCN7Frj9sFlxapMRPQmzMEe0CgYEAumsAzM8ZqNv4mAK65Mnr0rhLj1cbxcKRdUYACOgtEFQpzxN/HZnTeFAe5nx3pI3uFlRHq3DFEYnT6dHMWaJQmAULYpVIwMi9L6gtyJ9fzoI6uqMtxRDMUqKdaSsTGOY/kJ6KhQ/unXi1K3XXjR+yd1+C0q+HUm1+CYxvrZYLfskCgYEArsEy+IQOiqniJ0NE2vVUF+UK/IRZaic9YKcpov5Ot7Vvzm/MnnW4N1ljVskocETBWMmPUvNSExVjPebi+rxd8fa5kY8BJScPTzMFbunZn/wjtGdcM10qdlVQ9doG61A/9P3ezFKCfS4AvF/H/59LcSx2Bh28fp3/efiVIOpVd4Y=";
14026
+ const privateKey = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDHo80SecH7FuF2\nhFYnb+l26/VN8UMLXAQFLnxciNTEwkGVFMpdezlH8rU2HtUJL4RETogbAOLVY0XM\njs6sPn8G1nALmh9qeDpUtVqFOVtBHxEZ910TLOtQiunjqJKO5nWdqZ71EC3OFluR\niGQkO84BiIFbv37ub7xl3S8XarbtKoLcyVpkDHi+1wx1pgCAn6gtBUgckPL5NR8j\nLseabl3HAXQfhTCKo4tmOFM2Dxwl1IUMmIJrJg/aIU/U0tj/1Eoo7mG0JcNWX19l\nW3EecCbi0ncCJOrkUdwlBrcjaMayaX/ubEwyUeTGiLdyc4L3GRLHjyK8xgVNXRMH\nbZWJ2a5NAgMBAAECggEASxuE+35zTFO/XydKgmvIGcWL9FbgMlXb7Vcf0nBoG945\nbiz0NVc2paraIhJXc608xbYF3qLmtAE1MVBI0ORyRdBHNxY024l/6H6SH60Ed+uI\nM4ysp5ourY6Vj+DLwpdRiI9YDjqYAQDIUmhNxJP7XPhOMoZI6st+xZQBM34ic/bv\nAMSJm9OZphSp3+qXVkFZztr2mxD2EZSJJLYxi8BCdgM2qhazalbcJ6zDKHCZWVWm\n8RRxDGldyMb/237JxETzP40tAlzOZDmBAbUgEnurDJ93RVDIE3rbZUshwgeQd18a\nem096mWgvB1AIKYgsTAR3pw+V19YWAjq/glP6fz8wQKBgQD/oQq+ukKF0PRgBeM5\ngeTjSwsdGppQLmf5ndujvoiz/TpdjDEPu6R8kigQr1rG2t4K/yfdZoI8RdmJD1al\n3Q7N9hofooSy4rj6E3txzWZCHJjHad2cnCp/O26HiReGAl7wTcfTmNdiFHhZQzm5\nJBkvWAiwuvQMNfEbnXxw6/vIDwKBgQDH7fX8gsc77JLvAWgp1MaQN/sbqVb6JeT1\nFQfR8E/WFCSmzQBtNzd5KgYuCeelwr/8DyYytvN2BzCYZXp73gI1jF3YlW5jVn74\nOY6TwQ095digwo6Z0yuxopdIOApKgAkL9PRKgNrqAf3NAyMua6lOGifzjDojC3KU\nfylQmxMn4wKBgHp2B9O/H0dEBw5JQ8W0+JX6yWQz7mEjGiR2/1W+XXb8hQ1zr709\nw1r6Gb+EghRpnZ3fBpYGGbYOMFx8wKHM+N6qW3F0ReX8v2juFGE8aRSa5oYBrWzt\nU16Idjbv8hj84cZ1PJmdyvDtpYn9rpWHOZl4rxEbPvbqkIsOMyNVqdT5AoGAOSge\nmwIIU2le2FVeohbibXiToWTYKMuMmURZ5/r72AgKMmWJKbAPe+Q3wBG01/7FRBpQ\noU8Ma0HC8s6QJbliiEyIx9JwrJWd1vkdecBHONrtA4ibm/5zD2WcOllLF+FitLhi\n3qnX6+6F0IaFGFBPJrTzlv0P4dTz/OAdv52V7GECgYEA2TttOKBAqWllgOaZOkql\nLVMJVmgR7s6tLi1+cEP8ZcapV9aRbRzTAKXm4f8AEhtlG9F9kCOvHYCYGi6JaiWJ\nZkHjeex3T+eE6Di6y5Bm/Ift5jtVhJ4jCVwHOKTMej79NPUFTJfv8hCo29haBDv6\nRXFrv+T21KCcw8k3sJeJWWQ=\n-----END PRIVATE KEY-----";
13479
14027
  /*
13480
14028
  * The crypto algorithm used for importing key and signing string
13481
14029
  */
@@ -15250,7 +15798,7 @@ const updateUser = async (userId, patch) => {
15250
15798
  ingestInCache(payload, { cachedAt });
15251
15799
  fireEvent('user.updated', data);
15252
15800
  return {
15253
- data: payload.users.find(user => user.userId === userId),
15801
+ data: userLinkedObject(payload.users.find(user => user.userId === userId)),
15254
15802
  cachedAt,
15255
15803
  };
15256
15804
  };
@@ -15301,8 +15849,11 @@ const flagUser = async (userId, options) => {
15301
15849
  if (client.cache) {
15302
15850
  ingestInCache(payload);
15303
15851
  }
15852
+ const flagged = (_a = payload.users.find(user => user.userId === userId)) !== null && _a !== void 0 ? _a : payload.users[0];
15853
+ // The caller's own report may leave hashFlag unchanged; record that they flagged (REQ-016).
15854
+ onFlaggedByMe(flagged, userId);
15304
15855
  fireEvent('user.flagged', data);
15305
- return (_a = payload.users.find(user => user.userId === userId)) !== null && _a !== void 0 ? _a : payload.users[0];
15856
+ return userLinkedObject(flagged);
15306
15857
  };
15307
15858
  /* end_public_function */
15308
15859
 
@@ -15322,14 +15873,18 @@ const submitUserReport = async (apiName, userId, body) => {
15322
15873
  if (client.cache) {
15323
15874
  ingestInCache(payload);
15324
15875
  }
15876
+ const flagged = (_a = payload.users.find(user => user.userId === userId)) !== null && _a !== void 0 ? _a : payload.users[0];
15877
+ // The caller's own report may leave hashFlag unchanged; record that they flagged (REQ-016).
15878
+ onFlaggedByMe(flagged, userId);
15325
15879
  fireEvent('user.flagged', data);
15326
- return (_a = payload.users.find(user => user.userId === userId)) !== null && _a !== void 0 ? _a : payload.users[0];
15880
+ return userLinkedObject(flagged);
15327
15881
  };
15328
15882
 
15329
15883
  /** Maximum `comment` length, in string length units, after trimming. */
15330
15884
  const USER_REPORT_COMMENT_MAX_LENGTH = 300;
15331
15885
  const PROFILE_FIELDS = Object.values(UserProfileFieldEnum);
15332
15886
  const BEHAVIOR_REASONS = Object.values(ContentFlagReasonEnum);
15887
+ const REPORT_TYPE_FILTERS = Object.values(UserReportTypeFilterEnum);
15333
15888
  const assertOptionsObject = (apiName, options, shape) => {
15334
15889
  if (typeof options !== 'object' || options === null || Array.isArray(options)) {
15335
15890
  throw new ASCInvalidParameterError(`${apiName}: expected an options object \`${shape}\``);
@@ -15389,6 +15944,28 @@ const toUserBehaviorReportBody = (options) => {
15389
15944
  }
15390
15945
  return withComment({ reportType: 'behavior', reason }, normalizeUserReportComment(apiName, comment));
15391
15946
  };
15947
+ /**
15948
+ * Validates the optional `{ reportType? }` of `unflagUser` and builds its query parameters.
15949
+ * Returns `undefined` when no `reportType` is given, so the request carries no query string
15950
+ * and withdraws every report the caller filed on the user. The server validates the value
15951
+ * too (422); this local check only saves the round-trip for the string alias, which admits
15952
+ * raw strings. Untyped callers can pass anything at runtime, so nothing is trusted from the
15953
+ * declared type.
15954
+ *
15955
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when the options are malformed or `reportType` is unknown
15956
+ */
15957
+ const toUserReportTypeFilter = (options) => {
15958
+ if (options === undefined)
15959
+ return undefined;
15960
+ const apiName = 'unflagUser';
15961
+ const { reportType } = assertOptionsObject(apiName, options, '{ reportType? }');
15962
+ if (reportType === undefined)
15963
+ return undefined;
15964
+ if (typeof reportType !== 'string' || !REPORT_TYPE_FILTERS.includes(reportType)) {
15965
+ throw new ASCInvalidParameterError(`${apiName}: \`reportType\` must be a \`UserReportTypeFilterEnum\` value`);
15966
+ }
15967
+ return { reportType: reportType };
15968
+ };
15392
15969
 
15393
15970
  /* begin_public_function
15394
15971
  id: user.flag_profile
@@ -15461,24 +16038,53 @@ const flagUserBehavior = async (userId, options) => submitUserReport('flagUserBe
15461
16038
  */
15462
16039
  /**
15463
16040
  * ```js
15464
- * import { UserRepository } from '@amityco/ts-sdk'
15465
- * const unflagged = await UserRepository.unflagUser('post', postId)
16041
+ * import { UserRepository, UserReportTypeFilterEnum } from '@amityco/ts-sdk'
16042
+ *
16043
+ * // Withdraw every report I filed on the user.
16044
+ * const unflagged = await UserRepository.unflagUser('userId')
16045
+ *
16046
+ * // Withdraw only my behavior report; my other reports on the user stay.
16047
+ * await UserRepository.unflagUser('userId', { reportType: UserReportTypeFilterEnum.BEHAVIOR })
16048
+ *
16049
+ * // Withdraw only my typeless (legacy) report.
16050
+ * await UserRepository.unflagUser('userId', { reportType: UserReportTypeFilterEnum.UNTYPED })
15466
16051
  * ```
15467
16052
  *
16053
+ * Withdraws the caller's reports on a user. Without `reportType` every report the caller
16054
+ * filed on that user is withdrawn, as before. With `reportType` only the caller's report
16055
+ * of that type is withdrawn; the caller's other reports and other users' reports are
16056
+ * untouched. Withdrawing a report that does not exist succeeds and changes nothing.
16057
+ *
16058
+ * The returned user is persisted to the cache, so its `hashFlag` says whether the caller
16059
+ * may still hold a report on the user. Do not flip a local "flagged by me" flag to `false`
16060
+ * after a per-type withdrawal; observe the user and read `isFlaggedByMe`, or call
16061
+ * `user.getUserFlagsByMe()` for the remaining per-type state (`flags`). The SDK drops the
16062
+ * answer it confirmed for this user, so the next "maybe" is confirmed again.
16063
+ *
16064
+ * Sends `DELETE /api/v4/me/flags/:userId`, with `?reportType=<type>` when one is given.
16065
+ *
15468
16066
  * @param userId The ID of the user to unflag
15469
- * @returns the deleted report result
16067
+ * @param options Optional; `reportType` selects a single report to withdraw. For a report that `user.getUserFlagsByMe()` lists with `reportType: null`, pass `UserReportTypeFilterEnum.UNTYPED`
16068
+ * @returns `true` once the server has processed the withdrawal
16069
+ * @throws {ASCInvalidParameterError} `INVALID_PARAMETERS` when `reportType` is not a `UserReportTypeFilterEnum` value, before any request is made
15470
16070
  *
15471
16071
  * @category User API
15472
16072
  * @async
15473
16073
  * */
15474
- const unflagUser = async (userId) => {
15475
- const client = getActiveClient();
15476
- client.log('user/unflag', userId);
15477
- const { data } = await client.http.delete(`/api/v4/me/flags/${encodeURIComponent(userId)}`);
16074
+ const unflagUser = async (userId, options) => {
16075
+ const client = getActiveClient();
16076
+ client.log('user/unflag', { userId, options });
16077
+ const params = toUserReportTypeFilter(options);
16078
+ const url = `/api/v4/me/flags/${encodeURIComponent(userId)}`;
16079
+ const { data } = params
16080
+ ? await client.http.delete(url, { params })
16081
+ : await client.http.delete(url);
15478
16082
  const payload = prepareUserPayload(data);
15479
16083
  if (client.cache) {
15480
16084
  ingestInCache(payload);
15481
16085
  }
16086
+ // Drop the confirmed answer; never set it to false: other reports may remain (REQ-010).
16087
+ onUnflaggedByMe(userId);
15482
16088
  fireEvent('user.unflagged', data);
15483
16089
  return !!payload;
15484
16090
  };
@@ -15490,11 +16096,19 @@ const unflagUser = async (userId) => {
15490
16096
  /**
15491
16097
  * ```js
15492
16098
  * import { UserRepository } from '@amityco/ts-sdk'
15493
- * const isFlagged = await UserRepository.isUserFlaggedByMe(postId)
16099
+ * const isFlagged = await UserRepository.isUserFlaggedByMe(userId)
15494
16100
  * ```
15495
16101
  *
15496
- * @param userId The ID of the thing to check a report to.
15497
- * @returns `true` if the report is created by me, `false` if doesn't.
16102
+ * Whether the current user has reported a user. Sends one request per call and caches
16103
+ * nothing.
16104
+ *
16105
+ * @deprecated Read `user.isFlaggedByMe` on an {@link Amity.User} for a yes/no hint that
16106
+ * usually needs no request, and call `user.getUserFlagsByMe()` for the exact answer with the
16107
+ * caller's reports by type. Observe the user with `UserRepository.getUser(userId, callback)`
16108
+ * to follow a flag or unflag.
16109
+ *
16110
+ * @param userId The ID of the user to check
16111
+ * @returns `true` if the current user has reported the user, `false` if not
15498
16112
  *
15499
16113
  * @category Report API
15500
16114
  * @async
@@ -15502,7 +16116,7 @@ const unflagUser = async (userId) => {
15502
16116
  const isUserFlaggedByMe = async (userId) => {
15503
16117
  const client = getActiveClient();
15504
16118
  client.log('user/isUserFlaggedByMe', userId);
15505
- const { data: { isFlagByMe }, } = await client.http.get(`/api/v3/users/${userId}/isFlagByMe`);
16119
+ const { isFlagByMe } = await getFlagsByMe(userId);
15506
16120
  return isFlagByMe;
15507
16121
  };
15508
16122
  /* end_public_function */
@@ -15742,7 +16356,7 @@ const getUser = (userId, callback) => {
15742
16356
  const reactor = (response) => {
15743
16357
  return callback(Object.assign(Object.assign({}, response), { data: response.data ? LinkedObject.user(response.data) : response.data }));
15744
16358
  };
15745
- return liveObject(userId, reactor, 'userId', getUser$1, [
16359
+ const unsubscribeUser = liveObject(userId, reactor, 'userId', getUser$1, [
15746
16360
  onUserFetched,
15747
16361
  onUserUpdated,
15748
16362
  onUserDeleted$2,
@@ -15750,19 +16364,28 @@ const getUser = (userId, callback) => {
15750
16364
  onUserUnflagged,
15751
16365
  onUserFlagCleared,
15752
16366
  ]);
16367
+ // A confirmed flag answer leaves the cached user row unchanged, so the live object's own
16368
+ // dedupe would drop it. Deliver the re-linked user directly (user.check_flag_by_me REQ-016).
16369
+ const unsubscribeFlag = onUserFlagConfirmed(user => {
16370
+ if (user.userId !== userId)
16371
+ return;
16372
+ callback({ data: LinkedObject.user(user), loading: false, origin: 'event' });
16373
+ });
16374
+ return () => {
16375
+ unsubscribeUser();
16376
+ unsubscribeFlag();
16377
+ };
15753
16378
  };
15754
16379
  /* end_public_function */
15755
16380
 
15756
- class UserPaginationController extends PaginationController {
15757
- async getRequest(queryParams, token) {
15758
- const { limit = COLLECTION_DEFAULT_PAGINATION_LIMIT } = queryParams, params = __rest(queryParams, ["limit"]);
15759
- const options = token ? { token } : { limit };
15760
- const { data: queryResponse } = await this.http.get(`/api/v3/users`, {
15761
- params: Object.assign(Object.assign({}, params), { options, isDeleted: false }),
15762
- });
15763
- return queryResponse;
15764
- }
15765
- }
16381
+ var EnumUserActions;
16382
+ (function (EnumUserActions) {
16383
+ EnumUserActions["OnUserDeleted"] = "onUserDeleted";
16384
+ EnumUserActions["OnUserUpdated"] = "onUserUpdated";
16385
+ EnumUserActions["OnUserFlagged"] = "onUserFlagged";
16386
+ EnumUserActions["OnUserUnflagged"] = "onUserUnflagged";
16387
+ EnumUserActions["OnUserFlagCleared"] = "onUserFlagCleared";
16388
+ })(EnumUserActions || (EnumUserActions = {}));
15766
16389
 
15767
16390
  class UserQueryStreamController extends QueryStreamController {
15768
16391
  constructor(query, cacheKey, notifyChange, preparePayload) {
@@ -15810,14 +16433,16 @@ class UserQueryStreamController extends QueryStreamController {
15810
16433
  }
15811
16434
  }
15812
16435
 
15813
- var EnumUserActions;
15814
- (function (EnumUserActions) {
15815
- EnumUserActions["OnUserDeleted"] = "onUserDeleted";
15816
- EnumUserActions["OnUserUpdated"] = "onUserUpdated";
15817
- EnumUserActions["OnUserFlagged"] = "onUserFlagged";
15818
- EnumUserActions["OnUserUnflagged"] = "onUserUnflagged";
15819
- EnumUserActions["OnUserFlagCleared"] = "onUserFlagCleared";
15820
- })(EnumUserActions || (EnumUserActions = {}));
16436
+ class UserPaginationController extends PaginationController {
16437
+ async getRequest(queryParams, token) {
16438
+ const { limit = COLLECTION_DEFAULT_PAGINATION_LIMIT } = queryParams, params = __rest(queryParams, ["limit"]);
16439
+ const options = token ? { token } : { limit };
16440
+ const { data: queryResponse } = await this.http.get(`/api/v3/users`, {
16441
+ params: Object.assign(Object.assign({}, params), { options, isDeleted: false }),
16442
+ });
16443
+ return queryResponse;
16444
+ }
16445
+ }
15821
16446
 
15822
16447
  class UserLiveCollectionController extends LiveCollectionController {
15823
16448
  constructor(query, callback) {
@@ -15847,13 +16472,24 @@ class UserLiveCollectionController extends LiveCollectionController {
15847
16472
  this.queryStreamController.appendToQueryStream(response, direction, refresh);
15848
16473
  }
15849
16474
  startSubscription() {
15850
- return this.queryStreamController.subscribeRTE([
15851
- { fn: onUserDeleted$2, action: EnumUserActions.OnUserDeleted },
15852
- { fn: onUserUpdated, action: EnumUserActions.OnUserUpdated },
15853
- { fn: onUserFlagged, action: EnumUserActions.OnUserFlagged },
15854
- { fn: onUserUnflagged, action: EnumUserActions.OnUserUnflagged },
15855
- { fn: onUserFlagCleared, action: EnumUserActions.OnUserFlagCleared },
15856
- ]);
16475
+ return [
16476
+ ...this.queryStreamController.subscribeRTE([
16477
+ { fn: onUserDeleted$2, action: EnumUserActions.OnUserDeleted },
16478
+ { fn: onUserUpdated, action: EnumUserActions.OnUserUpdated },
16479
+ { fn: onUserFlagged, action: EnumUserActions.OnUserFlagged },
16480
+ { fn: onUserUnflagged, action: EnumUserActions.OnUserUnflagged },
16481
+ { fn: onUserFlagCleared, action: EnumUserActions.OnUserFlagCleared },
16482
+ ]),
16483
+ // A confirmed flag answer changes isFlaggedByMe on a listed user without changing its
16484
+ // cache row (user.check_flag_by_me REQ-016). Re-link only collections that hold it.
16485
+ onUserFlagConfirmed(user => {
16486
+ var _a;
16487
+ const collection = (_a = pullFromCache(this.cacheKey)) === null || _a === void 0 ? void 0 : _a.data;
16488
+ if (!(collection === null || collection === void 0 ? void 0 : collection.data.includes(user.userId)))
16489
+ return;
16490
+ this.notifyChange({ origin: "event" /* Amity.LiveDataOrigin.EVENT */, loading: false });
16491
+ }),
16492
+ ];
15857
16493
  }
15858
16494
  notifyChange({ origin, loading, error }) {
15859
16495
  var _a, _b;
@@ -15934,16 +16570,17 @@ const getUsers = (params, callback, config) => {
15934
16570
  };
15935
16571
  /* end_public_function */
15936
16572
 
15937
- class BlockedUserPaginationController extends PaginationController {
15938
- async getRequest(queryParams, token) {
15939
- const { limit = COLLECTION_DEFAULT_PAGINATION_LIMIT } = queryParams, params = __rest(queryParams, ["limit"]);
15940
- const options = token ? { token } : { limit };
15941
- const { data: queryResponse } = await this.http.get('/api/v4/me/user-blocks', {
15942
- params: Object.assign(Object.assign({}, params), { options, isDeleted: false }),
15943
- });
15944
- return queryResponse;
16573
+ const convertEventPayload = (eventHandler, sourceModelProp, destinationDomain) => (callback) => eventHandler(sourceModel => {
16574
+ var _a;
16575
+ if (!sourceModel) {
16576
+ return sourceModel;
15945
16577
  }
15946
- }
16578
+ const cacheKey = [destinationDomain, 'get', `${sourceModel[sourceModelProp]}`];
16579
+ const model = (_a = pullFromCache(cacheKey)) === null || _a === void 0 ? void 0 : _a.data;
16580
+ if (!model)
16581
+ return;
16582
+ return callback(model);
16583
+ });
15947
16584
 
15948
16585
  var EnumBlockActions;
15949
16586
  (function (EnumBlockActions) {
@@ -15999,17 +16636,16 @@ class BlockedUserQueryStreamController extends QueryStreamController {
15999
16636
  }
16000
16637
  }
16001
16638
 
16002
- const convertEventPayload = (eventHandler, sourceModelProp, destinationDomain) => (callback) => eventHandler(sourceModel => {
16003
- var _a;
16004
- if (!sourceModel) {
16005
- return sourceModel;
16639
+ class BlockedUserPaginationController extends PaginationController {
16640
+ async getRequest(queryParams, token) {
16641
+ const { limit = COLLECTION_DEFAULT_PAGINATION_LIMIT } = queryParams, params = __rest(queryParams, ["limit"]);
16642
+ const options = token ? { token } : { limit };
16643
+ const { data: queryResponse } = await this.http.get('/api/v4/me/user-blocks', {
16644
+ params: Object.assign(Object.assign({}, params), { options, isDeleted: false }),
16645
+ });
16646
+ return queryResponse;
16006
16647
  }
16007
- const cacheKey = [destinationDomain, 'get', `${sourceModel[sourceModelProp]}`];
16008
- const model = (_a = pullFromCache(cacheKey)) === null || _a === void 0 ? void 0 : _a.data;
16009
- if (!model)
16010
- return;
16011
- return callback(model);
16012
- });
16648
+ }
16013
16649
 
16014
16650
  class BlockedUserLiveCollectionController extends LiveCollectionController {
16015
16651
  constructor(query, callback) {
@@ -16319,17 +16955,6 @@ const getBlockingUsers = (callback) => {
16319
16955
  };
16320
16956
  /* end_public_function */
16321
16957
 
16322
- class SearchUserPaginationController extends PaginationController {
16323
- async getRequest(queryParams, token) {
16324
- const { limit = COLLECTION_DEFAULT_PAGINATION_LIMIT, displayName } = queryParams, params = __rest(queryParams, ["limit", "displayName"]);
16325
- const options = token ? { token } : { limit };
16326
- const { data: queryResponse } = await this.http.get(`/api/v3/users`, {
16327
- params: Object.assign(Object.assign({}, params), { keyword: displayName, options, isDeleted: false }),
16328
- });
16329
- return queryResponse;
16330
- }
16331
- }
16332
-
16333
16958
  class SearchUserQueryStreamController extends QueryStreamController {
16334
16959
  constructor(query, cacheKey, notifyChange, preparePayload) {
16335
16960
  super(query, cacheKey);
@@ -16376,6 +17001,17 @@ class SearchUserQueryStreamController extends QueryStreamController {
16376
17001
  }
16377
17002
  }
16378
17003
 
17004
+ class SearchUserPaginationController extends PaginationController {
17005
+ async getRequest(queryParams, token) {
17006
+ const { limit = COLLECTION_DEFAULT_PAGINATION_LIMIT, displayName } = queryParams, params = __rest(queryParams, ["limit", "displayName"]);
17007
+ const options = token ? { token } : { limit };
17008
+ const { data: queryResponse } = await this.http.get(`/api/v3/users`, {
17009
+ params: Object.assign(Object.assign({}, params), { keyword: displayName, options, isDeleted: false }),
17010
+ });
17011
+ return queryResponse;
17012
+ }
17013
+ }
17014
+
16379
17015
  class SearchUserLiveCollectionController extends LiveCollectionController {
16380
17016
  constructor(query, callback) {
16381
17017
  var _a;
@@ -16405,13 +17041,24 @@ class SearchUserLiveCollectionController extends LiveCollectionController {
16405
17041
  this.queryStreamController.appendToQueryStream(response, direction, refresh);
16406
17042
  }
16407
17043
  startSubscription() {
16408
- return this.queryStreamController.subscribeRTE([
16409
- { fn: onUserDeleted$2, action: EnumUserActions.OnUserDeleted },
16410
- { fn: onUserUpdated, action: EnumUserActions.OnUserUpdated },
16411
- { fn: onUserFlagged, action: EnumUserActions.OnUserFlagged },
16412
- { fn: onUserUnflagged, action: EnumUserActions.OnUserUnflagged },
16413
- { fn: onUserFlagCleared, action: EnumUserActions.OnUserFlagCleared },
16414
- ]);
17044
+ return [
17045
+ ...this.queryStreamController.subscribeRTE([
17046
+ { fn: onUserDeleted$2, action: EnumUserActions.OnUserDeleted },
17047
+ { fn: onUserUpdated, action: EnumUserActions.OnUserUpdated },
17048
+ { fn: onUserFlagged, action: EnumUserActions.OnUserFlagged },
17049
+ { fn: onUserUnflagged, action: EnumUserActions.OnUserUnflagged },
17050
+ { fn: onUserFlagCleared, action: EnumUserActions.OnUserFlagCleared },
17051
+ ]),
17052
+ // A confirmed flag answer changes isFlaggedByMe on a listed user without changing its
17053
+ // cache row (user.check_flag_by_me REQ-016). Re-link only collections that hold it.
17054
+ onUserFlagConfirmed(user => {
17055
+ var _a;
17056
+ const collection = (_a = pullFromCache(this.cacheKey)) === null || _a === void 0 ? void 0 : _a.data;
17057
+ if (!(collection === null || collection === void 0 ? void 0 : collection.data.includes(user.userId)))
17058
+ return;
17059
+ this.notifyChange({ origin: "event" /* Amity.LiveDataOrigin.EVENT */, loading: false });
17060
+ }),
17061
+ ];
16415
17062
  }
16416
17063
  notifyChange({ origin, loading, error }) {
16417
17064
  var _a, _b;
@@ -17242,7 +17889,7 @@ const preparePostPayload = (payload) => {
17242
17889
  // map users with community
17243
17890
  const mappedCommunityUsers = postPayload.communityUsers.map(communityUser => {
17244
17891
  const user = postPayload.users.find(user => user.userId === communityUser.userId);
17245
- return Object.assign(Object.assign({}, communityUser), { user });
17892
+ return Object.assign(Object.assign({}, communityUser), { user: user ? userLinkedObject(convertRawUserToInternalUser(user)) : undefined });
17246
17893
  });
17247
17894
  const communityWithMembershipStatus = updateMembershipStatus(communitiesWithPostSetting, mappedCommunityUsers);
17248
17895
  let mappedNewStream = [];
@@ -19053,7 +19700,8 @@ const getPostReport = async ({ client, referenceId, }) => {
19053
19700
  };
19054
19701
  const getUserReport = async ({ client, referenceId, }) => {
19055
19702
  var _a;
19056
- const { data } = await client.http.get(`/api/v3/users/${referenceId}/isflagbyme`);
19703
+ // v3 isFlagByMe is deprecated by the backend; v4 answers the same boolean plus the caller's flags.
19704
+ const { data } = await client.http.get(`/api/v4/users/${encodeURIComponent(referenceId)}/isFlagByMe`);
19057
19705
  const { result, isFlagByMe } = data !== null && data !== void 0 ? data : {};
19058
19706
  return (_a = result !== null && result !== void 0 ? result : isFlagByMe) !== null && _a !== void 0 ? _a : false;
19059
19707
  };
@@ -19670,7 +20318,7 @@ const getLocalId = () => `LOCAL_${uuid()}`;
19670
20318
  // FIXME: temp solution
19671
20319
  let uniqueId;
19672
20320
  const createMessageOptimistic = (bundle) => {
19673
- var _a, _b;
20321
+ var _a, _b, _c, _d;
19674
20322
  const client = getActiveClient();
19675
20323
  if (!client.cache)
19676
20324
  return;
@@ -19701,9 +20349,20 @@ const createMessageOptimistic = (bundle) => {
19701
20349
  }
19702
20350
  // as reused to update created and updated time, which should be the same
19703
20351
  const createdTime = new Date().toISOString();
19704
- const message = Object.assign({ creatorId: client.userId, creatorPrivateId: getActiveUser()._id, channelSegment: ((_b = subChannel === null || subChannel === void 0 ? void 0 : subChannel.data.messageCount) !== null && _b !== void 0 ? _b : 0) + 1, childrenNumber: 0, createdAt: createdTime, updatedAt: createdTime, syncState: "syncing" /* Amity.SyncState.Syncing */, isDeleted: false }, bundleWithMessageId);
20352
+ const message = Object.assign({
20353
+ // The optimistic message must name its channel (public id) so the own-send
20354
+ // update can move lastActivity / lastMessageAt at send time. Take it from the
20355
+ // cached sub-channel; when that is not cached, resolve the sub-channel id
20356
+ // through the channel cache, which for a channel's default sub-channel is
20357
+ // the channel itself (ChannelActivityService migration row I2). A thread or
20358
+ // unknown channel resolves to undefined, as before.
20359
+ channelId: (_b = subChannel === null || subChannel === void 0 ? void 0 : subChannel.data.channelId) !== null && _b !== void 0 ? _b : (_c = findCachedChannel(bundle.subChannelId)) === null || _c === void 0 ? void 0 : _c.channelId, creatorId: client.userId, creatorPrivateId: getActiveUser()._id, channelSegment: ((_d = subChannel === null || subChannel === void 0 ? void 0 : subChannel.data.messageCount) !== null && _d !== void 0 ? _d : 0) + 1, childrenNumber: 0, createdAt: createdTime, updatedAt: createdTime, syncState: "syncing" /* Amity.SyncState.Syncing */, isDeleted: false }, bundleWithMessageId);
19705
20360
  const cachedAt = UNSYNCED_OBJECT_CACHED_AT_VALUE;
19706
20361
  pushToCache(['message', 'get', message.messageId], message, { cachedAt });
20362
+ // Own-send update (ChannelActivityService row 1): move the channel at send
20363
+ // time, synchronously, so the confirmation on the send response always sees
20364
+ // this value rather than racing the scheduled local event below.
20365
+ updateChannelActivityTimestamps(message.channelId, message.createdAt);
19707
20366
  fireEvent('local.message.created', { messages: [message] });
19708
20367
  return message;
19709
20368
  };
@@ -19743,6 +20402,9 @@ const createMessage = async (bundle) => {
19743
20402
  if (client.cache) {
19744
20403
  ingestInCache(data, { cachedAt });
19745
20404
  }
20405
+ // The own-send update wrote the device-clock createdAt; replace it with the
20406
+ // server's now that the message is confirmed (ChannelActivityService REQ-009).
20407
+ confirmOwnMessage(optimisticData === null || optimisticData === void 0 ? void 0 : optimisticData.channelId, optimisticData === null || optimisticData === void 0 ? void 0 : optimisticData.createdAt, messages[0].createdAt);
19746
20408
  fireEvent('local.message.created', {
19747
20409
  messages: [Object.assign(Object.assign({}, messages[0]), { syncState: "synced" /* Amity.SyncState.Synced */ })],
19748
20410
  });
@@ -22186,6 +22848,9 @@ class ChannelLiveCollectionController extends LiveCollectionController {
22186
22848
  case 'lastCreated':
22187
22849
  channels = channels.sort(sortByLastCreated);
22188
22850
  break;
22851
+ case 'lastMessage':
22852
+ channels = channels.sort(sortByLastMessage);
22853
+ break;
22189
22854
  case 'displayName':
22190
22855
  /*
22191
22856
  * The server returns channels with empty | null displayName's first before
@@ -22246,9 +22911,8 @@ class ChannelLiveCollectionController extends LiveCollectionController {
22246
22911
  objectResolverEngine.resolve(message.channelId, "channel" /* Amity.ReferenceType.CHANNEL */);
22247
22912
  }
22248
22913
  else {
22249
- updateChannelCache(channelCache, {
22250
- lastActivity: message.createdAt,
22251
- });
22914
+ // lastActivity / lastMessageAt are moved by channelActivityOnLoginHandler
22915
+ // (session-wide, monotonic, one write). This listener only re-sorts.
22252
22916
  callback(message);
22253
22917
  }
22254
22918
  });
@@ -22266,9 +22930,7 @@ class ChannelLiveCollectionController extends LiveCollectionController {
22266
22930
  ])) === null || _a === void 0 ? void 0 : _a.data;
22267
22931
  if (!cacheData)
22268
22932
  return;
22269
- updateChannelCache(cacheData, {
22270
- lastActivity: message.createdAt,
22271
- });
22933
+ // Timestamps are moved by channelActivityOnLoginHandler; only re-sort here.
22272
22934
  callback(message);
22273
22935
  });
22274
22936
  }, 'channelId', 'channel'),
@@ -23006,9 +23668,7 @@ class SearchChannelLiveCollectionController extends LiveCollectionController {
23006
23668
  ])) === null || _a === void 0 ? void 0 : _a.data;
23007
23669
  if (!cacheData)
23008
23670
  return;
23009
- updateChannelCache(cacheData, {
23010
- lastActivity: message.createdAt,
23011
- });
23671
+ // Timestamps are moved by channelActivityOnLoginHandler; only re-sort here.
23012
23672
  callback(message);
23013
23673
  });
23014
23674
  }, 'channelId', 'channel'),
@@ -23025,9 +23685,7 @@ class SearchChannelLiveCollectionController extends LiveCollectionController {
23025
23685
  ])) === null || _a === void 0 ? void 0 : _a.data;
23026
23686
  if (!cacheData)
23027
23687
  return;
23028
- updateChannelCache(cacheData, {
23029
- lastActivity: message.createdAt,
23030
- });
23688
+ // Timestamps are moved by channelActivityOnLoginHandler; only re-sort here.
23031
23689
  callback(message);
23032
23690
  });
23033
23691
  }, 'channelId', 'channel'),
@@ -23450,7 +24108,7 @@ const onUserDeleted$1 = (channelId) => (callback) => {
23450
24108
  'get',
23451
24109
  channelUserCacheKey,
23452
24110
  ])) === null || _a === void 0 ? void 0 : _a.data;
23453
- upsertInCache(['channelUsers', 'get', channelUserCacheKey], Object.assign(Object.assign({}, cacheData), { user }));
24111
+ upsertInCache(['channelUsers', 'get', channelUserCacheKey], Object.assign(Object.assign({}, cacheData), { user: userLinkedObject(user) }));
23454
24112
  const channel = (_b = pullFromCache([
23455
24113
  'channel',
23456
24114
  'get',
@@ -24923,10 +25581,10 @@ const onUserDeleted = (communityId) => (callback) => {
24923
25581
  'get',
24924
25582
  communityUserCacheKey,
24925
25583
  ])) === null || _a === void 0 ? void 0 : _a.data;
24926
- pushToCache(['communityUsers', 'get', communityUserCacheKey], Object.assign(Object.assign({}, cacheData), { user }));
25584
+ pushToCache(['communityUsers', 'get', communityUserCacheKey], Object.assign(Object.assign({}, cacheData), { user: userLinkedObject(user) }));
24927
25585
  const community = (_b = pullFromCache(['community', 'get', communityId])) === null || _b === void 0 ? void 0 : _b.data;
24928
25586
  callback(community, [
24929
- Object.assign(Object.assign({}, cacheData), { user }),
25587
+ Object.assign(Object.assign({}, cacheData), { user: userLinkedObject(user) }),
24930
25588
  ]);
24931
25589
  };
24932
25590
  return createEventSubscriber(client, 'user.deleted', 'user.deleted', filter);
@@ -37529,4 +38187,4 @@ class AmityAttachmentProductTags {
37529
38187
  }
37530
38188
  }
37531
38189
 
37532
- export { API_REGIONS, index$6 as AdRepository, AmityAttachmentProductTags, AmityAutoSubscription, AmityChannelNotificationModeEnum, AmityCommunityType, AmityEventOrderOption, AmityEventOriginType, AmityEventResponseStatus, AmityEventSortOption, AmityEventStatus, AmityEventType, AmityLiveViewerCountMode, AmityModuleNotEntitledError, AmitySharableContentType, AnalyticsSourceTypeEnum, index$g as CategoryRepository, index$k as ChannelRepository, index$t as Client, index$f as CommentRepository, CommunityNotificationEventNameEnum, CommunityPostSettingMaps, CommunityPostSettings, index$h as CommunityRepository, ContentFeedType, ContentFlagReasonEnum, index as CuratedContentRepository, DefaultCommunityPostSetting, index$2 as EventRepository, FeedDataTypeEnum, index$e as FeedRepository, FeedSortByEnum, FeedSourceEnum, FeedTypeEnum, FileAccessTypeEnum, index$q as FileRepository, FileType, GET_WATCHER_URLS, index$4 as InvitationRepository, InvitationSortByEnum, InvitationStatusEnum, InvitationTargetTypeEnum, InvitationTypeEnum, JoinRequestStatusEnum, JoinResultStatusEnum, index$3 as LiveReactionRepository, index$8 as LiveStreamPlayer, MembershipAcceptanceTypeEnum, MessageContentType, index$o as MessageRepository, NotificationRolesFilterTypeEnum, NotificationSettingsLevelEnum, index$9 as PollRepository, PostContentType, index$c as PostRepository, PostStructureType, index$1 as ProductRepository, index$p as ReactionRepository, index$a as RoomPresenceRepository, index$d as RoomRepository, SearchUsersByEnum, index$7 as StoryRepository, index$b as StreamRepository, index$n as SubChannelRepository, SubscriptionLevels, UserNotificationModuleNameEnum, UserProfileFieldEnum, UserReportTypeEnum, index$r as UserRepository, UserTypeEnum, VERSION, VideoResolution, VideoSize, VideoTranscodingStatus, backupCache, createQuery, createReport, createUserToken, deleteReport, disableCache, dropFromCache, enableCache, exceedsUntilAtBoundary, filterByChannelMembership, filterByCommunityMembership, filterByFeedType, filterByPostDataTypes, filterByPropEquality, filterByPropInclusion, filterByPropIntersection, filterBySearchTerm, filterByStringComparePartially, filterByUntilAt, filterCommentsByBlockUsers, filterPostsByBlockUsers, getBlockUsersId, getChannelTopic, getCommentTopic, getCommunityStoriesTopic, getCommunityTopic, getLiveReactionTopic, getLiveStreamTopic, getMarkedMessageTopic, getMarkerUserFeedTopic, getMessageTopic, getMyFollowersTopic, getMyFollowingsTopic, getNetworkId, getNetworkTopic, getPostTopic, getRole, getRoomStreamerTopic, getRoomWatcherTopic, getSmartFeedChannelTopic, getSmartFeedMessageTopic, getSmartFeedSubChannelTopic, getStoryTopic, getSubChannelTopic, getUserTopic, isAfterBefore, isAfterBeforeRaw, isCachable, isFetcher, isFresh, isLocal, isMutator, isOffline, isPaged, isReportedByMe, isSkip, mergeInCache, index$5 as notificationTray, onChannelMarkerFetched, onFeedMarkerFetched, onFeedMarkerUpdated, onMessageMarked, onMessageMarkerFetched, onSubChannelMarkerFetched, onSubChannelMarkerUpdated, onUserMarkerFetched, onUserMarkerFetchedLegacy, pullFromCache, pushToCache, queryCache, queryOptions, queryRoles, resolveModuleAvailability, restoreCache, runQuery, sortByChannelSegment, sortByDisplayName, sortByFirstCreated, sortByFirstUpdated, sortByLastActivity, sortByLastCreated, sortByLastUpdated, sortByLocalSortingDate, sortByName, sortBySegmentNumber, subscribeTopic, toPage, toPageRaw, toToken, upsertInCache, validateUntilAt, wipeCache };
38190
+ export { API_REGIONS, index$6 as AdRepository, AmityAttachmentProductTags, AmityAutoSubscription, AmityChannelNotificationModeEnum, AmityCommunityType, AmityEventOrderOption, AmityEventOriginType, AmityEventResponseStatus, AmityEventSortOption, AmityEventStatus, AmityEventType, AmityLiveViewerCountMode, AmityModuleNotEntitledError, AmitySharableContentType, AnalyticsSourceTypeEnum, index$g as CategoryRepository, index$k as ChannelRepository, index$t as Client, index$f as CommentRepository, CommunityNotificationEventNameEnum, CommunityPostSettingMaps, CommunityPostSettings, index$h as CommunityRepository, ContentFeedType, ContentFlagReasonEnum, index as CuratedContentRepository, DefaultCommunityPostSetting, index$2 as EventRepository, FeedDataTypeEnum, index$e as FeedRepository, FeedSortByEnum, FeedSourceEnum, FeedTypeEnum, FileAccessTypeEnum, index$q as FileRepository, FileType, GET_WATCHER_URLS, index$4 as InvitationRepository, InvitationSortByEnum, InvitationStatusEnum, InvitationTargetTypeEnum, InvitationTypeEnum, JoinRequestStatusEnum, JoinResultStatusEnum, index$3 as LiveReactionRepository, index$8 as LiveStreamPlayer, MembershipAcceptanceTypeEnum, MessageContentType, index$o as MessageRepository, NotificationRolesFilterTypeEnum, NotificationSettingsLevelEnum, index$9 as PollRepository, PostContentType, index$c as PostRepository, PostStructureType, index$1 as ProductRepository, index$p as ReactionRepository, index$a as RoomPresenceRepository, index$d as RoomRepository, SearchUsersByEnum, index$7 as StoryRepository, index$b as StreamRepository, index$n as SubChannelRepository, SubscriptionLevels, UserNotificationModuleNameEnum, UserProfileFieldEnum, UserReportTypeEnum, UserReportTypeFilterEnum, index$r as UserRepository, UserTypeEnum, VERSION, VideoResolution, VideoSize, VideoTranscodingStatus, backupCache, createQuery, createReport, createUserToken, deleteReport, disableCache, dropFromCache, enableCache, exceedsUntilAtBoundary, filterByChannelMembership, filterByCommunityMembership, filterByFeedType, filterByPostDataTypes, filterByPropEquality, filterByPropInclusion, filterByPropIntersection, filterBySearchTerm, filterByStringComparePartially, filterByUntilAt, filterCommentsByBlockUsers, filterPostsByBlockUsers, getBlockUsersId, getChannelTopic, getCommentTopic, getCommunityStoriesTopic, getCommunityTopic, getLiveReactionTopic, getLiveStreamTopic, getMarkedMessageTopic, getMarkerUserFeedTopic, getMessageTopic, getMyFollowersTopic, getMyFollowingsTopic, getNetworkId, getNetworkTopic, getPostTopic, getRole, getRoomStreamerTopic, getRoomWatcherTopic, getSmartFeedChannelTopic, getSmartFeedMessageTopic, getSmartFeedSubChannelTopic, getStoryTopic, getSubChannelTopic, getUserTopic, isAfterBefore, isAfterBeforeRaw, isCachable, isFetcher, isFresh, isLocal, isMutator, isOffline, isPaged, isReportedByMe, isSkip, mergeInCache, index$5 as notificationTray, onChannelMarkerFetched, onFeedMarkerFetched, onFeedMarkerUpdated, onMessageMarked, onMessageMarkerFetched, onSubChannelMarkerFetched, onSubChannelMarkerUpdated, onUserMarkerFetched, onUserMarkerFetchedLegacy, pullFromCache, pushToCache, queryCache, queryOptions, queryRoles, resolveModuleAvailability, restoreCache, runQuery, sortByChannelSegment, sortByDisplayName, sortByFirstCreated, sortByFirstUpdated, sortByLastActivity, sortByLastCreated, sortByLastMessage, sortByLastUpdated, sortByLocalSortingDate, sortByName, sortBySegmentNumber, subscribeTopic, toPage, toPageRaw, toToken, upsertInCache, validateUntilAt, wipeCache };