@ethora/chat-component-rn 26.10.7 → 26.10.8

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 (88) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/lib/commonjs/components/MessageBubble/Message.js +16 -6
  3. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  4. package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js +16 -2
  5. package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js.map +1 -1
  6. package/lib/commonjs/components/VideoCalls/callActions.js +32 -2
  7. package/lib/commonjs/components/VideoCalls/callActions.js.map +1 -1
  8. package/lib/commonjs/components/VideoCalls/systemCall.js +83 -0
  9. package/lib/commonjs/components/VideoCalls/systemCall.js.map +1 -0
  10. package/lib/commonjs/components/VideoCalls/useCallKeep.js +83 -66
  11. package/lib/commonjs/components/VideoCalls/useCallKeep.js.map +1 -1
  12. package/lib/commonjs/helpers/incomingCallFromPush.js +95 -21
  13. package/lib/commonjs/helpers/incomingCallFromPush.js.map +1 -1
  14. package/lib/commonjs/main.js +33 -0
  15. package/lib/commonjs/main.js.map +1 -1
  16. package/lib/commonjs/networking/callTokenStanza.js +6 -0
  17. package/lib/commonjs/networking/callTokenStanza.js.map +1 -1
  18. package/lib/commonjs/networking/xmpp/sendMessageReaction.xmpp.js +22 -6
  19. package/lib/commonjs/networking/xmpp/sendMessageReaction.xmpp.js.map +1 -1
  20. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  21. package/lib/commonjs/push/sealedPush.js +13 -2
  22. package/lib/commonjs/push/sealedPush.js.map +1 -1
  23. package/lib/commonjs/push/sealedPushTask.js +43 -17
  24. package/lib/commonjs/push/sealedPushTask.js.map +1 -1
  25. package/lib/commonjs/version.js +1 -1
  26. package/lib/module/components/MessageBubble/Message.js +16 -6
  27. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  28. package/lib/module/components/VideoCalls/VideoCallOverlay.js +17 -3
  29. package/lib/module/components/VideoCalls/VideoCallOverlay.js.map +1 -1
  30. package/lib/module/components/VideoCalls/callActions.js +29 -0
  31. package/lib/module/components/VideoCalls/callActions.js.map +1 -1
  32. package/lib/module/components/VideoCalls/systemCall.js +73 -0
  33. package/lib/module/components/VideoCalls/systemCall.js.map +1 -0
  34. package/lib/module/components/VideoCalls/useCallKeep.js +77 -64
  35. package/lib/module/components/VideoCalls/useCallKeep.js.map +1 -1
  36. package/lib/module/helpers/incomingCallFromPush.js +92 -22
  37. package/lib/module/helpers/incomingCallFromPush.js.map +1 -1
  38. package/lib/module/main.js +3 -0
  39. package/lib/module/main.js.map +1 -1
  40. package/lib/module/networking/callTokenStanza.js +6 -0
  41. package/lib/module/networking/callTokenStanza.js.map +1 -1
  42. package/lib/module/networking/xmpp/sendMessageReaction.xmpp.js +21 -6
  43. package/lib/module/networking/xmpp/sendMessageReaction.xmpp.js.map +1 -1
  44. package/lib/module/networking/xmppClient.js.map +1 -1
  45. package/lib/module/push/sealedPush.js +11 -1
  46. package/lib/module/push/sealedPush.js.map +1 -1
  47. package/lib/module/push/sealedPushTask.js +42 -17
  48. package/lib/module/push/sealedPushTask.js.map +1 -1
  49. package/lib/module/version.js +1 -1
  50. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  51. package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts.map +1 -1
  52. package/lib/typescript/components/VideoCalls/callActions.d.ts +6 -0
  53. package/lib/typescript/components/VideoCalls/callActions.d.ts.map +1 -1
  54. package/lib/typescript/components/VideoCalls/systemCall.d.ts +25 -0
  55. package/lib/typescript/components/VideoCalls/systemCall.d.ts.map +1 -0
  56. package/lib/typescript/components/VideoCalls/useCallKeep.d.ts +8 -23
  57. package/lib/typescript/components/VideoCalls/useCallKeep.d.ts.map +1 -1
  58. package/lib/typescript/helpers/incomingCallFromPush.d.ts +5 -13
  59. package/lib/typescript/helpers/incomingCallFromPush.d.ts.map +1 -1
  60. package/lib/typescript/main.d.ts +5 -0
  61. package/lib/typescript/main.d.ts.map +1 -1
  62. package/lib/typescript/networking/callTokenStanza.d.ts.map +1 -1
  63. package/lib/typescript/networking/xmpp/sendMessageReaction.xmpp.d.ts +13 -1
  64. package/lib/typescript/networking/xmpp/sendMessageReaction.xmpp.d.ts.map +1 -1
  65. package/lib/typescript/networking/xmppClient.d.ts +2 -4
  66. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  67. package/lib/typescript/push/sealedPush.d.ts +6 -0
  68. package/lib/typescript/push/sealedPush.d.ts.map +1 -1
  69. package/lib/typescript/push/sealedPushTask.d.ts +18 -7
  70. package/lib/typescript/push/sealedPushTask.d.ts.map +1 -1
  71. package/lib/typescript/types/models/xmpp.model.d.ts +2 -4
  72. package/lib/typescript/types/models/xmpp.model.d.ts.map +1 -1
  73. package/lib/typescript/version.d.ts +1 -1
  74. package/package.json +1 -1
  75. package/src/components/MessageBubble/Message.tsx +16 -5
  76. package/src/components/VideoCalls/VideoCallOverlay.tsx +18 -2
  77. package/src/components/VideoCalls/callActions.ts +28 -0
  78. package/src/components/VideoCalls/systemCall.ts +109 -0
  79. package/src/components/VideoCalls/useCallKeep.ts +89 -97
  80. package/src/helpers/incomingCallFromPush.ts +97 -26
  81. package/src/main.ts +8 -0
  82. package/src/networking/callTokenStanza.ts +10 -0
  83. package/src/networking/xmpp/sendMessageReaction.xmpp.ts +38 -6
  84. package/src/networking/xmppClient.ts +2 -2
  85. package/src/push/sealedPush.ts +11 -0
  86. package/src/push/sealedPushTask.ts +61 -15
  87. package/src/types/models/xmpp.model.ts +2 -1
  88. package/src/version.ts +1 -1
