@ermis-network/ermis-chat-sdk 1.0.6 → 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.
@@ -1008,6 +1008,44 @@ var Channel = class {
1008
1008
  this.getClient().baseURL + `/messages/${this.type}/${this.id}/${messageID}/unpin`
1009
1009
  );
1010
1010
  }
1011
+ async pin() {
1012
+ if (this.data) this.data.is_pinned = true;
1013
+ this.getClient().dispatchEvent({
1014
+ type: "channel.pinned",
1015
+ cid: this.cid,
1016
+ channel: this.data
1017
+ });
1018
+ try {
1019
+ return await this.getClient().pinChannel(this.type, this.id);
1020
+ } catch (e) {
1021
+ if (this.data) this.data.is_pinned = false;
1022
+ this.getClient().dispatchEvent({
1023
+ type: "channel.unpinned",
1024
+ cid: this.cid,
1025
+ channel: this.data
1026
+ });
1027
+ throw e;
1028
+ }
1029
+ }
1030
+ async unpin() {
1031
+ if (this.data) this.data.is_pinned = false;
1032
+ this.getClient().dispatchEvent({
1033
+ type: "channel.unpinned",
1034
+ cid: this.cid,
1035
+ channel: this.data
1036
+ });
1037
+ try {
1038
+ return await this.getClient().unpinChannel(this.type, this.id);
1039
+ } catch (e) {
1040
+ if (this.data) this.data.is_pinned = true;
1041
+ this.getClient().dispatchEvent({
1042
+ type: "channel.pinned",
1043
+ cid: this.cid,
1044
+ channel: this.data
1045
+ });
1046
+ throw e;
1047
+ }
1048
+ }
1011
1049
  async editMessage(oldMessageID, message) {
1012
1050
  return await this.getClient().post(this.getClient().baseURL + `/messages/${this.type}/${this.id}/${oldMessageID}`, {
1013
1051
  message
@@ -1913,6 +1951,18 @@ var Channel = class {
1913
1951
  delete channelState.members[event.user.id];
1914
1952
  }
1915
1953
  break;
1954
+ case "channel.topic.enabled":
1955
+ if (channel.data) {
1956
+ channel.data.topics_enabled = true;
1957
+ }
1958
+ channelState.topics = channelState.topics || [];
1959
+ break;
1960
+ case "channel.topic.disabled":
1961
+ if (channel.data) {
1962
+ channel.data.topics_enabled = false;
1963
+ }
1964
+ channelState.topics = [];
1965
+ break;
1916
1966
  case "channel.updated":
1917
1967
  if (event.channel) {
1918
1968
  channel.data = {
@@ -2051,7 +2101,12 @@ var Channel = class {
2051
2101
  const topic = this.getClient().channel(event.channel_type || "", event.channel_id || "");
2052
2102
  topic.data = event.channel;
2053
2103
  topic._initializeState(topicState, "latest");
2054
- channelState.topics?.unshift(topic);
2104
+ if (!channelState.topics) {
2105
+ channelState.topics = [];
2106
+ }
2107
+ if (!channelState.topics.some((t) => t.cid === topic.cid)) {
2108
+ channelState.topics.push(topic);
2109
+ }
2055
2110
  break;
2056
2111
  case "channel.topic.closed":
2057
2112
  if (channel.data) {
@@ -2870,7 +2925,6 @@ var ErmisChat = class _ErmisChat {
2870
2925
  params.avatar = user.avatar;
2871
2926
  }
2872
2927
  const url = this.userBaseURL + "/get_token/external_auth";
2873
- const query = new URLSearchParams(params).toString();
2874
2928
  const headers = {
2875
2929
  "Content-Type": "application/json"
2876
2930
  };
@@ -2878,21 +2932,21 @@ var ErmisChat = class _ErmisChat {
2878
2932
  const tokenStr = typeof token === "string" && token.startsWith("Bearer ") ? token : `Bearer ${token}`;
2879
2933
  headers["Authorization"] = tokenStr;
2880
2934
  }
2881
- const response = await fetch(`${url}?${query}`, {
2882
- method: "GET",
2883
- headers
2884
- });
2885
- if (!response.ok) {
2886
- let errorMsg = "";
2887
- try {
2888
- const errorData = await response.json();
2889
- errorMsg = errorData.message || JSON.stringify(errorData);
2890
- } catch {
2891
- errorMsg = await response.text();
2935
+ try {
2936
+ const response = await this.axiosInstance.get(url, {
2937
+ params,
2938
+ headers
2939
+ });
2940
+ return response.data;
2941
+ } catch (error) {
2942
+ let errorMsg = "Failed to fetch external auth token";
2943
+ if (error.response && error.response.data) {
2944
+ errorMsg = error.response.data.message || JSON.stringify(error.response.data);
2945
+ } else if (error.message) {
2946
+ errorMsg = error.message;
2892
2947
  }
2893
2948
  throw new Error(errorMsg);
2894
2949
  }
2895
- return await response.json();
2896
2950
  }
2897
2951
  /**
2898
2952
  * Connects a user to the Ermis network and establishes the WebSocket connection.
@@ -2903,19 +2957,24 @@ var ErmisChat = class _ErmisChat {
2903
2957
  * @param extenal_auth - Set to `true` to use your custom backend external authentication flow.
2904
2958
  * @returns A promise resolving to the API connection response once authenticated.
2905
2959
  */
2906
- connectUser = async (user, userTokenOrProvider, extenal_auth) => {
2960
+ connectUser = async (user, userTokenOrProvider, external_auth) => {
2907
2961
  this.logger("info", "client:connectUser() - started", {
2908
2962
  tags: ["connection", "client"]
2909
2963
  });
2910
2964
  if (!user.id) {
2911
2965
  throw new Error('The "id" field on the user is missing');
2912
2966
  }
2913
- if (extenal_auth) {
2967
+ let connectionUser = user;
2968
+ let connectionToken = userTokenOrProvider;
2969
+ if (external_auth) {
2914
2970
  const external_auth_token = await this.getExternalAuthToken(user, userTokenOrProvider);
2915
- userTokenOrProvider = external_auth_token.token;
2916
- user.id = external_auth_token.user_id;
2971
+ connectionToken = external_auth_token.token;
2972
+ connectionUser = {
2973
+ ...user,
2974
+ id: external_auth_token.user_id
2975
+ };
2917
2976
  }
2918
- if (this.userID === user.id && this.setUserPromise) {
2977
+ if (this.userID === connectionUser.id && this.setUserPromise) {
2919
2978
  console.warn(
2920
2979
  "Consecutive calls to connectUser is detected, ideally you should only call this function once in your app."
2921
2980
  );
@@ -2931,10 +2990,10 @@ var ErmisChat = class _ErmisChat {
2931
2990
  '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.'
2932
2991
  );
2933
2992
  }
2934
- this.userID = user.id;
2935
- const setTokenPromise = this._setToken(user, userTokenOrProvider);
2936
- this._setUser(user);
2937
- this.state.updateUser({ id: user.id, name: user?.name || user.id, avatar: user?.avatar || "" });
2993
+ this.userID = connectionUser.id;
2994
+ const setTokenPromise = this._setToken(connectionUser, connectionToken);
2995
+ this._setUser(connectionUser);
2996
+ this.state.updateUser({ id: connectionUser.id, name: connectionUser?.name || connectionUser.id, avatar: connectionUser?.avatar || "" });
2938
2997
  const wsPromise = this.openConnection();
2939
2998
  this.setUserPromise = Promise.all([setTokenPromise, wsPromise]).then(
2940
2999
  (result) => result[1]
@@ -3160,7 +3219,7 @@ var ErmisChat = class _ErmisChat {
3160
3219
  }
3161
3220
  dispatchEvent = (event) => {
3162
3221
  if (!event.received_at) event.received_at = /* @__PURE__ */ new Date();
3163
- if (event.type === "channel.created") {
3222
+ if (event.type === "channel.created" || event.type === "channel.topic.created") {
3164
3223
  this._handleChannelCreatedEvent(event).then(() => {
3165
3224
  this._afterDispatchEvent(event);
3166
3225
  });
@@ -3294,6 +3353,79 @@ var ErmisChat = class _ErmisChat {
3294
3353
  });
3295
3354
  }
3296
3355
  }
3356
+ if (event.type === "message.new" && event.channel_type === "topic") {
3357
+ postListenerCallbacks.push(() => {
3358
+ const parentCid = event.parent_cid || event.channel?.parent_cid;
3359
+ if (parentCid && this.activeChannels[parentCid]) {
3360
+ const parentChannel = this.activeChannels[parentCid];
3361
+ if (parentChannel.state.topics) {
3362
+ parentChannel.state.topics.sort((a, b) => {
3363
+ const aLatest = a.state?.latestMessages?.[a.state.latestMessages.length - 1]?.created_at;
3364
+ const bLatest = b.state?.latestMessages?.[b.state.latestMessages.length - 1]?.created_at;
3365
+ const aTime = aLatest ? new Date(aLatest).getTime() : 0;
3366
+ const bTime = bLatest ? new Date(bLatest).getTime() : 0;
3367
+ return bTime - aTime;
3368
+ });
3369
+ parentChannel._callChannelListeners({ ...event, type: "channel.updated", channel: parentChannel.data });
3370
+ }
3371
+ }
3372
+ });
3373
+ }
3374
+ if (event.type === "channel.topic.updated") {
3375
+ postListenerCallbacks.push(() => {
3376
+ const parentCid = event.parent_cid || event.channel?.parent_cid;
3377
+ if (parentCid && this.activeChannels[parentCid]) {
3378
+ const parentChannel = this.activeChannels[parentCid];
3379
+ if (parentChannel.state?.topics && event.channel) {
3380
+ const topicIndex = parentChannel.state.topics.findIndex((t) => t.cid === event.cid || t.channel?.cid === event.cid);
3381
+ if (topicIndex !== -1) {
3382
+ const t = parentChannel.state.topics[topicIndex];
3383
+ if (t.data) {
3384
+ t.data = { ...t.data, ...event.channel };
3385
+ } else if (t.channel) {
3386
+ t.channel = { ...t.channel, ...event.channel };
3387
+ } else {
3388
+ Object.assign(t, event.channel);
3389
+ }
3390
+ }
3391
+ parentChannel._callChannelListeners({ ...event, type: "channel.updated", channel: parentChannel.data });
3392
+ }
3393
+ }
3394
+ if (event.cid && this.activeChannels[event.cid]) {
3395
+ const topicChannel = this.activeChannels[event.cid];
3396
+ if (event.channel) {
3397
+ topicChannel.data = { ...topicChannel.data, ...event.channel };
3398
+ topicChannel._callChannelListeners({ ...event, type: "channel.updated", channel: topicChannel.data });
3399
+ }
3400
+ }
3401
+ });
3402
+ }
3403
+ if (event.type === "channel.topic.closed" || event.type === "channel.topic.reopen") {
3404
+ postListenerCallbacks.push(() => {
3405
+ const isClosed = event.type === "channel.topic.closed";
3406
+ const parentCid = event.parent_cid;
3407
+ if (parentCid && this.activeChannels[parentCid]) {
3408
+ const parentChannel = this.activeChannels[parentCid];
3409
+ if (parentChannel.state?.topics) {
3410
+ const topicIndex = parentChannel.state.topics.findIndex((t) => t.cid === event.cid || t.channel?.cid === event.cid);
3411
+ if (topicIndex !== -1) {
3412
+ const t = parentChannel.state.topics[topicIndex];
3413
+ if (t.data) t.data.is_closed_topic = isClosed;
3414
+ else if (t.channel) t.channel.is_closed_topic = isClosed;
3415
+ else t.is_closed_topic = isClosed;
3416
+ }
3417
+ parentChannel._callChannelListeners({ ...event, type: "channel.updated", channel: parentChannel.data });
3418
+ }
3419
+ }
3420
+ if (event.cid && this.activeChannels[event.cid]) {
3421
+ const topicChannel = this.activeChannels[event.cid];
3422
+ if (topicChannel.data) {
3423
+ topicChannel.data.is_closed_topic = isClosed;
3424
+ }
3425
+ topicChannel._callChannelListeners({ ...event, type: "channel.updated", channel: topicChannel.data });
3426
+ }
3427
+ });
3428
+ }
3297
3429
  if (event.type === "connection.recovered") {
3298
3430
  postListenerCallbacks.push(() => {
3299
3431
  Object.values(this.activeChannels).forEach((channel) => {
@@ -3531,7 +3663,14 @@ var ErmisChat = class _ErmisChat {
3531
3663
  _updateProjectID(project_id) {
3532
3664
  this.projectId = project_id;
3533
3665
  }
3534
- async uploadFile(file) {
3666
+ /**
3667
+ * Uploads a new avatar image for the current user.
3668
+ * The user's avatar URL is automatically updated in both the client and the local state.
3669
+ *
3670
+ * @param file - The image file to upload.
3671
+ * @returns The response containing the new avatar URL.
3672
+ */
3673
+ async uploadAvatar(file) {
3535
3674
  const formData = new FormData();
3536
3675
  formData.append("avatar", file);
3537
3676
  let response = await this.post(this.userBaseURL + "/users/upload", formData, {
@@ -3546,12 +3685,8 @@ var ErmisChat = class _ErmisChat {
3546
3685
  }
3547
3686
  return response;
3548
3687
  }
3549
- async updateProfile(name, about_me) {
3550
- let body = {
3551
- name,
3552
- about_me
3553
- };
3554
- let response = await this.patch(this.userBaseURL + "/users/update", body);
3688
+ async updateProfile(updates) {
3689
+ let response = await this.patch(this.userBaseURL + "/users/update", updates);
3555
3690
  this.user = response;
3556
3691
  this.state.updateUser(response);
3557
3692
  return response;
@@ -3704,45 +3839,55 @@ var ErmisChat = class _ErmisChat {
3704
3839
  return channel;
3705
3840
  };
3706
3841
  /**
3707
- * Creates an interactive `meeting` Channel locally, and immediately creates it on the server.
3708
- * Consumers can customize the `name` field. `members` and `public` fields are constrained.
3842
+ * Creates a quick channel and immediately registers it on the server.
3843
+ * Quick channels are public group channels that anyone can join without an invitation.
3844
+ * The creator is added as the first member automatically.
3709
3845
  *
3710
- * @param name - The custom name for the meeting channel.
3846
+ * @param name - An optional display name for the channel.
3711
3847
  * @returns A promise that resolves to the created `Channel` object.
3712
3848
  */
3713
- async createMeetingChannel(name) {
3849
+ async createQuickChannel(name) {
3714
3850
  if (!this.userID) {
3715
3851
  throw Error("Call connectUser before creating a channel");
3716
3852
  }
3853
+ const now = /* @__PURE__ */ new Date();
3854
+ const formattedDate = new Intl.DateTimeFormat("en-US", {
3855
+ month: "short",
3856
+ day: "2-digit",
3857
+ year: "numeric",
3858
+ hour: "2-digit",
3859
+ minute: "2-digit",
3860
+ hour12: false
3861
+ }).format(now);
3717
3862
  const payload = {
3718
- name: name || `Meeting Public - ${(/* @__PURE__ */ new Date()).toISOString()}`,
3863
+ name: name || `Quick Channel - ${formattedDate}`,
3719
3864
  members: [this.userID],
3720
3865
  public: true
3721
3866
  };
3722
- const meetingChannel = this.channel("meeting", payload);
3723
- await meetingChannel.create();
3724
- return meetingChannel;
3867
+ const quickChannel = this.channel("meeting", payload);
3868
+ await quickChannel.create();
3869
+ return quickChannel;
3725
3870
  }
3726
3871
  /**
3727
- * Joins a `meeting` channel.
3728
- * It queries/watches the channel to see if caller is already a member.
3729
- * If not, it accepts the invite to join the channel, then watches it again to reflect changes.
3872
+ * Joins a quick channel by its ID.
3873
+ * Automatically checks whether the caller is already a member.
3874
+ * If not, it joins the channel and synchronizes state.
3730
3875
  *
3731
- * @param channelId - The ID of the meeting channel to join.
3876
+ * @param channelId - The ID of the quick channel to join.
3732
3877
  * @returns A promise that resolves to the joined `Channel` object.
3733
3878
  */
3734
- async joinMeetingChannel(channelId) {
3879
+ async joinQuickChannel(channelId) {
3735
3880
  if (!this.userID) {
3736
3881
  throw Error("Call connectUser before joining a channel");
3737
3882
  }
3738
- const meetingChannel = this.channel("meeting", channelId);
3739
- await meetingChannel.watch();
3740
- const isMember = meetingChannel.state.members && meetingChannel.state.members[this.userID];
3883
+ const quickChannel = this.channel("meeting", channelId);
3884
+ await quickChannel.watch();
3885
+ const isMember = quickChannel.state.members && quickChannel.state.members[this.userID];
3741
3886
  if (!isMember) {
3742
- await meetingChannel.acceptInvite("join");
3743
- await meetingChannel.watch();
3887
+ await quickChannel.acceptInvite("join");
3888
+ await quickChannel.watch();
3744
3889
  }
3745
- return meetingChannel;
3890
+ return quickChannel;
3746
3891
  }
3747
3892
  _normalizeExpiration(timeoutOrExpirationDate) {
3748
3893
  let pinExpires = null;
@@ -3758,7 +3903,7 @@ var ErmisChat = class _ErmisChat {
3758
3903
  return pinExpires;
3759
3904
  }
3760
3905
  getUserAgent() {
3761
- return this.userAgent || `ermis-chat-sdk-javascript-client-${this.node ? "node" : "browser"}-${"1.0.6"}`;
3906
+ return this.userAgent || `ermis-chat-sdk-javascript-client-${this.node ? "node" : "browser"}-${"1.0.7"}`;
3762
3907
  }
3763
3908
  setUserAgent(userAgent) {
3764
3909
  this.userAgent = userAgent;
@@ -7116,7 +7261,7 @@ var ErmisAuthProvider = class {
7116
7261
  return data;
7117
7262
  }
7118
7263
  getUserAgent() {
7119
- return this.userAgent || `ermis-chat-sdk-javascript-client-${this.node ? "node" : "browser"}-${"1.0.6"}`;
7264
+ return this.userAgent || `ermis-chat-sdk-javascript-client-${this.node ? "node" : "browser"}-${"1.0.7"}`;
7120
7265
  }
7121
7266
  setUserAgent(userAgent) {
7122
7267
  this.userAgent = userAgent;