@ethora/chat-component-rn 26.5.10 → 26.5.12

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 (209) hide show
  1. package/CHANGELOG.md +65 -1
  2. package/README.md +30 -1
  3. package/lib/commonjs/assets/icons.js +7 -1
  4. package/lib/commonjs/assets/icons.js.map +1 -1
  5. package/lib/commonjs/components/MainComponents/ChatHeader.js +31 -16
  6. package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
  7. package/lib/commonjs/components/MainComponents/ChatRoom.js +34 -4
  8. package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
  9. package/lib/commonjs/components/MainComponents/ChatWrapper.js +17 -14
  10. package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
  11. package/lib/commonjs/components/MainComponents/MediaMessage.js +298 -12
  12. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  13. package/lib/commonjs/components/MessageBubble/InteractionsOverlay.js +119 -0
  14. package/lib/commonjs/components/MessageBubble/InteractionsOverlay.js.map +1 -0
  15. package/lib/commonjs/components/MessageBubble/Message.js +39 -3
  16. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  17. package/lib/commonjs/components/MessageBubble/MessageInteractions.js +87 -39
  18. package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
  19. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js +153 -62
  20. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  21. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +20 -5
  22. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  23. package/lib/commonjs/components/Modals/FilePreviewModal/DocumentViewer.js +5 -44
  24. package/lib/commonjs/components/Modals/FilePreviewModal/DocumentViewer.js.map +1 -1
  25. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +40 -3
  26. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  27. package/lib/commonjs/components/Modals/FilePreviewModal/GViewWebView.js +149 -0
  28. package/lib/commonjs/components/Modals/FilePreviewModal/GViewWebView.js.map +1 -0
  29. package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js +4 -17
  30. package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
  31. package/lib/commonjs/components/Modals/ModalHeaderComponent.js +10 -0
  32. package/lib/commonjs/components/Modals/ModalHeaderComponent.js.map +1 -1
  33. package/lib/commonjs/components/Modals/styledModalComponents.js +13 -4
  34. package/lib/commonjs/components/Modals/styledModalComponents.js.map +1 -1
  35. package/lib/commonjs/components/RoomComponents/styled/LastAudioMessage.js +19 -33
  36. package/lib/commonjs/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
  37. package/lib/commonjs/components/styled/AudioMessage.js +643 -66
  38. package/lib/commonjs/components/styled/AudioMessage.js.map +1 -1
  39. package/lib/commonjs/components/styled/Button.js +9 -3
  40. package/lib/commonjs/components/styled/Button.js.map +1 -1
  41. package/lib/commonjs/components/styled/SendInput.js +239 -40
  42. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  43. package/lib/commonjs/components/styled/StyledComponents.js +6 -3
  44. package/lib/commonjs/components/styled/StyledComponents.js.map +1 -1
  45. package/lib/commonjs/components/styled/StyledInputComponents/MediaComponents.js +31 -18
  46. package/lib/commonjs/components/styled/StyledInputComponents/MediaComponents.js.map +1 -1
  47. package/lib/commonjs/components/styled/UnsupportedType.js +47 -5
  48. package/lib/commonjs/components/styled/UnsupportedType.js.map +1 -1
  49. package/lib/commonjs/components/styled/VideoMessage.js +44 -25
  50. package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
  51. package/lib/commonjs/context/xmppProvider.js +5 -2
  52. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  53. package/lib/commonjs/helpers/keyboardLayout.js +5 -3
  54. package/lib/commonjs/helpers/keyboardLayout.js.map +1 -1
  55. package/lib/commonjs/helpers/mimeToExtension.js +123 -0
  56. package/lib/commonjs/helpers/mimeToExtension.js.map +1 -1
  57. package/lib/commonjs/hooks/useEventHandlers.js +1 -1
  58. package/lib/commonjs/hooks/useEventHandlers.js.map +1 -1
  59. package/lib/commonjs/hooks/useLogout.js +33 -8
  60. package/lib/commonjs/hooks/useLogout.js.map +1 -1
  61. package/lib/commonjs/hooks/useSendMessage.js +6 -3
  62. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  63. package/lib/commonjs/networking/api-requests/rooms.api.js +29 -4
  64. package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
  65. package/lib/commonjs/roomStore/roomsSlice.js +6 -0
  66. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  67. package/lib/module/assets/icons.js +7 -1
  68. package/lib/module/assets/icons.js.map +1 -1
  69. package/lib/module/components/MainComponents/ChatHeader.js +31 -16
  70. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  71. package/lib/module/components/MainComponents/ChatRoom.js +36 -6
  72. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  73. package/lib/module/components/MainComponents/ChatWrapper.js +17 -14
  74. package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
  75. package/lib/module/components/MainComponents/MediaMessage.js +301 -15
  76. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  77. package/lib/module/components/MessageBubble/InteractionsOverlay.js +112 -0
  78. package/lib/module/components/MessageBubble/InteractionsOverlay.js.map +1 -0
  79. package/lib/module/components/MessageBubble/Message.js +40 -4
  80. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  81. package/lib/module/components/MessageBubble/MessageInteractions.js +89 -41
  82. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  83. package/lib/module/components/Modals/AttachSheet/AttachSheet.js +155 -64
  84. package/lib/module/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  85. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +20 -5
  86. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  87. package/lib/module/components/Modals/FilePreviewModal/DocumentViewer.js +5 -45
  88. package/lib/module/components/Modals/FilePreviewModal/DocumentViewer.js.map +1 -1
  89. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +41 -4
  90. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  91. package/lib/module/components/Modals/FilePreviewModal/GViewWebView.js +144 -0
  92. package/lib/module/components/Modals/FilePreviewModal/GViewWebView.js.map +1 -0
  93. package/lib/module/components/Modals/FilePreviewModal/PdfView.js +3 -17
  94. package/lib/module/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
  95. package/lib/module/components/Modals/ModalHeaderComponent.js +10 -0
  96. package/lib/module/components/Modals/ModalHeaderComponent.js.map +1 -1
  97. package/lib/module/components/Modals/styledModalComponents.js +13 -4
  98. package/lib/module/components/Modals/styledModalComponents.js.map +1 -1
  99. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js +20 -34
  100. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
  101. package/lib/module/components/styled/AudioMessage.js +644 -67
  102. package/lib/module/components/styled/AudioMessage.js.map +1 -1
  103. package/lib/module/components/styled/Button.js +9 -3
  104. package/lib/module/components/styled/Button.js.map +1 -1
  105. package/lib/module/components/styled/SendInput.js +240 -42
  106. package/lib/module/components/styled/SendInput.js.map +1 -1
  107. package/lib/module/components/styled/StyledComponents.js +6 -3
  108. package/lib/module/components/styled/StyledComponents.js.map +1 -1
  109. package/lib/module/components/styled/StyledInputComponents/MediaComponents.js +31 -18
  110. package/lib/module/components/styled/StyledInputComponents/MediaComponents.js.map +1 -1
  111. package/lib/module/components/styled/UnsupportedType.js +47 -5
  112. package/lib/module/components/styled/UnsupportedType.js.map +1 -1
  113. package/lib/module/components/styled/VideoMessage.js +45 -26
  114. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  115. package/lib/module/context/xmppProvider.js +5 -2
  116. package/lib/module/context/xmppProvider.js.map +1 -1
  117. package/lib/module/helpers/keyboardLayout.js +4 -2
  118. package/lib/module/helpers/keyboardLayout.js.map +1 -1
  119. package/lib/module/helpers/mimeToExtension.js +119 -0
  120. package/lib/module/helpers/mimeToExtension.js.map +1 -1
  121. package/lib/module/hooks/useEventHandlers.js +1 -1
  122. package/lib/module/hooks/useEventHandlers.js.map +1 -1
  123. package/lib/module/hooks/useLogout.js +33 -8
  124. package/lib/module/hooks/useLogout.js.map +1 -1
  125. package/lib/module/hooks/useSendMessage.js +6 -3
  126. package/lib/module/hooks/useSendMessage.js.map +1 -1
  127. package/lib/module/networking/api-requests/rooms.api.js +29 -4
  128. package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
  129. package/lib/module/roomStore/roomsSlice.js +6 -0
  130. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  131. package/lib/typescript/assets/icons.d.ts.map +1 -1
  132. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  133. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  134. package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
  135. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  136. package/lib/typescript/components/MessageBubble/InteractionsOverlay.d.ts +13 -0
  137. package/lib/typescript/components/MessageBubble/InteractionsOverlay.d.ts.map +1 -0
  138. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  139. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  140. package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts.map +1 -1
  141. package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
  142. package/lib/typescript/components/Modals/FilePreviewModal/DocumentViewer.d.ts +0 -7
  143. package/lib/typescript/components/Modals/FilePreviewModal/DocumentViewer.d.ts.map +1 -1
  144. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  145. package/lib/typescript/components/Modals/FilePreviewModal/GViewWebView.d.ts +8 -0
  146. package/lib/typescript/components/Modals/FilePreviewModal/GViewWebView.d.ts.map +1 -0
  147. package/lib/typescript/components/Modals/FilePreviewModal/PdfView.d.ts.map +1 -1
  148. package/lib/typescript/components/Modals/ModalHeaderComponent.d.ts.map +1 -1
  149. package/lib/typescript/components/Modals/styledModalComponents.d.ts.map +1 -1
  150. package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts.map +1 -1
  151. package/lib/typescript/components/styled/AudioMessage.d.ts +8 -2
  152. package/lib/typescript/components/styled/AudioMessage.d.ts.map +1 -1
  153. package/lib/typescript/components/styled/Button.d.ts.map +1 -1
  154. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  155. package/lib/typescript/components/styled/StyledComponents.d.ts.map +1 -1
  156. package/lib/typescript/components/styled/StyledInputComponents/MediaComponents.d.ts +5 -1
  157. package/lib/typescript/components/styled/StyledInputComponents/MediaComponents.d.ts.map +1 -1
  158. package/lib/typescript/components/styled/UnsupportedType.d.ts +5 -0
  159. package/lib/typescript/components/styled/UnsupportedType.d.ts.map +1 -1
  160. package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
  161. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  162. package/lib/typescript/helpers/keyboardLayout.d.ts +3 -1
  163. package/lib/typescript/helpers/keyboardLayout.d.ts.map +1 -1
  164. package/lib/typescript/helpers/mimeToExtension.d.ts +38 -0
  165. package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -1
  166. package/lib/typescript/hooks/useLogout.d.ts +23 -1
  167. package/lib/typescript/hooks/useLogout.d.ts.map +1 -1
  168. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  169. package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
  170. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  171. package/lib/typescript/types/models/modal.model.d.ts +3 -0
  172. package/lib/typescript/types/models/modal.model.d.ts.map +1 -1
  173. package/lib/typescript/types/types.d.ts +29 -0
  174. package/lib/typescript/types/types.d.ts.map +1 -1
  175. package/package.json +11 -4
  176. package/src/assets/icons.tsx +30 -25
  177. package/src/components/MainComponents/ChatHeader.tsx +46 -15
  178. package/src/components/MainComponents/ChatRoom.tsx +38 -3
  179. package/src/components/MainComponents/ChatWrapper.tsx +28 -21
  180. package/src/components/MainComponents/MediaMessage.tsx +304 -11
  181. package/src/components/MessageBubble/InteractionsOverlay.tsx +121 -0
  182. package/src/components/MessageBubble/Message.tsx +44 -3
  183. package/src/components/MessageBubble/MessageInteractions.tsx +103 -52
  184. package/src/components/Modals/AttachSheet/AttachSheet.tsx +144 -51
  185. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +25 -3
  186. package/src/components/Modals/FilePreviewModal/DocumentViewer.tsx +3 -40
  187. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +48 -4
  188. package/src/components/Modals/FilePreviewModal/GViewWebView.tsx +142 -0
  189. package/src/components/Modals/FilePreviewModal/PdfView.tsx +2 -18
  190. package/src/components/Modals/ModalHeaderComponent.tsx +8 -1
  191. package/src/components/Modals/styledModalComponents.tsx +13 -4
  192. package/src/components/RoomComponents/styled/LastAudioMessage.tsx +20 -35
  193. package/src/components/styled/AudioMessage.tsx +724 -59
  194. package/src/components/styled/Button.tsx +20 -11
  195. package/src/components/styled/SendInput.tsx +255 -46
  196. package/src/components/styled/StyledComponents.ts +6 -3
  197. package/src/components/styled/StyledInputComponents/MediaComponents.tsx +29 -18
  198. package/src/components/styled/UnsupportedType.tsx +60 -6
  199. package/src/components/styled/VideoMessage.tsx +21 -5
  200. package/src/context/xmppProvider.tsx +6 -2
  201. package/src/helpers/keyboardLayout.ts +5 -1
  202. package/src/helpers/mimeToExtension.ts +178 -0
  203. package/src/hooks/useEventHandlers.tsx +1 -1
  204. package/src/hooks/useLogout.tsx +33 -10
  205. package/src/hooks/useSendMessage.tsx +12 -6
  206. package/src/networking/api-requests/rooms.api.ts +31 -7
  207. package/src/roomStore/roomsSlice.ts +7 -0
  208. package/src/types/models/modal.model.ts +3 -0
  209. package/src/types/types.ts +29 -0
