@amityco/ts-sdk 0.0.1-beta.30 → 0.0.1-beta.31

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 (181) hide show
  1. package/dist/@types/core/cache.d.ts +3 -0
  2. package/dist/@types/core/cache.d.ts.map +1 -1
  3. package/dist/@types/core/errors.d.ts +2 -1
  4. package/dist/@types/core/errors.d.ts.map +1 -1
  5. package/dist/@types/core/events.d.ts +2 -0
  6. package/dist/@types/core/events.d.ts.map +1 -1
  7. package/dist/@types/core/live.d.ts +8 -4
  8. package/dist/@types/core/live.d.ts.map +1 -1
  9. package/dist/@types/core/query.d.ts +1 -1
  10. package/dist/@types/core/query.d.ts.map +1 -1
  11. package/dist/@types/domains/channel.d.ts +1 -0
  12. package/dist/@types/domains/channel.d.ts.map +1 -1
  13. package/dist/@types/domains/message.d.ts +13 -6
  14. package/dist/@types/domains/message.d.ts.map +1 -1
  15. package/dist/@types/domains/post.d.ts +10 -3
  16. package/dist/@types/domains/post.d.ts.map +1 -1
  17. package/dist/@types/domains/user.d.ts +1 -0
  18. package/dist/@types/domains/user.d.ts.map +1 -1
  19. package/dist/@types/index.d.ts +2 -2
  20. package/dist/@types/index.d.ts.map +1 -1
  21. package/dist/cache/api/isInTombstone.d.ts +18 -0
  22. package/dist/cache/api/isInTombstone.d.ts.map +1 -0
  23. package/dist/cache/api/pushToTombstone.d.ts +17 -0
  24. package/dist/cache/api/pushToTombstone.d.ts.map +1 -0
  25. package/dist/cache/utils/index.d.ts +17 -0
  26. package/dist/cache/utils/index.d.ts.map +1 -1
  27. package/dist/channel/api/queryChannelMembers.d.ts +1 -1
  28. package/dist/channel/api/queryChannelMembers.d.ts.map +1 -1
  29. package/dist/channel/api/queryChannels.d.ts +1 -1
  30. package/dist/channel/api/queryChannels.d.ts.map +1 -1
  31. package/dist/channel/api/tests/queryChannelMembers.test.d.ts +2 -0
  32. package/dist/channel/api/tests/queryChannelMembers.test.d.ts.map +1 -0
  33. package/dist/channel/api/tests/queryChannels.test.d.ts +2 -0
  34. package/dist/channel/api/tests/queryChannels.test.d.ts.map +1 -0
  35. package/dist/channel/events/index.d.ts +2 -0
  36. package/dist/channel/events/index.d.ts.map +1 -1
  37. package/dist/channel/events/onChannelMemberBanned.d.ts +17 -0
  38. package/dist/channel/events/onChannelMemberBanned.d.ts.map +1 -0
  39. package/dist/channel/events/onChannelMemberUnbanned.d.ts +17 -0
  40. package/dist/channel/events/onChannelMemberUnbanned.d.ts.map +1 -0
  41. package/dist/channel/events/tests/onChannelMemberBanned.test.d.ts +2 -0
  42. package/dist/channel/events/tests/onChannelMemberBanned.test.d.ts.map +1 -0
  43. package/dist/channel/events/tests/onChannelMemberUnbanned.test.d.ts +2 -0
  44. package/dist/channel/events/tests/onChannelMemberUnbanned.test.d.ts.map +1 -0
  45. package/dist/channel/observers/liveChannelMembers.d.ts.map +1 -1
  46. package/dist/channel/observers/liveChannels.d.ts.map +1 -1
  47. package/dist/channel/{tests/utils → utils/tests}/prepareChannelPayload.test.d.ts +0 -0
  48. package/dist/channel/utils/tests/prepareChannelPayload.test.d.ts.map +1 -0
  49. package/dist/core/events.d.ts +3 -3
  50. package/dist/core/events.d.ts.map +1 -1
  51. package/dist/core/query/filtering.d.ts +11 -0
  52. package/dist/core/query/filtering.d.ts.map +1 -1
  53. package/dist/core/query/query.d.ts.map +1 -1
  54. package/dist/index.cjs.js +963 -507
  55. package/dist/index.esm.js +946 -499
  56. package/dist/index.umd.js +4 -4
  57. package/dist/message/api/createMessage.d.ts +4 -1
  58. package/dist/message/api/createMessage.d.ts.map +1 -1
  59. package/dist/message/api/deleteMessage.d.ts.map +1 -1
  60. package/dist/message/api/getMessage.d.ts +1 -1
  61. package/dist/message/api/getMessage.d.ts.map +1 -1
  62. package/dist/message/api/getMessages.d.ts.map +1 -1
  63. package/dist/message/api/queryMessages.d.ts.map +1 -1
  64. package/dist/message/api/tests/createMessage.test.d.ts +2 -0
  65. package/dist/message/api/tests/createMessage.test.d.ts.map +1 -0
  66. package/dist/message/api/updateMessage.d.ts.map +1 -1
  67. package/dist/message/events/index.d.ts +1 -0
  68. package/dist/message/events/index.d.ts.map +1 -1
  69. package/dist/message/events/onMessageFetched.d.ts +17 -0
  70. package/dist/message/events/onMessageFetched.d.ts.map +1 -0
  71. package/dist/message/observers/index.d.ts +2 -1
  72. package/dist/message/observers/index.d.ts.map +1 -1
  73. package/dist/message/observers/liveMessage.d.ts +18 -0
  74. package/dist/message/observers/liveMessage.d.ts.map +1 -0
  75. package/dist/message/observers/liveMessages.d.ts.map +1 -1
  76. package/dist/message/observers/observeMessage.d.ts.map +1 -1
  77. package/dist/message/tests/api/getMessage.test.d.ts +2 -0
  78. package/dist/message/tests/api/getMessage.test.d.ts.map +1 -0
  79. package/dist/message/tests/observers/liveMessage.test.d.ts +2 -0
  80. package/dist/message/tests/observers/liveMessage.test.d.ts.map +1 -0
  81. package/dist/message/{observers/tests → tests/observers}/liveMessages.test.d.ts +0 -0
  82. package/dist/message/tests/observers/liveMessages.test.d.ts.map +1 -0
  83. package/dist/post/api/deletePost.d.ts.map +1 -1
  84. package/dist/post/api/getPost.d.ts.map +1 -1
  85. package/dist/post/api/updatePost.d.ts +6 -1
  86. package/dist/post/api/updatePost.d.ts.map +1 -1
  87. package/dist/user/api/getUser.d.ts +1 -1
  88. package/dist/user/api/getUser.d.ts.map +1 -1
  89. package/dist/user/api/getUsers.d.ts.map +1 -1
  90. package/dist/user/api/queryUsers.d.ts.map +1 -1
  91. package/dist/user/api/updateUser.d.ts.map +1 -1
  92. package/dist/user/events/index.d.ts +1 -0
  93. package/dist/user/events/index.d.ts.map +1 -1
  94. package/dist/user/events/onUserFetched.d.ts +17 -0
  95. package/dist/user/events/onUserFetched.d.ts.map +1 -0
  96. package/dist/user/observers/index.d.ts +1 -0
  97. package/dist/user/observers/index.d.ts.map +1 -1
  98. package/dist/user/observers/liveUser.d.ts +18 -0
  99. package/dist/user/observers/liveUser.d.ts.map +1 -0
  100. package/dist/user/observers/observeUser.d.ts.map +1 -1
  101. package/dist/user/tests/api/getUser.test.d.ts +2 -0
  102. package/dist/user/tests/api/getUser.test.d.ts.map +1 -0
  103. package/dist/user/tests/observers/liveUser.test.d.ts +2 -0
  104. package/dist/user/tests/observers/liveUser.test.d.ts.map +1 -0
  105. package/dist/utils/constants.d.ts +3 -0
  106. package/dist/utils/constants.d.ts.map +1 -1
  107. package/dist/utils/tests/dummy/channel.d.ts +15 -0
  108. package/dist/utils/tests/dummy/channel.d.ts.map +1 -1
  109. package/dist/utils/tests/dummy/message.d.ts +12 -1
  110. package/dist/utils/tests/dummy/message.d.ts.map +1 -1
  111. package/dist/utils/tests/dummy/user.d.ts +10 -0
  112. package/dist/utils/tests/dummy/user.d.ts.map +1 -1
  113. package/package.json +1 -1
  114. package/src/@types/core/cache.ts +4 -0
  115. package/src/@types/core/errors.ts +1 -0
  116. package/src/@types/core/events.ts +2 -0
  117. package/src/@types/core/live.ts +10 -4
  118. package/src/@types/core/query.ts +1 -1
  119. package/src/@types/domains/channel.ts +6 -0
  120. package/src/@types/domains/message.ts +13 -10
  121. package/src/@types/domains/post.ts +10 -3
  122. package/src/@types/domains/user.ts +8 -0
  123. package/src/@types/index.ts +2 -2
  124. package/src/cache/api/index.ts +2 -1
  125. package/src/cache/api/isInTombstone.ts +41 -0
  126. package/src/cache/api/pushToTombstone.ts +33 -0
  127. package/src/cache/utils/index.ts +22 -0
  128. package/src/channel/api/queryChannelMembers.ts +53 -5
  129. package/src/channel/api/queryChannels.ts +31 -4
  130. package/src/channel/api/tests/queryChannelMembers.test.ts +115 -0
  131. package/src/channel/api/tests/queryChannels.test.ts +101 -0
  132. package/src/channel/events/index.ts +5 -0
  133. package/src/channel/events/onChannelMemberBanned.ts +36 -0
  134. package/src/channel/events/onChannelMemberUnbanned.ts +36 -0
  135. package/src/channel/events/tests/onChannelMemberBanned.test.ts +58 -0
  136. package/src/channel/events/tests/onChannelMemberUnbanned.test.ts +58 -0
  137. package/src/channel/observers/liveChannelMembers.ts +28 -4
  138. package/src/channel/observers/liveChannels.ts +7 -1
  139. package/src/channel/observers/tests/liveChannelMembers.test.ts +53 -11
  140. package/src/channel/observers/tests/liveChannels.test.ts +50 -20
  141. package/src/channel/{tests/utils → utils/tests}/prepareChannelPayload.test.ts +1 -1
  142. package/src/core/events.ts +1 -0
  143. package/src/core/query/filtering.ts +49 -0
  144. package/src/core/query/query.ts +9 -6
  145. package/src/core/tests/query/query.test.ts +12 -2
  146. package/src/message/api/createMessage.ts +15 -9
  147. package/src/message/api/deleteMessage.ts +2 -1
  148. package/src/message/api/getMessage.ts +32 -4
  149. package/src/message/api/getMessages.ts +6 -1
  150. package/src/message/api/queryMessages.ts +3 -0
  151. package/src/message/api/tests/createMessage.test.ts +76 -0
  152. package/src/message/api/updateMessage.ts +2 -0
  153. package/src/message/events/index.ts +1 -0
  154. package/src/message/events/onMessageFetched.ts +30 -0
  155. package/src/message/observers/index.ts +2 -1
  156. package/src/message/observers/liveMessage.ts +100 -0
  157. package/src/message/observers/liveMessages.ts +3 -1
  158. package/src/message/observers/observeMessage.ts +4 -0
  159. package/src/message/tests/api/getMessage.test.ts +96 -0
  160. package/src/message/tests/observers/liveMessage.test.ts +248 -0
  161. package/src/message/{observers/tests → tests/observers}/liveMessages.test.ts +29 -3
  162. package/src/post/api/deletePost.ts +4 -3
  163. package/src/post/api/getPost.ts +8 -4
  164. package/src/post/api/updatePost.ts +6 -1
  165. package/src/user/api/getUser.ts +34 -6
  166. package/src/user/api/getUsers.ts +3 -0
  167. package/src/user/api/queryUsers.ts +3 -0
  168. package/src/user/api/updateUser.ts +3 -0
  169. package/src/user/events/index.ts +1 -0
  170. package/src/user/events/onUserFetched.ts +19 -0
  171. package/src/user/observers/index.ts +1 -0
  172. package/src/user/observers/liveUser.ts +89 -0
  173. package/src/user/observers/observeUser.ts +2 -0
  174. package/src/user/tests/api/getUser.test.ts +112 -0
  175. package/src/user/tests/observers/liveUser.test.ts +224 -0
  176. package/src/utils/constants.ts +5 -0
  177. package/src/utils/tests/dummy/channel.ts +43 -0
  178. package/src/utils/tests/dummy/message.ts +16 -2
  179. package/src/utils/tests/dummy/user.ts +11 -0
  180. package/dist/channel/tests/utils/prepareChannelPayload.test.d.ts.map +0 -1
  181. package/dist/message/observers/tests/liveMessages.test.d.ts.map +0 -1
