@amityco/ts-sdk 6.18.1-c018952.0 → 6.19.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 (90) hide show
  1. package/.env +26 -26
  2. package/dist/@types/core/events.d.ts +4 -0
  3. package/dist/@types/core/events.d.ts.map +1 -1
  4. package/dist/@types/core/objectResolver.d.ts +13 -0
  5. package/dist/@types/core/objectResolver.d.ts.map +1 -0
  6. package/dist/@types/domains/channel.d.ts +12 -1
  7. package/dist/@types/domains/channel.d.ts.map +1 -1
  8. package/dist/analytic/api/syncEvent.d.ts.map +1 -1
  9. package/dist/analytic/service/analytic/AnalyticsEngine.d.ts +1 -0
  10. package/dist/analytic/service/analytic/AnalyticsEngine.d.ts.map +1 -1
  11. package/dist/analytic/service/analytic/AnalyticsEventCapturer.d.ts +1 -0
  12. package/dist/analytic/service/analytic/AnalyticsEventCapturer.d.ts.map +1 -1
  13. package/dist/analytic/service/analytic/AnalyticsEventSyncer.d.ts.map +1 -1
  14. package/dist/channelRepository/api/getChannel.d.ts.map +1 -1
  15. package/dist/channelRepository/api/getChannelByIds.d.ts.map +1 -1
  16. package/dist/channelRepository/events/onChannelFetched.d.ts +17 -0
  17. package/dist/channelRepository/events/onChannelFetched.d.ts.map +1 -0
  18. package/dist/channelRepository/observers/getChannels/ChannelLiveCollectionController.d.ts.map +1 -1
  19. package/dist/channelRepository/observers/getChannels/ChannelQueryStreamController.d.ts +3 -3
  20. package/dist/channelRepository/observers/getChannels/ChannelQueryStreamController.d.ts.map +1 -1
  21. package/dist/channelRepository/observers/observeChannel.d.ts +1 -1
  22. package/dist/channelRepository/observers/observeChannel.d.ts.map +1 -1
  23. package/dist/channelRepository/utils/prepareChannelPayload.d.ts.map +1 -1
  24. package/dist/channelRepository/utils/prepateUnreadCountInfo.d.ts +2 -0
  25. package/dist/channelRepository/utils/prepateUnreadCountInfo.d.ts.map +1 -0
  26. package/dist/channelRepository/utils/resolveUnreadInfoOnChannelEvent.d.ts +2 -0
  27. package/dist/channelRepository/utils/resolveUnreadInfoOnChannelEvent.d.ts.map +1 -0
  28. package/dist/client/api/login.d.ts.map +1 -1
  29. package/dist/client/utils/ObjectResolver/objectResolverEngine.d.ts +26 -0
  30. package/dist/client/utils/ObjectResolver/objectResolverEngine.d.ts.map +1 -0
  31. package/dist/client/utils/ObjectResolver/objectResolverEngineOnLoginHandler.d.ts +3 -0
  32. package/dist/client/utils/ObjectResolver/objectResolverEngineOnLoginHandler.d.ts.map +1 -0
  33. package/dist/client/utils/onOffline.d.ts +2 -0
  34. package/dist/client/utils/onOffline.d.ts.map +1 -0
  35. package/dist/core/events.d.ts +3 -3
  36. package/dist/core/events.d.ts.map +1 -1
  37. package/dist/index.cjs.js +446 -158
  38. package/dist/index.esm.js +446 -158
  39. package/dist/index.umd.js +4 -4
  40. package/dist/marker/api/getUserMessageFeedMarkers.d.ts.map +1 -1
  41. package/dist/marker/events/onUserFeedMarkerFetched.d.ts +17 -0
  42. package/dist/marker/events/onUserFeedMarkerFetched.d.ts.map +1 -0
  43. package/dist/marker/utils/addFlagSubChannelUnreadByChannelId.d.ts +2 -0
  44. package/dist/marker/utils/addFlagSubChannelUnreadByChannelId.d.ts.map +1 -0
  45. package/dist/marker/utils/addFlagSubChannelUnreadBySubChannelId.d.ts +2 -0
  46. package/dist/marker/utils/addFlagSubChannelUnreadBySubChannelId.d.ts.map +1 -0
  47. package/dist/messageRepository/observers/tests/markReadMessage.test.d.ts +2 -0
  48. package/dist/messageRepository/observers/tests/markReadMessage.test.d.ts.map +1 -0
  49. package/dist/reactionRepository/events/onReactionAdded.d.ts.map +1 -1
  50. package/dist/subChannelRepository/events/onSubChannelCreated.d.ts.map +1 -1
  51. package/dist/subChannelRepository/utils/persistOptimisticUnreadInfo.d.ts +1 -1
  52. package/dist/subChannelRepository/utils/persistOptimisticUnreadInfo.d.ts.map +1 -1
  53. package/dist/utils/liveObject.d.ts.map +1 -1
  54. package/dist/utils/tests/dummy/message.d.ts +5 -4
  55. package/dist/utils/tests/dummy/message.d.ts.map +1 -1
  56. package/package.json +1 -1
  57. package/src/@types/core/events.ts +5 -0
  58. package/src/@types/core/objectResolver.ts +14 -0
  59. package/src/@types/domains/channel.ts +12 -10
  60. package/src/analytic/api/syncEvent.ts +1 -3
  61. package/src/analytic/service/analytic/AnalyticsEngine.ts +6 -6
  62. package/src/analytic/service/analytic/AnalyticsEventCapturer.ts +9 -1
  63. package/src/analytic/service/analytic/AnalyticsEventSyncer.ts +9 -3
  64. package/src/channelRepository/api/getChannel.ts +5 -0
  65. package/src/channelRepository/api/getChannelByIds.ts +3 -0
  66. package/src/channelRepository/events/onChannelFetched.ts +28 -0
  67. package/src/channelRepository/observers/getChannels/ChannelLiveCollectionController.ts +86 -26
  68. package/src/channelRepository/observers/getChannels/ChannelQueryStreamController.ts +21 -8
  69. package/src/channelRepository/observers/observeChannel.ts +22 -9
  70. package/src/channelRepository/observers/observeChannels.ts +9 -9
  71. package/src/channelRepository/utils/prepareChannelPayload.ts +0 -13
  72. package/src/channelRepository/utils/prepateUnreadCountInfo.ts +20 -0
  73. package/src/channelRepository/utils/resolveUnreadInfoOnChannelEvent.ts +15 -0
  74. package/src/channelRepository/utils/tests/prepareChannelPayload.test.ts +2 -2
  75. package/src/client/api/login.ts +3 -1
  76. package/src/client/utils/ObjectResolver/objectResolverEngine.ts +143 -0
  77. package/src/client/utils/ObjectResolver/objectResolverEngineOnLoginHandler.ts +21 -0
  78. package/src/client/utils/onOffline.ts +15 -0
  79. package/src/marker/api/getUserMessageFeedMarkers.ts +3 -0
  80. package/src/marker/events/onUserFeedMarkerFetched.ts +37 -0
  81. package/src/marker/utils/addFlagSubChannelUnreadByChannelId.ts +19 -0
  82. package/src/marker/utils/addFlagSubChannelUnreadBySubChannelId.ts +13 -0
  83. package/src/messageRepository/observers/tests/markReadMessage.test.ts +225 -0
  84. package/src/reactionRepository/events/onReactionAdded.ts +1 -0
  85. package/src/subChannelRepository/events/onSubChannelCreated.ts +8 -1
  86. package/src/subChannelRepository/utils/persistOptimisticUnreadInfo.ts +24 -19
  87. package/src/utils/liveObject.ts +1 -0
  88. package/src/utils/tests/client.ts +1 -1
  89. package/src/utils/tests/dummy/channel.ts +4 -4
  90. package/src/utils/tests/dummy/message.ts +11 -2
