@robono/messaging-react-native 0.10.0 → 0.11.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## 0.10.0 — release candidate
2
2
 
3
+ ## 0.11.0
4
+
5
+ Connects the supplied Messenger to recording/caption controls, on-device background blur and account device checks, using its existing call integration.
6
+
3
7
  Add supplied business cards, public groups and moderation, appearance controls, app-wide availability and inbox continuity. Own conversation opening and foreground incoming updates inside ExpoMessenger. Expand supplied interface languages.
4
8
 
5
9
  ## 0.9.0 — release candidate
package/README.md CHANGED
@@ -448,3 +448,12 @@ The complete Messenger connection supplies foreground activity independently of
448
448
  the selected chat. Online now and optional Last seen are chosen in Features &
449
449
  preview. `connection.availability` can also supply an authorized contact list.
450
450
  [Activity setup, privacy and timing](https://robono.com/guides/user-availability.html).
451
+
452
+
453
+ ## Recording, captions and local call tools
454
+
455
+ Robono hosts optional call recording and transcription. Enable recording once in the existing Calling settings; every participant must agree before capture, and anyone can stop it. The supplied controls include the consent notice, active-recording status and captions. No customer recording server is required.
456
+
457
+ The supplied adapters also offer camera/microphone/speaker checks and camera background blur. Screen sharing is separate. Phone blur requires rebuilding the native app; browser blur downloads its model only when selected and needs the documented content-security policy. Background processing stays on the device.
458
+
459
+ See the [recording and call tools guide](https://robono.com/guides/call-recording.html) for prices, retention, short-lived downloads, deletion, platform requirements and label customization. Captions arrive in short batches with several seconds of delay and can be inaccurate; incomplete transcripts are marked.
package/SBOM.spdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "spdxVersion": "SPDX-2.3",
3
3
  "dataLicense": "CC0-1.0",
4
4
  "SPDXID": "SPDXRef-DOCUMENT",
5
- "name": "@robono/messaging-react-native@0.10.0",
6
- "documentNamespace": "https://robono.com/sbom/messaging-react-native/0.10.0/f01772a63f341c1ecd147ed3249de2162211f081",
5
+ "name": "@robono/messaging-react-native@0.11.0",
6
+ "documentNamespace": "https://robono.com/sbom/messaging-react-native/0.11.0/3114ef5e47e539034d81dac5af0c8a459303e212",
7
7
  "creationInfo": {
8
- "created": "2026-11-05T10:33:43.580Z",
8
+ "created": "2026-11-05T15:11:49.198Z",
9
9
  "creators": [
10
10
  "Organization: Robono (support@robono.com)"
11
11
  ]
@@ -14,11 +14,11 @@
14
14
  {
15
15
  "name": "@robono/messaging-react-native",
16
16
  "SPDXID": "SPDXRef-Package",
17
- "versionInfo": "0.10.0",
17
+ "versionInfo": "0.11.0",
18
18
  "downloadLocation": "NOASSERTION",
19
19
  "filesAnalyzed": true,
20
20
  "packageVerificationCode": {
21
- "packageVerificationCodeValue": "f01772a63f341c1ecd147ed3249de2162211f081"
21
+ "packageVerificationCodeValue": "3114ef5e47e539034d81dac5af0c8a459303e212"
22
22
  },
23
23
  "licenseConcluded": "LicenseRef-RobonoSDK",
24
24
  "licenseDeclared": "LicenseRef-RobonoSDK",
@@ -26,7 +26,7 @@
26
26
  "LicenseRef-RobonoSDK"
27
27
  ],
28
28
  "copyrightText": "Copyright (c) 2026 Add to Loop LLC.",
29
- "comment": "Host-supplied peers (not bundled): {\"@robono/messaging\":\"^0.7.0\",\"react\":\">=18.2 <20\",\"react-native\":\">=0.76 <1\",\"expo-audio\":\">=55 <57\",\"expo-image-picker\":\">=55 <57\",\"expo-document-picker\":\">=55 <57\",\"expo-file-system\":\">=55.0.5 <57\",\"expo-sharing\":\">=55 <57\",\"expo-video\":\">=55 <57\",\"expo-crypto\":\">=55 <57\",\"react-native-safe-area-context\":\">=5 <6\",\"react-native-svg\":\">=15 <16\",\"@robono/calling-react-native\":\"^0.8.0\",\"expo-clipboard\":\">=8 <57\",\"expo-image-manipulator\":\">=14 <57\",\"expo-video-thumbnails\":\">=14 <57\",\"react-native-webview\":\">=13 <14\",\"expo-blur\":\">=55 <57\",\"expo-notifications\":\">=55 <57\",\"expo-secure-store\":\">=55 <57\",\"@robono/location-react-native\":\"^0.4.0\",\"expo-location\":\">=55 <57\",\"expo-constants\":\">=55 <57\",\"@robono/location\":\"^0.4.0\",\"expo-haptics\":\">=55 <57\",\"@robono/calling\":\"^0.5.0\"}"
29
+ "comment": "Host-supplied peers (not bundled): {\"@robono/messaging\":\"^0.7.1\",\"react\":\">=18.2 <20\",\"react-native\":\">=0.76 <1\",\"expo-audio\":\">=55 <57\",\"expo-image-picker\":\">=55 <57\",\"expo-document-picker\":\">=55 <57\",\"expo-file-system\":\">=55.0.5 <57\",\"expo-sharing\":\">=55 <57\",\"expo-video\":\">=55 <57\",\"expo-crypto\":\">=55 <57\",\"react-native-safe-area-context\":\">=5 <6\",\"react-native-svg\":\">=15 <16\",\"@robono/calling-react-native\":\"^0.9.0\",\"expo-clipboard\":\">=8 <57\",\"expo-image-manipulator\":\">=14 <57\",\"expo-video-thumbnails\":\">=14 <57\",\"react-native-webview\":\">=13 <14\",\"expo-blur\":\">=55 <57\",\"expo-notifications\":\">=55 <57\",\"expo-secure-store\":\">=55 <57\",\"@robono/location-react-native\":\"^0.4.0\",\"expo-location\":\">=55 <57\",\"expo-constants\":\">=55 <57\",\"@robono/location\":\"^0.4.0\",\"expo-haptics\":\">=55 <57\",\"@robono/calling\":\"^0.6.0\"}"
30
30
  }
31
31
  ],
32
32
  "files": [
@@ -36,11 +36,11 @@
36
36
  "checksums": [
37
37
  {
38
38
  "algorithm": "SHA256",
39
- "checksumValue": "e3a4e40d5aff6784f0bb33a300a0da6081650f18af008f0bb11dad8210c16d7c"
39
+ "checksumValue": "1f659bd2fe7d2211195ca04eebdfb9a6b05269f29a6b8f4d87421249e932fcbb"
40
40
  },
41
41
  {
42
42
  "algorithm": "SHA1",
43
- "checksumValue": "16049433f53065f12fb14d35b6143c7be4217fa9"
43
+ "checksumValue": "1568598175f7e994eb3908404f6d999e814a4b58"
44
44
  }
45
45
  ],
46
46
  "licenseConcluded": "LicenseRef-RobonoSDK",
@@ -74,11 +74,11 @@
74
74
  "checksums": [
75
75
  {
76
76
  "algorithm": "SHA256",
77
- "checksumValue": "cc3068ad5b67b7caec6c16643820a36b4011a490b78084721a4b11a6f8e8fe33"
77
+ "checksumValue": "c572a5f24a5e917c00e017b8a8ac87419dcd20b6ffd5a8de0350dbd2cd262187"
78
78
  },
79
79
  {
80
80
  "algorithm": "SHA1",
81
- "checksumValue": "5ab62ccfa7007f7c44377ecd1bf924eb30492a87"
81
+ "checksumValue": "2938fddafadf0c85ae3851d48d2014090fd5aa38"
82
82
  }
83
83
  ],
84
84
  "licenseConcluded": "LicenseRef-RobonoSDK",
@@ -112,11 +112,11 @@
112
112
  "checksums": [
113
113
  {
114
114
  "algorithm": "SHA256",
115
- "checksumValue": "2f1328361b2b432a4f48bc2bb013656ddbdc50353cfba9b5a45c19c0b3adeca2"
115
+ "checksumValue": "837f6a3887d02c596f9fd2e5774a7920b3fb4c51d9f40019c64d4b1af3e233ca"
116
116
  },
117
117
  {
118
118
  "algorithm": "SHA1",
119
- "checksumValue": "1d464839cbe9a033b2a8646f332b420142146b48"
119
+ "checksumValue": "b936af0371f7c382903f2a2f7d847faeee5688e8"
120
120
  }
121
121
  ],
122
122
  "licenseConcluded": "LicenseRef-RobonoSDK",
@@ -131,11 +131,11 @@
131
131
  "checksums": [
132
132
  {
133
133
  "algorithm": "SHA256",
134
- "checksumValue": "baf9dbfdade3385884a35da4986763bbc92a56e3e01ef1486f933a67e66d749c"
134
+ "checksumValue": "399340c0a4539b5918e1ce1265a0811c624e7401bf21e7d7328449cfbc2609d4"
135
135
  },
136
136
  {
137
137
  "algorithm": "SHA1",
138
- "checksumValue": "3d801b65753ad15f69c8cbaa513d7a65d3d07322"
138
+ "checksumValue": "cd14cf72b91bac56dfd5ba1bac94d7e5bf647f42"
139
139
  }
140
140
  ],
141
141
  "licenseConcluded": "LicenseRef-RobonoSDK",
@@ -454,11 +454,11 @@
454
454
  "checksums": [
455
455
  {
456
456
  "algorithm": "SHA256",
457
- "checksumValue": "c5bed28fd78ae8b05fccffa938f6b846b8577152e5eb400c96e13869f44d9b80"
457
+ "checksumValue": "42e29921bce9f8ec264aa6ebcd20132a3c8adf14d1eeadb1a19648ebdd318303"
458
458
  },
459
459
  {
460
460
  "algorithm": "SHA1",
461
- "checksumValue": "273d74b211a3ba24a9d332cff8d1715760b67c67"
461
+ "checksumValue": "ca3c4c7fc708e9d1eaa81850c7b11fa21dbd419b"
462
462
  }
463
463
  ],
464
464
  "licenseConcluded": "LicenseRef-RobonoSDK",
@@ -473,11 +473,11 @@
473
473
  "checksums": [
474
474
  {
475
475
  "algorithm": "SHA256",
476
- "checksumValue": "15e46684ce38127a2f9a34441b70be9f7fca4088b94624a320be96cfbafc59b9"
476
+ "checksumValue": "398e93c24289d0c125acab0557a35c63b1fee9149137006568fefed0830b264a"
477
477
  },
478
478
  {
479
479
  "algorithm": "SHA1",
480
- "checksumValue": "209b8ea92646e034069ccb9bd7e79e56544b79c3"
480
+ "checksumValue": "cb781c3a18e5e5e004219816735809d21a3fd970"
481
481
  }
482
482
  ],
483
483
  "licenseConcluded": "LicenseRef-RobonoSDK",
@@ -644,11 +644,11 @@
644
644
  "checksums": [
645
645
  {
646
646
  "algorithm": "SHA256",
647
- "checksumValue": "4f4510054783370605c7eb1bc3d456cfa3606d37fb9f3589451c33fe444e8e49"
647
+ "checksumValue": "2663ea07823c8dd95db571753c56ec4e655d9ae0cf66fb086b91ed6a50a0a8cc"
648
648
  },
649
649
  {
650
650
  "algorithm": "SHA1",
651
- "checksumValue": "163dfad478e8fe7239ec106f6187f47b01142a59"
651
+ "checksumValue": "08d09268b8fc87e2a79666b09336e3310419d897"
652
652
  }
653
653
  ],
654
654
  "licenseConcluded": "LicenseRef-RobonoSDK",
@@ -663,11 +663,11 @@
663
663
  "checksums": [
664
664
  {
665
665
  "algorithm": "SHA256",
666
- "checksumValue": "8182046cbe30b3127281c57a205916478d569105e09e7ea9db075d74b6676573"
666
+ "checksumValue": "2656d4f7e8004333e73215709b6d4f84175a13ea476508e4965da8c1164aaf62"
667
667
  },
668
668
  {
669
669
  "algorithm": "SHA1",
670
- "checksumValue": "e03a20dfb4b70961b8b36cfbf8c052172781be3d"
670
+ "checksumValue": "66b634d4434e5a8e676977684e8658456d18deb0"
671
671
  }
672
672
  ],
673
673
  "licenseConcluded": "LicenseRef-RobonoSDK",
@@ -1955,11 +1955,11 @@
1955
1955
  "checksums": [
1956
1956
  {
1957
1957
  "algorithm": "SHA256",
1958
- "checksumValue": "da40ab570ad2f2b2131997a34604bd58c9f62c30d79a506853ff1652d2e3e1be"
1958
+ "checksumValue": "ef2a8fdc3bc77bf6720d2ff806c935ad381889ff9e334ce0aa0be6e9d079b0c1"
1959
1959
  },
1960
1960
  {
1961
1961
  "algorithm": "SHA1",
1962
- "checksumValue": "c99b08d7714b84e57f1a04692ed452290d063e2c"
1962
+ "checksumValue": "d015990df7071695bf07c2367f9531fe37fc8f24"
1963
1963
  }
1964
1964
  ],
1965
1965
  "licenseConcluded": "LicenseRef-RobonoSDK",
@@ -2791,11 +2791,11 @@
2791
2791
  "checksums": [
2792
2792
  {
2793
2793
  "algorithm": "SHA256",
2794
- "checksumValue": "48a6ba0fb3bf3acf5f6c433c2f8ed57e0bc6d032bb531666c18257152034a54e"
2794
+ "checksumValue": "fb6672316fffda6ad6976a61870c516546f679e0833ef9aaf433a020e03d53e8"
2795
2795
  },
2796
2796
  {
2797
2797
  "algorithm": "SHA1",
2798
- "checksumValue": "f45f11dbfe9cb03a1a08b929b802cf2c3cb87fcc"
2798
+ "checksumValue": "c5cb0d9b9f83f746c1b689a837f92d2cf9a7f54e"
2799
2799
  }
2800
2800
  ],
2801
2801
  "licenseConcluded": "LicenseRef-RobonoSDK",
package/dist/account.d.ts CHANGED
@@ -1,6 +1,9 @@
1
+ import { type CallDeviceCheck } from '@robono/calling-react-native';
1
2
  import React from 'react';
2
3
  /** Profile and account actions belong to the customer's sign-in system. */
3
4
  export interface MessagingAccount {
5
+ deviceCheck?: CallDeviceCheck;
6
+ renderCallPreview?: (stream: MediaStream) => React.ReactNode;
4
7
  name: string;
5
8
  avatarUrl?: string;
6
9
  phone?: string;
package/dist/account.js CHANGED
@@ -1,12 +1,16 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { CallDeviceCheckPanel, callDeviceCheckText } from '@robono/calling-react-native';
2
3
  import { Text } from './typography.js';
3
4
  import { useRobonoLocale } from './localization.js';
4
5
  import { useEffect, useRef, useState } from 'react';
5
- import { Image, Platform, Pressable, ScrollView, View } from 'react-native';
6
+ import { AppState, Image, Platform, Pressable, ScrollView, View } from 'react-native';
6
7
  import { MessagingPopup, messagingPopupColors as c } from './popup.js';
7
8
  import { MessageIcon } from './icons.js';
8
9
  export function MessagingAccountScreen({ account, companyName, onClose }) {
9
10
  const { text: uiText, locale: uiLocale, number: uiNumber, count: uiCount } = useRobonoLocale();
11
+ const [checking, setChecking] = useState(false);
12
+ useEffect(() => { const sub = AppState.addEventListener('change', value => { if (value !== 'active')
13
+ void account?.deviceCheck?.close(); }); return () => { sub.remove(); void account?.deviceCheck?.close(); }; }, [account?.deviceCheck]);
10
14
  const [busy, setBusy] = useState(false), [error, setError] = useState(''), [visible, setVisible] = useState(true), pending = useRef(null);
11
15
  const dismissed = () => { const action = pending.current; pending.current = null; if (action)
12
16
  requestAnimationFrame(() => void run(action)); };
@@ -25,6 +29,12 @@ export function MessagingAccountScreen({ account, companyName, onClose }) {
25
29
  finally {
26
30
  setBusy(false);
27
31
  } };
28
- return _jsx(MessagingPopup, { visible: visible, onDismiss: dismissed, title: uiText("Account"), closeLabel: uiText("Close account"), closeDisabled: busy, background: c.background, contentWidth: 640, onClose: () => { if (!busy)
29
- onClose(); }, children: _jsxs(ScrollView, { contentContainerStyle: { gap: 16, paddingBottom: 12 }, children: [_jsxs(View, { style: { backgroundColor: '#111111', borderRadius: 16, padding: 16, gap: 14 }, children: [_jsxs(View, { style: { flexDirection: 'row', alignItems: 'center', gap: 14 }, children: [account?.avatarUrl ? _jsx(Image, { source: { uri: account.avatarUrl }, style: { width: 56, height: 56, borderRadius: 28 } }) : _jsx(View, { style: { width: 56, height: 56, borderRadius: 28, backgroundColor: '#252525', alignItems: 'center', justifyContent: 'center' }, children: _jsx(MessageIcon, { name: "person-circle-outline", color: c.foreground, size: 36 }) }), _jsxs(View, { style: { flex: 1, gap: 4 }, children: [_jsx(Text, { style: { color: c.foreground, fontSize: 20, fontWeight: '600' }, children: account?.name ?? uiText("Your account") }), _jsx(Text, { style: { color: c.secondary, fontSize: 14 }, children: companyName })] })] }), account?.phone && _jsx(Text, { style: { color: c.foreground, fontSize: 16 }, children: account.phone }), account?.email && _jsx(Text, { style: { color: c.foreground, fontSize: 16 }, children: account.email }), account?.onEditProfile && _jsxs(Pressable, { accessibilityRole: "button", accessibilityLabel: uiText("Edit profile"), disabled: busy, onPress: () => begin(account.onEditProfile), style: { minHeight: 44, flexDirection: 'row', alignItems: 'center', gap: 10 }, children: [_jsx(MessageIcon, { name: "edit", color: c.foreground, size: 20 }), _jsx(Text, { style: { color: c.foreground, fontSize: 16 }, children: uiText("Edit profile") })] })] }), account?.onEnableNotifications && _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: uiText("Enable notifications"), disabled: busy, onPress: () => void run(account.onEnableNotifications), style: { backgroundColor: '#111111', borderRadius: 12, minHeight: 52, paddingHorizontal: 16, justifyContent: 'center' }, children: _jsx(Text, { style: { color: c.foreground, fontSize: 16 }, children: uiText("Enable notifications") }) }), account?.onSignOut && _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: uiText("Sign out"), disabled: busy, onPress: () => begin(account.onSignOut), style: { backgroundColor: '#111111', borderRadius: 12, minHeight: 52, paddingHorizontal: 16, justifyContent: 'center' }, children: _jsx(Text, { style: { color: c.foreground, fontSize: 16 }, children: uiText("Sign out") }) }), !!error && _jsx(Text, { accessibilityRole: "alert", style: { color: c.foreground, fontSize: 15 }, children: error })] }) });
32
+ return _jsx(MessagingPopup, { visible: visible, onDismiss: dismissed, title: uiText(checking ? "Check call devices" : "Account"), headerBack: checking, closeLabel: uiText("Close account"), closeDisabled: busy, background: c.background, contentWidth: 640, onClose: () => { if (!busy) {
33
+ if (checking) {
34
+ void account?.deviceCheck?.close();
35
+ setChecking(false);
36
+ }
37
+ else
38
+ onClose();
39
+ } }, children: _jsx(ScrollView, { contentContainerStyle: { gap: 16, paddingBottom: 12 }, children: checking && account?.deviceCheck ? _jsx(CallDeviceCheckPanel, { check: account.deviceCheck, renderVideo: account.renderCallPreview, labels: Object.fromEntries(Object.entries(callDeviceCheckText).map(([key, value]) => [key, uiText(value)])) }) : _jsxs(_Fragment, { children: [_jsxs(View, { style: { backgroundColor: '#111111', borderRadius: 16, padding: 16, gap: 14 }, children: [_jsxs(View, { style: { flexDirection: 'row', alignItems: 'center', gap: 14 }, children: [account?.avatarUrl ? _jsx(Image, { source: { uri: account.avatarUrl }, style: { width: 56, height: 56, borderRadius: 28 } }) : _jsx(View, { style: { width: 56, height: 56, borderRadius: 28, backgroundColor: '#252525', alignItems: 'center', justifyContent: 'center' }, children: _jsx(MessageIcon, { name: "person-circle-outline", color: c.foreground, size: 36 }) }), _jsxs(View, { style: { flex: 1, gap: 4 }, children: [_jsx(Text, { style: { color: c.foreground, fontSize: 20, fontWeight: '600' }, children: account?.name ?? uiText("Your account") }), _jsx(Text, { style: { color: c.secondary, fontSize: 14 }, children: companyName })] })] }), account?.phone && _jsx(Text, { style: { color: c.foreground, fontSize: 16 }, children: account.phone }), account?.email && _jsx(Text, { style: { color: c.foreground, fontSize: 16 }, children: account.email }), account?.onEditProfile && _jsxs(Pressable, { accessibilityRole: "button", accessibilityLabel: uiText("Edit profile"), disabled: busy, onPress: () => begin(account.onEditProfile), style: { minHeight: 44, flexDirection: 'row', alignItems: 'center', gap: 10 }, children: [_jsx(MessageIcon, { name: "edit", color: c.foreground, size: 20 }), _jsx(Text, { style: { color: c.foreground, fontSize: 16 }, children: uiText("Edit profile") })] })] }), account?.deviceCheck && _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: uiText("Check call devices"), disabled: busy, onPress: () => setChecking(true), style: { backgroundColor: '#111111', borderRadius: 12, minHeight: 52, paddingHorizontal: 16, justifyContent: 'center' }, children: _jsx(Text, { style: { color: c.foreground, fontSize: 16 }, children: uiText("Check call devices") }) }), account?.onEnableNotifications && _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: uiText("Enable notifications"), disabled: busy, onPress: () => void run(account.onEnableNotifications), style: { backgroundColor: '#111111', borderRadius: 12, minHeight: 52, paddingHorizontal: 16, justifyContent: 'center' }, children: _jsx(Text, { style: { color: c.foreground, fontSize: 16 }, children: uiText("Enable notifications") }) }), account?.onSignOut && _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: uiText("Sign out"), disabled: busy, onPress: () => begin(account.onSignOut), style: { backgroundColor: '#111111', borderRadius: 12, minHeight: 52, paddingHorizontal: 16, justifyContent: 'center' }, children: _jsx(Text, { style: { color: c.foreground, fontSize: 16 }, children: uiText("Sign out") }) }), !!error && _jsx(Text, { accessibilityRole: "alert", style: { color: c.foreground, fontSize: 15 }, children: error })] }) }) });
30
40
  }
