@ermis-network/ermis-chat-sdk 1.0.9 → 2.0.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.
package/src/client.ts CHANGED
@@ -292,7 +292,11 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
292
292
 
293
293
  const setTokenPromise = this._setToken(connectionUser, connectionToken);
294
294
  this._setUser(connectionUser);
295
- this.state.updateUser({ id: connectionUser.id, name: connectionUser?.name || connectionUser.id, avatar: connectionUser?.avatar || '' });
295
+ this.state.updateUser({
296
+ id: connectionUser.id,
297
+ name: connectionUser?.name || connectionUser.id,
298
+ avatar: connectionUser?.avatar || '',
299
+ });
296
300
 
297
301
  const wsPromise = this.openConnection();
298
302
 
@@ -304,6 +308,21 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
304
308
  const result = await this.setUserPromise;
305
309
  // Call SSE after successful connect
306
310
  await this.connectToSSE();
311
+
312
+ // Automatically fetch full profile asynchronously and dispatch event
313
+ this.queryUser(connectionUser.id)
314
+ .then((fullProfile) => {
315
+ this.user = { ...this.user, ...fullProfile };
316
+ this.state.updateUser(this.user);
317
+ this.dispatchEvent({
318
+ type: 'user.updated',
319
+ me: this.user,
320
+ } as unknown as Event<ErmisChatGenerics>);
321
+ })
322
+ .catch((err) => {
323
+ this.logger('error', 'client:connectUser() - failed to fetch full user profile', { err });
324
+ });
325
+
307
326
  return result;
308
327
  } catch (err) {
309
328
  this.disconnectUser();
@@ -600,10 +619,11 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
600
619
  * @returns A Blob of the file content.
601
620
  */
602
621
  async downloadMedia(url: string): Promise<Blob> {
603
- const response = await this.axiosInstance.get(url, {
604
- responseType: 'blob',
605
- });
606
- return response.data as Blob;
622
+ const response = await fetch(url, { cache: 'no-store' });
623
+ if (!response.ok) {
624
+ throw new Error(`Failed to download media: ${response.statusText}`);
625
+ }
626
+ return await response.blob();
607
627
  }
608
628
 
609
629
  errorFromResponse(response: AxiosResponse<APIErrorResponse>): ErrorFromResponse<APIErrorResponse> {
@@ -709,21 +729,76 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
709
729
  };
710
730
 
711
731
  _updateMemberWatcherReferences = (user: UserResponse<ErmisChatGenerics>) => {
712
- const refMap = this.state.userChannelReferences[user.id] || {};
713
- for (const channelID in refMap) {
714
- const channel = this.activeChannels[channelID];
732
+ // Iterate through all active channels to ensure we update members even if they haven't sent messages yet
733
+ Object.values(this.activeChannels).forEach((channel) => {
715
734
  if (channel?.state) {
735
+ let hasChange = false;
716
736
  if (channel.state.members[user.id]) {
717
- channel.state.members[user.id].user = user;
737
+ channel.state.members = {
738
+ ...channel.state.members,
739
+ [user.id]: {
740
+ ...channel.state.members[user.id],
741
+ user,
742
+ },
743
+ };
744
+ hasChange = true;
745
+
746
+ // Update display name/image for 1-1 Messaging Channels
747
+ if (channel.data?.type === 'messaging') {
748
+ const members = Object.values(channel.state.members);
749
+ if (members.length === 2) {
750
+ const otherMember = members.find((m) => m.user?.id !== this.userID);
751
+ if (otherMember && otherMember.user?.id === user.id) {
752
+ channel.data.name = user.name || user.id;
753
+ channel.data.image = user.avatar || '';
754
+ }
755
+ }
756
+ }
718
757
  }
719
758
  if (channel.state.watchers[user.id]) {
720
- channel.state.watchers[user.id] = user;
759
+ channel.state.watchers = {
760
+ ...channel.state.watchers,
761
+ [user.id]: user,
762
+ };
763
+ hasChange = true;
721
764
  }
722
765
  if (channel.state.read[user.id]) {
723
- channel.state.read[user.id].user = user;
766
+ channel.state.read = {
767
+ ...channel.state.read,
768
+ [user.id]: {
769
+ ...channel.state.read[user.id],
770
+ user,
771
+ },
772
+ };
773
+ hasChange = true;
774
+ }
775
+
776
+ if (hasChange) {
777
+ // Trigger channel update for Sidebar and Header
778
+ channel._callChannelListeners({
779
+ type: 'channel.updated',
780
+ channel: channel.data,
781
+ cid: channel.cid,
782
+ } as any);
783
+
784
+ // Trigger member update specifically for Member List components
785
+ if (channel.state.members[user.id]) {
786
+ channel._callChannelListeners({
787
+ type: 'member.updated',
788
+ member: channel.state.members[user.id],
789
+ cid: channel.cid,
790
+ } as any);
791
+ }
792
+
793
+ // Trigger general user update at channel level
794
+ channel._callChannelListeners({
795
+ type: 'user.updated',
796
+ user: user,
797
+ cid: channel.cid,
798
+ } as any);
724
799
  }
725
800
  }
726
- }
801
+ });
727
802
  };
728
803
 
729
804
  _updateUserReferences = this._updateMemberWatcherReferences;
@@ -733,13 +808,29 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
733
808
 
734
809
  for (const channelID in refMap) {
735
810
  const channel = this.activeChannels[channelID];
736
-
737
811
  if (!channel) continue;
738
812
 
739
813
  const state = channel.state;
740
814
 
741
- /** update the messages from this user. */
815
+ /** Update the message objects from this user in the state. */
742
816
  state?.updateUserMessages(user);
817
+
818
+ // Trigger re-render for message list components
819
+ channel._callChannelListeners({
820
+ type: 'channel.updated',
821
+ channel: channel.data,
822
+ cid: channel.cid,
823
+ } as any);
824
+
825
+ // Force MessageList refresh by dispatching an update for the last message
826
+ const lastMessage = state?.messages[state.messages.length - 1];
827
+ if (lastMessage) {
828
+ channel._callChannelListeners({
829
+ type: 'message.updated',
830
+ message: lastMessage,
831
+ cid: channel.cid,
832
+ } as any);
833
+ }
743
834
  }
744
835
  };
