@amityco/ts-sdk-react-native 7.0.3-d7a9877.0 → 7.1.1-0e753e3.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 (95) hide show
  1. package/dist/@types/core/events.d.ts +2 -1
  2. package/dist/@types/core/events.d.ts.map +1 -1
  3. package/dist/@types/core/model.d.ts +2 -0
  4. package/dist/@types/core/model.d.ts.map +1 -1
  5. package/dist/@types/core/readReceipt.d.ts +12 -1
  6. package/dist/@types/core/readReceipt.d.ts.map +1 -1
  7. package/dist/@types/domains/channel.d.ts +10 -0
  8. package/dist/@types/domains/channel.d.ts.map +1 -1
  9. package/dist/@types/domains/client.d.ts +2 -0
  10. package/dist/@types/domains/client.d.ts.map +1 -1
  11. package/dist/channelRepository/api/markChannelsAsReadBySegment.d.ts +16 -0
  12. package/dist/channelRepository/api/markChannelsAsReadBySegment.d.ts.map +1 -0
  13. package/dist/channelRepository/events/onChannelDeleted.d.ts.map +1 -1
  14. package/dist/channelRepository/events/onChannelLeft.d.ts.map +1 -1
  15. package/dist/{marker → channelRepository}/events/onChannelUnreadUpdatedLocal.d.ts +2 -2
  16. package/dist/channelRepository/events/onChannelUnreadUpdatedLocal.d.ts.map +1 -0
  17. package/dist/channelRepository/internalApi/getTotalChannelsUnread.d.ts +11 -0
  18. package/dist/channelRepository/internalApi/getTotalChannelsUnread.d.ts.map +1 -0
  19. package/dist/channelRepository/observers/getChannel.d.ts.map +1 -1
  20. package/dist/channelRepository/observers/getChannels/ChannelLiveCollectionController.d.ts.map +1 -1
  21. package/dist/channelRepository/observers/getTotalChannelsUnread.d.ts +20 -0
  22. package/dist/channelRepository/observers/getTotalChannelsUnread.d.ts.map +1 -0
  23. package/dist/channelRepository/observers/index.d.ts +1 -0
  24. package/dist/channelRepository/observers/index.d.ts.map +1 -1
  25. package/dist/channelRepository/utils/constructChannelDynamicValue.d.ts.map +1 -1
  26. package/dist/channelRepository/utils/getLegacyChannelUnread.d.ts +2 -0
  27. package/dist/channelRepository/utils/getLegacyChannelUnread.d.ts.map +1 -0
  28. package/dist/channelRepository/utils/prepareChannelPayload.d.ts.map +1 -1
  29. package/dist/client/api/createClient.d.ts +1 -0
  30. package/dist/client/api/createClient.d.ts.map +1 -1
  31. package/dist/client/api/enableUnreadCount.d.ts.map +1 -1
  32. package/dist/client/api/login.d.ts.map +1 -1
  33. package/dist/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngine.d.ts +33 -0
  34. package/dist/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngine.d.ts.map +1 -0
  35. package/dist/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngineOnLoginHandler.d.ts +3 -0
  36. package/dist/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngineOnLoginHandler.d.ts.map +1 -0
  37. package/dist/client/utils/ReadReceiptSync/readReceiptSyncEngine.d.ts +2 -4
  38. package/dist/client/utils/ReadReceiptSync/readReceiptSyncEngine.d.ts.map +1 -1
  39. package/dist/client/utils/endpoints.d.ts +1 -0
  40. package/dist/client/utils/endpoints.d.ts.map +1 -1
  41. package/dist/client/utils/setClientToken.d.ts.map +1 -1
  42. package/dist/commentRepository/events/utils.d.ts.map +1 -1
  43. package/dist/core/events.d.ts +3 -3
  44. package/dist/core/events.d.ts.map +1 -1
  45. package/dist/core/model/idResolvers.d.ts.map +1 -1
  46. package/dist/index.cjs.js +616 -58
  47. package/dist/index.esm.js +616 -58
  48. package/dist/index.umd.js +3 -3
  49. package/dist/marker/events/onChannelUnreadInfoUpdatedLocal.d.ts +12 -0
  50. package/dist/marker/events/onChannelUnreadInfoUpdatedLocal.d.ts.map +1 -0
  51. package/dist/messageRepository/events/onMessageCreated.d.ts.map +1 -1
  52. package/dist/messageRepository/observers/getMessage.d.ts.map +1 -1
  53. package/dist/messageRepository/utils/markReadMessage.d.ts.map +1 -1
  54. package/dist/reactionRepository/api/addReaction.d.ts.map +1 -1
  55. package/dist/reactionRepository/api/removeReaction.d.ts.map +1 -1
  56. package/dist/reactionRepository/observers/getReactions/ReactionPaginationController.d.ts.map +1 -1
  57. package/package.json +1 -1
  58. package/src/@types/core/events.ts +2 -1
  59. package/src/@types/core/model.ts +4 -0
  60. package/src/@types/core/readReceipt.ts +14 -1
  61. package/src/@types/domains/channel.ts +13 -0
  62. package/src/@types/domains/client.ts +3 -0
  63. package/src/channelRepository/api/markChannelsAsReadBySegment.ts +29 -0
  64. package/src/channelRepository/events/onChannelDeleted.ts +17 -4
  65. package/src/channelRepository/events/onChannelLeft.ts +11 -3
  66. package/src/{marker → channelRepository}/events/onChannelUnreadUpdatedLocal.ts +3 -3
  67. package/src/channelRepository/internalApi/getTotalChannelsUnread.ts +38 -0
  68. package/src/channelRepository/observers/getChannel.ts +3 -1
  69. package/src/channelRepository/observers/getChannels/ChannelLiveCollectionController.ts +6 -1
  70. package/src/channelRepository/observers/getTotalChannelsUnread.ts +129 -0
  71. package/src/channelRepository/observers/index.ts +1 -0
  72. package/src/channelRepository/utils/constructChannelDynamicValue.ts +12 -2
  73. package/src/channelRepository/utils/getLegacyChannelUnread.ts +5 -0
  74. package/src/channelRepository/utils/prepareChannelPayload.ts +68 -17
  75. package/src/client/api/createClient.ts +7 -1
  76. package/src/client/api/enableUnreadCount.ts +1 -0
  77. package/src/client/api/login.ts +5 -1
  78. package/src/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngine.ts +267 -0
  79. package/src/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngineOnLoginHandler.ts +21 -0
  80. package/src/client/utils/ReadReceiptSync/readReceiptSyncEngine.ts +74 -99
  81. package/src/client/utils/endpoints.ts +1 -0
  82. package/src/client/utils/setClientToken.ts +8 -0
  83. package/src/commentRepository/events/utils.ts +73 -0
  84. package/src/core/model/idResolvers.ts +2 -0
  85. package/src/fileRepository/api/uploadFile.ts +1 -1
  86. package/src/fileRepository/api/uploadImage.ts +1 -1
  87. package/src/fileRepository/api/uploadVideo.ts +1 -1
  88. package/src/marker/events/onChannelUnreadInfoUpdatedLocal.ts +29 -0
  89. package/src/messageRepository/events/onMessageCreated.ts +45 -1
  90. package/src/messageRepository/observers/getMessage.ts +0 -1
  91. package/src/messageRepository/utils/markReadMessage.ts +10 -3
  92. package/src/reactionRepository/api/addReaction.ts +8 -0
  93. package/src/reactionRepository/api/removeReaction.ts +8 -0
  94. package/src/reactionRepository/observers/getReactions/ReactionPaginationController.ts +8 -0
  95. package/dist/marker/events/onChannelUnreadUpdatedLocal.d.ts.map +0 -1