@@ -1,7 +1,13 @@
1
1
  import { AppState, Platform } from 'react-native';
2
2
  import { pushLog } from '../utils/devLogger';
3
+ import { isCallPush } from '../helpers/callPush';
3
4
  import { loadPushSecretKey } from './pushKeyStore';
4
- import { openSealedPush, sealedPushEnvelopeOf, type OpenSealedPushResult } from './sealedPush';
5
+ import {
6
+ isReactionPush,
7
+ openSealedPush,
8
+ sealedPushEnvelopeOf,
9
+ type OpenSealedPushResult,
10
+ } from './sealedPush';
5
11
 
6
12
  // Android half of encrypted pushes. A sealed push is data-only, so the OS
7
13
  // shows nothing by itself: expo-notifications hands it to a background
@@ -18,12 +24,26 @@ export const SEALED_PUSH_TASK = 'ETHORA_SEALED_PUSH';
18
24
  export interface SealedPushTaskOptions {
19
25
  /** Android notification channel; the OS default channel when omitted. */
20
26
  channelId?: string;
27
+ /**
28
+ * Channel for a reaction to the user's message (a silent one, Telegram's
29
+ * way); `channelId` when omitted. Reactions carry no sound of their own
30
+ * either way.
31
+ */
32
+ reactionChannelId?: string;
21
33
  /** Shown when the box does not open (no key, another key, damaged). Default "New message". */
22
34
  placeholderTitle?: string;
23
35
  placeholderBody?: string;
36
+ /**
37
+ * An incoming-call push (data-only, high priority) while the app is not
38
+ * in the foreground; the host shows its own incoming-call notification
39
+ * (full-screen intent, Answer / Decline). Not called in the foreground,
40
+ * where the XMPP invite rings the in-app call screen.
41
+ */
42
+ onCallPush?: (data: Record<string, unknown>) => void | Promise<void>;
24
43
  }
