@ethora/chat-component-rn 26.10.7 → 26.10.9

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 (96) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/lib/commonjs/components/Menu/HeaderRoomListMenu.js +3 -3
  3. package/lib/commonjs/components/Menu/HeaderRoomListMenu.js.map +1 -1
  4. package/lib/commonjs/components/MessageBubble/Message.js +16 -6
  5. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  6. package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js +16 -2
  7. package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js.map +1 -1
  8. package/lib/commonjs/components/VideoCalls/callActions.js +32 -2
  9. package/lib/commonjs/components/VideoCalls/callActions.js.map +1 -1
  10. package/lib/commonjs/components/VideoCalls/systemCall.js +83 -0
  11. package/lib/commonjs/components/VideoCalls/systemCall.js.map +1 -0
  12. package/lib/commonjs/components/VideoCalls/useCallKeep.js +84 -69
  13. package/lib/commonjs/components/VideoCalls/useCallKeep.js.map +1 -1
  14. package/lib/commonjs/helpers/incomingCallFromPush.js +95 -21
  15. package/lib/commonjs/helpers/incomingCallFromPush.js.map +1 -1
  16. package/lib/commonjs/main.js +33 -0
  17. package/lib/commonjs/main.js.map +1 -1
  18. package/lib/commonjs/networking/callTokenStanza.js +19 -1
  19. package/lib/commonjs/networking/callTokenStanza.js.map +1 -1
  20. package/lib/commonjs/networking/xmpp/sendMessageReaction.xmpp.js +22 -6
  21. package/lib/commonjs/networking/xmpp/sendMessageReaction.xmpp.js.map +1 -1
  22. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  23. package/lib/commonjs/push/sealedPush.js +13 -2
  24. package/lib/commonjs/push/sealedPush.js.map +1 -1
  25. package/lib/commonjs/push/sealedPushTask.js +63 -18
  26. package/lib/commonjs/push/sealedPushTask.js.map +1 -1
  27. package/lib/commonjs/version.js +1 -1
  28. package/lib/module/components/Menu/HeaderRoomListMenu.js +3 -3
  29. package/lib/module/components/Menu/HeaderRoomListMenu.js.map +1 -1
  30. package/lib/module/components/MessageBubble/Message.js +16 -6
  31. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  32. package/lib/module/components/VideoCalls/VideoCallOverlay.js +17 -3
  33. package/lib/module/components/VideoCalls/VideoCallOverlay.js.map +1 -1
  34. package/lib/module/components/VideoCalls/callActions.js +29 -0
  35. package/lib/module/components/VideoCalls/callActions.js.map +1 -1
  36. package/lib/module/components/VideoCalls/systemCall.js +73 -0
  37. package/lib/module/components/VideoCalls/systemCall.js.map +1 -0
  38. package/lib/module/components/VideoCalls/useCallKeep.js +79 -68
  39. package/lib/module/components/VideoCalls/useCallKeep.js.map +1 -1
  40. package/lib/module/helpers/incomingCallFromPush.js +92 -22
  41. package/lib/module/helpers/incomingCallFromPush.js.map +1 -1
  42. package/lib/module/main.js +3 -0
  43. package/lib/module/main.js.map +1 -1
  44. package/lib/module/networking/callTokenStanza.js +19 -1
  45. package/lib/module/networking/callTokenStanza.js.map +1 -1
  46. package/lib/module/networking/xmpp/sendMessageReaction.xmpp.js +21 -6
  47. package/lib/module/networking/xmpp/sendMessageReaction.xmpp.js.map +1 -1
  48. package/lib/module/networking/xmppClient.js.map +1 -1
  49. package/lib/module/push/sealedPush.js +11 -1
  50. package/lib/module/push/sealedPush.js.map +1 -1
  51. package/lib/module/push/sealedPushTask.js +62 -18
  52. package/lib/module/push/sealedPushTask.js.map +1 -1
  53. package/lib/module/version.js +1 -1
  54. package/lib/typescript/components/Menu/HeaderRoomListMenu.d.ts.map +1 -1
  55. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  56. package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts.map +1 -1
  57. package/lib/typescript/components/VideoCalls/callActions.d.ts +6 -0
  58. package/lib/typescript/components/VideoCalls/callActions.d.ts.map +1 -1
  59. package/lib/typescript/components/VideoCalls/systemCall.d.ts +25 -0
  60. package/lib/typescript/components/VideoCalls/systemCall.d.ts.map +1 -0
  61. package/lib/typescript/components/VideoCalls/useCallKeep.d.ts +8 -23
  62. package/lib/typescript/components/VideoCalls/useCallKeep.d.ts.map +1 -1
  63. package/lib/typescript/helpers/incomingCallFromPush.d.ts +5 -13
  64. package/lib/typescript/helpers/incomingCallFromPush.d.ts.map +1 -1
  65. package/lib/typescript/main.d.ts +6 -1
  66. package/lib/typescript/main.d.ts.map +1 -1
  67. package/lib/typescript/networking/callTokenStanza.d.ts.map +1 -1
  68. package/lib/typescript/networking/xmpp/sendMessageReaction.xmpp.d.ts +13 -1
  69. package/lib/typescript/networking/xmpp/sendMessageReaction.xmpp.d.ts.map +1 -1
  70. package/lib/typescript/networking/xmppClient.d.ts +2 -4
  71. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  72. package/lib/typescript/push/sealedPush.d.ts +6 -0
  73. package/lib/typescript/push/sealedPush.d.ts.map +1 -1
  74. package/lib/typescript/push/sealedPushTask.d.ts +31 -7
  75. package/lib/typescript/push/sealedPushTask.d.ts.map +1 -1
  76. package/lib/typescript/types/models/xmpp.model.d.ts +2 -4
  77. package/lib/typescript/types/models/xmpp.model.d.ts.map +1 -1
  78. package/lib/typescript/version.d.ts +1 -1
  79. package/package.json +1 -1
  80. package/plugins/notification-service/NotificationService.swift +105 -19
  81. package/plugins/withNotificationServiceExtension.js +44 -4
  82. package/src/components/Menu/HeaderRoomListMenu.tsx +13 -8
  83. package/src/components/MessageBubble/Message.tsx +16 -5
  84. package/src/components/VideoCalls/VideoCallOverlay.tsx +18 -2
  85. package/src/components/VideoCalls/callActions.ts +28 -0
  86. package/src/components/VideoCalls/systemCall.ts +109 -0
  87. package/src/components/VideoCalls/useCallKeep.ts +91 -101
  88. package/src/helpers/incomingCallFromPush.ts +97 -26
  89. package/src/main.ts +9 -1
  90. package/src/networking/callTokenStanza.ts +20 -1
  91. package/src/networking/xmpp/sendMessageReaction.xmpp.ts +38 -6
  92. package/src/networking/xmppClient.ts +2 -2
  93. package/src/push/sealedPush.ts +11 -0
  94. package/src/push/sealedPushTask.ts +95 -16
  95. package/src/types/models/xmpp.model.ts +2 -1
  96. package/src/version.ts +1 -1