@@ -0,0 +1,58 @@
1
+ import { disableCache, enableCache } from '~/cache/api';
2
+
3
+ import {
4
+ client,
5
+ connectClient,
6
+ disconnectClient,
7
+ channel11,
8
+ channelUser1,
9
+ channelUser3,
10
+ } from '~/utils/tests';
11
+
12
+ import { onChannelMemberUnbanned } from '../onChannelMemberUnbanned';
13
+
14
+ describe('onChannelMemberBanned event', () => {
15
+ beforeAll(async () => {
16
+ await connectClient();
17
+ });
18
+
19
+ afterAll(async () => {
20
+ await disconnectClient();
21
+ });
22
+
23
+ beforeEach(async () => {
24
+ enableCache();
25
+ });
26
+
27
+ afterEach(disableCache);
28
+
29
+ const channelPayload: Amity.ChannelMembershipPayload = {
30
+ channelUsers: [channelUser1, channelUser3],
31
+ channels: [channel11],
32
+ files: [],
33
+ users: [],
34
+ };
35
+
36
+ test('it should call callback with channel and unbanned member', () => {
37
+ const callback = jest.fn();
38
+
39
+ const unsub = onChannelMemberUnbanned(callback);
40
+ client.emitter.emit('v3.channel.didUnban', channelPayload);
41
+
42
+ unsub();
43
+
44
+ expect(callback).toHaveBeenCalled();
45
+ expect(callback).toHaveBeenCalledWith(channel11, channelUser3);
46
+ });
47
+
48
+ test('it should return an Unsubscriber', () => {
49
+ const callback = jest.fn();
50
+
51
+ const unsub = onChannelMemberUnbanned(callback);
52
+ unsub();
53
+
54
+ client.emitter.emit('v3.channel.didUnban', channelPayload);
55
+
56
+ expect(callback).not.toHaveBeenCalled();
57
+ });
58
+ });
@@ -16,7 +16,12 @@ import {
16
16
  COLLECTION_DEFAULT_PAGINATION_LIMIT,
17
17
  ENABLE_CACHE_MESSAGE,
18
18
  } from '~/utils/constants';
