@amityco/ts-sdk 0.0.1-beta.37 → 0.0.1-beta.38

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 (179) hide show
  1. package/.eslintrc.json +3 -5
  2. package/dist/@types/core/events.d.ts +1 -0
  3. package/dist/@types/core/events.d.ts.map +1 -1
  4. package/dist/@types/domains/category.d.ts +7 -0
  5. package/dist/@types/domains/category.d.ts.map +1 -1
  6. package/dist/@types/domains/community.d.ts +12 -0
  7. package/dist/@types/domains/community.d.ts.map +1 -1
  8. package/dist/@types/global.d.ts +5 -0
  9. package/dist/@types/global.d.ts.map +1 -1
  10. package/dist/category/api/queryCategories.d.ts +1 -5
  11. package/dist/category/api/queryCategories.d.ts.map +1 -1
  12. package/dist/category/observers/index.d.ts +2 -0
  13. package/dist/category/observers/index.d.ts.map +1 -0
  14. package/dist/category/observers/liveCategories.d.ts +18 -0
  15. package/dist/category/observers/liveCategories.d.ts.map +1 -0
  16. package/dist/category/observers/tests/liveCategories.test.d.ts +2 -0
  17. package/dist/category/observers/tests/liveCategories.test.d.ts.map +1 -0
  18. package/dist/channel/api/tests/getChannels.test.d.ts +2 -0
  19. package/dist/channel/api/tests/getChannels.test.d.ts.map +1 -0
  20. package/dist/channel/api/tests/leaveChannel.test.d.ts +2 -0
  21. package/dist/channel/api/tests/leaveChannel.test.d.ts.map +1 -0
  22. package/dist/channel/observers/liveChannel.d.ts.map +1 -1
  23. package/dist/client/api/accessTokenExpiryWatcher.d.ts +4 -1
  24. package/dist/client/api/accessTokenExpiryWatcher.d.ts.map +1 -1
  25. package/dist/client/api/getToken.d.ts +3 -1
  26. package/dist/client/api/getToken.d.ts.map +1 -1
  27. package/dist/client/utils/setClientToken.d.ts +3 -3
  28. package/dist/comment/api/deleteComment.d.ts.map +1 -1
  29. package/dist/comment/api/getComment.d.ts.map +1 -1
  30. package/dist/comment/observers/index.d.ts +1 -0
  31. package/dist/comment/observers/index.d.ts.map +1 -1
  32. package/dist/comment/observers/liveComment.d.ts +21 -0
  33. package/dist/comment/observers/liveComment.d.ts.map +1 -0
  34. package/dist/comment/observers/tests/liveComment.test.d.ts +2 -0
  35. package/dist/comment/observers/tests/liveComment.test.d.ts.map +1 -0
  36. package/dist/community/api/queryCommunities.d.ts +1 -10
  37. package/dist/community/api/queryCommunities.d.ts.map +1 -1
  38. package/dist/community/events/index.d.ts +1 -0
  39. package/dist/community/events/index.d.ts.map +1 -1
  40. package/dist/community/events/onCommunityUserChanged.d.ts +17 -0
  41. package/dist/community/events/onCommunityUserChanged.d.ts.map +1 -0
  42. package/dist/community/events/utils.d.ts +1 -0
  43. package/dist/community/events/utils.d.ts.map +1 -1
  44. package/dist/community/observers/index.d.ts +1 -0
  45. package/dist/community/observers/index.d.ts.map +1 -1
  46. package/dist/community/observers/liveCommunities.d.ts +20 -0
  47. package/dist/community/observers/liveCommunities.d.ts.map +1 -0
  48. package/dist/community/observers/tests/liveCommunities.test.d.ts +2 -0
  49. package/dist/community/observers/tests/liveCommunities.test.d.ts.map +1 -0
  50. package/dist/core/events.d.ts +3 -3
  51. package/dist/core/events.d.ts.map +1 -1
  52. package/dist/core/query/sorting.d.ts +6 -0
  53. package/dist/core/query/sorting.d.ts.map +1 -1
  54. package/dist/core/transports/http.d.ts +0 -5
  55. package/dist/core/transports/http.d.ts.map +1 -1
  56. package/dist/file/api/test/createImage.test.d.ts +2 -0
  57. package/dist/file/api/test/createImage.test.d.ts.map +1 -0
  58. package/dist/file/api/test/createVideo.test.d.ts +2 -0
  59. package/dist/file/api/test/createVideo.test.d.ts.map +1 -0
  60. package/dist/index.cjs.js +441 -195
  61. package/dist/index.d.ts +1 -0
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.esm.js +436 -195
  64. package/dist/index.umd.js +4 -4
  65. package/dist/message/observers/liveMessage.d.ts.map +1 -1
  66. package/dist/post/observers/index.d.ts +1 -0
  67. package/dist/post/observers/index.d.ts.map +1 -1
  68. package/dist/post/observers/livePost.d.ts +21 -0
  69. package/dist/post/observers/livePost.d.ts.map +1 -0
  70. package/dist/post/observers/tests/livePost.test.d.ts +2 -0
  71. package/dist/post/observers/tests/livePost.test.d.ts.map +1 -0
  72. package/dist/subChannel/api/tests/deleteSubChannel.test.d.ts +2 -0
  73. package/dist/subChannel/api/tests/deleteSubChannel.test.d.ts.map +1 -0
  74. package/dist/subChannel/api/tests/getSubChannel.test.d.ts +2 -0
  75. package/dist/subChannel/api/tests/getSubChannel.test.d.ts.map +1 -0
  76. package/dist/subChannel/api/tests/getSubChannels.test.d.ts +2 -0
  77. package/dist/subChannel/api/tests/getSubChannels.test.d.ts.map +1 -0
  78. package/dist/subChannel/api/tests/updateSubChannel.test.d.ts +2 -0
  79. package/dist/subChannel/api/tests/updateSubChannel.test.d.ts.map +1 -0
  80. package/dist/subChannel/observers/liveSubChannel.d.ts.map +1 -1
  81. package/dist/user/api/tests/updateUser.test.d.ts +2 -0
  82. package/dist/user/api/tests/updateUser.test.d.ts.map +1 -0
  83. package/dist/user/observers/liveUser.d.ts.map +1 -1
  84. package/dist/utils/liveObject.d.ts +2 -0
  85. package/dist/utils/liveObject.d.ts.map +1 -0
  86. package/dist/utils/tests/dummy/category.d.ts +64 -0
  87. package/dist/utils/tests/dummy/category.d.ts.map +1 -0
  88. package/dist/utils/tests/dummy/channel.d.ts +50 -0
  89. package/dist/utils/tests/dummy/channel.d.ts.map +1 -1
  90. package/dist/utils/tests/dummy/community.d.ts +132 -0
  91. package/dist/utils/tests/dummy/community.d.ts.map +1 -1
  92. package/dist/utils/tests/dummy/file.d.ts +3 -0
  93. package/dist/utils/tests/dummy/file.d.ts.map +1 -1
  94. package/dist/utils/tests/dummy/index.d.ts +1 -0
  95. package/dist/utils/tests/dummy/index.d.ts.map +1 -1
  96. package/dist/utils/tests/dummy/post.d.ts +2 -0
  97. package/dist/utils/tests/dummy/post.d.ts.map +1 -1
  98. package/dist/utils/tests/dummy/subChannel.d.ts +0 -20
  99. package/dist/utils/tests/dummy/subChannel.d.ts.map +1 -1
  100. package/dist/utils/tests/dummy/user.d.ts +18 -0
  101. package/dist/utils/tests/dummy/user.d.ts.map +1 -1
  102. package/dist/utils/tests/liveObject.test.d.ts +2 -0
  103. package/dist/utils/tests/liveObject.test.d.ts.map +1 -0
  104. package/package.json +4 -4
  105. package/src/@types/core/events.ts +1 -0
  106. package/src/@types/domains/category.ts +13 -0
  107. package/src/@types/domains/community.ts +25 -0
  108. package/src/@types/global.ts +5 -0
  109. package/src/category/api/queryCategories.ts +3 -5
  110. package/src/category/observers/index.ts +1 -0
  111. package/src/category/observers/liveCategories.ts +141 -0
  112. package/src/category/observers/tests/liveCategories.test.ts +147 -0
  113. package/src/channel/api/tests/createChannel.test.ts +30 -6
  114. package/src/channel/api/tests/getChannel.test.ts +3 -1
  115. package/src/channel/api/tests/getChannels.test.ts +97 -0
  116. package/src/channel/api/tests/joinChannel.test.ts +62 -3
  117. package/src/channel/api/tests/leaveChannel.test.ts +90 -0
  118. package/src/channel/api/tests/queryChannels.test.ts +38 -1
  119. package/src/channel/observers/liveChannel.ts +8 -80
  120. package/src/client/api/accessTokenExpiryWatcher.ts +7 -5
  121. package/src/client/api/tests/accessTokenExpiryWatcher.test.ts +8 -5
  122. package/src/comment/api/deleteComment.ts +5 -5
  123. package/src/comment/api/getComment.ts +8 -3
  124. package/src/comment/observers/index.ts +1 -0
  125. package/src/comment/observers/liveComment.ts +43 -0
  126. package/src/comment/observers/tests/liveComment.test.ts +95 -0
  127. package/src/community/api/queryCommunities.ts +3 -10
  128. package/src/community/api/test/addCommunityMember.test.ts +14 -2
  129. package/src/community/api/test/joinCommunity.test.ts +14 -2
  130. package/src/community/api/test/leaveCommunity.test.ts +11 -1
  131. package/src/community/api/test/removeCommunityMembers.test.ts +11 -1
  132. package/src/community/events/index.ts +1 -0
  133. package/src/community/events/onCommunityUserChanged.ts +20 -0
  134. package/src/community/events/test/onCommunityJoined.test.ts +1 -1
  135. package/src/community/events/test/onCommunityUserAdded.test.ts +1 -1
  136. package/src/community/events/test/onCommunityUserBanned.test.ts +1 -1
  137. package/src/community/events/test/onCommunityUserUnbaned.test.ts +1 -1
  138. package/src/community/events/test/utils.test.ts +52 -3
  139. package/src/community/events/utils.ts +49 -0
  140. package/src/community/observers/index.ts +1 -0
  141. package/src/community/observers/liveCommunities.ts +188 -0
  142. package/src/community/observers/tests/liveCommunities.test.ts +284 -0
  143. package/src/core/query/sorting.ts +8 -0
  144. package/src/core/transports/http.ts +17 -13
  145. package/src/core/transports/test/http.test.ts +49 -60
  146. package/src/file/api/createFile.ts +1 -1
  147. package/src/file/api/createImage.ts +1 -1
  148. package/src/file/api/createVideo.ts +1 -1
  149. package/src/file/api/test/createImage.test.ts +69 -0
  150. package/src/file/api/test/createVideo.test.ts +81 -0
  151. package/src/index.ts +1 -0
  152. package/src/message/observers/liveMessage.ts +11 -83
  153. package/src/message/tests/observers/liveMessage.test.ts +1 -143
  154. package/src/post/observers/index.ts +1 -0
  155. package/src/post/observers/livePost.ts +47 -0
  156. package/src/post/observers/tests/livePost.test.ts +97 -0
  157. package/src/stream/api/tests/createStream.test.ts +3 -11
  158. package/src/stream/api/tests/deleteStream.test.ts +5 -3
  159. package/src/subChannel/api/tests/createSubChannel.test.ts +21 -14
  160. package/src/subChannel/api/tests/deleteSubChannel.test.ts +112 -0
  161. package/src/subChannel/api/tests/getSubChannel.test.ts +97 -0
  162. package/src/subChannel/api/tests/getSubChannels.test.ts +97 -0
  163. package/src/subChannel/api/tests/updateSubChannel.test.ts +86 -0
  164. package/src/subChannel/observers/liveSubChannel.ts +6 -56
  165. package/src/subChannel/observers/tests/liveSubChannel.test.ts +0 -82
  166. package/src/user/api/tests/updateUser.test.ts +60 -0
  167. package/src/user/observers/liveUser.ts +9 -61
  168. package/src/user/tests/observers/liveUser.test.ts +1 -154
  169. package/src/utils/liveObject.ts +86 -0
  170. package/src/utils/tests/dummy/category.ts +57 -0
  171. package/src/utils/tests/dummy/channel.ts +51 -1
  172. package/src/utils/tests/dummy/community.ts +94 -0
  173. package/src/utils/tests/dummy/file.ts +32 -0
  174. package/src/utils/tests/dummy/index.ts +1 -0
  175. package/src/utils/tests/dummy/post.ts +56 -45
  176. package/src/utils/tests/dummy/stream.ts +73 -73
  177. package/src/utils/tests/dummy/subChannel.ts +0 -22
  178. package/src/utils/tests/dummy/user.ts +21 -0
  179. package/src/utils/tests/liveObject.test.ts +172 -0
