@ethora/chat-component-rn 26.4.1

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 (273) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +148 -0
  3. package/empty-shim.js +15 -0
  4. package/index.js +13 -0
  5. package/metro.js +72 -0
  6. package/package.json +99 -0
  7. package/src/api.config.ts +34 -0
  8. package/src/assets/BG.png +0 -0
  9. package/src/assets/BG.tsx +56 -0
  10. package/src/assets/File.svg +4 -0
  11. package/src/assets/ForzaCare.png +0 -0
  12. package/src/assets/ForzaCare.tsx +27 -0
  13. package/src/assets/icons.tsx +685 -0
  14. package/src/assets/images.tsx +41 -0
  15. package/src/assets/test.xml +243 -0
  16. package/src/components/AuthForms/Login.tsx +295 -0
  17. package/src/components/AuthForms/LoginAnonym.tsx +68 -0
  18. package/src/components/AuthForms/Register.tsx +8 -0
  19. package/src/components/ContextMenu/ContextMenuComponents.tsx +87 -0
  20. package/src/components/DropdownMenu/DropdownMenu.tsx +244 -0
  21. package/src/components/ExampleComponents/CustomMessage.tsx +92 -0
  22. package/src/components/ExampleComponents/CustomTypingIndicatorExamples.tsx +239 -0
  23. package/src/components/Header/HeaderRoomList.tsx +104 -0
  24. package/src/components/InputComponents/AudioInput.tsx +24 -0
  25. package/src/components/InputComponents/AudioRecorder.tsx +100 -0
  26. package/src/components/InputComponents/MediaInput.tsx +114 -0
  27. package/src/components/InputComponents/RecordingIndicator.tsx +77 -0
  28. package/src/components/InputComponents/Search.tsx +135 -0
  29. package/src/components/InputComponents/TextInput.tsx +66 -0
  30. package/src/components/MainComponents/ChatHeader.tsx +163 -0
  31. package/src/components/MainComponents/ChatRoom.tsx +392 -0
  32. package/src/components/MainComponents/ChatWrapper.tsx +318 -0
  33. package/src/components/MainComponents/ChooseChatMessage.tsx +44 -0
  34. package/src/components/MainComponents/ConnectionBanner.tsx +33 -0
  35. package/src/components/MainComponents/EditWrapper.tsx +72 -0
  36. package/src/components/MainComponents/LoginWrapper.tsx +134 -0
  37. package/src/components/MainComponents/MediaMessage.tsx +55 -0
  38. package/src/components/MainComponents/MessageContainer.tsx +122 -0
  39. package/src/components/MainComponents/MessageList.tsx +429 -0
  40. package/src/components/MainComponents/NoMessagesPlaceholder.tsx +49 -0
  41. package/src/components/MainComponents/ProfileImagePlaceholder.tsx +143 -0
  42. package/src/components/MainComponents/RNStyled.tsx +34 -0
  43. package/src/components/MainComponents/ReduxWrapper.tsx +40 -0
  44. package/src/components/MainComponents/RoomList.tsx +246 -0
  45. package/src/components/MainComponents/Select.tsx +165 -0
  46. package/src/components/MainComponents/Switch.tsx +73 -0
  47. package/src/components/MainComponents/SystemMessage.tsx +42 -0
  48. package/src/components/Menu/HeaderRoomListMenu.tsx +169 -0
  49. package/src/components/MenuRoom/MenuRoom.tsx +46 -0
  50. package/src/components/MessageBubble/Avatar.tsx +84 -0
  51. package/src/components/MessageBubble/BottomReplyContainer.tsx +117 -0
  52. package/src/components/MessageBubble/CustomDivider.tsx +10 -0
  53. package/src/components/MessageBubble/DeletedMessage.tsx +36 -0
  54. package/src/components/MessageBubble/Message.tsx +400 -0
  55. package/src/components/MessageBubble/MessageInteractions.tsx +247 -0
  56. package/src/components/MessageBubble/MessageReaction.tsx +149 -0
  57. package/src/components/MessageBubble/MessageReply.tsx +50 -0
  58. package/src/components/MessageBubble/MessageTranslations.tsx +30 -0
  59. package/src/components/Modals/AddMembersModal/AddMembersModal.tsx +142 -0
  60. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +471 -0
  61. package/src/components/Modals/ChatProfileModal/DeleteChatModal.tsx +91 -0
  62. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +298 -0
  63. package/src/components/Modals/Modal/Modal.tsx +52 -0
  64. package/src/components/Modals/ModalHeaderComponent.tsx +41 -0
  65. package/src/components/Modals/ModalSelectMedia/ModalSelectMedia.tsx +285 -0
  66. package/src/components/Modals/ModalWrapper/ModalWrapper.tsx +103 -0
  67. package/src/components/Modals/NewChatModal/ModalContent.tsx +151 -0
  68. package/src/components/Modals/NewChatModal/NewChatModal.tsx +391 -0
  69. package/src/components/Modals/NewChatModal/UpdNewChatModal.tsx +136 -0
  70. package/src/components/Modals/SelectUsersModal/SelectUsersModal.tsx +134 -0
  71. package/src/components/Modals/SettingsModals/BlockedUsers/BlockedUsersModal.tsx +63 -0
  72. package/src/components/Modals/SettingsModals/DocumentShares/DocumentSharesModal.tsx +115 -0
  73. package/src/components/Modals/SettingsModals/ManageDataModal/ManageDataModal.tsx +121 -0
  74. package/src/components/Modals/SettingsModals/ManageDataModal/StyledComponents.ts +0 -0
  75. package/src/components/Modals/SettingsModals/ProfileShares/ProfileShares.tsx +65 -0
  76. package/src/components/Modals/SettingsModals/Referrals/Referrals.tsx +90 -0
  77. package/src/components/Modals/SettingsModals/SharedStyledComponents.ts +74 -0
  78. package/src/components/Modals/SettingsModals/Visibility/RadioInput.tsx +59 -0
  79. package/src/components/Modals/SettingsModals/Visibility/StyledComponents.ts +32 -0
  80. package/src/components/Modals/SettingsModals/Visibility/VisibilityModal.tsx +146 -0
  81. package/src/components/Modals/UserProfileModal/EditUserModal.tsx +163 -0
  82. package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +410 -0
  83. package/src/components/Modals/UserSettingsModal/UserSettingsModal.tsx +85 -0
  84. package/src/components/Modals/styledModalComponents.tsx +181 -0
  85. package/src/components/OperationalModal/OperationalModal.tsx +78 -0
  86. package/src/components/RoomComponents/ChatRoomItem.tsx +219 -0
  87. package/src/components/RoomComponents/LastMessageItem.tsx +47 -0
  88. package/src/components/RoomComponents/styled/LastAudioMessage.tsx +62 -0
  89. package/src/components/RoomComponents/styled/LastMessageEmoji.tsx +26 -0
  90. package/src/components/RoomComponents/styled/LastMessageFile.tsx +54 -0
  91. package/src/components/RoomComponents/styled/LastMessagePhoto.tsx +54 -0
  92. package/src/components/RoomComponents/styled/LastMessageVideo.tsx +70 -0
  93. package/src/components/RoomComponents/styled/LastTextMessage.tsx +65 -0
  94. package/src/components/RoomComponents/styled/StyledRoomComponents.ts +53 -0
  95. package/src/components/SortDropDown/index.tsx +76 -0
  96. package/src/components/Thread/ThreadHeader.tsx +41 -0
  97. package/src/components/Thread/ThreadWrapper.tsx +270 -0
  98. package/src/components/Toast/Toast.tsx +128 -0
  99. package/src/components/Toast/index.ts +13 -0
  100. package/src/components/UsersList/StyledComponents.ts +30 -0
  101. package/src/components/UsersList/UsersList.tsx +112 -0
  102. package/src/components/styled/ActionButton.tsx +40 -0
  103. package/src/components/styled/AudioMessage.tsx +82 -0
  104. package/src/components/styled/Button.tsx +107 -0
  105. package/src/components/styled/ChatWrapperBox.tsx +9 -0
  106. package/src/components/styled/DateLabel.tsx +77 -0
  107. package/src/components/styled/Loader.tsx +65 -0
  108. package/src/components/styled/MediaFilePreview.tsx +42 -0
  109. package/src/components/styled/MediaModal.tsx +54 -0
  110. package/src/components/styled/MessageImage.tsx +54 -0
  111. package/src/components/styled/NewMessageLabel.tsx +50 -0
  112. package/src/components/styled/RoomListComponents/index.tsx +164 -0
  113. package/src/components/styled/SendInput.tsx +297 -0
  114. package/src/components/styled/StyledComponents.ts +320 -0
  115. package/src/components/styled/StyledInput.tsx +101 -0
  116. package/src/components/styled/StyledInputComponents/AudioComponents.tsx +1 -0
  117. package/src/components/styled/StyledInputComponents/Composing.tsx +97 -0
  118. package/src/components/styled/StyledInputComponents/CustomTypingIndicator.tsx +261 -0
  119. package/src/components/styled/StyledInputComponents/MediaComponents.tsx +90 -0
  120. package/src/components/styled/StyledInputComponents/StyledInputComponents.tsx +131 -0
  121. package/src/components/styled/StyledInputComponents/VideoComponents.tsx +49 -0
  122. package/src/components/styled/TreadLabel.tsx +50 -0
  123. package/src/components/styled/UnsupportedType.tsx +95 -0
  124. package/src/components/styled/VideoMessage.tsx +68 -0
  125. package/src/context/MessageNotificationContext.tsx +335 -0
  126. package/src/context/PushNotificationProvider.tsx +78 -0
  127. package/src/context/ToastContext.tsx +63 -0
  128. package/src/context/toastProvider.tsx +0 -0
  129. package/src/context/xmppProvider.tsx +364 -0
  130. package/src/helpers/block_scroll.ts +95 -0
  131. package/src/helpers/chatAutoEnterer.ts +46 -0
  132. package/src/helpers/checkUniqueUsers.tsx +31 -0
  133. package/src/helpers/constants/CHAT_TYPES.ts +6 -0
  134. package/src/helpers/constants/LANGUAGE_OPTIONS.ts +14 -0
  135. package/src/helpers/constants/LOCAL_STORAGE.ts +3 -0
  136. package/src/helpers/constants/MESSAGE_INTERACTIONS.ts +29 -0
  137. package/src/helpers/constants/MODAL_TYPES.ts +57 -0
  138. package/src/helpers/constants/PLATFORM_CONSTANTS.ts +3 -0
  139. package/src/helpers/createMainMessageForThread.ts +23 -0
  140. package/src/helpers/createMessageFromXml.ts +118 -0
  141. package/src/helpers/createRoomFromApi.tsx +27 -0
  142. package/src/helpers/createUserNameFromSetUser.ts +15 -0
  143. package/src/helpers/dateComparison.ts +12 -0
  144. package/src/helpers/debounce.ts +10 -0
  145. package/src/helpers/ensureScopedChatCache.ts +61 -0
  146. package/src/helpers/extractUniqueMembersFromRooms.ts +45 -0
  147. package/src/helpers/formatNumberWithCommas.ts +3 -0
  148. package/src/helpers/getBooleanFromString.ts +7 -0
  149. package/src/helpers/getDataFromXml.ts +80 -0
  150. package/src/helpers/getNumberFromString.ts +5 -0
  151. package/src/helpers/getRoomsWithRetry.ts +45 -0
  152. package/src/helpers/getTintedColor.ts +28 -0
  153. package/src/helpers/getUnnamedUsers.ts +39 -0
  154. package/src/helpers/handleCopyClick.ts +3 -0
  155. package/src/helpers/handleXmppError.ts +14 -0
  156. package/src/helpers/hashcolor.ts +34 -0
  157. package/src/helpers/historyPreloadScheduler.ts +264 -0
  158. package/src/helpers/initRoomsPresence.ts +32 -0
  159. package/src/helpers/initXmppRooms.tsx +71 -0
  160. package/src/helpers/insertMessageWithDelimiter.ts +96 -0
  161. package/src/helpers/isChatIdPresentInArray.ts +54 -0
  162. package/src/helpers/localStorageUser.ts +9 -0
  163. package/src/helpers/markdownParser.tsx +295 -0
  164. package/src/helpers/nanoToMs.ts +3 -0
  165. package/src/helpers/parseMessageBody.tsx +516 -0
  166. package/src/helpers/parseUrls.tsx +42 -0
  167. package/src/helpers/resolveInitBeforeLoadUser.ts +240 -0
  168. package/src/helpers/storeConsole.ts +26 -0
  169. package/src/helpers/transformTranslatations.ts +16 -0
  170. package/src/helpers/updateMessagesTillLast.tsx +104 -0
  171. package/src/helpers/updatedChatLastTimestamps.ts +29 -0
  172. package/src/helpers/validator.ts +20 -0
  173. package/src/helpers/walletUsername.ts +12 -0
  174. package/src/hooks/hooks.ts +6 -0
  175. package/src/hooks/useChatSettingState.tsx +38 -0
  176. package/src/hooks/useChatWrapperInit.ts +266 -0
  177. package/src/hooks/useComposing.tsx +59 -0
  178. package/src/hooks/useCustomTypingIndicator.tsx +193 -0
  179. package/src/hooks/useEventHandlers.tsx +81 -0
  180. package/src/hooks/useGetNewArchRoom.tsx +57 -0
  181. package/src/hooks/useHeapSender.tsx +88 -0
  182. package/src/hooks/useLocalStorage.tsx +53 -0
  183. package/src/hooks/useLogout.tsx +31 -0
  184. package/src/hooks/useMessageHeapState.tsx +11 -0
  185. package/src/hooks/useMessageLoaderQueue.tsx +90 -0
  186. package/src/hooks/useMessageQueue.ts +146 -0
  187. package/src/hooks/usePendingNotification.ts +49 -0
  188. package/src/hooks/usePrevious.tsx +15 -0
  189. package/src/hooks/usePushNotifications.ts +106 -0
  190. package/src/hooks/useQRCodeChatHandler.ts +58 -0
  191. package/src/hooks/useRoomInitialization.tsx +160 -0
  192. package/src/hooks/useRoomState.tsx +47 -0
  193. package/src/hooks/useRoomUrl.tsx +41 -0
  194. package/src/hooks/useSendMessage.tsx +306 -0
  195. package/src/hooks/useUnreadMessagesCounter.ts +55 -0
  196. package/src/hooks/useXmppInitialization.ts +148 -0
  197. package/src/main.ts +45 -0
  198. package/src/networking/api-requests/auth.api.ts +140 -0
  199. package/src/networking/api-requests/push.api.ts +55 -0
  200. package/src/networking/api-requests/roomMembers.api.ts +20 -0
  201. package/src/networking/api-requests/rooms.api.ts +142 -0
  202. package/src/networking/api-requests/user.api.ts +65 -0
  203. package/src/networking/apiClient.ts +149 -0
  204. package/src/networking/helpers/refreshChecker.ts +13 -0
  205. package/src/networking/stanzaHandlers.ts +418 -0
  206. package/src/networking/xmpp/actionSetTimestampToPrivateStore.xmpp.ts +37 -0
  207. package/src/networking/xmpp/allRoomPresences.xmpp.ts +10 -0
  208. package/src/networking/xmpp/createPrivateRoom.xmpp.ts +26 -0
  209. package/src/networking/xmpp/createRoom.xmpp.ts +35 -0
  210. package/src/networking/xmpp/createRoomPresence.xmpp.ts +74 -0
  211. package/src/networking/xmpp/createTimeoutPromise.xmpp.ts +15 -0
  212. package/src/networking/xmpp/deleteMessage.xmpp.ts +19 -0
  213. package/src/networking/xmpp/editMessage.xmpp.ts +46 -0
  214. package/src/networking/xmpp/getChatsPrivateStoreRequest.xmpp.ts +57 -0
  215. package/src/networking/xmpp/getHistory.xmpp.ts +140 -0
  216. package/src/networking/xmpp/getLastMessageArchive.xmpp.ts +24 -0
  217. package/src/networking/xmpp/getRoomInfo.xmpp.ts +15 -0
  218. package/src/networking/xmpp/getRoomMembers.xmpp.ts +13 -0
  219. package/src/networking/xmpp/getRooms.xmpp.ts +43 -0
  220. package/src/networking/xmpp/getRoomsPaged.xmpp.ts +59 -0
  221. package/src/networking/xmpp/handlePong.xmpp.ts +7 -0
  222. package/src/networking/xmpp/handleStanzas.xmpp.ts +55 -0
  223. package/src/networking/xmpp/inviteRoomRequest.xmpp.ts +25 -0
  224. package/src/networking/xmpp/leaveTheRoom.xmpp.ts +15 -0
  225. package/src/networking/xmpp/listenerManager.ts +116 -0
  226. package/src/networking/xmpp/presenceInRoom.xmpp.ts +57 -0
  227. package/src/networking/xmpp/roomConfig.xmpp.ts +71 -0
  228. package/src/networking/xmpp/sendMediaMessage.xmpp.ts +97 -0
  229. package/src/networking/xmpp/sendMessageReaction.xmpp.ts +41 -0
  230. package/src/networking/xmpp/sendPing.xmpp.ts +21 -0
  231. package/src/networking/xmpp/sendTextMessage.xmpp.ts +99 -0
  232. package/src/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.ts +44 -0
  233. package/src/networking/xmpp/sendTypingRequest.xmpp.ts +26 -0
  234. package/src/networking/xmpp/setChatsPrivateStoreRequest.xmpp.ts +48 -0
  235. package/src/networking/xmpp/setMeAsOwner.xmpp.ts +52 -0
  236. package/src/networking/xmpp/setRoomImage.xmpp.ts +26 -0
  237. package/src/networking/xmpp/setVCard.xmpp.ts +19 -0
  238. package/src/networking/xmpp/subscribeToRoomMessages.xmpp.ts +75 -0
  239. package/src/networking/xmppClient.ts +603 -0
  240. package/src/roomStore/Middleware/logoutMiddleware.tsx +24 -0
  241. package/src/roomStore/Middleware/newMessageMidlleware.tsx +87 -0
  242. package/src/roomStore/Middleware/reactionsMiddleware.tsx +77 -0
  243. package/src/roomStore/Middleware/testMiddleware.tsx +20 -0
  244. package/src/roomStore/Middleware/unreadMidlleware.tsx +58 -0
  245. package/src/roomStore/chatSettingsSlice.ts +178 -0
  246. package/src/roomStore/index.ts +68 -0
  247. package/src/roomStore/persistence.ts +152 -0
  248. package/src/roomStore/roomHeapSlice.ts +37 -0
  249. package/src/roomStore/roomsSlice.ts +353 -0
  250. package/src/services/pushNotifications.ts +170 -0
  251. package/src/services/pushSubscriptionService.ts +145 -0
  252. package/src/types/messages.ts +61 -0
  253. package/src/types/models/action.model.ts +30 -0
  254. package/src/types/models/config.model.ts +16 -0
  255. package/src/types/models/customComponents.model.ts +53 -0
  256. package/src/types/models/language.model.ts +11 -0
  257. package/src/types/models/media.model.ts +5 -0
  258. package/src/types/models/message.model.ts +69 -0
  259. package/src/types/models/modal.model.ts +9 -0
  260. package/src/types/models/room.model.ts +120 -0
  261. package/src/types/models/user.model.ts +90 -0
  262. package/src/types/models/xmpp.model.ts +155 -0
  263. package/src/types/types.ts +505 -0
  264. package/src/utils/PriorityQueue.ts +47 -0
  265. package/src/utils/actionHelpers.ts +40 -0
  266. package/src/utils/actionLogger.ts +64 -0
  267. package/src/utils/clientRegistry.ts +80 -0
  268. package/src/utils/devLogger.ts +197 -0
  269. package/src/utils/messageNotificationManager.ts +108 -0
  270. package/src/utils/resendMessage.ts +87 -0
  271. package/src/utils/toastEmitter.ts +27 -0
  272. package/src/utils/xmppMemoryMonitor.ts +63 -0
  273. package/src/version.ts +1 -0