@@ -56,25 +56,38 @@ export const observeChannel = <Events extends [Exclude<Amity.ChannelActionType,
56
56
  const disposers: Amity.Unsubscriber[] = [];
57
57
 
58
58
  disposers.push(
59
- onChannelUpdated(data => router({ data, loading: false, origin: 'event' }, 'onUpdate')),
60
- onChannelDeleted(data => router({ data, loading: false, origin: 'event' }, 'onDelete')),
61
- onChannelJoined(data => router({ data, loading: false, origin: 'event' }, 'onJoin')),
62
- onChannelLeft(data => router({ data, loading: false, origin: 'event' }, 'onLeft')),
63
- onChannelMuted(data => router({ data, loading: false, origin: 'event' }, 'onMute')),
59
+ onChannelUpdated(data =>
60
+ router({ data, loading: false, origin: 'event' }, Amity.ChannelActionType.OnUpdate),
61
+ ),
62
+ onChannelDeleted(data =>
63
+ router({ data, loading: false, origin: 'event' }, Amity.ChannelActionType.OnDelete),
64
+ ),
65
+ onChannelJoined(data =>
66
+ router({ data, loading: false, origin: 'event' }, Amity.ChannelActionType.OnJoin),
67
+ ),
68
+ onChannelLeft(data =>
69
+ router({ data, loading: false, origin: 'event' }, Amity.ChannelActionType.OnLeft),
70
+ ),
71
+ onChannelMuted(data =>
72
+ router({ data, loading: false, origin: 'event' }, Amity.ChannelActionType.OnMute),
73
+ ),
64
74
  onChannelMemberAdded(data =>
65
- router({ data, loading: false, origin: 'event' }, 'onMemberAdded'),
75
+ router({ data, loading: false, origin: 'event' }, Amity.ChannelActionType.OnMemberAdded),
66
76
  ),
67
77
  onChannelMemberRemoved(data =>
68
- router({ data, loading: false, origin: 'event' }, 'onMemberRemoved'),
78
+ router({ data, loading: false, origin: 'event' }, Amity.ChannelActionType.OnMemberRemoved),
69
79
  ),
70
80
  convertEventPayload(
71
81
  onChannelMarkerFetched,
72
82
  'entityId',
73
83
  'channel',
74
- )(data => router({ data, loading: false, origin: 'event' }, 'onUpdate')),
84
+ )(data => router({ data, loading: false, origin: 'event' }, Amity.ChannelActionType.OnUpdate)),
75
85
  );
