@amityco/ts-sdk 6.17.1-e9b6af1.0 → 6.17.2-ea577e1.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 (86) hide show
  1. package/dist/@types/core/payload.d.ts +1 -0
  2. package/dist/@types/core/payload.d.ts.map +1 -1
  3. package/dist/@types/core/permissions.d.ts +5 -0
  4. package/dist/@types/core/permissions.d.ts.map +1 -1
  5. package/dist/@types/domains/client.d.ts +1 -0
  6. package/dist/@types/domains/client.d.ts.map +1 -1
  7. package/dist/@types/domains/story.d.ts +4 -0
  8. package/dist/@types/domains/story.d.ts.map +1 -1
  9. package/dist/channelRepository/observers/getChannel.d.ts.map +1 -1
  10. package/dist/channelRepository/observers/getChannels/ChannelLiveCollectionController.d.ts.map +1 -1
  11. package/dist/client/api/createClient.d.ts.map +1 -1
  12. package/dist/client/observers/getTotalUnreadCount.d.ts.map +1 -1
  13. package/dist/client/observers/getUserUnread.d.ts.map +1 -1
  14. package/dist/client/utils/hasPermission/checkChannelPermission.d.ts +2 -0
  15. package/dist/client/utils/hasPermission/checkChannelPermission.d.ts.map +1 -0
  16. package/dist/client/utils/hasPermission/checkCommunityPermission.d.ts +2 -0
  17. package/dist/client/utils/hasPermission/checkCommunityPermission.d.ts.map +1 -0
  18. package/dist/client/utils/hasPermission/checkUserPermission.d.ts +2 -0
  19. package/dist/client/utils/hasPermission/checkUserPermission.d.ts.map +1 -0
  20. package/dist/client/utils/hasPermission/hasPermission.d.ts +6 -0
  21. package/dist/client/utils/hasPermission/hasPermission.d.ts.map +1 -0
  22. package/dist/client/utils/hasPermission/index.d.ts +2 -0
  23. package/dist/client/utils/hasPermission/index.d.ts.map +1 -0
  24. package/dist/client/utils/markerSyncEngine.d.ts.map +1 -1
  25. package/dist/index.cjs.js +510 -70
  26. package/dist/index.esm.js +510 -71
  27. package/dist/index.umd.js +4 -4
  28. package/dist/marker/api/getUserMarker.d.ts +1 -1
  29. package/dist/marker/api/getUserMarker.d.ts.map +1 -1
  30. package/dist/marker/events/onChannelMarkerFetched.d.ts.map +1 -1
  31. package/dist/marker/events/onUserMarkerFetched.d.ts +2 -1
  32. package/dist/marker/events/onUserMarkerFetched.d.ts.map +1 -1
  33. package/dist/messageRepository/events/onMessageCreated.d.ts +2 -1
  34. package/dist/messageRepository/events/onMessageCreated.d.ts.map +1 -1
  35. package/dist/messageRepository/observers/observeMessages.d.ts.map +1 -1
  36. package/dist/messageRepository/utils/prepareMessagePayload.d.ts.map +1 -1
  37. package/dist/storyRepository/constants.d.ts +1 -0
  38. package/dist/storyRepository/constants.d.ts.map +1 -1
  39. package/dist/storyRepository/observers/getStoriesByTargetIds/StoryLiveCollectionController.d.ts +14 -0
  40. package/dist/storyRepository/observers/getStoriesByTargetIds/StoryLiveCollectionController.d.ts.map +1 -0
  41. package/dist/storyRepository/observers/getStoriesByTargetIds/StoryPaginationNoPageController.d.ts +5 -0
  42. package/dist/storyRepository/observers/getStoriesByTargetIds/StoryPaginationNoPageController.d.ts.map +1 -0
  43. package/dist/storyRepository/observers/getStoriesByTargetIds/StoryQueryStreamController.d.ts +16 -0
  44. package/dist/storyRepository/observers/getStoriesByTargetIds/StoryQueryStreamController.d.ts.map +1 -0
  45. package/dist/storyRepository/observers/getStoriesByTargetIds/getStoriesByTargetIds.d.ts +5 -0
  46. package/dist/storyRepository/observers/getStoriesByTargetIds/getStoriesByTargetIds.d.ts.map +1 -0
  47. package/dist/storyRepository/observers/getStoriesByTargetIds/index.d.ts +2 -0
  48. package/dist/storyRepository/observers/getStoriesByTargetIds/index.d.ts.map +1 -0
  49. package/dist/storyRepository/observers/index.d.ts +1 -0
  50. package/dist/storyRepository/observers/index.d.ts.map +1 -1
  51. package/dist/subChannelRepository/observers/getSubChannel.d.ts.map +1 -1
  52. package/dist/subChannelRepository/observers/getSubChannels/SubChannelLiveCollectionController.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/src/@types/core/payload.ts +1 -0
  55. package/src/@types/core/permissions.ts +6 -0
  56. package/src/@types/domains/client.ts +2 -0
  57. package/src/@types/domains/story.ts +5 -0
  58. package/src/channelRepository/observers/getChannel.ts +30 -2
  59. package/src/channelRepository/observers/getChannels/ChannelLiveCollectionController.ts +65 -3
  60. package/src/client/api/createClient.ts +3 -1
  61. package/src/client/observers/getTotalUnreadCount.ts +14 -4
  62. package/src/client/observers/getUserUnread.ts +95 -11
  63. package/src/client/utils/hasPermission/checkChannelPermission.ts +22 -0
  64. package/src/client/utils/hasPermission/checkCommunityPermission.ts +22 -0
  65. package/src/client/utils/hasPermission/checkUserPermission.ts +12 -0
  66. package/src/client/utils/hasPermission/hasPermission.ts +18 -0
  67. package/src/client/utils/hasPermission/index.ts +1 -0
  68. package/src/client/utils/markerSyncEngine.ts +46 -32
  69. package/src/marker/api/getUserMarker.ts +15 -4
  70. package/src/marker/events/onChannelMarkerFetched.ts +3 -1
  71. package/src/marker/events/onUserMarkerFetched.ts +17 -0
  72. package/src/messageRepository/api/tests/createMessage.test.ts +4 -4
  73. package/src/messageRepository/events/onMessageCreated.ts +20 -8
  74. package/src/messageRepository/events/tests/events.test.ts +2 -2
  75. package/src/messageRepository/observers/getMessages/MessageLiveCollectionController.ts +2 -2
  76. package/src/messageRepository/observers/observeMessages.ts +8 -2
  77. package/src/messageRepository/utils/prepareMessagePayload.ts +14 -4
  78. package/src/storyRepository/constants.ts +1 -0
  79. package/src/storyRepository/observers/getStoriesByTargetIds/StoryLiveCollectionController.ts +113 -0
  80. package/src/storyRepository/observers/getStoriesByTargetIds/StoryPaginationNoPageController.ts +18 -0
  81. package/src/storyRepository/observers/getStoriesByTargetIds/StoryQueryStreamController.ts +97 -0
  82. package/src/storyRepository/observers/getStoriesByTargetIds/getStoriesByTargetIds.ts +32 -0
  83. package/src/storyRepository/observers/getStoriesByTargetIds/index.ts +1 -0
  84. package/src/storyRepository/observers/index.ts +1 -0
  85. package/src/subChannelRepository/observers/getSubChannel.ts +35 -2
  86. package/src/subChannelRepository/observers/getSubChannels/SubChannelLiveCollectionController.ts +53 -2