19
- import { onChannelMemberAdded, onChannelMemberRemoved } from '../events';
19
+ import {
20
+ onChannelMemberAdded,
21
+ onChannelMemberRemoved,
22
+ onChannelMemberBanned,
23
+ onChannelMemberUnbanned,
24
+ } from '../events';
20
25
  import { queryChannelMembers } from '../api';
21
26
 
22
27
  /**
@@ -67,7 +72,22 @@ export const liveChannelMembers = (
67
72
  .map(({ data }) => data) ?? [];
68
73
 
69
74
  channelMembers = filterByPropEquality(channelMembers, 'roles', params.roles);
70
- channelMembers = filterByPropInclusion(channelMembers, 'membership', params.memberships);
75
+
76
+ if (params.memberships) {
77
+ /*
78
+ * even though membership includes muted as a possible value
79
+ * when querying the server.
80
+ * Muted is specified under seperarte property namely isMuted
81
+ * Hence why I've seperately checked for it's equality
82
+ */
83
+ if (params.memberships.some(m => ['member', 'banned'].includes(m))) {
84
+ channelMembers = filterByPropInclusion(channelMembers, 'membership', params.memberships);
85
+ }
86
+
87
+ if (params.memberships.includes('muted')) {
88
+ channelMembers = filterByPropEquality(channelMembers, 'isMuted', true);
89
+ }
90
+ }
71
91
 
