@stream-io/video-react-native-sdk 1.45.1 → 1.46.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 +36 -2
- package/android/build.gradle +7 -1
- package/dist/commonjs/components/Call/CallContent/CallContent.js +1 -1
- package/dist/commonjs/components/Call/CallContent/CallContent.js.map +1 -1
- package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js +78 -7
- package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
- package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js +3 -0
- package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
- package/dist/commonjs/components/Call/RingingCallContent/OutgoingCall.js +4 -12
- package/dist/commonjs/components/Call/RingingCallContent/OutgoingCall.js.map +1 -1
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +27 -4
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
- package/dist/commonjs/index.js +14 -1
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/modules/encryption/EncryptionManager.js +305 -0
- package/dist/commonjs/modules/encryption/EncryptionManager.js.map +1 -0
- package/dist/commonjs/modules/encryption/eventMapping.js +143 -0
- package/dist/commonjs/modules/encryption/eventMapping.js.map +1 -0
- package/dist/commonjs/modules/encryption/index.js +13 -0
- package/dist/commonjs/modules/encryption/index.js.map +1 -0
- package/dist/commonjs/modules/encryption/parity.js +6 -0
- package/dist/commonjs/modules/encryption/parity.js.map +1 -0
- package/dist/commonjs/utils/StreamVideoRN/index.js +23 -0
- package/dist/commonjs/utils/StreamVideoRN/index.js.map +1 -1
- package/dist/commonjs/utils/internal/callingx/callingx.js +29 -4
- package/dist/commonjs/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/commonjs/utils/internal/iosPipTrack.js +41 -0
- package/dist/commonjs/utils/internal/iosPipTrack.js.map +1 -0
- package/dist/commonjs/utils/internal/registerSDKGlobals.js +6 -0
- package/dist/commonjs/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/commonjs/utils/internal/ringingCallLifecycle.js +142 -0
- package/dist/commonjs/utils/internal/ringingCallLifecycle.js.map +1 -0
- package/dist/commonjs/utils/push/internal/utils.js +4 -0
- package/dist/commonjs/utils/push/internal/utils.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/module/components/Call/CallContent/CallContent.js +1 -1
- package/dist/module/components/Call/CallContent/CallContent.js.map +1 -1
- package/dist/module/components/Call/CallContent/RTCViewPipIOS.js +80 -9
- package/dist/module/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
- package/dist/module/components/Call/CallContent/RTCViewPipNative.js +3 -0
- package/dist/module/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
- package/dist/module/components/Call/RingingCallContent/OutgoingCall.js +4 -12
- package/dist/module/components/Call/RingingCallContent/OutgoingCall.js.map +1 -1
- package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +28 -5
- package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
- package/dist/module/index.js +5 -0
- package/dist/module/index.js.map +1 -1
- package/dist/module/modules/encryption/EncryptionManager.js +301 -0
- package/dist/module/modules/encryption/EncryptionManager.js.map +1 -0
- package/dist/module/modules/encryption/eventMapping.js +135 -0
- package/dist/module/modules/encryption/eventMapping.js.map +1 -0
- package/dist/module/modules/encryption/index.js +4 -0
- package/dist/module/modules/encryption/index.js.map +1 -0
- package/dist/module/modules/encryption/parity.js +4 -0
- package/dist/module/modules/encryption/parity.js.map +1 -0
- package/dist/module/utils/StreamVideoRN/index.js +23 -0
- package/dist/module/utils/StreamVideoRN/index.js.map +1 -1
- package/dist/module/utils/internal/callingx/callingx.js +29 -4
- package/dist/module/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/module/utils/internal/iosPipTrack.js +35 -0
- package/dist/module/utils/internal/iosPipTrack.js.map +1 -0
- package/dist/module/utils/internal/registerSDKGlobals.js +6 -0
- package/dist/module/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/module/utils/internal/ringingCallLifecycle.js +134 -0
- package/dist/module/utils/internal/ringingCallLifecycle.js.map +1 -0
- package/dist/module/utils/push/internal/utils.js +4 -0
- package/dist/module/utils/push/internal/utils.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts +12 -0
- package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts.map +1 -1
- package/dist/typescript/components/Call/RingingCallContent/OutgoingCall.d.ts.map +1 -1
- package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.d.ts +11 -0
- package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.d.ts.map +1 -1
- package/dist/typescript/index.d.ts +1 -0
- package/dist/typescript/index.d.ts.map +1 -1
- package/dist/typescript/modules/encryption/EncryptionManager.d.ts +190 -0
- package/dist/typescript/modules/encryption/EncryptionManager.d.ts.map +1 -0
- package/dist/typescript/modules/encryption/eventMapping.d.ts +38 -0
- package/dist/typescript/modules/encryption/eventMapping.d.ts.map +1 -0
- package/dist/typescript/modules/encryption/index.d.ts +2 -0
- package/dist/typescript/modules/encryption/index.d.ts.map +1 -0
- package/dist/typescript/modules/encryption/parity.d.ts +55 -0
- package/dist/typescript/modules/encryption/parity.d.ts.map +1 -0
- package/dist/typescript/utils/StreamVideoRN/index.d.ts +20 -1
- package/dist/typescript/utils/StreamVideoRN/index.d.ts.map +1 -1
- package/dist/typescript/utils/StreamVideoRN/types.d.ts +67 -0
- package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
- package/dist/typescript/utils/internal/callingx/callingx.d.ts +1 -1
- package/dist/typescript/utils/internal/callingx/callingx.d.ts.map +1 -1
- package/dist/typescript/utils/internal/iosPipTrack.d.ts +9 -0
- package/dist/typescript/utils/internal/iosPipTrack.d.ts.map +1 -0
- package/dist/typescript/utils/internal/registerSDKGlobals.d.ts.map +1 -1
- package/dist/typescript/utils/internal/ringingCallLifecycle.d.ts +27 -0
- package/dist/typescript/utils/internal/ringingCallLifecycle.d.ts.map +1 -0
- package/dist/typescript/utils/push/internal/utils.d.ts.map +1 -1
- package/dist/typescript/version.d.ts +1 -1
- package/ios/RTCViewPip.swift +33 -8
- package/ios/RTCViewPipManager.mm +1 -0
- package/package.json +8 -8
- package/src/components/Call/CallContent/CallContent.tsx +1 -1
- package/src/components/Call/CallContent/RTCViewPipIOS.tsx +92 -10
- package/src/components/Call/CallContent/RTCViewPipNative.tsx +13 -0
- package/src/components/Call/RingingCallContent/OutgoingCall.tsx +4 -12
- package/src/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.tsx +58 -5
- package/src/index.ts +5 -0
- package/src/modules/encryption/EncryptionManager.ts +360 -0
- package/src/modules/encryption/eventMapping.ts +151 -0
- package/src/modules/encryption/index.ts +1 -0
- package/src/modules/encryption/parity.ts +83 -0
- package/src/utils/StreamVideoRN/index.ts +24 -1
- package/src/utils/StreamVideoRN/types.ts +68 -0
- package/src/utils/internal/callingx/callingx.ts +37 -2
- package/src/utils/internal/iosPipTrack.ts +39 -0
- package/src/utils/internal/registerSDKGlobals.ts +2 -0
- package/src/utils/internal/ringingCallLifecycle.ts +148 -0
- package/src/utils/push/internal/utils.ts +4 -0
- package/src/version.ts +1 -1
|
@@ -19,6 +19,74 @@ export type KeepAliveAndroidNotificationTexts = {
|
|
|
19
19
|
export type NonRingingPushEvent =
|
|
20
20
|
'call.live_started' | 'call.notification' | 'call.missed';
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Hooks that run around a ringing call's join, wherever that join is triggered.
|
|
24
|
+
*
|
|
25
|
+
* A ringing call is joined by the SDK, not by your code: the accept button joins
|
|
26
|
+
* internally, and an outgoing call joins itself once the callee answers. That
|
|
27
|
+
* leaves no moment in which app code holds the call and the join has not yet
|
|
28
|
+
* happened - which is a problem for anything that must be installed first, most
|
|
29
|
+
* notably `call.setE2EEManager()`, since it throws once a call has peer
|
|
30
|
+
* connections.
|
|
31
|
+
*
|
|
32
|
+
* These hooks are that moment. They run for every ringing path: accepted from the
|
|
33
|
+
* CallKit/Telecom UI, accepted inside the app, and outgoing.
|
|
34
|
+
*
|
|
35
|
+
* Register them with {@link StreamVideoRN.setRingingCallLifecycleHooks} at your app's
|
|
36
|
+
* entry point, next to `setPushConfig`. A call accepted from a push notification
|
|
37
|
+
* can be created and joined before any component mounts - the app may be launched
|
|
38
|
+
* from a killed state - so registering them from inside React is too late.
|
|
39
|
+
*/
|
|
40
|
+
export type RingingCallLifecycleHooks = {
|
|
41
|
+
/**
|
|
42
|
+
* Awaited by `Call.join()` for a ringing call, before any peer connection exists.
|
|
43
|
+
* **Throwing aborts the join** and the call is not entered.
|
|
44
|
+
*
|
|
45
|
+
* That is deliberate: joining without whatever this hook installs would, in the
|
|
46
|
+
* E2EE case, publish unencrypted media on a call the user believes is private.
|
|
47
|
+
* On the push path there is no UI to reveal it, so the join fails closed instead.
|
|
48
|
+
* A failed join also ends the call, so the `Call` it failed on is finished with -
|
|
49
|
+
* a later attempt happens on a new incoming call or a newly created one.
|
|
50
|
+
*
|
|
51
|
+
* Keep it fast. On the CallKit accept path it runs inside iOS's accept deadline
|
|
52
|
+
* (roughly 30s before the app is killed) and anything slower than five seconds is
|
|
53
|
+
* treated as a failure. Deriving a key locally is fine; fetching one over the
|
|
54
|
+
* network does not belong here.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* onBeforeCallJoin: async (call) => {
|
|
58
|
+
* await attachE2EEIfConfigured(call);
|
|
59
|
+
* }
|
|
60
|
+
*/
|
|
61
|
+
onBeforeCallJoin?: (call: Call) => Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Called once, when a ringing call is finished with - it left, whether because
|
|
64
|
+
* the user hung up, the other side ended it, or its join failed and the SDK
|
|
65
|
+
* ended the flow.
|
|
66
|
+
*
|
|
67
|
+
* Use it to free per-call resources the SDK does not own. An E2EE manager is the
|
|
68
|
+
* motivating case: it has no native detach, closing the peer connections does not
|
|
69
|
+
* free it, and on the push path the call can end while the app is still in the
|
|
70
|
+
* background, where no React cleanup ever runs.
|
|
71
|
+
*
|
|
72
|
+
* When an {@link onBeforeCallJoin} is also registered, this is only called for a
|
|
73
|
+
* call that hook actually entered, so it always pairs with a setup that happened -
|
|
74
|
+
* a call that rings and is then rejected, cancelled or missed was never set up and
|
|
75
|
+
* releases nothing. Registered on its own it has no such pairing to honour and is
|
|
76
|
+
* called for every ringing call that ends. Either way it is tied to the call
|
|
77
|
+
* ending rather than to any view's lifetime, so navigating away from a live call
|
|
78
|
+
* does not release it.
|
|
79
|
+
*
|
|
80
|
+
* If the setup hook is still running when the call ends - it timed out, say - the
|
|
81
|
+
* release waits for it to settle, so it cannot free something that hook is about
|
|
82
|
+
* to create.
|
|
83
|
+
*
|
|
84
|
+
* May return a promise; rejections are logged. Nothing is gated on it, so do not
|
|
85
|
+
* rely on it completing before the OS suspends the app.
|
|
86
|
+
*/
|
|
87
|
+
onAfterCallLeave?: (call: Call) => void | Promise<void>;
|
|
88
|
+
};
|
|
89
|
+
|
|
22
90
|
export type StreamVideoConfig = {
|
|
23
91
|
/**
|
|
24
92
|
* The configuration to be used for push notifications.
|
|
@@ -67,9 +67,14 @@ function getCallingxCallArgs(call: Call): [string, string, string, boolean] {
|
|
|
67
67
|
call.state.participants,
|
|
68
68
|
call.currentUserId,
|
|
69
69
|
);
|
|
70
|
+
const createdByHandle = call.state.createdBy?.id ?? callDisplayName;
|
|
70
71
|
return [
|
|
71
72
|
call.cid, // unique id for call
|
|
72
|
-
|
|
73
|
+
// handle for the native call UI. On iOS it is shown in CallKit Recents and used for
|
|
74
|
+
// call-back, so we send the caller's user id (matches the Swift SDK). On Android the
|
|
75
|
+
// self-managed Telecom address is never displayed and callingx wraps it as
|
|
76
|
+
// `<packageName>:<handle>`, so we send the call id (matches the Android SDK).
|
|
77
|
+
Platform.OS === 'android' ? call.id : createdByHandle,
|
|
73
78
|
callDisplayName, // display name for display in call screen
|
|
74
79
|
call.state.settings?.video?.enabled ?? false, // is video call?
|
|
75
80
|
];
|
|
@@ -110,7 +115,11 @@ export async function registerOutgoingCall(call: Call) {
|
|
|
110
115
|
* 2. Displays the incoming call in the callingx library
|
|
111
116
|
* 3. Optionally for non-ringing calls also when ongoing calls are enabled.
|
|
112
117
|
*/
|
|
113
|
-
export async function joinCallingxCall(
|
|
118
|
+
export async function joinCallingxCall(
|
|
119
|
+
call: Call,
|
|
120
|
+
activeCalls: Call[],
|
|
121
|
+
isCancelled?: () => boolean,
|
|
122
|
+
) {
|
|
114
123
|
if (
|
|
115
124
|
!CallingxModule ||
|
|
116
125
|
!CallingxModule.isSetup ||
|
|
@@ -146,12 +155,25 @@ export async function joinCallingxCall(call: Call, activeCalls: Call[]) {
|
|
|
146
155
|
logger.error(`failed to leave active call ${activeCall.cid}`, e);
|
|
147
156
|
});
|
|
148
157
|
}
|
|
158
|
+
// Leaving the other calls above can take arbitrarily long, and this join may
|
|
159
|
+
// have been cancelled meanwhile. Registering now would create a native call
|
|
160
|
+
// nobody owns. The caller decides, not the call's state: a `Call` reused for
|
|
161
|
+
// a fresh ring is still `LEFT` here, because `setup()` runs after this.
|
|
162
|
+
if (isCancelled?.()) {
|
|
163
|
+
logger.debug(
|
|
164
|
+
`joinCallingxCall: skipping registration for ${call.cid}: join cancelled while waiting for other calls`,
|
|
165
|
+
);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
149
168
|
logger.debug(
|
|
150
169
|
`joinCallingxCall: Joining call ${call.cid} isIncoming: ${isIncomingCall} isOutgoing: ${isOutcomingCall}`,
|
|
151
170
|
);
|
|
152
171
|
const callArgs = getCallingxCallArgs(call);
|
|
153
172
|
if (isIncomingCall) {
|
|
154
173
|
await CallingxModule.displayIncomingCall(...callArgs);
|
|
174
|
+
// never answer a call that was hung up while the OS was displaying it -
|
|
175
|
+
// the cleanup below ends the registration instead
|
|
176
|
+
if (isCancelled?.()) return;
|
|
155
177
|
await CallingxModule.answerIncomingCall(call.cid);
|
|
156
178
|
} else {
|
|
157
179
|
await CallingxModule.startCall(...callArgs);
|
|
@@ -161,6 +183,19 @@ export async function joinCallingxCall(call: Call, activeCalls: Call[]) {
|
|
|
161
183
|
`startCallingxCall: Error starting call in callingx: ${call.cid} isIncoming: ${isIncomingCall} isOutgoing: ${isOutcomingCall}`,
|
|
162
184
|
error,
|
|
163
185
|
);
|
|
186
|
+
} finally {
|
|
187
|
+
// The registration above can outlive the join that asked for it: `leave()`
|
|
188
|
+
// may land while native is still bringing the call up, and by the time the
|
|
189
|
+
// caller aborts, the call exists natively with nobody to end it. Ringing
|
|
190
|
+
// calls have a lifecycle owner that would clean up; ordinary ones do not.
|
|
191
|
+
// Nothing was registered when the check above already skipped it, and
|
|
192
|
+
// `endCallingxCall` no-ops for an untracked cid, so this is safe either way.
|
|
193
|
+
if (isCancelled?.()) {
|
|
194
|
+
logger.debug(
|
|
195
|
+
`joinCallingxCall: ending ${call.cid}: join was cancelled while registering`,
|
|
196
|
+
);
|
|
197
|
+
await endCallingxCall(call, 'canceled');
|
|
198
|
+
}
|
|
164
199
|
}
|
|
165
200
|
}
|
|
166
201
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Call,
|
|
3
|
+
DebounceType,
|
|
4
|
+
type VideoTrackType,
|
|
5
|
+
} from '@stream-io/video-client';
|
|
6
|
+
import { BehaviorSubject } from 'rxjs';
|
|
7
|
+
|
|
8
|
+
export type PipTrack = { sessionId: string; trackType: VideoTrackType };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The track currently rendered by the native iOS Picture in Picture window,
|
|
12
|
+
* per Call instance. While set, the window's bounds are the demand of that
|
|
13
|
+
* track and the inline views of the same track stop requesting their layout.
|
|
14
|
+
*/
|
|
15
|
+
const tracks = new WeakMap<Call, BehaviorSubject<PipTrack | undefined>>();
|
|
16
|
+
|
|
17
|
+
export const getIosPipTrack$ = (call: Call) => {
|
|
18
|
+
let track$ = tracks.get(call);
|
|
19
|
+
if (!track$) {
|
|
20
|
+
track$ = new BehaviorSubject<PipTrack | undefined>(undefined);
|
|
21
|
+
tracks.set(call, track$);
|
|
22
|
+
}
|
|
23
|
+
return track$;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const setIosPipTrack = (call: Call, track: PipTrack | undefined) => {
|
|
27
|
+
const track$ = getIosPipTrack$(call);
|
|
28
|
+
const previous = track$.getValue();
|
|
29
|
+
if (previous) {
|
|
30
|
+
// Clear before opening the gate: mounted inline views replay their layout
|
|
31
|
+
// synchronously on the gate change and overwrite this. If none is mounted,
|
|
32
|
+
// the cleared demand stands.
|
|
33
|
+
call.state.updateParticipantTracks(previous.trackType, {
|
|
34
|
+
[previous.sessionId]: { dimension: undefined },
|
|
35
|
+
});
|
|
36
|
+
call.trackSubscriptionManager.apply(DebounceType.MEDIUM);
|
|
37
|
+
}
|
|
38
|
+
track$.next(track);
|
|
39
|
+
};
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
unwireAudioEngineSubscription,
|
|
17
17
|
} from './callingx/callingx';
|
|
18
18
|
import { registerCallMediaEngine } from './registerMediaEngine';
|
|
19
|
+
import { beforeJoin, onJoinFailed, onLeave } from './ringingCallLifecycle';
|
|
19
20
|
import { callManager as publicCallManager } from '../../modules/call-manager';
|
|
20
21
|
|
|
21
22
|
const StreamInCallManagerNativeModule = NativeModules.StreamInCallManager;
|
|
@@ -91,6 +92,7 @@ const streamRNVideoSDKGlobals: StreamRNVideoSDKGlobals = {
|
|
|
91
92
|
wireAudioEngineSubscription: wireAudioEngineSubscription,
|
|
92
93
|
unwireAudioEngineSubscription: unwireAudioEngineSubscription,
|
|
93
94
|
},
|
|
95
|
+
ringingCallLifecycle: { beforeJoin, onJoinFailed, onLeave },
|
|
94
96
|
callManager: {
|
|
95
97
|
setup: ({ defaultDevice, isRingingTypeCall, cid }) => {
|
|
96
98
|
const isTelecomManaged = isAndroidTelecomManaged({ cid });
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CallingState,
|
|
3
|
+
videoLoggerSystem,
|
|
4
|
+
type Call,
|
|
5
|
+
} from '@stream-io/video-client';
|
|
6
|
+
import type { RingingCallLifecycleHooks } from '../StreamVideoRN/types';
|
|
7
|
+
|
|
8
|
+
const logger = videoLoggerSystem.getLogger('ringingCallLifecycle');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* How long `onBeforeCallJoin` may take before the join gives up on it.
|
|
12
|
+
*
|
|
13
|
+
* The hook runs inside the CallKit accept, which iOS gives a hard deadline of
|
|
14
|
+
* roughly 30s before killing the app. Failing well short of that leaves room to
|
|
15
|
+
* report the failure and end the native call cleanly, and a hook that legitimately
|
|
16
|
+
* needs longer than this does not belong on the accept path at all.
|
|
17
|
+
*/
|
|
18
|
+
const ON_BEFORE_CALL_JOIN_TIMEOUT_MS = 5_000;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The registered hooks.
|
|
22
|
+
*
|
|
23
|
+
* Held here rather than on `StreamVideoRN.config` so the push internals and the
|
|
24
|
+
* globals bridge can both reach them without importing the `StreamVideoRN`
|
|
25
|
+
* class, which would close an import cycle through the push setup modules.
|
|
26
|
+
*/
|
|
27
|
+
let hooks: RingingCallLifecycleHooks | undefined;
|
|
28
|
+
|
|
29
|
+
export const setRingingCallLifecycleHooks = (
|
|
30
|
+
next: RingingCallLifecycleHooks,
|
|
31
|
+
) => {
|
|
32
|
+
hooks = next;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* How to release what a call's setup hook installed.
|
|
37
|
+
*
|
|
38
|
+
* Kept per call rather than in a single current-call slot: an abandoned call's
|
|
39
|
+
* hook can still finish after the next call has joined, and it has to release
|
|
40
|
+
* its own manager rather than whatever is current. An entry exists only for a
|
|
41
|
+
* call whose setup hook actually ran.
|
|
42
|
+
*/
|
|
43
|
+
const cleanups = new WeakMap<Call, () => void>();
|
|
44
|
+
|
|
45
|
+
/** Invokes the release hook, swallowing whatever it throws. */
|
|
46
|
+
const fireRelease = (call: Call): void => {
|
|
47
|
+
const hook = hooks?.onAfterCallLeave;
|
|
48
|
+
if (!hook) return;
|
|
49
|
+
try {
|
|
50
|
+
Promise.resolve(hook(call)).catch((e) => {
|
|
51
|
+
logger.warn(`onAfterCallLeave failed for callCid: ${call.cid}`, e);
|
|
52
|
+
});
|
|
53
|
+
} catch (e) {
|
|
54
|
+
logger.warn(`onAfterCallLeave threw for callCid: ${call.cid}`, e);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** Releases what the setup hook installed, at most once. */
|
|
59
|
+
const requestRelease = (call: Call): void => {
|
|
60
|
+
const cleanup = cleanups.get(call);
|
|
61
|
+
if (!cleanup) return;
|
|
62
|
+
// Dropped before it runs: that is what makes a failed join followed by a
|
|
63
|
+
// leave release exactly once.
|
|
64
|
+
cleanups.delete(call);
|
|
65
|
+
cleanup();
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Runs the app's pre-join setup, bounded by the deadline.
|
|
70
|
+
*
|
|
71
|
+
* Rejecting fails the join closed - joining without whatever this installs would
|
|
72
|
+
* publish unencrypted media on a call the user believes is private.
|
|
73
|
+
*/
|
|
74
|
+
export const beforeJoin = (call: Call): Promise<void> => {
|
|
75
|
+
const hook = hooks?.onBeforeCallJoin;
|
|
76
|
+
if (!hook) return Promise.resolve();
|
|
77
|
+
|
|
78
|
+
// Invoked synchronously - a caller expects its hook to start now - but a
|
|
79
|
+
// synchronous throw becomes a rejection so it cannot escape the bookkeeping.
|
|
80
|
+
let started: Promise<void>;
|
|
81
|
+
try {
|
|
82
|
+
started = Promise.resolve(hook(call));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
started = Promise.reject(error);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Cleanup tracks the hook's own promise, never the bounded wait below. The
|
|
88
|
+
// deadline ends this join's wait but cannot cancel the app's work, so a
|
|
89
|
+
// manager the hook creates late still has an owner waiting to release it.
|
|
90
|
+
const settled = started.catch(() => {});
|
|
91
|
+
cleanups.set(call, () => void settled.then(() => fireRelease(call)));
|
|
92
|
+
|
|
93
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
94
|
+
const expiry = new Promise<never>((_, reject) => {
|
|
95
|
+
timeout = setTimeout(
|
|
96
|
+
() =>
|
|
97
|
+
reject(
|
|
98
|
+
new Error(
|
|
99
|
+
`onBeforeCallJoin did not settle within ${ON_BEFORE_CALL_JOIN_TIMEOUT_MS}ms`,
|
|
100
|
+
),
|
|
101
|
+
),
|
|
102
|
+
ON_BEFORE_CALL_JOIN_TIMEOUT_MS,
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return Promise.race([started, expiry]).finally(() => clearTimeout(timeout));
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The join failed and will not be retried on this call.
|
|
111
|
+
*
|
|
112
|
+
* Ends the ringing flow rather than leaving the call sitting in `RINGING`: one
|
|
113
|
+
* `Call` is one call flow, so the accept button must not be able to offer this
|
|
114
|
+
* instance again. Never rejects, so the original join error survives.
|
|
115
|
+
*/
|
|
116
|
+
export const onJoinFailed = async (call: Call): Promise<void> => {
|
|
117
|
+
if (call.state.callingState !== CallingState.LEFT) {
|
|
118
|
+
try {
|
|
119
|
+
// Takes the call out of the client's list, which unmounts the ringing UI.
|
|
120
|
+
// `reject: false`: the join failed locally, so this is not the callee
|
|
121
|
+
// declining, and the native call has already been ended with 'error'.
|
|
122
|
+
await call.leave({ reject: false });
|
|
123
|
+
} catch (error) {
|
|
124
|
+
logger.warn(`failed to leave after a failed join: ${call.cid}`, error);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// The leave above notifies {@link onLeave}, which consumes the cleanup. This
|
|
128
|
+
// covers what it could not: a call already left, or a leave that itself failed.
|
|
129
|
+
requestRelease(call);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The call has ended.
|
|
134
|
+
*
|
|
135
|
+
* Releases what the setup hook installed, once the hook has actually settled -
|
|
136
|
+
* a hook still running may yet install something, and releasing before it
|
|
137
|
+
* finishes would strand that.
|
|
138
|
+
*/
|
|
139
|
+
export const onLeave = (call: Call): void => {
|
|
140
|
+
// A release-only registration has nothing to pair with, so its hook belongs to
|
|
141
|
+
// the call ending rather than to a join: it is owed for every ringing call
|
|
142
|
+
// that ends, including one declined without ever joining.
|
|
143
|
+
if (!hooks?.onBeforeCallJoin) {
|
|
144
|
+
fireRelease(call);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
requestRelease(call);
|
|
148
|
+
};
|
|
@@ -128,6 +128,10 @@ export const processCallFromPushInBackground = async (
|
|
|
128
128
|
'processCallFromPushInBackground: failed to join call from push notification',
|
|
129
129
|
e,
|
|
130
130
|
);
|
|
131
|
+
// Cleanup is not repeated here: `join()`'s own failure boundary already
|
|
132
|
+
// released whatever the pre-join hook installed, and a second call would
|
|
133
|
+
// invoke a release-only registration twice.
|
|
134
|
+
onIOSActionCanBeFulfilled(true);
|
|
131
135
|
}
|
|
132
136
|
} else if (action === 'decline') {
|
|
133
137
|
const alreadyLeft = callFromPush.state.callingState === CallingState.LEFT;
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.
|
|
1
|
+
export const version = '1.46.0';
|