package/CHANGELOG.md CHANGED
@@ -3,12 +3,53 @@
3
3
  All notable changes to `@ethora/chat-component-rn` are listed here.
4
4
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project doesn't follow strict semver yet — version corresponds to the `package.json` field.
5
5
 
6
+ ## [26.5.11]
7
+
8
+ Single-room and host-app hardening on top of 26.5.10: unread state no longer overloads `lastViewedTimestamp`, room JIDs are normalized before XMPP join paths, iOS keyboard spacing is normalized across devices, tracked default credentials are removed from source, and tenant-specific docs/testbed defaults are scrubbed. Verified with targeted Jest regression suites plus `npm run build`.
9
+
10
+ ### Changed
11
+
12
+ #### Unread / single-room lifecycle
13
+
14
+ - **Unread state split into persisted vs ephemeral visibility.** The store now tracks room visibility separately (`visibleRoomJID`) instead of overloading `lastViewedTimestamp = 0` as an "active room" sentinel. This removes the main source of tab-mounted single-room unread regressions and makes cold-start / blur / background semantics consistent.
15
+ - **Canonical private-store flush path.** Blur, unmount, background, and logout paths now converge on the same unread timestamp flush flow instead of mixing local state updates with special-case stanza writes.
16
+ - **Delimiter logic no longer depends on sentinel `0`.** "New messages" UI now derives from real timestamps / visibility instead of a magic unread value, which keeps divider behavior stable in mounted-tab hosts.
17
+
18
+ #### Single-room roomJID normalization
19
+
20
+ - **Bare room ids are normalized to full MUC JIDs** before single-room join/info/member/archive calls. This aligns the join path with the existing history path and prevents reconnect/join failures when a host passes only the room local-part.
21
+
22
+ #### iOS keyboard layout
23
+
24
+ - **Keyboard spacing normalized across iOS devices.** Chat input safe-area compensation is now centralized so devices with and without a home indicator keep the same visual dock behavior when the keyboard opens.
25
+ - **`SafeAreaProvider` wrapped by the RN testbed shell.** The shared wrapper now guarantees `useSafeAreaInsets()` has a provider in the local app path.
26
+
27
+ #### Credentials / docs hygiene
28
+
29
+ - **Tracked default credentials removed.** `AppLoginChatsRn` now ships with blank setup defaults; the JWT seed helper reads from an ignored local JSON file instead of secrets in tracked source.
30
+ - **Tenant-specific docs and QA notes replaced with generic runbooks.** Example profile names, QA hostnames, and customer-specific notes were scrubbed from README / scripts / docs in favor of reusable environment-agnostic instructions.
31
+
32
+ ### Fixed
33
+
34
+ - **Tab-mounted single-room unread regressions.** Focus/blur/background behavior now works without importing internal store paths from consumer apps.
35
+ - **Cold-start unread marker clobbering.** Persisted last-viewed state is no longer vulnerable to being overwritten by the old active-room sentinel flow.
36
+ - **Single-room reconnect/join mismatch for bare room ids.** XMPP room operations now operate on normalized MUC JIDs consistently.
37
+
38
+ ### Internal
39
+
40
+ - **Repo-side pre-commit typecheck hook is versioned and wired.** `.githooks/pre-commit` runs `tsc --noEmit --moduleResolution bundler --module esnext -p tsconfig.json`, and `scripts/install-git-hooks.js` sets `git config core.hooksPath .githooks` from the `prepare` script. Use `ETHORA_SKIP_TYPECHECK=1` or `git commit --no-verify` only when intentionally bypassing it.
41
+
6
42
  ## [26.5.10]