@@ -10,7 +10,11 @@ import {
10
10
  user11,
11
11
  } from '~/utils/tests';
12
12
 
13
- import { createCommunityEventSubscriber, createCommunityMemberEventSubscriber } from '../utils';
13
+ import {
14
+ createCommunityEventSubscriber,
15
+ createCommunityMemberEventSubscriber,
16
+ updateMembershipStatus,
17
+ } from '../utils';
14
18
 
15
19
  const event = 'community.created';
16
20
  const communityToCreate = community11;
@@ -115,7 +119,7 @@ describe('createCommunityMemberEventSubscriber', () => {
115
119
  unsub();
116
120
 
117
121
  expect(callback).toHaveBeenCalled();
118
- expect(callback).toHaveBeenCalledWith(communityToAddUser, addedUser);
122
+ expect(callback).toHaveBeenCalledWith({ ...communityToAddUser, isJoined: true }, addedUser);
119
123
  });
120
124
 
121
125
  test('it should trigger callback with correctly params when cache is enabled', () => {
@@ -128,7 +132,7 @@ describe('createCommunityMemberEventSubscriber', () => {
128
132
  unsub();
129
133
 
130
134
  expect(callback).toHaveBeenCalled();
131
- expect(callback).toHaveBeenCalledWith(communityToAddUser, addedUser);
135
+ expect(callback).toHaveBeenCalledWith({ ...communityToCreate, isJoined: true }, addedUser);
132
136
 
133
137
  disableCache();
134
138
  });
@@ -156,3 +160,48 @@ describe('createCommunityMemberEventSubscriber', () => {
156
160
  disableCache();
157
161
  });
158
162
  });