745
836
 
@@ -822,7 +913,11 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
822
913
  const bTime = bLatest ? new Date(bLatest).getTime() : 0;
823
914
  return bTime - aTime;
824
915
  });
825
- parentChannel._callChannelListeners({ ...event, type: 'channel.updated', channel: parentChannel.data } as any);
916
+ parentChannel._callChannelListeners({
917
+ ...event,
918
+ type: 'channel.updated',
919
+ channel: parentChannel.data,
920
+ } as any);
826
921
  }
827
922
  }
828
923
  });
@@ -833,7 +928,9 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
833
928
  if (parentCid && this.activeChannels[parentCid]) {
834
929
  const parentChannel = this.activeChannels[parentCid];
835
930
  if (parentChannel.state?.topics && event.channel) {
836
- const topicIndex = parentChannel.state.topics.findIndex((t: any) => t.cid === event.cid || t.channel?.cid === event.cid);
931
+ const topicIndex = parentChannel.state.topics.findIndex(
932
+ (t: any) => t.cid === event.cid || t.channel?.cid === event.cid,
933
+ );
837
934
  if (topicIndex !== -1) {
838
935
  const t = parentChannel.state.topics[topicIndex] as any;
839
936
  if (t.data) {
@@ -844,7 +941,11 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
844
941
  Object.assign(t, event.channel);
845
942
  }
846
943
  }
847
- parentChannel._callChannelListeners({ ...event, type: 'channel.updated', channel: parentChannel.data } as any);
944
+ parentChannel._callChannelListeners({
945
+ ...event,
946
+ type: 'channel.updated',
947
+ channel: parentChannel.data,
948
+ } as any);
848
949
  }
849
950
  }
850
951
 
@@ -852,7 +953,11 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
852
953
  const topicChannel = this.activeChannels[event.cid];
853
954
  if (event.channel) {
854
955
  topicChannel.data = { ...topicChannel.data, ...event.channel };
855
- topicChannel._callChannelListeners({ ...event, type: 'channel.updated', channel: topicChannel.data } as any);
956
+ topicChannel._callChannelListeners({
957
+ ...event,
958
+ type: 'channel.updated',
959
+ channel: topicChannel.data,
960
+ } as any);
856
961
  }