72
92
  if (params.search) {
73
93
  const containsMatcher = new RegExp(params.search);
@@ -91,7 +111,7 @@ export const liveChannelMembers = (
91
111
 
92
112
  const realtimeRouter = (
93
113
  channelMember: Amity.Membership<'channel'>,
94
- action: 'onMemberAdded' | 'onMemberRemoved',
114
+ action: Amity.ChannelMemberActionType,
95
115
  ) => {
96
116
  const collection = pullFromCache<Amity.ChannelMembersLiveCollectionCache>(cacheKey)?.data;
97
117
  if (!collection) return;
@@ -103,7 +123,9 @@ export const liveChannelMembers = (
103
123
 
104
124
  if (action === 'onMemberAdded') {
105
125
  collection.data = [...new Set([channelMemberCacheId, ...collection.data])];
106
- } else {
126
+ }
127
+
128
+ if (action === 'onMemberRemoved') {
107
129
  collection.data = collection.data.filter(m => m !== channelMemberCacheId);
108
130
  }
109
131
 
@@ -148,6 +170,8 @@ export const liveChannelMembers = (
148
170
  disposers.push(
149
171
  onChannelMemberAdded((_, member) => realtimeRouter(member, 'onMemberAdded')),
150
172
  onChannelMemberRemoved((_, member) => realtimeRouter(member, 'onMemberRemoved')),
173
+ onChannelMemberBanned((_, member) => realtimeRouter(member, 'onChannelMemberBanned')),
174
+ onChannelMemberUnbanned((_, member) => realtimeRouter(member, 'onChannelMemberUnbanned')),
151
175
  );
152
176
 
153
177
  onFetch();
@@ -7,6 +7,7 @@ import {
7
7
  runQuery,
8
8
  queryOptions,
9
9
  filterByPropEquality,
10
+ filterByChannelMembership,
10
11
  sortByLastCreated,
11
12
  sortByDisplayName,
12
13
  sortByFirstCreated,
@@ -43,7 +44,7 @@ export const liveChannels = (
43
44
  callback: Amity.LiveCollectionCallback<Amity.Channel>,
44
45
  config?: Amity.LiveCollectionConfig,
45
46
  ) => {
46
- const { log, cache } = getActiveClient();
47
+ const { log, cache, userId } = getActiveClient();
47
48
 
48
49
  if (!cache) {
49
50
  console.log(ENABLE_CACHE_MESSAGE);
@@ -82,6 +83,11 @@ export const liveChannels = (
82
83
  channels = channels.filter(c => !c.tags?.some(t => params.excludeTags?.includes(t)));
83
84
  }
84
85
 
86
+ // userId is required to be able to filter channel by current active user
87
+ if (params.membership && userId) {
88
+ channels = filterByChannelMembership(channels, params.membership, userId);
89
+ }
90
+
85
91
  const sortBy = params.sortBy || 'lastCreated';
86
92
 
87
93
  if (sortBy === 'lastCreated' || sortBy === 'firstCreated') {
@@ -6,6 +6,7 @@ import {
6
6
  channelUser1,
7
7
  channelUser4,
8
8
  channelUserQueryResponse,
9
+ channelUserQueryResponsePage3,
9
10
  channelUserQueryResponsePage2,
10
11
  channel11,
11
12
  pause,
@@ -57,24 +58,34 @@ describe('liveChannelMembers', () => {
57
58
  const filters: [string, Amity.ChannelMembersLiveCollection, Amity.Membership<'channel'>[]][] = [
58
59
  [
59
60
  'roles',
60
- { channelId, roles: channelUserQueryResponse.data.channelUsers[1].roles },
61
- [channelUserQueryResponse.data.channelUsers[1]],
61
+ { channelId, roles: channelUserQueryResponsePage3.data.channelUsers[0].roles },
62
+ [channelUserQueryResponsePage3.data.channelUsers[0]],
62
63
  ],
63
64
  [
64
65
  'searched term',
65
66
  { channelId, search: 'use' },
66
- [channelUserQueryResponse.data.channelUsers[1]],
67
+ [channelUserQueryResponsePage3.data.channelUsers[0]],
67
68
  ],
68
69
  [
69
- 'memberships',
70
+ 'member',
70
71
  { channelId, memberships: ['member'] },
71
- [channelUserQueryResponse.data.channelUsers[0]],
72
+ [channelUserQueryResponsePage3.data.channelUsers[0]],
73
+ ],
74
+ [
75
+ 'muted members',
76
+ { channelId, memberships: ['muted'] },
77
+ [channelUserQueryResponsePage3.data.channelUsers[1]],
78
+ ],
79
+ [
80
+ 'banned members',
81
+ { channelId, memberships: ['banned'] },
82
+ [channelUserQueryResponsePage3.data.channelUsers[1]],
72
83
  ],
73
84
  ];
74
85
 
75
86
  test.each(filters)('it should filter channelUsers by %s', async (filter, params, expected) => {
76
87
  const callback = jest.fn();
77
- client.http.get = jest.fn().mockResolvedValue(channelUserQueryResponse);
88
+ client.http.get = jest.fn().mockResolvedValue(channelUserQueryResponsePage3);
78
89
 
79
90
  liveChannelMembers(params, callback);
80
91
  await pause();
@@ -131,6 +142,16 @@ describe('liveChannelMembers', () => {
131
142
  'v3.channel.didRemoveUsers',
132
143
  channelUser1,
133
144
  ],
145
+ [
146
+ 'it should update channel memeber membership on onChannelMemberBanned',
147
+ 'v3.channel.didBan',
148
+ { ...channelUser1, membership: 'banned', isBanned: true },
149
+ ],
150
+ [
151
+ 'it should update channel memeber membership on onChannelMemberUnbanned',
152
+ 'v3.channel.didUnban',
153
+ { ...channelUser1, membership: 'none', isBanned: false },
154
+ ],
134
155
  ];
135
156
 
136
157
  test.each(events)('%s', async (test, event, channelUser) => {
@@ -153,11 +174,32 @@ describe('liveChannelMembers', () => {
153
174
 
154
175
  snapshot.loading = false;
155
176
  // get data based on event
156
- if (event === 'v3.channel.didAddUsers') {
157
- snapshot.data = [channelUser, ...payload.channelUsers];
158
- } else {
159
- const deletePayload = payload.channelUsers.filter(u => u.userId !== channelUser.userId);
160
- snapshot.data = deletePayload;
177
+ switch (event) {
178
+ case 'v3.channel.didAddUsers':
179
+ snapshot.data = [channelUser, ...payload.channelUsers];
180
+ break;
181
+
182
+ case 'v3.channel.didBan': {
183
+ const bannedUserIdx = payload.channelUsers.findIndex(u => u.userId === channelUser.userId);
184
+ payload.channelUsers[bannedUserIdx] = channelUser;
185
+
186
+ snapshot.data = payload.channelUsers;
187
+ break;
188
+ }
189
+
190
+ case 'v3.channel.didUnban': {
191
+ const unbannedUserIdx = payload.channelUsers.findIndex(
192
+ u => u.userId === channelUser.userId,
193
+ );
194
+ payload.channelUsers[unbannedUserIdx] = channelUser;
195
+
196
+ snapshot.data = payload.channelUsers;
197
+ break;
198
+ }
199
+
200
+ default:
201
+ // delete payload
202
+ snapshot.data = payload.channelUsers.filter(u => u.userId !== channelUser.userId);
161
203
  }
162
204
 
163
205
  expect(callback).toHaveBeenNthCalledWith(3, expect.objectContaining(snapshot));
@@ -1,4 +1,5 @@
1
- import { disableCache, enableCache } from '~/cache/api';
1
+ import { disableCache, enableCache, pushToCache } from '~/cache/api';
2
+ import { getResolver } from '~/core/model';
2
3
 
3
4
  import { liveChannels } from '../liveChannels';
4
5
  import { queryChannels } from '~/channel/api';
@@ -6,6 +7,7 @@ import { prepareChannelPayload } from '../../utils/prepareChannelPayload';
6
7
 
7
8
  import {
8
9
  client,
10
+ pause,
9
11
  connectClient,
10
12
  disconnectClient,
11
13
  channel11,
@@ -49,7 +51,7 @@ describe('liveChannels', () => {
49
51
  // check if cache data returned (should be empty)
50
52
  expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
51
53
 
52
- await expect(queryChannels()).resolves.toBeTruthy();
54
+ await pause();
53
55
 
54
56
  expect(callback).toHaveBeenCalledTimes(2);
55
57
 
@@ -63,33 +65,61 @@ describe('liveChannels', () => {
63
65
  });
64
66
 
65
67
  // table for channel filters
66
- const filters: [string, Amity.ChannelLiveCollection][] = [
68
+ const filters: [string, Amity.ChannelLiveCollection, number?][] = [
67
69
  ['deleted', { isDeleted: false }],
68
70
  ['tagged', { tags: ['tag2'] }],
69
71
  ['out excluded tags', { excludeTags: ['tag1'] }],
72
+ ['member', { membership: 'member' }],
73
+ ['not member', { membership: 'notMember' }, 1],
74
+ ['all membership', { membership: 'all' }],
70
75
  ];
71
76
 
72
- test.each(filters)('it should filter %s channels', async (filter, params) => {
73
- const callback = jest.fn();
74
- client.http.get = jest.fn().mockResolvedValue(channelQueryResponse);
77
+ test.each(filters)(
78
+ 'it should filter by %s channels',
79
+ async (filter, params, expectedChannelIdx = 0) => {
80
+ const callback = jest.fn();
81
+ client.http.get = jest.fn().mockResolvedValue(channelQueryResponse);
75
82
 
76
- liveChannels(params, callback);
83
+ const payload = prepareChannelPayload(channelQueryResponse.data);
84
+ let snapshotData = [payload.channels[expectedChannelIdx]];
77
85
 
78
- expect(callback).toHaveBeenCalledTimes(1);
86
+ // add channel users to cache for testing membership filter
87
+ if (params.membership) {
88
+ pushToCache(
89
+ [
90
+ 'channelUsers',
91
+ 'get',
92
+ getResolver('channelUsers')({
93
+ channelId: channelQueryResponse.data.channels[0].channelId,
94
+ userId: channelUser1.userId,
95
+ }),
96
+ ],
97
+ channelUser1,
98
+ );
79
99
 
80
- const snapshot = getSnapshot();
81
- // check if cache data returned (should be empty)
82
- expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
100
+ if (params.membership === 'all') {
101
+ snapshotData = payload.channels;
102
+ }
103
+ }
83
104
 
84
- await expect(queryChannels()).resolves.toBeTruthy();
105
+ liveChannels(params, callback);
85
106
 
86
- expect(callback).toHaveBeenCalledTimes(2);
107
+ expect(callback).toHaveBeenCalledTimes(1);
87
108
 
88
- snapshot.loading = false;
89
- snapshot.data = [channelQueryResponse.data.channels[0]];
109
+ const snapshot = getSnapshot();
110
+ // check if cache data returned (should be empty)
111
+ expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
90
112
 
91
- expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
92
- });
113
+ await pause();
114
+
115
+ expect(callback).toHaveBeenCalledTimes(2);
116
+
117
+ snapshot.loading = false;
118
+ snapshot.data = snapshotData;
119
+
120
+ expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
121
+ },
122
+ );
93
123
 
94
124
  test('it should return method to fetch next page', async () => {
95
125
  const callback = jest.fn();
@@ -102,7 +132,7 @@ describe('liveChannels', () => {
102
132
 
103
133
  expect(callback).toHaveBeenCalled();
104
134
 
105
- await expect(queryChannels()).resolves.toBeTruthy();
135
+ await pause();
106
136
 
107
137
  expect(callback.mock.lastCall).toHaveLength(1);
108
138
 
@@ -113,7 +143,7 @@ describe('liveChannels', () => {
113
143
 
114
144
  onNextPage();
115
145
 
116
- await expect(queryChannels()).resolves.toBeTruthy();
146
+ await pause();
117
147
 
118
148
  // 4 -> becuase 1 local & server call each per call (2)
119
149
  expect(callback).toHaveBeenCalledTimes(4);
@@ -146,7 +176,7 @@ describe('liveChannels', () => {
146
176
 
147
177
  liveChannels({}, callback);
148
178
 
149
- await expect(queryChannels()).resolves.toBeTruthy();
179
+ await pause();
150
180
  expect(callback).toHaveBeenCalledTimes(2);
151
181
 
152
182
  client.emitter.emit(event, {
@@ -9,7 +9,7 @@ import {
9
9
  channelUser2,
10
10
  } from '~/utils/tests';
11
11
 
12
- import { prepareChannelPayload } from '../../utils';
12
+ import { prepareChannelPayload } from '../prepareChannelPayload';
13
13
 
14
14
  beforeAll(async () => {
15
15
  await connectClient();
@@ -23,6 +23,7 @@ const WS_EVENTS = [
23
23
  'v3.channel.didBan',
24
24
  'v3.channel.didUnban',
25
25
 
26
+ 'v3.message.didFetch',
26
27
  'v3.message.didCreate',
27
28
  'v3.message.didUpdate',
28
29
  'v3.message.didDelete',
@@ -1,3 +1,5 @@
1
+ import { getResolver } from '~/core/model';
2
+ import { pullFromCache } from '~/cache/api';
1
3
  // Note:
2
4
  // this file should contain a suite of filtering utilities to help the
3
5
  // local version of the query functions.
@@ -23,3 +25,50 @@ export const filterByPropInclusion = <T = Record<string, unknown>>(
23
25
  key: keyof T,
24
26
  value: any[] | undefined,
25
27
  ) => (value !== undefined ? collection.filter(item => value.includes(item[key])) : collection);
28
+
29
+ /**
30
+ * Filter a channel collection by membership of the userId
31
+ *
32
+ * @param collection the channel collection to filter
33
+ * @param membership the membership to be filtered by
34
+ * @param userId user id to be filtered by
35
+ * @returns a filtered collection with items only matching the criteria
36
+ *
37
+ * @hidden
38
+ */
39
+ export const filterByChannelMembership = (
40
+ collection: Amity.Channel[],
41
+ membership: Amity.ChannelLiveCollection['membership'],
42
+ userId: Amity.Membership<'channel'>['userId'],
43
+ ): Amity.Channel[] => {
44
+ if (membership === 'all') {
45
+ return collection;
46
+ }
47
+
48
+ return collection.filter(c => {
49
+ // get resolver for the channel by user
50
+ const channelUserCacheKey = getResolver('channelUsers')({
51
+ channelId: c.channelId,
52
+ userId,
53
+ });
54
+
55
+ const channelUser = pullFromCache<Amity.Membership<'channel'>>([
56
+ 'channelUsers',
57
+ 'get',
58
+ channelUserCacheKey,
59
+ ])?.data;
60
+
61
+ // if channel user is defined for user, user is a member
62
+ if (membership === 'member' && channelUser) {
63
+ return true;
64
+ }
65
+
66
+ // if channel user by user id not found, user is not a member of that
67
+ // channel
68
+ if (membership === 'notMember' && !channelUser) {
69
+ return true;
70
+ }
71
+
72
+ return false;
73
+ });
74
+ };
@@ -2,6 +2,8 @@
2
2
  import { isPaged } from './paging';
3
3
  import { identifyModelKey } from '~/core/model';
4
4
 
5
+ import { CACHE_LIFESPAN } from '~/cache/utils';
6
+
5
7
  /**
6
8
  * Type guard to check and cast that a given async function is has the ".locally" property
7
9
  *
@@ -71,7 +73,7 @@ export const isLocal = (model?: any): model is Amity.Local => {
71
73
  *
72
74
  * @hidden
73
75
  */
74
- export const isFresh = (model?: Amity.Cachable, lifeSpan = 0) => {
76
+ export const isFresh = (model?: Amity.Cachable, lifeSpan = CACHE_LIFESPAN) => {
75
77
  return Date.now() - (model?.cachedAt ?? 0) <= lifeSpan;
76
78
  };
77
79
 
@@ -111,12 +113,11 @@ export const createQuery = <Args extends any[], Returned extends any>(
111
113
  */
112
114
  export const queryOptions = (
113
115
  policy: Amity.QueryPolicy,
114
- lifeSpan: number = 2 * 60 * 1000, // 2mn
116
+ lifeSpan: number = CACHE_LIFESPAN, // 1mn
115
117
  ): Amity.QueryOptions => {
116
118
  if (policy === 'cache_only') return { lifeSpan: Infinity };
117
- if (policy === 'server_only') return { lifeSpan: 0 };
118
119
 
119
- return { lifeSpan };
120
+ return { lifeSpan: lifeSpan < CACHE_LIFESPAN ? CACHE_LIFESPAN : lifeSpan };
120
121
  };
121
122
 
122
123
  /**
@@ -168,8 +169,10 @@ export const runQuery = <Args extends any[], Returned extends any>(
168
169
  ) => {
169
170
  let local: Returned | undefined;
170
171
 
172
+ const { lifeSpan } = queryOptions('cache_then_server', options.lifeSpan);
173
+
171
174
  // offline first
172
- if (isOffline(func) && options.lifeSpan !== 0) {
175
+ if (isOffline(func)) {
173
176
  try {
174
177
  local = isMutator(func) ? func.optimistically(...args) : func.locally(...args);
175
178
  } catch (error) {
@@ -182,7 +185,7 @@ export const runQuery = <Args extends any[], Returned extends any>(
182
185
  );
183
186
  }
184
187
 
185
- const shouldAbort = isCachable(local) && isFresh(local, options.lifeSpan);
188
+ const shouldAbort = isCachable(local) && isFresh(local, lifeSpan);
186
189
 
187
190
  callback?.(
188
191
  createSnapshot<Returned>(local, {
@@ -7,6 +7,7 @@ import {
7
7
  isFresh,
8
8
  queryOptions,
9
9
  } from '~/core/query';
10
+ import { CACHE_LIFESPAN } from '~/cache/utils';
10
11
 
11
12
  describe('Core/Query', () => {
12
13
  type isFetcherMockType = jest.Mock<any, any> & {
@@ -90,8 +91,17 @@ describe('Core/Query', () => {
90
91
  });
91
92
 
92
93
  describe('queryOptions', () => {
93
- test('should return lifespan 0 if server only', () => {
94
- expect(queryOptions('server_only').lifeSpan).toBe(0);
94
+ test('should return minimum lifeSpan', () => {
95
+ expect(queryOptions('cache_then_server').lifeSpan).toBe(CACHE_LIFESPAN);
96
+ });
97
+
98
+ test('should return minimum lifeSpan if provided lifeSpan is less than cache lifeSpan', () => {
99
+ expect(queryOptions('cache_then_server', 0).lifeSpan).toBe(CACHE_LIFESPAN);
100
+ });
101
+
102
+ test('should return provided lifeSpan if provided lifeSpan is more than cache lifeSpan', () => {
103
+ const value = CACHE_LIFESPAN * 2;
104
+ expect(queryOptions('cache_then_server', value).lifeSpan).toBe(value);
95
105
  });
96
106
 
97
107
  test('should return lifespan Infinity if cache only', () => {
@@ -1,9 +1,9 @@
1
+ import { uuid } from '~/core/uuid';
2
+ import { fireEvent } from '~/core/events';
1
3
  import { getActiveClient } from '~/client/api';
2
-
3
- import { upsertInCache, pullFromCache, pushToCache } from '~/cache/api';
4
4
  import { ingestInCache } from '~/cache/api/ingestInCache';
5
- import { fireEvent } from '~/core/events';
6
- import { uuid } from '~/core/uuid';
5
+ import { UNSYNCED_OBJECT_CACHED_AT_VALUE } from '~/utils/constants';
6
+ import { upsertInCache, pullFromCache, pushToCache } from '~/cache/api';
7
7
 
8
8
  /**
9
9
  * ```js
@@ -25,8 +25,11 @@ import { uuid } from '~/core/uuid';
25
25
  export const createMessage = async <T extends Amity.MessageContentType>(
26
26
  bundle: Pick<
27
27
  Amity.Message<T>,
28
- 'channelId' | 'parentId' | 'type' | 'data' | 'tags' | 'metadata' | 'mentionees'
29
- >,
28
+ 'channelId' | 'parentId' | 'type' | 'tags' | 'metadata' | 'mentionees'
29
+ > & {
30
+ data?: Amity.Message<T>['data'];
31
+ fileId?: Amity.Message<T>['fileId'];
32
+ },
30
33
  ): Promise<Amity.Cached<Amity.Message>> => {
31
34
  const client = getActiveClient();
32
35
 
@@ -36,10 +39,11 @@ export const createMessage = async <T extends Amity.MessageContentType>(
36
39
 
37
40
  const cachedAt = client.cache && Date.now();
38
41
  if (client.cache) ingestInCache(data, { cachedAt });
39
- // no need for event, fired by server
40
42
 
41
43
  const { messages } = data;
42
44
 
45
+ fireEvent('v3.message.didCreate', { messages });
46
+
43
47
  return {
44
48
  data: messages[0],
45
49
  cachedAt,
@@ -102,11 +106,13 @@ createMessage.optimistically = (
102
106
  ...bundleWithMessageId,
103
107
  } as Amity.Message;
104
108
 
105
- pushToCache(['message', 'get', message.messageId], message, { cachedAt: -1 });
109
+ const cachedAt = UNSYNCED_OBJECT_CACHED_AT_VALUE;
110
+
111
+ pushToCache(['message', 'get', message.messageId], message, { cachedAt });
106
112
  fireEvent('v3.message.didCreate', { messages: [message] });
107
113
 
108
114
  return {
115
+ cachedAt,
109
116
  data: message,
110
- cachedAt: -1,
111
117
  };
112
118
  };
@@ -31,7 +31,8 @@ export const deleteMessage = async (
31
31
  );
32
32
 
33
33
  const deleted = await getMessage(messageId);
34
- // no need for event, fired by server
34
+
35
+ fireEvent('v3.message.didDelete', { messages: [deleted.data] });
35
36
 
36
37
  return deleted.data;
37
38
  };
@@ -1,8 +1,13 @@
1
1
  import { getActiveClient } from '~/client/api';
2
2
 
3
+ import { fireEvent } from '~/core/events';
3
4
  import { pullFromCache } from '~/cache/api';
4
5
  import { ingestInCache } from '~/cache/api/ingestInCache';
5
6
 
7
+ import { isInTombstone } from '~/cache/api/isInTombstone';
8
+ import { checkIfShouldGoesToTombstone } from '~/cache/utils';
9
+ import { pushToTombstone } from '~/cache/api/pushToTombstone';
10
+
6
11
  /**
7
12
  * ```js
8
13
  * import { getMessage } from '@amityco/ts-sdk'
@@ -19,20 +24,43 @@ import { ingestInCache } from '~/cache/api/ingestInCache';
19
24
  */
20
25
  export const getMessage = async (
21
26
  messageId: Amity.Message['messageId'],
27
+ isLive = false,
22
28
  ): Promise<Amity.Cached<Amity.Message>> => {
23
29
  const client = getActiveClient();
24
30
  client.log('message/getMessage', messageId);
25
31
 
26
- // API-FIX: endpoint should not be /list, parameters should be querystring.
27
- const { data } = await client.http.get<Amity.MessagePayload>(
28
- `/api/v3/messages/${encodeURIComponent(messageId)}`,
29
- );
32
+ if (!isLive) {
33
+ console.log(
34
+ 'getMessage will be deprecated on 15th November 2022, please use liveMessage instead.',
35
+ );
36
+ }
37
+
38
+ isInTombstone('message', messageId);
39
+
40
+ let data: Amity.MessagePayload;
41
+
42
+ try {
43
+ // API-FIX: endpoint should not be /list, parameters should be querystring.
44
+ const response = await client.http.get<Amity.MessagePayload>(
45
+ `/api/v3/messages/${encodeURIComponent(messageId)}`,
46
+ );
47
+
48
+ data = response.data;
49
+ } catch (error) {
50
+ if (checkIfShouldGoesToTombstone((error as Amity.ErrorResponse)?.code)) {
51
+ pushToTombstone('message', messageId);
52
+ }
53
+
54
+ throw error;
55
+ }
30
56
 
31
57
  const cachedAt = client.cache && Date.now();
32
58
  if (client.cache) ingestInCache(data, { cachedAt });
33
59
 
34
60
  const { messages } = data;
35
61
 
62
+ fireEvent('v3.message.didFetch', { messages });
63
+
36
64
  return {
37
65
  data: messages.find(message => message.messageId === messageId)!,
38
66
  cachedAt,