163
+
164
+ describe('updateMembershipStatus', () => {
165
+ beforeAll(connectClient);
166
+ afterAll(disconnectClient);
167
+
168
+ const communities = [
169
+ {
170
+ communityId: 'test-membership',
171
+ } as Amity.Community,
172
+ ];
173
+
174
+ const getCommunityUsers = (membership = 'member') => [
175
+ {
176
+ communityId: 'test-membership',
177
+ communityMembership: membership,
178
+ userId: client?.userId || 'test',
179
+ } as Amity.Membership<'community'>,
180
+ {
181
+ communityId: 'test-membership',
182
+ communityMembership: 'member',
183
+ userId: 'some-other-user',
184
+ } as Amity.Membership<'community'>,
185
+ ];
186
+
187
+ test('it should update isJoined status to true if membership member', () => {
188
+ const expected = { ...communities[0], isJoined: true };
189
+ const recieved = updateMembershipStatus(communities, getCommunityUsers())[0];
190
+
191
+ expect(recieved).toStrictEqual(expected);
192
+ });
193
+
194
+ test('it should update isJoined status to true if membership banned', () => {
195
+ const expected = { ...communities[0], isJoined: true };
196
+ const recieved = updateMembershipStatus(communities, getCommunityUsers('banned'))[0];
197
+
198
+ expect(recieved).toStrictEqual(expected);
199
+ });
200
+
201
+ test('it should update isJoined status to false if membership none', () => {
202
+ const expected = { ...communities[0], isJoined: false };
203
+ const recieved = updateMembershipStatus(communities, getCommunityUsers('none'))[0];
204
+
205
+ expect(recieved).toStrictEqual(expected);
206
+ });
207
+ });
@@ -1,4 +1,5 @@
1
1
  import { getActiveClient } from '~/client/api';
