@ethora/chat-component-rn 26.5.5 → 26.5.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.
Files changed (149) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/lib/commonjs/components/MainComponents/ChatHeader.js +7 -1
  3. package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
  4. package/lib/commonjs/components/MainComponents/ChatRoom.js +8 -2
  5. package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
  6. package/lib/commonjs/components/MainComponents/ChatWrapper.js +2 -1
  7. package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
  8. package/lib/commonjs/components/MainComponents/MediaMessage.js +11 -5
  9. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  10. package/lib/commonjs/components/MainComponents/MessageList.js +10 -2
  11. package/lib/commonjs/components/MainComponents/MessageList.js.map +1 -1
  12. package/lib/commonjs/components/MessageBubble/Message.js +60 -7
  13. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  14. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +35 -16
  15. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  16. package/lib/commonjs/components/Modals/ModalWrapper/ModalWrapper.js +11 -2
  17. package/lib/commonjs/components/Modals/ModalWrapper/ModalWrapper.js.map +1 -1
  18. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +1 -1
  19. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  20. package/lib/commonjs/components/styled/MediaFilePreview.js +96 -5
  21. package/lib/commonjs/components/styled/MediaFilePreview.js.map +1 -1
  22. package/lib/commonjs/components/styled/MessageImage.js +1 -2
  23. package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
  24. package/lib/commonjs/components/styled/SendInput.js +4 -2
  25. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  26. package/lib/commonjs/components/styled/VideoMessage.js +2 -10
  27. package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
  28. package/lib/commonjs/context/xmppProvider.js +71 -2
  29. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  30. package/lib/commonjs/helpers/mimeToExtension.js +131 -0
  31. package/lib/commonjs/helpers/mimeToExtension.js.map +1 -0
  32. package/lib/commonjs/helpers/resolveInitBeforeLoadUser.js +73 -1
  33. package/lib/commonjs/helpers/resolveInitBeforeLoadUser.js.map +1 -1
  34. package/lib/commonjs/hooks/useChatWrapperInit.js +22 -10
  35. package/lib/commonjs/hooks/useChatWrapperInit.js.map +1 -1
  36. package/lib/commonjs/hooks/useEventHandlers.js +9 -1
  37. package/lib/commonjs/hooks/useEventHandlers.js.map +1 -1
  38. package/lib/commonjs/hooks/useMessageHeapState.js +6 -2
  39. package/lib/commonjs/hooks/useMessageHeapState.js.map +1 -1
  40. package/lib/commonjs/hooks/useSendMessage.js +48 -4
  41. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  42. package/lib/commonjs/networking/api-requests/auth.api.js +7 -1
  43. package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
  44. package/lib/commonjs/networking/xmppClient.js +87 -1
  45. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  46. package/lib/commonjs/roomStore/roomHeapSlice.js +26 -8
  47. package/lib/commonjs/roomStore/roomHeapSlice.js.map +1 -1
  48. package/lib/module/components/MainComponents/ChatHeader.js +7 -1
  49. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  50. package/lib/module/components/MainComponents/ChatRoom.js +8 -2
  51. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  52. package/lib/module/components/MainComponents/ChatWrapper.js +2 -1
  53. package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
  54. package/lib/module/components/MainComponents/MediaMessage.js +11 -5
  55. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  56. package/lib/module/components/MainComponents/MessageList.js +11 -3
  57. package/lib/module/components/MainComponents/MessageList.js.map +1 -1
  58. package/lib/module/components/MessageBubble/Message.js +60 -7
  59. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  60. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +36 -17
  61. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  62. package/lib/module/components/Modals/ModalWrapper/ModalWrapper.js +11 -2
  63. package/lib/module/components/Modals/ModalWrapper/ModalWrapper.js.map +1 -1
  64. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +1 -1
  65. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  66. package/lib/module/components/styled/MediaFilePreview.js +97 -6
  67. package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
  68. package/lib/module/components/styled/MessageImage.js +1 -2
  69. package/lib/module/components/styled/MessageImage.js.map +1 -1
  70. package/lib/module/components/styled/SendInput.js +4 -2
  71. package/lib/module/components/styled/SendInput.js.map +1 -1
  72. package/lib/module/components/styled/VideoMessage.js +2 -10
  73. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  74. package/lib/module/context/xmppProvider.js +73 -3
  75. package/lib/module/context/xmppProvider.js.map +1 -1
  76. package/lib/module/helpers/mimeToExtension.js +124 -0
  77. package/lib/module/helpers/mimeToExtension.js.map +1 -0
  78. package/lib/module/helpers/resolveInitBeforeLoadUser.js +71 -0
  79. package/lib/module/helpers/resolveInitBeforeLoadUser.js.map +1 -1
  80. package/lib/module/hooks/useChatWrapperInit.js +22 -10
  81. package/lib/module/hooks/useChatWrapperInit.js.map +1 -1
  82. package/lib/module/hooks/useEventHandlers.js +9 -1
  83. package/lib/module/hooks/useEventHandlers.js.map +1 -1
  84. package/lib/module/hooks/useMessageHeapState.js +6 -2
  85. package/lib/module/hooks/useMessageHeapState.js.map +1 -1
  86. package/lib/module/hooks/useSendMessage.js +49 -5
  87. package/lib/module/hooks/useSendMessage.js.map +1 -1
  88. package/lib/module/networking/api-requests/auth.api.js +7 -1
  89. package/lib/module/networking/api-requests/auth.api.js.map +1 -1
  90. package/lib/module/networking/xmppClient.js +87 -1
  91. package/lib/module/networking/xmppClient.js.map +1 -1
  92. package/lib/module/roomStore/roomHeapSlice.js +24 -7
  93. package/lib/module/roomStore/roomHeapSlice.js.map +1 -1
  94. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  95. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  96. package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
  97. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  98. package/lib/typescript/components/MainComponents/MessageList.d.ts.map +1 -1
  99. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  100. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  101. package/lib/typescript/components/Modals/ModalWrapper/ModalWrapper.d.ts +6 -0
  102. package/lib/typescript/components/Modals/ModalWrapper/ModalWrapper.d.ts.map +1 -1
  103. package/lib/typescript/components/styled/MediaFilePreview.d.ts.map +1 -1
  104. package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
  105. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  106. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  107. package/lib/typescript/helpers/mimeToExtension.d.ts +37 -0
  108. package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -0
  109. package/lib/typescript/helpers/resolveInitBeforeLoadUser.d.ts +18 -0
  110. package/lib/typescript/helpers/resolveInitBeforeLoadUser.d.ts.map +1 -1
  111. package/lib/typescript/hooks/useChatWrapperInit.d.ts.map +1 -1
  112. package/lib/typescript/hooks/useEventHandlers.d.ts +6 -0
  113. package/lib/typescript/hooks/useEventHandlers.d.ts.map +1 -1
  114. package/lib/typescript/hooks/useMessageHeapState.d.ts +2 -0
  115. package/lib/typescript/hooks/useMessageHeapState.d.ts.map +1 -1
  116. package/lib/typescript/hooks/useSendMessage.d.ts +2 -1
  117. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  118. package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
  119. package/lib/typescript/networking/xmppClient.d.ts +25 -1
  120. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  121. package/lib/typescript/roomStore/roomHeapSlice.d.ts +22 -1
  122. package/lib/typescript/roomStore/roomHeapSlice.d.ts.map +1 -1
  123. package/lib/typescript/types/types.d.ts +6 -0
  124. package/lib/typescript/types/types.d.ts.map +1 -1
  125. package/package.json +1 -1
  126. package/src/components/MainComponents/ChatHeader.tsx +5 -1
  127. package/src/components/MainComponents/ChatRoom.tsx +6 -1
  128. package/src/components/MainComponents/ChatWrapper.tsx +1 -0
  129. package/src/components/MainComponents/MediaMessage.tsx +15 -8
  130. package/src/components/MainComponents/MessageList.tsx +10 -3
  131. package/src/components/MessageBubble/Message.tsx +64 -10
  132. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +30 -12
  133. package/src/components/Modals/ModalWrapper/ModalWrapper.tsx +22 -1
  134. package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +1 -1
  135. package/src/components/styled/MediaFilePreview.tsx +91 -5
  136. package/src/components/styled/MessageImage.tsx +1 -2
  137. package/src/components/styled/SendInput.tsx +3 -0
  138. package/src/components/styled/VideoMessage.tsx +3 -10
  139. package/src/context/xmppProvider.tsx +90 -2
  140. package/src/helpers/mimeToExtension.ts +135 -0
  141. package/src/helpers/resolveInitBeforeLoadUser.ts +79 -0
  142. package/src/hooks/useChatWrapperInit.ts +51 -46
  143. package/src/hooks/useEventHandlers.tsx +24 -1
  144. package/src/hooks/useMessageHeapState.tsx +6 -2
  145. package/src/hooks/useSendMessage.tsx +75 -4
  146. package/src/networking/api-requests/auth.api.ts +5 -1
  147. package/src/networking/xmppClient.ts +95 -1
  148. package/src/roomStore/roomHeapSlice.ts +47 -4
  149. package/src/types/types.ts +10 -0