76
86
 
77
- runQuery(createQuery(getChannel, channelId), result => result.data && router(result, 'onFetch'));
87
+ runQuery(
88
+ createQuery(getChannel, channelId),
89
+ result => result.data && router(result, Amity.ChannelActionType.OnFetch),
90
+ );
78
91
 
79
92
  return () => {
80
93
  log(`observeChannel(tmpid: ${timestamp}) > dispose`);
@@ -46,19 +46,19 @@ export const observeChannels = (
46
46
  };
47
47
 
48
48
  disposers.push(
49
- onChannelCreated(data => router(data, 'onCreate')),
50
- onChannelUpdated(data => router(data, 'onUpdate')),
51
- onChannelDeleted(data => router(data, 'onDelete')),
52
- onChannelJoined(data => router(data, 'onJoin')),
53
- onChannelLeft(data => router(data, 'onLeft')),
54
- onChannelMuted(data => router(data, 'onMute')),
55
- onChannelMemberAdded(data => router(data, 'onMemberAdded')),
56
- onChannelMemberRemoved(data => router(data, 'onMemberRemoved')),
49
+ onChannelCreated(data => router(data, Amity.ChannelActionType.OnCreate)),
50
+ onChannelUpdated(data => router(data, Amity.ChannelActionType.OnUpdate)),
51
+ onChannelDeleted(data => router(data, Amity.ChannelActionType.OnDelete)),
52
+ onChannelJoined(data => router(data, Amity.ChannelActionType.OnJoin)),
53
+ onChannelLeft(data => router(data, Amity.ChannelActionType.OnLeft)),
54
+ onChannelMuted(data => router(data, Amity.ChannelActionType.OnMute)),
55
+ onChannelMemberAdded(data => router(data, Amity.ChannelActionType.OnMemberAdded)),
56
+ onChannelMemberRemoved(data => router(data, Amity.ChannelActionType.OnMemberRemoved)),
57
57
  convertEventPayload(
58
58
  onChannelMarkerFetched,
59
59
  'entityId',
60
60
  'channel',
61
- )(data => router(data, 'onUpdate')),
61
+ )(data => router(data, Amity.ChannelActionType.OnUpdate)),
62
62
  );
63
63
 