2
+ import { getActiveUser } from '~/client/api/activeUser';
2
3
  import { ingestInCache } from '~/cache/api/ingestInCache';
3
4
  import { createEventSubscriber } from '~/core/events';
4
5
  import { pullFromCache } from '~/cache/api';
@@ -35,6 +36,43 @@ export const createCommunityEventSubscriber = (
35
36
  return createEventSubscriber(client, event, event, filter);
36
37
  };
37
38
 
39
+ /*
40
+ * verifies membership status
41
+ */
42
+ function isMember(membership: Amity.Membership<'community'>['communityMembership']): boolean {
43
+ return membership !== 'none';
44
+ }
45
+
46
+ /*
47
+ * checks if currently logged in user is part of the community
48
+ */
49
+ function isCurrentUserPartOfCommunity(
50
+ c: Amity.Community,
51
+ m: Amity.Membership<'community'>,
52
+ ): boolean {
53
+ const { userId } = getActiveUser();
54
+
55
+ return c.communityId === m.communityId && m.userId === userId;
56
+ }
57
+
58
+ /*
59
+ * For mqtt events server will not send user specific data as it's broadcasted
60
+ * to multiple users. Althought this can be calculated, it's not scalable. Hence
61
+ * the client SDK needs to calculate this info
62
+ */
63
+ export function updateMembershipStatus(
64
+ communities: Amity.Community[],
65
+ communityUsers: Amity.Membership<'community'>[],
66
+ ): Amity.Community[] {
67
+ return communities.map(c => {
68
+ const isJoined = communityUsers.some(
69
+ m => isCurrentUserPartOfCommunity(c, m) && isMember(m.communityMembership),
70
+ );
71
+
72
+ return { ...c, isJoined };
73
+ });
74
+ }
75
+
38
76
  function hasPermission(
39
77
  member: Amity.Membership<'community'>,
40
78
  payload: {
@@ -88,7 +126,18 @@ export const createCommunityMemberEventSubscriber = (
88
126
 
89
127
  const filter = (payload: Amity.CommunityMembershipPayload) => {
90
128
  const communityPayload = unpackCommunityPayload(payload);
129
+ const { communities } = communityPayload;
130
+
91
131
  const preparedPayload = prepareMembershipPayload(communityPayload, 'communityUsers');
132
+ const { communityUsers } = preparedPayload;
133
+
134
+ /*
135
+ * community.isJoined is not part of the communityMembership payload, and needs
136
+ * to be calculated based on the communityMembership value
137
+ */
138
+ const communitiesWithMembership = updateMembershipStatus(communities, communityUsers);
139
+
140
+ preparedPayload.communities = communitiesWithMembership;
92
141
 
93
142
  if (!client.cache) {
94
143
  // TODO: here we are missing specific properties here!
@@ -1 +1,2 @@
1
1
  export * from './observeCommunity';
2
+ export * from './liveCommunities';
@@ -0,0 +1,188 @@
1
+ /* eslint-disable no-use-before-define */
2
+ import { getResolver } from '~/core/model';
3
+ import { pullFromCache, pushToCache } from '~/cache/api';
4
+ import { getActiveClient } from '~/client/api';
5
+ import {
6
+ createQuery,
7
+ runQuery,
8
+ queryOptions,
9
+ filterByPropEquality,
10
+ sortByLastCreated,
11
+ sortByDisplayName,
12
+ sortByFirstCreated,
13
+ } from '~/core/query';
14
+ import {
15
+ COLLECTION_DEFAULT_CACHING_POLICY,
16
+ COLLECTION_DEFAULT_PAGINATION_LIMIT,
17
+ ENABLE_CACHE_MESSAGE,
18
+ } from '~/utils/constants';
19
+ import {
20
+ onCommunityCreated,
21
+ onCommunityDeleted,
22
+ onCommunityUpdated,
23
+ onCommunityJoined,
24
+ onCommunityLeft,
25
+ onCommunityUserChanged,
26
+ } from '../events';
27
+ import { queryCommunities } from '../api';
28
+
29
+ /**
30
+ * ```js
31
+ * import { liveCommunities } from '@amityco/ts-sdk'
32
+ *
33
+ * let communities = []
34
+ * const unsub = liveCommunities({
35
+ * displayName: Amity.Community['displayName'],
36
+ * }, response => merge(communities, response.data))
37
+ * ```
38
+ *
39
+ * Observe all mutations on a list of {@link Amity.Community}s
40
+ *
41
+ * @param params for querying communities
42
+ * @param callback the function to call when new data are available
43
+ * @returns An {@link Amity.Unsubscriber} function to run when willing to stop observing the communities
44
+ *
45
+ * @category Community Live Collection
46
+ */
47
+ export const liveCommunities = (
48
+ params: Amity.CommunityLiveCollection,
49
+ callback: Amity.LiveCollectionCallback<Amity.Community>,
50
+ config?: Amity.LiveCollectionConfig,
51
+ ) => {
52
+ const { log, cache } = getActiveClient();
53
+
54
+ if (!cache) {
55
+ console.log(ENABLE_CACHE_MESSAGE);
56
+ }
57
+
58
+ const timestamp = Date.now();
59
+ log(`liveCommunities(tmpid: ${timestamp}) > listen`);
60
+
61
+ const { limit: queryLimit, ...queryParams } = params;
62
+
63
+ const limit = queryLimit ?? COLLECTION_DEFAULT_PAGINATION_LIMIT;
64
+ const { policy = COLLECTION_DEFAULT_CACHING_POLICY } = config ?? {};
65
+
66
+ const disposers: Amity.Unsubscriber[] = [];
67
+ const cacheKey = ['community', 'collection', {}];
68
+
69
+ const responder = (data: Amity.CommunityLiveCollectionCache) => {
70
+ let communities: Amity.Community[] =
71
+ data.data
72
+ .map(communityId => pullFromCache<Amity.Community>(['community', 'get', communityId])!)
73
+ .filter(Boolean)
74
+ .map(({ data }) => data) ?? [];
75
+
76
+ communities = filterByPropEquality(communities, 'isDeleted', params.isDeleted);
77
+ communities = filterByPropEquality(communities, 'displayName', params.displayName);
78
+
79
+ if (params.categoryId) {
80
+ communities = communities.filter(c => c.categoryIds?.includes(params.categoryId!));
81
+ }
82
+
83
+ if (params.tags) {
84
+ communities = communities.filter(c => c.tags?.some(t => params.tags?.includes(t)));
85
+ }
86
+
87
+ if (params.membership === 'member') {
88
+ communities = filterByPropEquality(communities, 'isJoined', true);
89
+ }
90
+
91
+ if (params.membership === 'notMember') {
92
+ communities = filterByPropEquality(communities, 'isJoined', false);
93
+ }
94
+
95
+ const sortBy = params.sortBy || 'lastCreated';
96
+
97
+ if (sortBy === 'lastCreated' || sortBy === 'firstCreated') {
98
+ communities = communities.sort(
99
+ sortBy === 'lastCreated' ? sortByLastCreated : sortByFirstCreated,
100
+ );
101
+ }
102
+
103
+ /*
104
+ * The server returns communities with empty | null displayName's first before
105
+ * returning sorted list of communities with displayNames
106
+ *
107
+ * This section needs to be updated as displayNames can be null as well
108
+ */
109
+ if (sortBy === 'displayName') {
110
+ communities = communities
111
+ // this needs to be aligned with the backend data type
112
+ .map(c => (c.displayName ? c : { ...c, displayName: '' }))
113
+ // @ts-ignore
114
+ .sort(sortByDisplayName);
115
+ }
116
+
117
+ callback({
118
+ onNextPage: onFetch,
119
+ data: communities,
120
+ hasNextPage: !!data.params?.page,
121
+ loading: data.loading,
122
+ error: data.error,
123
+ });
124
+ };
125
+
126
+ const realtimeRouter = (_: Amity.CommunityActionType) => (community: Amity.Community) => {
127
+ const collection = pullFromCache<Amity.CommunityLiveCollectionCache>(cacheKey)?.data;
128
+ if (!collection) return;
129
+
130
+ /*
131
+ * Simply update collection and let responder decide what to do with data
132
+ */
133
+ collection.data = [...new Set([community.communityId, ...collection.data])];
134
+
135
+ pushToCache(cacheKey, collection);
136
+ responder(collection);
137
+ };
138
+
139
+ const onFetch = () => {
140
+ const collection = pullFromCache<Amity.CommunityLiveCollectionCache>(cacheKey)?.data;
141
+
142
+ const communities = collection?.data ?? [];
143
+
144
+ if (communities.length > 0 && !collection?.params.page) return;
145
+
146
+ const query = createQuery(queryCommunities, {
147
+ ...queryParams,
148
+ page: collection?.params.page ?? { limit },
149
+ });
150
+
151
+ runQuery(
152
+ query,
153
+ ({ data: result, error, loading, nextPage: page }) => {
154
+ const data = {
155
+ loading,
156
+ error,
157
+ params: { page },
158
+ data: communities,
159
+ };
160
+
161
+ if (result) {
162
+ data.data = [...new Set([...communities, ...result.map(getResolver('community'))])];
163
+ }
164
+
165
+ pushToCache(cacheKey, data);
166
+
167
+ responder(data);
168
+ },
169
+ queryOptions(policy),
170
+ );
171
+ };
172
+
173
+ disposers.push(
174
+ onCommunityCreated(realtimeRouter('onCreate')),
175
+ onCommunityDeleted(realtimeRouter('onDelete')),
176
+ onCommunityUpdated(realtimeRouter('onUpdate')),
177
+ onCommunityJoined(realtimeRouter('onJoin')),
178
+ onCommunityLeft(realtimeRouter('onLeft')),
179
+ onCommunityUserChanged(realtimeRouter('onMemberCountChanged')),
180
+ );
181
+
182
+ onFetch();
183
+
184
+ return () => {
185
+ log(`liveCommunities(tmpid: ${timestamp}) > dispose`);
186
+ disposers.forEach(fn => fn());
187
+ };
188
+ };
@@ -0,0 +1,284 @@
1
+ import { disableCache, enableCache } from '~/cache/api';
2
+ import {
3
+ communityQueryResponse,
4
+ communityQueryResponsePage2,
5
+ client,
6
+ connectClient,
7
+ disconnectClient,
8
+ pause,
9
+ communityUser11,
10
+ } from '~/utils/tests';
11
+
12
+ import { liveCommunities } from '../liveCommunities';
13
+
14
+ import { unpackCommunityPayload } from '../../utils';
15
+
16
+ const getSnapshot = (params?: Record<string, any>) => {
17
+ return {
18
+ data: [] as Amity.Community[],
19
+ loading: true,
20
+ error: undefined as any,
21
+ ...params,
22
+ };
23
+ };
24
+
25
+ const { paging, ...payload } = communityQueryResponse.data;
26
+ const { paging: paging2, ...payload2 } = communityQueryResponsePage2.data;
27
+
28
+ const community1 = unpackCommunityPayload(payload).communities[0];
29
+ const community2 = unpackCommunityPayload(payload).communities[1];
30
+
31
+ describe('liveCommunities', () => {
32
+ beforeAll(connectClient);
33
+ afterAll(disconnectClient);
34
+
35
+ beforeEach(enableCache);
36
+
37
+ afterEach(disableCache);
38
+
39
+ test('it should return community collection', async () => {
40
+ const callback = jest.fn();
41
+ client.http.get = jest.fn().mockResolvedValue(communityQueryResponse);
42
+
43
+ liveCommunities({}, callback);
44
+ await pause();
45
+
46
+ expect(callback).toHaveBeenCalledTimes(2);
47
+ expect(callback).toHaveBeenNthCalledWith(1, expect.objectContaining(getSnapshot()));
48
+ expect(callback).toHaveBeenNthCalledWith(
49
+ 2,
50
+ expect.objectContaining(
51
+ getSnapshot({
52
+ data: unpackCommunityPayload(payload).communities,
53
+ loading: false,
54
+ }),
55
+ ),
56
+ );
57
+ });
58
+
59
+ test('it should return data from cache', async () => {
60
+ const callback = jest.fn();
61
+ client.http.get = jest.fn().mockResolvedValue(communityQueryResponse);
62
+
63
+ liveCommunities({}, () => undefined);
64
+ await pause();
65
+ liveCommunities({}, callback);
66
+ await pause();
67
+
68
+ expect(callback).toHaveBeenCalledTimes(2);
69
+ expect(callback).toHaveBeenNthCalledWith(
70
+ 1,
71
+ expect.objectContaining(
72
+ getSnapshot({
73
+ data: unpackCommunityPayload(payload).communities,
74
+ }),
75
+ ),
76
+ );
77
+ expect(callback).toHaveBeenNthCalledWith(
78
+ 2,
79
+ expect.objectContaining(
80
+ getSnapshot({
81
+ data: unpackCommunityPayload(payload).communities,
82
+ loading: false,
83
+ }),
84
+ ),
85
+ );
86
+ });
87
+
88
+ const filters: [string, Amity.CommunityLiveCollection, Amity.Community[]][] = [
89
+ ['deleted', { isDeleted: true }, [community1]],
90
+ ['categoryId', { categoryId: 'test-category-id' }, [community1]],
91
+ ['displayName', { displayName: 'community 1' }, [community1]],
92
+ ['tagged', { tags: ['test-community-tag'] }, [community1]],
93
+ ['member', { membership: 'member' }, [community1]],
94
+ ['not member', { membership: 'notMember' }, [community2]],
95
+ ['all membership', { membership: 'all' }, [community1, community2]],
96
+ ];
97
+
98
+ test.each(filters)('it should filter by %s communities', async (filter, params, expected) => {
99
+ const callback = jest.fn();
100
+ client.http.get = jest.fn().mockResolvedValue(communityQueryResponse);
101
+
102
+ liveCommunities(params, callback);
103
+
104
+ expect(callback).toHaveBeenCalledTimes(1);
105
+ // check if cache data returned (should be empty)
106
+ expect(callback).toHaveBeenCalledWith(expect.objectContaining(getSnapshot()));
107
+
108
+ await pause();
109
+
110
+ expect(callback).toHaveBeenCalledTimes(2);
111
+ expect(callback).toHaveBeenCalledWith(
112
+ expect.objectContaining(
113
+ getSnapshot({
114
+ loading: false,
115
+ data: expected,
116
+ }),
117
+ ),
118
+ );
119
+ });
120
+
121
+ test('it should return method to fetch next page', async () => {
122
+ const callback = jest.fn();
123
+ client.http.get = jest
124
+ .fn()
125
+ .mockResolvedValue(communityQueryResponse)
126
+ .mockResolvedValueOnce(communityQueryResponsePage2);
127
+
128
+ liveCommunities({}, callback);
129
+ await pause();
130
+
131
+ expect(callback).toHaveBeenCalled();
132
+ expect(callback.mock.lastCall).toHaveLength(1);
133
+
134
+ const { onNextPage, hasNextPage } = callback.mock.lastCall[0];
135
+
136
+ expect(hasNextPage).toBe(true);
137
+ expect(onNextPage).toBeTruthy();
138
+
139
+ onNextPage();
140
+ await pause();
141
+
142
+ // 4 -> because 1 local & server call each per call (2)
143
+ expect(callback).toHaveBeenCalledTimes(4);
144
+ expect(callback).toHaveBeenNthCalledWith(
145
+ 4,
146
+ expect.objectContaining(
147
+ getSnapshot({
148
+ loading: false,
149
+ data: unpackCommunityPayload(payload).communities,
150
+ }),
151
+ ),
152
+ );
153
+ });
154
+
155
+ describe('events', () => {
156
+ const newCommunityPayload = {
157
+ ...communityQueryResponse.data.communities[0],
158
+ communityId: 'new-community',
159
+ isDeleted: false,
160
+ };
161
+ const newCommunity = unpackCommunityPayload({ communities: [newCommunityPayload] })
162
+ .communities[0];
163
+
164
+ const communityPayload = {
165
+ communities: [communityQueryResponse.data.communities[0]],
166
+ communityUsers: [communityUser11],
167
+ };
168
+
169
+ const cases: [string, keyof Amity.Events, Amity.CommunityPayload, Amity.Community[]][] = [
170
+ [
171
+ 'it should add new community to collection onCreate',
172
+ 'community.created',
173
+ { communities: [newCommunityPayload] },
174
+ [newCommunity, community2],
175
+ ],
176
+ [
177
+ 'it should update community in collection onUpdate',
178
+ 'community.updated',
179
+ { communities: [{ ...newCommunityPayload, communityId: community1.communityId }] },
180
+ [{ ...newCommunity, communityId: community1.communityId }, community2],
181
+ ],
182
+ [
183
+ 'it should remove community from collection onDelete',
184
+ 'community.deleted',
185
+ communityPayload,
186
+ [community2],
187
+ ],
188
+ ];
189
+
190
+ test.each(cases)('%s', async (test, event, communityPayload, expected) => {
191
+ const callback = jest.fn();
192
+ client.http.get = jest.fn().mockResolvedValue(communityQueryResponse);
193
+
194
+ liveCommunities({ isDeleted: false }, callback);
195
+ await pause();
196
+
197
+ client.emitter.emit(event, communityPayload);
198
+ await pause();
199
+
200
+ expect(callback).toHaveBeenNthCalledWith(
201
+ 3,
202
+ expect.objectContaining(
203
+ getSnapshot({
204
+ data: expected,
205
+ loading: false,
206
+ }),
207
+ ),
208
+ );
209
+ });
210
+
211
+ const joinCommunityPayload = {
212
+ ...communityQueryResponse.data.communities[0],
213
+ communityId: 'joining-community',
214
+ isDeleted: false,
215
+ };
216
+ const joinedCommunity = unpackCommunityPayload({ communities: [joinCommunityPayload] })
217
+ .communities[0];
218
+
219
+ const joiningUser: Amity.MembershipRaw<'community'> = {
220
+ ...communityUser11,
221
+ userId: 'test',
222
+ communityId: 'joining-community',
223
+ communityMembership: 'member',
224
+ };
225
+
226
+ const memberEvents: [
227
+ string,
228
+ Amity.CommunityLiveCollection,
229
+ keyof Amity.Events,
230
+ Amity.CommunityPayload,
231
+ Amity.Community[],
232
+ ][] = [
233
+ [
234
+ 'it should add new community to collection onJoin',
235
+ { membership: 'member' },
236
+ 'community.joined',
237
+ { communities: [joinCommunityPayload], communityUsers: [joiningUser] },
238
+ [joinedCommunity, community1],
239
+ ],
240
+ [
241
+ 'it should not add new member to community collection onJoin if membership:not member',
242
+ { membership: 'notMember' },
243
+ 'community.joined',
244
+ { communities: [joinCommunityPayload], communityUsers: [joiningUser] },
245
+ [],
246
+ ],
247
+ [
248
+ 'it should update community in collection onLeft',
249
+ { membership: 'member' },
250
+ 'community.left',
251
+ { ...communityPayload, communities: [{ ...community1, isJoined: false }] },
252
+ [],
253
+ ],
254
+ [
255
+ 'it should add new community in collection onLeft and if membership:not member',
256
+ { membership: 'notMember' },
257
+ 'community.left',
258
+ { ...communityPayload, communities: [{ ...community1, isJoined: false }] },
259
+ [{ ...community1, isJoined: false }],
260
+ ],
261
+ ];
262
+
263
+ test.each(memberEvents)('%s', async (test, params, event, communityPayload, expected) => {
264
+ const callback = jest.fn();
265
+ client.http.get = jest.fn().mockResolvedValue(communityQueryResponsePage2);
266
+
267
+ liveCommunities(params, callback);
268
+ await pause();
269
+
270
+ client.emitter.emit(event, communityPayload);
271
+ await pause();
272
+
273
+ expect(callback).toHaveBeenNthCalledWith(
274
+ 3,
275
+ expect.objectContaining(
276
+ getSnapshot({
277
+ data: expected,
278
+ loading: false,
279
+ }),
280
+ ),
281
+ );
282
+ });
283
+ });
284
+ });
@@ -15,6 +15,14 @@ export const sortByDisplayName: SortingFunc<{ displayName: string }> = (
15
15
  return a < b ? -1 : 1;
16
16
  };
17
17
 
18
+ /**
19
+ * Alphabetic sorting of objects having a name
20
+ */
21
+ export const sortByName: SortingFunc<{ name: string }> = ({ name: a }, { name: b }) => {
22
+ if (a === b) return 0;
23
+ return a < b ? -1 : 1;
24
+ };
25
+
18
26
  /**
19
27
  * Sorting a collection by their apparition order (oldest first)
20
28
  */
@@ -1,5 +1,4 @@
1
1
  import axios from 'axios';
2
- import qs from 'qs';
3
2
  import HttpAgent, { HttpsAgent } from 'agentkeepalive';
4
3
 
5
4
  import { ASCError } from '~/core/errors';
@@ -7,14 +6,6 @@ import { ASCError } from '~/core/errors';
7
6
  import { fireEvent } from '../events';
8
7
  import { unwrapPayload } from './utils';
9
8
 
10
- /**
11
- * Axios param serializer (for querystring)
12
- * @hidden
13
- */
14
- export const stringify = (params: Record<string, unknown>): string => {
15
- return qs.stringify(params, { arrayFormat: 'brackets' });
16
- };
17
-
18
9
  /**
19
10
  * Creates a pre-configured axios instance
20
11
  *
@@ -36,7 +27,14 @@ export const createHttpTransport = (endpoint: string) => {
36
27
  baseURL: endpoint,
37
28
  httpAgent: new HttpAgent(options),
38
29
  httpsAgent: new HttpsAgent(options),
39
- paramsSerializer: stringify,
30
+ /*
31
+ * If paramSerializer is required, use the serialize option to stringify
32
+ * params. The encode option will pass complex params as string only whereas
33
+ * the serialize option will pass params in the format Record<string, any>
34
+ *
35
+ * For more details:
36
+ * https://github.com/axios/axios#request-config
37
+ */
40
38
  });
41
39
 
42
40
  instance.interceptors.request.use(config => {
@@ -59,8 +57,6 @@ export const createHttpTransport = (endpoint: string) => {
59
57
  response => response,
60
58
  error => {
61
59
  const { response } = error;
62
- // eslint-disable-next-line no-console
63
- console.error(response?.data ?? error);
64
60
 
65
61
  // handle unauthorized request
66
62
  if (response.data.code === Amity.ServerError.UNAUTHORIZED) {
@@ -78,7 +74,15 @@ export const createHttpTransport = (endpoint: string) => {
78
74
  );
79
75
  }
80
76
 
81
- if (response?.data) unwrapPayload(response.data);
77
+ // if it's an error with status in the response payload,
78
+ // then it's an expected error.
79
+ if (response?.data?.status) {
80
+ unwrapPayload(response?.data);
81
+ }
82
+ // unexpected error.
83
+ else {
84
+ throw new Error(response?.data ?? error);
85
+ }
82
86
  },
83
87
  );
84
88