@@ -8,7 +8,7 @@ import { onUserMarkerSync } from '~/marker/events/onUserMarkerSync';
8
8
  import { setIntervalTask } from '~/utils/timer';
9
9
  import { getUserMarker } from '~/marker/api';
10
10
  import { onFeedMarkerUpdated } from '~/marker/events';
11
- import { onMessageCreated } from '~/messageRepository/events';
11
+ import { onMessageCreatedMqtt } from '~/messageRepository/events';
12
12
  import { onSubChannelCreated, onSubChannelDeleted } from '~/subChannelRepository/events';
13
13
  import { isUnreadCountSupport } from '~/subChannelRepository/utils';
14
14
 
@@ -22,25 +22,27 @@ let isSyncRunning = false;
22
22
  let disposers: (() => void)[] = [];
23
23
  let isWaitingForResponse = false;
24
24
 
25
- let deviceLastSyncAt: string | null = null;
25
+ let deviceLastSyncAt: Date | null = null;
26
26
 
27
27
  const getDeviceLastSyncAt = () => {
28
28
  if (deviceLastSyncAt == null) {
29
- return new Date().toISOString();
29
+ return new Date();
30
30
  }
31
31
 
32
32
  return deviceLastSyncAt;
33
33
  };
34
34
 
35
- const saveDeviceLastSyncAt = (lastSyncAt: string) => {
36
- if (!deviceLastSyncAt || new Date(lastSyncAt).getTime() > new Date(deviceLastSyncAt).getTime()) {
35
+ const saveDeviceLastSyncAt = (lastSyncAt: Date | null) => {
36
+ if (lastSyncAt == null) return;
37
+ if (!deviceLastSyncAt || lastSyncAt.getTime() > deviceLastSyncAt.getTime()) {
37
38
  deviceLastSyncAt = lastSyncAt;
38
39
  }
39
40
  };
40
41
 
41
42
  const fetchDeviceLastSyncAt = async () => {
42
43
  const { data: userMarker } = await getUserMarker();
43
- saveDeviceLastSyncAt(userMarker.lastSyncAt);
44
+ if (userMarker == null) return;
45
+ saveDeviceLastSyncAt(new Date(userMarker.lastSyncAt));
44
46
  };
45
47
 
46
48
  /**
@@ -84,24 +86,41 @@ export const markerSyncTrigger = async () => {
84
86
  // no event that require to call marker sync API
85
87
  return;
86
88
  }
87
-
88
- isWaitingForResponse = true;
89
-
90
- // any past events are considered processed here.
91
- // however during waiting for the response, RTE could add the new message event;
92
- // which will make the engine trigger another call next round.
93
- events = [];
94
-
95
- const response = await markerSync(getDeviceLastSyncAt());
96
-
97
- saveDeviceLastSyncAt(response.data.userMarkers[0].lastSyncAt);
98
-
99
- if (response.hasMore) {
100
- events.push(Amity.MarkerSyncEvent.HAS_MORE);
89
+ try {
90
+ isWaitingForResponse = true;
91
+ // any past events are considered processed here.
92
+ // however during waiting for the response, RTE could add the new message event;
93
+ // which will make the engine trigger another call next round.
94
+ events = [];
95
+
96
+ const response = await markerSync(getDeviceLastSyncAt().toISOString());
97
+
98
+ const latestLastSyncAt: Date | null = response.data.userMarkers.reduce(
99
+ (maxLastSyncAt, userMarker) => {
100
+ if (
101
+ maxLastSyncAt == null ||
102
+ maxLastSyncAt.getTime() < new Date(userMarker.lastSyncAt).getTime()
103
+ ) {
104
+ return new Date(userMarker.lastSyncAt);
105
+ }
106
+
107
+ return maxLastSyncAt;
108
+ },
109
+ null as Date | null,
110
+ );
111
+
112
+ saveDeviceLastSyncAt(latestLastSyncAt);
113
+
114
+ if (response.hasMore) {
115
+ events.push(Amity.MarkerSyncEvent.HAS_MORE);
116
+ }
117
+ } catch {
118
+ // prevent sync from stopping
119
+ } finally {
120
+ if (isWaitingForResponse) {
121
+ isWaitingForResponse = false;
122
+ }
101
123
  }
102
-
103
- // eslint-disable-next-line require-atomic-updates
104
- isWaitingForResponse = false;
105
124
  };
106
125
 
107
126
  const registerEventListeners = () => {
@@ -119,15 +138,10 @@ const registerEventListeners = () => {
119
138
  events.push(Amity.MarkerSyncEvent.RESUME);
120
139
  }),
121
140
 
122
- onMessageCreated(
123
- message => {
124
- // only conversation, community and broadcast types can sync
125
- if (isUnreadCountSupport(message)) events.push(Amity.MarkerSyncEvent.NEW_MESSAGE);
126
- },
127
- // only trigger sync from remote events to prevent an unread count equal `-1`
128
- // when an optimistic message creation is performed.
129
- false,
130
- ),
141
+ onMessageCreatedMqtt(message => {
142
+ // only conversation, community and broadcast types can sync
143
+ if (isUnreadCountSupport(message)) events.push(Amity.MarkerSyncEvent.NEW_MESSAGE);
144
+ }),
131
145
 
132
146
  onChannelCreated(() => events.push(Amity.MarkerSyncEvent.CHANNEL_CREATED)),
133
147
  onChannelDeleted(() => events.push(Amity.MarkerSyncEvent.CHANNEL_DELETED)),
@@ -4,7 +4,7 @@ import { fireEvent } from '~/core/events';
4
4
  // import { convertMarkerResponse } from '~/utils/marker';
5
5
  import { convertUserMarkerResponse } from '~/utils/marker';
6
6
 
7
- export const getUserMarker = async (): Promise<Amity.Cached<Amity.UserMarker>> => {
7
+ export const getUserMarker = async (): Promise<Amity.Cached<Amity.UserMarker | null>> => {
8
8
  const client = getActiveClient();
9
9
  client.log('channel/getUserMarker');
10
10
 
@@ -14,8 +14,8 @@ export const getUserMarker = async (): Promise<Amity.Cached<Amity.UserMarker>> =
14
14
 
15
15
  const { userMarkers: UserMarkersPayload } = payload;
16
16
 
17
- /*
18
- change field isMentioned from backend to be hasMentioned
17
+ /*
18
+ change field isMentioned from backend to be hasMentioned
19
19
  */
20
20
  const userMarkers = convertUserMarkerResponse(UserMarkersPayload);
21
21
 
@@ -24,5 +24,16 @@ export const getUserMarker = async (): Promise<Amity.Cached<Amity.UserMarker>> =
24
24
 
25
25
  fireEvent('local.userMarker.fetched', { userMarkers });
26
26
 
27
- return { data: userMarkers[0], cachedAt };
27
+ const latestUserMarker = userMarkers.reduce((maxUserMarker, userMarker) => {
28
+ if (
29
+ maxUserMarker == null ||
30
+ new Date(maxUserMarker.lastSyncAt).getTime() < new Date(userMarker.lastSyncAt).getTime()
31
+ ) {
32
+ return userMarker;
33
+ }
34
+
35
+ return maxUserMarker;
36
+ }, null as Amity.UserMarker | null);
37
+
38
+ return { data: latestUserMarker, cachedAt };
28
39
  };
@@ -22,7 +22,9 @@ export const onChannelMarkerFetched = (
22
22
  const client = getActiveClient();
23
23
 
24
24
  const filter = (payload: Amity.Events['local.channelMarker.fetched']) => {
25
- callback(payload.userEntityMarkers[0]);
25
+ payload.userEntityMarkers.forEach(marker => {
26
+ callback(marker);
27
+ });
26
28
  };
27
29
 
28
30
  return createEventSubscriber(
@@ -17,6 +17,23 @@ import { createEventSubscriber } from '~/core/events';
17
17
  * @category UserMarker Events
18
18
  */
19
19
  export const onUserMarkerFetched = (
20
+ callback: Amity.Listener<Amity.UserMarker[]>,
21
+ ): Amity.Unsubscriber => {
22
+ const client = getActiveClient();
23
+
24
+ const filter = (payload: Amity.Events['local.userMarker.fetched']) => {
25
+ callback(payload.userMarkers);
26
+ };
27
+
28
+ return createEventSubscriber(
29
+ client,
30
+ 'userMarker/onUserMarkerFetched',
31
+ 'local.userMarker.fetched',
32
+ filter,
33
+ );
34
+ };
35
+
36
+ export const onUserMarkerFetchedLegacy = (
20
37
  callback: Amity.Listener<Amity.UserMarker>,
21
38
  ): Amity.Unsubscriber => {
22
39
  const client = getActiveClient();
@@ -5,7 +5,7 @@ import { disableCache, enableCache, pullFromCache, pushToCache } from '~/cache/a
5
5
  import { client, convertRawMessage, generateRawMessage, pause } from '~/utils/tests';
6
6
  import { createQuery, runQuery } from '~/core/query';
7
7
 
8
- import { onMessageCreated } from '../../events';
8
+ import { onMessageCreatedMqtt } from '../../events';
9
9
  import { createMessage } from '../createMessage';
10
10
  import * as getMessageMarkers from '../../../marker/api/getMessageMarkers';
11
11
 
@@ -72,7 +72,7 @@ describe('createMessage', () => {
72
72
  client.http.post = jest.fn().mockResolvedValueOnce(response);
73
73
 
74
74
  const callbackPromise = new Promise(resolve => {
75
- dispose = onMessageCreated(resolve);
75
+ dispose = onMessageCreatedMqtt(resolve);
76
76
  }).finally(dispose);
77
77
 
78
78
  await createMessage(messageWithoutId);
@@ -134,7 +134,7 @@ describe('createMessage.optimistically', () => {
134
134
  let dispose;
135
135
 
136
136
  const callbackPromise = new Promise(resolve => {
137
- dispose = onMessageCreated(resolve);
137
+ dispose = onMessageCreatedMqtt(resolve);
138
138
  }).finally(dispose);
139
139
 
140
140
  createMessage.optimistically(messageWithoutId);
@@ -193,7 +193,7 @@ describe('uniqueId / referenceId', () => {
193
193
  };
194
194
  const callback = jest.fn();
195
195
 
196
- onMessageCreated(callback);
196
+ onMessageCreatedMqtt(callback);
197
197
  client.emitter.emit('message.created', {
198
198
  messages: [rawNewMessage],
199
199
  messageFeeds: [],
@@ -19,31 +19,43 @@ import { prepareMessagePayload } from '../utils';
19
19
  *
20
20
  * @category Message Events
21
21
  */
22
- export const onMessageCreated = (
22
+ export const onMessageCreatedMqtt = (
23
23
  callback: Amity.Listener<Amity.Message>,
24
- local = true,
25
24
  ): Amity.Unsubscriber => {
26
25
  const client = getActiveClient();
27
26
 
28
27
  const filter = async (rawPayload: Amity.MessagePayload) => {
29
28
  const payload = await prepareMessagePayload(rawPayload);
30
- const message = payload.messages[0];
31
29
 
32
30
  // Update in cache
33
31
  ingestInCache(payload);
34
32
 
35
- callback(message);
33
+ payload.messages.forEach(message => {
34
+ callback(message);
35
+ });
36
36
  };
37
37
 
38
38
  const disposers = [
39
39
  createEventSubscriber(client, 'message/onMessageCreated', 'message.created', filter),
40
40
  ];
41
41
 
42
- if (local) {
42
+ return () => {
43
+ disposers.forEach(fn => fn());
44
+ };
45
+ };
46
+
47
+ export const onMessageCreatedLocal = (
48
+ callback: Amity.Listener<Amity.Message>,
49
+ ): Amity.Unsubscriber => {
50
+ const client = getActiveClient();
51
+
52
+ const disposers = [
43
53
  createEventSubscriber(client, 'message/onMessageCreated', 'local.message.created', payload =>
44
- callback(payload.messages[0]),
45
- );
46
- }
54
+ payload.messages.forEach(message => {
55
+ callback(message);
56
+ }),
57
+ ),
58
+ ];
47
59
 
48
60
  return () => {
49
61
  disposers.forEach(fn => fn());
@@ -1,7 +1,7 @@
1
1
  import { client, generateRawMessage, pause } from '~/utils/tests';
2
2
  import { convertFromRaw } from '~/messageRepository/utils';
3
3
  import {
4
- onMessageCreated,
4
+ onMessageCreatedMqtt,
5
5
  onMessageUpdated,
6
6
  onMessageDeleted,
7
7
  onMessageFlagged,
@@ -18,7 +18,7 @@ describe('Message Events', () => {
18
18
  const cases: Array<
19
19
  [(callback: Amity.Listener<Amity.Message>) => Amity.Unsubscriber, keyof Amity.MqttMessageEvents]
20
20
  > = [
21
- [onMessageCreated, 'message.created'],
21
+ [onMessageCreatedMqtt, 'message.created'],
22
22
  [onMessageUpdated, 'message.updated'],
23
23
  [onMessageDeleted, 'message.deleted'],
24
24
  [onMessageFlagged, 'message.flagged'],
@@ -3,7 +3,7 @@ import { pullFromCache, pushToCache } from '~/cache/api';
3
3
  import { MessagePaginationController } from './MessagePaginationController';
4
4
  import { MessageQueryStreamController } from './MessageQueryStreamController';
5
5
  import {
6
- onMessageCreated,
6
+ onMessageCreatedMqtt,
7
7
  onMessageDeleted,
8
8
  onMessageFlagCleared,
9
9
  onMessageFlagged,
@@ -75,7 +75,7 @@ export class MessageLiveCollectionController extends LiveCollectionController<
75
75
 
76
76
  startSubscription() {
77
77
  return this.queryStreamController.subscribeRTE([
78
- { fn: onMessageCreated, action: 'onCreate' },
78
+ { fn: onMessageCreatedMqtt, action: 'onCreate' },
79
79
  { fn: onMessageDeleted, action: 'onDelete' },
80
80
  { fn: onMessageUpdated, action: 'onUpdate' },
81
81
  { fn: onMessageFlagged, action: 'onFlagged' },
@@ -2,7 +2,12 @@ import { getActiveClient } from '~/client/api';
2
2
  import { onMessageMarked, onMessageMarkerFetched } from '~/marker/events';
3
3
  import { convertEventPayload } from '~/utils/event';
4
4
 
5
- import { onMessageCreated, onMessageUpdated, onMessageDeleted } from '../events';
5
+ import {
6
+ onMessageCreatedMqtt,
7
+ onMessageUpdated,
8
+ onMessageDeleted,
9
+ onMessageCreatedLocal,
10
+ } from '../events';
6
11
 
7
12
  /**
8
13
  * ```js
@@ -48,7 +53,8 @@ export const observeMessages = (
48
53
  };
49
54
 
50
55
  disposers.push(
51
- onMessageCreated(data => router({ data, loading: false, origin: 'event' }, 'onCreate')),
56
+ onMessageCreatedMqtt(data => router({ data, loading: false, origin: 'event' }, 'onCreate')),
57
+ onMessageCreatedLocal(data => router({ data, loading: false, origin: 'event' }, 'onCreate')),
52
58
  onMessageUpdated(data => router({ data, loading: false, origin: 'event' }, 'onUpdate')),
53
59
  onMessageDeleted(data => router({ data, loading: false, origin: 'event' }, 'onDelete')),
54
60
  convertEventPayload(
@@ -92,6 +92,9 @@ const preUpdateMessageCache = (rawPayload: Amity.MessagePayload) => {
92
92
  });
93
93
  };
94
94
 
95
+ const DEBOUNCE_TIME = 2000;
96
+ const currentDebounceMap: Record<string, NodeJS.Timeout | undefined> = {};
97
+
95
98
  export const prepareMessagePayload = async (
96
99
  payload: Amity.MessagePayload,
97
100
  event?: keyof Amity.MqttMessageEvents,
@@ -102,11 +105,18 @@ export const prepareMessagePayload = async (
102
105
  // since the get markers method requires a channel cache to function with the reducer.
103
106
  preUpdateMessageCache(payload);
104
107
 
105
- try {
106
- await getMessageMarkers(markerIds);
107
- } catch (e) {
108
- // empty block (from the spec, allow marker fetch to fail without having to do anything)
108
+ const markerIdsKey = markerIds.join('');
109
+
110
+ if (currentDebounceMap[markerIdsKey]) {
111
+ clearTimeout(currentDebounceMap[markerIdsKey]);
109
112
  }
113
+ currentDebounceMap[markerIdsKey] = setTimeout(() => {
114
+ try {
115
+ getMessageMarkers(markerIds);
116
+ } catch (_error) {
117
+ // do nothing
118
+ }
119
+ }, DEBOUNCE_TIME);
110
120
  }
111
121
 
112
122
  const { messageFeeds, ...restPayload } = payload;
@@ -1,6 +1,7 @@
1
1
  export const enum STORY_KEY_CACHE {
2
2
  STORY = 'story',
3
3
  SYNC_STATE = 'story-sync-state',
4
+ STORY_TARGET_IDS = 'story-target-ids',
4
5
  IS_SEEN = 'story-seen',
5
6
  LAST_SEEN = 'story-last-seen',
6
7
  EXPIRE = 'story-expire',
@@ -0,0 +1,113 @@
1
+ import hash from 'object-hash';
2
+ import { LiveCollectionController } from '~/core/liveCollection/LiveCollectionController';
3
+ import { pullFromCache, pushToCache } from '~/cache/api';
4
+ import { LinkedObject } from '~/utils/linkedObject';
5
+ import { sortByFirstCreated, sortByLastCreated } from '~/core/query';
6
+ import { onStoryCreated } from '~/storyRepository/events/onStoryCreated';
7
+ import { onStoryUpdated } from '~/storyRepository/events/onStoryUpdated';
8
+ import { onStoryDeleted } from '~/storyRepository/events/onStoryDeleted';
9
+ import { onStoryError } from '~/storyRepository/events/onStoryError';
10
+ import { STORY_KEY_CACHE } from '~/storyRepository/constants';
11
+ import { StoryQueryStreamController } from './StoryQueryStreamController';
12
+ import { StoryPaginationNoPageController } from './StoryPaginationNoPageController';
13
+
14
+ export class StoryLiveCollectionController extends LiveCollectionController<
15
+ 'story',
16
+ Amity.StoryLiveCollection,
17
+ Amity.Story,
18
+ StoryPaginationNoPageController
19
+ > {
20
+ private queryStreamController: StoryQueryStreamController;
21
+
22
+ private query: Amity.StoryLiveCollection;
23
+
24
+ constructor(
25
+ query: Amity.StoryLiveCollection,
26
+ callback: Amity.LiveCollectionCallback<Amity.Story>,
27
+ ) {
28
+ const queryStreamId = hash(query);
29
+ const cacheKey = [STORY_KEY_CACHE.STORY_TARGET_IDS, 'collection', queryStreamId];
30
+ const paginationController = new StoryPaginationNoPageController(query);
31
+
32
+ super(paginationController, queryStreamId, cacheKey, callback);
33
+
34
+ this.query = query;
35
+
36
+ this.queryStreamController = new StoryQueryStreamController(
37
+ this.query,
38
+ this.cacheKey,
39
+ this.notifyChange.bind(this),
40
+ paginationController,
41
+ );
42
+
43
+ this.paginationController = paginationController;
44
+ this.callback = callback.bind(this);
45
+ this.loadPage(true);
46
+ }
47
+
48
+ protected setup() {
49
+ const collection = pullFromCache<Amity.StoryLiveCollectionCache>(this.cacheKey)?.data;
50
+ if (!collection) {
51
+ pushToCache(this.cacheKey, {
52
+ data: [],
53
+ params: {},
54
+ });
55
+ }
56
+ }
57
+
58
+ protected persistModel(response: Amity.StoryPayload) {
59
+ this.queryStreamController.saveToMainDB(response);
60
+ }
61
+
62
+ protected persistQueryStream({
63
+ response,
64
+ direction,
65
+ refresh,
66
+ }: Amity.LiveCollectionPersistQueryStreamParams<'story'>) {
67
+ this.queryStreamController.appendToQueryStream(response, direction, refresh);
68
+ }
69
+
70
+ notifyChange({ origin, loading, error }: Amity.LiveCollectionNotifyParams) {
71
+ const collection = pullFromCache<Amity.StoryLiveCollectionCache>(this.cacheKey)?.data;
72
+ if (!collection) return;
73
+
74
+ let data = collection.data
75
+ .map(
76
+ referenceId =>
77
+ pullFromCache<Amity.InternalStory>([STORY_KEY_CACHE.STORY, 'get', referenceId])!, // Story use referenceId instead of storyId
78
+ )
79
+ .filter(Boolean)
80
+ .map(internStory => LinkedObject.story(internStory.data));
81
+
82
+ if (!this.shouldNotify(data) && origin === 'event') return;
83
+
84
+ data = this.applyFilter(data);
85
+
86
+ this.callback({
87
+ onNextPage: undefined,
88
+ data,
89
+ hasNextPage: false,
90
+ loading,
91
+ error,
92
+ });
93
+ }
94
+
95
+ private applyFilter(data: Amity.Story[]) {
96
+ const internalStories = data;
97
+
98
+ const orderBy = this.query?.options?.orderBy || 'desc';
99
+
100
+ return orderBy === 'asc'
101
+ ? internalStories.sort(sortByFirstCreated)
102
+ : internalStories.sort(sortByLastCreated);
103
+ }
104
+
105
+ startSubscription() {
106
+ return this.queryStreamController.subscribeRTE([
107
+ { fn: onStoryCreated, action: Amity.StoryActionType.OnCreate },
108
+ { fn: onStoryUpdated, action: Amity.StoryActionType.OnUpdate },
109
+ { fn: onStoryDeleted, action: Amity.StoryActionType.OnDelete },
110
+ { fn: onStoryError, action: Amity.StoryActionType.OnError },
111
+ ]);
112
+ }
113
+ }
@@ -0,0 +1,18 @@
1
+ /* eslint-disable no-use-before-define */
2
+
3
+ import { PaginationNoPageController } from '~/core/liveCollection/PaginationNoPageController';
4
+
5
+ export class StoryPaginationNoPageController extends PaginationNoPageController<
6
+ 'story',
7
+ Pick<Amity.StoryLiveCollection, 'targets'>
8
+ > {
9
+ async getRequest(queryParams: Pick<Amity.StoryLiveCollection, 'targets'>) {
10
+ const { data: queryResponse } = await this.http.get<Amity.StoryPayload>(
11
+ '/api/v4/stories-by-targets',
12
+ {
13
+ params: { ...queryParams },
14
+ },
15
+ );
16
+ return queryResponse;
17
+ }
18
+ }
@@ -0,0 +1,97 @@
1
+ import { QueryStreamController } from '~/core/liveCollection/QueryStreamController';
2
+ import { pullFromCache, pushToCache } from '~/cache/api';
3
+ import { getActiveClient } from '~/client';
4
+ import { ingestInCache } from '~/cache/api/ingestInCache';
5
+ import { updateLocalLastStoryExpires } from '~/storyRepository/utils/updateLocalLastStoryExpires';
6
+ import { mappingStoryIdToReferenceId } from '~/storyRepository/utils/mappingStoryIdToReferenceId';
7
+ import { convertStoryRawToInternal } from '~/storyRepository/utils/convertRawToStory';
8
+ import { StoryPaginationNoPageController } from './StoryPaginationNoPageController';
9
+
10
+ export class StoryQueryStreamController extends QueryStreamController<
11
+ Amity.StoryPayload,
12
+ Amity.StoryLiveCollection
13
+ > {
14
+ private notifyChange: (params: Amity.LiveCollectionNotifyParams) => void;
15
+
16
+ private paginationController: StoryPaginationNoPageController;
17
+
18
+ constructor(
19
+ query: Amity.StoryLiveCollection,
20
+ cacheKey: string[],
21
+ notifyChange: (params: Amity.LiveCollectionNotifyParams) => void,
22
+ paginationController: StoryPaginationNoPageController,
23
+ ) {
24
+ super(query, cacheKey);
25
+ this.notifyChange = notifyChange;
26
+ this.paginationController = paginationController;
27
+ }
28
+
29
+ // eslint-disable-next-line class-methods-use-this
30
+ saveToMainDB(response: Amity.StoryPayload) {
31
+ const client = getActiveClient();
32
+ const cachedAt = client.cache && Date.now();
33
+
34
+ const convertedData: Amity.StoryPayload = convertStoryRawToInternal(response);
35
+
36
+ if (client.cache) {
37
+ ingestInCache(convertedData, { cachedAt });
38
+
39
+ // Update local last story expires
40
+ updateLocalLastStoryExpires(convertedData.stories);
41
+
42
+ // Map storyId to referenceId
43
+ mappingStoryIdToReferenceId(convertedData.stories);
44
+ }
45
+ }
46
+
47
+ // eslint-disable-next-line class-methods-use-this
48
+ getStoryReferenceIds(story: Amity.RawStory) {
49
+ if (story?.referenceId) {
50
+ return story.referenceId;
51
+ }
52
+ return story.storyId;
53
+ }
54
+
55
+ appendToQueryStream(
56
+ response: Amity.StoryPayload,
57
+ direction: Amity.LiveCollectionPageDirection,
58
+ refresh = false,
59
+ ) {
60
+ if (refresh) {
61
+ pushToCache(this.cacheKey, {
62
+ data: response.stories.map(this.getStoryReferenceIds),
63
+ });
64
+ } else {
65
+ const collection = pullFromCache<Amity.StoryLiveCollectionCache>(this.cacheKey)?.data;
66
+ const stories = collection?.data ?? [];
67
+
68
+ pushToCache(this.cacheKey, {
69
+ ...collection,
70
+ data: [...new Set([...stories, ...response.stories.map(this.getStoryReferenceIds)])],
71
+ });
72
+ }
73
+ }
74
+
75
+ reactor(action: Amity.StoryActionType) {
76
+ return (payload: Amity.InternalStory[]) => {
77
+ const collection = pullFromCache<Amity.StoryLiveCollectionCache>(this.cacheKey)?.data;
78
+ if (!collection) return;
79
+
80
+ const newReferenceIds = payload.map(({ referenceId }) => referenceId);
81
+
82
+ collection.data = [...new Set([...newReferenceIds, ...collection.data])];
83
+ pushToCache(this.cacheKey, collection);
84
+
85
+ this.notifyChange({ origin: Amity.LiveDataOrigin.EVENT, loading: false });
86
+ };
87
+ }
88
+
89
+ subscribeRTE(
90
+ createSubscriber: {
91
+ fn: (reactor: Amity.Listener<Amity.InternalStory[]>) => Amity.Unsubscriber;
92
+ action: Amity.StoryActionType;
93
+ }[],
94
+ ) {
95
+ return createSubscriber.map(subscriber => subscriber.fn(this.reactor(subscriber.action)));
96
+ }
97
+ }
@@ -0,0 +1,32 @@
1
+ import { dropFromCache } from '~/cache/api';
2
+ import { getActiveClient } from '~/client';
3
+ import { ENABLE_CACHE_MESSAGE } from '~/utils/constants';
4
+ import { StoryLiveCollectionController } from './StoryLiveCollectionController';
5
+
6
+ export const getStoriesByTargetIds = (
7
+ params: { targets: Amity.StoryTargetQueryParam[]; options?: Amity.StorySortOption },
8
+ callback: Amity.LiveCollectionCallback<Amity.Story>,
9
+ config?: Amity.LiveCollectionConfig,
10
+ ) => {
11
+ const { log, cache, userId } = getActiveClient();
12
+
13
+ if (!cache) {
14
+ console.log(ENABLE_CACHE_MESSAGE);
15
+ }
16
+
17
+ const timestamp = Date.now();
18
+ log(`getStoriesByTargetIds(tmpid: ${timestamp}) > listen`);
19
+
20
+ const storyLiveCollection = new StoryLiveCollectionController(params, callback);
21
+ const disposers = storyLiveCollection.startSubscription();
22
+ const cacheKey = storyLiveCollection.getCacheKey();
23
+
24
+ disposers.push(() => {
25
+ dropFromCache(cacheKey);
26
+ });
27
+
28
+ return () => {
29
+ log(`getStoriesByTargetIds(tmpid: ${timestamp}) > dispose`);
30
+ disposers.forEach(fn => fn());
31
+ };
32
+ };
@@ -0,0 +1 @@
1
+ export * from './getStoriesByTargetIds';
@@ -2,3 +2,4 @@ export * from './getActiveStoriesByTarget';
2
2
  export * from './getStoryByStoryId';
3
3
  export * from './getTargetById';
4
4
  export * from './getTargetsByTargetIds';
5
+ export * from './getStoriesByTargetIds';