@stream-io/video-react-native-sdk 1.45.0 → 2.0.0-beta.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 +8 -7
- package/android/src/main/java/com/streamvideo/reactnative/keepalive/StreamCallKeepAliveHeadlessService.kt +112 -26
- package/android/src/main/java/com/streamvideo/reactnative/util/CallAlivePermissionsHelper.kt +68 -37
- package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js +3 -1
- package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js.map +1 -1
- package/dist/commonjs/hooks/useAndroidKeepCallAliveEffect.js +38 -11
- package/dist/commonjs/hooks/useAndroidKeepCallAliveEffect.js.map +1 -1
- package/dist/commonjs/utils/internal/callingx/callingx.js +37 -55
- package/dist/commonjs/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/commonjs/utils/keepCallAliveHeadlessTask.js +43 -4
- package/dist/commonjs/utils/keepCallAliveHeadlessTask.js.map +1 -1
- package/dist/commonjs/utils/push/internal/android.js +28 -10
- package/dist/commonjs/utils/push/internal/android.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/commonjs/version.js.map +1 -1
- package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js +3 -1
- package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js.map +1 -1
- package/dist/module/hooks/useAndroidKeepCallAliveEffect.js +39 -12
- package/dist/module/hooks/useAndroidKeepCallAliveEffect.js.map +1 -1
- package/dist/module/utils/internal/callingx/callingx.js +37 -55
- package/dist/module/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/module/utils/keepCallAliveHeadlessTask.js +41 -3
- package/dist/module/utils/keepCallAliveHeadlessTask.js.map +1 -1
- package/dist/module/utils/push/internal/android.js +28 -10
- package/dist/module/utils/push/internal/android.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/module/version.js.map +1 -1
- package/dist/typescript/hooks/push/useCallingExpWithCallingStateEffect.d.ts.map +1 -1
- package/dist/typescript/hooks/useAndroidKeepCallAliveEffect.d.ts.map +1 -1
- package/dist/typescript/utils/StreamVideoRN/types.d.ts +1 -2
- package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
- package/dist/typescript/utils/internal/callingx/callingx.d.ts.map +1 -1
- package/dist/typescript/utils/keepCallAliveHeadlessTask.d.ts +9 -0
- package/dist/typescript/utils/keepCallAliveHeadlessTask.d.ts.map +1 -1
- package/dist/typescript/utils/push/internal/android.d.ts.map +1 -1
- package/dist/typescript/version.d.ts +1 -1
- package/dist/typescript/version.d.ts.map +1 -1
- package/expo-config-plugin/dist/withAndroidPermissions.js +1 -1
- package/package.json +4 -4
- package/src/hooks/push/useCallingExpWithCallingStateEffect.ts +13 -6
- package/src/hooks/useAndroidKeepCallAliveEffect.ts +59 -21
- package/src/utils/StreamVideoRN/types.ts +1 -2
- package/src/utils/internal/callingx/callingx.ts +47 -66
- package/src/utils/keepCallAliveHeadlessTask.ts +45 -3
- package/src/utils/push/internal/android.ts +31 -10
- package/src/version.ts +1 -1
|
@@ -176,8 +176,7 @@ export type StreamVideoConfig = {
|
|
|
176
176
|
*/
|
|
177
177
|
notificationTexts: KeepAliveAndroidNotificationTexts;
|
|
178
178
|
/**
|
|
179
|
-
* The task to run in the foreground service
|
|
180
|
-
* The task must resolve a promise once complete
|
|
179
|
+
* The task to run in the foreground service.
|
|
181
180
|
*/
|
|
182
181
|
taskToRun: (call: Call) => Promise<void>;
|
|
183
182
|
};
|
|
@@ -55,15 +55,24 @@ export function getCallDisplayName(
|
|
|
55
55
|
return names.sort().join(', ');
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Args shared by the callingx call registration APIs:
|
|
60
|
+
* `(callId, phoneNumber, callerName, hasVideo)`.
|
|
61
|
+
*/
|
|
62
|
+
function getCallingxCallArgs(call: Call): [string, string, string, boolean] {
|
|
63
|
+
const callDisplayName =
|
|
60
64
|
call.state.custom?.display_name ||
|
|
61
65
|
getCallDisplayName(
|
|
62
66
|
call.state.members,
|
|
63
67
|
call.state.participants,
|
|
64
68
|
call.currentUserId,
|
|
65
|
-
)
|
|
66
|
-
|
|
69
|
+
);
|
|
70
|
+
return [
|
|
71
|
+
call.cid, // unique id for call
|
|
72
|
+
call.state.createdBy?.id ?? callDisplayName, // handle for native call UI (prefer createdBy user id, fallback to call display name)
|
|
73
|
+
callDisplayName, // display name for display in call screen
|
|
74
|
+
call.state.settings?.video?.enabled ?? false, // is video call?
|
|
75
|
+
];
|
|
67
76
|
}
|
|
68
77
|
|
|
69
78
|
export async function registerOutgoingCall(call: Call) {
|
|
@@ -84,13 +93,7 @@ export async function registerOutgoingCall(call: Call) {
|
|
|
84
93
|
|
|
85
94
|
try {
|
|
86
95
|
logger.debug(`registerOutgoingCall: Registering outgoing call ${call.cid}`);
|
|
87
|
-
|
|
88
|
-
await CallingxModule.startCall(
|
|
89
|
-
call.cid, // unique id for call
|
|
90
|
-
call.state.createdBy?.id ?? callDisplayName, // handle for native call UI (prefer createdBy user id, fallback to call display name)
|
|
91
|
-
callDisplayName, // display name for display in call screen
|
|
92
|
-
call.state.settings?.video?.enabled ?? false, // is video call?
|
|
93
|
-
);
|
|
96
|
+
await CallingxModule.startCall(...getCallingxCallArgs(call));
|
|
94
97
|
} catch (error) {
|
|
95
98
|
logger.error(
|
|
96
99
|
`registerOutgoingCall: Error registering outgoing call in callingx: ${call.cid}`,
|
|
@@ -119,67 +122,45 @@ export async function joinCallingxCall(call: Call, activeCalls: Call[]) {
|
|
|
119
122
|
const logger = videoLoggerSystem.getLogger('callingx');
|
|
120
123
|
const isOutcomingCall = call.ringing && call.isCreatedByMe;
|
|
121
124
|
const isIncomingCall = call.ringing && !call.isCreatedByMe;
|
|
125
|
+
const isOngoingCall = (c: Call) =>
|
|
126
|
+
!c.ringing &&
|
|
127
|
+
CallingxModule.isOngoingCallsEnabled &&
|
|
128
|
+
!c.isOwnTracksLoopbackAllowed;
|
|
122
129
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
await CallingxModule.startCall(
|
|
127
|
-
call.cid, // unique id for call
|
|
128
|
-
call.state.createdBy?.id ?? callDisplayName, // handle for native call UI (prefer createdBy user id, fallback to call display name)
|
|
129
|
-
callDisplayName, // display name for display in call screen
|
|
130
|
-
call.state.settings?.video?.enabled ?? false, // is video call?
|
|
131
|
-
);
|
|
132
|
-
};
|
|
130
|
+
if (!isIncomingCall && !isOutcomingCall && !isOngoingCall(call)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
134
|
+
try {
|
|
135
|
+
const activeCallsToLeave = activeCalls.filter(
|
|
136
|
+
(c) =>
|
|
137
|
+
c.cid !== call.cid &&
|
|
138
|
+
(c.ringing || isOngoingCall(c)) &&
|
|
139
|
+
c.state.callingState !== CallingState.LEFT,
|
|
140
|
+
);
|
|
141
|
+
for (const activeCall of activeCallsToLeave) {
|
|
142
|
+
logger.debug(
|
|
143
|
+
`leaving currently-active-call:${activeCall.cid} before joining the call:${call.cid}`,
|
|
144
144
|
);
|
|
145
|
+
await activeCall.leave({ reason: 'cancel' }).catch((e) => {
|
|
146
|
+
logger.error(`failed to leave active call ${activeCall.cid}`, e);
|
|
147
|
+
});
|
|
145
148
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
(c) =>
|
|
153
|
-
c.cid !== call.cid &&
|
|
154
|
-
c.ringing &&
|
|
155
|
-
c.state.callingState !== CallingState.LEFT,
|
|
156
|
-
);
|
|
157
|
-
for (const activeCall of activeCallsToLeave) {
|
|
158
|
-
logger.debug(
|
|
159
|
-
`leaving active call ${activeCall.cid} before joining ${call.cid}`,
|
|
160
|
-
);
|
|
161
|
-
await activeCall.leave({ reason: 'cancel' }).catch((e) => {
|
|
162
|
-
logger.error(`failed to leave active call ${activeCall.cid}`, e);
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
// Awaits native CallKit/Telecom registration before answering.
|
|
166
|
-
// Safe to call even if the call is already registered (e.g. from VoIP push) --
|
|
167
|
-
// iOS early-returns with no error, Android sends the registered broadcast.
|
|
168
|
-
const callDisplayName = getCallDisplayNameFromCall(call);
|
|
169
|
-
await CallingxModule.displayIncomingCall(
|
|
170
|
-
call.cid, // unique id for call
|
|
171
|
-
call.state.createdBy?.id ?? callDisplayName, // handle for native call UI (prefer createdBy user id, fallback to call display name)
|
|
172
|
-
callDisplayName, // display name for display in call screen
|
|
173
|
-
call.state.settings?.video?.enabled ?? false, // is video call?
|
|
174
|
-
);
|
|
175
|
-
|
|
149
|
+
logger.debug(
|
|
150
|
+
`joinCallingxCall: Joining call ${call.cid} isIncoming: ${isIncomingCall} isOutgoing: ${isOutcomingCall}`,
|
|
151
|
+
);
|
|
152
|
+
const callArgs = getCallingxCallArgs(call);
|
|
153
|
+
if (isIncomingCall) {
|
|
154
|
+
await CallingxModule.displayIncomingCall(...callArgs);
|
|
176
155
|
await CallingxModule.answerIncomingCall(call.cid);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
`Error joining incoming call in callingx: ${call.cid}`,
|
|
180
|
-
error,
|
|
181
|
-
);
|
|
156
|
+
} else {
|
|
157
|
+
await CallingxModule.startCall(...callArgs);
|
|
182
158
|
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
logger.error(
|
|
161
|
+
`startCallingxCall: Error starting call in callingx: ${call.cid} isIncoming: ${isIncomingCall} isOutgoing: ${isOutcomingCall}`,
|
|
162
|
+
error,
|
|
163
|
+
);
|
|
183
164
|
}
|
|
184
165
|
}
|
|
185
166
|
|
|
@@ -13,6 +13,21 @@ export const keepCallAliveCallRef: { current: Call | undefined } = {
|
|
|
13
13
|
current: undefined,
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
let resolveRunningTask: (() => void) | undefined;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Ends the currently running keep-alive headless task.
|
|
20
|
+
*
|
|
21
|
+
* The task's promise is app-provided (`foregroundService.android.taskToRun`) and by default never
|
|
22
|
+
* resolves, so stopping the native service alone would leave the task registered in
|
|
23
|
+
* `HeadlessJsTaskContext` forever - which keeps RN's timer choreographer callback posted for the
|
|
24
|
+
* rest of the process lifetime. Resolving it here lets React Native unwind the task properly.
|
|
25
|
+
*/
|
|
26
|
+
export const endKeepCallAliveHeadlessTask = () => {
|
|
27
|
+
resolveRunningTask?.();
|
|
28
|
+
resolveRunningTask = undefined;
|
|
29
|
+
};
|
|
30
|
+
|
|
16
31
|
function registerKeepCallAliveHeadlessTaskOnce() {
|
|
17
32
|
if (Platform.OS !== 'android') return;
|
|
18
33
|
|
|
@@ -42,10 +57,37 @@ function registerKeepCallAliveHeadlessTaskOnce() {
|
|
|
42
57
|
|
|
43
58
|
const config = StreamVideoRN.getConfig();
|
|
44
59
|
const taskToRun = config.foregroundService.android.taskToRun;
|
|
60
|
+
// A previous task (if any) must not keep hanging around.
|
|
61
|
+
endKeepCallAliveHeadlessTask();
|
|
62
|
+
const taskEnded = new Promise<void>((resolve) => {
|
|
63
|
+
resolveRunningTask = resolve;
|
|
64
|
+
});
|
|
65
|
+
const ownResolver = resolveRunningTask;
|
|
66
|
+
|
|
67
|
+
// React Native stops the foreground service as soon as the promise returned from here
|
|
68
|
+
// settles. `taskToRun` is app-provided and may resolve early - or immediately - so its
|
|
69
|
+
// completion must not end the keep-alive: that would drop the call's background protection
|
|
70
|
+
// while the call is still ongoing. The task's lifetime is owned by the SDK instead, and ends
|
|
71
|
+
// via endKeepCallAliveHeadlessTask() when the call is left.
|
|
72
|
+
(async () => {
|
|
73
|
+
try {
|
|
74
|
+
await taskToRun(call);
|
|
75
|
+
logger.debug(
|
|
76
|
+
'Keep-alive taskToRun completed, keeping the call alive until the call ends',
|
|
77
|
+
);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
logger.error('Keep-alive headless task failed', e);
|
|
80
|
+
}
|
|
81
|
+
})();
|
|
82
|
+
|
|
45
83
|
try {
|
|
46
|
-
await
|
|
47
|
-
}
|
|
48
|
-
|
|
84
|
+
await taskEnded;
|
|
85
|
+
} finally {
|
|
86
|
+
// a newer task may already have installed its own resolver - clearing it here would leave
|
|
87
|
+
// that task with no way to be ended.
|
|
88
|
+
if (resolveRunningTask === ownResolver) {
|
|
89
|
+
resolveRunningTask = undefined;
|
|
90
|
+
}
|
|
49
91
|
}
|
|
50
92
|
},
|
|
51
93
|
);
|
|
@@ -79,6 +79,35 @@ export const onRingNotificationReceived = async (
|
|
|
79
79
|
callingx.releaseBackgroundTask(backgroundTaskOwner);
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Gives up on this push: dismisses the call the native push path already displayed, then asks
|
|
84
|
+
* the call service to stop. `stopService` is only a request — it no-ops while any other call is
|
|
85
|
+
* registered or being registered — so this call must be ended explicitly rather than relying on
|
|
86
|
+
* the service teardown to wipe it.
|
|
87
|
+
*/
|
|
88
|
+
const abandonPush = async () => {
|
|
89
|
+
if (asForegroundService) {
|
|
90
|
+
finishBackgroundTask();
|
|
91
|
+
}
|
|
92
|
+
// Each step is contained on its own: failing to dismiss the call must not skip the stop
|
|
93
|
+
// request, and neither may skip the unsubscription cleanup — a stale entry makes every later
|
|
94
|
+
// push for this cid look like a duplicate and get discarded.
|
|
95
|
+
try {
|
|
96
|
+
await callingx.endCallWithReason(call_cid, 'error');
|
|
97
|
+
} catch (error) {
|
|
98
|
+
nativeLog(`Failed to end call ${call_cid}: ${error}`, 'error');
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
await callingx.stopService();
|
|
102
|
+
} catch (error) {
|
|
103
|
+
nativeLog(
|
|
104
|
+
`Failed to stop the call service for ${call_cid}: ${error}`,
|
|
105
|
+
'error',
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
pushUnsubscriptionCallbacks.delete(call_cid);
|
|
109
|
+
};
|
|
110
|
+
|
|
82
111
|
if (asForegroundService) {
|
|
83
112
|
// initialize the callback array immediately to avoid race condition
|
|
84
113
|
pushUnsubscriptionCallbacks.set(call_cid, []);
|
|
@@ -98,21 +127,13 @@ export const onRingNotificationReceived = async (
|
|
|
98
127
|
client = await pushConfig.createStreamVideoClient();
|
|
99
128
|
if (!client) {
|
|
100
129
|
nativeLog(`video client not found, skipping the call.ring notification`);
|
|
101
|
-
|
|
102
|
-
finishBackgroundTask();
|
|
103
|
-
}
|
|
104
|
-
await callingx.stopService();
|
|
105
|
-
pushUnsubscriptionCallbacks.delete(call_cid);
|
|
130
|
+
await abandonPush();
|
|
106
131
|
return;
|
|
107
132
|
}
|
|
108
133
|
} catch (error) {
|
|
109
134
|
//we need to release the background task and stop the service to avoid stale owner
|
|
110
135
|
nativeLog(`Failed to create video client: ${error}`, 'error');
|
|
111
|
-
|
|
112
|
-
finishBackgroundTask();
|
|
113
|
-
}
|
|
114
|
-
await callingx.stopService();
|
|
115
|
-
pushUnsubscriptionCallbacks.delete(call_cid);
|
|
136
|
+
await abandonPush();
|
|
116
137
|
return;
|
|
117
138
|
}
|
|
118
139
|
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '
|
|
1
|
+
export const version = '2.0.0-beta.0';
|