@@ -26,6 +26,25 @@ import { pushLog as devPushLog } from '../utils/devLogger';
26
26
  // Canonical production XMPP WSS endpoint (standard wss/443, no port suffix).
27
27
  const DEFAULT_DEV_SERVER = 'xmpp.chat.ethora.com';
28
28
 
29
+ // @xmpp/client surfaces SASL/stream errors in a few shapes depending on
30
+ // where it fails — XMPPError on stream:error, SASLError on SASL bind, or
31
+ // a generic Error whose message contains "not-authorized". Match all of
32
+ // them so we trigger the credentials refresh path consistently.
33
+ function isNotAuthorizedError(err: any): boolean {
34
+ if (!err) {return false;}
35
+ const condition =
36
+ err?.condition ||
37
+ err?.name ||
38
+ err?.type ||
39
+ err?.error?.condition ||
40
+ '';
41
+ if (typeof condition === 'string' && condition.includes('not-authorized')) {
42
+ return true;
43
+ }
44
+ const message = (err?.message || err?.text || '').toString();
45
+ return /not-authorized|SASLError/i.test(message);
46
+ }
47
+
29
48
  type HistorySource = 'active' | 'send_ack' | 'background' | 'default';
30
49
 
31
50
  interface HistoryOptions {
@@ -40,6 +59,11 @@ interface MamInFlightEntry {
40
59
  startedAt: number;
41
60
  }
42
61
 
62
+ export type XmppCredentialsProvider = () => Promise<{
63
+ username: string;
64
+ password: string;
65
+ }>;
66
+
43
67
  export class XmppClient {
44
68
  client!: Client;
45
69
  xmppSettings?: xmppSettingsInterface;
@@ -56,6 +80,13 @@ export class XmppClient {
56
80
  reconnectDelay = 2000;
57
81
  suppressReconnect = false;
58
82
 
83
+ // Set when SASL fails with `not-authorized` (typically a stale JWT-
84
+ // derived XMPP password after idle). Triggers the credentialsProvider
85
+ // refresh path in reconnect().
86
+ lastAuthError: 'not-authorized' | null = null;
87
+ private credentialsProvider: XmppCredentialsProvider | null = null;
88
+ private credentialsRefreshInFlight: Promise<void> | null = null;
89
+
59
90
  // ---- QoS state (mirrors web XmppClient) ----------------------------
60
91
  presencesReady = false;
61
92
  disableLastRead = false;
@@ -74,6 +105,23 @@ export class XmppClient {
74
105
  return this.client && this.client.status === 'online';
75
106
  }
76
107
 
108
+ /**
109
+ * Inject (or replace) the callback used by reconnect() to fetch fresh
110
+ * XMPP credentials after a `not-authorized` SASL failure. The provider
111
+ * is responsible for refreshing whatever upstream token is needed
112
+ * (REST JWT, /users/client, etc.) and returning the resulting
113
+ * username + password.
114
+ */
115
+ setCredentialsProvider(provider: XmppCredentialsProvider | null) {
116
+ this.credentialsProvider = provider;
117
+ }
118
+
119
+ /** Swap in fresh credentials before the next reconnect. */
120
+ updateCredentials(username: string, password: string) {
121
+ this.username = username;
122
+ this.password = password;
123
+ }
124
+
77
125
  constructor(
78
126
  username: string,
79
127
  password: string,
@@ -285,6 +333,9 @@ export class XmppClient {
285
333
  this.attachEventListeners();
286
334
  this.client.start().catch((error: any) => {
287
335
  console.error('Error starting xmpp client:', error);
336
+ if (isNotAuthorizedError(error)) {
337
+ this.lastAuthError = 'not-authorized';
338
+ }
288
339
  this.status = 'error';
289
340
  });
290
341
  } catch (error) {
@@ -320,6 +371,9 @@ export class XmppClient {
320
371
 
321
372
  this.client.on('error', (error: any) => {
322
373
  console.error('XMPP client error:', error);
374
+ if (isNotAuthorizedError(error)) {
375
+ this.lastAuthError = 'not-authorized';
376
+ }
323
377
  try {
324
378
  devPushLog(
325
379
  'xmpp',
@@ -385,9 +439,25 @@ export class XmppClient {
385
439
  setTimeout(() => this.reconnect(), delay);
386
440
  }
387
441
 
388
- reconnect() {
442
+ async reconnect() {
389
443
  if (this.suppressReconnect) {return;}
390
444
  console.log('Attempting to reconnect xmpp client...');
445
+
446
+ // If the last failure was a SASL `not-authorized` (typically a stale
447
+ // JWT-derived XMPP password after idle), try to fetch fresh creds
448
+ // before retrying. Without this, reconnect retries with the same
449
+ // stale password forever and the user has to kill the app.
450
+ if (this.lastAuthError === 'not-authorized' && this.credentialsProvider) {
451
+ try {
452
+ await this.refreshCredentialsOnce();
453
+ } catch (err) {
454
+ console.warn(
455
+ 'XMPP credential refresh failed; reconnecting with cached creds',
456
+ err
457
+ );
458
+ }
459
+ }
460
+
391
461
  if (this.client) {
392
462
  this.client.stop().finally(() => {
393
463
  this.initializeClient();
@@ -397,6 +467,30 @@ export class XmppClient {
397
467
  }
398
468
  }
399
469
 
470
+ /**
471
+ * Single-flight wrapper around credentialsProvider. Multiple concurrent
472
+ * reconnect() calls (e.g. from the provider's reconnect effect firing
473
+ * twice quickly) share one in-flight request and update `this.password`
474
+ * exactly once.
475
+ */
476
+ private refreshCredentialsOnce(): Promise<void> {
477
+ if (this.credentialsRefreshInFlight) {return this.credentialsRefreshInFlight;}
478
+ if (!this.credentialsProvider) {return Promise.resolve();}
479
+ const provider = this.credentialsProvider;
480
+ this.credentialsRefreshInFlight = (async () => {
481
+ try {
482
+ const fresh = await provider();
483
+ if (fresh?.username && fresh?.password) {
484
+ this.updateCredentials(fresh.username, fresh.password);
485
+ this.lastAuthError = null;
486
+ }
487
+ } finally {
488
+ this.credentialsRefreshInFlight = null;
489
+ }
490
+ })();
491
+ return this.credentialsRefreshInFlight;
492
+ }
493
+
400
494
  async disconnect(options?: { suppressReconnect?: boolean }): Promise<void> {
401
495
  if (options?.suppressReconnect) {this.suppressReconnect = true;}
402
496
  return this.close();
@@ -2,18 +2,43 @@ import { createSlice, PayloadAction, type Slice } from '@reduxjs/toolkit';
2
2
  import type { WritableDraft } from 'immer';
3
3
  import { IMessage } from '../types/types';
4
4
 
5
+ // Saved alongside a failed-message id so the retry button can resend
6
+ // without the bubble needing to remember the original draft itself.
7
+ // `kind === 'text'` carries the plain body; `kind === 'media'` carries
8
+ // the upload payload so we don't have to make the user re-pick the file.
9
+ export type FailedMessagePayload =
10
+ | {
11
+ kind: 'text';
12
+ id: string;
13
+ roomJID: string;
14
+ body: string;
15
+ isReply?: boolean;
16
+ isChecked?: boolean;
17
+ mainMessage?: string;
18
+ }
19
+ | {
20
+ kind: 'media';
21
+ id: string;
22
+ roomJID: string;
23
+ data: any;
24
+ type: string;
25
+ isReply?: boolean;
26
+ isChecked?: boolean;
27
+ mainMessage?: string;
28
+ };
29
+
5
30
  export interface roomHeapSliceState {
6
31
  messageHeap: IMessage[];
32
+ // Keyed by message id so the bubble's `isFailed = failedMessages[id]`
33
+ // check is O(1). Carries enough payload data to actually retry.
34
+ failedMessages: Record<string, FailedMessagePayload>;
7
35
  }
8
36
 
9
37
  const initialState: roomHeapSliceState = {
10
38
  messageHeap: [],
39
+ failedMessages: {},
11
40
  };
12
41
 
13
- // Reducers extracted as a named const so we can give the slice an
14
- // explicit Slice<State, typeof reducers, Name> type. Without that, tsc's
15
- // declaration emission inlines immer's internal WritableNonArrayDraft
16
- // type and fails with TS4023.
17
42
  const reducers = {
18
43
  addMessageToHeap: (state: WritableDraft<roomHeapSliceState>, action: PayloadAction<IMessage>) => {
19
44
  state.messageHeap.push(action.payload);
@@ -26,6 +51,22 @@ const reducers = {
26
51
  },
27
52
  clearHeap: (state: WritableDraft<roomHeapSliceState>) => {
28
53
  state.messageHeap = [];
54
+ state.failedMessages = {};
55
+ },
56
+ markMessageFailed: (
57
+ state: WritableDraft<roomHeapSliceState>,
58
+ action: PayloadAction<FailedMessagePayload>
59
+ ) => {
60
+ state.failedMessages[action.payload.id] = action.payload;
61
+ // Drop from the pending heap — the bubble looks at both, and
62
+ // showing "sending..." alongside a failure indicator is confusing.
63
+ const index = state.messageHeap.findIndex((m) => m.id === action.payload.id);
64
+ if (index !== -1) {
65
+ state.messageHeap.splice(index, 1);
66
+ }
67
+ },
68
+ clearMessageFailure: (state: WritableDraft<roomHeapSliceState>, action: PayloadAction<string>) => {
69
+ delete state.failedMessages[action.payload];
29
70
  },
30
71
  };
31
72
 
@@ -39,6 +80,8 @@ export const {
39
80
  addMessageToHeap,
40
81
  clearHeap,
41
82
  removeMessageFromHeapById,
83
+ markMessageFailed,
84
+ clearMessageFailure,
42
85
  } = roomHeapSlice.actions;
43
86
 
44
87
  export default roomHeapSlice.reducer;
@@ -249,6 +249,11 @@ export interface IConfig {
249
249
  disableHeader?: boolean;
250
250
  disableMedia?: boolean;
251
251
  chatHeaderBurgerMenu?: boolean;
252
+ // Hide the burger icon entirely (the icon that opens the chatHeader-
253
+ // BurgerMenu dropdown). `chatHeaderBurgerMenu` controls dropdown
254
+ // visibility only — set this flag when you want neither the icon nor
255
+ // the dropdown to render (e.g. patient-facing apps with a single room).
256
+ disableChatHeaderBurgerMenuIcon?: boolean;
252
257
  chatHeaderAdditional?: { enabled: boolean; element: any };
253
258
  headerMenu?: () => void;
254
259
  headerChatMenu?: () => void;
@@ -420,6 +425,11 @@ export interface IConfig {
420
425
  roomJID: string;
421
426
  user: any;
422
427
  }) => void;
428
+ onMessageRetry?: (event: {
429
+ messageId: string;
430
+ roomJID: string;
431
+ messageType: 'text' | 'media';
432
+ }) => void;
423
433
  };
424
434
  }
425
435