@@ -0,0 +1,353 @@
1
+ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
2
+ import { EditAction, HistoryPreloadState, IMessage, IRoom } from '../types/types';
3
+ import { insertMessageWithDelimiter } from '../helpers/insertMessageWithDelimiter';
4
+
5
+ export interface RoomPreloadPatch {
6
+ jid: string;
7
+ messages?: IMessage[];
8
+ historyPreloadState?: HistoryPreloadState;
9
+ unreadCapped?: boolean;
10
+ historyComplete?: boolean;
11
+ }
12
+
13
+ interface RoomMessagesState {
14
+ rooms: { [jid: string]: IRoom };
15
+ activeRoomJID: string | null;
16
+ editAction?: EditAction;
17
+ isLoading: boolean;
18
+ pendingNotificationJid?: string | null;
19
+ }
20
+
21
+ const initialState: RoomMessagesState = {
22
+ rooms: {},
23
+ activeRoomJID: null,
24
+ isLoading: false,
25
+ editAction: {
26
+ isEdit: false,
27
+ roomJid: '',
28
+ messageId: '',
29
+ text: '',
30
+ },
31
+ pendingNotificationJid: null,
32
+ };
33
+
34
+ export const roomsStore = createSlice({
35
+ name: 'roomMessages',
36
+ initialState,
37
+ reducers: {
38
+ addRoom(state, action: PayloadAction<{ roomData: IRoom }>) {
39
+ const { roomData } = action.payload;
40
+ state.rooms[roomData.jid] = roomData;
41
+ },
42
+ deleteRoom(state, action: PayloadAction<{ jid: string }>) {
43
+ const { jid } = action.payload;
44
+ if (state.rooms[jid]) {
45
+ delete state.rooms[jid];
46
+ }
47
+ },
48
+ updateRoom(
49
+ state,
50
+ action: PayloadAction<{ jid: string; updates: Partial<IRoom> }>
51
+ ) {
52
+ const { jid, updates } = action.payload;
53
+ const existingRoom = state.rooms[jid];
54
+
55
+ if (existingRoom) {
56
+ state.rooms[jid] = {
57
+ ...existingRoom,
58
+ ...updates,
59
+ };
60
+ }
61
+ },
62
+ setRoomMessages(
63
+ state,
64
+ action: PayloadAction<{ roomJID: string; messages: IMessage[] }>
65
+ ) {
66
+ const { roomJID, messages } = action.payload;
67
+ if (state.rooms[roomJID]) {
68
+ state.rooms[roomJID].messages = messages;
69
+ }
70
+ },
71
+ deleteRoomMessage(
72
+ state,
73
+ action: PayloadAction<{ roomJID: string; messageId: string }>
74
+ ) {
75
+ const { roomJID, messageId } = action.payload;
76
+ if (state.rooms[roomJID]) {
77
+ state.rooms[roomJID].messages.map((message) => {
78
+ if (message.id === messageId) {
79
+ message.isDeleted = true;
80
+ }
81
+ });
82
+ }
83
+ },
84
+ setEditAction: (state, action: PayloadAction<EditAction | undefined>) => {
85
+ if (action.payload?.isEdit) {
86
+ state.editAction = action.payload;
87
+ } else {
88
+ state.editAction = {
89
+ isEdit: false,
90
+ roomJid: '',
91
+ messageId: '',
92
+ text: '',
93
+ };
94
+ }
95
+ },
96
+ editRoomMessage(
97
+ state,
98
+ action: PayloadAction<{
99
+ roomJID: string;
100
+ messageId: string;
101
+ text: string;
102
+ }>
103
+ ) {
104
+ const { roomJID, messageId, text } = action.payload;
105
+ if (state.rooms[roomJID]) {
106
+ state.rooms[roomJID].messages.map((message) => {
107
+ if (message.id === messageId) {
108
+ message.body = text;
109
+ }
110
+ });
111
+ }
112
+ },
113
+ addRoomMessage(
114
+ state,
115
+ action: PayloadAction<{
116
+ roomJID: string;
117
+ message: IMessage;
118
+ start?: boolean;
119
+ }>
120
+ ) {
121
+ const { roomJID, message, start } = action.payload;
122
+
123
+ if (!state.rooms[roomJID]?.messages) {
124
+ state.rooms[roomJID].messages = [];
125
+ }
126
+
127
+ const roomMessages = state.rooms[roomJID].messages;
128
+
129
+ if (roomMessages.length === 0 || start) {
130
+ roomMessages.unshift(message);
131
+ } else {
132
+ const lastViewedTimestamp = state.rooms[roomJID].lastViewedTimestamp
133
+ ? new Date(state.rooms[roomJID].lastViewedTimestamp)
134
+ : null;
135
+
136
+ insertMessageWithDelimiter(roomMessages, message, lastViewedTimestamp);
137
+ }
138
+ },
139
+ deleteAllRooms(state) {
140
+ state.rooms = {};
141
+ },
142
+ setComposing(
143
+ state,
144
+ action: PayloadAction<{
145
+ chatJID: string;
146
+ composing: boolean;
147
+ composingList?: string[];
148
+ }>
149
+ ) {
150
+ const { chatJID, composing, composingList } = action.payload;
151
+ state.rooms[chatJID].composing = composing;
152
+ state.rooms[chatJID].composingList = composingList;
153
+ },
154
+ setIsLoading: (
155
+ state,
156
+ action: PayloadAction<{ chatJID?: string; loading: boolean }>
157
+ ) => {
158
+ const { chatJID, loading } = action.payload;
159
+ if (chatJID && state.rooms?.[chatJID]) {
160
+ state.rooms[chatJID].isLoading = loading;
161
+ }
162
+ state.isLoading = loading;
163
+ },
164
+ setLastViewedTimestamp: (
165
+ state,
166
+ action: PayloadAction<{ chatJID: string; timestamp: number }>
167
+ ) => {
168
+ const { chatJID, timestamp } = action.payload;
169
+ if (state.rooms[chatJID]) {
170
+ state.rooms[chatJID].lastViewedTimestamp = timestamp;
171
+ // timestamp === 0 means "user is currently viewing this room" —
172
+ // unread is cleared. Otherwise count messages received strictly
173
+ // after the last-viewed instant.
174
+ if (!timestamp) {
175
+ state.rooms[chatJID].unreadMessages = 0;
176
+ } else {
177
+ state.rooms[chatJID].unreadMessages = countNewerMessages(
178
+ state.rooms[chatJID].messages,
179
+ timestamp
180
+ );
181
+ }
182
+ }
183
+ },
184
+ setRoomRole: (
185
+ state,
186
+ action: PayloadAction<{ chatJID: string; role: string }>
187
+ ) => {
188
+ const { chatJID, role } = action.payload;
189
+ if (state.rooms[chatJID]) {
190
+ state.rooms[chatJID].role = role;
191
+ }
192
+ },
193
+ setRoomNoMessages: (
194
+ state,
195
+ action: PayloadAction<{ value: boolean; chatJID?: string }>
196
+ ) => {
197
+ const { value, chatJID } = action.payload;
198
+ if (chatJID) {
199
+ state.rooms[chatJID].noMessages = value;
200
+ }
201
+ },
202
+ setCurrentRoom: (
203
+ state,
204
+ action: PayloadAction<{ roomJID: string | null }>
205
+ ) => {
206
+ // Accept null/empty so callers can clear the active room (e.g.
207
+ // back button from chat → return to RoomList). Mirrors web.
208
+ state.activeRoomJID = action.payload.roomJID || '';
209
+ },
210
+ /**
211
+ * Stash a JID that a push notification asked us to open before
212
+ * the rooms list has loaded. The Chat component clears this once
213
+ * the room exists locally and dispatches `setCurrentRoom`.
214
+ */
215
+ setPendingNotificationJid: (
216
+ state,
217
+ action: PayloadAction<string | null>
218
+ ) => {
219
+ state.pendingNotificationJid = action.payload;
220
+ },
221
+ clearPendingNotificationJid: (state) => {
222
+ state.pendingNotificationJid = null;
223
+ },
224
+ /**
225
+ * Stamp a message in `state.rooms[roomJID].messages` with an updated
226
+ * reactions list. The reactionsMiddleware listens for this action to
227
+ * keep `IRoom.lastMessage` / `lastMessageTimestamp` in sync.
228
+ */
229
+ setReactions: (
230
+ state,
231
+ action: PayloadAction<{
232
+ roomJID: string;
233
+ messageId: string;
234
+ from?: string;
235
+ reactions: string[];
236
+ latestReactionTimestamp?: string;
237
+ data?: Record<string, string>;
238
+ }>
239
+ ) => {
240
+ const { roomJID, messageId, reactions } = action.payload;
241
+ const room = state.rooms[roomJID];
242
+ if (!room?.messages) {return;}
243
+ for (const msg of room.messages) {
244
+ if (msg?.id === messageId) {
245
+ (msg as any).reactions = reactions;
246
+ break;
247
+ }
248
+ }
249
+ },
250
+ setLogoutState: (state) => {
251
+ state.rooms = {};
252
+ state.activeRoomJID = null;
253
+ state.isLoading = false;
254
+ },
255
+ setActiveMessage: (
256
+ state,
257
+ action: PayloadAction<{ id: string; chatJID: string }>
258
+ ) => {
259
+ const { id, chatJID } = action.payload;
260
+
261
+ state.rooms[chatJID].messages.map((message) => {
262
+ if (message.id === id) {
263
+ message.activeMessage = true;
264
+ } else {
265
+ message.activeMessage = false;
266
+ }
267
+ });
268
+ },
269
+ setCloseActiveMessage: (
270
+ state,
271
+ action: PayloadAction<{ chatJID: string }>
272
+ ) => {
273
+ const { chatJID } = action.payload;
274
+
275
+ state.rooms[chatJID].messages.map((message) => {
276
+ message.activeMessage = false;
277
+ });
278
+ },
279
+ /**
280
+ * Batched update used by the history preload scheduler. Each patch is
281
+ * applied to its room: messages REPLACE (when provided), state fields
282
+ * MERGE. Rooms that don't exist yet are ignored.
283
+ */
284
+ applyRoomsPreloadBatch: (
285
+ state,
286
+ action: PayloadAction<{ rooms: RoomPreloadPatch[] }>
287
+ ) => {
288
+ const { rooms } = action.payload;
289
+ for (const patch of rooms) {
290
+ const room = state.rooms[patch.jid];
291
+ if (!room) {continue;}
292
+ if (typeof patch.historyPreloadState !== 'undefined') {
293
+ room.historyPreloadState = patch.historyPreloadState;
294
+ }
295
+ if (typeof patch.unreadCapped !== 'undefined') {
296
+ room.unreadCapped = patch.unreadCapped;
297
+ }
298
+ if (typeof patch.historyComplete !== 'undefined') {
299
+ room.historyComplete = patch.historyComplete;
300
+ }
301
+ if (Array.isArray(patch.messages)) {
302
+ // Merge: keep any locally-pending messages, replace the rest.
303
+ const pending = room.messages?.filter((m) => m?.pending) || [];
304
+ room.messages = [...patch.messages, ...pending];
305
+ }
306
+ }
307
+ },
308
+ },
309
+ });
310
+
311
+ // Count messages strictly newer than the given millisecond timestamp.
312
+ // Uses `msg.date` (canonical ISO/Date) so it matches the unread
313
+ // middleware. Excludes the "delimiter-new" sentinel and pending sends.
314
+ const countNewerMessages = (
315
+ messages: IMessage[],
316
+ timestamp: number
317
+ ): number => {
318
+ if (!messages?.length || !timestamp) {return 0;}
319
+ let count = 0;
320
+ for (const message of messages) {
321
+ if (!message || message.id === 'delimiter-new' || message.pending) {continue;}
322
+ const ms = new Date(message.date as any).getTime();
323
+ if (Number.isFinite(ms) && ms > timestamp) {count += 1;}
324
+ }
325
+ return count;
326
+ };
327
+
328
+ export const {
329
+ addRoom,
330
+ deleteAllRooms,
331
+ setRoomMessages,
332
+ addRoomMessage,
333
+ deleteRoomMessage,
334
+ setEditAction,
335
+ editRoomMessage,
336
+ setComposing,
337
+ setIsLoading,
338
+ setLastViewedTimestamp,
339
+ setRoomNoMessages,
340
+ setCurrentRoom,
341
+ setRoomRole,
342
+ setLogoutState,
343
+ setActiveMessage,
344
+ setCloseActiveMessage,
345
+ deleteRoom,
346
+ updateRoom,
347
+ applyRoomsPreloadBatch,
348
+ setPendingNotificationJid,
349
+ clearPendingNotificationJid,
350
+ setReactions,
351
+ } = roomsStore.actions;
352
+
353
+ export default roomsStore.reducer;
@@ -0,0 +1,170 @@
1
+ import messaging, { FirebaseMessagingTypes } from '@react-native-firebase/messaging';
2
+ import { getApp } from '@react-native-firebase/app';
3
+ import { Platform, PermissionsAndroid } from 'react-native';
4
+
5
+ let appInstance: any;
6
+ try {
7
+ appInstance = getApp();
8
+ } catch (error) {
9
+ }
10
+
11
+ const AuthorizationStatus = (messaging as any).AuthorizationStatus;
12
+
13
+ let cachedMessagingInstance: any = null;
14
+
15
+ const getMessagingInstance = () => {
16
+ if (cachedMessagingInstance) {
17
+ return cachedMessagingInstance;
18
+ }
19
+
20
+ if (!appInstance) {
21
+ try {
22
+ appInstance = getApp();
23
+ } catch (e) {
24
+ }
25
+ }
26
+
27
+ cachedMessagingInstance = messaging();
28
+ return cachedMessagingInstance;
29
+ };
30
+
31
+ export async function requestNotificationPermission(): Promise<boolean> {
32
+ const messagingInstance = getMessagingInstance();
33
+ if (Platform.OS === 'ios') {
34
+ const authStatus = await messagingInstance.requestPermission();
35
+ return (
36
+ authStatus === AuthorizationStatus.AUTHORIZED ||
37
+ authStatus === AuthorizationStatus.PROVISIONAL
38
+ );
39
+ }
40
+
41
+ if (Platform.OS === 'android' && Platform.Version >= 33) {
42
+ const granted = await PermissionsAndroid.request(
43
+ PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
44
+ );
45
+ return granted === PermissionsAndroid.RESULTS.GRANTED;
46
+ }
47
+
48
+ return true;
49
+ }
50
+
51
+ export async function getFCMToken(): Promise<string | null> {
52
+ try {
53
+ const messagingInstance = getMessagingInstance();
54
+ if (!messagingInstance.isDeviceRegisteredForRemoteMessages) {
55
+ await messagingInstance.registerDeviceForRemoteMessages();
56
+ }
57
+
58
+ if (Platform.OS === 'ios') {
59
+ await new Promise(resolve => setTimeout(resolve, 2000));
60
+
61
+ let apnsToken = await messagingInstance.getAPNSToken();
62
+
63
+ if (!apnsToken) {
64
+ await new Promise(resolve => setTimeout(resolve, 3000));
65
+ apnsToken = await messagingInstance.getAPNSToken();
66
+ }
67
+
68
+ // No APNS token means push not configured - this is OK for development
69
+ if (!apnsToken) {
70
+ console.log('Push notifications not configured (missing APNs). App will work without push.');
71
+ return null;
72
+ }
73
+ }
74
+
75
+ const token = await messagingInstance.getToken();
76
+
77
+ return token;
78
+ } catch (error: any) {
79
+ // Silently handle missing aps-environment (push not configured in Xcode)
80
+ const errorMsg = error?.message || String(error);
81
+ if (errorMsg.includes('aps-environment') || errorMsg.includes('messaging/unknown')) {
82
+ console.log('Push notifications not configured in Xcode. App will work without push.');
83
+ return null;
84
+ }
85
+ console.warn('Error getting FCM token:', error);
86
+ return null;
87
+ }
88
+ }
89
+
90
+ export function onForegroundMessage(
91
+ callback: (message: FirebaseMessagingTypes.RemoteMessage) => void
92
+ ): () => void {
93
+ const messagingInstance = getMessagingInstance();
94
+ return messagingInstance.onMessage(callback);
95
+ }
96
+
97
+ export function setBackgroundMessageHandler(
98
+ handler: (message: any) => Promise<void>
99
+ ): void {
100
+ const messagingInstance = getMessagingInstance();
101
+ messagingInstance.setBackgroundMessageHandler(handler);
102
+ }
103
+
104
+ export function onNotificationOpenedApp(
105
+ callback: (message: any) => void
106
+ ): () => void {
107
+ const messagingInstance = getMessagingInstance();
108
+ return messagingInstance.onNotificationOpenedApp(callback);
109
+ }
110
+
111
+ export async function getInitialNotification(): Promise<any | null> {
112
+ try {
113
+ const messagingInstance = getMessagingInstance();
114
+ const message = await messagingInstance.getInitialNotification();
115
+ return message;
116
+ } catch (error) {
117
+ console.error('Error getting initial notification:', error);
118
+ return null;
119
+ }
120
+ }
121
+
122
+ export async function initPushNotifications(): Promise<string | null> {
123
+
124
+ const hasPermission = await requestNotificationPermission();
125
+
126
+ if (!hasPermission) {
127
+ console.error('Push notification permission denied');
128
+ return null;
129
+ }
130
+
131
+ const token = await getFCMToken();
132
+
133
+ return token;
134
+ }
135
+
136
+ export async function checkNotificationPermission(): Promise<boolean> {
137
+ try {
138
+ const messagingInstance = getMessagingInstance();
139
+ if (Platform.OS === 'ios') {
140
+ try {
141
+ const apnsToken = await messagingInstance.getAPNSToken();
142
+ return apnsToken !== null;
143
+ } catch (error) {
144
+ return false;
145
+ }
146
+ }
147
+
148
+ if (Platform.OS === 'android' && Platform.Version >= 33) {
149
+ const granted = await PermissionsAndroid.check(
150
+ PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
151
+ );
152
+ return granted;
153
+ }
154
+
155
+ return true;
156
+ } catch (error) {
157
+ console.warn('Error checking notification permission:', error);
158
+ return false;
159
+ }
160
+ }
161
+
162
+ export async function disablePushNotifications(): Promise<void> {
163
+ try {
164
+ const messagingInstance = getMessagingInstance();
165
+ await messagingInstance.deleteToken();
166
+ console.log('FCM token deleted successfully');
167
+ } catch (error) {
168
+ console.warn('Error deleting FCM token:', error);
169
+ }
170
+ }
@@ -0,0 +1,145 @@
1
+ import { Client } from '@xmpp/client';
2
+ import { User } from '../types/types';
3
+ import AsyncStorage from '@react-native-async-storage/async-storage';
4
+ import { subscribeToPushNotifications } from '../networking/api-requests/push.api';
5
+ import { subscribeToRoomMessages } from '../networking/xmpp/subscribeToRoomMessages.xmpp';
6
+
7
+ const SUBSCRIBED_ROOMS_KEY = 'preshent_subscribed_rooms';
8
+
9
+ export class PushSubscriptionService {
10
+ private subscribedRooms: Set<string> = new Set();
11
+ private isPushSubscribed: boolean = false;
12
+ private lastSubscriptionKey: string | null = null;
13
+ private isInitialized: boolean = false;
14
+
15
+ private async loadSubscribedRoomsFromStorage(): Promise<void> {
16
+ if (this.isInitialized) {return;}
17
+
18
+ try {
19
+ const storedRooms = await AsyncStorage.getItem(SUBSCRIBED_ROOMS_KEY);
20
+ if (storedRooms) {
21
+ const roomsArray = JSON.parse(storedRooms) as string[];
22
+ this.subscribedRooms = new Set(roomsArray);
23
+ }
24
+ this.isInitialized = true;
25
+ } catch (error) {
26
+ console.error('[PushService] Failed to load subscribed rooms from storage:', error);
27
+ }
28
+ }
29
+
30
+ private async saveSubscribedRoomsToStorage(): Promise<void> {
31
+ try {
32
+ const roomsArray = Array.from(this.subscribedRooms);
33
+ await AsyncStorage.setItem(SUBSCRIBED_ROOMS_KEY, JSON.stringify(roomsArray));
34
+ } catch (error) {
35
+ console.error('[PushService] Failed to save subscribed rooms to storage:', error);
36
+ }
37
+ }
38
+
39
+ async subscribeToPush(
40
+ fcmToken: string,
41
+ user: User,
42
+ projectName: string,
43
+ ): Promise<void> {
44
+ const walletAddress = user.defaultWallet?.walletAddress || user.walletAddress;
45
+ const subscriptionKey = `${fcmToken}_${walletAddress}`;
46
+
47
+ if (this.isPushSubscribed && this.lastSubscriptionKey === subscriptionKey) {
48
+ console.log('⚠️ Push already subscribed with this token, skipping...');
49
+ return;
50
+ }
51
+
52
+ try {
53
+ const userJid: string = user.xmppUsername || '';
54
+
55
+ if (!userJid) {
56
+ throw new Error('User JID is required for push subscription');
57
+ }
58
+
59
+ await subscribeToPushNotifications(fcmToken, userJid, projectName);
60
+ this.isPushSubscribed = true;
61
+ this.lastSubscriptionKey = subscriptionKey;
62
+ } catch (error: any) {
63
+ console.error('Failed to subscribe to push after all retries:', error);
64
+ }
65
+ }
66
+
67
+ async subscribeToRoom(
68
+ client: Client,
69
+ roomJID: string,
70
+ userNick?: string
71
+ ): Promise<boolean> {
72
+ await this.loadSubscribedRoomsFromStorage();
73
+
74
+ if (this.subscribedRooms.has(roomJID)) {
75
+ return true;
76
+ }
77
+
78
+ try {
79
+ const result = await subscribeToRoomMessages(client, roomJID, userNick);
80
+ if (result) {
81
+ this.subscribedRooms.add(roomJID);
82
+ await this.saveSubscribedRoomsToStorage();
83
+ }
84
+ console.log('Successfully Subscribed to room', roomJID);
85
+ return result;
86
+ } catch (error) {
87
+ console.error(`[PushService] Failed to subscribe to room ${roomJID}:`, error);
88
+ return false;
89
+ }
90
+ }
91
+
92
+ async subscribeToRooms(
93
+ client: Client,
94
+ roomJIDs: string[],
95
+ userNick?: string
96
+ ): Promise<void> {
97
+ let successful = 0;
98
+ let failed = 0;
99
+
100
+ console.log('test roomJIDs', roomJIDs);
101
+
102
+ for (const roomJID of roomJIDs) {
103
+ try {
104
+ const result = await this.subscribeToRoom(client, roomJID, userNick);
105
+ console.log('test roomJID-1', result);
106
+ if (result) {
107
+ successful++;
108
+ } else {
109
+ failed++;
110
+ }
111
+ await new Promise(resolve => setTimeout(resolve, 100));
112
+ } catch (error) {
113
+ failed++;
114
+ console.error(`Failed to subscribe to room ${roomJID}:`, error);
115
+ }
116
+ }
117
+
118
+ console.log(`Subscribed to ${successful} rooms, ${failed} failed`);
119
+ }
120
+
121
+ async reset(): Promise<void> {
122
+ this.subscribedRooms.clear();
123
+ this.isPushSubscribed = false;
124
+ this.lastSubscriptionKey = null;
125
+ this.isInitialized = false;
126
+
127
+ try {
128
+ await AsyncStorage.removeItem(SUBSCRIBED_ROOMS_KEY);
129
+ console.log('[PushService] Subscribed rooms cleared from storage');
130
+ } catch (error) {
131
+ console.error('[PushService] Failed to clear subscribed rooms from storage:', error);
132
+ }
133
+ }
134
+
135
+ isRoomSubscribed(roomJID: string): boolean {
136
+ return this.subscribedRooms.has(roomJID);
137
+ }
138
+
139
+ getSubscribedRooms(): string[] {
140
+ return Array.from(this.subscribedRooms);
141
+ }
142
+ }
143
+
144
+ export const pushSubscriptionService = new PushSubscriptionService();
145
+