@@ -18,6 +18,14 @@ export declare function createExpoMessengerCalling(options: {
18
18
  name: string;
19
19
  avatarUrl?: string;
20
20
  }>): void;
21
+ deviceCheck: {
22
+ getSnapshot: () => import("@robono/calling-react-native").CallDeviceCheckState;
23
+ subscribe(fn: () => void): () => void;
24
+ start(kind?: "voice" | "video"): Promise<void>;
25
+ speaker(): Promise<void>;
26
+ close: () => Promise<void>;
27
+ dispose(): Promise<void>;
28
+ } | undefined;
21
29
  start: () => Promise<void>;
22
30
  getSnapshot: () => MessengerCallingState;
23
31
  subscribe(fn: () => void): () => void;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { callDiagnosticsText } from '@robono/calling-react-native';
2
+ import { callDiagnosticsText, callRecordingText } from '@robono/calling-react-native';
3
3
  import { Text } from './typography.js';
4
4
  import { callingText } from '@robono/calling-react-native/appearance';
5
5
  import { useRobonoLocale } from './localization.js';
@@ -7,7 +7,7 @@ import { useEffect, useSyncExternalStore } from 'react';
7
7
  import { Pressable, View } from 'react-native';
8
8
  import { setAudioModeAsync } from 'expo-audio';
