@ethora/chat-component-rn 26.10.8 → 26.10.9

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 (36) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/lib/commonjs/components/Menu/HeaderRoomListMenu.js +3 -3
  3. package/lib/commonjs/components/Menu/HeaderRoomListMenu.js.map +1 -1
  4. package/lib/commonjs/components/VideoCalls/useCallKeep.js +1 -3
  5. package/lib/commonjs/components/VideoCalls/useCallKeep.js.map +1 -1
  6. package/lib/commonjs/networking/callTokenStanza.js +13 -1
  7. package/lib/commonjs/networking/callTokenStanza.js.map +1 -1
  8. package/lib/commonjs/push/sealedPushTask.js +20 -1
  9. package/lib/commonjs/push/sealedPushTask.js.map +1 -1
  10. package/lib/commonjs/version.js +1 -1
  11. package/lib/module/components/Menu/HeaderRoomListMenu.js +3 -3
  12. package/lib/module/components/Menu/HeaderRoomListMenu.js.map +1 -1
  13. package/lib/module/components/VideoCalls/useCallKeep.js +2 -4
  14. package/lib/module/components/VideoCalls/useCallKeep.js.map +1 -1
  15. package/lib/module/networking/callTokenStanza.js +13 -1
  16. package/lib/module/networking/callTokenStanza.js.map +1 -1
  17. package/lib/module/push/sealedPushTask.js +20 -1
  18. package/lib/module/push/sealedPushTask.js.map +1 -1
  19. package/lib/module/version.js +1 -1
  20. package/lib/typescript/components/Menu/HeaderRoomListMenu.d.ts.map +1 -1
  21. package/lib/typescript/components/VideoCalls/useCallKeep.d.ts.map +1 -1
  22. package/lib/typescript/main.d.ts +1 -1
  23. package/lib/typescript/main.d.ts.map +1 -1
  24. package/lib/typescript/networking/callTokenStanza.d.ts.map +1 -1
  25. package/lib/typescript/push/sealedPushTask.d.ts +13 -0
  26. package/lib/typescript/push/sealedPushTask.d.ts.map +1 -1
  27. package/lib/typescript/version.d.ts +1 -1
  28. package/package.json +1 -1
  29. package/plugins/notification-service/NotificationService.swift +105 -19
  30. package/plugins/withNotificationServiceExtension.js +44 -4
  31. package/src/components/Menu/HeaderRoomListMenu.tsx +13 -8
  32. package/src/components/VideoCalls/useCallKeep.ts +2 -4
  33. package/src/main.ts +1 -1
  34. package/src/networking/callTokenStanza.ts +10 -1
  35. package/src/push/sealedPushTask.ts +34 -1
  36. package/src/version.ts +1 -1
@@ -5,7 +5,15 @@
5
5
  * the keychain and shows the real title and text in place of the
6
6
  * "New message" placeholder;
7
7
  * - shows the attachment thumbnail (photo / video preview) the push service
8
- * sends as `previewUrl`.
8
+ * sends as `previewUrl`;
9
+ * - shows the sender's avatar (`image`, unless the app switched it off on
10
+ * the push service) next to the text with the app icon as the small
11
+ * badge: a communication notification. For that the app gets the
12
+ * Communication Notifications capability
13
+ * (com.apple.developer.usernotifications.communication) and
14
+ * INSendMessageIntent in NSUserActivityTypes. With manual signing the
15
+ * App ID needs the capability switched on in the developer portal; EAS
16
+ * and Xcode's automatic signing do that themselves.
9
17
  *
10
18
  * An extension is a separate target signed with the app, so it cannot ship
11
19
  * inside the npm package as code: this plugin writes it into ios/ on every
@@ -25,11 +33,19 @@
25
33
  * Options:
26
34
  * appleTeamId signing team of the extension; default: ios.appleTeamId,
27
35
  * else the team already set on the app target in Xcode.
36
+ * communicationNotifications
37
+ * false leaves the capability out (the avatar is then never
38
+ * shown, pushes look as before); default true.
28
39
  */
29
40
 
30
41
  const fs = require('fs');
31
42
  const path = require('path');
32
- const { withDangerousMod, withEntitlementsPlist, withXcodeProject } = require('expo/config-plugins');
43
+ const {
44
+ withDangerousMod,
45
+ withEntitlementsPlist,
46
+ withInfoPlist,
47
+ withXcodeProject,
48
+ } = require('expo/config-plugins');
33
49
 
34
50
  const TARGET_NAME = 'EthoraNotificationService';
35
51
  const SOURCE_DIR = path.join(__dirname, 'notification-service');
