@amityco/ts-sdk 7.1.1-61f30ce0.0 → 7.1.1-67cf0d9.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 (146) hide show
  1. package/dist/@types/core/events.d.ts +2 -5
  2. package/dist/@types/core/events.d.ts.map +1 -1
  3. package/dist/@types/core/model.d.ts +2 -4
  4. package/dist/@types/core/model.d.ts.map +1 -1
  5. package/dist/@types/core/payload.d.ts +0 -18
  6. package/dist/@types/core/payload.d.ts.map +1 -1
  7. package/dist/@types/core/readReceipt.d.ts +12 -1
  8. package/dist/@types/core/readReceipt.d.ts.map +1 -1
  9. package/dist/@types/domains/channel.d.ts +10 -0
  10. package/dist/@types/domains/channel.d.ts.map +1 -1
  11. package/dist/@types/domains/client.d.ts +2 -0
  12. package/dist/@types/domains/client.d.ts.map +1 -1
  13. package/dist/channelRepository/api/markChannelsAsReadBySegment.d.ts +16 -0
  14. package/dist/channelRepository/api/markChannelsAsReadBySegment.d.ts.map +1 -0
  15. package/dist/channelRepository/events/onChannelDeleted.d.ts.map +1 -1
  16. package/dist/channelRepository/events/onChannelLeft.d.ts.map +1 -1
  17. package/dist/{marker → channelRepository}/events/onChannelUnreadUpdatedLocal.d.ts +2 -2
  18. package/dist/channelRepository/events/onChannelUnreadUpdatedLocal.d.ts.map +1 -0
  19. package/dist/channelRepository/internalApi/getTotalChannelsUnread.d.ts +11 -0
  20. package/dist/channelRepository/internalApi/getTotalChannelsUnread.d.ts.map +1 -0
  21. package/dist/channelRepository/observers/getChannel.d.ts.map +1 -1
  22. package/dist/channelRepository/observers/getChannels/ChannelLiveCollectionController.d.ts.map +1 -1
  23. package/dist/channelRepository/observers/getTotalChannelsUnread.d.ts +20 -0
  24. package/dist/channelRepository/observers/getTotalChannelsUnread.d.ts.map +1 -0
  25. package/dist/channelRepository/observers/index.d.ts +1 -0
  26. package/dist/channelRepository/observers/index.d.ts.map +1 -1
  27. package/dist/channelRepository/utils/constructChannelDynamicValue.d.ts.map +1 -1
  28. package/dist/channelRepository/utils/getLegacyChannelUnread.d.ts +2 -0
  29. package/dist/channelRepository/utils/getLegacyChannelUnread.d.ts.map +1 -0
  30. package/dist/channelRepository/utils/prepareChannelPayload.d.ts.map +1 -1
  31. package/dist/client/api/createClient.d.ts +1 -0
  32. package/dist/client/api/createClient.d.ts.map +1 -1
  33. package/dist/client/api/enableUnreadCount.d.ts.map +1 -1
  34. package/dist/client/api/login.d.ts.map +1 -1
  35. package/dist/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngine.d.ts +33 -0
  36. package/dist/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngine.d.ts.map +1 -0
  37. package/dist/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngineOnLoginHandler.d.ts +3 -0
  38. package/dist/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngineOnLoginHandler.d.ts.map +1 -0
  39. package/dist/client/utils/ReadReceiptSync/readReceiptSyncEngine.d.ts +2 -4
  40. package/dist/client/utils/ReadReceiptSync/readReceiptSyncEngine.d.ts.map +1 -1
  41. package/dist/client/utils/endpoints.d.ts +1 -0
  42. package/dist/client/utils/endpoints.d.ts.map +1 -1
  43. package/dist/client/utils/setClientToken.d.ts.map +1 -1
  44. package/dist/core/events.d.ts +3 -3
  45. package/dist/core/events.d.ts.map +1 -1
  46. package/dist/core/model/idResolvers.d.ts.map +1 -1
  47. package/dist/core/model/index.d.ts.map +1 -1
  48. package/dist/index.cjs.js +599 -477
  49. package/dist/index.d.ts +0 -1
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.esm.js +580 -457
  52. package/dist/index.umd.js +4 -4
  53. package/dist/marker/events/onChannelUnreadInfoUpdatedLocal.d.ts +12 -0
  54. package/dist/marker/events/onChannelUnreadInfoUpdatedLocal.d.ts.map +1 -0
  55. package/dist/messageRepository/events/onMessageCreated.d.ts.map +1 -1
  56. package/dist/messageRepository/observers/getMessage.d.ts.map +1 -1
  57. package/dist/messageRepository/utils/markReadMessage.d.ts.map +1 -1
  58. package/dist/utils/linkedObject/index.d.ts +0 -1
  59. package/dist/utils/linkedObject/index.d.ts.map +1 -1
  60. package/package.json +1 -1
  61. package/src/@types/core/events.ts +2 -6
  62. package/src/@types/core/model.ts +4 -6
  63. package/src/@types/core/payload.ts +0 -25
  64. package/src/@types/core/readReceipt.ts +14 -1
  65. package/src/@types/domains/channel.ts +13 -0
  66. package/src/@types/domains/client.ts +3 -0
  67. package/src/channelRepository/api/markChannelsAsReadBySegment.ts +29 -0
  68. package/src/channelRepository/events/onChannelDeleted.ts +17 -4
  69. package/src/channelRepository/events/onChannelLeft.ts +11 -3
  70. package/src/{marker → channelRepository}/events/onChannelUnreadUpdatedLocal.ts +3 -3
  71. package/src/channelRepository/internalApi/getTotalChannelsUnread.ts +38 -0
  72. package/src/channelRepository/observers/getChannel.ts +3 -1
  73. package/src/channelRepository/observers/getChannels/ChannelLiveCollectionController.ts +6 -1
  74. package/src/channelRepository/observers/getTotalChannelsUnread.ts +129 -0
  75. package/src/channelRepository/observers/index.ts +1 -0
  76. package/src/channelRepository/utils/constructChannelDynamicValue.ts +12 -2
  77. package/src/channelRepository/utils/getLegacyChannelUnread.ts +5 -0
  78. package/src/channelRepository/utils/prepareChannelPayload.ts +68 -17
  79. package/src/client/api/createClient.ts +7 -1
  80. package/src/client/api/enableUnreadCount.ts +1 -0
  81. package/src/client/api/login.ts +5 -1
  82. package/src/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngine.ts +267 -0
  83. package/src/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngineOnLoginHandler.ts +21 -0
  84. package/src/client/utils/ReadReceiptSync/readReceiptSyncEngine.ts +74 -99
  85. package/src/client/utils/endpoints.ts +1 -0
  86. package/src/client/utils/setClientToken.ts +8 -0
  87. package/src/core/model/idResolvers.ts +2 -3
  88. package/src/core/model/index.ts +0 -2
  89. package/src/fileRepository/api/uploadFile.ts +1 -1
  90. package/src/fileRepository/api/uploadImage.ts +1 -1
  91. package/src/fileRepository/api/uploadVideo.ts +1 -1
  92. package/src/index.ts +0 -2
  93. package/src/marker/events/onChannelUnreadInfoUpdatedLocal.ts +29 -0
  94. package/src/messageRepository/events/onMessageCreated.ts +45 -1
  95. package/src/messageRepository/observers/getMessage.ts +0 -1
  96. package/src/messageRepository/utils/markReadMessage.ts +10 -3
  97. package/src/utils/linkedObject/index.ts +0 -2
  98. package/dist/@types/domains/notification.d.ts +0 -81
  99. package/dist/@types/domains/notification.d.ts.map +0 -1
  100. package/dist/marker/events/onChannelUnreadUpdatedLocal.d.ts.map +0 -1
  101. package/dist/notificationTrayRepository/api/index.d.ts +0 -3
  102. package/dist/notificationTrayRepository/api/index.d.ts.map +0 -1
  103. package/dist/notificationTrayRepository/api/markNotificationItemsSeen.d.ts +0 -16
  104. package/dist/notificationTrayRepository/api/markNotificationItemsSeen.d.ts.map +0 -1
  105. package/dist/notificationTrayRepository/api/markNotificationTraySeen.d.ts +0 -19
  106. package/dist/notificationTrayRepository/api/markNotificationTraySeen.d.ts.map +0 -1
  107. package/dist/notificationTrayRepository/events/index.d.ts +0 -2
  108. package/dist/notificationTrayRepository/events/index.d.ts.map +0 -1
  109. package/dist/notificationTrayRepository/events/onNotificationTraySeenUpdated.d.ts +0 -17
  110. package/dist/notificationTrayRepository/events/onNotificationTraySeenUpdated.d.ts.map +0 -1
  111. package/dist/notificationTrayRepository/index.d.ts +0 -4
  112. package/dist/notificationTrayRepository/index.d.ts.map +0 -1
  113. package/dist/notificationTrayRepository/internalApi/getNotificationTraySeen.d.ts +0 -30
  114. package/dist/notificationTrayRepository/internalApi/getNotificationTraySeen.d.ts.map +0 -1
  115. package/dist/notificationTrayRepository/observers/getNotificationTrayItems/NotificationTrayItemsLiveCollectionController.d.ts +0 -13
  116. package/dist/notificationTrayRepository/observers/getNotificationTrayItems/NotificationTrayItemsLiveCollectionController.d.ts.map +0 -1
  117. package/dist/notificationTrayRepository/observers/getNotificationTrayItems/NotificationTrayItemsPaginationController.d.ts +0 -9
  118. package/dist/notificationTrayRepository/observers/getNotificationTrayItems/NotificationTrayItemsPaginationController.d.ts.map +0 -1
  119. package/dist/notificationTrayRepository/observers/getNotificationTrayItems/NotificationTrayItemsQuerystreamController.d.ts +0 -9
  120. package/dist/notificationTrayRepository/observers/getNotificationTrayItems/NotificationTrayItemsQuerystreamController.d.ts.map +0 -1
  121. package/dist/notificationTrayRepository/observers/getNotificationTrayItems.d.ts +0 -12
  122. package/dist/notificationTrayRepository/observers/getNotificationTrayItems.d.ts.map +0 -1
  123. package/dist/notificationTrayRepository/observers/getNotificationTraySeen.d.ts +0 -21
  124. package/dist/notificationTrayRepository/observers/getNotificationTraySeen.d.ts.map +0 -1
  125. package/dist/notificationTrayRepository/observers/index.d.ts +0 -3
  126. package/dist/notificationTrayRepository/observers/index.d.ts.map +0 -1
  127. package/dist/notificationTrayRepository/utils/prepareNotificationTrayItemsPayload.d.ts +0 -2
  128. package/dist/notificationTrayRepository/utils/prepareNotificationTrayItemsPayload.d.ts.map +0 -1
  129. package/dist/utils/linkedObject/notificationTrayLinkedObject.d.ts +0 -2
  130. package/dist/utils/linkedObject/notificationTrayLinkedObject.d.ts.map +0 -1
  131. package/src/@types/domains/notification.ts +0 -90
  132. package/src/notificationTrayRepository/api/index.ts +0 -2
  133. package/src/notificationTrayRepository/api/markNotificationItemsSeen.ts +0 -59
  134. package/src/notificationTrayRepository/api/markNotificationTraySeen.ts +0 -65
  135. package/src/notificationTrayRepository/events/index.ts +0 -1
  136. package/src/notificationTrayRepository/events/onNotificationTraySeenUpdated.ts +0 -36
  137. package/src/notificationTrayRepository/index.ts +0 -3
  138. package/src/notificationTrayRepository/internalApi/getNotificationTraySeen.ts +0 -81
  139. package/src/notificationTrayRepository/observers/getNotificationTrayItems/NotificationTrayItemsLiveCollectionController.ts +0 -96
  140. package/src/notificationTrayRepository/observers/getNotificationTrayItems/NotificationTrayItemsPaginationController.ts +0 -31
  141. package/src/notificationTrayRepository/observers/getNotificationTrayItems/NotificationTrayItemsQuerystreamController.ts +0 -68
  142. package/src/notificationTrayRepository/observers/getNotificationTrayItems.ts +0 -44
  143. package/src/notificationTrayRepository/observers/getNotificationTraySeen.ts +0 -43
  144. package/src/notificationTrayRepository/observers/index.ts +0 -2
  145. package/src/notificationTrayRepository/utils/prepareNotificationTrayItemsPayload.ts +0 -12
  146. package/src/utils/linkedObject/notificationTrayLinkedObject.ts +0 -28
