@dialstack/sdk-native 0.1.0 → 0.2.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/README.md +166 -36
- package/dist/index.d.ts +670 -54
- package/dist/index.js +3139 -604
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +361 -0
- package/dist/testing.js +606 -0
- package/dist/testing.js.map +1 -0
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -8,28 +8,10 @@ pieces (`<DialPad>` / `<IncomingCall>` / `<OngoingCall>`).
|
|
|
8
8
|
import { Softphone, SoftphoneProvider } from '@dialstack/sdk-native';
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
This is
|
|
14
|
-
|
|
15
|
-
graph is _structurally_ free of anything React Native — a web app that installs
|
|
16
|
-
`@dialstack/sdk` can never pull `react-native-webrtc`/`-svg`/`-incall-manager`.
|
|
17
|
-
|
|
18
|
-
`@dialstack/sdk-native` is self-contained: it inlines its own compiled copy of
|
|
19
|
-
the shared headless calling core and call-state hooks at build time, so it has
|
|
20
|
-
**no runtime dependency on `@dialstack/sdk`** (the core is authored once in that
|
|
21
|
-
package and shared at build time only). The core is written to the standard
|
|
22
|
-
browser WebRTC surface (`RTCPeerConnection`, `MediaStream`, `navigator.mediaDevices`);
|
|
23
|
-
call `registerGlobals()` from your WebRTC package at your app's entry point so
|
|
24
|
-
that surface exists on React Native before the SDK runs. The two RN-only gaps —
|
|
25
|
-
outbound ringback audio and E911 persistence — are supplied by the softphone
|
|
26
|
-
provider (an InCallManager-backed ringback and your `storage` adapter).
|
|
27
|
-
|
|
28
|
-
```js
|
|
29
|
-
// index.js — before anything imports the SDK
|
|
30
|
-
import { registerGlobals } from 'react-native-webrtc';
|
|
31
|
-
registerGlobals();
|
|
32
|
-
```
|
|
11
|
+
React components for the web live in
|
|
12
|
+
[`@dialstack/sdk-react`](https://www.npmjs.com/package/@dialstack/sdk-react).
|
|
13
|
+
This package is self-contained: install it alone, and nothing from the web SDK
|
|
14
|
+
comes with it.
|
|
33
15
|
|
|
34
16
|
## Install
|
|
35
17
|
|
|
@@ -52,8 +34,99 @@ globals `registerGlobals()` installs, so any spec-compliant build works. The
|
|
|
52
34
|
`react-native-webrtc` peer is **optional** precisely so a differently-named
|
|
53
35
|
package can satisfy it without npm installing a second copy alongside.
|
|
54
36
|
|
|
55
|
-
|
|
56
|
-
|
|
37
|
+
> **Installing our fork from GitHub:** its entry points are generated by its
|
|
38
|
+
> `prepare` script at install time. If your npm or CI blocks install scripts,
|
|
39
|
+
> the package installs without them and the failure surfaces later as
|
|
40
|
+
> `Cannot find module`, not as an install error — allow install scripts for
|
|
41
|
+
> that dependency.
|
|
42
|
+
|
|
43
|
+
### Install the WebRTC globals
|
|
44
|
+
|
|
45
|
+
The SDK core is written to the standard browser WebRTC surface
|
|
46
|
+
(`RTCPeerConnection`, `MediaStream`, `navigator.mediaDevices`), so call
|
|
47
|
+
`registerGlobals()` from your WebRTC package at your app's entry point, before
|
|
48
|
+
anything imports the SDK:
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
// index.js
|
|
52
|
+
import { registerGlobals } from 'react-native-webrtc';
|
|
53
|
+
registerGlobals();
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Two RN-only gaps — outbound ringback audio and E911 persistence — are supplied
|
|
57
|
+
by the softphone provider (an InCallManager-backed ringback and your `storage`
|
|
58
|
+
adapter).
|
|
59
|
+
|
|
60
|
+
## Permissions (required)
|
|
61
|
+
|
|
62
|
+
**Without microphone permission a call fails with `Call failed` and no other
|
|
63
|
+
symptom** — `getUserMedia` never runs, so there is nothing else to see. Declare
|
|
64
|
+
these before your first call.
|
|
65
|
+
|
|
66
|
+
### Expo
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"expo": {
|
|
71
|
+
"plugins": ["@config-plugins/react-native-webrtc"],
|
|
72
|
+
"android": {
|
|
73
|
+
"permissions": [
|
|
74
|
+
"android.permission.RECORD_AUDIO",
|
|
75
|
+
"android.permission.MODIFY_AUDIO_SETTINGS",
|
|
76
|
+
"android.permission.INTERNET",
|
|
77
|
+
"android.permission.ACCESS_NETWORK_STATE",
|
|
78
|
+
"android.permission.VIBRATE"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
"ios": {
|
|
82
|
+
"infoPlist": {
|
|
83
|
+
"NSMicrophoneUsageDescription": "… needs the microphone to place and receive calls.",
|
|
84
|
+
"UIBackgroundModes": ["audio", "voip"]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`react-native-webrtc` is a native module, so it needs a **development build** —
|
|
92
|
+
it cannot run in Expo Go. Re-run `expo prebuild` after changing any of the above.
|
|
93
|
+
|
|
94
|
+
The config plugin also injects permissions of its own that this SDK does not use
|
|
95
|
+
— including `CAMERA`, `SYSTEM_ALERT_WINDOW` and `BLUETOOTH` — and writes a
|
|
96
|
+
default camera-usage string. If a store listing or privacy review asks about
|
|
97
|
+
them, drop the ones you don't need with Expo's
|
|
98
|
+
[`android.blockedPermissions`](https://docs.expo.dev/versions/latest/config/app/#blockedpermissions).
|
|
99
|
+
|
|
100
|
+
### Bare React Native
|
|
101
|
+
|
|
102
|
+
`android/app/src/main/AndroidManifest.xml`:
|
|
103
|
+
|
|
104
|
+
```xml
|
|
105
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
106
|
+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
107
|
+
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
|
108
|
+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
109
|
+
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
110
|
+
<uses-permission android:name="android.permission.VIBRATE" />
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`VIBRATE` is what gives the incoming call its haptic buzz: Android enforces it
|
|
114
|
+
by throwing rather than no-op'ing, so without it the call still rings but never
|
|
115
|
+
vibrates. The SDK catches that throw, so omitting the permission degrades
|
|
116
|
+
gracefully — it does not break the call. Neither `react-native` nor
|
|
117
|
+
`react-native-incall-manager` declares it, so your app must.
|
|
118
|
+
|
|
119
|
+
`ios/<YourApp>/Info.plist`:
|
|
120
|
+
|
|
121
|
+
```xml
|
|
122
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
123
|
+
<string>… needs the microphone to place and receive calls.</string>
|
|
124
|
+
<key>UIBackgroundModes</key>
|
|
125
|
+
<array>
|
|
126
|
+
<string>audio</string>
|
|
127
|
+
<string>voip</string>
|
|
128
|
+
</array>
|
|
129
|
+
```
|
|
57
130
|
|
|
58
131
|
## DTMF
|
|
59
132
|
|
|
@@ -68,7 +141,7 @@ npm install 'github:dialstack/react-native-webrtc#7e9a0eb55e068b2ba452f22e02e4b7
|
|
|
68
141
|
With any other WebRTC package — including LiveKit's and Stream's forks, neither
|
|
69
142
|
of which exposes `RTCRtpSender.dtmf` — `Call.canSendDtmf` is `false`, the in-call
|
|
70
143
|
keypad stays hidden, `Call.sendDtmf()` throws `call_failed`, and the SDK logs
|
|
71
|
-
`[dialstack] DTMF is unavailable …`
|
|
144
|
+
`[dialstack] DTMF is unavailable …` once per app process. Calls are otherwise
|
|
72
145
|
unaffected, but there is no way to navigate an IVR or phone tree. That is the
|
|
73
146
|
tradeoff to weigh if another library forces a particular WebRTC package on you.
|
|
74
147
|
|
|
@@ -96,20 +169,77 @@ const storage: PlatformStorage = {
|
|
|
96
169
|
</SoftphoneProvider>;
|
|
97
170
|
```
|
|
98
171
|
|
|
99
|
-
The Expo `../examples/mobile/expo` app ships this MMKV adapter; the bare
|
|
100
|
-
`../examples/mobile/bare` app ships an AsyncStorage one. Either works —
|
|
101
172
|
`storage` just needs `getItem`/`setItem`/`removeItem`.
|
|
102
173
|
|
|
174
|
+
## Call audio
|
|
175
|
+
|
|
176
|
+
By default `<SoftphoneProvider>` owns call audio through
|
|
177
|
+
`react-native-incall-manager`: it rings inbound calls, plays the outbound
|
|
178
|
+
ringback, and holds the audio session (which also turns on the proximity sensor)
|
|
179
|
+
while a call is connected. The ringback goes only to a phone the provider creates
|
|
180
|
+
from `token`: a phone passed as `existingPhone` keeps the `ringback` it was
|
|
181
|
+
created with, so pass one to `createPhone({ ringback })` yourself.
|
|
182
|
+
|
|
183
|
+
When the operating system owns the call instead, because you report calls to
|
|
184
|
+
CallKit or Telecom, the provider must stay out of it: the OS rings the phone and
|
|
185
|
+
activates the session itself, and ringing on top of it costs the answered call
|
|
186
|
+
its microphone. Set `callAudio="host"` and the provider does none of the above.
|
|
187
|
+
With a `bridge` it is always `"host"`. Anything you still want, such as the proximity
|
|
188
|
+
sensor, is then yours to drive (`InCallManager.startProximitySensor()` does not
|
|
189
|
+
touch the audio session).
|
|
190
|
+
|
|
191
|
+
The output route is yours too. Without an `audioOutput` controller the in-call
|
|
192
|
+
speaker button is disabled. To enable it, pass one built over your OS integration:
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
const audioOutput: AudioOutputController = {
|
|
196
|
+
current: () => lastReportedRoute, // what the OS reports, same object until it changes
|
|
197
|
+
onChange: (listener) => subscribeToRouteChanges(listener),
|
|
198
|
+
setSpeaker: (on) => requestSpeaker(on),
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
<SoftphoneProvider bridge={bridge} audioOutput={audioOutput}>
|
|
202
|
+
…
|
|
203
|
+
</SoftphoneProvider>;
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The button shows only the route the OS reports. A switch made from the CallKit
|
|
207
|
+
or Telecom UI, a headset, or a car therefore shows up in-app as well.
|
|
208
|
+
`useAudioOutput()` exposes the same state if you build your own UI.
|
|
209
|
+
|
|
210
|
+
## Example apps
|
|
211
|
+
|
|
212
|
+
Three runnable apps, all wired with the permissions and plugin above:
|
|
213
|
+
|
|
214
|
+
- [Expo](https://github.com/dialstack/dialstack-sdk/tree/main/examples/mobile/expo)
|
|
215
|
+
— ships the MMKV storage adapter
|
|
216
|
+
- [Bare React Native](https://github.com/dialstack/dialstack-sdk/tree/main/examples/mobile/bare)
|
|
217
|
+
— ships an AsyncStorage adapter
|
|
218
|
+
- [OS integration](https://github.com/dialstack/dialstack-sdk/tree/main/examples/mobile/os-integration)
|
|
219
|
+
— the worked example for the OS call surface: calls on a locked or killed
|
|
220
|
+
phone via push wake, `OsCallAdapter` over expo-callkit-telecom. Built and
|
|
221
|
+
verified on both Android (FCM + Telecom) and iOS (PushKit + CallKit).
|
|
222
|
+
|
|
103
223
|
## Scope
|
|
104
224
|
|
|
105
|
-
This package provides the in-app calling surface
|
|
106
|
-
and the React Native UI
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
225
|
+
This package provides the in-app calling surface — the provider, the call state,
|
|
226
|
+
and the React Native UI — plus the bridge between the SDK and the operating
|
|
227
|
+
system's own call experience.
|
|
228
|
+
|
|
229
|
+
Binding the two remains the integrator's work, but not from scratch:
|
|
230
|
+
`NativeCallBridge` keeps an OS call session and an SDK `Call` in step in both
|
|
231
|
+
directions, and `OsCallAdapter` is the interface it drives, so the part you write
|
|
232
|
+
is one adapter over your chosen call library rather than the whole state machine.
|
|
233
|
+
A contract suite in `@dialstack/sdk-native/testing` exercises an adapter against
|
|
234
|
+
the behaviours the bridge depends on.
|
|
235
|
+
|
|
236
|
+
What the package does not ship is the platform wiring itself: the native call
|
|
237
|
+
library (iOS PushKit + CallKit, Android FCM + Telecom/ConnectionService) and the
|
|
238
|
+
push delivery. DialStack stores no device tokens — arranging the push that wakes
|
|
239
|
+
your app is yours. The OS-integration example above is a complete worked wiring
|
|
240
|
+
of both on Android.
|
|
110
241
|
|
|
111
|
-
The seams
|
|
242
|
+
The lower-level seams stay public too: `useSoftphone()` exposes `calls`,
|
|
112
243
|
`incomingCalls`, `activeCall` and `actions.callActionsFor(call)`, and every
|
|
113
|
-
`Call` carries a stable `id` and `state`, so
|
|
114
|
-
|
|
115
|
-
no device tokens — delivering the push that wakes your app is yours to arrange.
|
|
244
|
+
`Call` carries a stable `id` and `state`, so an integrator who wants to drive the
|
|
245
|
+
OS session directly can still do so.
|