@@ -37,6 +53,8 @@ const SOURCES = ['NotificationService.swift', 'SealedBox.swift'];
37
53
  const FILES = [...SOURCES, 'Info.plist'];
38
54
  const ENTITLEMENTS_FILE = `${TARGET_NAME}.entitlements`;
39
55
  const KEYCHAIN_GROUPS_KEY = 'keychain-access-groups';
56
+ const COMMUNICATION_KEY = 'com.apple.developer.usernotifications.communication';
57
+ const SEND_MESSAGE_INTENT = 'INSendMessageIntent';
40
58
 
41
59
  const appBundleId = (config) => {
42
60
  const bundleId = config.ios && config.ios.bundleIdentifier;
@@ -93,6 +111,25 @@ const withAppKeychainGroup = (config) =>
93
111
  return cfg;
94
112
  });
95
113
 
114
+ // The sender's avatar: iOS shows a notification as a message from a person
115
+ // only for an app with this capability that declares the intent.
116
+ const withCommunicationNotifications = (config) =>
117
+ withInfoPlist(
118
+ withEntitlementsPlist(config, (cfg) => {
119
+ cfg.modResults[COMMUNICATION_KEY] = true;
120
+ return cfg;
121
+ }),
122
+ (cfg) => {
123
+ const types = Array.isArray(cfg.modResults.NSUserActivityTypes)
124
+ ? cfg.modResults.NSUserActivityTypes
125
+ : [];
126
+ if (!types.includes(SEND_MESSAGE_INTENT)) {
127
+ cfg.modResults.NSUserActivityTypes = [...types, SEND_MESSAGE_INTENT];
128
+ }
129
+ return cfg;
130
+ }
131
+ );
132
+
96
133
  const entitlementsPlist = (group) => `<?xml version="1.0" encoding="UTF-8"?>
97
134
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
98
135
  <plist version="1.0">
@@ -183,7 +220,10 @@ const withExtensionTarget = (config, { appleTeamId } = {}) =>
183
220
  return cfg;
184
221
  });
185
222
 
186
- const withNotificationServiceExtension = (config, props = {}) =>
187
- withExtensionTarget(withExtensionFiles(withAppKeychainGroup(withEasAppExtension(config))), props);
223
+ const withNotificationServiceExtension = (config, props = {}) => {
224
+ let next = withAppKeychainGroup(withEasAppExtension(config));
225
+ if (props.communicationNotifications !== false) next = withCommunicationNotifications(next);
226
+ return withExtensionTarget(withExtensionFiles(next), props);
227
+ };
188
228
 
189
229
  module.exports = withNotificationServiceExtension;
@@ -273,14 +273,18 @@ export const HeaderRoomListMenu: FC<HeaderRoomListMenuProps> = ({
273
273
  onClick: () => void;
274
274
  styles?: { color: string };
275
275
  }[] = [
276
- {
277
- label: t('menu.newChat'),
278
- icon: <AddNewIcon color={iconColor} />,
279
- onClick: () => {
280
- dispatch(setActiveModal(MODAL_TYPES.NEW_CHAT));
281
- },
282
- styles: { color: labelColor },
283
- },
276
+ ...(!config?.chatHeaderSettings?.disableCreate
277
+ ? [
278
+ {
279
+ label: t('menu.newChat'),
280
+ icon: <AddNewIcon color={iconColor} />,
281
+ onClick: () => {
282
+ dispatch(setActiveModal(MODAL_TYPES.NEW_CHAT));
283
+ },
284
+ styles: { color: labelColor },
285
+ },
286
+ ]
287
+ : []),
284
288
  {
285
289
  label: t('menu.profile'),
286
290
  icon: <ProfileIcon color={iconColor} />,
@@ -329,6 +333,7 @@ export const HeaderRoomListMenu: FC<HeaderRoomListMenuProps> = ({
329
333
  iconColor,
330
334
  labelColor,
331
335
  config?.disablePublicChatsDirectory,
336
+ config?.chatHeaderSettings?.disableCreate,
332
337
  t,
333
338
  ]);
334
339
 
@@ -2,8 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { Platform } from 'react-native';
3
3
  import { useDispatch, useSelector } from 'react-redux';
4
4
  import { RootState, store } from '../../roomStore';
5
- import { acceptIncomingCall, declineIncomingCall } from '../../roomStore/callSlice';
6
- import { sendCallStateSignal } from '../../networking/callTokenStanza';
5
+ import { acceptIncomingCall } from '../../roomStore/callSlice';
7
6
  import { declineActiveCallWhenOnline, terminateActiveCall } from './callActions';
8
7
  import { setCallMicEnabled, useCallSession } from './callSessionController';
9
8
  import { loadLiveKit } from './livekitRuntime';
@@ -162,8 +161,7 @@ export const useCallKeep = (): void => {
162
161
  snapshot.direction === 'incoming' &&
163
162
  snapshot.phase === 'ringing-incoming'
164
163
  ) {
165
- sendCallStateSignal('declined');
166
- dispatch(declineIncomingCall());
164
+ declineActiveCallWhenOnline();
167
165
  return;
168
166
  }
169
167
  // Ended from the Dynamic Island, the lock screen or a headset.
package/src/main.ts CHANGED
@@ -81,7 +81,7 @@ export {
81
81
  presentSealedPush,
82
82
  SEALED_PUSH_TASK,
83
83
  } from './push/sealedPushTask';
84
- export type { SealedPushTaskOptions } from './push/sealedPushTask';
84
+ export type { SealedPushTaskOptions, ShownMessagePush } from './push/sealedPushTask';
85
85
  export {
86
86
  openSealedPush,
87
87
  isSealedPush,
@@ -310,6 +310,8 @@ const handleCallStateData = (
310
310
  !!currentCall.callId &&
311
311
  currentCall.callId === stanzaCallId;
312
312
 
313
+ if (stanzaCallId && currentCall.callId && !callIdMatches) {return;}
314
+
313
315
  const isTerminalState =
314
316
  state === 'ended' ||
315
317
  state === 'declined' ||
@@ -381,7 +383,7 @@ export const onCallTokenMessage = (stanza: Element): boolean => {
381
383
  .split('/')[0]
382
384
  .split('@')[0]
383
385
  .trim();
384
- handleCallStateData(inner, fromRoom);
386
+ handleCallStateData(data, fromRoom);
385
387
 
386
388
  // Server call-logs (carry callerXmppUsername / durationMs) should
387
389
  // flow on to onRealtimeMessage / onMessageHistory where they become
@@ -394,6 +396,13 @@ export const onCallTokenMessage = (stanza: Element): boolean => {
394
396
  return true;
395
397
  }
396
398
 
399
+ if (dataType === 'call-state-declined') {
400
+ handleCallStateData({
401
+ attrs: { ...data.attrs, state: 'declined' },
402
+ } as unknown as Element);
403
+ return true;
404
+ }
405
+
397
406
  if (dataType === 'call-invite') {
398
407
  const inviteKind = String(data.attrs?.kind || '').toLowerCase();
399
408
  const normalized: 'audio' | 'video' =
@@ -40,6 +40,20 @@ export interface SealedPushTaskOptions {
40
40
  * where the XMPP invite rings the in-app call screen.
41
41
  */
42
42
  onCallPush?: (data: Record<string, unknown>) => void | Promise<void>;
43
+ onMessageShown?: (shown: ShownMessagePush) => void | Promise<void>;
44
+ }
45
+
46
+ export interface ShownMessagePush {
47
+ identifier: string;
48
+ channelId?: string;
49
+ title: string;
50
+ body: string;
51
+ sender: string;
52
+ senderJid: string;
53
+ chat: string;
54
+ chatJid: string;
55
+ image: string;
56
+ reaction: boolean;
43
57
  }
44
58
 
45
59
  const PLACEHOLDER_TITLE = 'New message';
@@ -157,11 +171,30 @@ export async function presentSealedPush(
157
171
  const base = envelope.jid || envelope.msgID || '';
158
172
  const identifier = base ? (reaction ? `${base}#reaction` : base) : undefined;
159
173
  const channelId = (reaction && options.reactionChannelId) || options.channelId;
174
+ const content = notificationContent(result, options);
160
175
  await notifications.scheduleNotificationAsync({
161
176
  identifier,
162
- content: notificationContent(result, options),
177
+ content,
163
178
  trigger: channelId ? { channelId } : null,
164
179
  });
180
+ if (result.ok && result.content.image && identifier && options.onMessageShown) {
181
+ try {
182
+ await options.onMessageShown({
183
+ identifier,
184
+ channelId,
185
+ title: content.title,
186
+ body: content.body ?? '',
187
+ sender: result.content.sender,
188
+ senderJid: result.content.senderJid,
189
+ chat: result.content.chat,
190
+ chatJid: result.content.chatJid,
191
+ image: result.content.image,
192
+ reaction,
193
+ });
194
+ } catch (e) {
195
+ pushLog('warn', 'push: drawing the sender avatar failed', e);
196
+ }
197
+ }
165
198
  return result;
166
199
  }
167
200
 
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.8';
3
+ export const ETHORA_CHAT_COMPONENT_VERSION = '26.10.9';