@@ -5,9 +5,8 @@ import { getChannelMarkers } from '~/marker/api/getChannelMarkers';
5
5
  import { updateChannelMessagePreviewCache } from '~/messagePreview/utils';
6
6
  import { getActiveClient } from '~/client/api/activeClient';
7
7
  import { pullFromCache } from '~/cache/api/pullFromCache';
8
- import { getSubChannelsUnreadCount } from './getSubChannelsUnreadCount';
9
- import { getChannelIsMentioned } from './getChannelIsMentioned';
10
8
  import { convertRawUserToInternalUser } from '~/userRepository/utils/convertRawUserToInternalUser';
9
+ import { pushToCache } from '~/cache/api';
11
10
 
12
11
  export const MARKER_INCLUDED_CHANNEL_TYPE = ['broadcast', 'conversation', 'community'];
13
12
  export const isUnreadCountSupport = ({ type }: Pick<Amity.RawChannel, 'type'>) =>
@@ -48,6 +47,48 @@ export const preUpdateChannelCache = (
48
47
  });
49
48
  };
50
49
 
50
+ const updateChannelUnread = ({
51
+ currentUserId,
52
+ channels,
53
+ channelUsers,
54
+ }: {
55
+ currentUserId: Amity.User['userId'];
56
+ channels: Amity.RawChannel[];
57
+ channelUsers: Amity.RawMembership<'channel'>[];
58
+ }) => {
59
+ for (let i = 0; i < channels.length; i += 1) {
60
+ const cacheKey = ['channelUnread', 'get', channels[i].channelId];
61
+ const channelUser = channelUsers.find(
62
+ channelUser =>
63
+ channelUser.channelId === channels[i].channelId && channelUser.userId === currentUserId,
64
+ );
65
+
66
+ let unreadCount = 0;
67
+ let readToSegment = null;
68
+ let lastMentionedSegment = null;
69
+ let isMentioned = false;
70
+
71
+ if (channelUser) {
72
+ readToSegment = channelUser.readToSegment;
73
+ lastMentionedSegment = channelUser.lastMentionedSegment;
74
+ unreadCount = Math.max(channels[i].messageCount - readToSegment, 0);
75
+ isMentioned = lastMentionedSegment > readToSegment;
76
+ }
77
+
78
+ const cacheChannelUnread: Amity.ChannelUnread = {
79
+ channelId: channels[i].channelId,
80
+ lastSegment: channels[i].messageCount,
81
+ readToSegment,
82
+ lastMentionedSegment,
83
+ unreadCount,
84
+ isMentioned,
85
+ isDeleted: channels[i].isDeleted || false,
86
+ };
87
+
88
+ pushToCache(cacheKey, cacheChannelUnread);
89
+ }
90
+ };
91
+
51
92
  export const prepareChannelPayload = async (
52
93
  rawPayload: Amity.ChannelPayload,
53
94
  options: { isMessagePreviewUpdated?: boolean } = { isMessagePreviewUpdated: true },
@@ -64,28 +105,38 @@ export const prepareChannelPayload = async (
64
105
  updateChannelMessagePreviewCache(rawPayload);
65
106
  }
66
107
 
67
- const markerIds = rawPayload.channels
68
- // filter channel by type. Only conversation, community and broadcast type are included.
69
- .filter(isUnreadCountSupport)
70
- .map(({ channelInternalId }) => channelInternalId);
71
-
72
- if (markerIds.length > 0) {
73
- // since the get markers method requires a channel cache to function with the reducer.
74
- preUpdateChannelCache(rawPayload, { isMessagePreviewUpdated: options.isMessagePreviewUpdated });
75
-
76
- try {
77
- await getChannelMarkers(markerIds);
78
- } catch (e) {
79
- // empty block (from the spec, allow marker fetch to fail without having to do anything)
108
+ if (client.useLegacyUnreadCount) {
109
+ updateChannelUnread({
110
+ channels: rawPayload.channels,
111
+ channelUsers: rawPayload.channelUsers,
112
+ currentUserId: client.userId!,
113
+ });
114
+ } else {
115
+ const markerIds = rawPayload.channels
116
+ // filter channel by type. Only conversation, community and broadcast type are included.
117
+ .filter(isUnreadCountSupport)
118
+ .map(({ channelInternalId }) => channelInternalId);
119
+
120
+ if (markerIds.length > 0) {
121
+ // since the get markers method requires a channel cache to function with the reducer.
122
+ preUpdateChannelCache(rawPayload, {
123
+ isMessagePreviewUpdated: options.isMessagePreviewUpdated,
124
+ });
125
+
126
+ try {
127
+ await getChannelMarkers(markerIds);
128
+ } catch (e) {
129
+ // empty block (from the spec, allow marker fetch to fail without having to do anything)
130
+ }
80
131
  }
81
132
  }
82
133
 
83
- // attach marker to channel
134
+ // convert raw channel to internal channel
84
135
  const channels = rawPayload.channels.map(payload =>
85
136
  convertFromRaw(payload, { isMessagePreviewUpdated: options.isMessagePreviewUpdated }),
86
137
  );
87
138
 
88
- // user marker to channel users
139
+ // convert raw channel user to membership (add user object)
89
140
  const channelUsers: Amity.Membership<'channel'>[] = rawPayload.channelUsers.map(channelUser => {
90
141
  return convertRawMembershipToMembership<'channel'>(channelUser);
91
142
  });
@@ -50,7 +50,7 @@ export const createClient = (
50
50
  rteEnabled = true,
51
51
  }: {
52
52
  debugSession?: string;
53
- apiEndpoint?: { http?: string; mqtt?: string };
53
+ apiEndpoint?: { http?: string; mqtt?: string; upload?: string };
54
54
  prefixDeviceIdKey?: string;
55
55
  rteEnabled?: boolean;
56
56
  } = {},
@@ -63,9 +63,11 @@ export const createClient = (
63
63
  });
64
64
 
65
65
  const httpEndpoint = apiEndpoint?.http ?? computeUrl('http', apiRegion);
66
+ const uploadEndpoint = apiEndpoint?.upload ?? computeUrl('upload', apiRegion);
66
67
  const mqttEndpoint = apiEndpoint?.mqtt ?? computeUrl('mqtt', apiRegion);
67
68
 
68
69
  const http = createHttpTransport(httpEndpoint);
70
+ const upload = createHttpTransport(uploadEndpoint);
69
71
 
70
72
  let ws;
71
73
  let mqtt;
@@ -86,6 +88,8 @@ export const createClient = (
86
88
  const sessionHandler = undefined;
87
89
 
88
90
  const isUnreadCountEnabled = false;
91
+ // Legacy unread count is true by default
92
+ const useLegacyUnreadCount = true;
89
93
 
90
94
  const client = {
91
95
  version: `${VERSION}`,
@@ -103,6 +107,7 @@ export const createClient = (
103
107
  http,
104
108
  ws,
105
109
  mqtt,
110
+ upload,
106
111
  emitter,
107
112
 
108
113
  /*
@@ -122,6 +127,7 @@ export const createClient = (
122
127
  use: () => setActiveClient(client),
123
128
 
124
129
  isUnreadCountEnabled,
130
+ useLegacyUnreadCount,
125
131
 
126
132
  getMarkerSyncConsistentMode,
127
133
 
@@ -17,6 +17,7 @@ export const enableUnreadCount = () => {
17
17
  if (client.isUnreadCountEnabled) return false;
18
18
 
19
19
  client.isUnreadCountEnabled = true;
20
+ client.useLegacyUnreadCount = false;
20
21
 
21
22
  client.emitter.emit('unreadCountEnabled', true);
22
23
 
@@ -11,6 +11,7 @@ import { onUserDeleted } from '~/userRepository/events/onUserDeleted';
11
11
 
12
12
  import analyticsEngineOnLoginHandler from '~/analytic/utils/analyticsEngineOnLoginHandler';
13
13
  import readReceiptSyncEngineOnLoginHandler from '~/client/utils/ReadReceiptSync/readReceiptSyncEngineOnLoginHandler';
14
+ import legacyReadReceiptSyncEngineOnLoginHandler from '~/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngineOnLoginHandler';
14
15
  import objectResolverEngineOnLoginHandler from '~/client/utils/ObjectResolver/objectResolverEngineOnLoginHandler';
15
16
  import { logout } from './logout';
16
17
 
@@ -200,10 +201,13 @@ export const login = async (
200
201
 
201
202
  markReadEngineOnLoginHandler(),
202
203
  analyticsEngineOnLoginHandler(),
203
- readReceiptSyncEngineOnLoginHandler(),
204
204
  objectResolverEngineOnLoginHandler(),
205
205
  );
206
206
 
207
+ if (client.useLegacyUnreadCount) {
208
+ subscriptions.push(readReceiptSyncEngineOnLoginHandler());
209
+ } else subscriptions.push(legacyReadReceiptSyncEngineOnLoginHandler());
210
+
207
211
  const markerSyncUnsubscriber = await startMarkerSync();
208
212
  subscriptions.push(markerSyncUnsubscriber);
209
213
  }
@@ -0,0 +1,267 @@
1
+ import { pullFromCache, pushToCache, queryCache } from '~/cache/api';
2
+ import { getActiveClient } from '../../api/activeClient';
3
+ import { markAsReadBySegment } from '~/subChannelRepository/api/markAsReadBySegment';
4
+ import { reCalculateChannelUnreadInfo } from '~/marker/utils/reCalculateChannelUnreadInfo';
5
+ import { fireEvent } from '~/core/events';
6
+
7
+ export class LegacyMessageReadReceiptSyncEngine {
8
+ private client: Amity.Client;
9
+
10
+ private isActive = true;
11
+
12
+ private MAX_RETRY = 3;
13
+
14
+ private JOB_QUEUE_SIZE = 120;
15
+
16
+ private jobQueue: Amity.LegacyReadReceiptSyncJob[] = [];
17
+
18
+ private timer: NodeJS.Timer | undefined;
19
+
20
+ // Interval for message read receipt sync in seconds
21
+ private RECEIPT_SYNC_INTERVAL = 1;
22
+
23
+ constructor() {
24
+ this.client = getActiveClient();
25
+ // Get remaining unsync read receipts from cache
26
+ this.getUnsyncJobs();
27
+ }
28
+
29
+ // Call this when client call client.login
30
+ startSyncReadReceipt() {
31
+ // Start timer when start receipt sync
32
+ this.timer = setInterval(() => {
33
+ this.syncReadReceipts();
34
+ }, this.RECEIPT_SYNC_INTERVAL * 1000);
35
+ }
36
+
37
+ // Read receipt observer handling
38
+ syncReadReceipts(): void {
39
+ if (this.jobQueue.length === 0 || this.isActive === false) return;
40
+
41
+ const readReceipt = this.getReadReceipt();
42
+ if (readReceipt) {
43
+ this.markReadApi(readReceipt);
44
+ }
45
+ }
46
+
47
+ private getUnsyncJobs(): void {
48
+ // Get all read receipts that has latestSyncSegment < latestSegment
49
+ const readReceipts = queryCache<Amity.LegacyReadReceipt>(['legacyReadReceipt'])?.filter(
50
+ ({ data }) => {
51
+ return data.latestSyncSegment < data.latestSegment;
52
+ },
53
+ );
54
+
55
+ // Enqueue unsync read receipts to the job queue
56
+ readReceipts?.forEach(({ data: readReceipt }) => {
57
+ this.enqueueReadReceipt(readReceipt.subChannelId, readReceipt.latestSegment);
58
+ });
59
+ }
60
+
61
+ private getReadReceipt(): Amity.LegacyReadReceiptSyncJob | undefined {
62
+ // Get first read receipt in queue
63
+ const syncJob = this.jobQueue[0];
64
+
65
+ if (!syncJob) return;
66
+ // Skip when it's syncing
67
+ if (syncJob.syncState === Amity.ReadReceiptSyncState.SYNCING) return;
68
+
69
+ // Get readReceipt from cache by subChannelId
70
+ const readReceipt = pullFromCache<Amity.LegacyReadReceipt>([
71
+ 'legacyReadReceipt',
72
+ syncJob.subChannelId,
73
+ ])?.data;
74
+
75
+ if (!readReceipt) return;
76
+
77
+ if (readReceipt?.latestSegment > readReceipt?.latestSyncSegment) {
78
+ syncJob.segment = readReceipt.latestSegment;
79
+ return syncJob;
80
+ }
81
+ // Clear all synced job in job queue
82
+ this.removeSynedReceipt(readReceipt.subChannelId, readReceipt.latestSegment);
83
+
84
+ // Recursion getReadReceipt() until get unsync read receipt or job queue is empty
85
+ return this.getReadReceipt();
86
+ }
87
+
88
+ private async markReadApi(syncJob: Amity.LegacyReadReceiptSyncJob): Promise<void> {
89
+ const newSyncJob = syncJob;
90
+ newSyncJob.syncState = Amity.ReadReceiptSyncState.SYNCING;
91
+
92
+ const { subChannelId, segment } = newSyncJob;
93
+
94
+ const response = await markAsReadBySegment({ subChannelId, readToSegment: segment });
95
+
96
+ if (response) {
97
+ this.removeSynedReceipt(syncJob.subChannelId, syncJob.segment);
98
+
99
+ const readReceiptCache = pullFromCache<Amity.LegacyReadReceipt>([
100
+ 'legacyReadReceipt',
101
+ subChannelId,
102
+ ])?.data;
103
+
104
+ pushToCache(['legacyReadReceipt', subChannelId], {
105
+ ...readReceiptCache,
106
+ latestSyncSegment: segment,
107
+ });
108
+ } else if (!response) {
109
+ if (newSyncJob.retryCount > this.MAX_RETRY) {
110
+ this.removeJobFromQueue(newSyncJob);
111
+ } else {
112
+ newSyncJob.retryCount += 1;
113
+ newSyncJob.syncState = Amity.ReadReceiptSyncState.CREATED;
114
+ }
115
+ }
116
+ }
117
+
118
+ private removeSynedReceipt(subChannelId: string, segment: number) {
119
+ const syncJobs = this.jobQueue;
120
+
121
+ syncJobs.forEach(job => {
122
+ if (job.subChannelId === subChannelId && job.segment <= segment) {
123
+ this.removeJobFromQueue(job);
124
+ }
125
+ });
126
+ }
127
+
128
+ private startObservingReadReceiptQueue(): void {
129
+ if (this.client.isUnreadCountEnabled) {
130
+ this.isActive = true;
131
+ this.startSyncReadReceipt();
132
+ }
133
+ }
134
+
135
+ private stopObservingReadReceiptQueue(): void {
136
+ this.isActive = false;
137
+
138
+ const syncJobs = this.jobQueue;
139
+ syncJobs.map(job => {
140
+ if (job.syncState === Amity.ReadReceiptSyncState.SYNCING) {
141
+ return { ...job, syncState: Amity.ReadReceiptSyncState.CREATED };
142
+ }
143
+
144
+ return job;
145
+ });
146
+
147
+ if (this.timer) clearInterval(this.timer);
148
+ }
149
+
150
+ // Session Management
151
+ onSessionEstablished(): void {
152
+ this.startObservingReadReceiptQueue();
153
+ }
154
+
155
+ onSessionDestroyed(): void {
156
+ this.stopObservingReadReceiptQueue();
157
+ this.jobQueue = [];
158
+ }
159
+
160
+ onTokenExpired(): void {
161
+ this.stopObservingReadReceiptQueue();
162
+ }
163
+
164
+ // Network Connection Management
165
+ onNetworkOffline(): void {
166
+ // Stop observing to the read receipt queue.
167
+ this.stopObservingReadReceiptQueue();
168
+ }
169
+
170
+ onNetworkOnline(): void {
171
+ // Resume observing to the read receipt queue.
172
+ this.startObservingReadReceiptQueue();
173
+ }
174
+
175
+ markRead(subChannelId: string, segment: number): void {
176
+ // Step 1: Optimistic update of subChannelUnreadInfo.readToSegment to message.segment
177
+ const cacheKey = ['subChannelUnreadInfo', 'get', subChannelId];
178
+ const subChannelUnreadInfo = pullFromCache<Amity.SubChannelUnreadInfo>(cacheKey)?.data;
179
+
180
+ if (subChannelUnreadInfo && segment > subChannelUnreadInfo.readToSegment) {
181
+ subChannelUnreadInfo.readToSegment = segment;
182
+ subChannelUnreadInfo.unreadCount = Math.max(subChannelUnreadInfo.lastSegment - segment, 0);
183
+
184
+ const channelUnreadInfo = reCalculateChannelUnreadInfo(subChannelUnreadInfo.channelId);
185
+ fireEvent('local.channelUnreadInfo.updated', channelUnreadInfo);
186
+
187
+ pushToCache(cacheKey, subChannelUnreadInfo);
188
+ fireEvent('local.subChannelUnread.updated', subChannelUnreadInfo);
189
+ }
190
+
191
+ // Step 2: Enqueue the read receipt
192
+ this.enqueueReadReceipt(subChannelId, segment);
193
+ }
194
+
195
+ private enqueueReadReceipt(subChannelId: string, segment: number): void {
196
+ const readReceipt = pullFromCache<Amity.LegacyReadReceipt>([
197
+ 'legacyReadReceipt',
198
+ subChannelId,
199
+ ])?.data;
200
+
201
+ // Create new read receipt if it's not exists and add job to queue
202
+ if (!readReceipt) {
203
+ const readReceiptSubChannel: Amity.LegacyReadReceipt = {
204
+ subChannelId,
205
+ latestSegment: segment,
206
+ latestSyncSegment: 0,
207
+ };
208
+
209
+ pushToCache(['legacyReadReceipt', subChannelId], readReceiptSubChannel);
210
+ } else if (readReceipt.latestSegment < segment) {
211
+ pushToCache(['legacyReadReceipt', subChannelId], { ...readReceipt, latestSegment: segment });
212
+ } else if (readReceipt.latestSyncSegment >= segment) {
213
+ // Skip the job when lastSyncSegment > = segment
214
+ return;
215
+ }
216
+
217
+ let syncJob: Amity.LegacyReadReceiptSyncJob | null = this.getSyncJob(subChannelId);
218
+
219
+ if (syncJob === null || syncJob.syncState === Amity.ReadReceiptSyncState.SYNCING) {
220
+ syncJob = {
221
+ subChannelId,
222
+ segment,
223
+ syncState: Amity.ReadReceiptSyncState.CREATED,
224
+ retryCount: 0,
225
+ };
226
+
227
+ this.enqueueJob(syncJob);
228
+ } else if (syncJob.segment < segment) {
229
+ syncJob.segment = segment;
230
+ }
231
+ }
232
+
233
+ private getSyncJob(subChannelId: string): Amity.LegacyReadReceiptSyncJob | null {
234
+ const syncJobs = this.jobQueue;
235
+
236
+ const targetJob = syncJobs.find(job => job.subChannelId === subChannelId);
237
+
238
+ return targetJob || null;
239
+ }
240
+
241
+ private enqueueJob(syncJob: Amity.LegacyReadReceiptSyncJob) {
242
+ if (this.jobQueue.length < this.JOB_QUEUE_SIZE) {
243
+ this.jobQueue.push(syncJob);
244
+ } else {
245
+ // Remove oldest job when queue reach maximum capacity
246
+ this.jobQueue.shift();
247
+ this.jobQueue.push(syncJob);
248
+ }
249
+ }
250
+
251
+ private removeJobFromQueue(item: Amity.LegacyReadReceiptSyncJob) {
252
+ const index = this.jobQueue.indexOf(item);
253
+ if (index > -1) {
254
+ this.jobQueue.splice(index, 1);
255
+ }
256
+ }
257
+ }
258
+
259
+ let instance: LegacyMessageReadReceiptSyncEngine | null = null;
260
+
261
+ export default {
262
+ getInstance: () => {
263
+ if (!instance) instance = new LegacyMessageReadReceiptSyncEngine();
264
+
265
+ return instance;
266
+ },
267
+ };
@@ -0,0 +1,21 @@
1
+ import { onSessionStateChange } from '~/client/events/onSessionStateChange';
2
+ import LegacyReadReceiptSyncEngine from '~/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngine';
3
+
4
+ export default () => {
5
+ const readReceiptSyncEngine = LegacyReadReceiptSyncEngine.getInstance();
6
+ readReceiptSyncEngine.startSyncReadReceipt();
7
+
8
+ onSessionStateChange(state => {
9
+ if (state === Amity.SessionStates.ESTABLISHED) {
10
+ readReceiptSyncEngine.onSessionEstablished();
11
+ } else if (state === Amity.SessionStates.TOKEN_EXPIRED) {
12
+ readReceiptSyncEngine.onTokenExpired();
13
+ } else {
14
+ readReceiptSyncEngine.onSessionDestroyed();
15
+ }
16
+ });
17
+
18
+ return () => {
19
+ readReceiptSyncEngine.onSessionDestroyed();
20
+ };
21
+ };