64
64
  return () => {
@@ -68,19 +68,6 @@ export const prepareChannelPayload = async (
68
68
  const client = getActiveClient();
69
69
  const networkPreviewSetting = await client.getMessagePreviewSetting(false);
70
70
 
71
- // if consistent mode is enabled, persist the unread count info to the cache
72
- if (client.isUnreadCountEnabled && client.getMarkerSyncConsistentMode()) {
73
- const queryPayload = await getUserMessageFeedMakers(
74
- rawPayload.channels.map(({ channelId }) => channelId),
75
- );
76
-
77
- const { feedMarkers, userFeedMarkers } = queryPayload;
78
- persistUnreadCountInfo({
79
- feedMarkers,
80
- userFeedMarkers,
81
- });
82
- }
83
-
84
71
  if (
85
72
  options.isMessagePreviewUpdated &&
86
73
  networkPreviewSetting !== Amity.MessagePreviewSetting.NO_MESSAGE_PREVIEW &&
@@ -0,0 +1,20 @@
1
+ import { getActiveClient } from '~/client/api/activeClient';
2
+ import { getUserMessageFeedMakers } from '~/marker/api/getUserMessageFeedMarkers';
3
+ import { persistUnreadCountInfo } from '~/marker/utils/persistUnreadCountInfo';
4
+
5
+ export const prepareUnreadCountInfo = async (rawPayload: Amity.ChannelPayload) => {
6
+ const client = getActiveClient();
7
+ // if consistent mode is enabled, persist the unread count info to the cache
8
+
9
+ const queryPayload = await getUserMessageFeedMakers(
10
+ rawPayload.channels.map(({ channelId }) => channelId),
11
+ );
12
+
13
+ const { feedMarkers, userFeedMarkers } = queryPayload;
14
+ persistUnreadCountInfo({
15
+ feedMarkers,
16
+ userFeedMarkers,
17
+ });
18
+
19
+ client.log('channel/prepareUnreadCountInfo', rawPayload.channels);
20
+ };
@@ -0,0 +1,15 @@
1
+ import { getActiveClient } from '~/client/api/activeClient';
2
+ import ObjectResolverEngine from '~/client/utils/ObjectResolver/objectResolverEngine';
3
+
4
+ export const resolveUnreadInfoOnChannelEvent = (
5
+ channel: Amity.InternalChannel,
6
+ callback: Amity.Listener<Amity.InternalChannel>,
7
+ ) => {
8
+ const client = getActiveClient();
9
+
10
+ if (client.isUnreadCountEnabled && client.getMarkerSyncConsistentMode()) {
11
+ const objectResolverEngine = ObjectResolverEngine.getInstance();
12
+
13
+ objectResolverEngine.resolve(channel.channelId, Amity.ReferenceType.USER_MESSAGE_FEED_MARKER);
14
+ } else callback(channel);
15
+ };
@@ -60,9 +60,9 @@ const convertedChannelPayload: Amity.ProcessedChannelPayload = {
60
60
  isUnreadCountSupport: true,
61
61
  unreadCount: channelMarker1.unreadCount,
62
62
  hasMentioned: channelMarker1.hasMentioned,
63
+ isMentioned: channelMarker1.hasMentioned,
63
64
  messagePreviewId: undefined,
64
65
  subChannelsUnreadCount: channelMarker1.unreadCount,
65
- isMentioned: channelMarker1.hasMentioned,
66
66
  },
67
67
  {
68
68
  ...liveChannel,
@@ -70,9 +70,9 @@ const convertedChannelPayload: Amity.ProcessedChannelPayload = {
70
70
  isUnreadCountSupport: false,
71
71
  unreadCount: 0,
72
72
  hasMentioned: false,
73
+ isMentioned: false,
73
74
  messagePreviewId: undefined,
74
75
  subChannelsUnreadCount: 0,
75
- isMentioned: false,
76
76
  },
77
77
  {
78
78
  ...conversationChannel,
@@ -10,7 +10,8 @@ import { markReadEngineOnLoginHandler } from '~/subChannelRepository/utils/markR
10
10
  import { onUserDeleted } from '~/userRepository/events/onUserDeleted';
11
11
 
12
12
  import analyticsEngineOnLoginHandler from '~/analytic/utils/analyticsEngineOnLoginHandler';
13
- import readReceiptSyncEngineOnLoginHandler from '../utils/ReadReceiptSync/readReceiptSyncEngineOnLoginHandler';
13
+ import readReceiptSyncEngineOnLoginHandler from '~/client/utils/ReadReceiptSync/readReceiptSyncEngineOnLoginHandler';
14
+ import objectResolverEngineOnLoginHandler from '~/client/utils/ObjectResolver/objectResolverEngineOnLoginHandler';
14
15
  import { logout } from './logout';
15
16
 
16
17
  import { getActiveClient } from './activeClient';
@@ -198,6 +199,7 @@ export const login = async (
198
199
  markReadEngineOnLoginHandler(),
199
200
  analyticsEngineOnLoginHandler(),
200
201
  readReceiptSyncEngineOnLoginHandler(),
202
+ objectResolverEngineOnLoginHandler(),
201
203
  );
202
204
 
203
205
  const markerSyncUnsubscriber = await startMarkerSync();
@@ -0,0 +1,143 @@
1
+ import { onOnline } from '../onOnline';
2
+ import { getChannelByIds } from '~/channelRepository/api/getChannelByIds';
3
+
4
+ import { onOffline } from '../onOffline';
5
+ import { getUserMessageFeedMakers } from '~/marker/api/getUserMessageFeedMarkers';
6
+ import { SECOND } from '~/utils/constants';
7
+
8
+ class ObjectResolverEngine {
9
+ private readonly TIMER_INTERVAL_MS = SECOND;
10
+
11
+ private readonly BUFFER_ID_LIMIT = 100;
12
+
13
+ private buffer: Amity.ObjectIdBuffer = {
14
+ [Amity.ReferenceType.CHANNEL]: [],
15
+ [Amity.ReferenceType.USER_MESSAGE_FEED_MARKER]: [],
16
+ };
17
+
18
+ private timer: NodeJS.Timer | undefined;
19
+
20
+ private isResolvingTask = false;
21
+
22
+ private connectionListener: (() => void)[] = [];
23
+
24
+ private isConnected = true;
25
+
26
+ constructor() {
27
+ this.addConnectionListener();
28
+ }
29
+
30
+ startResolver() {
31
+ if (!this.timer) {
32
+ this.timer = setInterval(() => {
33
+ if (this.isConnected) this.resolveObjects();
34
+ }, this.TIMER_INTERVAL_MS);
35
+ }
36
+ }
37
+
38
+ stopResolver() {
39
+ if (this.timer) {
40
+ clearInterval(this.timer);
41
+ this.timer = undefined;
42
+ }
43
+ }
44
+
45
+ resolve(id: string, referenceType: Amity.ReferenceType) {
46
+ const objectIdList = this.getBuffer(referenceType);
47
+
48
+ if (objectIdList.includes(id)) return;
49
+
50
+ if (objectIdList.length >= this.BUFFER_ID_LIMIT) {
51
+ objectIdList.shift(); // Remove first element of the array
52
+ }
53
+
54
+ objectIdList.push(id);
55
+ }
56
+
57
+ private addConnectionListener() {
58
+ if (this.connectionListener.length > 0) return;
59
+ this.connectionListener.push(
60
+ onOnline(() => {
61
+ this.isConnected = true;
62
+ }),
63
+ );
64
+
65
+ this.connectionListener.push(
66
+ onOffline(() => {
67
+ this.isConnected = false;
68
+ }),
69
+ );
70
+ }
71
+
72
+ private removeConnectionListener() {
73
+ if (this.connectionListener.length > 0) this.connectionListener.forEach(listener => listener());
74
+ }
75
+
76
+ private resolveObjects() {
77
+ if (this.isResolvingTask) {
78
+ return;
79
+ }
80
+
81
+ this.isResolvingTask = true;
82
+
83
+ // Get first 100 ids for all reference type supported.
84
+ const channelIds = this.getBuffer(Amity.ReferenceType.CHANNEL);
85
+ const userMessageFeedMarkerIds = this.getBuffer(Amity.ReferenceType.USER_MESSAGE_FEED_MARKER);
86
+
87
+ // Clear buffer
88
+ this.clearBuffer();
89
+
90
+ // Send Requests.
91
+ // Incase of failure: Ignore
92
+ // Incase of success: Persist in Domain DB & Notify Live collection
93
+ if (channelIds.length > 0) getChannelByIds(channelIds);
94
+ if (userMessageFeedMarkerIds.length > 0) getUserMessageFeedMakers(userMessageFeedMarkerIds);
95
+
96
+ // After sending request
97
+ this.isResolvingTask = false;
98
+ }
99
+
100
+ private clearBuffer() {
101
+ this.buffer = {
102
+ [Amity.ReferenceType.CHANNEL]: [],
103
+ [Amity.ReferenceType.USER_MESSAGE_FEED_MARKER]: [],
104
+ };
105
+ }
106
+
107
+ private getBuffer(referenceType: Amity.ReferenceType) {
108
+ return this.buffer[referenceType];
109
+ }
110
+
111
+ // Session Management : SessionComponent
112
+ onSessionEstablished() {
113
+ this.startResolver();
114
+ this.addConnectionListener();
115
+ }
116
+
117
+ onSessionDestroyed() {
118
+ // Stop timer
119
+ this.stopResolver();
120
+
121
+ // Clear buffer
122
+ this.clearBuffer();
123
+
124
+ // Reset state
125
+ this.isResolvingTask = false;
126
+
127
+ // remove connection listener
128
+ this.removeConnectionListener();
129
+ }
130
+
131
+ onTokenExpired() {
132
+ this.stopResolver();
133
+ }
134
+ }
135
+
136
+ let instance: ObjectResolverEngine | null = null;
137
+
138
+ export default {
139
+ getInstance: () => {
140
+ if (!instance) instance = new ObjectResolverEngine();
141
+ return instance;
142
+ },
143
+ };
@@ -0,0 +1,21 @@
1
+ import { onSessionStateChange } from '~/client/events/onSessionStateChange';
2
+ import ObjectResolverEngine from './objectResolverEngine';
3
+
4
+ export default () => {
5
+ const objectResolverEngine = ObjectResolverEngine.getInstance();
6
+ objectResolverEngine.startResolver();
7
+
8
+ onSessionStateChange(state => {
9
+ if (state === Amity.SessionStates.ESTABLISHED) {
10
+ objectResolverEngine.onSessionEstablished();
11
+ } else if (state === Amity.SessionStates.TOKEN_EXPIRED) {
12
+ objectResolverEngine.onTokenExpired();
13
+ } else {
14
+ objectResolverEngine.onSessionDestroyed();
15
+ }
16
+ });
17
+
18
+ return () => {
19
+ objectResolverEngine.onSessionDestroyed();
20
+ };
21
+ };
@@ -0,0 +1,15 @@
1
+ export const onOffline = (callback: () => void) => {
2
+ if (typeof window !== 'undefined' && window.addEventListener) {
3
+ window.addEventListener('offline', callback);
4
+ return () => window.removeEventListener('offline', callback);
5
+ }
6
+
7
+ if (typeof document !== 'undefined' && document.addEventListener) {
8
+ document.addEventListener('offline', callback);
9
+ return () => document.removeEventListener('offline', callback);
10
+ }
11
+
12
+ // Handle unsupported environment
13
+ console.error('Unsupported environment');
14
+ return () => console.error('Unsupported environment');
15
+ };
@@ -1,4 +1,5 @@
1
1
  import { getActiveClient } from '~/client/api/activeClient';
2
+ import { fireEvent } from '~/core/events';
2
3
 
3
4
  /**
4
5
  * ```js
@@ -31,5 +32,7 @@ export const getUserMessageFeedMakers = async (
31
32
  },
32
33
  );
33
34
 
35
+ fireEvent('local.userMessageFeedMarker.fetched', { userMessageFeedMarker: data });
36
+
34
37
  return data;
35
38
  };
@@ -0,0 +1,37 @@
1
+ import { getActiveClient } from '~/client/api/activeClient';
2
+ import { createEventSubscriber } from '~/core/events';
3
+ import { persistUnreadCountInfo } from '../utils/persistUnreadCountInfo';
4
+
5
+ /**
6
+ * ```js
7
+ * import { onFeedMarkerUpdated } from '@amityco/ts-sdk'
8
+ * const dispose = onFeedMarkerUpdated(feedMarker => {
9
+ * // ...
10
+ * })
11
+ * ```
12
+ *
13
+ * Fired when an {@link Amity.UserMessageFeedMarkerPayload} has been updated
14
+ *
15
+ * @param callback The function to call when the event was fired
16
+ * @returns an {@link Amity.Unsubscriber} function to stop listening
17
+ *
18
+ * @category FeedMarker Events
19
+ */
20
+ export const onUserFeedMarkerFetched = (
21
+ callback: Amity.Listener<Amity.UserMessageFeedMarkerPayload>,
22
+ ): Amity.Unsubscriber => {
23
+ const client = getActiveClient();
24
+
25
+ const filter = (payload: Amity.Events['local.userMessageFeedMarker.fetched']) => {
26
+ // update sub channel unread info and channel unread info in cache
27
+ persistUnreadCountInfo(payload.userMessageFeedMarker);
28
+ callback(payload.userMessageFeedMarker);
29
+ };
30
+
31
+ return createEventSubscriber(
32
+ client,
33
+ 'feedMarker/onUserFeedMarkerFetched',
34
+ 'local.userMessageFeedMarker.fetched',
35
+ filter,
36
+ );
37
+ };
@@ -0,0 +1,19 @@
1
+ import { dropFromCache, pushToCache, queryCache } from '~/cache/api';
2
+
3
+ export const addFlagSubChannelUnreadByChannelId = (channelId: string) => {
4
+ const cacheKey = ['subChannelUnreadInfo', 'get'];
5
+ const cachedSubChannelUnread = queryCache<Amity.SubChannelUnreadInfo>(cacheKey);
6
+
7
+ if (cachedSubChannelUnread) {
8
+ const cachedTargetSubChannelUnread = cachedSubChannelUnread?.filter(
9
+ ({ data }) => data.channelId === channelId,
10
+ );
11
+
12
+ cachedTargetSubChannelUnread.forEach(({ key, data }) => {
13
+ pushToCache(key, {
14
+ ...data,
15
+ isDeleted: true,
16
+ });
17
+ });
18
+ }
19
+ };
@@ -0,0 +1,13 @@
1
+ import { pullFromCache, pushToCache } from '~/cache/api';
2
+
3
+ export const addFlagSubChannelUnreadBySubChannelId = (subChannelId: string) => {
4
+ const cacheKey = ['subChannelUnreadInfo', 'get', subChannelId];
5
+ const cachedSubChannelUnread = pullFromCache<Amity.SubChannelUnreadInfo>(cacheKey)?.data;
6
+
7
+ if (cachedSubChannelUnread) {
8
+ pushToCache(cacheKey, {
9
+ ...cachedSubChannelUnread,
10
+ isDeleted: true,
11
+ });
12
+ }
13
+ };
@@ -0,0 +1,225 @@
1
+ import {
2
+ client,
3
+ generateMessageMarker,
4
+ generateRawChannel,
5
+ generateRawMessage,
6
+ messages,
7
+ pause,
8
+ generateRawSubChannel,
9
+ } from '~/utils/tests';
10
+ import { getMessages } from '../getMessages';
11
+ import { getChannels } from '~/channelRepository/observers/getChannels';
12
+
13
+ import { enableUnreadCount } from '~/client';
14
+
15
+ import ReadReceiptSyncEngine from '~/client/utils/ReadReceiptSync/readReceiptSyncEngine';
16
+ import { ingestInCache } from '~/cache/api/ingestInCache';
17
+ import { pushToCache } from '~/cache/api';
18
+
19
+ describe('readReceiptSyncEngine', () => {
20
+ const channelsReturn: Amity.ChannelPayload = {
21
+ channels: [generateRawChannel({ channelId: 'channel-id' })],
22
+ channelUsers: [],
23
+ messagePreviews: [],
24
+ messageFeedsInfo: [],
25
+ users: [],
26
+ files: [],
27
+ };
28
+
29
+ const channelMarkerReturn: Amity.ChannelMarkerPayload = {
30
+ userEntityMarkers: [],
31
+ userMarkers: [],
32
+ };
33
+
34
+ const messageReturn: Amity.MessagePayload = {
35
+ messages: [
36
+ generateRawMessage({
37
+ messageId: messages.page1[0],
38
+ messageFeedId: 'channel-id',
39
+ channelId: 'channel-id',
40
+ }),
41
+ ],
42
+ messageFeeds: [],
43
+ reactions: [],
44
+ users: [],
45
+ files: [],
46
+ };
47
+
48
+ const messasgeMarkersReturn: Amity.MessageMarkerPayload = {
49
+ contentMarkers: [
50
+ generateMessageMarker({
51
+ contentId: messages.page1[0],
52
+ feedId: messageReturn.messages[0].messageFeedId,
53
+ creatorId: messageReturn.messages[0].creatorId,
54
+ }),
55
+ ],
56
+ feedMarkers: [],
57
+ userMarkers: [],
58
+ };
59
+
60
+ const userMessageFeedMarkersReturn: Amity.UserMessageFeedMarkerPayload = {
61
+ feedMarkers: [
62
+ {
63
+ feedId: 'channel-id',
64
+ entityId: 'channel-id',
65
+ lastSegment: 0,
66
+ isDeleted: false,
67
+ createdAt: new Date().toISOString(),
68
+ updatedAt: new Date().toISOString(),
69
+ },
70
+ ],
71
+ userFeedMarkers: [
72
+ {
73
+ userId: 'user-id',
74
+ entityId: 'channel-id',
75
+ feedId: 'channel-id',
76
+ readToSegment: 0,
77
+ deliveredToSegment: 0,
78
+ unreadCount: 0,
79
+ oldUnreadCount: 0,
80
+ lastMentionSegment: 0,
81
+ isMentioned: false,
82
+ createdAt: new Date().toISOString(),
83
+ updatedAt: new Date().toISOString(),
84
+ },
85
+ ],
86
+ };
87
+ const { subChannelId } = messages;
88
+
89
+ const rawNewMessage = generateRawMessage({
90
+ messageId: 'new-message-id',
91
+ messageFeedId: 'channel-id',
92
+ channelPublicId: 'channel-id',
93
+ channelId: 'channel-id',
94
+ });
95
+
96
+ client.use();
97
+ client.http.get = jest.fn().mockImplementation(params => {
98
+ if (params.includes('/api/v5/messages')) return { data: { ...messageReturn } };
99
+ if (params.includes('/api/v3/channels')) return { data: { ...channelsReturn } };
100
+ if (params.includes('/api/v1/markers/user-message-feed'))
101
+ return { data: { ...userMessageFeedMarkersReturn } };
102
+ if (params.includes('/api/v1/markers/channels'))
103
+ return {
104
+ data: { ...channelMarkerReturn },
105
+ };
106
+ if (params.includes('/api/v1/markers/messages'))
107
+ return {
108
+ data: { ...messasgeMarkersReturn },
109
+ };
110
+ return { data: {} };
111
+ });
112
+
113
+ it('should increase unreadCount when a message is received', async () => {
114
+ enableUnreadCount();
115
+
116
+ const callbackChannels = jest.fn();
117
+
118
+ getChannels({}, callbackChannels);
119
+
120
+ await pause();
121
+
122
+ client.emitter.emit('message.created', {
123
+ messages: [rawNewMessage],
124
+ messageFeeds: [
125
+ {
126
+ ...generateRawSubChannel({
127
+ channelId: channelsReturn.channels[0].channelId,
128
+ messageFeedId: channelsReturn.channels[0].channelId,
129
+ name: 'general',
130
+ }),
131
+ },
132
+ ],
133
+ users: [],
134
+ files: [],
135
+ reactions: [],
136
+ });
137
+
138
+ await pause();
139
+
140
+ expect(callbackChannels.mock.calls[2][0].data[0].unreadCount).toBe(1);
141
+ expect(callbackChannels.mock.calls[2][0].data[0].subChannelsUnreadCount).toBe(1);
142
+ });
143
+
144
+ it('should mark a message as read', async () => {
145
+ const readReceiptSyncEngine = ReadReceiptSyncEngine.getInstance();
146
+
147
+ readReceiptSyncEngine.startSyncReadReceipt();
148
+
149
+ const spyPut = jest.spyOn(client.http, 'put').mockResolvedValue({});
150
+ const callback = jest.fn();
151
+
152
+ getMessages({ subChannelId }, callback);
153
+
154
+ callback.mock.calls[1][0].data[0].markRead();
155
+
156
+ await pause(2000);
157
+
158
+ expect(spyPut).toHaveBeenCalledWith(`/api/v1/markers/message-feeds/${subChannelId}/mark-read`, {
159
+ readToSegment: 1,
160
+ });
161
+
162
+ readReceiptSyncEngine.onSessionDestroyed();
163
+
164
+ client.emitter.emit('message.created', {
165
+ messages: [rawNewMessage],
166
+ messageFeeds: [
167
+ {
168
+ ...generateRawSubChannel({
169
+ channelId: channelsReturn.channels[0].channelId,
170
+ messageFeedId: channelsReturn.channels[0].channelId,
171
+ name: 'general',
172
+ }),
173
+ },
174
+ ],
175
+ users: [],
176
+ files: [],
177
+ reactions: [],
178
+ });
179
+ });
180
+
181
+ it.only('should increase message readCount when a message is marked as read', async () => {
182
+ const callback = jest.fn();
183
+ getMessages({ subChannelId: 'channel-id' }, callback);
184
+
185
+ pushToCache(['messageMarker', 'get', 'channel-id#message11#user-id'], {
186
+ feedId: 'channel-id',
187
+ contentId: 'message11',
188
+ creatorId: 'user-id',
189
+ readCount: 0,
190
+ deliveredCount: 0,
191
+ createdAt: new Date().toISOString(),
192
+ updatedAt: new Date().toISOString(),
193
+ });
194
+
195
+ await pause();
196
+
197
+ client.emitter.emit('marker.marked-message', {
198
+ contentMarkers: [
199
+ {
200
+ feedId: 'channel-id',
201
+ contentId: 'message11',
202
+ creatorId: 'user-id',
203
+ readCount: 1,
204
+ deliveredCount: 0,
205
+ createdAt: new Date().toISOString(),
206
+ updatedAt: new Date().toISOString(),
207
+ },
208
+ ],
209
+ feedMarkers: [
210
+ {
211
+ feedId: 'channel-id',
212
+ entityId: 'channel-id',
213
+ lastSegment: 1,
214
+ isDeleted: false,
215
+ createdAt: new Date().toISOString(),
216
+ updatedAt: new Date().toISOString(),
217
+ },
218
+ ],
219
+ });
220
+
221
+ await pause();
222
+
223
+ expect(callback.mock.calls[2][0].data[0].readCount).toBe(1);
224
+ });
225
+ });
@@ -40,6 +40,7 @@ export const onReactionAdded = (
40
40
  * Important: Want to point out that reference type will never be 'message'
41
41
  * for onReactionAdded
42
42
  */
43
+
43
44
  if (referenceType === 'message') {
44
45
  const client = getActiveClient();
45
46