@ethora/chat-component-rn 26.5.10 → 26.5.11

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 (111) hide show
  1. package/CHANGELOG.md +59 -1
  2. package/README.md +29 -1
  3. package/lib/commonjs/components/MainComponents/ChatHeader.js +31 -16
  4. package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
  5. package/lib/commonjs/components/MainComponents/ChatRoom.js +17 -3
  6. package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
  7. package/lib/commonjs/components/MainComponents/ChatWrapper.js +17 -14
  8. package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
  9. package/lib/commonjs/components/MainComponents/MediaMessage.js +12 -9
  10. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  11. package/lib/commonjs/components/MessageBubble/InteractionsOverlay.js +119 -0
  12. package/lib/commonjs/components/MessageBubble/InteractionsOverlay.js.map +1 -0
  13. package/lib/commonjs/components/MessageBubble/Message.js +39 -3
  14. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  15. package/lib/commonjs/components/MessageBubble/MessageInteractions.js +64 -36
  16. package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
  17. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js +153 -62
  18. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  19. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +20 -5
  20. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  21. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +5 -1
  22. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  23. package/lib/commonjs/components/Modals/ModalHeaderComponent.js +10 -0
  24. package/lib/commonjs/components/Modals/ModalHeaderComponent.js.map +1 -1
  25. package/lib/commonjs/components/Modals/styledModalComponents.js +13 -4
  26. package/lib/commonjs/components/Modals/styledModalComponents.js.map +1 -1
  27. package/lib/commonjs/components/styled/StyledComponents.js +6 -3
  28. package/lib/commonjs/components/styled/StyledComponents.js.map +1 -1
  29. package/lib/commonjs/components/styled/VideoMessage.js +44 -25
  30. package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
  31. package/lib/commonjs/helpers/mimeToExtension.js +56 -0
  32. package/lib/commonjs/helpers/mimeToExtension.js.map +1 -1
  33. package/lib/commonjs/hooks/useLogout.js +33 -8
  34. package/lib/commonjs/hooks/useLogout.js.map +1 -1
  35. package/lib/commonjs/networking/api-requests/rooms.api.js +29 -4
  36. package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
  37. package/lib/module/components/MainComponents/ChatHeader.js +31 -16
  38. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  39. package/lib/module/components/MainComponents/ChatRoom.js +18 -4
  40. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  41. package/lib/module/components/MainComponents/ChatWrapper.js +17 -14
  42. package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
  43. package/lib/module/components/MainComponents/MediaMessage.js +13 -10
  44. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  45. package/lib/module/components/MessageBubble/InteractionsOverlay.js +112 -0
  46. package/lib/module/components/MessageBubble/InteractionsOverlay.js.map +1 -0
  47. package/lib/module/components/MessageBubble/Message.js +40 -4
  48. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  49. package/lib/module/components/MessageBubble/MessageInteractions.js +66 -38
  50. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  51. package/lib/module/components/Modals/AttachSheet/AttachSheet.js +155 -64
  52. package/lib/module/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  53. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +20 -5
  54. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  55. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +6 -2
  56. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  57. package/lib/module/components/Modals/ModalHeaderComponent.js +10 -0
  58. package/lib/module/components/Modals/ModalHeaderComponent.js.map +1 -1
  59. package/lib/module/components/Modals/styledModalComponents.js +13 -4
  60. package/lib/module/components/Modals/styledModalComponents.js.map +1 -1
  61. package/lib/module/components/styled/StyledComponents.js +6 -3
  62. package/lib/module/components/styled/StyledComponents.js.map +1 -1
  63. package/lib/module/components/styled/VideoMessage.js +45 -26
  64. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  65. package/lib/module/helpers/mimeToExtension.js +55 -0
  66. package/lib/module/helpers/mimeToExtension.js.map +1 -1
  67. package/lib/module/hooks/useLogout.js +33 -8
  68. package/lib/module/hooks/useLogout.js.map +1 -1
  69. package/lib/module/networking/api-requests/rooms.api.js +29 -4
  70. package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
  71. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  72. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  73. package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
  74. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  75. package/lib/typescript/components/MessageBubble/InteractionsOverlay.d.ts +13 -0
  76. package/lib/typescript/components/MessageBubble/InteractionsOverlay.d.ts.map +1 -0
  77. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  78. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  79. package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts.map +1 -1
  80. package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
  81. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  82. package/lib/typescript/components/Modals/ModalHeaderComponent.d.ts.map +1 -1
  83. package/lib/typescript/components/Modals/styledModalComponents.d.ts.map +1 -1
  84. package/lib/typescript/components/styled/StyledComponents.d.ts.map +1 -1
  85. package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
  86. package/lib/typescript/helpers/mimeToExtension.d.ts +8 -0
  87. package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -1
  88. package/lib/typescript/hooks/useLogout.d.ts +23 -1
  89. package/lib/typescript/hooks/useLogout.d.ts.map +1 -1
  90. package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
  91. package/lib/typescript/types/types.d.ts +16 -0
  92. package/lib/typescript/types/types.d.ts.map +1 -1
  93. package/package.json +7 -4
  94. package/src/components/MainComponents/ChatHeader.tsx +46 -15
  95. package/src/components/MainComponents/ChatRoom.tsx +17 -3
  96. package/src/components/MainComponents/ChatWrapper.tsx +28 -21
  97. package/src/components/MainComponents/MediaMessage.tsx +11 -8
  98. package/src/components/MessageBubble/InteractionsOverlay.tsx +121 -0
  99. package/src/components/MessageBubble/Message.tsx +44 -3
  100. package/src/components/MessageBubble/MessageInteractions.tsx +67 -48
  101. package/src/components/Modals/AttachSheet/AttachSheet.tsx +144 -51
  102. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +25 -3
  103. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +10 -2
  104. package/src/components/Modals/ModalHeaderComponent.tsx +8 -1
  105. package/src/components/Modals/styledModalComponents.tsx +13 -4
  106. package/src/components/styled/StyledComponents.ts +6 -3
  107. package/src/components/styled/VideoMessage.tsx +21 -5
  108. package/src/helpers/mimeToExtension.ts +64 -0
  109. package/src/hooks/useLogout.tsx +33 -10
  110. package/src/networking/api-requests/rooms.api.ts +31 -7
  111. package/src/types/types.ts +16 -0