9
9
  import { createMessengerCalling } from '@robono/calling-react-native/messenger';
10
- import { createNativeCallAdapters } from '@robono/calling-react-native/native';
10
+ import { createNativeCallAdapters, createNativeCallDeviceCheck } from '@robono/calling-react-native/native';
11
11
  import { createNativeCallRuntime } from '@robono/calling-react-native/runtime';
12
12
  import { createNativeSystemCalling } from '@robono/calling-react-native/system-calling';
13
13
  import { NativeCallingOverlay, NativeGroupCallingOverlay } from '@robono/calling-react-native/native-video';
@@ -18,6 +18,7 @@ export function createExpoMessengerCalling(options) {
18
18
  let people = {};
19
19
  const system = createNativeSystemCalling({ start: () => calling.start(), onError: () => calling.reportError('The incoming call could not connect. Open the app and check microphone access and your connection.') });
20
20
  const calling = createMessengerCalling({
21
+ deviceCheck: active => createNativeCallDeviceCheck({ active, restoreAudio: () => setAudioModeAsync({ allowsRecording: false, playsInSilentMode: true, shouldPlayInBackground: false, shouldRouteThroughEarpiece: false, interruptionMode: 'mixWithOthers' }) }),
21
22
  request: createMessagingTransport({ ...options, url: url.href }),
22
23
  resolveContact: id => {
23
24
  const row = options.inbox.getSnapshot().conversations.find(c => c.id === id);
@@ -54,5 +55,5 @@ export function useExpoMessengerCalling(calling) {
54
55
  }
55
56
  export function ExpoMessengerCalls({ calling, state }) {
56
57
  const { text: uiText, locale: uiLocale, number: uiNumber, count: uiCount } = useRobonoLocale();
57
- return _jsxs(_Fragment, { children: [state.error && _jsxs(View, { style: { backgroundColor: '#171717', paddingHorizontal: 16, paddingVertical: 8, flexDirection: 'row', gap: 12, alignItems: 'center' }, children: [_jsx(Text, { accessibilityRole: "alert", style: { color: '#FAFAFA', flex: 1 }, children: state.error }), _jsx(Pressable, { accessibilityRole: "button", onPress: () => void calling?.start().catch(() => { }), style: { padding: 8 }, children: _jsx(Text, { style: { color: '#FF8A2C' }, children: uiText("Retry calls") }) })] }), state.direct && _jsx(NativeCallingOverlay, { controller: state.direct, diagnostics: { labels: Object.fromEntries(Object.entries(callDiagnosticsText).map(([key, value]) => [key, uiText(value)])) }, appearance: state.appearance, labels: Object.fromEntries(Object.entries({ ...callingText, ...state.appearance?.labels }).map(([key, value]) => [key, uiText(value)])) }), state.group && _jsx(NativeGroupCallingOverlay, { controller: state.group, diagnostics: { labels: Object.fromEntries(Object.entries(callDiagnosticsText).map(([key, value]) => [key, uiText(value)])) }, appearance: state.appearance, labels: Object.fromEntries(Object.entries({ ...callingText, ...state.appearance?.labels }).map(([key, value]) => [key, uiText(value)])) })] });
58
+ return _jsxs(_Fragment, { children: [state.error && _jsxs(View, { style: { backgroundColor: '#171717', paddingHorizontal: 16, paddingVertical: 8, flexDirection: 'row', gap: 12, alignItems: 'center' }, children: [_jsx(Text, { accessibilityRole: "alert", style: { color: '#FAFAFA', flex: 1 }, children: state.error }), _jsx(Pressable, { accessibilityRole: "button", onPress: () => void calling?.start().catch(() => { }), style: { padding: 8 }, children: _jsx(Text, { style: { color: '#FF8A2C' }, children: uiText("Retry calls") }) })] }), state.direct && _jsx(NativeCallingOverlay, { controller: state.direct, recordingLabels: Object.fromEntries(Object.entries(callRecordingText).map(([key, value]) => [key, uiText(value)])), diagnostics: { labels: Object.fromEntries(Object.entries(callDiagnosticsText).map(([key, value]) => [key, uiText(value)])) }, appearance: state.appearance, labels: Object.fromEntries(Object.entries({ ...callingText, ...state.appearance?.labels }).map(([key, value]) => [key, uiText(value)])) }), state.group && _jsx(NativeGroupCallingOverlay, { controller: state.group, recordingLabels: Object.fromEntries(Object.entries(callRecordingText).map(([key, value]) => [key, uiText(value)])), diagnostics: { labels: Object.fromEntries(Object.entries(callDiagnosticsText).map(([key, value]) => [key, uiText(value)])) }, appearance: state.appearance, labels: Object.fromEntries(Object.entries({ ...callingText, ...state.appearance?.labels }).map(([key, value]) => [key, uiText(value)])) })] });
58
59
  }
@@ -46,6 +46,14 @@ export declare function createExpoMessenger(options: {
46
46
  name: string;
47
47
  avatarUrl?: string;
48
48
  }>): void;
49
+ deviceCheck: {
50
+ getSnapshot: () => import("@robono/calling-react-native").CallDeviceCheckState;
51
+ subscribe(fn: () => void): () => void;
52
+ start(kind?: "voice" | "video"): Promise<void>;
53
+ speaker(): Promise<void>;
54
+ close: () => Promise<void>;
55
+ dispose(): Promise<void>;
56
+ } | undefined;
49
57
  start: () => Promise<void>;
50
58
  getSnapshot: () => import("@robono/calling-react-native/messenger").MessengerCallingState;
51
59
  subscribe(fn: () => void): () => void;
@@ -83,6 +91,14 @@ export declare function createHostedExpoMessenger(options: Omit<Parameters<typeo
83
91
  name: string;
84
92
  avatarUrl?: string;
85
93
  }>): void;
94
+ deviceCheck: {
95
+ getSnapshot: () => import("@robono/calling-react-native").CallDeviceCheckState;
96
+ subscribe(fn: () => void): () => void;
97
+ start(kind?: "voice" | "video"): Promise<void>;
98
+ speaker(): Promise<void>;
99
+ close: () => Promise<void>;
100
+ dispose(): Promise<void>;
101
+ } | undefined;
86
102
  start: () => Promise<void>;
87
103
  getSnapshot: () => import("@robono/calling-react-native/messenger").MessengerCallingState;
88
104
  subscribe(fn: () => void): () => void;
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { renderNativeCallVideo } from '@robono/calling-react-native/native-video';
2
3
  import { createInboxViewMemory } from './inbox-view.js';
3
4
  import { MessagingPublicRooms } from './public-rooms.js';
4
5
  import { RobonoLocaleProvider, useRobonoLocale } from './localization.js';
@@ -171,7 +172,8 @@ function ExpoMessengerContent({ inbox, contacts = {}, initialConversation, selec
171
172
  };
172
173
  useEffect(() => { void notifications?.start(async (id, message) => { await inbox.refresh(); const row = inbox.getSnapshot().conversations.find(c => c.id === id); if (row)
173
174
  open(row, message); }).catch(() => { }); }, [notifications, inbox]);
174
- const account = notifications ? { name: props.account?.name ?? 'Your account', ...props.account, onEnableNotifications: () => notifications.enable() } : props.account;
175
+ const notificationAccount = notifications ? { name: props.account?.name ?? 'Your account', ...props.account, onEnableNotifications: () => notifications.enable() } : props.account;
176
+ const account = calling?.deviceCheck ? { name: 'Your account', ...notificationAccount, deviceCheck: calling.deviceCheck, renderCallPreview: (stream) => renderNativeCallVideo({ stream, local: true, style: { width: '100%', height: '100%' } }) } : notificationAccount;
175
177
  const contact = selected?.kind === 'group' ? { name: selected.name ?? 'Group' } : contacts[selected?.contactId ?? ''] ?? { name: selected?.contactId ?? 'Chat' };
176
178
  return _jsxs(View, { style: { flex: 1, minHeight: 0 }, children: [_jsx(MessengerMapsNotice, { error: mapConnection.error, retry: mapConnection.retry }), showMaps && maps ? _jsx(RobonoMaps, { interfaceLocale: mapsLocale, workspace: maps, contacts: contacts, onClose: () => setShowMaps(false) }) : _jsx(ExpoMessagingScreen, { ...props, account: account, contact: contact, participants: props.participants ?? contacts, extras: selected ? props.extras : undefined, callActions: selected ? { ...autoCalls, ...props.callActions, phoneNumber: contacts[selected.contactId ?? '']?.phoneNumber ?? props.callActions?.phoneNumber } : undefined, onBack: () => { props.client.stop(); setSelected(null); void inbox.refresh().catch(() => { }); }, renderConversation: screen => selected ? _jsx(MessagingCallingScreen, { ...screen, onCall: screen.callActions?.voice, onVideoCall: screen.callActions?.video, callLabel: "Internet call", unansweredCallId: callState.unanswered?.conversationId === selected.id ? callState.unanswered.id : undefined, onDismissCallMessage: calling?.dismissUnanswered }) : _jsxs(MessagingProvider, { ...screen, children: [_jsx(MessagingInbox, { viewMemory: inboxMemory, client: inbox, userId: props.userId, contacts: contacts, companyName: props.companyName, appearance: props.appearance, active: props.active !== false, headerActions: _jsx(MessagingAccountMenu, { onPublicRooms: inboxState.publicRoomsAvailable ? () => setPublicRooms(true) : undefined, onMaps: mapsAvailable ? () => setShowMaps(true) : undefined, onNewGroup: inboxState.groupCreationAvailable ? () => setNewGroup(true) : undefined }), onSelect: open, onDeleted: id => props.client.forgetConversation(id) }), publicRooms && _jsx(MessagingPublicRooms, { inbox: inbox, accent: (props.appearance ?? inboxState.appearance ?? defaultMessagingSettings().appearance).colors.accent, createRequestId: () => props.client.createForwardId(), onClose: () => setPublicRooms(false), onJoined: (id, name) => { setPublicRooms(false); open(inbox.getSnapshot().conversations.find(c => c.id === id) ?? { id, kind: 'group', name, contactId: null, lastMessage: null, unreadCount: 0, canSend: true }); } }), newGroup && _jsx(MessagingGroupEditor, { client: props.client, inbox: inbox, userId: props.userId, contacts: contacts, groups: inboxState.groups, accent: (props.appearance ?? inboxState.appearance ?? defaultMessagingSettings().appearance).colors.accent, onClose: () => setNewGroup(false), onSaved: (id, name) => { setNewGroup(false); const row = inbox.getSnapshot().conversations.find(c => c.id === id) ?? { id, kind: 'group', name, contactId: null, lastMessage: null, unreadCount: 0, canSend: true }; open(row); } })] }) }), _jsx(ExpoMessengerCalls, { calling: calling, state: callState })] });
177
179
  }
package/dist/popup.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
2
3
  import { Text } from './typography.js';
3
4
  import { useRobonoLocale } from './localization.js';
4
5
  import { createContext, useContext, useEffect, useLayoutEffect, useRef, useState, useCallback, useMemo } from 'react';
5
- import { Keyboard, KeyboardAvoidingView, Modal, Platform, Pressable, SafeAreaView, View, useWindowDimensions } from 'react-native';
6
+ import { Keyboard, KeyboardAvoidingView, Modal, Platform, Pressable, SafeAreaView, StatusBar, View, useWindowDimensions } from 'react-native';
6
7
  import { MessageIcon } from './icons.js';
7
8
  /** Browser hosts can keep settings dialogs inside their embedded chat. */
8
9
  export const MessagingPopupSurface = createContext(null);
@@ -97,7 +98,7 @@ export function MessagingMenuSurface({ visible, onClose, onDismiss, children })
97
98
  }
98
99
  /** Content panels occupy the area below the persistent header; confirmations stay compact. */
99
100
  export function MessagingPopup({ children, onClose, background, form = false, visible = true, onDismiss, expanded = true, bottomPadding, title, foreground, closeLabel, closeDisabled = false, contentWidth, headerBack = false, withinChat = false, compact = false }) {
100
- const ChatSurface = useContext(MessagingPopupSurface), bounds = useContext(PopupBounds), viewport = useWindowDimensions();
101
+ const ChatSurface = useContext(MessagingPopupSurface), bounds = useContext(PopupBounds), viewport = useWindowDimensions(), safeInsets = useContext(SafeAreaInsetsContext);
101
102
  const [rect, setRect] = useState(null);
102
103
  useLayoutEffect(() => {
103
104
  if (!visible || Platform.OS === 'web' || !bounds)
@@ -139,7 +140,7 @@ export function MessagingPopup({ children, onClose, background, form = false, vi
139
140
  } }; document.addEventListener('keydown', escape, true); return () => document.removeEventListener('keydown', escape, true); }, [visible]);
140
141
  const bounded = Platform.OS !== 'web' && !!bounds;
141
142
  const Frame = Platform.OS === 'ios' && !bounded ? SafeAreaView : View;
142
- const content = _jsx(Frame, { style: [{ flex: 1, backgroundColor: messagingPopupBackdrop }, bounded ? { position: 'absolute', ...(rect ?? { left: 0, top: 0, width: 0, height: 0 }) } : undefined], children: _jsxs(KeyboardAvoidingView, { testID: "messaging-popup-backdrop", behavior: Platform.OS === 'ios' ? 'padding' : undefined, style: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: compact ? 24 : 8 }, children: [_jsx(Pressable, { accessible: false, onPress: onClose, style: { position: 'absolute', inset: 0 } }), _jsxs(View, { testID: "messaging-popup-panel", accessibilityViewIsModal: true, onAccessibilityEscape: onClose, style: { width: '100%', ...(compact ? { maxWidth: 360, maxHeight: '100%', flexShrink: 1 } : { flex: 1, minHeight: 0 }), backgroundColor: compact ? '#111111' : messagingPopupColors.background, borderRadius: compact ? 20 : 18, paddingHorizontal: compact ? 20 : 8, paddingTop: compact ? 8 : 0, paddingBottom: bottomPadding ?? (compact ? 20 : 4), overflow: 'hidden' }, children: [title && _jsx(MessagingPopupHeader, { title: title, foreground: messagingPopupColors.foreground, onClose: onClose, closeLabel: closeLabel, disabled: closeDisabled, back: headerBack }), contentWidth ? _jsx(View, { style: { ...(compact ? { flexShrink: 1 } : { flex: 1, minHeight: 0 }), width: '100%', maxWidth: contentWidth, alignSelf: 'center', paddingTop: 12 }, children: children }) : children] })] }) });
143
+ const content = _jsx(Frame, { style: [{ flex: 1, backgroundColor: messagingPopupBackdrop }, bounded ? { position: 'absolute', ...(rect ?? { left: 0, top: 0, width: 0, height: 0 }) } : Platform.OS === 'android' ? { paddingTop: safeInsets?.top ?? StatusBar.currentHeight ?? 0, paddingBottom: safeInsets?.bottom ?? 0 } : undefined], children: _jsxs(KeyboardAvoidingView, { testID: "messaging-popup-backdrop", behavior: Platform.OS === 'ios' ? 'padding' : undefined, style: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: compact ? 24 : 8 }, children: [_jsx(Pressable, { accessible: false, onPress: onClose, style: { position: 'absolute', inset: 0 } }), _jsxs(View, { testID: "messaging-popup-panel", accessibilityViewIsModal: true, onAccessibilityEscape: onClose, style: { width: '100%', ...(compact ? { maxWidth: 360, maxHeight: '100%', flexShrink: 1 } : { flex: 1, minHeight: 0 }), backgroundColor: compact ? '#111111' : messagingPopupColors.background, borderRadius: compact ? 20 : 18, paddingHorizontal: compact ? 20 : 8, paddingTop: compact ? 8 : 0, paddingBottom: bottomPadding ?? (compact ? 20 : 4), overflow: 'hidden' }, children: [title && _jsx(MessagingPopupHeader, { title: title, foreground: messagingPopupColors.foreground, onClose: onClose, closeLabel: closeLabel, disabled: closeDisabled, back: headerBack }), contentWidth ? _jsx(View, { style: { ...(compact ? { flexShrink: 1 } : { flex: 1, minHeight: 0 }), width: '100%', maxWidth: contentWidth, alignSelf: 'center', paddingTop: 12 }, children: children }) : children] })] }) });
143
144
  if (Platform.OS === 'web' && ChatSurface)
144
145
  return _jsx(ChatSurface, { visible: visible, title: title, onDismiss: onDismiss, onClose: onClose, children: content });
145
146
  return _jsx(Modal, { visible: visible, statusBarTranslucent: true, navigationBarTranslucent: true, onDismiss: onDismiss, transparent: true, animationType: "fade", onRequestClose: onClose, children: content });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robono/messaging-react-native",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Optional, composable direct and group messaging screens for Robono.",
@@ -55,7 +55,7 @@
55
55
  "prepack": "npm run build && node ../../scripts/generate-sdk-file-sbom.mjs"
56
56
  },
57
57
  "peerDependencies": {
58
- "@robono/messaging": "^0.7.0",
58
+ "@robono/messaging": "^0.7.1",
59
59
  "react": ">=18.2 <20",
60
60
  "react-native": ">=0.76 <1",
61
61
  "expo-audio": ">=55 <57",
@@ -67,7 +67,7 @@
67
67
  "expo-crypto": ">=55 <57",
68
68
  "react-native-safe-area-context": ">=5 <6",
69
69
  "react-native-svg": ">=15 <16",
70
- "@robono/calling-react-native": "^0.8.0",
70
+ "@robono/calling-react-native": "^0.9.0",
71
71
  "expo-clipboard": ">=8 <57",
72
72
  "expo-image-manipulator": ">=14 <57",
73
73
  "expo-video-thumbnails": ">=14 <57",
@@ -80,7 +80,7 @@
80
80
  "expo-constants": ">=55 <57",
81
81
  "@robono/location": "^0.4.0",
82
82
  "expo-haptics": ">=55 <57",
83
- "@robono/calling": "^0.5.0"
83
+ "@robono/calling": "^0.6.0"
84
84
  },
85
85
  "license": "SEE LICENSE IN LICENSE",
86
86
  "peerDependenciesMeta": {