@@ -51,7 +51,7 @@ export const createClient = (
51
51
  rteEnabled = true,
52
52
  }: {
53
53
  debugSession?: string;
54
- apiEndpoint?: { http?: string; mqtt?: string };
54
+ apiEndpoint?: { http?: string; mqtt?: string; upload?: string };
55
55
  prefixDeviceIdKey?: string;
56
56
  rteEnabled?: boolean;
57
57
  } = {},
@@ -64,9 +64,11 @@ export const createClient = (
64
64
  });
65
65
 
66
66
  const httpEndpoint = apiEndpoint?.http ?? computeUrl('http', apiRegion);
67
+ const uploadEndpoint = apiEndpoint?.upload ?? computeUrl('upload', apiRegion);
67
68
  const mqttEndpoint = apiEndpoint?.mqtt ?? computeUrl('mqtt', apiRegion);
68
69
 
69
70
  const http = createHttpTransport(httpEndpoint);
71
+ const upload = createHttpTransport(uploadEndpoint);
70
72
 
71
73
  let ws;
72
74
  let mqtt;
@@ -87,6 +89,8 @@ export const createClient = (
87
89
  const sessionHandler = undefined;
88
90
 
89
91
  const isUnreadCountEnabled = false;
92
+ // Legacy unread count is true by default
93
+ const useLegacyUnreadCount = true;
90
94
 
91
95
  const client = {
92
96
  version: `${VERSION}`,
@@ -104,6 +108,7 @@ export const createClient = (
104
108
  http,
105
109
  ws,
106
110
  mqtt,
111
+ upload,
107
112
  emitter,
108
113
 
109
114
  /*
@@ -125,6 +130,7 @@ export const createClient = (
125
130
  use: () => setActiveClient(client),
126
131
 
127
132
  isUnreadCountEnabled,
133
+ useLegacyUnreadCount,
128
134
 
129
135
  getMarkerSyncConsistentMode,
130
136
 
@@ -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
+ };
@@ -1,8 +1,7 @@
1
1
  import { pullFromCache, pushToCache, queryCache } from '~/cache/api';
2
2
  import { getActiveClient } from '../../api/activeClient';
3
- import { markAsReadBySegment } from '~/subChannelRepository/api/markAsReadBySegment';
4
- import { reCalculateChannelUnreadInfo } from '~/marker/utils/reCalculateChannelUnreadInfo';
5
3
  import { fireEvent } from '~/core/events';
4
+ import { markChannelsAsReadBySegment } from '~/channelRepository/api/markChannelsAsReadBySegment';
6
5
 
7
6
  export class MessageReadReceiptSyncEngine {
8
7
  private client: Amity.Client;
@@ -38,9 +37,9 @@ export class MessageReadReceiptSyncEngine {
38
37
  syncReadReceipts(): void {
39
38
  if (this.jobQueue.length === 0 || this.isActive === false) return;
40
39
 
41
- const readReceipt = this.getReadReceipt();
42
- if (readReceipt) {
43
- this.markReadApi(readReceipt);
40
+ const readReceipts = this.getReadReceipts();
41
+ if (readReceipts) {
42
+ this.markReadApi(readReceipts);
44
43
  }
45
44
  }
46
45
 
@@ -52,79 +51,64 @@ export class MessageReadReceiptSyncEngine {
52
51
 
53
52
  // Enqueue unsync read receipts to the job queue
54
53
  readReceipts?.forEach(({ data: readReceipt }) => {
55
- this.enqueueReadReceipt(readReceipt.subChannelId, readReceipt.latestSegment);
54
+ this.enqueueReadReceipt(readReceipt.channelId, readReceipt.latestSegment);
56
55
  });
57
56
  }
58
57
 
59
- private getReadReceipt(): Amity.ReadReceiptSyncJob | undefined {
60
- // Get first read receipt in queue
61
- const syncJob = this.jobQueue[0];
58
+ private getReadReceipts(): Amity.ReadReceiptSyncJob[] | undefined {
59
+ // get all read receipts from queue, now the queue is empty
60
+ const syncJob = this.jobQueue.splice(0, this.jobQueue.length);
61
+ if (syncJob.length === 0) return;
62
62
 
63
- if (!syncJob) return;
64
- // Skip when it's syncing
65
- if (syncJob.syncState === Amity.ReadReceiptSyncState.SYNCING) return;
66
-
67
- // Get readReceipt from cache by subChannelId
68
- const readReceipt = pullFromCache<Amity.ReadReceipt>([
69
- 'readReceipt',
70
- syncJob.subChannelId,
71
- ])?.data;
72
-
73
- if (!readReceipt) return;
74
-
75
- if (readReceipt?.latestSegment > readReceipt?.latestSyncSegment) {
76
- syncJob.segment = readReceipt.latestSegment;
77
- return syncJob;
78
- }
79
- // Clear all synced job in job queue
80
- this.removeSynedReceipt(readReceipt.subChannelId, readReceipt.latestSegment);
81
-
82
- // Recursion getReadReceipt() until get unsync read receipt or job queue is empty
83
- return this.getReadReceipt();
63
+ return syncJob.filter(job => {
64
+ const readReceipt = pullFromCache<Amity.ReadReceipt>(['readReceipt', job.channelId])?.data;
65
+ if (!readReceipt) return false;
66
+ if (readReceipt.latestSegment > readReceipt.latestSyncSegment) return true;
67
+ return false;
68
+ });
84
69
  }
85
70
 
86
- private async markReadApi(syncJob: Amity.ReadReceiptSyncJob): Promise<void> {
87
- const newSyncJob = syncJob;
88
- newSyncJob.syncState = Amity.ReadReceiptSyncState.SYNCING;
89
-
90
- const { subChannelId, segment } = newSyncJob;
71
+ private async markReadApi(syncJobs: Amity.ReadReceiptSyncJob[]): Promise<void> {
72
+ // constuct payload
73
+ // example: [{ channelId: 'channelId', readToSegment: 2 }]
74
+ const syncJobsPayload = syncJobs.map(job => {
75
+ return {
76
+ channelId: job.channelId,
77
+ readToSegment: job.segment,
78
+ };
79
+ });
91
80
 
92
- const response = await markAsReadBySegment({ subChannelId, readToSegment: segment });
81
+ const response = await markChannelsAsReadBySegment(syncJobsPayload);
93
82
 
94
83
  if (response) {
95
- this.removeSynedReceipt(syncJob.subChannelId, syncJob.segment);
96
-
97
- const readReceiptCache = pullFromCache<Amity.ReadReceipt>([
98
- 'readReceipt',
99
- subChannelId,
100
- ])?.data;
101
-
102
- pushToCache(['readReceipt', subChannelId], {
103
- ...readReceiptCache,
104
- latestSyncSegment: segment,
105
- });
106
- } else if (!response) {
107
- if (newSyncJob.retryCount > this.MAX_RETRY) {
108
- this.removeJobFromQueue(newSyncJob);
109
- } else {
110
- newSyncJob.retryCount += 1;
111
- newSyncJob.syncState = Amity.ReadReceiptSyncState.CREATED;
84
+ for (let i = 0; i < syncJobs.length; i += 1) {
85
+ // update lastestSyncSegment in read receipt cache
86
+ const cacheKey = ['readReceipt', syncJobs[i].channelId];
87
+ const readReceiptCache = pullFromCache<Amity.ReadReceipt>(cacheKey)?.data;
88
+
89
+ pushToCache(cacheKey, {
90
+ ...readReceiptCache,
91
+ latestSyncSegment: syncJobs[i].segment,
92
+ });
112
93
  }
113
- }
114
- }
94
+ } else {
95
+ for (let i = 0; i < syncJobs.length; i += 1) {
96
+ // push them back to queue if the syncing is failed and retry count is less than max retry
97
+ if (syncJobs[i].retryCount >= this.MAX_RETRY) return;
115
98
 
116
- private removeSynedReceipt(subChannelId: string, segment: number) {
117
- const syncJobs = this.jobQueue;
99
+ const updatedJob = {
100
+ ...syncJobs[i],
101
+ syncState: Amity.ReadReceiptSyncState.CREATED,
102
+ retryCount: syncJobs[i].retryCount + 1,
103
+ };
118
104
 
119
- syncJobs.forEach(job => {
120
- if (job.subChannelId === subChannelId && job.segment <= segment) {
121
- this.removeJobFromQueue(job);
105
+ this.enqueueJob(updatedJob);
122
106
  }
123
- });
107
+ }
124
108
  }
125
109
 
126
110
  private startObservingReadReceiptQueue(): void {
127
- if (this.client.isUnreadCountEnabled) {
111
+ if (this.client.useLegacyUnreadCount) {
128
112
  this.isActive = true;
129
113
  this.startSyncReadReceipt();
130
114
  }
@@ -133,8 +117,7 @@ export class MessageReadReceiptSyncEngine {
133
117
  private stopObservingReadReceiptQueue(): void {
134
118
  this.isActive = false;
135
119
 
136
- const syncJobs = this.jobQueue;
137
- syncJobs.map(job => {
120
+ this.jobQueue.map(job => {
138
121
  if (job.syncState === Amity.ReadReceiptSyncState.SYNCING) {
139
122
  return { ...job, syncState: Amity.ReadReceiptSyncState.CREATED };
140
123
  }
@@ -170,50 +153,51 @@ export class MessageReadReceiptSyncEngine {
170
153
  this.startObservingReadReceiptQueue();
171
154
  }
172
155
 
173
- markRead(subChannelId: string, segment: number): void {
174
- // Step 1: Optimistic update of subChannelUnreadInfo.readToSegment to message.segment
175
- const cacheKey = ['subChannelUnreadInfo', 'get', subChannelId];
176
- const subChannelUnreadInfo = pullFromCache<Amity.SubChannelUnreadInfo>(cacheKey)?.data;
156
+ markRead(channelId: string, segment: number): void {
157
+ // Step 1: Optimistic update of channelUnread.readToSegment to message.segment and update unreadCount value
158
+ const cacheKey = ['channelUnread', 'get', channelId];
159
+ const channelUnread = pullFromCache<Amity.ChannelUnread>(cacheKey)?.data;
177
160
 
178
- if (subChannelUnreadInfo && segment > subChannelUnreadInfo.readToSegment) {
179
- subChannelUnreadInfo.readToSegment = segment;
180
- subChannelUnreadInfo.unreadCount = Math.max(subChannelUnreadInfo.lastSegment - segment, 0);
161
+ if (
162
+ typeof channelUnread?.readToSegment === 'number' &&
163
+ channelUnread &&
164
+ segment > channelUnread.readToSegment
165
+ ) {
166
+ channelUnread.readToSegment = segment;
167
+ channelUnread.unreadCount = Math.max(channelUnread.lastSegment - segment, 0);
181
168
 
182
- const channelUnreadInfo = reCalculateChannelUnreadInfo(subChannelUnreadInfo.channelId);
183
- fireEvent('local.channelUnread.updated', channelUnreadInfo);
184
-
185
- pushToCache(cacheKey, subChannelUnreadInfo);
186
- fireEvent('local.subChannelUnread.updated', subChannelUnreadInfo);
169
+ pushToCache(cacheKey, channelUnread);
170
+ fireEvent('local.channelUnread.updated', channelUnread);
187
171
  }
188
172
 
189
173
  // Step 2: Enqueue the read receipt
190
- this.enqueueReadReceipt(subChannelId, segment);
174
+ this.enqueueReadReceipt(channelId, segment);
191
175
  }
192
176
 
193
- private enqueueReadReceipt(subChannelId: string, segment: number): void {
194
- const readReceipt = pullFromCache<Amity.ReadReceipt>(['readReceipt', subChannelId])?.data;
177
+ private enqueueReadReceipt(channelId: string, segment: number): void {
178
+ const readReceipt = pullFromCache<Amity.ReadReceipt>(['readReceipt', channelId])?.data;
195
179
 
196
- // Create new read receipt if it's not exists and add job to queue
180
+ // Create new read receipt if it's not exists and add the job to queue
197
181
  if (!readReceipt) {
198
- const readReceiptSubChannel: Amity.ReadReceipt = {
199
- subChannelId,
182
+ const readReceiptChannel: Amity.ReadReceipt = {
183
+ channelId,
200
184
  latestSegment: segment,
201
185
  latestSyncSegment: 0,
202
186
  };
203
-
204
- pushToCache(['readReceipt', subChannelId], readReceiptSubChannel);
187
+ pushToCache(['readReceipt', channelId], readReceiptChannel);
205
188
  } else if (readReceipt.latestSegment < segment) {
206
- pushToCache(['readReceipt', subChannelId], { ...readReceipt, latestSegment: segment });
189
+ // Update latestSegment in read receipt cache
190
+ pushToCache(['readReceipt', channelId], { ...readReceipt, latestSegment: segment });
207
191
  } else if (readReceipt.latestSyncSegment >= segment) {
208
192
  // Skip the job when lastSyncSegment > = segment
209
193
  return;
210
194
  }
211
195
 
212
- let syncJob: Amity.ReadReceiptSyncJob | null = this.getSyncJob(subChannelId);
196
+ let syncJob: Amity.ReadReceiptSyncJob | null = this.getSyncJob(channelId);
213
197
 
214
198
  if (syncJob === null || syncJob.syncState === Amity.ReadReceiptSyncState.SYNCING) {
215
199
  syncJob = {
216
- subChannelId,
200
+ channelId,
217
201
  segment,
218
202
  syncState: Amity.ReadReceiptSyncState.CREATED,
219
203
  retryCount: 0,
@@ -225,11 +209,9 @@ export class MessageReadReceiptSyncEngine {
225
209
  }
226
210
  }
227
211
 
228
- private getSyncJob(subChannelId: string): Amity.ReadReceiptSyncJob | null {
229
- const syncJobs = this.jobQueue;
230
-
231
- const targetJob = syncJobs.find(job => job.subChannelId === subChannelId);
232
-
212
+ private getSyncJob(channelId: string): Amity.ReadReceiptSyncJob | null {
213
+ const { jobQueue } = this;
214
+ const targetJob = jobQueue.find(job => job.channelId === channelId);
233
215
  return targetJob || null;
234
216
  }
235
217
 
@@ -242,13 +224,6 @@ export class MessageReadReceiptSyncEngine {
242
224
  this.jobQueue.push(syncJob);
243
225
  }
244
226
  }
245
-
246
- private removeJobFromQueue(item: Amity.ReadReceiptSyncJob) {
247
- const index = this.jobQueue.indexOf(item);
248
- if (index > -1) {
249
- this.jobQueue.splice(index, 1);
250
- }
251
- }
252
227
  }
253
228
 
254
229
  let instance: MessageReadReceiptSyncEngine | null = null;
@@ -6,6 +6,7 @@ export const API_REGIONS = {
6
6
 
7
7
  const URLS = {
8
8
  http: 'https://apix.{region}.amity.co',
9
+ upload: 'https://upload.{region}.amity.co',
9
10
  mqtt: 'wss://sse.{region}.amity.co:443/mqtt',
10
11
  } as const;
11
12
 
@@ -30,6 +30,14 @@ export const setClientToken = async (params: Parameters<typeof getToken>[0]) =>
30
30
  isUserDeleted: false,
31
31
  };
32
32
 
33
+ client.upload.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
34
+
35
+ client.upload.defaults.metadata = {
36
+ tokenExpiry: expiresAt,
37
+ isGlobalBanned: false,
38
+ isUserDeleted: false,
39
+ };
40
+
33
41
  // manually setup the token for ws transport
34
42
  if (client.ws) client.ws.io.opts.query = { token: accessToken };
35
43