7
43
 
8
- Lifecycle hardening on top of 26.5.9, driven by live on-device testing: a full reconnect-after-loss overhaul (a live test surfaced — and this release fixes — a reconnect storm), cache no longer wiped on re-entry, automatic AppState-driven unread visibility plus a new `isVisible` prop for tab hosts, `initBeforeLoad` auto-retry, and the "New messages" divider polish. Full regression sweep — typecheck clean, 583 jest tests green.
44
+ Lifecycle hardening on top of 26.5.9, driven by live on-device testing: a full reconnect-after-loss overhaul (a live test surfaced — and this release fixes — a reconnect storm), cache no longer wiped on re-entry, automatic AppState-driven unread visibility plus a new `isVisible` prop for tab hosts, `initBeforeLoad` auto-retry, and the "New messages" divider polish. Full regression sweep — typecheck clean, 584 jest tests green.
9
45
 
10
46
  ### Fixed
11
47
 
48
+ #### Packaging / TypeScript (regression from 26.5.5)
49
+
50
+ - **#8 — consumers' tsc no longer compiles our raw source.** `exports['.'].react-native` pointed at `./src/main.ts`, so React Native consumers' TypeScript (which resolves with `customConditions: ["react-native"]`) walked our entire `src/` tree and hit 51 errors about our internal devDeps (`@types/ltx`, `@types/uuid`, `@types/xmpp__client`, JSX namespace) — blocking pre-commit hooks on consumer side. The condition is now a nested entry that exposes the **compiled** artifacts: `react-native.types → ./lib/typescript/main.d.ts`, `react-native.default → ./lib/module/main.js`. No `src/*.ts` is reachable through any `exports` chain. (Verified by parsing the resolution graph and confirming zero `./src/` references in `exports`.)
51
+ - **Pre-commit typecheck hook (repo-side).** Added `.githooks/pre-commit` that runs `tsc --noEmit` with the project's `--moduleResolution bundler --module esnext` flags before every commit; a `scripts/install-git-hooks.js` wires `core.hooksPath` from the `prepare` script on `npm install`. Zero runtime deps. Bypass with `ETHORA_SKIP_TYPECHECK=1` or `git commit --no-verify` if you really need to. Catches future packaging-style regressions before they ship.
52
+
12
53
  #### Connectivity / reconnect (the big one)
