diff --git a/packages/client/index.ts b/packages/client/index.ts index 1a98e3f21a..335de308b2 100644 --- a/packages/client/index.ts +++ b/packages/client/index.ts @@ -11,6 +11,7 @@ export * from './src/stats/types'; export * from './src/Call'; export * from './src/CallType'; +export * from './src/rtc/mediaEngine'; export * from './src/StreamVideoClient'; export * from './src/StreamSfuClient'; export * from './src/devices'; diff --git a/packages/client/src/Call.ts b/packages/client/src/Call.ts index 788a98187c..49191f588c 100644 --- a/packages/client/src/Call.ts +++ b/packages/client/src/Call.ts @@ -2,7 +2,9 @@ import { StreamSfuClient } from './StreamSfuClient'; import { SfuJoinError } from './errors'; import { BasePeerConnectionOpts, + type CallMediaEngine, Dispatcher, + getCallMediaEngineProvider, getGenericSdp, isAudioTrackType, isSfuEvent, @@ -332,6 +334,8 @@ export class Call { private allowOwnTracksLoopback = false; private hasJoinedOnce = false; private deviceSettingsAppliedOnce = false; + private callManagerStarted = false; + private leaveGeneration = 0; private credentials?: Credentials; private initialized = false; @@ -355,6 +359,14 @@ export class Call { ClientCapability.SUBSCRIBER_VIDEO_PAUSE, ]); + /** + * The in-flight per-call media engine. On web/React this resolves to a thin + * globals-backed engine (no provider registered); React Native registers a + * provider that owns a per-call native factory. + * @internal + */ + private mediaEnginePromise?: Promise; + /** * Constructs a new `Call` instance. * @@ -703,6 +715,8 @@ export class Call { return; } + this.leaveGeneration += 1; + if (callingState === CallingState.JOINING) { const waitUntilCallJoined = () => { return new Promise((resolve) => { @@ -803,6 +817,7 @@ export class Call { globalThis.streamRNVideoSDK?.callManager.stop({ isRingingTypeCall: this.ringing, + shouldStopCallManager: this.callManagerStarted, }); this.camera.dispose(); @@ -810,6 +825,7 @@ export class Call { this.screenShare.dispose(); this.speaker.dispose(); this.deviceSettingsAppliedOnce = false; + this.callManagerStarted = false; const stopOnLeavePromises: Promise[] = []; if (this.camera.stopOnLeave) { @@ -822,6 +838,23 @@ export class Call { stopOnLeavePromises.push(this.screenShare.disable(true)); } await Promise.all(stopOnLeavePromises); + + // Dispose the per-call media engine last — after peer connections and + // local tracks are gone — so the backing factory tears down with no + // owned PCs/tracks. A fresh `join()` builds a new engine. + if (this.mediaEnginePromise) { + const enginePromise = this.mediaEnginePromise; + this.mediaEnginePromise = undefined; + this.logger.debug('Disposing per-call media factory'); + await enginePromise + .then((engine) => { + globalThis.streamRNVideoSDK?.callingX?.unwireAudioEngineSubscription(); + return engine.dispose(); + }) + .catch((err) => { + this.logger.warn('Failed to dispose media engine', err); + }); + } }); }; @@ -941,11 +974,7 @@ export class Call { this.clientStore.registerOrUpdateCall(this); } // Skip speaker setup on RN if ringing was requested or the call is already ringing - const skipSpeakerApply = isReactNative() - ? params?.ring === true - ? true - : this.ringing - : false; + const skipSpeakerApply = isReactNative(); await this.applyDeviceConfig( response.call.settings, false, @@ -982,11 +1011,7 @@ export class Call { } // Skip speaker setup on RN if ringing was requested or the call is already ringing - const skipSpeakerApply = isReactNative() - ? data?.ring === true - ? true - : this.ringing - : false; + const skipSpeakerApply = isReactNative(); await this.applyDeviceConfig( response.call.settings, false, @@ -1194,12 +1219,26 @@ export class Call { private doJoin = async (data?: JoinCallData): Promise => { const connectStartTime = Date.now(); const callingState = this.state.callingState; + const joinLeaveGeneration = this.leaveGeneration; + const supersededByLeave = () => + this.leaveGeneration !== joinLeaveGeneration; this.joinCallData = data; this.logger.debug('Starting join flow'); this.state.setCallingState(CallingState.JOINING); + // Ensure the per-call media engine exists before any peer connection + // (codec probe, subscriber, publisher) or capture happens, so the WebRTC + // globals resolve to the call's factory. Idempotent across + // reconnect/migration attempts. + await this.ensureMediaFactory(); + + const callingX = globalThis.streamRNVideoSDK?.callingX; + if (callingX) { + callingX.wireAudioEngineSubscription(); + } + const performingMigration = this.reconnectStrategy === WebsocketReconnectStrategy.MIGRATE; const performingRejoin = @@ -1270,6 +1309,11 @@ export class Call { // the capabilities of the client (codec support, etc.) const { dangerouslyForceCodec, fmtpLine, subscriberFmtpLine } = this.clientPublishOptions || {}; + // skip if a leave superseded this join so codec detection doesn't resolve to a default factory. + if (supersededByLeave()) { + this.logger.debug('Join superseded by leave; skipping codec detection'); + return; + } const [subscriberSdp, publisherSdp] = await Promise.all([ getGenericSdp('recvonly', dangerouslyForceCodec, subscriberFmtpLine), getGenericSdp('sendonly', dangerouslyForceCodec, fmtpLine), @@ -1329,6 +1373,13 @@ export class Call { } } + // If the user left while this join was in flight, bail before re-setting JOINED and before + // peer-connection setup below (both run synchronously after this, so one check covers them). + if (supersededByLeave()) { + this.logger.debug('Join superseded by leave; aborting join flow'); + return; + } + if (!performingMigration) { // in MIGRATION, `JOINED` state is set in `this.reconnectMigrate()` this.state.setCallingState(CallingState.JOINED); @@ -1375,12 +1426,21 @@ export class Call { // device settings should be applied only once, we don't have to // re-apply them on later reconnections or server-side data fetches - if (!this.deviceSettingsAppliedOnce && this.state.settings) { + if ( + !this.deviceSettingsAppliedOnce && + this.state.settings && + !supersededByLeave() + ) { await this.applyDeviceConfig(this.state.settings, true, false); + this.deviceSettingsAppliedOnce = true; + } + + if (!this.callManagerStarted && !supersededByLeave()) { globalThis.streamRNVideoSDK?.callManager.start({ isRingingTypeCall: this.ringing, + cid: this.cid, }); - this.deviceSettingsAppliedOnce = true; + this.callManagerStarted = true; } // We shouldn't persist the `ring` and `notify` state after joining the call @@ -1669,6 +1729,39 @@ export class Call { return joinResponse; }; + /** + * Whether the per-call media engine currently exists. True from join until leave. + * + * @internal an internal getter and should not be used outside the SDK. + */ + get hasMediaEngine(): boolean { + return !!this.mediaEnginePromise; + } + + /** + * Ensures a {@link CallMediaEngine} exists for this call's media session and + * returns it. Idempotent: the engine is created once via the registered + * provider (see `setCallMediaEngineProvider`) and cached until `leave()` + * disposes it. Concurrent callers (e.g. camera + microphone enabling in + * parallel) share the same engine because the in-flight creation promise is + * cached, never the unresolved result. + * + * @internal + */ + ensureMediaFactory = async (): Promise => { + if (!this.mediaEnginePromise) { + const provider = getCallMediaEngineProvider(); + + this.logger.debug(`Requesting per-call media factory creation`); + this.mediaEnginePromise = Promise.resolve(provider()).catch((err) => { + // Drop the cached rejection so a retried join() can rebuild the engine + this.mediaEnginePromise = undefined; + throw err; + }); + } + return this.mediaEnginePromise; + }; + /** * Handles the closing of the SFU signal connection. * diff --git a/packages/client/src/devices/AudioDeviceManager.ts b/packages/client/src/devices/AudioDeviceManager.ts index 2884700ea3..b8b22e68d5 100644 --- a/packages/client/src/devices/AudioDeviceManager.ts +++ b/packages/client/src/devices/AudioDeviceManager.ts @@ -2,6 +2,7 @@ import { DeviceManager } from './DeviceManager'; import { AudioDeviceManagerState } from './AudioDeviceManagerState'; import { AudioBitrateProfile } from '../gen/video/sfu/models/models'; import { TrackPublishOptions } from '../rtc'; +import { isReactNative } from '../helpers/platforms'; /** * Base class for High Fidelity enabled Device Managers. @@ -17,6 +18,11 @@ export abstract class AudioDeviceManager< if (!this.call.state.settings?.audio.hifi_audio_enabled) { throw new Error('High Fidelity audio is not enabled for this call'); } + if (isReactNative() && this.call.hasMediaEngine) { + throw new Error( + 'setAudioBitrateProfile must be called before joining the call.', + ); + } this.doSetAudioBitrateProfile(profile); this.state.setAudioBitrateProfile(profile); if (this.enabled) { diff --git a/packages/client/src/devices/CameraManager.ts b/packages/client/src/devices/CameraManager.ts index 28c4e8f41e..b3720882ef 100644 --- a/packages/client/src/devices/CameraManager.ts +++ b/packages/client/src/devices/CameraManager.ts @@ -7,6 +7,7 @@ import { VideoSettingsResponse } from '../gen/coordinator'; import { TrackType } from '../gen/video/sfu/models/models'; import { isMobile } from '../helpers/compatibility'; import { isReactNative } from '../helpers/platforms'; +import { CallingState } from '../store'; import { DevicePersistenceOptions } from './devicePersistence'; export class CameraManager extends DeviceManager { @@ -124,6 +125,53 @@ export class CameraManager extends DeviceManager { } } + override enable(): Promise { + if ( + isReactNative() && + this.call.state.callingState !== CallingState.JOINED + ) { + this.state.setPendingStatus('enabled'); + return Promise.resolve(); + } + + return super.enable(); + } + + override disable(options: { forceStop?: boolean }): Promise; + override disable(forceStop?: boolean): Promise; + override async disable( + forceStopOrOptions?: boolean | { forceStop?: boolean }, + ): Promise { + if ( + isReactNative() && + this.call.state.callingState !== CallingState.JOINED + ) { + this.state.setPendingStatus('disabled'); + return; + } + + // forward verbatim to the base, narrowing so the right overload is selected + if (forceStopOrOptions === undefined) return super.disable(); + if (typeof forceStopOrOptions === 'boolean') { + return super.disable(forceStopOrOptions); + } + return super.disable(forceStopOrOptions); + } + + override toggle(): Promise { + if ( + isReactNative() && + this.call.state.callingState !== CallingState.JOINED + ) { + this.state.setPendingStatus( + this.state.optimisticStatus === 'enabled' ? 'disabled' : 'enabled', + ); + return Promise.resolve(); + } + + return super.toggle(); + } + /** * Applies the video settings to the camera. * @@ -166,9 +214,15 @@ export class CameraManager extends DeviceManager { } } - const { mediaStream } = this.state; - if (canPublish && publish && this.enabled && mediaStream) { - await this.publishStream(mediaStream); + if (isReactNative() && publish && canPublish) { + // On RN the camera is enabled/disabled optimistically before JOINED. Reconcile now + // acquires the track and publishes it, so it fully owns the publish. + await this.reconcileOptimisticStatus(); + } else { + const { mediaStream } = this.state; + if (canPublish && publish && this.enabled && mediaStream) { + await this.publishStream(mediaStream); + } } } @@ -196,9 +250,12 @@ export class CameraManager extends DeviceManager { return constraints; } - protected override getStream( + protected override async getStream( constraints: MediaTrackConstraints, ): Promise { + // Ensure the call's media factory exists before capture so the resulting + // track is owned by it (the WebRTC globals resolve to the live factory). + await this.call.ensureMediaFactory(); return getVideoStream(constraints, this.call.tracer); } } diff --git a/packages/client/src/devices/DeviceManager.ts b/packages/client/src/devices/DeviceManager.ts index 5487932851..182926d3a6 100644 --- a/packages/client/src/devices/DeviceManager.ts +++ b/packages/client/src/devices/DeviceManager.ts @@ -509,6 +509,23 @@ export abstract class DeviceManager< } } + protected reconcileOptimisticStatus = async (): Promise => { + const target = this.state.optimisticStatus; + await withCancellation(this.statusChangeConcurrencyTag, async (signal) => { + try { + if (target === 'enabled' && this.state.status !== 'enabled') { + await this.unmuteStream(); + if (!signal.aborted) this.state.setStatus('enabled'); + } else if (target === 'disabled' && this.state.status === 'enabled') { + // mirror whatever disable() does to stop/pause the track per disableMode + if (!signal.aborted) this.state.setStatus('disabled'); + } + } finally { + if (!signal.aborted) this.state.setPendingStatus(this.state.status); + } + }); + }; + private disableTracks() { this.getTracks().forEach((track) => { if (track.enabled) track.enabled = false; diff --git a/packages/client/src/devices/MicrophoneManager.ts b/packages/client/src/devices/MicrophoneManager.ts index e28d52451b..46a2395f68 100644 --- a/packages/client/src/devices/MicrophoneManager.ts +++ b/packages/client/src/devices/MicrophoneManager.ts @@ -76,7 +76,10 @@ export class MicrophoneManager extends AudioDeviceManager { try { if (callingState === CallingState.LEFT) { - this.setMutedRecordingPrepared(false); + // The muted-recording-prepared mode is reset in `callManager.stop()` + // (during leave, while the call factory is still alive), not here — + // this subscription fires asynchronously and could land after the + // factory is disposed, forcing a default-ADM rebuild. await this.stopSpeakingWhileMutedDetection(); } if (callingState !== CallingState.JOINED) return; @@ -187,6 +190,53 @@ export class MicrophoneManager extends AudioDeviceManager { + if ( + isReactNative() && + this.call.state.callingState !== CallingState.JOINED + ) { + this.state.setPendingStatus('enabled'); + return Promise.resolve(); + } + + return super.enable(); + } + + override disable(options: { forceStop?: boolean }): Promise; + override disable(forceStop?: boolean): Promise; + override async disable( + forceStopOrOptions?: boolean | { forceStop?: boolean }, + ): Promise { + if ( + isReactNative() && + this.call.state.callingState !== CallingState.JOINED + ) { + this.state.setPendingStatus('disabled'); + return; + } + + // forward verbatim to the base, narrowing so the right overload is selected + if (forceStopOrOptions === undefined) return super.disable(); + if (typeof forceStopOrOptions === 'boolean') { + return super.disable(forceStopOrOptions); + } + return super.disable(forceStopOrOptions); + } + + override toggle(): Promise { + if ( + isReactNative() && + this.call.state.callingState !== CallingState.JOINED + ) { + this.state.setPendingStatus( + this.state.optimisticStatus === 'enabled' ? 'disabled' : 'enabled', + ); + return Promise.resolve(); + } + + return super.toggle(); + } + /** * Enables noise cancellation for the microphone. * @@ -376,9 +426,15 @@ export class MicrophoneManager extends AudioDeviceManager { + // Ensure the call's media factory exists before capture so the resulting + // track is owned by it (the WebRTC globals resolve to the live factory). + await this.call.ensureMediaFactory(); return getAudioStream(constraints, this.call.tracer); } diff --git a/packages/client/src/devices/ScreenShareManager.ts b/packages/client/src/devices/ScreenShareManager.ts index 950dc3a020..7de389285c 100644 --- a/packages/client/src/devices/ScreenShareManager.ts +++ b/packages/client/src/devices/ScreenShareManager.ts @@ -90,6 +90,9 @@ export class ScreenShareManager extends AudioDeviceManager< if (!this.state.audioEnabled) { constraints.audio = false; } + // Ensure the call's media factory exists before capture so the resulting + // track is owned by it (the WebRTC globals resolve to the live factory). + await this.call.ensureMediaFactory(); const stream = await getScreenShareStream(constraints, this.call.tracer); const [track] = stream.getVideoTracks(); const { contentHint } = this.state.settings || {}; diff --git a/packages/client/src/devices/SpeakerManager.ts b/packages/client/src/devices/SpeakerManager.ts index 18754570a8..d84e8291d8 100644 --- a/packages/client/src/devices/SpeakerManager.ts +++ b/packages/client/src/devices/SpeakerManager.ts @@ -90,6 +90,7 @@ export class SpeakerManager { globalThis.streamRNVideoSDK?.callManager.setup({ defaultDevice, isRingingTypeCall: this.call.ringing, + cid: this.call.cid, }); } } @@ -164,6 +165,7 @@ export class SpeakerManager { this.subscriptions.forEach((unsubscribe) => unsubscribe()); this.subscriptions = []; this.areSubscriptionsSetUp = false; + this.defaultDevice = undefined; }; /** diff --git a/packages/client/src/devices/__tests__/MicrophoneManagerRN.test.ts b/packages/client/src/devices/__tests__/MicrophoneManagerRN.test.ts index 4d10d04820..d6735ff74e 100644 --- a/packages/client/src/devices/__tests__/MicrophoneManagerRN.test.ts +++ b/packages/client/src/devices/__tests__/MicrophoneManagerRN.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { MicrophoneManager } from '../MicrophoneManager'; import { Call } from '../../Call'; import { StreamClient } from '../../coordinator/connection/client'; -import { CallingState, StreamVideoWriteableStateStore } from '../../store'; +import { StreamVideoWriteableStateStore } from '../../store'; import { mockAudioDevices, mockAudioStream, @@ -263,25 +263,6 @@ describe('MicrophoneManager React Native', () => { expect(setMutedRecordingPreparedMock).not.toHaveBeenCalledWith(true); }); - it('should release prepared muted recording when the call is left', async () => { - await manager.disable(); - await vi.waitUntil( - () => - setMutedRecordingPreparedMock.mock.calls.some(([arg]) => arg === true), - { timeout: 100 }, - ); - setMutedRecordingPreparedMock.mockClear(); - - manager['call'].state.setCallingState(CallingState.LEFT); - - await vi.waitUntil( - () => - setMutedRecordingPreparedMock.mock.calls.some(([arg]) => arg === false), - { timeout: 100 }, - ); - expect(setMutedRecordingPreparedMock).toHaveBeenCalledWith(false); - }); - afterEach(() => { globalThis.streamRNVideoSDK = undefined; vi.clearAllMocks(); diff --git a/packages/client/src/devices/__tests__/mocks.ts b/packages/client/src/devices/__tests__/mocks.ts index 12a33c8dfc..d8d1dd8623 100644 --- a/packages/client/src/devices/__tests__/mocks.ts +++ b/packages/client/src/devices/__tests__/mocks.ts @@ -106,6 +106,9 @@ export const mockCall = (): Partial => { notifyNoiseCancellationStopped: vi.fn().mockResolvedValue(undefined), notifyTrackMuteState: vi.fn().mockResolvedValue(undefined), refreshPublishedTrack: vi.fn().mockResolvedValue(undefined), + ensureMediaFactory: vi.fn().mockResolvedValue({ + dispose: vi.fn().mockResolvedValue(undefined), + }), tracer: new Tracer('tests'), }; }; diff --git a/packages/client/src/rtc/index.ts b/packages/client/src/rtc/index.ts index f986a26acb..1f46445ae1 100644 --- a/packages/client/src/rtc/index.ts +++ b/packages/client/src/rtc/index.ts @@ -1,4 +1,5 @@ export * from './codecs'; +export * from './mediaEngine'; export * from './Dispatcher'; export * from './NegotiationError'; export * from './IceTrickleBuffer'; diff --git a/packages/client/src/rtc/mediaEngine.ts b/packages/client/src/rtc/mediaEngine.ts new file mode 100644 index 0000000000..3feb8decd0 --- /dev/null +++ b/packages/client/src/rtc/mediaEngine.ts @@ -0,0 +1,44 @@ +/** + * A per-call media engine. On React Native it owns the call's native + * `PeerConnectionFactory` (built with the call's audio configuration); on web it + * is a no-op. Capture (`getUserMedia`/`getDisplayMedia`) and peer-connection creation + * always go through the WebRTC globals, which resolve to this factory while it + * is the live call factory — so the engine only needs to manage its lifecycle. + * + * @internal + */ +export interface CallMediaEngine { + dispose(): Promise; +} + +/** + * Creates a per-call {@link CallMediaEngine}. Registered once at SDK startup + * via {@link setCallMediaEngineProvider}. May return the engine synchronously + * (the default globals engine) or asynchronously (React Native, where allocating + * the native per-call factory is an async bridge call). + * + * @internal + */ +export type CallMediaEngineProvider = () => + CallMediaEngine | Promise; + +/** + * The default engine: a thin, stateless wrapper over the WebRTC globals. + */ +const defaultGlobalsEngine: CallMediaEngine = { + dispose: () => Promise.resolve(true), +}; + +const defaultGlobalsEngineProvider: CallMediaEngineProvider = () => + defaultGlobalsEngine; + +let provider: CallMediaEngineProvider = defaultGlobalsEngineProvider; + +export const setCallMediaEngineProvider = ( + newProvider: CallMediaEngineProvider, +): void => { + provider = newProvider; +}; + +export const getCallMediaEngineProvider = (): CallMediaEngineProvider => + provider; diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts index ee4e4f8e10..c0ff39dd34 100644 --- a/packages/client/src/types.ts +++ b/packages/client/src/types.ts @@ -421,9 +421,20 @@ type StreamRNVideoSDKCallManagerRingingParams = { type StreamRNVideoSDKCallManagerSetupParams = StreamRNVideoSDKCallManagerRingingParams & { + cid: string; defaultDevice: AudioSettingsRequestDefaultDeviceEnum; }; +type StreamRNVideoSDKCallManagerStartParams = + StreamRNVideoSDKCallManagerRingingParams & { + cid: string; + }; + +type StreamRNVideoSDKCallManagerStopParams = + StreamRNVideoSDKCallManagerRingingParams & { + shouldStopCallManager: boolean; + }; + type StreamRNVideoSDKEndCallReason = /** Call ended by the local user (e.g., hanging up). */ | 'local' @@ -453,6 +464,8 @@ type StreamRNVideoSDKCallingX = { reason?: StreamRNVideoSDKEndCallReason, ) => Promise; registerOutgoingCall: (call: Call) => Promise; + wireAudioEngineSubscription: () => void; + unwireAudioEngineSubscription: () => void; }; export type StreamRNVideoSDKGlobals = { @@ -464,6 +477,7 @@ export type StreamRNVideoSDKGlobals = { setup({ defaultDevice, isRingingTypeCall, + cid, }: StreamRNVideoSDKCallManagerSetupParams): void; /** @@ -471,12 +485,16 @@ export type StreamRNVideoSDKGlobals = { */ start({ isRingingTypeCall, - }: StreamRNVideoSDKCallManagerRingingParams): void; + cid, + }: StreamRNVideoSDKCallManagerStartParams): void; /** * Stops the in call manager. */ - stop({ isRingingTypeCall }: StreamRNVideoSDKCallManagerRingingParams): void; + stop({ + isRingingTypeCall, + shouldStopCallManager, + }: StreamRNVideoSDKCallManagerStopParams): void; /** * iOS-only. Keeps the audio engine's microphone-input (voice-processing) diff --git a/packages/noise-cancellation-react-native/package.json b/packages/noise-cancellation-react-native/package.json index 8417099c8c..55e0be5b97 100644 --- a/packages/noise-cancellation-react-native/package.json +++ b/packages/noise-cancellation-react-native/package.json @@ -48,7 +48,7 @@ }, "homepage": "https://github.com/GetStream/stream-video-js#readme", "devDependencies": { - "@stream-io/react-native-webrtc": "145.2.0", + "@stream-io/react-native-webrtc": "145.3.0", "@stream-io/typescript-config": "workspace:^", "react": "19.2.3", "react-native": "0.86.2", @@ -57,7 +57,7 @@ "typescript": "^6.0.3" }, "peerDependencies": { - "@stream-io/react-native-webrtc": "^145.2.0", + "@stream-io/react-native-webrtc": "^145.3.0", "react-native": "*" }, "react-native-builder-bob": { diff --git a/packages/react-native-callingx/android/src/newarch/java/io/getstream/rn/callingx/CallingxModule.kt b/packages/react-native-callingx/android/src/newarch/java/io/getstream/rn/callingx/CallingxModule.kt index 6e687b3344..2e2e54b4e4 100644 --- a/packages/react-native-callingx/android/src/newarch/java/io/getstream/rn/callingx/CallingxModule.kt +++ b/packages/react-native-callingx/android/src/newarch/java/io/getstream/rn/callingx/CallingxModule.kt @@ -57,6 +57,14 @@ class CallingxModule(reactContext: ReactApplicationContext) : impl.requestAudioEndpointChange(callId, endpointId, promise) } + override fun wireAudioEngineSubscription() { + // leave empty + } + + override fun unwireAudioEngineSubscription() { + // leave empty + } + override fun setupAndroid(options: ReadableMap) { impl.setupAndroid(options) } diff --git a/packages/react-native-callingx/ios/Callingx.mm b/packages/react-native-callingx/ios/Callingx.mm index e728c16941..0813ad289a 100644 --- a/packages/react-native-callingx/ios/Callingx.mm +++ b/packages/react-native-callingx/ios/Callingx.mm @@ -169,9 +169,6 @@ - (void)_setupiOSWithOptions:(NSDictionary *)optionsDict { WebRTCModule *webrtcModule = [self.moduleRegistry moduleForName:"WebRTCModule"]; _moduleImpl.webRTCModule = webrtcModule; - // Must run after webRTCModule injection: getAudioDeviceModule() depends on it. - [_moduleImpl wireEngineSubscription]; - self.callKeepCallController = _moduleImpl.callKeepCallController; self.callKeepProvider = _moduleImpl.callKeepProvider; } @@ -224,6 +221,30 @@ - (void)setupAndroid:(JS::NativeCallingx::SpecSetupAndroidOptions &)options { } #endif +#pragma mark - wireAudioEngineSubscription + +#ifdef RCT_NEW_ARCH_ENABLED +- (void)wireAudioEngineSubscription { + [_moduleImpl wireAudioEngineSubscription]; +} +#else +RCT_EXPORT_METHOD(wireAudioEngineSubscription) { + [_moduleImpl wireAudioEngineSubscription]; +} +#endif + +#pragma mark - unwireAudioEngineSubscription + +#ifdef RCT_NEW_ARCH_ENABLED +- (void)unwireAudioEngineSubscription { + [_moduleImpl unwireAudioEngineSubscription]; +} +#else +RCT_EXPORT_METHOD(unwireAudioEngineSubscription) { + [_moduleImpl unwireAudioEngineSubscription]; +} +#endif + #pragma mark - stopService #ifdef RCT_NEW_ARCH_ENABLED @@ -238,8 +259,8 @@ - (void)stopService:(RCTPromiseResolveBlock)resolve // Not implemented on iOS resolve(@YES); } - #endif + #pragma mark - setShouldRejectCallWhenBusy #ifdef RCT_NEW_ARCH_ENABLED diff --git a/packages/react-native-callingx/ios/CallingxImpl.swift b/packages/react-native-callingx/ios/CallingxImpl.swift index 4e0cca6f3a..8ae6e77dee 100644 --- a/packages/react-native-callingx/ios/CallingxImpl.swift +++ b/packages/react-native-callingx/ios/CallingxImpl.swift @@ -51,6 +51,9 @@ import stream_react_native_webrtc /// The ADM `engineSubscription` is bound to. Tracked so we can detect a new ADM /// (a JS reload recreates WebRTCModule) and re-wire instead of staying on a dead publisher. private weak var subscribedADM: AudioDeviceModule? + /// Backing storage for the CallKit-provider view of the desired engine gate. + private var desiredEngineAvailability: RTCAudioEngineAvailability = .default + private let engineAvailabilityQueue = DispatchQueue(label: "io.getstream.callingx.engineAvailability") // Pending CXActions awaiting JS fulfillment private var pendingAnswerActions: [String: (action: CXAnswerCallAction, enqueuedAt: DispatchTime)] = [:] @@ -388,16 +391,31 @@ import stream_react_native_webrtc isSetup = true } - /// Wires the ADM engine-lifecycle subscription. Call after `webRTCModule` is injected - /// (it's nil during `setup()` on the callingx path). Re-wires when the ADM changes — a JS - /// reload recreates WebRTCModule while this singleton persists; a no-op for the same ADM. - @objc public func wireEngineSubscription() { - guard let adm = getAudioDeviceModule() else { return } + /// Wires the ADM engine-lifecycle subscription to the live call factory's ADM. A no-op when + /// already wired to that ADM; re-wires when the ADM changes. + @objc public func wireAudioEngineSubscription() { + // Only wire when callingx (CallKit) owns the session. For non-CallKit calls the engine gate + // and the sink below are irrelevant, so skipping keeps their ADM on `.default`. + guard CallingxSessionOwnership.callingxOwnsSession else { + CallingxLog.core.debugPublic("[wireEngineSubscription] skipped — callingx does not own the session") + return + } + guard let adm = getCurrentAudioDeviceModule() else { + CallingxLog.core.debugPublic("[wireEngineSubscription] skipped — adm is not instantiated") + return + } guard subscribedADM !== adm else { return } // already wired to this ADM engineSubscription?.cancel() // ADM changed (e.g. JS reload) — rewire subscribedADM = adm CallingxLog.core.debugPublic("[wireEngineSubscription]") + // Replay fixes potential race for a case when CallKit callbacks are invoked before adm instance exists. + // Serialized with setDesiredEngineAvailability so the read-then-apply pair can't interleave with a + // concurrent CX-queue write; otherwise a stale value could land on the ADM after CX applied a newer one. + engineAvailabilityQueue.sync { + _ = adm.setEngineAvailability(self.desiredEngineAvailability) + } + engineSubscription = adm.publisher.sink { [weak self] event in guard CallingxSessionOwnership.callingxOwnsSession else { return } switch event { @@ -414,6 +432,14 @@ import stream_react_native_webrtc } } } + + /// Cancels the ADM engine-lifecycle subscription wired by `wireAudioEngineSubscription`. + @objc public func unwireAudioEngineSubscription() { + engineSubscription?.cancel() + engineSubscription = nil + subscribedADM = nil + CallingxLog.core.debugPublic("[unwireEngineSubscription]") + } @objc public func getInitialEvents() -> [[String: Any]] { var events: [[String: Any]] = [] @@ -654,7 +680,7 @@ import stream_react_native_webrtc // Gate the audio engine off until CallKit activates the AVAudioSession // (provider:didActivate:). Prevents the engine starting on the wrong, // CallKit-restricted timing. - _ = getAudioDeviceModule()?.setEngineAvailability(.none) + setDesiredEngineAvailability(.none) AudioSessionManager.shared.applyCallKitConfigurationSync() sendEvent(CallingxEvents.didReceiveStartCallAction, body: [ @@ -684,7 +710,7 @@ import stream_react_native_webrtc // Gate the audio engine off until CallKit activates the AVAudioSession // (provider:didActivate:). Prevents the engine starting on the wrong, // CallKit-restricted timing. - _ = getAudioDeviceModule()?.setEngineAvailability(.none) + setDesiredEngineAvailability(.none) AudioSessionManager.shared.applyCallKitConfigurationSync() let source = call.isSelfAnswered ? "app" : "sys" @@ -837,7 +863,7 @@ import stream_react_native_webrtc AudioSessionManager.shared.applyCallKitConfigurationSync() // CallKit owns the session timing now — allow the audio engine to start. - _ = getAudioDeviceModule()?.setEngineAvailability(.default) + setDesiredEngineAvailability(.default) // When CallKit activates the AVAudioSession, inform WebRTC as well. RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession) @@ -854,7 +880,7 @@ import stream_react_native_webrtc CallingxLog.core.debugPublic("[CXProviderDelegate][provider:didDeactivateAudioSession] category=\(audioSession.category) mode=\(audioSession.mode)") // do not let webrtc audio engine auto start until after provider:didActivate:. - _ = getAudioDeviceModule()?.setEngineAvailability(.none) + setDesiredEngineAvailability(.none) // When CallKit deactivates the AVAudioSession, inform WebRTC as well. RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession) @@ -964,9 +990,22 @@ import stream_react_native_webrtc return Int((nowNs - startNs) / 1_000_000) } - private func getAudioDeviceModule() -> AudioDeviceModule? { - guard let adm = webRTCModule?.audioDeviceModule else { - CallingxLog.core.errorPublic("WebRTCModule is not available. Ensure it was injected from the TurboModule host.") + /// Records the desired engine availability and applies it to the current call's ADM if one + /// exists. When the ADM doesn't exist yet (a CallKit callback landed before join built the + /// per-call factory), the direct apply is a no-op and the stored value is replayed onto the + /// ADM in `wireAudioEngineSubscription()`. + private func setDesiredEngineAvailability(_ availability: RTCAudioEngineAvailability) { + engineAvailabilityQueue.sync { + self.desiredEngineAvailability = availability + _ = self.getCurrentAudioDeviceModule()?.setEngineAvailability(availability) + } + } + + /// The live call factory's ADM, or nil when no call is active. Never triggers a default factory + /// build, so wiring before a call exists (or after it ends) is a safe no-op. + private func getCurrentAudioDeviceModule() -> AudioDeviceModule? { + guard let adm = webRTCModule?.currentAudioDeviceModuleOrNil() else { + CallingxLog.core.errorPublic("No live call factory ADM. WebRTCModule missing, or wired outside the join↔leave window.") return nil } return adm diff --git a/packages/react-native-callingx/package.json b/packages/react-native-callingx/package.json index e098b376b2..d08c860f0d 100644 --- a/packages/react-native-callingx/package.json +++ b/packages/react-native-callingx/package.json @@ -61,7 +61,7 @@ "devDependencies": { "@react-native-community/cli": "20.2.0", "@react-native/babel-preset": "0.86.2", - "@stream-io/react-native-webrtc": "145.2.0", + "@stream-io/react-native-webrtc": "145.3.0", "@stream-io/typescript-config": "workspace:^", "@types/react": "^19.2.18", "del-cli": "^6.0.0", @@ -73,7 +73,7 @@ "peerDependencies": { "@react-native-firebase/app": ">=23.0.0", "@react-native-firebase/messaging": ">=23.0.0", - "@stream-io/react-native-webrtc": "^145.2.0", + "@stream-io/react-native-webrtc": "^145.3.0", "react": "*", "react-native": "*" }, diff --git a/packages/react-native-callingx/src/CallingxModule.ts b/packages/react-native-callingx/src/CallingxModule.ts index 211fa79f1b..93bb5f508d 100644 --- a/packages/react-native-callingx/src/CallingxModule.ts +++ b/packages/react-native-callingx/src/CallingxModule.ts @@ -149,6 +149,18 @@ class CallingxModule implements ICallingxModule { this._isSetup = true; }; + wireAudioEngineSubscription(): void { + if (Platform.OS !== 'ios') return; + + NativeCallingModule.wireAudioEngineSubscription(); + } + + unwireAudioEngineSubscription(): void { + if (Platform.OS !== 'ios') return; + + NativeCallingModule.unwireAudioEngineSubscription(); + } + setShouldRejectCallWhenBusy = (shouldReject: boolean): void => { NativeCallingModule.setShouldRejectCallWhenBusy(shouldReject); }; diff --git a/packages/react-native-callingx/src/spec/NativeCallingx.ts b/packages/react-native-callingx/src/spec/NativeCallingx.ts index 8d13dd6f44..4d7b045f9f 100644 --- a/packages/react-native-callingx/src/spec/NativeCallingx.ts +++ b/packages/react-native-callingx/src/spec/NativeCallingx.ts @@ -39,6 +39,10 @@ export interface Spec extends TurboModule { skipIncomingPushInForeground: boolean; }): void; + wireAudioEngineSubscription(): void; + + unwireAudioEngineSubscription(): void; + setShouldRejectCallWhenBusy(shouldReject: boolean): void; setDefaultAudioDeviceEndpointType(endpointType: string): void; diff --git a/packages/react-native-callingx/src/types.ts b/packages/react-native-callingx/src/types.ts index fdf88e59a8..1f471ca523 100644 --- a/packages/react-native-callingx/src/types.ts +++ b/packages/react-native-callingx/src/types.ts @@ -47,6 +47,19 @@ export interface ICallingxModule { * @param options - The options to setup the callingx module. See {@link CallingExpOptions} */ setup(options: CallingExpOptions): void; + + /** + * Wire the audio engine subscription to the live call factory's ADM. + */ + wireAudioEngineSubscription(): void; + + /** + * Cancels the ADM engine-lifecycle subscription wired by + * {@link wireAudioEngineSubscription}. iOS only; no-op on Android. + * Call when the per-call media engine is disposed. + */ + unwireAudioEngineSubscription(): void; + /** * Set whether to reject calls when the user is busy. * The value is used in iOS native module to prevent calls registration in CallKit when the user is busy. diff --git a/packages/react-native-sdk/__tests__/call-manager/CallManager.test.ts b/packages/react-native-sdk/__tests__/call-manager/CallManager.test.ts index 1373c314cf..e4d23cc439 100644 --- a/packages/react-native-sdk/__tests__/call-manager/CallManager.test.ts +++ b/packages/react-native-sdk/__tests__/call-manager/CallManager.test.ts @@ -13,6 +13,7 @@ const makeNativeManager = () => ({ setTelecomManagedMode: jest.fn(), setAudioRole: jest.fn(), setDefaultAudioDeviceEndpointType: jest.fn(), + setEnableStereoAudioOutput: jest.fn(), start: jest.fn(), stop: jest.fn(), setup: jest.fn(), @@ -26,6 +27,7 @@ const makeCallingx = (overrides: Partial = {}) => ({ isTelecomBacked: true, isOngoingCallsEnabled: false, hasRegisteredCall: jest.fn().mockReturnValue(true), + isCallTracked: jest.fn().mockReturnValue(true), getRegisteredCallIds: jest.fn().mockReturnValue(['type:id']), getAvailableAudioEndpoints: jest.fn(), requestAudioEndpointChange: jest.fn().mockResolvedValue(undefined), @@ -45,10 +47,17 @@ const loadCallManager = ({ callingx: ReturnType | undefined; }) => { let mod!: typeof import('../../src/modules/call-manager/CallManager'); + let publicCallManager!: import('../../src/modules/call-manager/CallManager').CallManager; + let internalCallManager!: NonNullable< + typeof globalThis.streamRNVideoSDK + >['callManager']; jest.isolateModules(() => { jest.doMock('react-native', () => ({ Platform: { OS: os, select: (o: any) => o[os] }, - NativeModules: { StreamInCallManager: nativeManager }, + NativeModules: { + StreamInCallManager: nativeManager, + StreamVideoReactNative: {}, + }, // mock to avoid pulling the video-client / react-native-webrtc runtime into the test NativeEventEmitter: class { addListener() { return { remove: jest.fn() }; @@ -58,9 +67,25 @@ const loadCallManager = ({ jest.doMock('../../src/utils/push/libs/callingx', () => ({ getCallingxLibIfAvailable: () => callingx, })); + jest.doMock('../../src/utils/internal/callingx/callingx', () => ({ + endCallingxCall: jest.fn(), + registerOutgoingCall: jest.fn(), + joinCallingxCall: jest.fn(), + wireAudioEngineSubscription: jest.fn(), + unwireAudioEngineSubscription: jest.fn(), + })); + jest.doMock('../../src/utils/internal/registerMediaEngine', () => ({ + registerCallMediaEngine: jest.fn(), + })); mod = require('../../src/modules/call-manager/CallManager'); + publicCallManager = require('../../src/modules/call-manager').callManager; + const { + registerSDKGlobals, + } = require('../../src/utils/internal/registerSDKGlobals'); + registerSDKGlobals(); + internalCallManager = globalThis.streamRNVideoSDK!.callManager; }); - return mod; + return { ...mod, publicCallManager, internalCallManager }; }; const speakerSnapshot: Snapshot = { @@ -73,7 +98,10 @@ const speakerSnapshot: Snapshot = { }; describe('CallManager Android Telecom branch', () => { - afterEach(() => jest.resetModules()); + afterEach(() => { + jest.resetModules(); + delete (globalThis as any).streamRNVideoSDK; + }); it('adapts a callingx snapshot to AudioDevicesState', async () => { const nativeManager = makeNativeManager(); @@ -163,15 +191,16 @@ describe('CallManager Android Telecom branch', () => { it('start() enters telecom-managed mode and forwards the default endpoint', () => { const nativeManager = makeNativeManager(); const callingx = makeCallingx(); - const { CallManager } = loadCallManager({ + const { publicCallManager, internalCallManager } = loadCallManager({ os: 'android', nativeManager, callingx, }); - new CallManager().start({ + publicCallManager.start({ audioRole: 'communicator', deviceEndpointType: 'earpiece', }); + internalCallManager.start({ isRingingTypeCall: false, cid: 'type:id' }); expect(callingx.setDefaultAudioDeviceEndpointType).toHaveBeenCalledWith( 'earpiece', @@ -186,14 +215,16 @@ describe('CallManager Android Telecom branch', () => { // callingx present but no registered call and ongoing disabled -> classic path. const callingx = makeCallingx({ hasRegisteredCall: jest.fn().mockReturnValue(false), + isCallTracked: jest.fn().mockReturnValue(false), isOngoingCallsEnabled: false, }); - const { CallManager } = loadCallManager({ + const { publicCallManager, internalCallManager } = loadCallManager({ os: 'android', nativeManager, callingx, }); - new CallManager().start({ audioRole: 'communicator' }); + publicCallManager.start({ audioRole: 'communicator' }); + internalCallManager.start({ isRingingTypeCall: false, cid: 'type:id' }); expect(nativeManager.setTelecomManagedMode).toHaveBeenCalledWith(false); expect(nativeManager.start).toHaveBeenCalled(); diff --git a/packages/react-native-sdk/ios/StreamInCallManager.m b/packages/react-native-sdk/ios/StreamInCallManager.m index 4a12a7f32a..d716ed4016 100644 --- a/packages/react-native-sdk/ios/StreamInCallManager.m +++ b/packages/react-native-sdk/ios/StreamInCallManager.m @@ -9,6 +9,10 @@ @interface RCT_EXTERN_MODULE(StreamInCallManager, RCTEventEmitter) RCT_EXTERN_METHOD(setEnableStereoAudioOutput:(BOOL)enable) +RCT_EXTERN_METHOD(setMuteMode:(NSInteger)mode) + +RCT_EXTERN_METHOD(setRecordingAlwaysPreparedMode:(BOOL)enabled) + RCT_EXTERN_METHOD(setup) RCT_EXTERN_METHOD(start) diff --git a/packages/react-native-sdk/ios/StreamInCallManager.swift b/packages/react-native-sdk/ios/StreamInCallManager.swift index 5a1541e1b6..4d65316fa0 100644 --- a/packages/react-native-sdk/ios/StreamInCallManager.swift +++ b/packages/react-native-sdk/ios/StreamInCallManager.swift @@ -124,7 +124,30 @@ class StreamInCallManager: RCTEventEmitter { self.enableStereo = enabled } } - + + @objc(setMuteMode:) + func setMuteMode(mode: NSInteger) { + audioSessionQueue.async { [self] in + guard let adm = getAudioDeviceModule() else { + log("setMuteMode(\(mode)) skipped: no live call ADM") + return + } + let muteMode = RTCAudioEngineMuteMode(rawValue: mode) ?? .voiceProcessing + _ = adm.setMuteMode(muteMode) + } + } + + @objc(setRecordingAlwaysPreparedMode:) + func setRecordingAlwaysPreparedMode(enabled: Bool) { + audioSessionQueue.async { [self] in + guard let adm = getAudioDeviceModule() else { + log("setRecordingAlwaysPreparedMode(\(enabled)) skipped: no live call ADM") + return + } + _ = adm.setRecordingAlwaysPreparedMode(enabled) + } + } + /// Builds the audio config for the current role/device and sets it as WebRTC's default. private func makeAudioConfiguration(for routing: OutputRouting?) -> RTCAudioSessionConfiguration { let category: AVAudioSession.Category @@ -199,11 +222,11 @@ class StreamInCallManager: RCTEventEmitter { // This path (no CallKit) owns audio-session activation itself, so the // engine must be allowed to start. Restores availability in case a // prior CallKit call had gated it off. - _ = adm.setEngineAvailability(.default) + _ = adm?.setEngineAvailability(.default) // Stereo is listener-only and applies live. Cleared by stop()'s reset(). if callAudioRole == .listener && enableStereo { - adm.setStereoPlayoutPreference(true) + adm?.setStereoPlayoutPreference(true) } let rtcConfig = makeAudioConfiguration(for: selectedOutput) @@ -228,7 +251,7 @@ class StreamInCallManager: RCTEventEmitter { #if DEBUG NSLog("%@","[StreamInCallManager][wireEngineSubscription]") #endif - engineSubscription = adm.publisher.sink { [weak self] event in + engineSubscription = adm?.publisher.sink { [weak self] event in guard let self else { return } self.audioSessionQueue.async { switch event { @@ -339,7 +362,7 @@ class StreamInCallManager: RCTEventEmitter { // session.setActive(true) here. do { let adm = getAudioDeviceModule() - try adm.setPlayout(true) + try adm?.setPlayout(true) self.log("adm.setPlayout(true) done") } catch { // String(describing:) surfaces the real error code (see setup()). @@ -357,7 +380,7 @@ class StreamInCallManager: RCTEventEmitter { return } let adm = getAudioDeviceModule() - adm.reset() + adm?.reset() clearOutputRouting() // Deactivate directly: the .didDisableAudioEngine sink is async and we cancel it below. applyConfigForEngineDisable() @@ -603,7 +626,10 @@ class StreamInCallManager: RCTEventEmitter { @objc(getAudioStateLog) func getAudioStateLog() -> String { let session = AVAudioSession.sharedInstance() - let adm = getAudioDeviceModule() + + guard let adm = getAudioDeviceModule() else { + return "No audio device module found" + } // WebRTC wraps AVAudioSession with RTCAudioSession; log its state as well. let rtcSession = RTCAudioSession.sharedInstance() @@ -798,7 +824,11 @@ class StreamInCallManager: RCTEventEmitter { stereoRefreshWorkItem?.cancel() // Create a new debounced work item let workItem = DispatchWorkItem { [weak self] in - self?.getAudioDeviceModule().refreshStereoPlayoutState() + guard let adm = self?.getAudioDeviceModule() else { + self?.log("Audio device module is not ready") + return + } + adm.refreshStereoPlayoutState() self?.log("Executed debounced refreshStereoPlayoutState") } stereoRefreshWorkItem = workItem @@ -863,12 +893,14 @@ class StreamInCallManager: RCTEventEmitter { } // MARK: - Helper Methods - private func getAudioDeviceModule() -> AudioDeviceModule { + private func getAudioDeviceModule() -> AudioDeviceModule? { guard let webrtcModule = moduleRegistry?.module(forName: "WebRTCModule") as? WebRTCModule else { fatalError("WebRTCModule is required but not registered with the module registry") } - return webrtcModule.audioDeviceModule + // Follow the live call's ADM; fall back to the default only when no call factory is + // active (bare-fork, or an in-call-manager op firing outside the join↔leave window). + return webrtcModule.currentAudioDeviceModuleOrNil() } private func getCurrentWindow() -> UIWindow? { diff --git a/packages/react-native-sdk/package.json b/packages/react-native-sdk/package.json index 28c0269d82..cdb2969660 100644 --- a/packages/react-native-sdk/package.json +++ b/packages/react-native-sdk/package.json @@ -65,7 +65,7 @@ "@react-native-firebase/messaging": ">=17.5.0", "@stream-io/noise-cancellation-react-native": ">=0.1.0", "@stream-io/react-native-callingx": ">=0.1.0", - "@stream-io/react-native-webrtc": "^145.2.0", + "@stream-io/react-native-webrtc": "^145.3.0", "@stream-io/video-filters-react-native": ">=0.1.0", "expo": ">=47.0.0", "expo-notifications": "*", @@ -119,7 +119,7 @@ "@react-native/metro-config": "0.86.2", "@stream-io/noise-cancellation-react-native": "workspace:^", "@stream-io/react-native-callingx": "workspace:^", - "@stream-io/react-native-webrtc": "145.2.0", + "@stream-io/react-native-webrtc": "145.3.0", "@stream-io/typescript-config": "workspace:^", "@stream-io/video-filters-react-native": "workspace:^", "@testing-library/jest-native": "^5.4.3", diff --git a/packages/react-native-sdk/src/components/Call/Lobby/Lobby.tsx b/packages/react-native-sdk/src/components/Call/Lobby/Lobby.tsx index 2d5ff4dce9..0cc17c5db8 100644 --- a/packages/react-native-sdk/src/components/Call/Lobby/Lobby.tsx +++ b/packages/react-native-sdk/src/components/Call/Lobby/Lobby.tsx @@ -7,9 +7,8 @@ import { } from '@stream-io/video-react-bindings'; import { Avatar } from '../../utility/Avatar'; import type { StreamVideoParticipant } from '@stream-io/video-client'; -import type { MediaStream } from '@stream-io/react-native-webrtc'; -import { RTCView } from '@stream-io/react-native-webrtc'; import { LobbyControls as DefaultLobbyControls } from '../CallControls/LobbyControls'; +import { LobbyCameraPreview } from './LobbyCameraPreview'; import { JoinCallButton as DefaultJoinCallButton, type JoinCallButtonProps, @@ -65,9 +64,8 @@ export const Lobby = ({ const { useCameraState, useCallSettings } = useCallStateHooks(); const callSettings = useCallSettings(); const isVideoEnabledInCall = callSettings?.video.enabled; - const { isMute: cameraIsMuted, mediaStream } = useCameraState(); + const { optimisticIsMute: cameraIsMuted } = useCameraState(); const { t } = useI18n(); - const localVideoStream = mediaStream as unknown as MediaStream | undefined; const connectedUserAsParticipant = useMemo( () => @@ -98,13 +96,8 @@ export const Lobby = ({ ]} > - {!cameraIsMuted && localVideoStream ? ( - + {!cameraIsMuted ? ( + ) : ( diff --git a/packages/react-native-sdk/src/components/Call/Lobby/LobbyCameraPreview.tsx b/packages/react-native-sdk/src/components/Call/Lobby/LobbyCameraPreview.tsx new file mode 100644 index 0000000000..788b8667ae --- /dev/null +++ b/packages/react-native-sdk/src/components/Call/Lobby/LobbyCameraPreview.tsx @@ -0,0 +1,99 @@ +import React, { useEffect, useState } from 'react'; +import { Platform, StyleSheet, ViewStyle } from 'react-native'; +import { + permissions, + RTCCameraPreviewView, +} from '@stream-io/react-native-webrtc'; +import { useCallStateHooks } from '@stream-io/video-react-bindings'; + +/** + * Props for the {@link LobbyCameraPreview} component. + */ +export type LobbyCameraPreviewProps = { + /** + * Resembles the CSS style object-fit. + * + * @default 'cover' + */ + objectFit?: 'contain' | 'cover'; + /** + * Style applied to the preview view. + */ + style?: ViewStyle; +}; + +/** + * The lobby runs before the call is joined. This component drives the native + * camera capturer directly via {@link RTCCameraPreviewView} which requires + * no track/peer connection factory to be created. + * + * It is driven by the *intended* camera state (optimistic): the lobby camera + * toggle only updates `optimisticStatus`; the real WebRTC track is acquired and + * published at join. + */ +export const LobbyCameraPreview = ({ + style, + objectFit = 'cover', +}: LobbyCameraPreviewProps) => { + const { useCameraState, useCallSettings } = useCallStateHooks(); + + const { optimisticIsMute, direction, selectedDevice } = useCameraState(); + const settings = useCallSettings(); + + const wantsCamera = !optimisticIsMute; + const hasCameraPermission = useEnsureCameraPermission(wantsCamera); + + const facing = direction === 'back' ? 'back' : 'front'; + + // Capture at the call's target resolution so the running preview capturer matches + // what the track publishes and can be adopted at join without reconfiguring. + // Normalize to landscape, matching CameraManager.selectTargetResolution. + const targetResolution = settings?.video.target_resolution; + let captureWidth = targetResolution?.width ?? 1280; + let captureHeight = targetResolution?.height ?? 720; + if (captureWidth < captureHeight) { + [captureWidth, captureHeight] = [captureHeight, captureWidth]; + } + + return ( + + ); +}; + +const useEnsureCameraPermission = (enabled: boolean): boolean => { + const [granted, setGranted] = useState(false); + + useEffect(() => { + if (!enabled || granted) return; + + let cancelled = false; + (async () => { + try { + const status = await permissions.query({ name: 'camera' }); + if (status === 'granted') { + if (!cancelled) setGranted(true); + return; + } + const result = await permissions.request({ name: 'camera' }); + if (!cancelled) setGranted(!!result); + } catch { + if (!cancelled) setGranted(false); + } + })(); + + return () => { + cancelled = true; + }; + }, [enabled, granted]); + + return granted; +}; diff --git a/packages/react-native-sdk/src/components/Call/Lobby/index.ts b/packages/react-native-sdk/src/components/Call/Lobby/index.ts index 9a17b409fa..419d709925 100644 --- a/packages/react-native-sdk/src/components/Call/Lobby/index.ts +++ b/packages/react-native-sdk/src/components/Call/Lobby/index.ts @@ -1,3 +1,4 @@ export * from './Lobby'; +export * from './LobbyCameraPreview'; export * from './JoinCallButton'; export * from './LobbyFooter'; diff --git a/packages/react-native-sdk/src/modules/call-manager/CallManager.ts b/packages/react-native-sdk/src/modules/call-manager/CallManager.ts index 4792999fff..f43cf1b5ba 100644 --- a/packages/react-native-sdk/src/modules/call-manager/CallManager.ts +++ b/packages/react-native-sdk/src/modules/call-manager/CallManager.ts @@ -9,8 +9,8 @@ import type { AudioEndpoint as CallingxAudioEndpoint, AudioEndpointsSnapshot as CallingxAudioSnapshot, } from '@stream-io/react-native-callingx'; -import { getCallingxLibIfAvailable } from '../../utils/push/libs/callingx'; import { videoLoggerSystem } from '@stream-io/video-client'; +import { getCallingxLibIfAvailable } from '../../utils/push/libs'; const NativeManager = NativeModules.StreamInCallManager; const CallingxModule = getCallingxLibIfAvailable(); @@ -296,26 +296,32 @@ class SpeakerManager { }; } -const shouldBypassForCallKit = (): boolean => { - if (Platform.OS !== 'ios') { - return false; - } - if (!CallingxModule) { - return false; - } - return ( - CallingxModule.isSetup && - (CallingxModule.hasRegisteredCall() || CallingxModule.isOngoingCallsEnabled) - ); -}; - export class CallManager { audioDevices = new AudioDevicesManager(); ios = new IOSCallManager(); speaker = new SpeakerManager(); /** - * Starts the in call manager. + * The audio config recorded via {@link start}. The SDK's internal call manager reads it at the + * next join-time start and applies it before the native audio manager is activated. + */ + private storedConfig?: StreamInCallManagerConfig; + + /** + * The config recorded via {@link start}. + * + * @internal Read by the SDK's internal call manager at join; not intended for app use. + */ + getStoredConfig = (): StreamInCallManagerConfig | undefined => + this.storedConfig; + + /** + * Records the desired audio config for the call. + * + * This does NOT start the native audio manager — the SDK owns native start/stop and applies this + * config at the next join-time start (before the audio manager is activated). Call it **before** + * joining. Calling it mid-call only updates the stored config; it does not change the running + * call's audio, and the new config takes effect on the next call/rejoin. * * @param config.audioRole The audio role to set. It can be one of the following: * - `'communicator'`: (Default) For use cases like video or voice calls. @@ -325,80 +331,43 @@ export class CallManager { * It prioritizes high-quality stereo audio streaming. * Audio routing is controlled by the OS, and manual switching is not supported. * - * @param config.deviceEndpointType The default audio device endpoint type to set. It can be one of the following: - * - `'speaker'`: (Default) For normal video or voice calls. + * @param config.deviceEndpointType Overrides the default audio device endpoint. When omitted, + * the SDK uses the device derived from the call settings. It can be one of the following: + * - `'speaker'`: For normal video or voice calls. * - `'earpiece'`: For voice-only mobile call type scenarios. - * - * @param config.enableStereoAudioOutput Whether to enable stereo audio output. Only supported for listener audio role. */ start = (config?: StreamInCallManagerConfig): void => { - if (shouldBypassForCallKit()) { - if (config?.audioRole === 'communicator' && CallingxModule) { - const type = config.deviceEndpointType ?? 'speaker'; - safeNativeCall('setDefaultAudioDeviceEndpointType (callingx)', () => - CallingxModule.setDefaultAudioDeviceEndpointType(type), - ); - safeNativeCall('setDefaultAudioDeviceEndpointType', () => - NativeManager.setDefaultAudioDeviceEndpointType(type), - ); - } - videoLoggerSystem - .getLogger('CallManager') - .debug( - 'start: skipping start as callkit is handling the audio session', - ); - return; - } - if (isAndroidTelecomManaged()) { - // Telecom owns routing/focus; forward the sticky preference to callingx and run in - // telecom-managed mode (StreamInCallManager keeps proximity/keep-screen-on only). - if (config?.audioRole !== 'listener' && CallingxModule) { - safeNativeCall('setDefaultAudioDeviceEndpointType (callingx)', () => - CallingxModule.setDefaultAudioDeviceEndpointType( - config?.deviceEndpointType ?? 'speaker', - ), - ); - } - NativeManager.setTelecomManagedMode(true); - NativeManager.setAudioRole(config?.audioRole ?? 'communicator'); - NativeManager.start(); - return; - } - if (Platform.OS === 'android') { - NativeManager.setTelecomManagedMode(false); - } - NativeManager.setAudioRole(config?.audioRole ?? 'communicator'); - if (config?.audioRole === 'communicator') { - const type = config.deviceEndpointType ?? 'speaker'; - NativeManager.setDefaultAudioDeviceEndpointType(type); - } - if (config?.audioRole === 'listener' && config.enableStereoAudioOutput) { - NativeManager.setEnableStereoAudioOutput(true); - } - NativeManager.start(); + this.storedConfig = config; + videoLoggerSystem + .getLogger('CallManager') + .debug('start: stored call manager config', { config }); }; /** - * Stops the in call manager. + * Clears the stored audio config. */ stop = (): void => { - if (shouldBypassForCallKit()) { - videoLoggerSystem - .getLogger('CallManager') - .debug('stop: skipping stop as callkit is handling the audio session'); - return; - } - NativeManager.stop(); + this.storedConfig = undefined; + videoLoggerSystem + .getLogger('CallManager') + .debug('[public] stop(): cleared stored config'); }; /** * For debugging purposes, will emit a log event with the current audio state. * in the native layer. + * + * NOTE: This method might be called outside of the call JOIN/LEFT window, + * so it may lead to default peer connection factory and adm being created. */ logAudioState = (): void => NativeManager.logAudioState(); /** * For debugging purposes, returns the current audio state as a string. + * + * NOTE: This method might be called outside of the call JOIN/LEFT window, + * so it may lead to default peer connection factory and adm being created. + * * @returns A string containing the current audio state information. */ getAudioStateLog = (): string => NativeManager.getAudioStateLog(); diff --git a/packages/react-native-sdk/src/modules/call-manager/native-module.d.ts b/packages/react-native-sdk/src/modules/call-manager/native-module.d.ts index 0c8fad4456..4881e7ff58 100644 --- a/packages/react-native-sdk/src/modules/call-manager/native-module.d.ts +++ b/packages/react-native-sdk/src/modules/call-manager/native-module.d.ts @@ -92,6 +92,19 @@ export interface CallManager extends NativeModule { */ setEnableStereoAudioOutput: (enable: boolean) => void; + /** + * Sets the microphone mute mode on the call's ADM. No-ops when no call ADM is active. iOS-only. + * @param mode - The `AudioEngineMuteMode` value. + */ + setMuteMode: (mode: number) => void; + + /** + * Keeps the recording chain prepared while muted so the engine stays + * full-duplex. No-ops when no call ADM is active. iOS-only. + * @param enabled - Whether to keep recording always prepared. + */ + setRecordingAlwaysPreparedMode: (enabled: boolean) => void; + /** * Log the current audio state natively. * Meant for debugging purposes. diff --git a/packages/react-native-sdk/src/modules/call-manager/types.ts b/packages/react-native-sdk/src/modules/call-manager/types.ts index eeeb7a565d..3b7950bd1c 100644 --- a/packages/react-native-sdk/src/modules/call-manager/types.ts +++ b/packages/react-native-sdk/src/modules/call-manager/types.ts @@ -56,5 +56,4 @@ export type StreamInCallManagerConfig = } | { audioRole: 'listener'; - enableStereoAudioOutput?: boolean; }; diff --git a/packages/react-native-sdk/src/utils/internal/callingx/callingx.ts b/packages/react-native-sdk/src/utils/internal/callingx/callingx.ts index 32004d6823..bfc4d9b7e5 100644 --- a/packages/react-native-sdk/src/utils/internal/callingx/callingx.ts +++ b/packages/react-native-sdk/src/utils/internal/callingx/callingx.ts @@ -2,7 +2,7 @@ * Internal utils for callingx library usage from video-client. * See @./registerSDKGlobals.ts for more usage details. */ -import { NativeModules, Platform } from 'react-native'; +import { Platform } from 'react-native'; import type { EndCallReason } from '@stream-io/react-native-callingx'; import { getCallingxLibIfAvailable } from '../../push/libs/callingx'; import type { @@ -14,46 +14,6 @@ import { CallingState, videoLoggerSystem } from '@stream-io/video-client'; const CallingxModule = getCallingxLibIfAvailable(); -/** - * Fallback when Telecom registration fails/times out on Android: no component would own audio - * (Telecom never took over), so re-establish StreamInCallManager in its classic (non-telecom) - * mode to keep the call audible. - */ -function recoverAudioToClassicMode() { - if (Platform.OS !== 'android') { - return; - } - if (!CallingxModule?.isSetup || !CallingxModule.isTelecomBacked) { - return; - } - // If a call is already registered, Telecom owns audio for it (the registration - // partially succeeded). Switching to classic mode here would fight Telecom's - // routing/focus — leave ownership intact. - if (CallingxModule.hasRegisteredCall()) { - videoLoggerSystem - .getLogger('callingx') - .debug( - 'recoverAudioToClassicMode: Telecom already owns a registered call; skipping classic fallback', - ); - return; - } - const StreamInCallManagerNativeModule = NativeModules.StreamInCallManager; - if (!StreamInCallManagerNativeModule) { - return; - } - const logger = videoLoggerSystem.getLogger('callingx'); - logger.warn( - 'Telecom registration failed; falling back to classic StreamInCallManager audio', - ); - try { - StreamInCallManagerNativeModule.stop(); - StreamInCallManagerNativeModule.setTelecomManagedMode(false); - StreamInCallManagerNativeModule.start(); - } catch (error) { - logger.error('recoverAudioToClassicMode: failed to recover audio', error); - } -} - /** * Gets the call display name. To be used for display in native call screen. */ @@ -136,7 +96,6 @@ export async function registerOutgoingCall(call: Call) { `registerOutgoingCall: Error registering outgoing call in callingx: ${call.cid}`, error, ); - recoverAudioToClassicMode(); } } @@ -183,7 +142,6 @@ export async function joinCallingxCall(call: Call, activeCalls: Call[]) { `startCallingxCall: Error starting call in callingx: ${call.cid}`, error, ); - recoverAudioToClassicMode(); } } else if (isIncomingCall) { logger.debug(`joinCallingxCall: Joining incoming call ${call.cid}`); @@ -221,7 +179,6 @@ export async function joinCallingxCall(call: Call, activeCalls: Call[]) { `Error joining incoming call in callingx: ${call.cid}`, error, ); - recoverAudioToClassicMode(); } } } @@ -246,3 +203,37 @@ export async function endCallingxCall(call: Call, reason?: EndCallReason) { ); } } + +export async function wireAudioEngineSubscription() { + if (!CallingxModule || !CallingxModule.isSetup || Platform.OS !== 'ios') { + return; + } + const logger = videoLoggerSystem.getLogger('callingx'); + + try { + logger.debug('wireEngineSubscription: Wiring engine subscription'); + CallingxModule.wireAudioEngineSubscription(); + } catch (error) { + logger.error( + 'wireAudioEngineSubscription: Error wiring engine subscription', + error, + ); + } +} + +export function unwireAudioEngineSubscription() { + if (!CallingxModule || !CallingxModule.isSetup || Platform.OS !== 'ios') { + return; + } + const logger = videoLoggerSystem.getLogger('callingx'); + + try { + logger.debug('unwireEngineSubscription: Cancelling engine subscription'); + CallingxModule.unwireAudioEngineSubscription(); + } catch (error) { + logger.error( + 'unwireAudioEngineSubscription: Error cancelling engine subscription', + error, + ); + } +} diff --git a/packages/react-native-sdk/src/utils/internal/registerMediaEngine.ts b/packages/react-native-sdk/src/utils/internal/registerMediaEngine.ts new file mode 100644 index 0000000000..2d5f2f5c28 --- /dev/null +++ b/packages/react-native-sdk/src/utils/internal/registerMediaEngine.ts @@ -0,0 +1,42 @@ +import { + type CallMediaEngine, + setCallMediaEngineProvider, + videoLoggerSystem, +} from '@stream-io/video-client'; +import { CallFactory } from '@stream-io/react-native-webrtc'; +import { callManager } from '../../modules/call-manager'; + +const logger = videoLoggerSystem.getLogger('CallMediaEngine'); + +/** + * Registers the React Native {@link CallMediaEngine} provider. + * + * Once registered, every `Call.ensureMediaFactory()` builds the call's native + * `PeerConnectionFactory` (with the call's audio configuration) and sets it as + * the single live factory. The WebRTC globals (`getUserMedia`/`getDisplayMedia`/ + * `new RTCPeerConnection`) then resolve to it, so the call's tracks and peer + * connections are created with — and torn down with — that one native factory, + * which is what lets the call own its AudioDeviceModule. The engine itself only + * manages the factory lifecycle (`dispose` at leave). + * + * @internal + */ +export function registerCallMediaEngine() { + setCallMediaEngineProvider(async (): Promise => { + const config = callManager.getStoredConfig(); + const bypassVoiceProcessing = config?.audioRole === 'listener'; + const factory = await CallFactory.create({ + bypassVoiceProcessing, + }); + logger.debug( + `Created per-call factory (bypassVoiceProcessing=${bypassVoiceProcessing})`, + ); + + return { + dispose: async () => { + logger.debug('Disposing per-call factory'); + return await factory.dispose(); + }, + }; + }); +} diff --git a/packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts b/packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts index f821a087f5..1a318307c8 100644 --- a/packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts +++ b/packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts @@ -1,7 +1,9 @@ -import { StreamRNVideoSDKGlobals } from '@stream-io/video-client'; +import { + StreamRNVideoSDKGlobals, + videoLoggerSystem, +} from '@stream-io/video-client'; import { NativeModules, PermissionsAndroid, Platform } from 'react-native'; import { - AudioDeviceModule, AudioEngineMuteMode, audioDeviceModuleEvents, } from '@stream-io/react-native-webrtc'; @@ -10,7 +12,11 @@ import { endCallingxCall, registerOutgoingCall, joinCallingxCall, + wireAudioEngineSubscription, + unwireAudioEngineSubscription, } from './callingx/callingx'; +import { registerCallMediaEngine } from './registerMediaEngine'; +import { callManager as publicCallManager } from '../../modules/call-manager'; const StreamInCallManagerNativeModule = NativeModules.StreamInCallManager; const StreamVideoReactNativeModule = NativeModules.StreamVideoReactNative as { @@ -19,6 +25,18 @@ const StreamVideoReactNativeModule = NativeModules.StreamVideoReactNative as { const CallingxModule = getCallingxLibIfAvailable(); +/** + * Runs a fire-and-forget native call, logging instead of throwing on a bridge + * error so it can't crash the caller (e.g. the join/leave flow). + */ +const safeNativeCall = (label: string, fn: () => void): void => { + try { + fn(); + } catch (error) { + videoLoggerSystem.getLogger('CallManager').warn(`${label} failed`, error); + } +}; + /** * Checks if StreamInCallManager should be bypassed because CallKit is handling * the audio session via CallingX. @@ -52,22 +70,17 @@ const shouldBypassForCallKit = ({ * StreamInCallManager entirely — Telecom provides no proximity/keep-screen-on — instead we * run it in "telecom-managed" mode where it only keeps proximity/keep-screen-on/mute. */ -const isAndroidTelecomManaged = ({ - isRingingTypeCall, -}: { - isRingingTypeCall: boolean; -}): boolean => { - if (Platform.OS !== 'android') { +const isAndroidTelecomManaged = ({ cid }: { cid: string }): boolean => { + if (Platform.OS !== 'android' || !CallingxModule) { return false; } - if (!CallingxModule) { + if (!CallingxModule.isSetup || !CallingxModule.isTelecomBacked) { return false; } - return ( - CallingxModule.isSetup && - CallingxModule.isTelecomBacked && - (isRingingTypeCall || CallingxModule.isOngoingCallsEnabled) - ); + // Trust Telecom's actual per-call registration. A call that never registered + // (e.g. Telecom registration failed) is not managed, so start() falls through + // to the classic path — this is where audio recovery happens. + return CallingxModule.isCallTracked(cid); }; const streamRNVideoSDKGlobals: StreamRNVideoSDKGlobals = { @@ -75,43 +88,101 @@ const streamRNVideoSDKGlobals: StreamRNVideoSDKGlobals = { joinCall: joinCallingxCall, endCall: endCallingxCall, registerOutgoingCall: registerOutgoingCall, + wireAudioEngineSubscription: wireAudioEngineSubscription, + unwireAudioEngineSubscription: unwireAudioEngineSubscription, }, callManager: { - setup: ({ defaultDevice, isRingingTypeCall }) => { - if (shouldBypassForCallKit({ isRingingTypeCall })) { - // Forward the sticky preference; callingx reads it on next CallKit activation. - CallingxModule?.setDefaultAudioDeviceEndpointType(defaultDevice); - return; + setup: ({ defaultDevice, isRingingTypeCall, cid }) => { + const isTelecomManaged = isAndroidTelecomManaged({ cid }); + const isCallKitManaged = shouldBypassForCallKit({ isRingingTypeCall }); + if (Platform.OS === 'android') { + StreamInCallManagerNativeModule.setTelecomManagedMode(isTelecomManaged); } - if (isAndroidTelecomManaged({ isRingingTypeCall })) { - // Telecom owns routing; forward the sticky preference to callingx and run the - // in-call manager in telecom-managed mode (proximity/keep-screen-on only). - CallingxModule?.setDefaultAudioDeviceEndpointType(defaultDevice); - StreamInCallManagerNativeModule.setTelecomManagedMode(true); - StreamInCallManagerNativeModule.setup(); - return; + + if (isTelecomManaged || isCallKitManaged) { + safeNativeCall('setup defaultAudioDevice (callingx)', () => + CallingxModule?.setDefaultAudioDeviceEndpointType(defaultDevice), + ); } - if (Platform.OS === 'android') { - StreamInCallManagerNativeModule.setTelecomManagedMode(false); + + if (!isTelecomManaged) { + safeNativeCall('setup defaultAudioDevice', () => + StreamInCallManagerNativeModule.setDefaultAudioDeviceEndpointType( + defaultDevice, + ), + ); } - StreamInCallManagerNativeModule.setDefaultAudioDeviceEndpointType( - defaultDevice, - ); - StreamInCallManagerNativeModule.setup(); }, - start: ({ isRingingTypeCall }) => { + start: ({ isRingingTypeCall, cid }) => { + // Apply the audio config a consumer recorded via `callManager.start(config)` at this single + // join-time start, before the native audio manager is activated. + const config = publicCallManager.getStoredConfig(); + const deviceOverride = + config?.audioRole === 'communicator' + ? config.deviceEndpointType + : undefined; + if (shouldBypassForCallKit({ isRingingTypeCall })) { + // CallKit owns activation. Only forward an explicit endpoint override; the + // SpeakerManager-derived default was already forwarded via `setup`. + if (deviceOverride) { + safeNativeCall('start defaultAudioDevice (callingx)', () => + CallingxModule?.setDefaultAudioDeviceEndpointType(deviceOverride), + ); + safeNativeCall('start defaultAudioDevice', () => + StreamInCallManagerNativeModule.setDefaultAudioDeviceEndpointType( + deviceOverride, + ), + ); + } return; } - // Android telecom-managed calls still start (for proximity/keep-screen-on); - // the native side skips routing/focus internally. + + const isTelecomManaged = isAndroidTelecomManaged({ cid }); + if (Platform.OS === 'android') { + StreamInCallManagerNativeModule.setTelecomManagedMode(isTelecomManaged); + } + if (config?.audioRole) { + StreamInCallManagerNativeModule.setAudioRole(config.audioRole); + } + if (deviceOverride) { + // Override the SpeakerManager-derived default device (set via `setup`). + if (isTelecomManaged) { + safeNativeCall('start defaultAudioDevice (callingx)', () => + CallingxModule?.setDefaultAudioDeviceEndpointType(deviceOverride), + ); + } else { + safeNativeCall('start defaultAudioDevice', () => + StreamInCallManagerNativeModule.setDefaultAudioDeviceEndpointType( + deviceOverride, + ), + ); + } + } + + const stereoOutput = config?.audioRole === 'listener'; + StreamInCallManagerNativeModule.setEnableStereoAudioOutput(stereoOutput); StreamInCallManagerNativeModule.start(); }, - stop: ({ isRingingTypeCall }) => { - if (shouldBypassForCallKit({ isRingingTypeCall })) { - return; + stop: ({ isRingingTypeCall, shouldStopCallManager }) => { + // Clear the stored audio config so it doesn't carry into the next call. + publicCallManager.stop(); + + // We want to interact with ADM only when it was instantiated. This guards a case when + // leave is invoked for ringing call - in this case PC Factory and ADM are not yet created. + if (shouldStopCallManager) { + // Teardown of setMutedRecordingPrepared. Done here (before the CallKit gate) + // so it runs on both paths and while the call factory is still alive: leave() + // calls stop() before disposing the engine, so the ADM resolves to the call's + // factory rather than a default. + if (Platform.OS === 'ios') { + StreamInCallManagerNativeModule.setRecordingAlwaysPreparedMode(false); + } + if (shouldBypassForCallKit({ isRingingTypeCall })) { + return; + } + StreamInCallManagerNativeModule.stop(); } - StreamInCallManagerNativeModule.stop(); }, // iOS-only. Keep the AVAudioEngine mic-input (voice-processing) chain // prepared while muted so the engine stays full-duplex and remote audio @@ -125,11 +196,11 @@ const streamRNVideoSDKGlobals: StreamRNVideoSDKGlobals = { if (enabled) { // Mute via the voice-processing unit (it's the default, fail safe config here) so the input chain // stays built while muted, rather than tearing the engine down. - AudioDeviceModule.setMuteMode( + StreamInCallManagerNativeModule.setMuteMode( AudioEngineMuteMode.VoiceProcessing, - ).catch(() => {}); + ); } - AudioDeviceModule.setRecordingAlwaysPreparedMode(enabled).catch(() => {}); + StreamInCallManagerNativeModule.setRecordingAlwaysPreparedMode(enabled); }, }, permissions: { @@ -169,4 +240,6 @@ export function registerSDKGlobals() { if (!globalThis.streamRNVideoSDK) { globalThis.streamRNVideoSDK = streamRNVideoSDKGlobals; } + + registerCallMediaEngine(); } diff --git a/packages/video-filters-react-native/package.json b/packages/video-filters-react-native/package.json index a1e999d1ae..97a7a42c40 100644 --- a/packages/video-filters-react-native/package.json +++ b/packages/video-filters-react-native/package.json @@ -48,7 +48,7 @@ }, "homepage": "https://github.com/GetStream/stream-video-js#readme", "devDependencies": { - "@stream-io/react-native-webrtc": "145.2.0", + "@stream-io/react-native-webrtc": "145.3.0", "@stream-io/typescript-config": "workspace:^", "react": "19.2.3", "react-native": "0.86.2", @@ -57,7 +57,7 @@ "typescript": "^6.0.3" }, "peerDependencies": { - "@stream-io/react-native-webrtc": "^145.2.0", + "@stream-io/react-native-webrtc": "^145.3.0", "react-native": "*" }, "react-native-builder-bob": { diff --git a/sample-apps/react-native/dogfood/ios/Podfile.lock b/sample-apps/react-native/dogfood/ios/Podfile.lock index 39f22a8268..eb56d12d50 100644 --- a/sample-apps/react-native/dogfood/ios/Podfile.lock +++ b/sample-apps/react-native/dogfood/ios/Podfile.lock @@ -1564,7 +1564,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-safe-area-context (5.8.0): + - react-native-safe-area-context (5.8.1): - hermes-engine - RCTRequired - RCTTypeSafety @@ -1576,8 +1576,8 @@ PODS: - React-graphics - React-ImageManager - React-jsi - - react-native-safe-area-context/common (= 5.8.0) - - react-native-safe-area-context/fabric (= 5.8.0) + - react-native-safe-area-context/common (= 5.8.1) + - react-native-safe-area-context/fabric (= 5.8.1) - React-NativeModulesApple - React-RCTFabric - React-renderercss @@ -1588,7 +1588,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-safe-area-context/common (5.8.0): + - react-native-safe-area-context/common (5.8.1): - hermes-engine - RCTRequired - RCTTypeSafety @@ -1610,7 +1610,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-safe-area-context/fabric (5.8.0): + - react-native-safe-area-context/fabric (5.8.1): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2549,9 +2549,9 @@ PODS: - ReactNativeDependencies - stream-react-native-webrtc - Yoga - - stream-react-native-webrtc (145.2.0): + - stream-react-native-webrtc (145.3.0): - React-Core - - StreamWebRTC (= 145.12.0) + - StreamWebRTC (= 145.14.0) - stream-video-react-native (1.42.0): - hermes-engine - RCTRequired @@ -2576,7 +2576,7 @@ PODS: - stream-react-native-webrtc - Yoga - StreamVideoNoiseCancellation (1.0.3) - - StreamWebRTC (145.12.0) + - StreamWebRTC (145.14.0) - Teleport (1.1.12): - hermes-engine - RCTRequired @@ -2995,7 +2995,7 @@ SPEC CHECKSUMS: react-native-blob-util: 37e8b9921fe7bfa1c83c156bdba73992e19b9bde react-native-image-picker: 9dce42d17f5917de55bdff39a587390fd496beda react-native-netinfo: 10fc5ca4331d893efc2b90adb43869a85d66654e - react-native-safe-area-context: e587b3bb773c66ba6f14e319522f951c73186987 + react-native-safe-area-context: bcea1b7671431001d60db53f48c2e768698d2860 react-native-video: c8a32ec11cf5134121fa8bf07017a2522f736b65 React-NativeModulesApple: f313ed47b56405d621e12ecda106a72020959ff3 React-networking: e424e5e77a7b124143ceac8acf44352e0baae8bd @@ -3047,10 +3047,10 @@ SPEC CHECKSUMS: stream-chat-react-native: baf7e1c3fc101f8a1b95ad0c6693ddaf2bab384e stream-io-noise-cancellation-react-native: b96decfa2b58fcfb2737b2c35c710a002c7b67c3 stream-io-video-filters-react-native: b6bd02235657b56c9855126f94ffd91a7cfbaf13 - stream-react-native-webrtc: 74a1d1404c0c7087eef0b1e1a077620033ad996e + stream-react-native-webrtc: 1ca47396c7e271a6cce7897dbcdfbe19873398a4 stream-video-react-native: cb02a465bae6219aeefa7065b834e93d67f8adbc StreamVideoNoiseCancellation: 41f5a712aba288f9636b64b17ebfbdff52c61490 - StreamWebRTC: e46af4ca150d71aaa662985d6fd0d62b575c6f49 + StreamWebRTC: 3c6cb5fd01458172a28046ed7f162e24e88c1332 Teleport: 4cb9855422c04666fbeee9a52e5342466e27b009 VisionCamera: 68d40255fa8866e815cbbb063730102d3da276fd Yoga: 542a30dafe5b0f5f1d9f185ea7b2a3811ac54801 diff --git a/sample-apps/react-native/dogfood/package.json b/sample-apps/react-native/dogfood/package.json index 0bea83c80c..6618a49af0 100644 --- a/sample-apps/react-native/dogfood/package.json +++ b/sample-apps/react-native/dogfood/package.json @@ -23,7 +23,7 @@ "@react-navigation/native-stack": "^7.18.6", "@stream-io/noise-cancellation-react-native": "workspace:^", "@stream-io/react-native-callingx": "workspace:^", - "@stream-io/react-native-webrtc": "145.2.0", + "@stream-io/react-native-webrtc": "145.3.0", "@stream-io/video-filters-react-native": "workspace:^", "@stream-io/video-react-native-sdk": "workspace:^", "axios": "^1.19.0", diff --git a/sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx b/sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx index 0913a85060..ba7f1a98e7 100644 --- a/sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx +++ b/sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx @@ -43,7 +43,7 @@ export const ViewLiveStreamChildren = ({ } = route; useEffect(() => { - callManager.start({ audioRole: 'listener', enableStereoAudioOutput: true }); + callManager.start({ audioRole: 'listener' }); return () => { callManager.stop(); }; diff --git a/sample-apps/react-native/expo-video-sample/package.json b/sample-apps/react-native/expo-video-sample/package.json index 27fce3cc85..05ed362688 100644 --- a/sample-apps/react-native/expo-video-sample/package.json +++ b/sample-apps/react-native/expo-video-sample/package.json @@ -19,7 +19,7 @@ "@react-native-firebase/messaging": "^24.1.1", "@stream-io/noise-cancellation-react-native": "workspace:^", "@stream-io/react-native-callingx": "workspace:^", - "@stream-io/react-native-webrtc": "145.2.0", + "@stream-io/react-native-webrtc": "145.3.0", "@stream-io/video-filters-react-native": "workspace:^", "@stream-io/video-react-native-sdk": "workspace:^", "expo": "~57.0.9", diff --git a/sample-apps/react-native/ringing-tutorial/package.json b/sample-apps/react-native/ringing-tutorial/package.json index 0fe4126024..340b33920a 100644 --- a/sample-apps/react-native/ringing-tutorial/package.json +++ b/sample-apps/react-native/ringing-tutorial/package.json @@ -18,7 +18,7 @@ "@react-native-firebase/app": "^24.1.1", "@react-native-firebase/messaging": "^24.1.1", "@stream-io/react-native-callingx": "workspace:^", - "@stream-io/react-native-webrtc": "145.2.0", + "@stream-io/react-native-webrtc": "145.3.0", "@stream-io/video-react-native-sdk": "workspace:^", "expo": "~57.0.9", "expo-build-properties": "~57.0.8", diff --git a/yarn.lock b/yarn.lock index b79c976966..33ca0198a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1688,16 +1688,6 @@ __metadata: languageName: node linkType: hard -"@emnapi/core@npm:2.0.0-alpha.3": - version: 2.0.0-alpha.3 - resolution: "@emnapi/core@npm:2.0.0-alpha.3" - dependencies: - "@emnapi/wasi-threads": "npm:2.0.1" - tslib: "npm:^2.4.0" - checksum: 10/cd71d0af78c858daf14d378d960922ac9a00e108401c76d852fe467916fd5ac8d5575303a14c73d24eaed8dc626464f2a29f2905542b9a457e4b90a6ed519008 - languageName: node - linkType: hard - "@emnapi/core@npm:^1.1.0": version: 1.11.3 resolution: "@emnapi/core@npm:1.11.3" @@ -1717,16 +1707,7 @@ __metadata: languageName: node linkType: hard -"@emnapi/runtime@npm:2.0.0-alpha.3": - version: 2.0.0-alpha.3 - resolution: "@emnapi/runtime@npm:2.0.0-alpha.3" - dependencies: - tslib: "npm:^2.4.0" - checksum: 10/f5acb0f51e225b18f4aba6223016e3628d23a05f90ef4be11f7a25b09e0c8b285350f095818e087946945b696127286a50a57f7aaf94d420e6116f5e2101c531 - languageName: node - linkType: hard - -"@emnapi/runtime@npm:^1.1.0, @emnapi/runtime@npm:^1.7.0": +"@emnapi/runtime@npm:^1.1.0, @emnapi/runtime@npm:^1.11.1": version: 1.11.3 resolution: "@emnapi/runtime@npm:1.11.3" dependencies: @@ -1753,15 +1734,6 @@ __metadata: languageName: node linkType: hard -"@emnapi/wasi-threads@npm:2.0.1": - version: 2.0.1 - resolution: "@emnapi/wasi-threads@npm:2.0.1" - dependencies: - tslib: "npm:^2.4.0" - checksum: 10/91dbd7d2ec4ebe01e0e889d9d4d6b79a2a3180ed61e2bdc537c2f7dcb026ac2f8d54126a0966bbcf6fc714f834638d40462c6c9a9bcfb35ee2a7b9897cd06877 - languageName: node - linkType: hard - "@emotion/babel-plugin@npm:^11.13.5": version: 11.13.5 resolution: "@emotion/babel-plugin@npm:11.13.5" @@ -2016,9 +1988,9 @@ __metadata: languageName: node linkType: hard -"@expo/cli@npm:^57.0.11": - version: 57.0.11 - resolution: "@expo/cli@npm:57.0.11" +"@expo/cli@npm:^57.0.12": + version: 57.0.12 + resolution: "@expo/cli@npm:57.0.12" dependencies: "@expo/code-signing-certificates": "npm:^0.0.6" "@expo/config": "npm:~57.0.6" @@ -2037,7 +2009,7 @@ __metadata: "@expo/plist": "npm:^0.8.1" "@expo/prebuild-config": "npm:^57.0.10" "@expo/require-utils": "npm:^57.0.4" - "@expo/router-server": "npm:^57.0.4" + "@expo/router-server": "npm:^57.0.5" "@expo/schema-utils": "npm:^57.0.2" "@expo/spawn-async": "npm:^1.8.0" "@expo/ws-tunnel": "npm:^2.0.0" @@ -2089,7 +2061,7 @@ __metadata: optional: true bin: expo-internal: main.js - checksum: 10/c50e131a4bb7ca0049299cdc951d1b91560ed31bc6d292ccbf79c49ee4ecb8a6f65598b0018bee3105f64ad576fa45322c3239411f1981e9183a5e319d8a0eea + checksum: 10/e37ba9773a187dfe2adf154fdb4edb83f2548537d61e2becc4d3ae2121c1ff149cc66da64230105a7968db31ac870538c6654b5709a6be5efb38a101baee22bc languageName: node linkType: hard @@ -2458,15 +2430,15 @@ __metadata: languageName: node linkType: hard -"@expo/router-server@npm:^57.0.4": - version: 57.0.4 - resolution: "@expo/router-server@npm:57.0.4" +"@expo/router-server@npm:^57.0.5": + version: 57.0.5 + resolution: "@expo/router-server@npm:57.0.5" dependencies: debug: "npm:^4.3.4" peerDependencies: - "@expo/metro-runtime": ^57.0.7 + "@expo/metro-runtime": ^57.0.8 expo: "*" - expo-constants: ^57.0.7 + expo-constants: ^57.0.9 expo-font: ^57.0.1 expo-router: "*" expo-server: ^57.0.1 @@ -2482,7 +2454,7 @@ __metadata: optional: true react-server-dom-webpack: optional: true - checksum: 10/effd7311160d8233824b231d9729c68a07f6ff2923a1bdad4bcfaa82b92cc3b995686eb213a2e983cd536621abd2dc2ecb5c70ed5f8bec5d334f5b744e2dc82e + checksum: 10/87a6acd5a81a5a31a0ceb5126b48565133f9a257ed5fe33af758c68874df2ea3086bfd8763ac1b4ae5524773199047c9f8fbfc57760e3ea6862f1534dfd746a3 languageName: node linkType: hard @@ -2516,9 +2488,9 @@ __metadata: languageName: node linkType: hard -"@expo/ui@npm:^57.0.8": - version: 57.0.8 - resolution: "@expo/ui@npm:57.0.8" +"@expo/ui@npm:^57.0.9": + version: 57.0.9 + resolution: "@expo/ui@npm:57.0.9" dependencies: sf-symbols-typescript: "npm:^2.1.0" vaul: "npm:^1.1.2" @@ -2536,7 +2508,7 @@ __metadata: optional: true react-native-worklets: optional: true - checksum: 10/63e8f507cc28bc1528d395b5075ba04926a12f71a23d27712b201a15cc7c63ada9232c2331e592bd08dfa6b7a5b1ebf19af30db9b552e5107501e2a10f90ebfa + checksum: 10/e779a7e415737003fa775276aee7db6701f726a72cb658ba18b95989fa99c4e4e91762fbef9771ad1ea2ff72440934786fe5e4430baa478eeba20b183f116105 languageName: node linkType: hard @@ -3277,18 +3249,18 @@ __metadata: languageName: node linkType: hard -"@img/colour@npm:^1.0.0": +"@img/colour@npm:^1.1.0": version: 1.1.0 resolution: "@img/colour@npm:1.1.0" checksum: 10/2a29be7b06b046bd33c80ffa0f3493b7535b0841a69f54af81bf5e2d8867f21704fab42e9cf24ec30c089de34f790bae4dad1fc617c3163fbf264998dc316f0a languageName: node linkType: hard -"@img/sharp-darwin-arm64@npm:0.34.5": - version: 0.34.5 - resolution: "@img/sharp-darwin-arm64@npm:0.34.5" +"@img/sharp-darwin-arm64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-darwin-arm64@npm:0.35.3" dependencies: - "@img/sharp-libvips-darwin-arm64": "npm:1.2.4" + "@img/sharp-libvips-darwin-arm64": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-darwin-arm64": optional: true @@ -3296,11 +3268,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-darwin-x64@npm:0.34.5": - version: 0.34.5 - resolution: "@img/sharp-darwin-x64@npm:0.34.5" +"@img/sharp-darwin-x64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-darwin-x64@npm:0.35.3" dependencies: - "@img/sharp-libvips-darwin-x64": "npm:1.2.4" + "@img/sharp-libvips-darwin-x64": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-darwin-x64": optional: true @@ -3308,81 +3280,90 @@ __metadata: languageName: node linkType: hard -"@img/sharp-libvips-darwin-arm64@npm:1.2.4": - version: 1.2.4 - resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.4" +"@img/sharp-freebsd-wasm32@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-freebsd-wasm32@npm:0.35.3" + dependencies: + "@img/sharp-wasm32": "npm:0.35.3" + conditions: os=freebsd + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-arm64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.3.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@img/sharp-libvips-darwin-x64@npm:1.2.4": - version: 1.2.4 - resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.4" +"@img/sharp-libvips-darwin-x64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.3.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@img/sharp-libvips-linux-arm64@npm:1.2.4": - version: 1.2.4 - resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.4" +"@img/sharp-libvips-linux-arm64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.3.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-arm@npm:1.2.4": - version: 1.2.4 - resolution: "@img/sharp-libvips-linux-arm@npm:1.2.4" +"@img/sharp-libvips-linux-arm@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-arm@npm:1.3.2" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-ppc64@npm:1.2.4": - version: 1.2.4 - resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.4" +"@img/sharp-libvips-linux-ppc64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-ppc64@npm:1.3.2" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-riscv64@npm:1.2.4": - version: 1.2.4 - resolution: "@img/sharp-libvips-linux-riscv64@npm:1.2.4" +"@img/sharp-libvips-linux-riscv64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-riscv64@npm:1.3.2" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-s390x@npm:1.2.4": - version: 1.2.4 - resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.4" +"@img/sharp-libvips-linux-s390x@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.3.2" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-x64@npm:1.2.4": - version: 1.2.4 - resolution: "@img/sharp-libvips-linux-x64@npm:1.2.4" +"@img/sharp-libvips-linux-x64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-x64@npm:1.3.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4": - version: 1.2.4 - resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4" +"@img/sharp-libvips-linuxmusl-arm64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.3.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@img/sharp-libvips-linuxmusl-x64@npm:1.2.4": - version: 1.2.4 - resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.4" +"@img/sharp-libvips-linuxmusl-x64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.3.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@img/sharp-linux-arm64@npm:0.34.5": - version: 0.34.5 - resolution: "@img/sharp-linux-arm64@npm:0.34.5" +"@img/sharp-linux-arm64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-arm64@npm:0.35.3" dependencies: - "@img/sharp-libvips-linux-arm64": "npm:1.2.4" + "@img/sharp-libvips-linux-arm64": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-linux-arm64": optional: true @@ -3390,11 +3371,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-arm@npm:0.34.5": - version: 0.34.5 - resolution: "@img/sharp-linux-arm@npm:0.34.5" +"@img/sharp-linux-arm@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-arm@npm:0.35.3" dependencies: - "@img/sharp-libvips-linux-arm": "npm:1.2.4" + "@img/sharp-libvips-linux-arm": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-linux-arm": optional: true @@ -3402,11 +3383,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-ppc64@npm:0.34.5": - version: 0.34.5 - resolution: "@img/sharp-linux-ppc64@npm:0.34.5" +"@img/sharp-linux-ppc64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-ppc64@npm:0.35.3" dependencies: - "@img/sharp-libvips-linux-ppc64": "npm:1.2.4" + "@img/sharp-libvips-linux-ppc64": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-linux-ppc64": optional: true @@ -3414,11 +3395,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-riscv64@npm:0.34.5": - version: 0.34.5 - resolution: "@img/sharp-linux-riscv64@npm:0.34.5" +"@img/sharp-linux-riscv64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-riscv64@npm:0.35.3" dependencies: - "@img/sharp-libvips-linux-riscv64": "npm:1.2.4" + "@img/sharp-libvips-linux-riscv64": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-linux-riscv64": optional: true @@ -3426,11 +3407,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-s390x@npm:0.34.5": - version: 0.34.5 - resolution: "@img/sharp-linux-s390x@npm:0.34.5" +"@img/sharp-linux-s390x@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-s390x@npm:0.35.3" dependencies: - "@img/sharp-libvips-linux-s390x": "npm:1.2.4" + "@img/sharp-libvips-linux-s390x": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-linux-s390x": optional: true @@ -3438,11 +3419,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-x64@npm:0.34.5": - version: 0.34.5 - resolution: "@img/sharp-linux-x64@npm:0.34.5" +"@img/sharp-linux-x64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-x64@npm:0.35.3" dependencies: - "@img/sharp-libvips-linux-x64": "npm:1.2.4" + "@img/sharp-libvips-linux-x64": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-linux-x64": optional: true @@ -3450,11 +3431,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linuxmusl-arm64@npm:0.34.5": - version: 0.34.5 - resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.5" +"@img/sharp-linuxmusl-arm64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.35.3" dependencies: - "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-linuxmusl-arm64": optional: true @@ -3462,11 +3443,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linuxmusl-x64@npm:0.34.5": - version: 0.34.5 - resolution: "@img/sharp-linuxmusl-x64@npm:0.34.5" +"@img/sharp-linuxmusl-x64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linuxmusl-x64@npm:0.35.3" dependencies: - "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-linuxmusl-x64": optional: true @@ -3474,32 +3455,41 @@ __metadata: languageName: node linkType: hard -"@img/sharp-wasm32@npm:0.34.5": - version: 0.34.5 - resolution: "@img/sharp-wasm32@npm:0.34.5" +"@img/sharp-wasm32@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-wasm32@npm:0.35.3" dependencies: - "@emnapi/runtime": "npm:^1.7.0" + "@emnapi/runtime": "npm:^1.11.1" + checksum: 10/9cad3671879be2448c6252a978af2dc39727e4464d335a3eea5aab6751596b8af68bba9487f5fd2600671807f7d4ec34abfbd78b01ededb48843d904c6a1387d + languageName: node + linkType: hard + +"@img/sharp-webcontainers-wasm32@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-webcontainers-wasm32@npm:0.35.3" + dependencies: + "@img/sharp-wasm32": "npm:0.35.3" conditions: cpu=wasm32 languageName: node linkType: hard -"@img/sharp-win32-arm64@npm:0.34.5": - version: 0.34.5 - resolution: "@img/sharp-win32-arm64@npm:0.34.5" +"@img/sharp-win32-arm64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-win32-arm64@npm:0.35.3" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@img/sharp-win32-ia32@npm:0.34.5": - version: 0.34.5 - resolution: "@img/sharp-win32-ia32@npm:0.34.5" +"@img/sharp-win32-ia32@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-win32-ia32@npm:0.35.3" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@img/sharp-win32-x64@npm:0.34.5": - version: 0.34.5 - resolution: "@img/sharp-win32-x64@npm:0.34.5" +"@img/sharp-win32-x64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-win32-x64@npm:0.35.3" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -4253,18 +4243,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-runtime@npm:^1.2.0": - version: 1.2.2 - resolution: "@napi-rs/wasm-runtime@npm:1.2.2" - dependencies: - "@tybys/wasm-util": "npm:^0.10.3" - peerDependencies: - "@emnapi/core": ^1.7.1 || ^2.0.0-alpha.3 - "@emnapi/runtime": ^1.7.1 || ^2.0.0-alpha.3 - checksum: 10/19b07c017845142b711e1515684d33c16a4c0c0d64a8876c30953f0e6975a3d018ce6e8bfb1721f25dc34df3bcc689cfb44e80fe8b8ee1b72b8429d5ff2eaf61 - languageName: node - linkType: hard - "@nestjs/axios@npm:4.0.1": version: 4.0.1 resolution: "@nestjs/axios@npm:4.0.1" @@ -4326,78 +4304,78 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:16.2.12": - version: 16.2.12 - resolution: "@next/env@npm:16.2.12" - checksum: 10/b9cfd8e32892dc1bcfc34496cbc09a30bdba9a83d2caaf828fd646d03da65a8e2f2a01db2a6779ac9527592cfacf6a6e71975851657da277d8c4af6097bc5b21 +"@next/env@npm:16.3.0": + version: 16.3.0 + resolution: "@next/env@npm:16.3.0" + checksum: 10/ffe56c5fe45e3c054487a795584ff17c7261db2a07d1244dc68d497e5aa54c13bfdff95155fbe8f9e8db21b73481ad1b463145231055a681668ac9de3a3b86ac languageName: node linkType: hard -"@next/swc-darwin-arm64@npm:16.2.12": - version: 16.2.12 - resolution: "@next/swc-darwin-arm64@npm:16.2.12" +"@next/swc-darwin-arm64@npm:16.3.0": + version: 16.3.0 + resolution: "@next/swc-darwin-arm64@npm:16.3.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@next/swc-darwin-x64@npm:16.2.12": - version: 16.2.12 - resolution: "@next/swc-darwin-x64@npm:16.2.12" +"@next/swc-darwin-x64@npm:16.3.0": + version: 16.3.0 + resolution: "@next/swc-darwin-x64@npm:16.3.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@next/swc-linux-arm64-gnu@npm:16.2.12": - version: 16.2.12 - resolution: "@next/swc-linux-arm64-gnu@npm:16.2.12" +"@next/swc-linux-arm64-gnu@npm:16.3.0": + version: 16.3.0 + resolution: "@next/swc-linux-arm64-gnu@npm:16.3.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:16.2.12": - version: 16.2.12 - resolution: "@next/swc-linux-arm64-musl@npm:16.2.12" +"@next/swc-linux-arm64-musl@npm:16.3.0": + version: 16.3.0 + resolution: "@next/swc-linux-arm64-musl@npm:16.3.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:16.2.12": - version: 16.2.12 - resolution: "@next/swc-linux-x64-gnu@npm:16.2.12" +"@next/swc-linux-x64-gnu@npm:16.3.0": + version: 16.3.0 + resolution: "@next/swc-linux-x64-gnu@npm:16.3.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:16.2.12": - version: 16.2.12 - resolution: "@next/swc-linux-x64-musl@npm:16.2.12" +"@next/swc-linux-x64-musl@npm:16.3.0": + version: 16.3.0 + resolution: "@next/swc-linux-x64-musl@npm:16.3.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:16.2.12": - version: 16.2.12 - resolution: "@next/swc-win32-arm64-msvc@npm:16.2.12" +"@next/swc-win32-arm64-msvc@npm:16.3.0": + version: 16.3.0 + resolution: "@next/swc-win32-arm64-msvc@npm:16.3.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@next/swc-win32-x64-msvc@npm:16.2.12": - version: 16.2.12 - resolution: "@next/swc-win32-x64-msvc@npm:16.2.12" +"@next/swc-win32-x64-msvc@npm:16.3.0": + version: 16.3.0 + resolution: "@next/swc-win32-x64-msvc@npm:16.3.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@next/third-parties@npm:^16.2.12": - version: 16.2.12 - resolution: "@next/third-parties@npm:16.2.12" + version: 16.3.0 + resolution: "@next/third-parties@npm:16.3.0" dependencies: third-party-capital: "npm:1.0.20" peerDependencies: next: ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0-beta.0 react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 - checksum: 10/fc07a2ec296978dfaa3bdd3db557b10349b3a818a75469a627f8af742df735a67d036819f569c27ad57354aeaa7c6a7369c830b2baf8a1690de1e39fe5f49f4e + checksum: 10/8b9632cf71844f828772de740f357be5f70908e9d6c69ecb0cf9a91c3c30a6327431cc038562a6035b5e74a8cc7e8b8246a72b77702e0e49ccae33ad2b28977a languageName: node linkType: hard @@ -5888,111 +5866,100 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-android-arm64@npm:1.2.1": - version: 1.2.1 - resolution: "@rolldown/binding-android-arm64@npm:1.2.1" +"@rolldown/binding-android-arm64@npm:1.2.2": + version: 1.2.2 + resolution: "@rolldown/binding-android-arm64@npm:1.2.2" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rolldown/binding-darwin-arm64@npm:1.2.1": - version: 1.2.1 - resolution: "@rolldown/binding-darwin-arm64@npm:1.2.1" +"@rolldown/binding-darwin-arm64@npm:1.2.2": + version: 1.2.2 + resolution: "@rolldown/binding-darwin-arm64@npm:1.2.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rolldown/binding-darwin-x64@npm:1.2.1": - version: 1.2.1 - resolution: "@rolldown/binding-darwin-x64@npm:1.2.1" +"@rolldown/binding-darwin-x64@npm:1.2.2": + version: 1.2.2 + resolution: "@rolldown/binding-darwin-x64@npm:1.2.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rolldown/binding-freebsd-x64@npm:1.2.1": - version: 1.2.1 - resolution: "@rolldown/binding-freebsd-x64@npm:1.2.1" +"@rolldown/binding-freebsd-x64@npm:1.2.2": + version: 1.2.2 + resolution: "@rolldown/binding-freebsd-x64@npm:1.2.2" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rolldown/binding-linux-arm-gnueabihf@npm:1.2.1": - version: 1.2.1 - resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.2.1" +"@rolldown/binding-linux-arm-gnueabihf@npm:1.2.2": + version: 1.2.2 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.2.2" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@rolldown/binding-linux-arm64-gnu@npm:1.2.1": - version: 1.2.1 - resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.2.1" +"@rolldown/binding-linux-arm64-gnu@npm:1.2.2": + version: 1.2.2 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.2.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rolldown/binding-linux-arm64-musl@npm:1.2.1": - version: 1.2.1 - resolution: "@rolldown/binding-linux-arm64-musl@npm:1.2.1" +"@rolldown/binding-linux-arm64-musl@npm:1.2.2": + version: 1.2.2 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.2.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rolldown/binding-linux-ppc64-gnu@npm:1.2.1": - version: 1.2.1 - resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.2.1" +"@rolldown/binding-linux-ppc64-gnu@npm:1.2.2": + version: 1.2.2 + resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.2.2" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rolldown/binding-linux-s390x-gnu@npm:1.2.1": - version: 1.2.1 - resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.2.1" +"@rolldown/binding-linux-s390x-gnu@npm:1.2.2": + version: 1.2.2 + resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.2.2" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rolldown/binding-linux-x64-gnu@npm:1.2.1": - version: 1.2.1 - resolution: "@rolldown/binding-linux-x64-gnu@npm:1.2.1" +"@rolldown/binding-linux-x64-gnu@npm:1.2.2": + version: 1.2.2 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.2.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rolldown/binding-linux-x64-musl@npm:1.2.1": - version: 1.2.1 - resolution: "@rolldown/binding-linux-x64-musl@npm:1.2.1" +"@rolldown/binding-linux-x64-musl@npm:1.2.2": + version: 1.2.2 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.2.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rolldown/binding-openharmony-arm64@npm:1.2.1": - version: 1.2.1 - resolution: "@rolldown/binding-openharmony-arm64@npm:1.2.1" +"@rolldown/binding-openharmony-arm64@npm:1.2.2": + version: 1.2.2 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.2.2" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@rolldown/binding-wasm32-wasi@npm:1.2.1": - version: 1.2.1 - resolution: "@rolldown/binding-wasm32-wasi@npm:1.2.1" - dependencies: - "@emnapi/core": "npm:2.0.0-alpha.3" - "@emnapi/runtime": "npm:2.0.0-alpha.3" - "@napi-rs/wasm-runtime": "npm:^1.2.0" - checksum: 10/230cb2f4d2a1dad5ae2c70ef5073c6a21d5fce3fd38cde49c80fd2e22f656807db69622830fa9dbf9c071bc37f705f2a5aca84d1dadccd0c32dbf5c0f9eea4ec - languageName: node - linkType: hard - -"@rolldown/binding-win32-arm64-msvc@npm:1.2.1": - version: 1.2.1 - resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.2.1" +"@rolldown/binding-win32-arm64-msvc@npm:1.2.2": + version: 1.2.2 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.2.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rolldown/binding-win32-x64-msvc@npm:1.2.1": - version: 1.2.1 - resolution: "@rolldown/binding-win32-x64-msvc@npm:1.2.1" +"@rolldown/binding-win32-x64-msvc@npm:1.2.2": + version: 1.2.2 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.2.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -6836,7 +6803,7 @@ __metadata: "@react-native/metro-config": "npm:^0.86.2" "@stream-io/noise-cancellation-react-native": "workspace:^" "@stream-io/react-native-callingx": "workspace:^" - "@stream-io/react-native-webrtc": "npm:145.2.0" + "@stream-io/react-native-webrtc": "npm:145.3.0" "@stream-io/video-filters-react-native": "workspace:^" "@stream-io/video-react-native-sdk": "workspace:^" "@types/react": "npm:~19.2.18" @@ -6939,7 +6906,7 @@ __metadata: version: 0.0.0-use.local resolution: "@stream-io/noise-cancellation-react-native@workspace:packages/noise-cancellation-react-native" dependencies: - "@stream-io/react-native-webrtc": "npm:145.2.0" + "@stream-io/react-native-webrtc": "npm:145.3.0" "@stream-io/typescript-config": "workspace:^" react: "npm:19.2.3" react-native: "npm:0.86.2" @@ -6947,7 +6914,7 @@ __metadata: rimraf: "npm:^6.1.3" typescript: "npm:^6.0.3" peerDependencies: - "@stream-io/react-native-webrtc": ^145.2.0 + "@stream-io/react-native-webrtc": ^145.3.0 react-native: "*" languageName: unknown linkType: soft @@ -6958,7 +6925,7 @@ __metadata: dependencies: "@react-native-community/cli": "npm:20.2.0" "@react-native/babel-preset": "npm:0.86.2" - "@stream-io/react-native-webrtc": "npm:145.2.0" + "@stream-io/react-native-webrtc": "npm:145.3.0" "@stream-io/typescript-config": "workspace:^" "@types/react": "npm:^19.2.18" del-cli: "npm:^6.0.0" @@ -6969,21 +6936,21 @@ __metadata: peerDependencies: "@react-native-firebase/app": ">=23.0.0" "@react-native-firebase/messaging": ">=23.0.0" - "@stream-io/react-native-webrtc": ^145.2.0 + "@stream-io/react-native-webrtc": ^145.3.0 react: "*" react-native: "*" languageName: unknown linkType: soft -"@stream-io/react-native-webrtc@npm:145.2.0": - version: 145.2.0 - resolution: "@stream-io/react-native-webrtc@npm:145.2.0" +"@stream-io/react-native-webrtc@npm:145.3.0": + version: 145.3.0 + resolution: "@stream-io/react-native-webrtc@npm:145.3.0" dependencies: base64-js: "npm:^1.5.1" debug: "npm:^4.4.3" peerDependencies: react-native: ">=0.73.0" - checksum: 10/86fb70746a6a793986e7a54385c84955996b1858fcb7fb8950dd36a362ac42968d23e7b463a18aa8d8fe06a00ccdc4c0529162bd8b3842d8e0b13a624baeabae + checksum: 10/9d2981f333d8a267a9e59a78018e9427a60c96a52eb7feb1aa89dd42f4379c7c6d6394633bf6391b302f1b193b0251641f4e48245c1c2dcb3d8ce606aeb9ca5c languageName: node linkType: hard @@ -7083,7 +7050,7 @@ __metadata: version: 0.0.0-use.local resolution: "@stream-io/video-filters-react-native@workspace:packages/video-filters-react-native" dependencies: - "@stream-io/react-native-webrtc": "npm:145.2.0" + "@stream-io/react-native-webrtc": "npm:145.3.0" "@stream-io/typescript-config": "workspace:^" react: "npm:19.2.3" react-native: "npm:0.86.2" @@ -7091,7 +7058,7 @@ __metadata: rimraf: "npm:^6.1.3" typescript: "npm:^6.0.3" peerDependencies: - "@stream-io/react-native-webrtc": ^145.2.0 + "@stream-io/react-native-webrtc": ^145.3.0 react-native: "*" languageName: unknown linkType: soft @@ -7251,7 +7218,7 @@ __metadata: "@react-navigation/native-stack": "npm:^7.18.6" "@stream-io/noise-cancellation-react-native": "workspace:^" "@stream-io/react-native-callingx": "workspace:^" - "@stream-io/react-native-webrtc": "npm:145.2.0" + "@stream-io/react-native-webrtc": "npm:145.3.0" "@stream-io/video-filters-react-native": "workspace:^" "@stream-io/video-react-native-sdk": "workspace:^" "@types/react": "npm:^19.2.18" @@ -7299,7 +7266,7 @@ __metadata: "@react-native-firebase/messaging": "npm:^24.1.1" "@react-native/metro-config": "npm:^0.86.2" "@stream-io/react-native-callingx": "workspace:^" - "@stream-io/react-native-webrtc": "npm:145.2.0" + "@stream-io/react-native-webrtc": "npm:145.3.0" "@stream-io/video-react-native-sdk": "workspace:^" "@types/react": "npm:~19.2.18" expo: "npm:~57.0.9" @@ -7343,7 +7310,7 @@ __metadata: "@react-native/metro-config": "npm:0.86.2" "@stream-io/noise-cancellation-react-native": "workspace:^" "@stream-io/react-native-callingx": "workspace:^" - "@stream-io/react-native-webrtc": "npm:145.2.0" + "@stream-io/react-native-webrtc": "npm:145.3.0" "@stream-io/typescript-config": "workspace:^" "@stream-io/video-client": "workspace:*" "@stream-io/video-filters-react-native": "workspace:^" @@ -7378,7 +7345,7 @@ __metadata: "@react-native-firebase/messaging": ">=17.5.0" "@stream-io/noise-cancellation-react-native": ">=0.1.0" "@stream-io/react-native-callingx": ">=0.1.0" - "@stream-io/react-native-webrtc": ^145.2.0 + "@stream-io/react-native-webrtc": ^145.3.0 "@stream-io/video-filters-react-native": ">=0.1.0" expo: ">=47.0.0" expo-notifications: "*" @@ -7551,11 +7518,11 @@ __metadata: linkType: hard "@testing-library/user-event@npm:^14.6.1": - version: 14.6.1 - resolution: "@testing-library/user-event@npm:14.6.1" + version: 14.6.3 + resolution: "@testing-library/user-event@npm:14.6.3" peerDependencies: "@testing-library/dom": ">=7.21.4" - checksum: 10/34b74fff56a0447731a94b40d4cf246deb8dbc1c1e3aec93acd1c3377a760bb062e979f1572bb34ec164ad28ee2a391744b42d0d6d6cc16c4ce527e5e09610e1 + checksum: 10/a41add78922fa7ea4a0026995d8c00f0fc8bc51f037d67a0ecac1b11b2d1632f4fa2147e9690d2bb755789d2c33bf173195230d7ede13957d36f86607c2316c1 languageName: node linkType: hard @@ -7599,15 +7566,6 @@ __metadata: languageName: node linkType: hard -"@tybys/wasm-util@npm:^0.10.3": - version: 0.10.3 - resolution: "@tybys/wasm-util@npm:0.10.3" - dependencies: - tslib: "npm:^2.4.0" - checksum: 10/6cf39f7a2926b1c8bc6fe3f9f03318a33dd6dae81bdbd059983f9c6ee22d10a827f12564d648c05a2d4926e03c86cbe2799fb20609ee65e9efc39603039b4765 - languageName: node - linkType: hard - "@types/babel__core@npm:^7.1.14": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" @@ -8023,105 +7981,105 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.65.0": - version: 8.65.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.65.0" +"@typescript-eslint/eslint-plugin@npm:8.66.0": + version: 8.66.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.66.0" dependencies: "@eslint-community/regexpp": "npm:^4.12.2" - "@typescript-eslint/scope-manager": "npm:8.65.0" - "@typescript-eslint/type-utils": "npm:8.65.0" - "@typescript-eslint/utils": "npm:8.65.0" - "@typescript-eslint/visitor-keys": "npm:8.65.0" + "@typescript-eslint/scope-manager": "npm:8.66.0" + "@typescript-eslint/type-utils": "npm:8.66.0" + "@typescript-eslint/utils": "npm:8.66.0" + "@typescript-eslint/visitor-keys": "npm:8.66.0" ignore: "npm:^7.0.5" natural-compare: "npm:^1.4.0" ts-api-utils: "npm:^2.5.0" peerDependencies: - "@typescript-eslint/parser": ^8.65.0 + "@typescript-eslint/parser": ^8.66.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10/20b1e5fd0c01d450c345750582e4911affef8ba934b2b78953ff593102d2a01f21c5f6469e13239fdd6a0e30152d6122906e7623f53aa8c937c6faae9407be47 + checksum: 10/bb144ff0c27592b6a53d964fe1e2145dd0737f5fe50fe7dcd88217ca5ac4e470d7965d97745a63de16252ac2880d00732a472b9cec14d7d6929ebda97e7bcbb1 languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.65.0": - version: 8.65.0 - resolution: "@typescript-eslint/parser@npm:8.65.0" +"@typescript-eslint/parser@npm:8.66.0": + version: 8.66.0 + resolution: "@typescript-eslint/parser@npm:8.66.0" dependencies: - "@typescript-eslint/scope-manager": "npm:8.65.0" - "@typescript-eslint/types": "npm:8.65.0" - "@typescript-eslint/typescript-estree": "npm:8.65.0" - "@typescript-eslint/visitor-keys": "npm:8.65.0" + "@typescript-eslint/scope-manager": "npm:8.66.0" + "@typescript-eslint/types": "npm:8.66.0" + "@typescript-eslint/typescript-estree": "npm:8.66.0" + "@typescript-eslint/visitor-keys": "npm:8.66.0" debug: "npm:^4.4.3" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10/55f68666953c02c8adae35a46076848da6456181b1849a28ec836a5866ca37b902f475fb4c32ba7aa3a6a7e5d66828df8859edec297da09181fb937aeea30f8e + checksum: 10/a0451abe17d2eeff6ff2e1bf637d5ea19b400378d98f4511672e0f30dc5a7971d03352e32764927176c3632f245d5f85e8b28a0a3e66d020effcf0c61624e9b6 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.65.0": - version: 8.65.0 - resolution: "@typescript-eslint/project-service@npm:8.65.0" +"@typescript-eslint/project-service@npm:8.66.0": + version: 8.66.0 + resolution: "@typescript-eslint/project-service@npm:8.66.0" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.65.0" - "@typescript-eslint/types": "npm:^8.65.0" + "@typescript-eslint/tsconfig-utils": "npm:^8.66.0" + "@typescript-eslint/types": "npm:^8.66.0" debug: "npm:^4.4.3" peerDependencies: typescript: ">=4.8.4 <6.1.0" - checksum: 10/915662449a66d90f03661a805f7c62a5efaa8f7887671272e08b684b41edab51a9021f47aac4d78001a0f87960e8587adc037424d56f13de883e0ce699e7ca55 + checksum: 10/e8a71f69ee7f4bd9ca1c6a0a5110361b9927d17df2f41cdf6d042203b49aabfbddece79dec7882fde8dc016b6278671a2ba3f7bf08e5ae241582627b68be3a8b languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.65.0": - version: 8.65.0 - resolution: "@typescript-eslint/scope-manager@npm:8.65.0" +"@typescript-eslint/scope-manager@npm:8.66.0": + version: 8.66.0 + resolution: "@typescript-eslint/scope-manager@npm:8.66.0" dependencies: - "@typescript-eslint/types": "npm:8.65.0" - "@typescript-eslint/visitor-keys": "npm:8.65.0" - checksum: 10/038e208c907aa45fe5bb7168e1dccf89c1fc6678d1715a9c04f4b332d4e79ab719b5b43a4e0c2d5a183ea863813ff59fda040b2717fe5517468453af6b99a50a + "@typescript-eslint/types": "npm:8.66.0" + "@typescript-eslint/visitor-keys": "npm:8.66.0" + checksum: 10/f2024cd2819dc3b967f32089ea1a0b9099acabf12860699432f0ba802d75b01ed35ea488a50576cae544813d5cbe9bd9ee5815bc4f11cefc89fe2fb24559238f languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.65.0, @typescript-eslint/tsconfig-utils@npm:^8.65.0": - version: 8.65.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.65.0" +"@typescript-eslint/tsconfig-utils@npm:8.66.0, @typescript-eslint/tsconfig-utils@npm:^8.66.0": + version: 8.66.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.66.0" peerDependencies: typescript: ">=4.8.4 <6.1.0" - checksum: 10/f88253a4df1d599a1bebeeb403611538485e22c52213f2f2b9c435bde8ebce64645d6bb985c900b01ef221032835fcd4f6fea4b94d178220c18de5d93af905c4 + checksum: 10/271b28660edc9e6272ae07a91f8da2c69e8a6b009e8588f1009c485c11fe4e66e09356d50dbeabf8e6a44c95c8adeb630f1b6089bdd68aec2512d0036cef7704 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.65.0": - version: 8.65.0 - resolution: "@typescript-eslint/type-utils@npm:8.65.0" +"@typescript-eslint/type-utils@npm:8.66.0": + version: 8.66.0 + resolution: "@typescript-eslint/type-utils@npm:8.66.0" dependencies: - "@typescript-eslint/types": "npm:8.65.0" - "@typescript-eslint/typescript-estree": "npm:8.65.0" - "@typescript-eslint/utils": "npm:8.65.0" + "@typescript-eslint/types": "npm:8.66.0" + "@typescript-eslint/typescript-estree": "npm:8.66.0" + "@typescript-eslint/utils": "npm:8.66.0" debug: "npm:^4.4.3" ts-api-utils: "npm:^2.5.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10/d52e0c341c9731d8f3bfd2f475bbcd5a5632434e11fc39e8e21acb706145bedb8c6c1998b8ced73e132b43179dd95f2c318effc36c9a1dd61f14546b07b25852 + checksum: 10/6d3c55a591b96cbac4ac845ad1d281bac54df12a631ca550370d266aed30760fa9d498e44b938e454941930e3decda820c6e544e0748254c5089425d120e1ed1 languageName: node linkType: hard -"@typescript-eslint/types@npm:8.65.0, @typescript-eslint/types@npm:^8.65.0": - version: 8.65.0 - resolution: "@typescript-eslint/types@npm:8.65.0" - checksum: 10/a6fc10a733adbb98bbb9c312c8d99791e1a2fd3efef5c2a5b51da1c166aac3db4427c30bf32059808abe305a289f820bf610683914e897baeb18315af6a1d16c +"@typescript-eslint/types@npm:8.66.0, @typescript-eslint/types@npm:^8.66.0": + version: 8.66.0 + resolution: "@typescript-eslint/types@npm:8.66.0" + checksum: 10/de1ea79e7056c11e38e4001a899b88511793a0aa1702508af42ee179bc7303bed2eb02f177a7ae438f20eab00aee9407c7e842bec6fc5b7bb1fdcab4a7cd5c4b languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.65.0": - version: 8.65.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.65.0" +"@typescript-eslint/typescript-estree@npm:8.66.0": + version: 8.66.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.66.0" dependencies: - "@typescript-eslint/project-service": "npm:8.65.0" - "@typescript-eslint/tsconfig-utils": "npm:8.65.0" - "@typescript-eslint/types": "npm:8.65.0" - "@typescript-eslint/visitor-keys": "npm:8.65.0" + "@typescript-eslint/project-service": "npm:8.66.0" + "@typescript-eslint/tsconfig-utils": "npm:8.66.0" + "@typescript-eslint/types": "npm:8.66.0" + "@typescript-eslint/visitor-keys": "npm:8.66.0" debug: "npm:^4.4.3" minimatch: "npm:^10.2.2" semver: "npm:^7.7.3" @@ -8129,32 +8087,32 @@ __metadata: ts-api-utils: "npm:^2.5.0" peerDependencies: typescript: ">=4.8.4 <6.1.0" - checksum: 10/711fdb5eff67ff34437c56a1a661b16a2e1d6bcd96c9f96196c4242b8d4ddaea8e835ca8badd340c703e043d8dfe8cfca90b32eca60069a4f1d2880afdb670fb + checksum: 10/0834efcdd4468c0dd954089daf26b40175e204402bd4075b06cd910786bb6de7a15aec4e79e40f0980f39fce5bfd9c871ec8fcc6871a8895a34a273bb187fd26 languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.65.0": - version: 8.65.0 - resolution: "@typescript-eslint/utils@npm:8.65.0" +"@typescript-eslint/utils@npm:8.66.0": + version: 8.66.0 + resolution: "@typescript-eslint/utils@npm:8.66.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.9.1" - "@typescript-eslint/scope-manager": "npm:8.65.0" - "@typescript-eslint/types": "npm:8.65.0" - "@typescript-eslint/typescript-estree": "npm:8.65.0" + "@typescript-eslint/scope-manager": "npm:8.66.0" + "@typescript-eslint/types": "npm:8.66.0" + "@typescript-eslint/typescript-estree": "npm:8.66.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10/c1dcd555b58aef1e066164978335e521809acac36b56bd6a6dae62cffae80f3ea5f43527506be76dfef0fe3d4c8382a24355a28867c4904b0a7729691ba45656 + checksum: 10/e7a24ee0f35d58b4392daa03fb1ea37ab7e4519949a034be89151df1b0e9fc416831f50cb69f54150272e252cba350964632ee9bfbdb7e62ac599cc731de56e1 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.65.0": - version: 8.65.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.65.0" +"@typescript-eslint/visitor-keys@npm:8.66.0": + version: 8.66.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.66.0" dependencies: - "@typescript-eslint/types": "npm:8.65.0" + "@typescript-eslint/types": "npm:8.66.0" eslint-visitor-keys: "npm:^5.0.0" - checksum: 10/e7f86d21f0bf03ca7cff9fa9428aab98620564d15fb06c9f56a294c13cee324624d42f9115f3d76fbad92266220479cca334b5c66562f885da5c4d2bda3d87b3 + checksum: 10/d47e936b10ec94a96f69f77a62463f4634fe72d12d42dd302d7777f7b98689468e0b67c538a52232b723695208f1b3fdeae5745eb3e71a70a90818fdd827a0bc languageName: node linkType: hard @@ -9232,11 +9190,11 @@ __metadata: linkType: hard "baseline-browser-mapping@npm:^2.10.44, baseline-browser-mapping@npm:^2.9.19": - version: 2.11.11 - resolution: "baseline-browser-mapping@npm:2.11.11" + version: 2.11.12 + resolution: "baseline-browser-mapping@npm:2.11.12" bin: baseline-browser-mapping: dist/cli.cjs - checksum: 10/aca65585ce9bdfa29ef0143285cc3afe93db6312869bd74b1dbe171562b1551be8131c313530e6264841bb451912b208a019663ef350c6ad56a290fb7fa32278 + checksum: 10/ad91bf463bcc673c7a3d0aa0232af0e3d7a9c5be936acba665c53fc01107b8ea3dd84471f6e266e05107b34744d7288ffdd087b713905472b36b5adb7e4fd72d languageName: node linkType: hard @@ -10898,9 +10856,9 @@ __metadata: linkType: hard "electron-to-chromium@npm:^1.5.393": - version: 1.5.399 - resolution: "electron-to-chromium@npm:1.5.399" - checksum: 10/a976327d4410156a547aec3b604c73e729ada8a75a2a16ac31995191632f3326a1d39fac957cfbcb36ca1ffba70c7ef93ef508fdb05653f33a73d04fff9dc2fb + version: 1.5.400 + resolution: "electron-to-chromium@npm:1.5.400" + checksum: 10/ba3ab844aa35393dac68ef5c56f965f6762abe93e83a4009d7b26dfc72fa29bf6301e0643ef3d72246dda6fe57fbea9614c87948c7e632ae51cb6886631afaf1 languageName: node linkType: hard @@ -11684,15 +11642,15 @@ __metadata: languageName: node linkType: hard -"expo-constants@npm:~57.0.7, expo-constants@npm:~57.0.8": - version: 57.0.8 - resolution: "expo-constants@npm:57.0.8" +"expo-constants@npm:~57.0.8, expo-constants@npm:~57.0.9": + version: 57.0.9 + resolution: "expo-constants@npm:57.0.9" dependencies: "@expo/env": "npm:~2.4.2" peerDependencies: expo: "*" react-native: "*" - checksum: 10/d90daf7756c6e739de91e63c51dd46158cef0a59b52ec2834a704dc6a18b03ac9a48b41f74bc2f26ae5d2a55f24d18f207392943d71e7a541bd0fe02f6f1c51c + checksum: 10/b297f00dd0eed8f52db8182ad8050f92bda022eab332b4e825727e89fe1458fa851c743cb70282b4ca52553d808bf3cfa2a3f24850927fe74946a7385c10f7d3 languageName: node linkType: hard @@ -11798,15 +11756,15 @@ __metadata: linkType: hard "expo-linking@npm:~57.0.4": - version: 57.0.4 - resolution: "expo-linking@npm:57.0.4" + version: 57.0.5 + resolution: "expo-linking@npm:57.0.5" dependencies: - expo-constants: "npm:~57.0.7" + expo-constants: "npm:~57.0.9" invariant: "npm:^2.2.4" peerDependencies: react: "*" react-native: "*" - checksum: 10/29b23f7f2d63792c3939bf79ec54bcc2636b0bfaa848197cd24ad2e9651ed00ca4a6cc7634c30d48d907b08ba32fdd295928bcf2b57be2221cae1407b694f30d + checksum: 10/9e97df2ba1b250b60943b7faf42d6f7dc2f0825f9a244b3815a62c6e445975a083b4754109e2dd1336765014a2aa3cefd920380b7f281ec8af3ea8ce2d51bc52 languageName: node linkType: hard @@ -11835,9 +11793,9 @@ __metadata: languageName: node linkType: hard -"expo-modules-core@npm:~57.0.8": - version: 57.0.8 - resolution: "expo-modules-core@npm:57.0.8" +"expo-modules-core@npm:~57.0.9": + version: 57.0.9 + resolution: "expo-modules-core@npm:57.0.9" dependencies: "@expo/expo-modules-macros-plugin": "npm:0.6.1" expo-modules-jsi: "npm:~57.0.4" @@ -11849,7 +11807,7 @@ __metadata: peerDependenciesMeta: react-native-worklets: optional: true - checksum: 10/a9f4025ca26338c57653c215cff11ab25bc78f312b798c5cd35efc3c6a411ffa9210642557e2a2c9bab338594a67b16926eff0d7ee1551ec74b5a5e7bc126bdf + checksum: 10/7936172304426441090079124f2c2ad560752b49e9cec6ff0351b4ca3e2c9b52557d77574931dba26203a20d6cbdeaf1a5c6b9013779611a1296f76bca9ded47 languageName: node linkType: hard @@ -11880,13 +11838,13 @@ __metadata: linkType: hard "expo-router@npm:~57.0.9": - version: 57.0.9 - resolution: "expo-router@npm:57.0.9" + version: 57.0.10 + resolution: "expo-router@npm:57.0.10" dependencies: "@expo/log-box": "npm:^57.0.2" "@expo/metro-runtime": "npm:^57.0.8" "@expo/schema-utils": "npm:^57.0.2" - "@expo/ui": "npm:^57.0.8" + "@expo/ui": "npm:^57.0.9" "@radix-ui/react-slot": "npm:^1.2.0" "@radix-ui/react-tabs": "npm:^1.1.12" "@react-native-masked-view/masked-view": "npm:^0.3.2" @@ -11917,8 +11875,8 @@ __metadata: "@expo/metro-runtime": ^57.0.8 "@testing-library/react-native": ">= 13.2.0" expo: "*" - expo-constants: ^57.0.8 - expo-linking: ^57.0.4 + expo-constants: ^57.0.9 + expo-linking: ^57.0.5 react: "*" react-dom: "*" react-native: "*" @@ -11941,7 +11899,7 @@ __metadata: optional: true react-server-dom-webpack: optional: true - checksum: 10/d1ec7652a98ee41c3417e57072592885b4693311d47841eae41d7a51a6b8d942575c026715a23ee6c08e4881ec4b28bba6babd78d92c33fdf30e9ca4577f40ce + checksum: 10/d8d7299874f0fc524f3e7aa3ad79ac05c626fdabff442c1ceb35c06c22220b0bc4eb5e7b32e1f75951eba5f96028dc094d33cbcb30a98fa9439806dfc5b197f2 languageName: node linkType: hard @@ -12017,11 +11975,11 @@ __metadata: linkType: hard "expo@npm:~57.0.9": - version: 57.0.9 - resolution: "expo@npm:57.0.9" + version: 57.0.10 + resolution: "expo@npm:57.0.10" dependencies: "@babel/runtime": "npm:^7.20.0" - "@expo/cli": "npm:^57.0.11" + "@expo/cli": "npm:^57.0.12" "@expo/config": "npm:~57.0.6" "@expo/config-plugins": "npm:~57.0.6" "@expo/devtools": "npm:~57.0.1" @@ -12034,12 +11992,12 @@ __metadata: "@ungap/structured-clone": "npm:^1.3.0" babel-preset-expo: "npm:~57.0.5" expo-asset: "npm:~57.0.8" - expo-constants: "npm:~57.0.8" + expo-constants: "npm:~57.0.9" expo-file-system: "npm:~57.0.1" expo-font: "npm:~57.0.1" expo-keep-awake: "npm:~57.0.1" expo-modules-autolinking: "npm:~57.0.9" - expo-modules-core: "npm:~57.0.8" + expo-modules-core: "npm:~57.0.9" pretty-format: "npm:^29.7.0" react-refresh: "npm:^0.14.2" whatwg-url-minimum: "npm:^0.1.2" @@ -12066,7 +12024,7 @@ __metadata: expo: bin/cli expo-modules-autolinking: bin/autolinking fingerprint: bin/fingerprint - checksum: 10/70b86df75d3326018f8583956c51f646640bcd2b61054ddbed4d1e5d98102dc61720c34fc05efe80e5fecb43f6b2a1361cfceca873f3544338717e1511735b4a + checksum: 10/8c328d43a15e9a947a8abac125fbc88725d35b1e6241def1b75dd28d91a2ee6efccb3340f60dbab1f5e51494e5380a871a3ca862cd1a5b0e5fc682f27b4fa5c0 languageName: node linkType: hard @@ -16541,12 +16499,12 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.1, nanoid@npm:^3.3.11, nanoid@npm:^3.3.12, nanoid@npm:^3.3.16, nanoid@npm:^3.3.6, nanoid@npm:^3.3.8": - version: 3.3.16 - resolution: "nanoid@npm:3.3.16" +"nanoid@npm:^3.3.1, nanoid@npm:^3.3.11, nanoid@npm:^3.3.12, nanoid@npm:^3.3.16, nanoid@npm:^3.3.8": + version: 3.3.17 + resolution: "nanoid@npm:3.3.17" bin: nanoid: bin/nanoid.cjs - checksum: 10/8004af92b5541af1dbd23b69845b5026f777d5b7ef07163cea1837aae86e052ced8b383cecbf8a4f1b5e77ae207df96dc45e16b9e0fa3c4b761d085f1e42851b + checksum: 10/54c3238ba6ea31c173ccf70922481814892075dbf1b4636abec249d0b34d5594fc9a8892c04872068674010a11fa3d18316dc06e59e700def131ff9d8e740426 languageName: node linkType: hard @@ -16637,23 +16595,23 @@ __metadata: linkType: hard "next@npm:^16.2.12": - version: 16.2.12 - resolution: "next@npm:16.2.12" - dependencies: - "@next/env": "npm:16.2.12" - "@next/swc-darwin-arm64": "npm:16.2.12" - "@next/swc-darwin-x64": "npm:16.2.12" - "@next/swc-linux-arm64-gnu": "npm:16.2.12" - "@next/swc-linux-arm64-musl": "npm:16.2.12" - "@next/swc-linux-x64-gnu": "npm:16.2.12" - "@next/swc-linux-x64-musl": "npm:16.2.12" - "@next/swc-win32-arm64-msvc": "npm:16.2.12" - "@next/swc-win32-x64-msvc": "npm:16.2.12" + version: 16.3.0 + resolution: "next@npm:16.3.0" + dependencies: + "@next/env": "npm:16.3.0" + "@next/swc-darwin-arm64": "npm:16.3.0" + "@next/swc-darwin-x64": "npm:16.3.0" + "@next/swc-linux-arm64-gnu": "npm:16.3.0" + "@next/swc-linux-arm64-musl": "npm:16.3.0" + "@next/swc-linux-x64-gnu": "npm:16.3.0" + "@next/swc-linux-x64-musl": "npm:16.3.0" + "@next/swc-win32-arm64-msvc": "npm:16.3.0" + "@next/swc-win32-x64-msvc": "npm:16.3.0" "@swc/helpers": "npm:0.5.15" baseline-browser-mapping: "npm:^2.9.19" caniuse-lite: "npm:^1.0.30001579" - postcss: "npm:8.4.31" - sharp: "npm:^0.34.5" + postcss: "npm:8.5.23" + sharp: "npm:^0.35.3" styled-jsx: "npm:5.1.6" peerDependencies: "@opentelemetry/api": ^1.1.0 @@ -16692,7 +16650,7 @@ __metadata: optional: true bin: next: dist/bin/next - checksum: 10/29434e14b1a3026eeb2e2e719885be592319f4777cb0414017769d87c382051fb1352cb2c94b9b1b0a5f17526d8c6d731f5d425012146135a4583b09c1e7be00 + checksum: 10/d568df8e16d2cda6c9e08ed6936bd9b170c7f6d0643fb78d7b2483f5a98303e453c8aaf7ebdf8e38c67ebe283b09c78d1c1ca5a9e519e9698a3dfa145d919565 languageName: node linkType: hard @@ -16773,9 +16731,9 @@ __metadata: linkType: hard "node-releases@npm:^2.0.51": - version: 2.0.51 - resolution: "node-releases@npm:2.0.51" - checksum: 10/9d08dbc740bb2fa40b2234108dd9dec333d76b8dd22435ab10c9b1c7d8813885449ba36033b5303158d1ff8f66cc8db2d35a74eef42f4f65f540790cd377584b + version: 2.0.52 + resolution: "node-releases@npm:2.0.52" + checksum: 10/0aa457c2805a560608c3ceca4df13c8b1b932f23aa367f2678681c84cc864bd4860fbfacc09f7260954fa66e8c05f343dbc5e9088163000fedae12f8da83ed48 languageName: node linkType: hard @@ -17614,7 +17572,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:1.1.1, picocolors@npm:^1.0.0, picocolors@npm:^1.0.1, picocolors@npm:^1.1.1": +"picocolors@npm:1.1.1, picocolors@npm:^1.0.1, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 @@ -17798,14 +17756,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:8.4.31": - version: 8.4.31 - resolution: "postcss@npm:8.4.31" +"postcss@npm:8.5.23": + version: 8.5.23 + resolution: "postcss@npm:8.5.23" dependencies: - nanoid: "npm:^3.3.6" - picocolors: "npm:^1.0.0" - source-map-js: "npm:^1.0.2" - checksum: 10/1a6653e72105907377f9d4f2cd341d8d90e3fde823a5ddea1e2237aaa56933ea07853f0f2758c28892a1d70c53bbaca200eb8b80f8ed55f13093003dbec5afa0 + nanoid: "npm:^3.3.16" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10/8387f421216696bf62a13e5a270e9fefdafc81e196903c0f8d7653f8bab315367cc2261b3c22cf197e492cb075f31bdfc07fd9c3be1cb165ff3cabe14c5a039a languageName: node linkType: hard @@ -18319,8 +18277,8 @@ __metadata: linkType: hard "react-native-drawer-layout@npm:^4.2.2": - version: 4.2.9 - resolution: "react-native-drawer-layout@npm:4.2.9" + version: 4.2.10 + resolution: "react-native-drawer-layout@npm:4.2.10" dependencies: color: "npm:^4.2.3" use-latest-callback: "npm:^0.2.4" @@ -18329,7 +18287,7 @@ __metadata: react-native: "*" react-native-gesture-handler: ">= 2.0.0" react-native-reanimated: ">= 2.0.0" - checksum: 10/c1b1db1e080c40c5b58f6ec2639d9bec0cbd22fb4efdcea5b2f82a78a35c6870204002d6bb254f69e5892ea1b8264a03d5954eb45b713166f5a6bc8c6a4d2882 + checksum: 10/ed74459371e4f450bfccc2ae12e11399508917442a96a0039a8aacaea3903615e64c866a9da4365edf70bf34534fffc6c7bc4423250f9d264cddb4a56cc136f5 languageName: node linkType: hard @@ -18447,12 +18405,12 @@ __metadata: linkType: hard "react-native-safe-area-context@npm:~5.8.0": - version: 5.8.0 - resolution: "react-native-safe-area-context@npm:5.8.0" + version: 5.8.1 + resolution: "react-native-safe-area-context@npm:5.8.1" peerDependencies: react: "*" react-native: "*" - checksum: 10/d4f526a98110f88ba9f5dfe6d6bda2c26b86404b2d29a6571931a1f02a531f565a2fd58d20b4ac70c1fa37c00ad6a72332fb0a463e27262c4ff1d1dfb694af7c + checksum: 10/2ea47449420a1d89354648bc0cec7e93d9dc7fc577321781126d4f274a13d8dc94a09be78454f3720d54f90511d4ff9e8412bb81e2aed71f135a80bbf9ed0e63 languageName: node linkType: hard @@ -19220,25 +19178,24 @@ __metadata: linkType: hard "rolldown@npm:~1.2.0": - version: 1.2.1 - resolution: "rolldown@npm:1.2.1" + version: 1.2.2 + resolution: "rolldown@npm:1.2.2" dependencies: "@oxc-project/types": "npm:=0.142.0" - "@rolldown/binding-android-arm64": "npm:1.2.1" - "@rolldown/binding-darwin-arm64": "npm:1.2.1" - "@rolldown/binding-darwin-x64": "npm:1.2.1" - "@rolldown/binding-freebsd-x64": "npm:1.2.1" - "@rolldown/binding-linux-arm-gnueabihf": "npm:1.2.1" - "@rolldown/binding-linux-arm64-gnu": "npm:1.2.1" - "@rolldown/binding-linux-arm64-musl": "npm:1.2.1" - "@rolldown/binding-linux-ppc64-gnu": "npm:1.2.1" - "@rolldown/binding-linux-s390x-gnu": "npm:1.2.1" - "@rolldown/binding-linux-x64-gnu": "npm:1.2.1" - "@rolldown/binding-linux-x64-musl": "npm:1.2.1" - "@rolldown/binding-openharmony-arm64": "npm:1.2.1" - "@rolldown/binding-wasm32-wasi": "npm:1.2.1" - "@rolldown/binding-win32-arm64-msvc": "npm:1.2.1" - "@rolldown/binding-win32-x64-msvc": "npm:1.2.1" + "@rolldown/binding-android-arm64": "npm:1.2.2" + "@rolldown/binding-darwin-arm64": "npm:1.2.2" + "@rolldown/binding-darwin-x64": "npm:1.2.2" + "@rolldown/binding-freebsd-x64": "npm:1.2.2" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.2.2" + "@rolldown/binding-linux-arm64-gnu": "npm:1.2.2" + "@rolldown/binding-linux-arm64-musl": "npm:1.2.2" + "@rolldown/binding-linux-ppc64-gnu": "npm:1.2.2" + "@rolldown/binding-linux-s390x-gnu": "npm:1.2.2" + "@rolldown/binding-linux-x64-gnu": "npm:1.2.2" + "@rolldown/binding-linux-x64-musl": "npm:1.2.2" + "@rolldown/binding-openharmony-arm64": "npm:1.2.2" + "@rolldown/binding-win32-arm64-msvc": "npm:1.2.2" + "@rolldown/binding-win32-x64-msvc": "npm:1.2.2" "@rolldown/pluginutils": "npm:^1.0.0" dependenciesMeta: "@rolldown/binding-android-arm64": @@ -19265,15 +19222,13 @@ __metadata: optional: true "@rolldown/binding-openharmony-arm64": optional: true - "@rolldown/binding-wasm32-wasi": - optional: true "@rolldown/binding-win32-arm64-msvc": optional: true "@rolldown/binding-win32-x64-msvc": optional: true bin: rolldown: ./bin/cli.mjs - checksum: 10/32cdaf5488cb64700907d27ede2a5936d3c1acb88e2b701bf6ecf88846fbed5f3b23f5da55ba101a0cebe2f0d6f5a746fa66b81692adfb806feb44b784d06de7 + checksum: 10/398c9c5f0d22061db134d8df76711e8430921514ec126c10da7bd37b97b2ec51b92482258baaf258f7e2db1976e54e6eb24e2cc9fcbd1981932cbd7beea598f4 languageName: node linkType: hard @@ -19572,7 +19527,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.0.0, semver@npm:^7.1.3, semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.3, semver@npm:^7.7.3, semver@npm:^7.7.4": +"semver@npm:^7.0.0, semver@npm:^7.1.3, semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.3, semver@npm:^7.7.3, semver@npm:^7.7.4, semver@npm:^7.8.5": version: 7.8.5 resolution: "semver@npm:7.8.5" bin: @@ -19702,42 +19657,45 @@ __metadata: languageName: node linkType: hard -"sharp@npm:^0.34.5": - version: 0.34.5 - resolution: "sharp@npm:0.34.5" - dependencies: - "@img/colour": "npm:^1.0.0" - "@img/sharp-darwin-arm64": "npm:0.34.5" - "@img/sharp-darwin-x64": "npm:0.34.5" - "@img/sharp-libvips-darwin-arm64": "npm:1.2.4" - "@img/sharp-libvips-darwin-x64": "npm:1.2.4" - "@img/sharp-libvips-linux-arm": "npm:1.2.4" - "@img/sharp-libvips-linux-arm64": "npm:1.2.4" - "@img/sharp-libvips-linux-ppc64": "npm:1.2.4" - "@img/sharp-libvips-linux-riscv64": "npm:1.2.4" - "@img/sharp-libvips-linux-s390x": "npm:1.2.4" - "@img/sharp-libvips-linux-x64": "npm:1.2.4" - "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4" - "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4" - "@img/sharp-linux-arm": "npm:0.34.5" - "@img/sharp-linux-arm64": "npm:0.34.5" - "@img/sharp-linux-ppc64": "npm:0.34.5" - "@img/sharp-linux-riscv64": "npm:0.34.5" - "@img/sharp-linux-s390x": "npm:0.34.5" - "@img/sharp-linux-x64": "npm:0.34.5" - "@img/sharp-linuxmusl-arm64": "npm:0.34.5" - "@img/sharp-linuxmusl-x64": "npm:0.34.5" - "@img/sharp-wasm32": "npm:0.34.5" - "@img/sharp-win32-arm64": "npm:0.34.5" - "@img/sharp-win32-ia32": "npm:0.34.5" - "@img/sharp-win32-x64": "npm:0.34.5" +"sharp@npm:^0.35.3": + version: 0.35.3 + resolution: "sharp@npm:0.35.3" + dependencies: + "@img/colour": "npm:^1.1.0" + "@img/sharp-darwin-arm64": "npm:0.35.3" + "@img/sharp-darwin-x64": "npm:0.35.3" + "@img/sharp-freebsd-wasm32": "npm:0.35.3" + "@img/sharp-libvips-darwin-arm64": "npm:1.3.2" + "@img/sharp-libvips-darwin-x64": "npm:1.3.2" + "@img/sharp-libvips-linux-arm": "npm:1.3.2" + "@img/sharp-libvips-linux-arm64": "npm:1.3.2" + "@img/sharp-libvips-linux-ppc64": "npm:1.3.2" + "@img/sharp-libvips-linux-riscv64": "npm:1.3.2" + "@img/sharp-libvips-linux-s390x": "npm:1.3.2" + "@img/sharp-libvips-linux-x64": "npm:1.3.2" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.3.2" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.3.2" + "@img/sharp-linux-arm": "npm:0.35.3" + "@img/sharp-linux-arm64": "npm:0.35.3" + "@img/sharp-linux-ppc64": "npm:0.35.3" + "@img/sharp-linux-riscv64": "npm:0.35.3" + "@img/sharp-linux-s390x": "npm:0.35.3" + "@img/sharp-linux-x64": "npm:0.35.3" + "@img/sharp-linuxmusl-arm64": "npm:0.35.3" + "@img/sharp-linuxmusl-x64": "npm:0.35.3" + "@img/sharp-webcontainers-wasm32": "npm:0.35.3" + "@img/sharp-win32-arm64": "npm:0.35.3" + "@img/sharp-win32-ia32": "npm:0.35.3" + "@img/sharp-win32-x64": "npm:0.35.3" detect-libc: "npm:^2.1.2" - semver: "npm:^7.7.3" + semver: "npm:^7.8.5" dependenciesMeta: "@img/sharp-darwin-arm64": optional: true "@img/sharp-darwin-x64": optional: true + "@img/sharp-freebsd-wasm32": + optional: true "@img/sharp-libvips-darwin-arm64": optional: true "@img/sharp-libvips-darwin-x64": @@ -19774,7 +19732,7 @@ __metadata: optional: true "@img/sharp-linuxmusl-x64": optional: true - "@img/sharp-wasm32": + "@img/sharp-webcontainers-wasm32": optional: true "@img/sharp-win32-arm64": optional: true @@ -19782,7 +19740,10 @@ __metadata: optional: true "@img/sharp-win32-x64": optional: true - checksum: 10/d62bc638c8ad382dffc266beeaffab71457d592abeb6fdf95b512e6dcbce0abf47b8d903b4ea081f012ceb40e4462f1e219184c729329146df32a5ccec2c231f + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/5f5c7739421f470d18e1b0d8160342103530724f59e3ef11d9cf15d5ec22e36fd2226d3c8f15ac72b3da947605c4076faf9ab8902e08575ed950c6f48a36ffa8 languageName: node linkType: hard @@ -20008,7 +19969,7 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.2, source-map-js@npm:^1.2.1": +"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.2.1": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" checksum: 10/ff9d8c8bf096d534a5b7707e0382ef827b4dd360a577d3f34d2b9f48e12c9d230b5747974ee7c607f0df65113732711bb701fe9ece3c7edbd43cb2294d707df3 @@ -20739,14 +20700,14 @@ __metadata: linkType: hard "swr@npm:^2.4.2": - version: 2.4.2 - resolution: "swr@npm:2.4.2" + version: 2.5.0 + resolution: "swr@npm:2.5.0" dependencies: dequal: "npm:^2.0.3" use-sync-external-store: "npm:^1.6.0" peerDependencies: react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10/53f5891599af6fdd58fa47727a2931e40ec308e7e0ff5e6ceef9c649b83703e4a2934f05cb5be159c6195b5ff934857144d1bad08129f42ce0a855f338455364 + checksum: 10/a607f098d13494683c9660d678610a87616d2cc131b969f14b5545b7003d99ef2074d84f0d98c6c679536633eb5b4486829e922de8ee9d81d9491d2bb53b1983 languageName: node linkType: hard @@ -20827,8 +20788,8 @@ __metadata: linkType: hard "terser@npm:^5.15.0": - version: 5.49.0 - resolution: "terser@npm:5.49.0" + version: 5.49.1 + resolution: "terser@npm:5.49.1" dependencies: "@jridgewell/source-map": "npm:^0.3.3" acorn: "npm:^8.15.0" @@ -20836,7 +20797,7 @@ __metadata: source-map-support: "npm:~0.5.20" bin: terser: bin/terser - checksum: 10/f5c4fe514a5d5bbb712f2b9082bce2058c60d5fe06a4e8ba7ca9f223a3bd5e0789a43aafde023443ce6569752adabfc0908fc848c5cb1620007321256411fd30 + checksum: 10/387f00780d9d18eeee6915c6ef41291cf36ec48884c78d75cc591ae4f7f141339394ea029deb11ec12304d24d64eef41afa3951e4705a976c005a31333db5b45 languageName: node linkType: hard @@ -21201,17 +21162,17 @@ __metadata: linkType: hard "typescript-eslint@npm:^8.65.0": - version: 8.65.0 - resolution: "typescript-eslint@npm:8.65.0" + version: 8.66.0 + resolution: "typescript-eslint@npm:8.66.0" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.65.0" - "@typescript-eslint/parser": "npm:8.65.0" - "@typescript-eslint/typescript-estree": "npm:8.65.0" - "@typescript-eslint/utils": "npm:8.65.0" + "@typescript-eslint/eslint-plugin": "npm:8.66.0" + "@typescript-eslint/parser": "npm:8.66.0" + "@typescript-eslint/typescript-estree": "npm:8.66.0" + "@typescript-eslint/utils": "npm:8.66.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10/5b2242f59005afdd57190849be7df2e86ce33b007fa0bf605f700ad0e38ea14fc14c48deafdf4a039614c1f012b71c61a19edb27f76d52fdc924cde476a100d1 + checksum: 10/e76da9c684e4fe012b49a5120dd57553df0788cf1cffcbd5dc72c98de33e549f306495e710292f111ee058366b1e5d8d603c2bb14385b12bb35edc7bbf13ad55 languageName: node linkType: hard @@ -21310,9 +21271,9 @@ __metadata: linkType: hard "undici@npm:^8.4.1": - version: 8.9.0 - resolution: "undici@npm:8.9.0" - checksum: 10/dfad3e233087eafdf1d361acd17c4d45a9590d5db9400458f1c03f47d8f1ef68647e2109ebb982c862e50c227093abd2d5f44b154904fc0b3d22576b6e95cfe7 + version: 8.10.0 + resolution: "undici@npm:8.10.0" + checksum: 10/254219966d4a2fb110f565ffe73131caa82e949f207b9dd88930ebc23dfb6561db72ca187391cbd408ceeba7c647cb47d4110e8ae00df44c0c66e8a21e091dc0 languageName: node linkType: hard @@ -22172,8 +22133,8 @@ __metadata: linkType: hard "ws@npm:^8.12.1, ws@npm:^8.20.1, ws@npm:^8.21.0": - version: 8.21.1 - resolution: "ws@npm:8.21.1" + version: 8.21.2 + resolution: "ws@npm:8.21.2" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -22182,7 +22143,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10/8493bc543072763d7bacffb2282c9d7954dc5c599c9df4c2d15f91c217f63e293a33ffc70756e3f2bf8c5d85bc6069ad7d9983c382d4713c807b083ffb1b1719 + checksum: 10/1fa65bfadc0dc73674638482b448e2a8cf1833d0acc2263fd27bf74b98f6f2917b2db8263b460a1ff37b6c2b8b892eba0e0eca33325a1ed7a8fe187f38550b40 languageName: node linkType: hard