@ermis-network/ermis-chat-sdk 1.0.6 → 1.0.8

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
@@ -1066,6 +1066,44 @@ var Channel = class {
1066
1066
  this.getClient().baseURL + `/messages/${this.type}/${this.id}/${messageID}/unpin`
1067
1067
  );
1068
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
+ }
1069
1107
  async editMessage(oldMessageID, message) {
1070
1108
  return await this.getClient().post(this.getClient().baseURL + `/messages/${this.type}/${this.id}/${oldMessageID}`, {
1071
1109
  message
@@ -1971,6 +2009,18 @@ var Channel = class {
1971
2009
  delete channelState.members[event.user.id];
1972
2010
  }
1973
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;
1974
2024
  case "channel.updated":
1975
2025
  if (event.channel) {
1976
2026
  channel.data = {
@@ -2109,7 +2159,12 @@ var Channel = class {
2109
2159
  const topic = this.getClient().channel(event.channel_type || "", event.channel_id || "");
2110
2160
  topic.data = event.channel;
2111
2161
  topic._initializeState(topicState, "latest");
2112
- 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
+ }
2113
2168
  break;
2114
2169
  case "channel.topic.closed":
2115
2170
  if (channel.data) {
@@ -2928,7 +2983,6 @@ var ErmisChat = class _ErmisChat {
2928
2983
  params.avatar = user.avatar;
2929
2984
  }
2930
2985
  const url = this.userBaseURL + "/get_token/external_auth";
2931
- const query = new URLSearchParams(params).toString();
2932
2986
  const headers = {
2933
2987
  "Content-Type": "application/json"
2934
2988
  };
@@ -2936,21 +2990,21 @@ var ErmisChat = class _ErmisChat {
2936
2990
  const tokenStr = typeof token === "string" && token.startsWith("Bearer ") ? token : `Bearer ${token}`;
2937
2991
  headers["Authorization"] = tokenStr;
2938
2992
  }
2939
- const response = await fetch(`${url}?${query}`, {
2940
- method: "GET",
2941
- headers
2942
- });
2943
- if (!response.ok) {
2944
- let errorMsg = "";
2945
- try {
2946
- const errorData = await response.json();
2947
- errorMsg = errorData.message || JSON.stringify(errorData);
2948
- } catch {
2949
- 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;
2950
3005
  }
2951
3006
  throw new Error(errorMsg);
2952
3007
  }
2953
- return await response.json();
2954
3008
  }
2955
3009
  /**
2956
3010
  * Connects a user to the Ermis network and establishes the WebSocket connection.
@@ -2961,19 +3015,24 @@ var ErmisChat = class _ErmisChat {
2961
3015
  * @param extenal_auth - Set to `true` to use your custom backend external authentication flow.
2962
3016
  * @returns A promise resolving to the API connection response once authenticated.
2963
3017
  */
2964
- connectUser = async (user, userTokenOrProvider, extenal_auth) => {
3018
+ connectUser = async (user, userTokenOrProvider, external_auth) => {
2965
3019
  this.logger("info", "client:connectUser() - started", {
2966
3020
  tags: ["connection", "client"]
2967
3021
  });
2968
3022
  if (!user.id) {
2969
3023
  throw new Error('The "id" field on the user is missing');
2970
3024
  }
2971
- if (extenal_auth) {
3025
+ let connectionUser = user;
3026
+ let connectionToken = userTokenOrProvider;
3027
+ if (external_auth) {
2972
3028
  const external_auth_token = await this.getExternalAuthToken(user, userTokenOrProvider);
2973
- userTokenOrProvider = external_auth_token.token;
2974
- 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
+ };
2975
3034
  }
2976
- if (this.userID === user.id && this.setUserPromise) {
3035
+ if (this.userID === connectionUser.id && this.setUserPromise) {
2977
3036
  console.warn(
2978
3037
  "Consecutive calls to connectUser is detected, ideally you should only call this function once in your app."
2979
3038
  );
@@ -2989,10 +3048,10 @@ var ErmisChat = class _ErmisChat {
2989
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.'
2990
3049
  );
2991
3050
  }
2992
- this.userID = user.id;
2993
- const setTokenPromise = this._setToken(user, userTokenOrProvider);
2994
- this._setUser(user);
2995
- 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 || "" });
2996
3055
  const wsPromise = this.openConnection();
2997
3056
  this.setUserPromise = Promise.all([setTokenPromise, wsPromise]).then(
2998
3057
  (result) => result[1]
@@ -3198,6 +3257,20 @@ var ErmisChat = class _ErmisChat {
3198
3257
  }
3199
3258
  });
3200
3259
  }
3260
+ /**
3261
+ * Downloads a media file as a Blob via the SDK's configured axiosInstance.
3262
+ * This avoids CORS issues that arise when using `fetch()` directly from the browser,
3263
+ * because axios is routed through the SDK's authenticated transport layer.
3264
+ *
3265
+ * @param url - The full URL of the media file to download.
3266
+ * @returns A Blob of the file content.
3267
+ */
3268
+ async downloadMedia(url) {
3269
+ const response = await this.axiosInstance.get(url, {
3270
+ responseType: "blob"
3271
+ });
3272
+ return response.data;
3273
+ }
3201
3274
  errorFromResponse(response) {
3202
3275
  let err;
3203
3276
  err = new ErrorFromResponse(`ErmisChat error HTTP code: ${response.status}`);
@@ -3218,7 +3291,7 @@ var ErmisChat = class _ErmisChat {
3218
3291
  }
3219
3292
  dispatchEvent = (event) => {
3220
3293
  if (!event.received_at) event.received_at = /* @__PURE__ */ new Date();
3221
- if (event.type === "channel.created") {
3294
+ if (event.type === "channel.created" || event.type === "channel.topic.created") {
3222
3295
  this._handleChannelCreatedEvent(event).then(() => {
3223
3296
  this._afterDispatchEvent(event);
3224
3297
  });
@@ -3352,6 +3425,79 @@ var ErmisChat = class _ErmisChat {
3352
3425
  });
3353
3426
  }
3354
3427
  }
3428
+ if (event.type === "message.new" && event.channel_type === "topic") {
3429
+ postListenerCallbacks.push(() => {
3430
+ const parentCid = event.parent_cid || event.channel?.parent_cid;
3431
+ if (parentCid && this.activeChannels[parentCid]) {
3432
+ const parentChannel = this.activeChannels[parentCid];
3433
+ if (parentChannel.state.topics) {
3434
+ parentChannel.state.topics.sort((a, b) => {
3435
+ const aLatest = a.state?.latestMessages?.[a.state.latestMessages.length - 1]?.created_at;
3436
+ const bLatest = b.state?.latestMessages?.[b.state.latestMessages.length - 1]?.created_at;
3437
+ const aTime = aLatest ? new Date(aLatest).getTime() : 0;
3438
+ const bTime = bLatest ? new Date(bLatest).getTime() : 0;
3439
+ return bTime - aTime;
3440
+ });
3441
+ parentChannel._callChannelListeners({ ...event, type: "channel.updated", channel: parentChannel.data });
3442
+ }
3443
+ }
3444
+ });
3445
+ }
3446
+ if (event.type === "channel.topic.updated") {
3447
+ postListenerCallbacks.push(() => {
3448
+ const parentCid = event.parent_cid || event.channel?.parent_cid;
3449
+ if (parentCid && this.activeChannels[parentCid]) {
3450
+ const parentChannel = this.activeChannels[parentCid];
3451
+ if (parentChannel.state?.topics && event.channel) {
3452
+ const topicIndex = parentChannel.state.topics.findIndex((t) => t.cid === event.cid || t.channel?.cid === event.cid);
3453
+ if (topicIndex !== -1) {
3454
+ const t = parentChannel.state.topics[topicIndex];
3455
+ if (t.data) {
3456
+ t.data = { ...t.data, ...event.channel };
3457
+ } else if (t.channel) {
3458
+ t.channel = { ...t.channel, ...event.channel };
3459
+ } else {
3460
+ Object.assign(t, event.channel);
3461
+ }
3462
+ }
3463
+ parentChannel._callChannelListeners({ ...event, type: "channel.updated", channel: parentChannel.data });
3464
+ }
3465
+ }
3466
+ if (event.cid && this.activeChannels[event.cid]) {
3467
+ const topicChannel = this.activeChannels[event.cid];
3468
+ if (event.channel) {
3469
+ topicChannel.data = { ...topicChannel.data, ...event.channel };
3470
+ topicChannel._callChannelListeners({ ...event, type: "channel.updated", channel: topicChannel.data });
3471
+ }
3472
+ }
3473
+ });
3474
+ }
3475
+ if (event.type === "channel.topic.closed" || event.type === "channel.topic.reopen") {
3476
+ postListenerCallbacks.push(() => {
3477
+ const isClosed = event.type === "channel.topic.closed";
3478
+ const parentCid = event.parent_cid;
3479
+ if (parentCid && this.activeChannels[parentCid]) {
3480
+ const parentChannel = this.activeChannels[parentCid];
3481
+ if (parentChannel.state?.topics) {
3482
+ const topicIndex = parentChannel.state.topics.findIndex((t) => t.cid === event.cid || t.channel?.cid === event.cid);
3483
+ if (topicIndex !== -1) {
3484
+ const t = parentChannel.state.topics[topicIndex];
3485
+ if (t.data) t.data.is_closed_topic = isClosed;
3486
+ else if (t.channel) t.channel.is_closed_topic = isClosed;
3487
+ else t.is_closed_topic = isClosed;
3488
+ }
3489
+ parentChannel._callChannelListeners({ ...event, type: "channel.updated", channel: parentChannel.data });
3490
+ }
3491
+ }
3492
+ if (event.cid && this.activeChannels[event.cid]) {
3493
+ const topicChannel = this.activeChannels[event.cid];
3494
+ if (topicChannel.data) {
3495
+ topicChannel.data.is_closed_topic = isClosed;
3496
+ }
3497
+ topicChannel._callChannelListeners({ ...event, type: "channel.updated", channel: topicChannel.data });
3498
+ }
3499
+ });
3500
+ }
3355
3501
  if (event.type === "connection.recovered") {
3356
3502
  postListenerCallbacks.push(() => {
3357
3503
  Object.values(this.activeChannels).forEach((channel) => {
@@ -3589,7 +3735,14 @@ var ErmisChat = class _ErmisChat {
3589
3735
  _updateProjectID(project_id) {
3590
3736
  this.projectId = project_id;
3591
3737
  }
3592
- async uploadFile(file) {
3738
+ /**
3739
+ * Uploads a new avatar image for the current user.
3740
+ * The user's avatar URL is automatically updated in both the client and the local state.
3741
+ *
3742
+ * @param file - The image file to upload.
3743
+ * @returns The response containing the new avatar URL.
3744
+ */
3745
+ async uploadAvatar(file) {
3593
3746
  const formData = new FormData();
3594
3747
  formData.append("avatar", file);
3595
3748
  let response = await this.post(this.userBaseURL + "/users/upload", formData, {
@@ -3604,12 +3757,8 @@ var ErmisChat = class _ErmisChat {
3604
3757
  }
3605
3758
  return response;
3606
3759
  }
3607
- async updateProfile(name, about_me) {
3608
- let body = {
3609
- name,
3610
- about_me
3611
- };
3612
- let response = await this.patch(this.userBaseURL + "/users/update", body);
3760
+ async updateProfile(updates) {
3761
+ let response = await this.patch(this.userBaseURL + "/users/update", updates);
3613
3762
  this.user = response;
3614
3763
  this.state.updateUser(response);
3615
3764
  return response;
@@ -3762,45 +3911,55 @@ var ErmisChat = class _ErmisChat {
3762
3911
  return channel;
3763
3912
  };
3764
3913
  /**
3765
- * Creates an interactive `meeting` Channel locally, and immediately creates it on the server.
3766
- * Consumers can customize the `name` field. `members` and `public` fields are constrained.
3914
+ * Creates a quick channel and immediately registers it on the server.
3915
+ * Quick channels are public group channels that anyone can join without an invitation.
3916
+ * The creator is added as the first member automatically.
3767
3917
  *
3768
- * @param name - The custom name for the meeting channel.
3918
+ * @param name - An optional display name for the channel.
3769
3919
  * @returns A promise that resolves to the created `Channel` object.
3770
3920
  */
3771
- async createMeetingChannel(name) {
3921
+ async createQuickChannel(name) {
3772
3922
  if (!this.userID) {
3773
3923
  throw Error("Call connectUser before creating a channel");
3774
3924
  }
3925
+ const now = /* @__PURE__ */ new Date();
3926
+ const formattedDate = new Intl.DateTimeFormat("en-US", {
3927
+ month: "short",
3928
+ day: "2-digit",
3929
+ year: "numeric",
3930
+ hour: "2-digit",
3931
+ minute: "2-digit",
3932
+ hour12: false
3933
+ }).format(now);
3775
3934
  const payload = {
3776
- name: name || `Meeting Public - ${(/* @__PURE__ */ new Date()).toISOString()}`,
3935
+ name: name || `Quick Channel - ${formattedDate}`,
3777
3936
  members: [this.userID],
3778
3937
  public: true
3779
3938
  };
3780
- const meetingChannel = this.channel("meeting", payload);
3781
- await meetingChannel.create();
3782
- return meetingChannel;
3939
+ const quickChannel = this.channel("meeting", payload);
3940
+ await quickChannel.create();
3941
+ return quickChannel;
3783
3942
  }
3784
3943
  /**
3785
- * Joins a `meeting` channel.
3786
- * It queries/watches the channel to see if caller is already a member.
3787
- * If not, it accepts the invite to join the channel, then watches it again to reflect changes.
3944
+ * Joins a quick channel by its ID.
3945
+ * Automatically checks whether the caller is already a member.
3946
+ * If not, it joins the channel and synchronizes state.
3788
3947
  *
3789
- * @param channelId - The ID of the meeting channel to join.
3948
+ * @param channelId - The ID of the quick channel to join.
3790
3949
  * @returns A promise that resolves to the joined `Channel` object.
3791
3950
  */
3792
- async joinMeetingChannel(channelId) {
3951
+ async joinQuickChannel(channelId) {
3793
3952
  if (!this.userID) {
3794
3953
  throw Error("Call connectUser before joining a channel");
3795
3954
  }
3796
- const meetingChannel = this.channel("meeting", channelId);
3797
- await meetingChannel.watch();
3798
- const isMember = meetingChannel.state.members && meetingChannel.state.members[this.userID];
3955
+ const quickChannel = this.channel("meeting", channelId);
3956
+ await quickChannel.watch();
3957
+ const isMember = quickChannel.state.members && quickChannel.state.members[this.userID];
3799
3958
  if (!isMember) {
3800
- await meetingChannel.acceptInvite("join");
3801
- await meetingChannel.watch();
3959
+ await quickChannel.acceptInvite("join");
3960
+ await quickChannel.watch();
3802
3961
  }
3803
- return meetingChannel;
3962
+ return quickChannel;
3804
3963
  }
3805
3964
  _normalizeExpiration(timeoutOrExpirationDate) {
3806
3965
  let pinExpires = null;
@@ -3816,7 +3975,7 @@ var ErmisChat = class _ErmisChat {
3816
3975
  return pinExpires;
3817
3976
  }
3818
3977
  getUserAgent() {
3819
- return this.userAgent || `ermis-chat-sdk-javascript-client-${this.node ? "node" : "browser"}-${"1.0.6"}`;
3978
+ return this.userAgent || `ermis-chat-sdk-javascript-client-${this.node ? "node" : "browser"}-${"1.0.8"}`;
3820
3979
  }
3821
3980
  setUserAgent(userAgent) {
3822
3981
  this.userAgent = userAgent;
@@ -7175,7 +7334,7 @@ var ErmisAuthProvider = class {
7175
7334
  return data;
7176
7335
  }
7177
7336
  getUserAgent() {
7178
- return this.userAgent || `ermis-chat-sdk-javascript-client-${this.node ? "node" : "browser"}-${"1.0.6"}`;
7337
+ return this.userAgent || `ermis-chat-sdk-javascript-client-${this.node ? "node" : "browser"}-${"1.0.8"}`;
7179
7338
  }
7180
7339
  setUserAgent(userAgent) {
7181
7340
  this.userAgent = userAgent;