857
962
  }
858
963
  });
@@ -865,14 +970,20 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
865
970
  if (parentCid && this.activeChannels[parentCid]) {
866
971
  const parentChannel = this.activeChannels[parentCid];
867
972
  if (parentChannel.state?.topics) {
868
- const topicIndex = parentChannel.state.topics.findIndex((t: any) => t.cid === event.cid || t.channel?.cid === event.cid);
973
+ const topicIndex = parentChannel.state.topics.findIndex(
974
+ (t: any) => t.cid === event.cid || t.channel?.cid === event.cid,
975
+ );
869
976
  if (topicIndex !== -1) {
870
977
  const t = parentChannel.state.topics[topicIndex] as any;
871
978
  if (t.data) t.data.is_closed_topic = isClosed;
872
979
  else if (t.channel) t.channel.is_closed_topic = isClosed;
873
980
  else t.is_closed_topic = isClosed;
874
981
  }
875
- parentChannel._callChannelListeners({ ...event, type: 'channel.updated', channel: parentChannel.data } as any);
982
+ parentChannel._callChannelListeners({
983
+ ...event,
984
+ type: 'channel.updated',
985
+ channel: parentChannel.data,
986
+ } as any);
876
987
  }
877
988
  }
878
989
 
@@ -886,8 +997,6 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
886
997
  });
887
998
  }
888
999
 