@@ -64,6 +64,70 @@ const MIME_TO_EXT: Record<string, string> = {
64
64
 
65
65
  const VALID_EXT_RE = /\.[a-z0-9]{1,8}$/i;
66
66
 
67
+ // Audio file extensions we recognise across the SDK. Used for the
68
+ // "looks like audio even when the mimetype is application/octet-stream"
69
+ // heuristic in MediaMessage / FilePreviewModal. Voice-message senders
70
+ // (web app, push voice memos) sometimes ship audio as octet-stream with
71
+ // no audio mime — sniffing the extension lets us still play it.
72
+ // `.bin` is intentionally NOT here: it's our own fallback extension for
73
+ // truly unknown payloads (see getExtensionForMime), not a real format.
74
+ const AUDIO_EXTENSIONS = [
75
+ '.mp3', '.m4a', '.wav', '.aac', '.ogg', '.flac',
76
+ '.weba', '.webm', '.opus', '.amr', '.3gp', '.3gpp',
77
+ ] as const;
78
+ const AUDIO_EXT_RE = new RegExp(
79
+ `(${AUDIO_EXTENSIONS.map((e) => e.replace('.', '\\.')).join('|')})$`,
80
+ 'i'
81
+ );
82
+ // Filename / URL substrings that strongly indicate a voice / audio
83
+ // payload even without an explicit audio extension — e.g. a web-app
84
+ // voicemail uploaded as application/octet-stream named "voicemail-…".
85
+ const AUDIO_NAME_HINT_RE =
86
+ /(?:^|[_\-./])(voice|voicemail|audio|audio-message|audiomessage|recording|voice-?note)(?:[_\-./]|$)/i;
87
+
88
+ /**
89
+ * Returns true when the (mime, fileName, url) triple looks like an
90
+ * audio payload — either by mime, by recognised extension on the
91
+ * filename or URL path, or by a voice-message naming hint. The hint
92
+ * branch is what catches voicemails sent as `application/octet-stream`
93
+ * with a generic `.bin` filename (customer-reported #9 voicemail).
94
+ */
95
+ export function isLikelyAudio(
96
+ mime: string | undefined | null,
97
+ fileName?: string | null,
98
+ url?: string | null
99
+ ): boolean {
100
+ const m = (mime || '').toLowerCase();
101
+ if (m.startsWith('audio/')) {return true;}
102
+ // For octet-stream (and any other non-audio mime) fall through to the
103
+ // filename / URL heuristics. We don't restrict to octet-stream — some
104
+ // backends send no mime at all, or send text/plain by accident.
105
+ const name = (fileName || '').toLowerCase();
106
+ const urlLast = filenameFromUrl(url).toLowerCase();
107
+ if (AUDIO_EXT_RE.test(name) || AUDIO_EXT_RE.test(urlLast)) {return true;}
108
+ // Last resort: voice-message naming hint anywhere in the filename, the
109
+ // URL's last segment, OR the URL's full path. Voicemails are commonly
110
+ // hosted under `/voice/abc-123` or `/voicemail/abc-123` where the last
111
+ // segment is an opaque id with no hint by itself.
112
+ const urlPath = (() => {
113
+ if (!url) {return '';}
114
+ try {
115
+ const noQuery = url.split('?')[0]!.split('#')[0]!;
116
+ return decodeURIComponent(noQuery).toLowerCase();
117
+ } catch {
118
+ return '';
119
+ }
120
+ })();
121
+ if (
122
+ AUDIO_NAME_HINT_RE.test(name) ||
123
+ AUDIO_NAME_HINT_RE.test(urlLast) ||
124
+ AUDIO_NAME_HINT_RE.test(urlPath)
125
+ ) {
126
+ return true;
127
+ }
128
+ return false;
129
+ }
130
+
67
131
  /**
68
132
  * Returns the conventional extension (including the leading dot) for a
69
133
  * MIME type, or `.bin` as a last-resort fallback so callers never end up
@@ -134,18 +134,41 @@ const logoutService = {
134
134
  },
135
135
  };
136
136
 
137
- export const useLogout = () => {
138
- const dispatch = useDispatch();
137
+ /**
138
+ * Hook that returns an awaitable logout function.
139
+ *
140
+ * ```tsx
141
+ * const logout = useLogout();
142
+ * await logout();
143
+ * navigation.replace('SignIn'); // disk is provably clean here
144
+ * ```
145
+ *
146
+ * The returned function resolves AFTER all teardown work has landed:
147
+ * XMPP disconnect, redux reset, persisted slices wiped, stray
148
+ * AsyncStorage keys removed, REST cache cleared. It never rejects —
149
+ * any internal failure is logged via console.warn so a fire-and-forget
150
+ * `logout()` call (no `await`) still won't crash the host.
151
+ *
152
+ * Why awaitable: the persistence middleware debounces writes by 200ms,
153
+ * and the chat-slice `logout` reducer removes ETHORA_USER fire-and-
154
+ * forget. If the host navigated / re-mounted `<Chat>` immediately
155
+ * after a non-awaited call, the next bootstrap could occasionally
156
+ * read stale persisted state ("old chats reappear"). Awaiting the
157
+ * returned promise eliminates that race entirely.
158
+ */
159
+ export const useLogout = (): (() => Promise<void>) => {
160
+ // dispatch is kept for backward-compat with consumers who treat the
161
+ // hook's presence as a redux-context requirement; not used directly
162
+ // here because the work happens through the module-level service.
163
+ useDispatch();
139
164
 
140
- const handleLogout = useCallback(() => {
141
- // The hook fires-and-forgets — consumers that need to await the
142
- // teardown should call `logoutService.performLogout()` directly.
143
- logoutService.performLogout().catch((err) => {
144
- console.warn('useLogout: performLogout rejected', err);
145
- });
165
+ return useCallback(async () => {
166
+ try {
167
+ await logoutService.performLogout();
168
+ } catch (err) {
169
+ console.warn('useLogout: performLogout failed', err);
170
+ }
146
171
  }, []);
147
-
148
- return handleLogout;
149
172
  };
150
173
 
151
174
  export { logoutService };
@@ -21,21 +21,45 @@ interface ApiRoomMember {
21
21
  * 1. explicit `room.jid` from the server (if present)
22
22
  * 2. `<name>@conference.<xmppHost>` where xmppHost comes from the
23
23
  * provider's `xmppSettings.host` saved to `chatSettingStore.config`
24
- * 3. fallback: `<name>@conference.xmpp.chat.ethora.com`
24
+ * 3. derive host from `config.baseUrl` (api.<host> → xmpp.<host>)
25
+ * as a last-resort heuristic for misconfigured deployments
26
+ * 4. otherwise: skip the room with a warn log (NEVER fall back to a
27
+ * hardcoded vendor host — previously `xmpp.chat.ethora.com`, which
28
+ * manifested as a phantom "ethora" room stub on third-party servers,
29
+ * customer-reported #23)
25
30
  */
26
31
  function dispatchRoomsFromRestItems(items: ApiRoom[]): void {
27
32
  if (!items?.length) return;
28
33
  const config = store.getState().chatSettingStore?.config as any;
29
- const host: string =
34
+ // Best: explicit xmpp settings.
35
+ let host: string | undefined =
30
36
  config?.xmppSettings?.host ||
31
- config?.xmppSettings?.conference?.replace(/^conference\./, '') ||
32
- 'xmpp.chat.ethora.com';
33
- const conference =
34
- config?.xmppSettings?.conference || `conference.${host}`;
37
+ config?.xmppSettings?.conference?.replace(/^conference\./, '');
38
+ // Heuristic fallback: derive xmpp host from the REST baseUrl.
39
+ // `https://api.foo.com/v1` → `xmpp.foo.com`. Better than a hardcoded
40
+ // vendor host and right in 99% of single-domain deployments.
41
+ if (!host && typeof config?.baseUrl === 'string') {
42
+ const m = /^https?:\/\/(?:api\.)?([^/]+)/i.exec(config.baseUrl);
43
+ if (m && m[1]) {host = `xmpp.${m[1].replace(/^api\./, '')}`;}
44
+ }
45
+ const conference = config?.xmppSettings?.conference
46
+ || (host ? `conference.${host}` : undefined);
35
47
 
36
48
  for (const item of items) {
37
49
  if (!item) continue;
38
- const jid = item.jid || `${item.name}@${conference}`;
50
+ let jid = item.jid;
51
+ if (!jid) {
52
+ if (!conference) {
53
+ // No server-supplied JID and no way to synthesize one safely —
54
+ // skip rather than create a phantom room on a wrong host.
55
+ // Consumers who hit this should set config.xmppSettings.host.
56
+ console.warn(
57
+ `[ethora-rn] rooms.api: skipping room "${item.name}" — no item.jid and no xmppSettings.host / baseUrl to derive host from`
58
+ );
59
+ continue;
60
+ }
61
+ jid = `${item.name}@${conference}`;
62
+ }
39
63
  if (!jid.includes('@')) continue;
40
64
  const room: IRoom = {
41
65
  id: item._id || jid,
@@ -338,6 +338,22 @@ export interface IConfig {
338
338
  disableMembers?: boolean;
339
339
  hideMembers?: boolean;
340
340
  disableChatHeaderMenu?: boolean;
341
+ /**
342
+ * Disables uploading / removing the chat icon from the chat-info
343
+ * modal. The avatar stays rendered (initials / current icon) but
344
+ * the press-to-pick / remove affordances are gone — so the room
345
+ * icon becomes read-only regardless of the user's role.
346
+ */
347
+ disableIconEdit?: boolean;
348
+ /**
349
+ * Disables the tap on a member row in the chat-info member list —
350
+ * the popup that would show user-profile + per-member actions
351
+ * (message / copy-id) never opens. `disableMemberProfileActions`
352
+ * only hid the action BLOCK inside that popup; the popup itself
353
+ * still opened on tap. Set this when no per-member interaction is
354
+ * appropriate (e.g. patient-facing apps). Customer-reported #16.
355
+ */
356
+ disableMemberTap?: boolean;
341
357
  };
342
358
  qrUrl?: string;
343
359