25
44
 
26
45
  const PLACEHOLDER_TITLE = 'New message';
46
+ const PLACEHOLDER_REACTION_TITLE = 'New reaction';
27
47
 
28
48
  let registered = false;
29
49
 
@@ -49,9 +69,9 @@ const loadNotifications = (): any => {
49
69
  * itself, `data.dataString` carries the same as a JSON string on some
50
70
  * versions, and a Firebase `RemoteMessage` keeps them under `data.data`.
51
71
  */
52
- export const sealedPushDataOf = (payload: unknown): Record<string, unknown> | null => {
72
+ const pushDataCandidates = (payload: unknown): unknown[] => {
53
73
  const root = payload && typeof payload === 'object' ? (payload as Record<string, unknown>) : null;
54
- if (!root) {return null;}
74
+ if (!root) {return [];}
55
75
  const candidates: unknown[] = [root.data, root];
56
76
  const data = root.data && typeof root.data === 'object' ? (root.data as Record<string, unknown>) : null;
57
77
  if (data) {
@@ -64,30 +84,39 @@ export const sealedPushDataOf = (payload: unknown): Record<string, unknown> | nu
64
84
  }
65
85
  candidates.push(data.data);
66
86
  }
67
- for (const candidate of candidates) {
68
- if (sealedPushEnvelopeOf(candidate as Record<string, unknown>)) {
69
- return candidate as Record<string, unknown>;
70
- }
71
- }
72
- return null;
87
+ return candidates.filter((c) => c && typeof c === 'object');
73
88
  };
74
89
 
90
+ export const sealedPushDataOf = (payload: unknown): Record<string, unknown> | null =>
91
+ (pushDataCandidates(payload).find((candidate) =>
92
+ sealedPushEnvelopeOf(candidate as Record<string, unknown>)
93
+ ) as Record<string, unknown> | undefined) ?? null;
94
+
95
+ /** The call fields of an incoming-call push in the task payload, if it is one. */
96
+ export const callPushDataOf = (payload: unknown): Record<string, unknown> | null =>
97
+ (pushDataCandidates(payload).find((candidate) =>
98
+ isCallPush(candidate as Record<string, unknown>)
99
+ ) as Record<string, unknown> | undefined) ?? null;
100
+
75
101
  const notificationContent = (result: OpenSealedPushResult, options: SealedPushTaskOptions) => {
76
102
  const { envelope } = result;
77
103
  // What the tap handler (`handlePushPayload` through the host's response
78
104
  // listener) needs: the same ids a plain push carries on `data`.
105
+ const reaction = isReactionPush(envelope);
79
106
  const data = {
80
107
  jid: envelope.jid,
81
108
  msgID: envelope.msgID,
82
109
  userJid: envelope.userJid,
83
110
  sealed: true,
111
+ ...(reaction ? { type: 'reaction' } : {}),
84
112
  };
113
+ const sound = reaction ? false : 'default';
85
114
  if (!result.ok) {
86
115
  return {
87
- title: options.placeholderTitle || PLACEHOLDER_TITLE,
116
+ title: options.placeholderTitle || (reaction ? PLACEHOLDER_REACTION_TITLE : PLACEHOLDER_TITLE),
88
117
  ...(options.placeholderBody ? { body: options.placeholderBody } : {}),
89
118
  data,
90
- sound: 'default',
119
+ sound,
91
120
  };
92
121
  }
93
122
  const { content } = result;
@@ -95,14 +124,16 @@ const notificationContent = (result: OpenSealedPushResult, options: SealedPushTa
95
124
  title: content.title || content.sender || content.chat || PLACEHOLDER_TITLE,
96
125
  body: content.body,
97
126
  data: { ...data, chatJid: content.chatJid, senderJid: content.senderJid },
98
- sound: 'default',
127
+ sound,
99
128
  };
100
129
  };
101
130
 
102
131
  /**
103
132
  * Open a sealed push and show it. One notification per chat: the room JID
104
133
  * is the identifier, so a newer message replaces the older one the way the
105
- * service's collapse key does for plain pushes.
134
+ * service's collapse key does for plain pushes. A reaction has its own
135
+ * identifier per chat (and its own channel), so it never replaces the
136
+ * chat's new-message notification.
106
137
  */
107
138
  export async function presentSealedPush(
108
139
  data: Record<string, unknown>,
@@ -122,10 +153,14 @@ export async function presentSealedPush(
122
153
  console.warn(`[push] sealed push kid=${envelope.kid} not opened (${result.reason}), showing the placeholder`);
123
154
  pushLog('warn', `push: sealed push not opened (${result.reason}), showing the placeholder`);
124
155
  }
156
+ const reaction = isReactionPush(envelope);
157
+ const base = envelope.jid || envelope.msgID || '';
158
+ const identifier = base ? (reaction ? `${base}#reaction` : base) : undefined;
159
+ const channelId = (reaction && options.reactionChannelId) || options.channelId;
125
160
  await notifications.scheduleNotificationAsync({
126
- identifier: envelope.jid || envelope.msgID || undefined,
161
+ identifier,
127
162
  content: notificationContent(result, options),
128
- trigger: options.channelId ? { channelId: options.channelId } : null,
163
+ trigger: channelId ? { channelId } : null,
129
164
  });
130
165
  return result;
131
166
  }
@@ -150,6 +185,17 @@ export function registerSealedPushTask(options: SealedPushTaskOptions = {}): boo
150
185
  }