13
54
 
14
55
  - **Never permanently gives up.** `scheduleReconnect` used to hard-stop at `maxReconnectAttempts` (5) — ~62s, after which only a NetInfo/foreground event could revive the client. It now retries indefinitely while mounted, with the exponential backoff **clamped to `maxReconnectDelay` (30s)**.
@@ -35,8 +76,31 @@ Lifecycle hardening on top of 26.5.9, driven by live on-device testing: a full r
35
76
  - **White text.** The label sits on a dark pill but rendered in the (often blue) primary color — unreadable. It's now white.
36
77
  - **Removed when you leave the chat** (tab switch / navigation / app background) and **spliced out rather than tombstoned**, so it correctly re-appears for the next batch of unread — previously it was a one-shot (the tombstone blocked every future divider).
37
78
 
79
+ #### Voice messages (new — opt-in, matches the web UX)
80
+
81
+ - **Tap-to-record voice messages from the input bar.** Behind a new opt-in `config.enableAudio: true` (default off — set `true` to enable). The right-side action button swaps between **mic** (when input is empty) and **send** (when there's text or attachments) — same single-button pattern as the web app. Tap the mic → recording overlay with a live timer + cancel + stop&send. Recording uses `expo-av`'s `Audio.Recording` (HIGH_QUALITY preset: AAC in an M4A container on both iOS and Android), and on stop ships through the same `sendMedia` pipeline with `mimetype: "audio/m4a"` and `filename: "voice-<timestamp>.m4a"`. Receivers route it through `MediaMessage`'s audio branch directly.
82
+ - **Incoming audio always plays** — independent of `enableAudio`. The 26.5.10 `isLikelyAudio` heuristic still catches octet-stream voicemails coming from older web senders.
83
+ - **iOS apps need `NSMicrophoneUsageDescription`** in `Info.plist`. The bundled `expo-av` plugin block in `app.json` adds it on `expo prebuild`; standalone apps should configure it the same way.
84
+
85
+ #### Customer-reported bugs (this round)
86
+
87
+ - **#19 — `useUnread()` always 0 in tab-mounted hosts.** `ChatRoom`'s visibility effect had `client` in its dep array, so any `client` identity change (reconnect, provider re-render) re-fired setup → `dispatch(setVisibleRoom(activeRoomJID))` ~one tick after the host cleared visibility via the `isVisible` prop on `<Chat>`, clobbering it. Effect now splits setup (deps without `client`) from cleanup (reads client through a ref). `<Chat isVisible={...} />` now works as documented.
88
+ - **#15 — `disableChatHeaderBurgerMenuIcon` clipped the chat avatar/name.** When the burger was hidden the header still reserved an empty `leftContainer` at `width: 15%`, eating space and clipping the title. The placeholder is no longer rendered — `CenterContainer` expands to fill, the chat-name row now sits flush with the left edge as intended.
89
+ - **#16 — new `disableChatInfo.disableMemberTap` flag.** `disableMemberProfileActions` only hides the *action block inside* the member-profile popup; the popup itself still opened on tap. The new flag blocks the tap entirely, so the popup never opens. Both flags can be combined for full lock-down.
90
+ - **#9 (video) — video preview tap unresponsive.** `VideoMessage` wrapped a `VideoView` in a `TouchableOpacity` with `pointerEvents="none"` on the inner view — on some `expo-video` versions / iOS the native VideoView still intercepted gestures, so tapping the poster did nothing. Switched to a capture-overlay pattern: VideoView at the bottom, transparent `Pressable` absolute-filled on top owns the tap → `setActiveModal(FILE_PREVIEW)` reliably fires. (The .bin voicemail case is a web-app issue — see migration note below.)
91
+ - **#23 — phantom "ethora.com" room stub.** `rooms.api` synthesized JIDs as `<name>@conference.xmpp.chat.ethora.com` when the REST item carried no JID and the consumer hadn't configured `xmppSettings.host` — surfacing a fake "ethora" room on third-party servers. Removed the hardcoded vendor host; the fallback now derives the host from `config.baseUrl` (`api.foo.com` → `xmpp.foo.com`), and if that's still ambiguous the room is skipped with a `warn` log rather than fabricated.
92
+
93
+ **Migration notes for consumers**
94
+
95
+ - *#9 voicemail .bin*: the testbed sees voicemails from the web app arrive with `mimetype: application/octet-stream` and no audio file extension, so the audio-extension sniff in `MediaMessage.tsx` doesn't match and the file falls through to `FileDownload` (unplayable). Fix on the **web sender side**: when uploading a voice message, set `mimetype: audio/mp3` (or `audio/m4a` / whatever the actual codec is) and ensure the filename ends in a recognised audio extension (`.mp3` / `.m4a` / `.wav` / `.aac` / `.ogg` / `.flac`). The RN side then plays it correctly without further changes.
96
+ - *#16 migration*: if you previously set `disableMemberProfileActions: true` expecting it to also suppress the tap, set `disableChatInfo.disableMemberTap: true` additionally.
97
+
38
98
  #### Misc
39
99
 
100
+ - **Modal header layout fixed.** `HeaderContainer` had `padding-top: 62px` (status-bar offset) clamped under `min-height/max-height: 24px`, so content overflowed the visible header band — the back arrow visibly hugged the bottom of the white header strip instead of being vertically centered. Dropped the height clamps and switched to symmetric vertical padding (`12px` + `56px` top inset). Fixes every modal that uses `ModalHeaderComponent` (chat-profile, user-profile, settings, file-preview, …).
101
+ - **New `disableChatInfo.disableIconEdit` flag.** Hides the press-to-pick / remove affordances on the chat icon in the chat-info modal regardless of the user's role, so consumers can make a branded / read-only room icon. The icon itself still renders.
102
+ - **Profile-vs-chat-info flag split (semantic fix).** `disableProfilesInteractions` is now correctly scoped to **user-profile** popups only — wired in `Message.tsx` for the in-bubble avatar tap (where it was missing) and removed from `ChatHeader`'s chat-title press (where it had the wrong semantic — that surface opens the *chat-info* modal). The header tap is now gated by the already-existing-but-previously-unwired `disableChatInfo.disableChatHeaderMenu`, so a consumer can disable user profiles without losing chat info — and vice versa. Migration note for anyone relying on the old behaviour: if you set `disableProfilesInteractions: true` to suppress the header tap, also set `disableChatInfo.disableChatHeaderMenu: true`.
103
+ - **`useLogout()` now returns an awaitable `() => Promise<void>`.** Previously the hook fired-and-forgot `logoutService.performLogout()`, so a host that navigated immediately after `logout()` could race the persistence middleware's 200 ms debounce and rehydrate stale state on the next mount ("old chats reappear"). The returned promise resolves after XMPP disconnect + persisted slices wiped + REST/AsyncStorage cleared; it never rejects. **Type-level change** (`() => void` → `() => Promise<void>`) — non-awaiting callers continue to work, but you can now `await logout()` before navigating.
40
104
  - **Benign unhandled-rejection red box gone.** The dev rejection tracker now reports only *genuinely* unhandled rejections (`allRejections: false`), and `createTimeoutPromise` self-observes its timeout so a settled `Promise.race` no longer leaks a `reject(undefined)`.
41
105
  - **Input bar spacing.** The send button's horizontal margin is removed so its gap from the input matches the attach button's.
42
106
 
package/README.md CHANGED
@@ -16,6 +16,7 @@ React Native chat UI + chat core for iOS and Android, powered by the Ethora plat
16
16
  - [Authentication modes](#authentication-modes)
17
17
  - [Pinning a single room](#pinning-a-single-room)
18
18
  - [Unread tracking in tab-based hosts](#unread-tracking-in-tab-based-hosts)
19
+ - [Logging out](#logging-out)
19
20
  - [Customization flags worth knowing](#customization-flags-worth-knowing)
20
21
  - [Quality & test coverage](#quality--test-coverage)
21
22
  - [Local development](#local-development)
@@ -248,12 +249,40 @@ function TabBar() {
248
249
 
249
250
  Full details (including the cold-start fix and the scroll-to-bottom unread chip) live in [`docs/unread-tracking.md`](docs/unread-tracking.md).
250
251
 
252
+ ## Logging out
253
+
254
+ `useLogout()` returns an **awaitable** `() => Promise<void>` that resolves only after the SDK has fully torn down: XMPP disconnect, redux reset, persisted slices wiped, AsyncStorage stray keys removed, REST cache cleared.
255
+
256
+ ```tsx
257
+ import { useLogout } from '@ethora/chat-component-rn';
258
+
259
+ function SignOutButton() {
260
+ const logout = useLogout();
261
+ return (
262
+ <Button
263
+ title="Sign out"
264
+ onPress={async () => {
265
+ await logout(); // ← awaits the full teardown
266
+ navigation.replace('SignIn'); // disk is provably clean here
267
+ }}
268
+ />
269
+ );
270
+ }
271
+ ```
272
+
273
+ Why awaitable: the persistence layer debounces writes by 200 ms, and the chat slice removes its persisted user fire-and-forget. If the host navigated / re-mounted `<Chat>` immediately after a non-awaited call, the next bootstrap could occasionally rehydrate stale state ("old chats reappear"). Awaiting the returned promise eliminates that race. The function never rejects — any internal failure is logged via `console.warn`, so a non-awaited call still won't crash the host. For non-React contexts you can call `logoutService.performLogout()` directly (same Promise).
274
+
251
275
  ## Customization flags worth knowing
252
276
 
253
277
  | Flag | What it does |
254
278
  | --- | --- |
279
+ | `disableProfilesInteractions` | Disables entry to the **user-profile popup** — the in-bubble avatar tap on other users' messages. The avatar is still rendered, just non-interactive. (Does **not** affect the chat-title press → use `disableChatInfo.disableChatHeaderMenu` for that.) |
280
+ | `disableChatInfo.disableChatHeaderMenu` | Disables the **chat-info modal entry point** in the header (tapping the chat title / icon). Use this when you want the header purely informational. The chat-info modal itself has further granular flags (`disableDescription`, `disableType`, `disableMembers`, `hideMembers`, `disableIconEdit`). |
281
+ | `disableChatInfo.disableIconEdit` | Makes the chat icon read-only in the chat-info modal — hides the press-to-pick picker AND the remove affordance regardless of the user's role. The icon still renders. |
255
282
  | `disableChatHeaderBurgerMenuIcon` | Hides the burger icon in the chat header (the icon that opens the room-list dropdown). `chatHeaderBurgerMenu` controls only the dropdown — set this when you want neither rendered. |
256
- | `disableMemberProfileActions` | Hides the whole "Message / Copy User Id" action block in the chat-info member modal. Use when no per-user actions are appropriate (e.g. patient-facing apps). |
283
+ | `enableAudio` | Opt-in voice messages. **Off by default.** When `true`, an idle input (no text, no attachments) shows a mic icon in the send-button slot — tap → start recording → stop & send. iOS apps need `NSMicrophoneUsageDescription` in Info.plist (add via `expo-av`'s plugin block in `app.json`). Receiving voice messages from other clients (incl. legacy web `.bin` voicemails) is **independent of this flag** — incoming audio plays regardless. |
284
+ | `disableMemberProfileActions` | Hides the whole "Message / Copy User Id" action block **inside** the chat-info member-profile popup. The popup itself still opens — to block the tap entirely, use `disableChatInfo.disableMemberTap`. |
285
+ | `disableChatInfo.disableMemberTap` | Disables the tap on a member row in the chat-info list — the user-profile popup never opens. Set when no per-member interaction is appropriate (e.g. patient-facing apps). |
257
286
  | `hideMemberSendMessageAction` | Hides only the "Message" button, keeps everything else. |
258
287
  | `hideMemberCopyIdAction` | Hides only the "Copy User Id" button, keeps everything else. |
259
288
  | `disableConnectionErrorOverlay` | Replaces the full-screen "Connection error" overlay with a small, non-blocking `ConnectionBanner`. Set this when a transient reconnect shouldn't take over the whole screen. |