@ermis-network/ermis-chat-sdk 1.0.5 → 1.0.7

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/dist/index.cjs CHANGED
@@ -996,10 +996,9 @@ var Channel = class {
996
996
  };
997
997
  this.state.addMessageSorted(optimisticMessage);
998
998
  try {
999
- return await this.getClient().post(
1000
- this._channelURL() + "/message",
1001
- { message: { ...message } }
1002
- );
999
+ return await this.getClient().post(this._channelURL() + "/message", {
1000
+ message: { ...message }
1001
+ });
1003
1002
  } catch (error) {
1004
1003
  const isOfflineError = !error.response || error.code === "ERR_NETWORK" || error.isWSFailure || !this.getClient().wsConnection?.isHealthy;
1005
1004
  const statusToSet = isOfflineError ? "failed_offline" : "error";
@@ -1024,10 +1023,9 @@ var Channel = class {
1024
1023
  messagePayload.show_in_channel = stateMsg.show_in_channel;
1025
1024
  }
1026
1025
  try {
1027
- return await this.getClient().post(
1028
- this._channelURL() + "/message",
1029
- { message: messagePayload }
1030
- );
1026
+ return await this.getClient().post(this._channelURL() + "/message", {
1027
+ message: messagePayload
1028
+ });
1031
1029
  } catch (error) {
1032
1030
  const isOfflineError = !error.response || error.code === "ERR_NETWORK" || error.isWSFailure || !this.getClient().wsConnection?.isHealthy;
1033
1031
  this.state.updateMessageStatus(messageId, isOfflineError ? "failed_offline" : "error");
@@ -1068,6 +1066,44 @@ var Channel = class {
1068
1066
  this.getClient().baseURL + `/messages/${this.type}/${this.id}/${messageID}/unpin`
1069
1067
  );
1070
1068
  }
1069
+ async pin() {
1070
+ if (this.data) this.data.is_pinned = true;
1071
+ this.getClient().dispatchEvent({
1072
+ type: "channel.pinned",
1073
+ cid: this.cid,
1074
+ channel: this.data
1075
+ });
1076
+ try {
1077
+ return await this.getClient().pinChannel(this.type, this.id);
1078
+ } catch (e) {
1079
+ if (this.data) this.data.is_pinned = false;
1080
+ this.getClient().dispatchEvent({
1081
+ type: "channel.unpinned",
1082
+ cid: this.cid,
1083
+ channel: this.data
1084
+ });
1085
+ throw e;
1086
+ }
1087
+ }
1088
+ async unpin() {
1089
+ if (this.data) this.data.is_pinned = false;
1090
+ this.getClient().dispatchEvent({
1091
+ type: "channel.unpinned",
1092
+ cid: this.cid,
1093
+ channel: this.data
1094
+ });
1095
+ try {
1096
+ return await this.getClient().unpinChannel(this.type, this.id);
1097
+ } catch (e) {
1098
+ if (this.data) this.data.is_pinned = true;
1099
+ this.getClient().dispatchEvent({
1100
+ type: "channel.pinned",
1101
+ cid: this.cid,
1102
+ channel: this.data
1103
+ });
1104
+ throw e;
1105
+ }
1106
+ }
1071
1107
  async editMessage(oldMessageID, message) {
1072
1108
  return await this.getClient().post(this.getClient().baseURL + `/messages/${this.type}/${this.id}/${oldMessageID}`, {
1073
1109
  message
@@ -1094,9 +1130,7 @@ var Channel = class {
1094
1130
  return file;
1095
1131
  });
1096
1132
  const uploadResults = await Promise.allSettled(
1097
- processedFiles.map(
1098
- (file) => this.sendFile(file, file.name, file.type)
1099
- )
1133
+ processedFiles.map((file) => this.sendFile(file, file.name, file.type))
1100
1134
  );
1101
1135
  const thumbUrls = /* @__PURE__ */ new Map();
1102
1136
  const thumbPromises = [];
@@ -1108,11 +1142,7 @@ var Channel = class {
1108
1142
  try {
1109
1143
  const thumbBlob = await this.getThumbBlobVideo(files[i]);
1110
1144
  if (thumbBlob) {
1111
- const thumbFile = new File(
1112
- [thumbBlob],
1113
- `thumb_${processedFiles[i].name}.jpg`,
1114
- { type: "image/jpeg" }
1115
- );
1145
+ const thumbFile = new File([thumbBlob], `thumb_${processedFiles[i].name}.jpg`, { type: "image/jpeg" });
1116
1146
  const thumbResp = await this.sendFile(thumbFile, thumbFile.name, "image/jpeg");
1117
1147
  thumbUrls.set(i, thumbResp.file);
1118
1148
  }
@@ -1130,9 +1160,7 @@ var Channel = class {
1130
1160
  const uploadedUrl = result.value.file;
1131
1161
  const thumbUrl = thumbUrls.get(i);
1132
1162
  const voiceMeta = options?.voiceMetadata?.get(i);
1133
- attachments.push(
1134
- buildAttachmentPayload(processedFiles[i], uploadedUrl, thumbUrl, voiceMeta)
1135
- );
1163
+ attachments.push(buildAttachmentPayload(processedFiles[i], uploadedUrl, thumbUrl, voiceMeta));
1136
1164
  } else {
1137
1165
  failedFiles.push({
1138
1166
  file: files[i],
@@ -1480,7 +1508,7 @@ var Channel = class {
1480
1508
  if (this.id) {
1481
1509
  queryURL += `/${this.id}`;
1482
1510
  } else {
1483
- if (this.type === "team") {
1511
+ if (this.type === "team" || this.type === "meeting") {
1484
1512
  const uuid = randomId();
1485
1513
  this.id = `${project_id}:${uuid}`;
1486
1514
  queryURL += `/${this.id}`;
@@ -1981,6 +2009,18 @@ var Channel = class {
1981
2009
  delete channelState.members[event.user.id];
1982
2010
  }
1983
2011
  break;
2012
+ case "channel.topic.enabled":
2013
+ if (channel.data) {
2014
+ channel.data.topics_enabled = true;
2015
+ }
2016
+ channelState.topics = channelState.topics || [];
2017
+ break;
2018
+ case "channel.topic.disabled":
2019
+ if (channel.data) {
2020
+ channel.data.topics_enabled = false;
2021
+ }
2022
+ channelState.topics = [];
2023
+ break;
1984
2024
  case "channel.updated":
1985
2025
  if (event.channel) {
1986
2026
  channel.data = {
@@ -2119,7 +2159,12 @@ var Channel = class {
2119
2159
  const topic = this.getClient().channel(event.channel_type || "", event.channel_id || "");
2120
2160
  topic.data = event.channel;
2121
2161
  topic._initializeState(topicState, "latest");
2122
- channelState.topics?.unshift(topic);
2162
+ if (!channelState.topics) {
2163
+ channelState.topics = [];
2164
+ }
2165
+ if (!channelState.topics.some((t) => t.cid === topic.cid)) {
2166
+ channelState.topics.push(topic);
2167
+ }
2123
2168
  break;
2124
2169
  case "channel.topic.closed":
2125
2170
  if (channel.data) {
@@ -2938,7 +2983,6 @@ var ErmisChat = class _ErmisChat {
2938
2983
  params.avatar = user.avatar;
2939
2984
  }
2940
2985
  const url = this.userBaseURL + "/get_token/external_auth";
2941
- const query = new URLSearchParams(params).toString();
2942
2986
  const headers = {
2943
2987
  "Content-Type": "application/json"
2944
2988
  };
@@ -2946,21 +2990,21 @@ var ErmisChat = class _ErmisChat {
2946
2990
  const tokenStr = typeof token === "string" && token.startsWith("Bearer ") ? token : `Bearer ${token}`;
2947
2991
  headers["Authorization"] = tokenStr;
2948
2992
  }
2949
- const response = await fetch(`${url}?${query}`, {
2950
- method: "GET",
2951
- headers
2952
- });
2953
- if (!response.ok) {
2954
- let errorMsg = "";
2955
- try {
2956
- const errorData = await response.json();
2957
- errorMsg = errorData.message || JSON.stringify(errorData);
2958
- } catch {
2959
- errorMsg = await response.text();
2993
+ try {
2994
+ const response = await this.axiosInstance.get(url, {
2995
+ params,
2996
+ headers
2997
+ });
2998
+ return response.data;
2999
+ } catch (error) {
3000
+ let errorMsg = "Failed to fetch external auth token";
3001
+ if (error.response && error.response.data) {
3002
+ errorMsg = error.response.data.message || JSON.stringify(error.response.data);
3003
+ } else if (error.message) {
3004
+ errorMsg = error.message;
2960
3005
  }
2961
3006
  throw new Error(errorMsg);
2962
3007
  }
2963
- return await response.json();
2964
3008
  }
2965
3009
  /**
2966
3010
  * Connects a user to the Ermis network and establishes the WebSocket connection.
@@ -2971,19 +3015,24 @@ var ErmisChat = class _ErmisChat {
2971
3015
  * @param extenal_auth - Set to `true` to use your custom backend external authentication flow.
2972
3016
  * @returns A promise resolving to the API connection response once authenticated.
2973
3017
  */
2974
- connectUser = async (user, userTokenOrProvider, extenal_auth) => {
3018
+ connectUser = async (user, userTokenOrProvider, external_auth) => {
2975
3019
  this.logger("info", "client:connectUser() - started", {
2976
3020
  tags: ["connection", "client"]
2977
3021
  });
2978
3022
  if (!user.id) {
2979
3023
  throw new Error('The "id" field on the user is missing');
2980
3024
  }
2981
- if (extenal_auth) {
3025
+ let connectionUser = user;
3026
+ let connectionToken = userTokenOrProvider;
3027
+ if (external_auth) {
2982
3028
  const external_auth_token = await this.getExternalAuthToken(user, userTokenOrProvider);
2983
- userTokenOrProvider = external_auth_token.token;
2984
- user.id = external_auth_token.user_id;
3029
+ connectionToken = external_auth_token.token;
3030
+ connectionUser = {
3031
+ ...user,
3032
+ id: external_auth_token.user_id
3033
+ };
2985
3034
  }
2986
- if (this.userID === user.id && this.setUserPromise) {
3035
+ if (this.userID === connectionUser.id && this.setUserPromise) {
2987
3036
  console.warn(
2988
3037
  "Consecutive calls to connectUser is detected, ideally you should only call this function once in your app."
2989
3038
  );
@@ -2999,10 +3048,10 @@ var ErmisChat = class _ErmisChat {
2999
3048
  'Please do not use connectUser server side. connectUser impacts MAU and concurrent connection usage and thus your bill. If you have a valid use-case, add "allowServerSideConnect: true" to the client options to disable this warning.'
3000
3049
  );
3001
3050
  }
3002
- this.userID = user.id;
3003
- const setTokenPromise = this._setToken(user, userTokenOrProvider);
3004
- this._setUser(user);
3005
- this.state.updateUser({ id: user.id, name: user?.name || user.id, avatar: user?.avatar || "" });
3051
+ this.userID = connectionUser.id;
3052
+ const setTokenPromise = this._setToken(connectionUser, connectionToken);
3053
+ this._setUser(connectionUser);
3054
+ this.state.updateUser({ id: connectionUser.id, name: connectionUser?.name || connectionUser.id, avatar: connectionUser?.avatar || "" });
3006
3055
  const wsPromise = this.openConnection();
3007
3056
  this.setUserPromise = Promise.all([setTokenPromise, wsPromise]).then(
3008
3057
  (result) => result[1]
@@ -3228,7 +3277,7 @@ var ErmisChat = class _ErmisChat {
3228
3277
  }
3229
3278
  dispatchEvent = (event) => {
3230
3279
  if (!event.received_at) event.received_at = /* @__PURE__ */ new Date();
3231
- if (event.type === "channel.created") {
3280
+ if (event.type === "channel.created" || event.type === "channel.topic.created") {
3232
3281
  this._handleChannelCreatedEvent(event).then(() => {
3233
3282
  this._afterDispatchEvent(event);
3234
3283
  });
@@ -3362,6 +3411,79 @@ var ErmisChat = class _ErmisChat {
3362
3411
  });
3363
3412
  }
3364
3413
  }
3414
+ if (event.type === "message.new" && event.channel_type === "topic") {
3415
+ postListenerCallbacks.push(() => {
3416
+ const parentCid = event.parent_cid || event.channel?.parent_cid;
3417
+ if (parentCid && this.activeChannels[parentCid]) {
3418
+ const parentChannel = this.activeChannels[parentCid];
3419
+ if (parentChannel.state.topics) {
3420
+ parentChannel.state.topics.sort((a, b) => {
3421
+ const aLatest = a.state?.latestMessages?.[a.state.latestMessages.length - 1]?.created_at;
3422
+ const bLatest = b.state?.latestMessages?.[b.state.latestMessages.length - 1]?.created_at;
3423
+ const aTime = aLatest ? new Date(aLatest).getTime() : 0;
3424
+ const bTime = bLatest ? new Date(bLatest).getTime() : 0;
3425
+ return bTime - aTime;
3426
+ });
3427
+ parentChannel._callChannelListeners({ ...event, type: "channel.updated", channel: parentChannel.data });
3428
+ }
3429
+ }
3430
+ });
3431
+ }
3432
+ if (event.type === "channel.topic.updated") {
3433
+ postListenerCallbacks.push(() => {
3434
+ const parentCid = event.parent_cid || event.channel?.parent_cid;
3435
+ if (parentCid && this.activeChannels[parentCid]) {
3436
+ const parentChannel = this.activeChannels[parentCid];
3437
+ if (parentChannel.state?.topics && event.channel) {
3438
+ const topicIndex = parentChannel.state.topics.findIndex((t) => t.cid === event.cid || t.channel?.cid === event.cid);
3439
+ if (topicIndex !== -1) {
3440
+ const t = parentChannel.state.topics[topicIndex];
3441
+ if (t.data) {
3442
+ t.data = { ...t.data, ...event.channel };
3443
+ } else if (t.channel) {
3444
+ t.channel = { ...t.channel, ...event.channel };
3445
+ } else {
3446
+ Object.assign(t, event.channel);
3447
+ }
3448
+ }
3449
+ parentChannel._callChannelListeners({ ...event, type: "channel.updated", channel: parentChannel.data });
3450
+ }
3451
+ }
3452
+ if (event.cid && this.activeChannels[event.cid]) {
3453
+ const topicChannel = this.activeChannels[event.cid];
3454
+ if (event.channel) {
3455
+ topicChannel.data = { ...topicChannel.data, ...event.channel };
3456
+ topicChannel._callChannelListeners({ ...event, type: "channel.updated", channel: topicChannel.data });
3457
+ }
3458
+ }
3459
+ });
3460
+ }
3461
+ if (event.type === "channel.topic.closed" || event.type === "channel.topic.reopen") {
3462
+ postListenerCallbacks.push(() => {
3463
+ const isClosed = event.type === "channel.topic.closed";
3464
+ const parentCid = event.parent_cid;
3465
+ if (parentCid && this.activeChannels[parentCid]) {
3466
+ const parentChannel = this.activeChannels[parentCid];
3467
+ if (parentChannel.state?.topics) {
3468
+ const topicIndex = parentChannel.state.topics.findIndex((t) => t.cid === event.cid || t.channel?.cid === event.cid);
3469
+ if (topicIndex !== -1) {
3470
+ const t = parentChannel.state.topics[topicIndex];
3471
+ if (t.data) t.data.is_closed_topic = isClosed;
3472
+ else if (t.channel) t.channel.is_closed_topic = isClosed;
3473
+ else t.is_closed_topic = isClosed;
3474
+ }
3475
+ parentChannel._callChannelListeners({ ...event, type: "channel.updated", channel: parentChannel.data });
3476
+ }
3477
+ }
3478
+ if (event.cid && this.activeChannels[event.cid]) {
3479
+ const topicChannel = this.activeChannels[event.cid];
3480
+ if (topicChannel.data) {
3481
+ topicChannel.data.is_closed_topic = isClosed;
3482
+ }
3483
+ topicChannel._callChannelListeners({ ...event, type: "channel.updated", channel: topicChannel.data });
3484
+ }
3485
+ });
3486
+ }
3365
3487
  if (event.type === "connection.recovered") {
3366
3488
  postListenerCallbacks.push(() => {
3367
3489
  Object.values(this.activeChannels).forEach((channel) => {
@@ -3599,7 +3721,14 @@ var ErmisChat = class _ErmisChat {
3599
3721
  _updateProjectID(project_id) {
3600
3722
  this.projectId = project_id;
3601
3723
  }
3602
- async uploadFile(file) {
3724
+ /**
3725
+ * Uploads a new avatar image for the current user.
3726
+ * The user's avatar URL is automatically updated in both the client and the local state.
3727
+ *
3728
+ * @param file - The image file to upload.
3729
+ * @returns The response containing the new avatar URL.
3730
+ */
3731
+ async uploadAvatar(file) {
3603
3732
  const formData = new FormData();
3604
3733
  formData.append("avatar", file);
3605
3734
  let response = await this.post(this.userBaseURL + "/users/upload", formData, {
@@ -3614,12 +3743,8 @@ var ErmisChat = class _ErmisChat {
3614
3743
  }
3615
3744
  return response;
3616
3745
  }
3617
- async updateProfile(name, about_me) {
3618
- let body = {
3619
- name,
3620
- about_me
3621
- };
3622
- let response = await this.patch(this.userBaseURL + "/users/update", body);
3746
+ async updateProfile(updates) {
3747
+ let response = await this.patch(this.userBaseURL + "/users/update", updates);
3623
3748
  this.user = response;
3624
3749
  this.state.updateUser(response);
3625
3750
  return response;
@@ -3771,6 +3896,57 @@ var ErmisChat = class _ErmisChat {
3771
3896
  this.activeChannels[channel.cid] = channel;
3772
3897
  return channel;
3773
3898
  };
3899
+ /**
3900
+ * Creates a quick channel and immediately registers it on the server.
3901
+ * Quick channels are public group channels that anyone can join without an invitation.
3902
+ * The creator is added as the first member automatically.
3903
+ *
3904
+ * @param name - An optional display name for the channel.
3905
+ * @returns A promise that resolves to the created `Channel` object.
3906
+ */
3907
+ async createQuickChannel(name) {
3908
+ if (!this.userID) {
3909
+ throw Error("Call connectUser before creating a channel");
3910
+ }
3911
+ const now = /* @__PURE__ */ new Date();
3912
+ const formattedDate = new Intl.DateTimeFormat("en-US", {
3913
+ month: "short",
3914
+ day: "2-digit",
3915
+ year: "numeric",
3916
+ hour: "2-digit",
3917
+ minute: "2-digit",
3918
+ hour12: false
3919
+ }).format(now);
3920
+ const payload = {
3921
+ name: name || `Quick Channel - ${formattedDate}`,
3922
+ members: [this.userID],
3923
+ public: true
3924
+ };
3925
+ const quickChannel = this.channel("meeting", payload);
3926
+ await quickChannel.create();
3927
+ return quickChannel;
3928
+ }
3929
+ /**
3930
+ * Joins a quick channel by its ID.
3931
+ * Automatically checks whether the caller is already a member.
3932
+ * If not, it joins the channel and synchronizes state.
3933
+ *
3934
+ * @param channelId - The ID of the quick channel to join.
3935
+ * @returns A promise that resolves to the joined `Channel` object.
3936
+ */
3937
+ async joinQuickChannel(channelId) {
3938
+ if (!this.userID) {
3939
+ throw Error("Call connectUser before joining a channel");
3940
+ }
3941
+ const quickChannel = this.channel("meeting", channelId);
3942
+ await quickChannel.watch();
3943
+ const isMember = quickChannel.state.members && quickChannel.state.members[this.userID];
3944
+ if (!isMember) {
3945
+ await quickChannel.acceptInvite("join");
3946
+ await quickChannel.watch();
3947
+ }
3948
+ return quickChannel;
3949
+ }
3774
3950
  _normalizeExpiration(timeoutOrExpirationDate) {
3775
3951
  let pinExpires = null;
3776
3952
  if (typeof timeoutOrExpirationDate === "number") {
@@ -3785,7 +3961,7 @@ var ErmisChat = class _ErmisChat {
3785
3961
  return pinExpires;
3786
3962
  }
3787
3963
  getUserAgent() {
3788
- return this.userAgent || `ermis-chat-sdk-javascript-client-${this.node ? "node" : "browser"}-${"1.0.5"}`;
3964
+ return this.userAgent || `ermis-chat-sdk-javascript-client-${this.node ? "node" : "browser"}-${"1.0.7"}`;
3789
3965
  }
3790
3966
  setUserAgent(userAgent) {
3791
3967
  this.userAgent = userAgent;
@@ -7144,7 +7320,7 @@ var ErmisAuthProvider = class {
7144
7320
  return data;
7145
7321
  }
7146
7322
  getUserAgent() {
7147
- return this.userAgent || `ermis-chat-sdk-javascript-client-${this.node ? "node" : "browser"}-${"1.0.5"}`;
7323
+ return this.userAgent || `ermis-chat-sdk-javascript-client-${this.node ? "node" : "browser"}-${"1.0.7"}`;
7148
7324
  }
7149
7325
  setUserAgent(userAgent) {
7150
7326
  this.userAgent = userAgent;