@ethora/chat-component-rn 26.10.2 → 26.10.4
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.
- package/lib/commonjs/components/Header/HeaderRoomList.js +72 -69
- package/lib/commonjs/components/Header/HeaderRoomList.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatHeader.js +13 -11
- package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MessageContainer.js +10 -2
- package/lib/commonjs/components/MainComponents/MessageContainer.js.map +1 -1
- package/lib/commonjs/components/MainComponents/RoomList.js +8 -18
- package/lib/commonjs/components/MainComponents/RoomList.js.map +1 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +17 -14
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +131 -17
- package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/ChatRoomItem.js +6 -1
- package/lib/commonjs/components/RoomComponents/ChatRoomItem.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/ActiveCallBar.js +203 -0
- package/lib/commonjs/components/VideoCalls/ActiveCallBar.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/CallButtons.js +3 -1
- package/lib/commonjs/components/VideoCalls/CallButtons.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js +123 -129
- package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/VideoCallSession.js +212 -414
- package/lib/commonjs/components/VideoCalls/VideoCallSession.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/callActions.js +61 -0
- package/lib/commonjs/components/VideoCalls/callActions.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/callSessionController.js +461 -0
- package/lib/commonjs/components/VideoCalls/callSessionController.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/callUiStore.js +91 -0
- package/lib/commonjs/components/VideoCalls/callUiStore.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/livekitRuntime.js +1 -0
- package/lib/commonjs/components/VideoCalls/livekitRuntime.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/useDraggable.js +59 -0
- package/lib/commonjs/components/VideoCalls/useDraggable.js.map +1 -0
- package/lib/commonjs/helpers/createRoomFromApi.js +4 -0
- package/lib/commonjs/helpers/createRoomFromApi.js.map +1 -1
- package/lib/commonjs/helpers/incomingCallFromPush.js +1 -1
- package/lib/commonjs/helpers/incomingCallFromPush.js.map +1 -1
- package/lib/commonjs/helpers/roomUserCount.js +7 -1
- package/lib/commonjs/helpers/roomUserCount.js.map +1 -1
- package/lib/commonjs/helpers/uniqueMembers.js +36 -0
- package/lib/commonjs/helpers/uniqueMembers.js.map +1 -0
- package/lib/commonjs/hooks/useLeaveRoom.js +52 -0
- package/lib/commonjs/hooks/useLeaveRoom.js.map +1 -0
- package/lib/commonjs/i18n/strings.js +24 -0
- package/lib/commonjs/i18n/strings.js.map +1 -1
- package/lib/commonjs/networking/api-requests/rooms.api.js +36 -1
- package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/commonjs/networking/callTokenStanza.js +47 -3
- package/lib/commonjs/networking/callTokenStanza.js.map +1 -1
- package/lib/commonjs/networking/xmppClient.js +9 -2
- package/lib/commonjs/networking/xmppClient.js.map +1 -1
- package/lib/commonjs/version.js +1 -1
- package/lib/module/components/Header/HeaderRoomList.js +73 -70
- package/lib/module/components/Header/HeaderRoomList.js.map +1 -1
- package/lib/module/components/MainComponents/ChatHeader.js +15 -13
- package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/module/components/MainComponents/MessageContainer.js +10 -2
- package/lib/module/components/MainComponents/MessageContainer.js.map +1 -1
- package/lib/module/components/MainComponents/RoomList.js +8 -18
- package/lib/module/components/MainComponents/RoomList.js.map +1 -1
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +18 -15
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +132 -18
- package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
- package/lib/module/components/RoomComponents/ChatRoomItem.js +6 -1
- package/lib/module/components/RoomComponents/ChatRoomItem.js.map +1 -1
- package/lib/module/components/VideoCalls/ActiveCallBar.js +195 -0
- package/lib/module/components/VideoCalls/ActiveCallBar.js.map +1 -0
- package/lib/module/components/VideoCalls/CallButtons.js +3 -1
- package/lib/module/components/VideoCalls/CallButtons.js.map +1 -1
- package/lib/module/components/VideoCalls/VideoCallOverlay.js +125 -131
- package/lib/module/components/VideoCalls/VideoCallOverlay.js.map +1 -1
- package/lib/module/components/VideoCalls/VideoCallSession.js +213 -417
- package/lib/module/components/VideoCalls/VideoCallSession.js.map +1 -1
- package/lib/module/components/VideoCalls/callActions.js +55 -0
- package/lib/module/components/VideoCalls/callActions.js.map +1 -0
- package/lib/module/components/VideoCalls/callSessionController.js +450 -0
- package/lib/module/components/VideoCalls/callSessionController.js.map +1 -0
- package/lib/module/components/VideoCalls/callUiStore.js +84 -0
- package/lib/module/components/VideoCalls/callUiStore.js.map +1 -0
- package/lib/module/components/VideoCalls/livekitRuntime.js +1 -0
- package/lib/module/components/VideoCalls/livekitRuntime.js.map +1 -1
- package/lib/module/components/VideoCalls/useDraggable.js +55 -0
- package/lib/module/components/VideoCalls/useDraggable.js.map +1 -0
- package/lib/module/helpers/createRoomFromApi.js +4 -0
- package/lib/module/helpers/createRoomFromApi.js.map +1 -1
- package/lib/module/helpers/incomingCallFromPush.js +1 -1
- package/lib/module/helpers/incomingCallFromPush.js.map +1 -1
- package/lib/module/helpers/roomUserCount.js +7 -1
- package/lib/module/helpers/roomUserCount.js.map +1 -1
- package/lib/module/helpers/uniqueMembers.js +31 -0
- package/lib/module/helpers/uniqueMembers.js.map +1 -0
- package/lib/module/hooks/useLeaveRoom.js +48 -0
- package/lib/module/hooks/useLeaveRoom.js.map +1 -0
- package/lib/module/i18n/strings.js +24 -0
- package/lib/module/i18n/strings.js.map +1 -1
- package/lib/module/networking/api-requests/rooms.api.js +35 -1
- package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/module/networking/callTokenStanza.js +47 -3
- package/lib/module/networking/callTokenStanza.js.map +1 -1
- package/lib/module/networking/xmppClient.js +8 -1
- package/lib/module/networking/xmppClient.js.map +1 -1
- package/lib/module/version.js +1 -1
- package/lib/typescript/components/Header/HeaderRoomList.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MessageContainer.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/RoomList.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/ChatRoomItem.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/ActiveCallBar.d.ts +21 -0
- package/lib/typescript/components/VideoCalls/ActiveCallBar.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/CallButtons.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts +0 -6
- package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts +15 -22
- package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/callActions.d.ts +15 -0
- package/lib/typescript/components/VideoCalls/callActions.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/callSessionController.d.ts +63 -0
- package/lib/typescript/components/VideoCalls/callSessionController.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/callUiStore.d.ts +24 -0
- package/lib/typescript/components/VideoCalls/callUiStore.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts +2 -0
- package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/useDraggable.d.ts +19 -0
- package/lib/typescript/components/VideoCalls/useDraggable.d.ts.map +1 -0
- package/lib/typescript/helpers/createRoomFromApi.d.ts.map +1 -1
- package/lib/typescript/helpers/roomUserCount.d.ts +1 -0
- package/lib/typescript/helpers/roomUserCount.d.ts.map +1 -1
- package/lib/typescript/helpers/uniqueMembers.d.ts +11 -0
- package/lib/typescript/helpers/uniqueMembers.d.ts.map +1 -0
- package/lib/typescript/hooks/useLeaveRoom.d.ts +9 -0
- package/lib/typescript/hooks/useLeaveRoom.d.ts.map +1 -0
- package/lib/typescript/i18n/strings.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/rooms.api.d.ts +8 -0
- package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
- package/lib/typescript/networking/callTokenStanza.d.ts.map +1 -1
- package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
- package/lib/typescript/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/Header/HeaderRoomList.tsx +66 -61
- package/src/components/MainComponents/ChatHeader.tsx +13 -8
- package/src/components/MainComponents/MessageContainer.tsx +14 -1
- package/src/components/MainComponents/RoomList.tsx +8 -14
- package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +16 -13
- package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +167 -36
- package/src/components/RoomComponents/ChatRoomItem.tsx +8 -1
- package/src/components/VideoCalls/ActiveCallBar.tsx +176 -0
- package/src/components/VideoCalls/CallButtons.tsx +5 -1
- package/src/components/VideoCalls/VideoCallOverlay.tsx +153 -183
- package/src/components/VideoCalls/VideoCallSession.tsx +227 -439
- package/src/components/VideoCalls/callActions.ts +63 -0
- package/src/components/VideoCalls/callSessionController.ts +439 -0
- package/src/components/VideoCalls/callUiStore.ts +76 -0
- package/src/components/VideoCalls/livekitRuntime.ts +3 -0
- package/src/components/VideoCalls/useDraggable.ts +46 -0
- package/src/helpers/createRoomFromApi.tsx +2 -0
- package/src/helpers/incomingCallFromPush.ts +2 -2
- package/src/helpers/roomUserCount.ts +6 -2
- package/src/helpers/uniqueMembers.ts +26 -0
- package/src/hooks/useLeaveRoom.ts +42 -0
- package/src/i18n/strings.ts +24 -0
- package/src/networking/api-requests/rooms.api.ts +28 -1
- package/src/networking/callTokenStanza.ts +46 -6
- package/src/networking/xmppClient.ts +10 -1
- package/src/version.ts +1 -1
|
@@ -2,7 +2,7 @@ import { IRoom } from '../types/types';
|
|
|
2
2
|
|
|
3
3
|
type CountSource =
|
|
4
4
|
| Pick<IRoom, 'members' | 'usersCnt'>
|
|
5
|
-
| { members?: unknown; usersCnt?: unknown }
|
|
5
|
+
| { members?: unknown; usersCnt?: unknown; type?: unknown }
|
|
6
6
|
| null
|
|
7
7
|
| undefined;
|
|
8
8
|
|
|
@@ -17,11 +17,15 @@ export const getRoomUserCount = (room: CountSource): number => {
|
|
|
17
17
|
typeof room?.usersCnt === 'number' && Number.isFinite(room.usersCnt)
|
|
18
18
|
? room.usersCnt
|
|
19
19
|
: 0;
|
|
20
|
-
|
|
20
|
+
const total = Math.max(cnt, members);
|
|
21
|
+
// A private chat is two people by definition; the server's usersCnt has
|
|
22
|
+
// been seen to drift above that (3 for a 1:1 room).
|
|
23
|
+
return (room as { type?: unknown })?.type === 'private' ? Math.min(total, 2) : total;
|
|
21
24
|
};
|
|
22
25
|
|
|
23
26
|
/** True when the room is known to have more users than members loaded. */
|
|
24
27
|
export const isRoomMembersTruncated = (room: CountSource): boolean => {
|
|
28
|
+
if ((room as { type?: unknown })?.type === 'private') {return false;}
|
|
25
29
|
const members = Array.isArray(room?.members) ? room.members.length : 0;
|
|
26
30
|
const cnt =
|
|
27
31
|
typeof room?.usersCnt === 'number' && Number.isFinite(room.usersCnt)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop repeated entries from a room's member list, keeping the first.
|
|
3
|
+
*
|
|
4
|
+
* The same person could appear twice in a 1:1 chat ("Members: 3", four
|
|
5
|
+
* rows): the list came back from the server with repeats after the private
|
|
6
|
+
* chat was requested again for an existing room. Members are keyed by
|
|
7
|
+
* whatever identity the entry carries: xmppUsername, then _id, then the
|
|
8
|
+
* JID's localpart.
|
|
9
|
+
*/
|
|
10
|
+
export const uniqueMembers = <T,>(members: T[] | undefined | null): T[] => {
|
|
11
|
+
if (!Array.isArray(members)) {return [];}
|
|
12
|
+
const seen = new Set<string>();
|
|
13
|
+
const out: T[] = [];
|
|
14
|
+
for (const member of members) {
|
|
15
|
+
const m = member as any;
|
|
16
|
+
const key = String(
|
|
17
|
+
m?.xmppUsername || m?._id || String(m?.jid || '').split('@')[0] || ''
|
|
18
|
+
).trim();
|
|
19
|
+
if (key) {
|
|
20
|
+
if (seen.has(key)) {continue;}
|
|
21
|
+
seen.add(key);
|
|
22
|
+
}
|
|
23
|
+
out.push(member);
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useDispatch } from 'react-redux';
|
|
3
|
+
import { deleteRoom } from '../roomStore/roomsSlice';
|
|
4
|
+
import { leaveRoom } from '../networking/api-requests/rooms.api';
|
|
5
|
+
import { useXmppClient } from '../context/xmppProvider';
|
|
6
|
+
|
|
7
|
+
/** Rooms with a leave request in flight: a second tap waits for the first. */
|
|
8
|
+
const inFlight = new Set<string>();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Leave a chat, shared by the room-list swipe, the chat header and the chat
|
|
12
|
+
* profile. The server goes first: if it refuses, the chat stays where it is
|
|
13
|
+
* (dropping it locally would only have it come back on the next /chats/my).
|
|
14
|
+
* Then the unavailable presence to the MUC and the room out of the store.
|
|
15
|
+
* Resolves false on failure, so the caller can say so in its own way.
|
|
16
|
+
*/
|
|
17
|
+
export const useLeaveRoom = () => {
|
|
18
|
+
const dispatch = useDispatch();
|
|
19
|
+
const { client } = useXmppClient();
|
|
20
|
+
return useCallback(
|
|
21
|
+
async (jid: string): Promise<boolean> => {
|
|
22
|
+
if (!jid || inFlight.has(jid)) {return true;}
|
|
23
|
+
inFlight.add(jid);
|
|
24
|
+
try {
|
|
25
|
+
await leaveRoom(jid.split('@')[0]);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.warn('[ethora-rn] leave failed', error);
|
|
28
|
+
return false;
|
|
29
|
+
} finally {
|
|
30
|
+
inFlight.delete(jid);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
client?.leaveTheRoomStanza?.(jid);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('Failed to leave the room:', error);
|
|
36
|
+
}
|
|
37
|
+
dispatch(deleteRoom({ jid }));
|
|
38
|
+
return true;
|
|
39
|
+
},
|
|
40
|
+
[client, dispatch]
|
|
41
|
+
);
|
|
42
|
+
};
|
package/src/i18n/strings.ts
CHANGED
|
@@ -115,6 +115,7 @@ const en: UiStringTable = {
|
|
|
115
115
|
'call.action.stopCamera': 'Turn camera off',
|
|
116
116
|
'call.action.startCamera': 'Turn camera on',
|
|
117
117
|
'call.action.switchCamera': 'Switch camera',
|
|
118
|
+
'call.action.swapVideo': 'Swap videos',
|
|
118
119
|
'call.action.speakerOn': 'Turn speaker on',
|
|
119
120
|
'call.action.speakerOff': 'Turn speaker off',
|
|
120
121
|
'call.action.startAudioCall': 'Start audio call',
|
|
@@ -128,6 +129,8 @@ const en: UiStringTable = {
|
|
|
128
129
|
'call.error.deviceInUse': 'Your camera or microphone is already in use by another app.',
|
|
129
130
|
'call.error.deviceGeneric': 'Could not access your camera or microphone.',
|
|
130
131
|
'call.error.noAnswerTimeout': 'No answer, the call timed out',
|
|
132
|
+
'call.peerMuted': '{name} is muted',
|
|
133
|
+
'call.selfMuted': 'Your microphone is muted',
|
|
131
134
|
'action.send': 'Send',
|
|
132
135
|
'action.cancel': 'Cancel',
|
|
133
136
|
'action.save': 'Save',
|
|
@@ -138,6 +141,7 @@ const en: UiStringTable = {
|
|
|
138
141
|
'action.mute': 'Mute',
|
|
139
142
|
'action.unmute': 'Unmute',
|
|
140
143
|
'toast.muteFailed': 'Could not update notifications for this chat',
|
|
144
|
+
'toast.leaveFailed': 'Could not leave this chat',
|
|
141
145
|
'action.create': 'Create',
|
|
142
146
|
'action.submit': 'Submit',
|
|
143
147
|
'action.translate': 'Translate',
|
|
@@ -492,6 +496,7 @@ const fr: UiStringTable = {
|
|
|
492
496
|
'call.action.stopCamera': 'Désactiver la caméra',
|
|
493
497
|
'call.action.startCamera': 'Activer la caméra',
|
|
494
498
|
'call.action.switchCamera': 'Changer de caméra',
|
|
499
|
+
'call.action.swapVideo': 'Inverser les vidéos',
|
|
495
500
|
'call.action.speakerOn': 'Activer le haut-parleur',
|
|
496
501
|
'call.action.speakerOff': 'Désactiver le haut-parleur',
|
|
497
502
|
'call.action.startAudioCall': 'Démarrer un appel audio',
|
|
@@ -505,6 +510,8 @@ const fr: UiStringTable = {
|
|
|
505
510
|
'call.error.deviceInUse': 'Votre caméra ou microphone est déjà utilisé par une autre application.',
|
|
506
511
|
'call.error.deviceGeneric': 'Impossible d\'accéder à votre caméra ou microphone.',
|
|
507
512
|
'call.error.noAnswerTimeout': 'Pas de réponse, l\'appel a expiré',
|
|
513
|
+
'call.peerMuted': '{name} a coupé son micro',
|
|
514
|
+
'call.selfMuted': 'Votre micro est coupé',
|
|
508
515
|
'action.send': 'Envoyer',
|
|
509
516
|
'action.cancel': 'Annuler',
|
|
510
517
|
'action.save': 'Enregistrer',
|
|
@@ -515,6 +522,7 @@ const fr: UiStringTable = {
|
|
|
515
522
|
'action.mute': 'Muet',
|
|
516
523
|
'action.unmute': 'Réactiver',
|
|
517
524
|
'toast.muteFailed': 'Impossible de modifier les notifications de ce chat',
|
|
525
|
+
'toast.leaveFailed': 'Impossible de quitter ce chat',
|
|
518
526
|
'action.create': 'Créer',
|
|
519
527
|
'action.submit': 'Soumettre',
|
|
520
528
|
'action.translate': 'Traduire',
|
|
@@ -869,6 +877,7 @@ const es: UiStringTable = {
|
|
|
869
877
|
'call.action.stopCamera': 'Apagar la cámara',
|
|
870
878
|
'call.action.startCamera': 'Encender la cámara',
|
|
871
879
|
'call.action.switchCamera': 'Cambiar de cámara',
|
|
880
|
+
'call.action.swapVideo': 'Intercambiar vídeos',
|
|
872
881
|
'call.action.speakerOn': 'Activar el altavoz',
|
|
873
882
|
'call.action.speakerOff': 'Desactivar el altavoz',
|
|
874
883
|
'call.action.startAudioCall': 'Iniciar llamada de audio',
|
|
@@ -882,6 +891,8 @@ const es: UiStringTable = {
|
|
|
882
891
|
'call.error.deviceInUse': 'Tu cámara o micrófono ya está siendo usado por otra aplicación.',
|
|
883
892
|
'call.error.deviceGeneric': 'No se pudo acceder a tu cámara o micrófono.',
|
|
884
893
|
'call.error.noAnswerTimeout': 'Sin respuesta, la llamada expiró',
|
|
894
|
+
'call.peerMuted': '{name} tiene el micrófono silenciado',
|
|
895
|
+
'call.selfMuted': 'Tu micrófono está silenciado',
|
|
885
896
|
'action.send': 'Enviar',
|
|
886
897
|
'action.cancel': 'Cancelar',
|
|
887
898
|
'action.save': 'Guardar',
|
|
@@ -892,6 +903,7 @@ const es: UiStringTable = {
|
|
|
892
903
|
'action.mute': 'Silenciar',
|
|
893
904
|
'action.unmute': 'Activar',
|
|
894
905
|
'toast.muteFailed': 'No se pudieron cambiar las notificaciones de este chat',
|
|
906
|
+
'toast.leaveFailed': 'No se pudo salir de este chat',
|
|
895
907
|
'action.create': 'Crear',
|
|
896
908
|
'action.submit': 'Enviar',
|
|
897
909
|
'action.translate': 'Traducir',
|
|
@@ -1246,6 +1258,7 @@ const pt: UiStringTable = {
|
|
|
1246
1258
|
'call.action.stopCamera': 'Desligar a câmara',
|
|
1247
1259
|
'call.action.startCamera': 'Ligar a câmara',
|
|
1248
1260
|
'call.action.switchCamera': 'Mudar de câmara',
|
|
1261
|
+
'call.action.swapVideo': 'Trocar vídeos',
|
|
1249
1262
|
'call.action.speakerOn': 'Ligar o altifalante',
|
|
1250
1263
|
'call.action.speakerOff': 'Desligar o altifalante',
|
|
1251
1264
|
'call.action.startAudioCall': 'Iniciar chamada de áudio',
|
|
@@ -1259,6 +1272,8 @@ const pt: UiStringTable = {
|
|
|
1259
1272
|
'call.error.deviceInUse': 'A sua câmara ou microfone já está a ser utilizado por outra aplicação.',
|
|
1260
1273
|
'call.error.deviceGeneric': 'Não foi possível aceder à câmara ou microfone.',
|
|
1261
1274
|
'call.error.noAnswerTimeout': 'Sem resposta, a chamada expirou',
|
|
1275
|
+
'call.peerMuted': '{name} está com o microfone desativado',
|
|
1276
|
+
'call.selfMuted': 'Seu microfone está desativado',
|
|
1262
1277
|
'action.send': 'Enviar',
|
|
1263
1278
|
'action.cancel': 'Cancelar',
|
|
1264
1279
|
'action.save': 'Guardar',
|
|
@@ -1269,6 +1284,7 @@ const pt: UiStringTable = {
|
|
|
1269
1284
|
'action.mute': 'Silenciar',
|
|
1270
1285
|
'action.unmute': 'Ativar som',
|
|
1271
1286
|
'toast.muteFailed': 'Could not update notifications for this chat',
|
|
1287
|
+
'toast.leaveFailed': 'Não foi possível sair deste chat',
|
|
1272
1288
|
'action.create': 'Criar',
|
|
1273
1289
|
'action.submit': 'Enviar',
|
|
1274
1290
|
'action.translate': 'Traduzir',
|
|
@@ -1623,6 +1639,7 @@ const ht: UiStringTable = {
|
|
|
1623
1639
|
'call.action.stopCamera': 'Etenn kamera a',
|
|
1624
1640
|
'call.action.startCamera': 'Limen kamera a',
|
|
1625
1641
|
'call.action.switchCamera': 'Chanje kamera',
|
|
1642
|
+
'call.action.swapVideo': 'Chanje videyo yo',
|
|
1626
1643
|
'call.action.speakerOn': 'Limen opalè a',
|
|
1627
1644
|
'call.action.speakerOff': 'Etenn opalè a',
|
|
1628
1645
|
'call.action.startAudioCall': 'Kòmanse yon apèl odyo',
|
|
@@ -1636,6 +1653,8 @@ const ht: UiStringTable = {
|
|
|
1636
1653
|
'call.error.deviceInUse': 'Kamera oswa mikwofòn ou deja ap itilize pa yon lòt app.',
|
|
1637
1654
|
'call.error.deviceGeneric': 'Nou pa t kapab jwenn aksè nan kamera oswa mikwofòn ou.',
|
|
1638
1655
|
'call.error.noAnswerTimeout': 'Pa gen repons, apèl la fini',
|
|
1656
|
+
'call.peerMuted': '{name} koupe mikwo li',
|
|
1657
|
+
'call.selfMuted': 'Mikwo ou koupe',
|
|
1639
1658
|
'action.send': 'Voye',
|
|
1640
1659
|
'action.cancel': 'Anile',
|
|
1641
1660
|
'action.save': 'Anrejistre',
|
|
@@ -1646,6 +1665,7 @@ const ht: UiStringTable = {
|
|
|
1646
1665
|
'action.mute': 'Mete an silans',
|
|
1647
1666
|
'action.unmute': 'Retire silans',
|
|
1648
1667
|
'toast.muteFailed': 'Could not update notifications for this chat',
|
|
1668
|
+
'toast.leaveFailed': 'Pa t kapab kite chat sa a',
|
|
1649
1669
|
'action.create': 'Kreye',
|
|
1650
1670
|
'action.submit': 'Soumèt',
|
|
1651
1671
|
'action.translate': 'Tradui',
|
|
@@ -2000,6 +2020,7 @@ const zh: UiStringTable = {
|
|
|
2000
2020
|
'call.action.stopCamera': '关闭摄像头',
|
|
2001
2021
|
'call.action.startCamera': '打开摄像头',
|
|
2002
2022
|
'call.action.switchCamera': '切换摄像头',
|
|
2023
|
+
'call.action.swapVideo': '切换画面',
|
|
2003
2024
|
'call.action.speakerOn': '打开扬声器',
|
|
2004
2025
|
'call.action.speakerOff': '关闭扬声器',
|
|
2005
2026
|
'call.action.startAudioCall': '发起语音通话',
|
|
@@ -2013,6 +2034,8 @@ const zh: UiStringTable = {
|
|
|
2013
2034
|
'call.error.deviceInUse': '您的摄像头或麦克风正被其他应用占用。',
|
|
2014
2035
|
'call.error.deviceGeneric': '无法访问您的摄像头或麦克风。',
|
|
2015
2036
|
'call.error.noAnswerTimeout': '无人接听,通话已超时',
|
|
2037
|
+
'call.peerMuted': '{name} 已静音',
|
|
2038
|
+
'call.selfMuted': '你的麦克风已静音',
|
|
2016
2039
|
'action.send': '发送',
|
|
2017
2040
|
'action.cancel': '取消',
|
|
2018
2041
|
'action.save': '保存',
|
|
@@ -2023,6 +2046,7 @@ const zh: UiStringTable = {
|
|
|
2023
2046
|
'action.mute': '静音',
|
|
2024
2047
|
'action.unmute': '取消静音',
|
|
2025
2048
|
'toast.muteFailed': 'Could not update notifications for this chat',
|
|
2049
|
+
'toast.leaveFailed': '无法退出此聊天',
|
|
2026
2050
|
'action.create': '创建',
|
|
2027
2051
|
'action.submit': '提交',
|
|
2028
2052
|
'action.translate': '翻译',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ApiRoom, DeleteRoomMember, PostAddRoomMember, PostReportRoom, PostRoom, RoomMember } from '../../types/models/room.model';
|
|
2
|
+
import { uniqueMembers } from '../../helpers/uniqueMembers';
|
|
2
3
|
import { store } from '../../roomStore';
|
|
3
4
|
import { addRoom, mergeUsersSet, updateRoom } from '../../roomStore/roomsSlice';
|
|
4
5
|
import { IRoom } from '../../types/types';
|
|
@@ -118,7 +119,7 @@ export function ownAppId(): string {
|
|
|
118
119
|
|
|
119
120
|
const toRoomMembers = (members: any[] | undefined): RoomMember[] => {
|
|
120
121
|
const known = (store.getState().rooms?.usersSet || {}) as Record<string, RoomMember>;
|
|
121
|
-
return (members
|
|
122
|
+
return uniqueMembers(members).map((m: any) => ({
|
|
122
123
|
firstName: m.firstName,
|
|
123
124
|
lastName: m.lastName,
|
|
124
125
|
profileImage:
|
|
@@ -495,6 +496,32 @@ export async function deleteRoomMember(data: DeleteRoomMember) {
|
|
|
495
496
|
}
|
|
496
497
|
}
|
|
497
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Take the signed-in user out of a chat on the server (self-removal via the
|
|
501
|
+
* same endpoint that removes other members). The unavailable presence alone
|
|
502
|
+
* only leaves the MUC for this session: the backend keeps the user in
|
|
503
|
+
* `members` and the next /chats/my brings the chat back. A 404 means the
|
|
504
|
+
* server already has us out, which is what we wanted.
|
|
505
|
+
*/
|
|
506
|
+
export async function leaveRoom(chatName: string): Promise<void> {
|
|
507
|
+
const me = (store.getState().chatSettingStore?.user?.xmppUsername || '').split('@')[0];
|
|
508
|
+
if (!chatName || !me) {
|
|
509
|
+
throw new Error('Cannot leave the chat: no chat name or user');
|
|
510
|
+
}
|
|
511
|
+
const token = store.getState().chatSettingStore.user.token || '';
|
|
512
|
+
invalidateRoomsCache();
|
|
513
|
+
try {
|
|
514
|
+
await http.delete('/v1/chats/users-access', {
|
|
515
|
+
headers: { Authorization: token },
|
|
516
|
+
data: { chatName, members: [me] },
|
|
517
|
+
});
|
|
518
|
+
} catch (error: any) {
|
|
519
|
+
if (error?.response?.status !== 404) {throw error;}
|
|
520
|
+
} finally {
|
|
521
|
+
invalidateRoomsCache();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
498
525
|
export async function deleteRoom(name: string) {
|
|
499
526
|
const token = store.getState().chatSettingStore.user.token || '';
|
|
500
527
|
|
|
@@ -57,6 +57,15 @@ const consumeKindHint = (
|
|
|
57
57
|
|
|
58
58
|
const MAX_CALL_TOKEN_AGE_MS = 5000;
|
|
59
59
|
|
|
60
|
+
// Dev-only trace of call signalling: which `<data type="call-*">` frames
|
|
61
|
+
// arrive and why a call-token was dropped. A dropped token is silent
|
|
62
|
+
// otherwise: the caller sits on "Calling..." and the callee never rings.
|
|
63
|
+
const callTrace = (message: string, extra?: Record<string, unknown>) => {
|
|
64
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
65
|
+
console.log(`[calls] ${message}`, extra ? JSON.stringify(extra) : '');
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
60
69
|
// Hermes has no global `atob` and RN ships no Buffer polyfill by default,
|
|
61
70
|
// so the JWT payload is decoded here by hand. Only used on the ~500-byte
|
|
62
71
|
// middle segment of a call token, so a table-driven loop is plenty fast
|
|
@@ -215,7 +224,7 @@ const isFreshCallToken = (stanza: Element): boolean => {
|
|
|
215
224
|
const resolveRoomByStanzaRoom = (roomAttr: string) => {
|
|
216
225
|
const rooms = (store.getState().rooms.rooms || {}) as Record<
|
|
217
226
|
string,
|
|
218
|
-
{ jid?: string; name?: string; type?: string; usersCnt?: number }
|
|
227
|
+
{ jid?: string; name?: string; title?: string; type?: string; usersCnt?: number }
|
|
219
228
|
>;
|
|
220
229
|
const roomEntries = Object.values(rooms);
|
|
221
230
|
|
|
@@ -338,6 +347,18 @@ export const onCallTokenMessage = (stanza: Element): boolean => {
|
|
|
338
347
|
const data = findCallData(inner);
|
|
339
348
|
if (data) {
|
|
340
349
|
const dataType = String(data.attrs?.type || '').toLowerCase();
|
|
350
|
+
const traceStanzaId = inner.getChild?.('stanza-id')?.attrs?.id;
|
|
351
|
+
const traceTs = extractNumericTimestamp(traceStanzaId);
|
|
352
|
+
callTrace(`received ${dataType}`, {
|
|
353
|
+
from: stanza.attrs?.from,
|
|
354
|
+
type: stanza.attrs?.type,
|
|
355
|
+
wrapped: inner !== stanza,
|
|
356
|
+
room: data.attrs?.room,
|
|
357
|
+
callId: data.attrs?.callId || data.attrs?.callid,
|
|
358
|
+
hasToken: !!data.attrs?.token,
|
|
359
|
+
stanzaId: traceStanzaId,
|
|
360
|
+
ageMs: traceTs ? Date.now() - traceTs : null,
|
|
361
|
+
});
|
|
341
362
|
|
|
342
363
|
if (dataType === 'call-state') {
|
|
343
364
|
// Surface the raw call-state attrs so we can confirm what the server
|
|
@@ -404,9 +425,11 @@ export const onCallTokenMessage = (stanza: Element): boolean => {
|
|
|
404
425
|
const videoCallsConfig =
|
|
405
426
|
store.getState().chatSettingStore.config?.videoCalls;
|
|
406
427
|
if (videoCallsConfig?.enabled !== true) {
|
|
428
|
+
callTrace('drop call-token: calls disabled');
|
|
407
429
|
return true;
|
|
408
430
|
}
|
|
409
431
|
if (!isFreshCallToken(inner)) {
|
|
432
|
+
callTrace('drop call-token: not fresh');
|
|
410
433
|
return true;
|
|
411
434
|
}
|
|
412
435
|
|
|
@@ -423,6 +446,7 @@ export const onCallTokenMessage = (stanza: Element): boolean => {
|
|
|
423
446
|
const kind: 'audio' | 'video' =
|
|
424
447
|
hintedKind || (stanzaKind === 'audio' ? 'audio' : 'video');
|
|
425
448
|
if (!token || !stanzaRoom) {
|
|
449
|
+
callTrace('drop call-token: no token or room');
|
|
426
450
|
return true;
|
|
427
451
|
}
|
|
428
452
|
|
|
@@ -436,9 +460,11 @@ export const onCallTokenMessage = (stanza: Element): boolean => {
|
|
|
436
460
|
resolvedRoom.type !== 'private' &&
|
|
437
461
|
!isOneToOneLikeRoom
|
|
438
462
|
) {
|
|
463
|
+
callTrace('drop call-token: room type', { type: resolvedRoom.type, usersCnt });
|
|
439
464
|
return true;
|
|
440
465
|
}
|
|
441
466
|
if (resolvedRoom?.type === 'private' && !isPrivateAllowed) {
|
|
467
|
+
callTrace('drop call-token: private not allowed');
|
|
442
468
|
return true;
|
|
443
469
|
}
|
|
444
470
|
if (
|
|
@@ -446,6 +472,7 @@ export const onCallTokenMessage = (stanza: Element): boolean => {
|
|
|
446
472
|
!stanzaRoom.includes('@') &&
|
|
447
473
|
!isOneToOneLikeRoom
|
|
448
474
|
) {
|
|
475
|
+
callTrace('drop call-token: room unknown', { stanzaRoom });
|
|
449
476
|
return true;
|
|
450
477
|
}
|
|
451
478
|
|
|
@@ -457,17 +484,25 @@ export const onCallTokenMessage = (stanza: Element): boolean => {
|
|
|
457
484
|
// title (which for stale / deleted-peer chats reads "deleted").
|
|
458
485
|
const jwtPayload = decodeJwtPayload(token);
|
|
459
486
|
const callerName = extractCallerDisplayName(jwtPayload);
|
|
460
|
-
|
|
487
|
+
// `title` is the display name; on new-arch private rooms `name` is the
|
|
488
|
+
// bare room id ("${appId}_<a>-${appId}_<b>"), never something to show.
|
|
489
|
+
const resolvedTitle = String(
|
|
490
|
+
resolvedRoom?.title || resolvedRoom?.name || ''
|
|
491
|
+
).trim();
|
|
461
492
|
const titleLooksBad =
|
|
462
493
|
!resolvedTitle ||
|
|
494
|
+
resolvedTitle === stanzaRoom ||
|
|
463
495
|
['deleted', 'deleted user', 'unknown', 'null'].includes(
|
|
464
496
|
resolvedTitle.toLowerCase()
|
|
465
497
|
);
|
|
498
|
+
// Never fall back to the room id: an empty name renders as
|
|
499
|
+
// "Unknown caller" on the ring screen.
|
|
466
500
|
const incomingRoomName =
|
|
467
|
-
(
|
|
468
|
-
|
|
469
|
-
stanzaRoom;
|
|
470
|
-
|
|
501
|
+
(titleLooksBad ? callerName : resolvedTitle) || callerName || '';
|
|
502
|
+
if (!roomJid) {
|
|
503
|
+
callTrace('drop call-token: no room jid', { stanzaRoom });
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
471
506
|
|
|
472
507
|
const callId = extractCallId(data, jwtPayload);
|
|
473
508
|
const selfXmpp =
|
|
@@ -481,6 +516,11 @@ export const onCallTokenMessage = (stanza: Element): boolean => {
|
|
|
481
516
|
callState.roomName === incomingRoomName ||
|
|
482
517
|
callState.roomBareName === stanzaRoom);
|
|
483
518
|
|
|
519
|
+
callTrace('accept call-token', {
|
|
520
|
+
outgoing: isOutgoingRoomMatch,
|
|
521
|
+
roomJid,
|
|
522
|
+
callId,
|
|
523
|
+
});
|
|
484
524
|
if (isOutgoingRoomMatch) {
|
|
485
525
|
store.dispatch(
|
|
486
526
|
setOutgoingCallToken({ roomJid, token, callId: callId || null })
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import xmpp, { Client } from '@xmpp/client';
|
|
1
|
+
import xmpp, { Client, xml } from '@xmpp/client';
|
|
2
2
|
import { walletToUsername } from '../helpers/walletUsername';
|
|
3
3
|
import { IMessage, xmppSettingsInterface } from '../types/types';
|
|
4
4
|
|
|
@@ -765,6 +765,15 @@ export class XmppClient {
|
|
|
765
765
|
this.authRecoveryFailures = 0;
|
|
766
766
|
this.authRejectionsSinceOnline = 0;
|
|
767
767
|
this.reconnectAttempts = 0;
|
|
768
|
+
// Initial presence, as the web client sends on 'online'. Without it
|
|
769
|
+
// the session is never "available" to ejabberd, which then holds
|
|
770
|
+
// back every `type="chat"` message addressed to the bare JID: the
|
|
771
|
+
// call-invite and the call-token never arrived, so the phone neither
|
|
772
|
+
// rang nor joined its own outgoing call. Room traffic was unaffected
|
|
773
|
+
// (MUC joins and mucsub don't need it), which hid the problem.
|
|
774
|
+
Promise.resolve()
|
|
775
|
+
.then(() => this.client.send(xml('presence')))
|
|
776
|
+
.catch((err: unknown) => console.warn('initial presence failed', err));
|
|
768
777
|
this.sendAllPresencesAndMarkReady();
|
|
769
778
|
try {
|
|
770
779
|
devPushLog(
|
package/src/version.ts
CHANGED