889
-
890
-
891
1000
  if (event.type === 'connection.recovered') {
892
1001
  postListenerCallbacks.push(() => {
893
1002
  // Auto-resend offline failed messages
@@ -944,14 +1053,11 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
944
1053
  tags: ['connection', 'client'],
945
1054
  });
946
1055
 
947
- const filter: ChannelFilters = {
948
- type: ['messaging', 'team'],
949
- };
950
- const sort: [] = [];
951
- const options = {
952
- message_limit: 25,
953
- };
954
-
1056
+ const {
1057
+ filter = { type: ['messaging', 'team', 'meeting'] } as ChannelFilters,
1058
+ sort = [],
1059
+ options = { message_limit: 1 },
1060
+ } = this.options.recoveryConfig || {};
955
1061
  await this.queryChannels(filter, sort, options);
956
1062
 
957
1063
  this.logger('info', 'client:recoverState() - Querying channels finished', { tags: ['connection', 'client'] });
@@ -1024,45 +1130,42 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
1024
1130
  const data = JSON.parse(event.data);
1025
1131
 
1026
1132
  this.logger('info', `client:connectToSSE() - SSE message received event : ${JSON.stringify(data)}`, { event });
1027
-
1028
1133
  if (data.type === 'AccountUserChainProjects') {
1029
- let user: UserResponse = {
1030
- name: data.name,
1134
+ const userInfo: UserResponse<ErmisChatGenerics> = {
1031
1135
  id: data.id,
1136
+ name: data.name,
1032
1137
  avatar: data.avatar,
1033
1138
  about_me: data.about_me,
1034
1139
  project_id: data.project_id,
1035
1140
  };
1036
1141
 
1037
- if (this.user?.id === user.id) {
1038
- this.user = { ...this.user, ...user };
1142
+ // 1. Update current user info if ID matches
1143
+ if (this.user?.id === userInfo.id) {
1144
+ this.user = { ...this.user, ...userInfo };
1039
1145
  }
1040
1146
 
1041
- this.state.updateUser(user);
1147
+ // 2. Update Client State
1148
+ this.state.updateUser(userInfo);
1042
1149
 
1043
- const userInfo = {
1044
- id: user.id,
1045
- name: user.name ? user.name : user.id,
1046
- avatar: user?.avatar || '',
1150
+ const minimalUserInfo = {
1151
+ id: userInfo.id,
1152
+ name: userInfo.name || userInfo.id,
1153
+ avatar: userInfo.avatar || '',
1047
1154
  };
1048
1155
 
1049
- this._updateMemberWatcherReferences(userInfo);
1050
- this._updateUserMessageReferences(userInfo);
1051
-
1052
- Object.values(this.activeChannels).forEach((channel) => {
1053
- if (channel.data?.type === 'messaging' && Object.keys(channel.state.members).length === 2) {
1054
- const otherMember = Object.values(channel.state.members).find((member) => member.user?.id !== this.userID);
1055
- if (otherMember && otherMember.user?.id === user.id) {
1056
- // Cập nhật tên và avatar channel theo user vừa đổi thông tin
1057
- channel.data.name = user.name || user.id;
1058
- channel.data.image = user.avatar || '';
1059
- }
1060
- }
1061
- });
1156
+ // 3. Update references and trigger re-renders
1157
+ this._updateMemberWatcherReferences(minimalUserInfo);
1158
+ this._updateUserMessageReferences(minimalUserInfo);
1062
1159
 
1063
1160
  if (onCallBack) {
1064
1161
  onCallBack(data);
1065
1162
  }
1163
+
1164
+ this.dispatchEvent({
1165
+ type: 'user.updated',
1166
+ user: userInfo,
1167
+ me: this.user?.id === userInfo.id ? this.user : undefined,
1168
+ } as any);
1066
1169
  }
1067
1170
  };
1068
1171
  this.eventSource.onerror = (event: any) => {
@@ -1204,6 +1307,20 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
1204
1307
  this.user.avatar = response.avatar;
1205
1308
  const new_user = { ...this.user, avatar: response.avatar };
1206
1309
  this.state.updateUser(new_user);
1310
+
1311
+ const userInfo = {
1312
+ id: this.user.id,
1313
+ name: this.user.name ? this.user.name : this.user.id,
1314
+ avatar: this.user?.avatar || '',
1315
+ };
1316
+
1317
+ this._updateMemberWatcherReferences(userInfo);
1318
+ this._updateUserMessageReferences(userInfo);
1319
+
1320
+ this.dispatchEvent({
1321
+ type: 'user.updated',
1322
+ me: this.user,
1323
+ } as unknown as Event<ErmisChatGenerics>);
1207
1324
  }
1208
1325
 
1209
1326
  return response;
@@ -1212,6 +1329,23 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
1212
1329
  let response = await this.patch<UserResponse<ErmisChatGenerics>>(this.userBaseURL + '/users/update', updates);
1213
1330
  this.user = response;
1214
1331
  this.state.updateUser(response);
1332
+
1333
+ if (this.user) {
1334
+ const userInfo = {
1335
+ id: this.user.id,
1336
+ name: this.user.name ? this.user.name : this.user.id,
1337
+ avatar: this.user?.avatar || '',
1338
+ };
1339
+
1340
+ this._updateMemberWatcherReferences(userInfo);
1341
+ this._updateUserMessageReferences(userInfo);
1342
+
1343
+ this.dispatchEvent({
1344
+ type: 'user.updated',
1345
+ me: this.user,
1346
+ } as unknown as Event<ErmisChatGenerics>);
1347
+ }
1348
+
1215
1349
  return response;
1216
1350
  }
1217
1351
 
@@ -1309,6 +1443,10 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
1309
1443
 
1310
1444
  console.log('---channels---', channels);
1311
1445
 
1446
+ this.dispatchEvent({
1447
+ type: 'channels.queried',
1448
+ } as unknown as Event<ErmisChatGenerics>);
1449
+
1312
1450
  return channels;
1313
1451
  }
1314
1452
 
@@ -1475,8 +1613,12 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
1475
1613
 
1476
1614
  const now = new Date();
1477
1615
  const formattedDate = new Intl.DateTimeFormat('en-US', {
1478
- month: 'short', day: '2-digit', year: 'numeric',
1479
- hour: '2-digit', minute: '2-digit', hour12: false,
1616
+ month: 'short',
1617
+ day: '2-digit',
1618
+ year: 'numeric',
1619
+ hour: '2-digit',
1620
+ minute: '2-digit',
1621
+ hour12: false,
1480
1622
  }).format(now);
1481
1623
 
1482
1624
  const payload = {
@@ -1487,7 +1629,7 @@ export class ErmisChat<ErmisChatGenerics extends ExtendableGenerics = DefaultGen
1487
1629
 
1488
1630
  const quickChannel = this.channel('meeting', payload);
1489
1631
  await quickChannel.create();
1490
-
1632
+
1491
1633
  return quickChannel;
1492
1634
  }
1493
1635