151
186
  taskManager.defineTask(SEALED_PUSH_TASK, async ({ data, error }: { data: unknown; error: unknown }) => {
152
187
  if (error) {return;}
188
+ const call = callPushDataOf(data);
189
+ if (call) {
190
+ if (AppState.currentState === 'active' || !options.onCallPush) {return;}
191
+ try {
192
+ console.log(`[push] incoming call push ${String(call.callId ?? '')}`);
193
+ await options.onCallPush(call);
194
+ } catch (e) {
195
+ pushLog('warn', 'push: showing an incoming call failed', e);
196
+ }
197
+ return;
198
+ }
153
199
  const map = sealedPushDataOf(data);
154
200
  if (!map) {return;}
155
201
  // In the foreground the room is live over XMPP; the host's
@@ -5,6 +5,7 @@
5
5
  // methods) rather than duplicating here.
6
6
 
7
7
  import { Client } from '@xmpp/client';
8
+ import type { ReactionStanzaData } from '../../networking/xmpp/sendMessageReaction.xmpp';
8
9
  import XmppClient, {
9
10
  HistoryPage,
10
11
  HistoryPageCursor,
@@ -153,7 +154,7 @@ export interface XmppClientInterface {
153
154
  messageId: string,
154
155
  roomJid: string,
155
156
  reactionsList: string[],
156
- data?: { firstName?: string; lastName?: string }
157
+ data?: ReactionStanzaData
157
158
  ): void;
158
159
  sendTextMessageWithTranslateTagStanza?(
159
160
  roomJID: string,
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated by scripts/sync-version.mjs on `npm version`.
2
2
  // Edit package.json's version instead of this file.
3
- export const ETHORA_CHAT_COMPONENT_VERSION = '26.10.7';
3
+ export const ETHORA_CHAT_COMPONENT_VERSION = '26.10.8';