@@ -310,6 +310,8 @@ const handleCallStateData = (
310
310
  !!currentCall.callId &&
311
311
  currentCall.callId === stanzaCallId;
312
312
 
313
+ if (stanzaCallId && currentCall.callId && !callIdMatches) {return;}
314
+
313
315
  const isTerminalState =
314
316
  state === 'ended' ||
315
317
  state === 'declined' ||
@@ -381,7 +383,7 @@ export const onCallTokenMessage = (stanza: Element): boolean => {
381
383
  .split('/')[0]
382
384
  .split('@')[0]
383
385
  .trim();
384
- handleCallStateData(inner, fromRoom);
386
+ handleCallStateData(data, fromRoom);
385
387
 
386
388
  // Server call-logs (carry callerXmppUsername / durationMs) should
387
389
  // flow on to onRealtimeMessage / onMessageHistory where they become
@@ -394,6 +396,13 @@ export const onCallTokenMessage = (stanza: Element): boolean => {
394
396
  return true;
395
397
  }
396
398
 
399
+ if (dataType === 'call-state-declined') {
400
+ handleCallStateData({
401
+ attrs: { ...data.attrs, state: 'declined' },
402
+ } as unknown as Element);
403
+ return true;
404
+ }
405
+
397
406
  if (dataType === 'call-invite') {
398
407
  const inviteKind = String(data.attrs?.kind || '').toLowerCase();
399
408
  const normalized: 'audio' | 'video' =
@@ -528,6 +537,16 @@ export const onCallTokenMessage = (stanza: Element): boolean => {
528
537
  return true;
529
538
  }
530
539
 
540
+ if (
541
+ callState.direction === 'incoming' &&
542
+ callState.phase !== 'idle' &&
543
+ !!callId &&
544
+ callState.callId === callId
545
+ ) {
546
+ callTrace('drop call-token: call already known from push', { callId });
547
+ return true;
548
+ }
549
+
531
550
  store.dispatch(
532
551
  setIncomingCallToken({
533
552
  roomJid,
@@ -1,18 +1,51 @@
1
1
  import { Client, xml } from '@xmpp/client';
2
+ import { shouldEncrypt } from '../../e2ee/send';
3
+
4
+ export interface ReactionStanzaData {
5
+ firstName?: string;
6
+ lastName?: string;
7
+ photoURL?: string;
8
+ push?: boolean;
9
+ messageAuthor?: string;
10
+ messageText?: string;
11
+ isMediafile?: string;
12
+ mimetype?: string;
13
+ fileName?: string;
14
+ }
15
+
16
+ export const REACTION_MESSAGE_TEXT_MAX = 120;
2
17
 
3
18
  export function sendMessageReaction(
4
19
  client: Client,
5
20
  messageId: string,
6
21
  roomJid: string,
7
22
  reactionsList: string[],
8
- data: any,
9
- reactionSymbol?: any,
23
+ data: ReactionStanzaData = {}
10
24
  ) {
11
25
  const id = `message-reaction:${Date.now().toString()}`;
12
26
 
13
- const dataReaction = {
14
- senderFirstName: data.firstName,
15
- senderLastName: data.lastName,
27
+ const push = Boolean(data.push) && reactionsList.length > 0;
28
+ const messageText =
29
+ push && !shouldEncrypt(roomJid)
30
+ ? String(data.messageText ?? '').slice(0, REACTION_MESSAGE_TEXT_MAX)
31
+ : '';
32
+
33
+ const dataReaction: Record<string, string> = {
34
+ senderFirstName: data.firstName ?? '',
35
+ senderLastName: data.lastName ?? '',
36
+ senderJID: client.jid?.toString() ?? '',
37
+ roomJid,
38
+ ...(data.photoURL ? { photoURL: data.photoURL } : {}),
39
+ ...(push
40
+ ? {
41
+ push: 'true',
42
+ messageAuthor: data.messageAuthor ?? '',
43
+ messageText,
44
+ isMediafile: data.isMediafile ?? '',
45
+ mimetype: data.mimetype ?? '',
46
+ fileName: data.fileName ?? '',
47
+ }
48
+ : {}),
16
49
  };
17
50
 
18
51
  const message = xml(
@@ -31,7 +64,6 @@ export function sendMessageReaction(
31
64
  xmlns: 'urn:xmpp:reactions:0',
32
65
  },
33
66
  ...reactionsList.map((reaction) => xml('reaction', {}, reaction ?? ''))
34
- // xml('reaction', {}, reactionSymbol ?? '👋'),
35
67
  ),
36
68
  xml('data', dataReaction),
37
69
  xml('store', { xmlns: 'urn:xmpp:hints' })
@@ -16,7 +16,7 @@ import {
16
16
  HistoryPageCursor,
17
17
  } from './xmpp/getHistory.xmpp';
18
18
  import { sendTextMessage } from './xmpp/sendTextMessage.xmpp';
19
- import { sendMessageReaction } from './xmpp/sendMessageReaction.xmpp';
19
+ import { sendMessageReaction, type ReactionStanzaData } from './xmpp/sendMessageReaction.xmpp';
20
20
  import { sendReceivedReceipt } from './xmpp/sendReceivedReceipt.xmpp';
21
21
  import { sendDisplayedMarker } from './xmpp/sendDisplayedMarker.xmpp';
22
22
  import { sendTextMessageWithTranslateTag } from './xmpp/sendTextMessageWithTranslateTag.xmpp';
@@ -1646,7 +1646,7 @@ export class XmppClient {
1646
1646
  messageId: string,
1647
1647
  roomJid: string,
1648
1648
  reactionsList: string[],
1649
- data?: { firstName?: string; lastName?: string }
1649
+ data?: ReactionStanzaData
1650
1650
  ) {
1651
1651
  sendMessageReaction(
1652
1652
  this.client,
@@ -20,6 +20,8 @@ export interface SealedPushEnvelope {
20
20
  msgID: string;
21
21
  /** Recipient. */
22
22
  userJid: string;
23
+ /** 'reaction' for a push about an emoji put on the user's message; absent for a message. */
24
+ type?: string;
23
25
  /** Base64 of the sealed box; absent when the service could not seal. */
24
26
  enc?: string;
25
27
  }
@@ -35,8 +37,14 @@ export interface SealedPushContent {
35
37
  msgID: string;
36
38
  /** Sender avatar URL, '' when none. */
37
39
  image: string;
40
+ /** 'reaction' with the emoji in `reaction`; absent for a message. */
41
+ type?: string;
42
+ reaction?: string;
38
43
  }
39
44
 
45
+ export const isReactionPush = (envelope: Pick<SealedPushEnvelope, 'type'>): boolean =>
46
+ envelope.type === 'reaction';
47
+
40
48
  export type SealedPushFailure =
41
49
  /** No key on this device (never generated, or the store is not readable). */
42
50
  | 'no-key'
@@ -68,6 +76,7 @@ export const sealedPushEnvelopeOf = (
68
76
  jid: str(data.jid),
69
77
  msgID: str(data.msgID),
70
78
  userJid: str(data.userJid),
79
+ ...(str(data.type) ? { type: str(data.type) } : {}),
71
80
  ...(enc ? { enc } : {}),
72
81
  };
73
82
  };
@@ -88,6 +97,8 @@ const parseContent = (plaintext: Uint8Array): SealedPushContent | null => {
88
97
  chatJid: str(parsed.chatJid),
89
98
  msgID: str(parsed.msgID),
90
99
  image: str(parsed.image),
100
+ ...(str(parsed.type) ? { type: str(parsed.type) } : {}),
101
+ ...(str(parsed.reaction) ? { reaction: str(parsed.reaction) } : {}),
91
102
  };
92
103
  } catch {
93
104
  return null;
@@ -1,7 +1,13 @@
1
1
  import { AppState, Platform } from 'react-native';
2
2
  import { pushLog } from '../utils/devLogger';
3
+ import { isCallPush } from '../helpers/callPush';
3
4
  import { loadPushSecretKey } from './pushKeyStore';
4
- import { openSealedPush, sealedPushEnvelopeOf, type OpenSealedPushResult } from './sealedPush';
5
+ import {
6
+ isReactionPush,
7
+ openSealedPush,
8
+ sealedPushEnvelopeOf,
9
+ type OpenSealedPushResult,
10
+ } from './sealedPush';
5
11
 
6
12
  // Android half of encrypted pushes. A sealed push is data-only, so the OS
7
13
  // shows nothing by itself: expo-notifications hands it to a background
@@ -18,12 +24,40 @@ export const SEALED_PUSH_TASK = 'ETHORA_SEALED_PUSH';
18
24
  export interface SealedPushTaskOptions {
19
25
  /** Android notification channel; the OS default channel when omitted. */
20
26
  channelId?: string;
27
+ /**
28
+ * Channel for a reaction to the user's message (a silent one, Telegram's
29
+ * way); `channelId` when omitted. Reactions carry no sound of their own
30
+ * either way.
31
+ */
32
+ reactionChannelId?: string;
21
33
  /** Shown when the box does not open (no key, another key, damaged). Default "New message". */
22
34
  placeholderTitle?: string;
23
35
  placeholderBody?: string;
36
+ /**
37
+ * An incoming-call push (data-only, high priority) while the app is not
38
+ * in the foreground; the host shows its own incoming-call notification
39
+ * (full-screen intent, Answer / Decline). Not called in the foreground,
40
+ * where the XMPP invite rings the in-app call screen.
41
+ */
42
+ onCallPush?: (data: Record<string, unknown>) => void | Promise<void>;
43
+ onMessageShown?: (shown: ShownMessagePush) => void | Promise<void>;
44
+ }
45
+
46
+ export interface ShownMessagePush {
47
+ identifier: string;
48
+ channelId?: string;
49
+ title: string;
50
+ body: string;
51
+ sender: string;
52
+ senderJid: string;
53
+ chat: string;
54
+ chatJid: string;
55
+ image: string;
56
+ reaction: boolean;
24
57
  }
25
58
 
26
59
  const PLACEHOLDER_TITLE = 'New message';
60
+ const PLACEHOLDER_REACTION_TITLE = 'New reaction';
27
61
 
28
62
  let registered = false;
29
63
 
@@ -49,9 +83,9 @@ const loadNotifications = (): any => {
49
83
  * itself, `data.dataString` carries the same as a JSON string on some
50
84
  * versions, and a Firebase `RemoteMessage` keeps them under `data.data`.
51
85
  */
52
- export const sealedPushDataOf = (payload: unknown): Record<string, unknown> | null => {
86
+ const pushDataCandidates = (payload: unknown): unknown[] => {
53
87
  const root = payload && typeof payload === 'object' ? (payload as Record<string, unknown>) : null;
54
- if (!root) {return null;}
88
+ if (!root) {return [];}
55
89
  const candidates: unknown[] = [root.data, root];
56
90
  const data = root.data && typeof root.data === 'object' ? (root.data as Record<string, unknown>) : null;
57
91
  if (data) {
@@ -64,30 +98,39 @@ export const sealedPushDataOf = (payload: unknown): Record<string, unknown> | nu
64
98
  }
65
99
  candidates.push(data.data);
66
100
  }
67
- for (const candidate of candidates) {
68
- if (sealedPushEnvelopeOf(candidate as Record<string, unknown>)) {
69
- return candidate as Record<string, unknown>;
70
- }
71
- }
72
- return null;
101
+ return candidates.filter((c) => c && typeof c === 'object');
73
102
  };
74
103
 
104
+ export const sealedPushDataOf = (payload: unknown): Record<string, unknown> | null =>
105
+ (pushDataCandidates(payload).find((candidate) =>
106
+ sealedPushEnvelopeOf(candidate as Record<string, unknown>)
107
+ ) as Record<string, unknown> | undefined) ?? null;
108
+
109
+ /** The call fields of an incoming-call push in the task payload, if it is one. */
110
+ export const callPushDataOf = (payload: unknown): Record<string, unknown> | null =>
111
+ (pushDataCandidates(payload).find((candidate) =>
112
+ isCallPush(candidate as Record<string, unknown>)
113
+ ) as Record<string, unknown> | undefined) ?? null;
114
+
75
115
  const notificationContent = (result: OpenSealedPushResult, options: SealedPushTaskOptions) => {
76
116
  const { envelope } = result;
77
117
  // What the tap handler (`handlePushPayload` through the host's response
78
118
  // listener) needs: the same ids a plain push carries on `data`.
119
+ const reaction = isReactionPush(envelope);
79
120
  const data = {
80
121
  jid: envelope.jid,
81
122
  msgID: envelope.msgID,
82
123
  userJid: envelope.userJid,
83
124
  sealed: true,
125
+ ...(reaction ? { type: 'reaction' } : {}),
84
126
  };
127
+ const sound = reaction ? false : 'default';
85
128
  if (!result.ok) {
86
129
  return {
87
- title: options.placeholderTitle || PLACEHOLDER_TITLE,
130
+ title: options.placeholderTitle || (reaction ? PLACEHOLDER_REACTION_TITLE : PLACEHOLDER_TITLE),
88
131
  ...(options.placeholderBody ? { body: options.placeholderBody } : {}),
89
132
  data,
90
- sound: 'default',
133
+ sound,
91
134
  };
92
135
  }
93
136
  const { content } = result;
@@ -95,14 +138,16 @@ const notificationContent = (result: OpenSealedPushResult, options: SealedPushTa
95
138
  title: content.title || content.sender || content.chat || PLACEHOLDER_TITLE,
96
139
  body: content.body,
97
140
  data: { ...data, chatJid: content.chatJid, senderJid: content.senderJid },
98
- sound: 'default',
141
+ sound,
99
142
  };
100
143
  };
101
144
 
102
145
  /**
103
146
  * Open a sealed push and show it. One notification per chat: the room JID
104
147
  * is the identifier, so a newer message replaces the older one the way the
105
- * service's collapse key does for plain pushes.
148
+ * service's collapse key does for plain pushes. A reaction has its own
149
+ * identifier per chat (and its own channel), so it never replaces the
150
+ * chat's new-message notification.
106
151
  */
107
152
  export async function presentSealedPush(
108
153
  data: Record<string, unknown>,
@@ -122,11 +167,34 @@ export async function presentSealedPush(
122
167
  console.warn(`[push] sealed push kid=${envelope.kid} not opened (${result.reason}), showing the placeholder`);
123
168
  pushLog('warn', `push: sealed push not opened (${result.reason}), showing the placeholder`);
124
169
  }
170
+ const reaction = isReactionPush(envelope);
171
+ const base = envelope.jid || envelope.msgID || '';
172
+ const identifier = base ? (reaction ? `${base}#reaction` : base) : undefined;
173
+ const channelId = (reaction && options.reactionChannelId) || options.channelId;
174
+ const content = notificationContent(result, options);
125
175
  await notifications.scheduleNotificationAsync({
126
- identifier: envelope.jid || envelope.msgID || undefined,
127
- content: notificationContent(result, options),
128
- trigger: options.channelId ? { channelId: options.channelId } : null,
176
+ identifier,
177
+ content,
178
+ trigger: channelId ? { channelId } : null,
129
179
  });
180
+ if (result.ok && result.content.image && identifier && options.onMessageShown) {
181
+ try {
182
+ await options.onMessageShown({
183
+ identifier,
184
+ channelId,
185
+ title: content.title,
186
+ body: content.body ?? '',
187
+ sender: result.content.sender,
188
+ senderJid: result.content.senderJid,
189
+ chat: result.content.chat,
190
+ chatJid: result.content.chatJid,
191
+ image: result.content.image,
192
+ reaction,
193
+ });
194
+ } catch (e) {
195
+ pushLog('warn', 'push: drawing the sender avatar failed', e);
196
+ }
197
+ }
130
198
  return result;
131
199
  }
132
200
 
@@ -150,6 +218,17 @@ export function registerSealedPushTask(options: SealedPushTaskOptions = {}): boo
150
218
  }
151
219
  taskManager.defineTask(SEALED_PUSH_TASK, async ({ data, error }: { data: unknown; error: unknown }) => {
152
220
  if (error) {return;}
221
+ const call = callPushDataOf(data);
222
+ if (call) {
223
+ if (AppState.currentState === 'active' || !options.onCallPush) {return;}
224
+ try {
225
+ console.log(`[push] incoming call push ${String(call.callId ?? '')}`);
226
+ await options.onCallPush(call);
227
+ } catch (e) {
228
+ pushLog('warn', 'push: showing an incoming call failed', e);
229
+ }
230
+ return;
231
+ }
153
232
  const map = sealedPushDataOf(data);
154
233
  if (!map) {return;}
155
234
  // In the foreground the room is live over XMPP; the host's
@@ -5,6 +5,7 @@
5
5
  // methods) rather than duplicating here.
6
6
 
7
7
  import { Client } from '@xmpp/client';
8
+ import type { ReactionStanzaData } from '../../networking/xmpp/sendMessageReaction.xmpp';
8
9
  import XmppClient, {
9
10
  HistoryPage,
10
11
  HistoryPageCursor,
@@ -153,7 +154,7 @@ export interface XmppClientInterface {
153
154
  messageId: string,
154
155
  roomJid: string,
155
156
  reactionsList: string[],
156
- data?: { firstName?: string; lastName?: string }
157
+ data?: ReactionStanzaData
157
158
  ): void;
158
159
  sendTextMessageWithTranslateTagStanza?(
159
160
  roomJID: string,
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated by scripts/sync-version.mjs on `npm version`.
2
2
  // Edit package.json's version instead of this file.
3
- export const ETHORA_CHAT_COMPONENT_VERSION = '26.10.7';
3
+ export const ETHORA_CHAT_COMPONENT_VERSION = '26.10.9';