diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 552191d1316..a4c7293f099 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -13,6 +13,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -87,6 +98,19 @@
+
+
+
+
+
+
+
+
diff --git a/app/definitions/Voip.ts b/app/definitions/Voip.ts
new file mode 100644
index 00000000000..1f13d16e56f
--- /dev/null
+++ b/app/definitions/Voip.ts
@@ -0,0 +1,5 @@
+export type IceServer = {
+ urls: string;
+ username?: string;
+ credential?: string;
+};
diff --git a/app/lib/constants/defaultSettings.ts b/app/lib/constants/defaultSettings.ts
index 74b3f1ed355..d2ea6708a49 100644
--- a/app/lib/constants/defaultSettings.ts
+++ b/app/lib/constants/defaultSettings.ts
@@ -300,5 +300,11 @@ export const defaultSettings = {
Cloud_Workspace_AirGapped_Restrictions_Remaining_Days: {
type: 'valueAsNumber'
},
+ VoIP_TeamCollab_Ice_Servers: {
+ type: 'valueAsString'
+ },
+ VoIP_TeamCollab_Ice_Gathering_Timeout: {
+ type: 'valueAsNumber'
+ },
...deprecatedSettings
} as const;
diff --git a/app/lib/services/voip/MediaCallLogger.ts b/app/lib/services/voip/MediaCallLogger.ts
new file mode 100644
index 00000000000..0d9444d7f7a
--- /dev/null
+++ b/app/lib/services/voip/MediaCallLogger.ts
@@ -0,0 +1,21 @@
+import type { IMediaSignalLogger } from '@rocket.chat/media-signaling';
+
+export class MediaCallLogger implements IMediaSignalLogger {
+ log(...args: unknown[]): void {
+ console.log(`[Media Call] ${JSON.stringify(args)}`);
+ }
+
+ debug(...args: unknown[]): void {
+ if (__DEV__) {
+ console.log(`[Media Call Debug] ${JSON.stringify(args)}`);
+ }
+ }
+
+ error(...args: unknown[]): void {
+ console.log(`[Media Call Error] ${JSON.stringify(args)}`);
+ }
+
+ warn(...args: unknown[]): void {
+ console.log(`[Media Call Warning] ${JSON.stringify(args)}`);
+ }
+}
diff --git a/app/lib/services/voip/MediaSessionInstance.ts b/app/lib/services/voip/MediaSessionInstance.ts
new file mode 100644
index 00000000000..555093164ac
--- /dev/null
+++ b/app/lib/services/voip/MediaSessionInstance.ts
@@ -0,0 +1,159 @@
+import {
+ ClientMediaSignal,
+ IClientMediaCall,
+ MediaCallWebRTCProcessor,
+ MediaSignalingSession,
+ WebRTCProcessorConfig
+} from '@rocket.chat/media-signaling';
+import RNCallKeep from 'react-native-callkeep';
+import { registerGlobals } from 'react-native-webrtc';
+
+import { mediaSessionStore } from './MediaSessionStore';
+import { store } from '../../store/auxStore';
+import sdk from '../sdk';
+import { parseStringToIceServers } from './parseStringToIceServers';
+import { IceServer } from '../../../definitions/Voip';
+import { IDDPMessage } from '../../../definitions/IDDPMessage';
+
+class MediaSessionInstance {
+ private iceServers: IceServer[] = [];
+ private iceGatheringTimeout: number = 5000;
+ private mediaSignalListener: { stop: () => void } | null = null;
+ private mediaSignalsListener: { stop: () => void } | null = null;
+ private instance: MediaSignalingSession | null = null;
+ private storeTimeoutUnsubscribe: (() => void) | null = null;
+ private storeIceServersUnsubscribe: (() => void) | null = null;
+
+ public init(userId: string): void {
+ this.stop();
+ registerGlobals();
+ this.configureRNCallKeep();
+ this.configureIceServers();
+
+ mediaSessionStore.setWebRTCProcessorFactory(
+ (config: WebRTCProcessorConfig) =>
+ new MediaCallWebRTCProcessor({
+ ...config,
+ rtc: { ...config.rtc, iceServers: this.iceServers },
+ iceGatheringTimeout: this.iceGatheringTimeout
+ })
+ );
+ mediaSessionStore.setSendSignalFn((signal: ClientMediaSignal) => {
+ sdk.methodCall('stream-notify-user', `${userId}/media-calls`, JSON.stringify(signal));
+ });
+ this.instance = mediaSessionStore.getInstance(userId);
+ mediaSessionStore.onChange(() => (this.instance = mediaSessionStore.getInstance(userId)));
+
+ this.mediaSignalListener = sdk.onStreamData('stream-notify-user', (ddpMessage: IDDPMessage) => {
+ if (!this.instance) {
+ return;
+ }
+ const [, ev] = ddpMessage.fields.eventName.split('/');
+ if (ev !== 'media-signal') {
+ return;
+ }
+ const signal = ddpMessage.fields.args[0];
+ this.instance.processSignal(signal);
+ });
+
+ this.instance?.on('newCall', ({ call }: { call: IClientMediaCall }) => {
+ if (call && !call.hidden) {
+ call.emitter.on('stateChange', oldState => {
+ console.log(`📊 ${oldState} → ${call.state}`);
+ });
+
+ const displayName = call.contact.displayName || call.contact.username || 'Unknown';
+ RNCallKeep.displayIncomingCall(call.callId, displayName, displayName, 'generic', false);
+
+ call.emitter.on('ended', () => RNCallKeep.endCall(call.callId));
+ }
+ });
+ }
+
+ private configureRNCallKeep() {
+ RNCallKeep.addEventListener('answerCall', async ({ callUUID }) => {
+ const mainCall = this.instance?.getMainCall();
+ if (mainCall && mainCall.callId === callUUID) {
+ await mainCall.accept();
+ RNCallKeep.setCurrentCallActive(mainCall.callId);
+ } else {
+ RNCallKeep.endCall(callUUID);
+ }
+ });
+
+ RNCallKeep.addEventListener('endCall', ({ callUUID }) => {
+ const mainCall = this.instance?.getMainCall();
+ if (mainCall && mainCall.callId === callUUID) {
+ if (mainCall.state === 'ringing') {
+ mainCall.reject();
+ } else {
+ mainCall.hangup();
+ }
+ }
+ });
+
+ RNCallKeep.addEventListener('didPerformSetMutedCallAction', ({ muted, callUUID }) => {
+ const mainCall = this.instance?.getMainCall();
+ if (mainCall && mainCall.callId === callUUID) {
+ mainCall.setMuted(muted);
+ }
+ });
+
+ RNCallKeep.addEventListener('didPerformDTMFAction', ({ digits }) => {
+ const mainCall = this.instance?.getMainCall();
+ if (mainCall) {
+ mainCall.sendDTMF(digits);
+ }
+ });
+ }
+
+ private getIceServers() {
+ const iceServers = store.getState().settings.VoIP_TeamCollab_Ice_Servers as any;
+ return parseStringToIceServers(iceServers);
+ }
+
+ private configureIceServers() {
+ this.iceServers = this.getIceServers();
+ this.iceGatheringTimeout = store.getState().settings.VoIP_TeamCollab_Ice_Gathering_Timeout as number;
+
+ this.storeTimeoutUnsubscribe = store.subscribe(() => {
+ const currentTimeout = store.getState().settings.VoIP_TeamCollab_Ice_Gathering_Timeout as number;
+ if (currentTimeout !== this.iceGatheringTimeout) {
+ this.iceGatheringTimeout = currentTimeout;
+ this.instance?.setIceGatheringTimeout(this.iceGatheringTimeout);
+ }
+ });
+
+ this.storeIceServersUnsubscribe = store.subscribe(() => {
+ const currentIceServers = this.getIceServers();
+ if (currentIceServers !== this.iceServers) {
+ this.iceServers = currentIceServers;
+ this.instance?.setIceServers(this.iceServers);
+ }
+ });
+ }
+
+ private stop() {
+ if (this.mediaSignalListener) {
+ this.mediaSignalListener.stop();
+ }
+ if (this.mediaSignalsListener) {
+ this.mediaSignalsListener.stop();
+ }
+ RNCallKeep.removeEventListener('answerCall');
+ RNCallKeep.removeEventListener('endCall');
+ RNCallKeep.removeEventListener('didPerformSetMutedCallAction');
+ RNCallKeep.removeEventListener('didPerformDTMFAction');
+ if (this.storeTimeoutUnsubscribe) {
+ this.storeTimeoutUnsubscribe();
+ }
+ if (this.storeIceServersUnsubscribe) {
+ this.storeIceServersUnsubscribe();
+ }
+ if (this.instance) {
+ this.instance.endSession();
+ }
+ }
+}
+
+export const mediaSessionInstance = new MediaSessionInstance();
diff --git a/app/lib/services/voip/MediaSessionStore.ts b/app/lib/services/voip/MediaSessionStore.ts
new file mode 100644
index 00000000000..b359287c8cc
--- /dev/null
+++ b/app/lib/services/voip/MediaSessionStore.ts
@@ -0,0 +1,100 @@
+import { Emitter } from '@rocket.chat/emitter';
+import { MediaSignalingSession, MediaCallWebRTCProcessor } from '@rocket.chat/media-signaling';
+import type { MediaSignalTransport, ClientMediaSignal, WebRTCProcessorConfig } from '@rocket.chat/media-signaling';
+import { mediaDevices } from 'react-native-webrtc';
+import BackgroundTimer from 'react-native-background-timer';
+
+import { MediaCallLogger } from './MediaCallLogger';
+
+type SignalTransport = MediaSignalTransport;
+
+const randomStringFactory = (): string =>
+ Date.now().toString(36) + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
+
+class MediaSessionStore extends Emitter<{ change: void }> {
+ private sessionInstance: MediaSignalingSession | null = null;
+ private sendSignalFn: SignalTransport | null = null;
+ private _webrtcProcessorFactory: ((config: WebRTCProcessorConfig) => MediaCallWebRTCProcessor) | null = null;
+
+ private change() {
+ this.emit('change');
+ }
+
+ public onChange(callback: () => void) {
+ return this.on('change', callback);
+ }
+
+ private webrtcProcessorFactory(config: WebRTCProcessorConfig): MediaCallWebRTCProcessor {
+ if (!this._webrtcProcessorFactory) {
+ throw new Error('WebRTC processor factory not set');
+ }
+ return this._webrtcProcessorFactory(config);
+ }
+
+ private sendSignal(signal: ClientMediaSignal) {
+ if (!this.sendSignalFn) {
+ throw new Error('Send signal function not set');
+ }
+ return this.sendSignalFn(signal);
+ }
+
+ private makeInstance(userId: string): MediaSignalingSession | null {
+ if (this.sessionInstance !== null) {
+ this.sessionInstance.endSession();
+ this.sessionInstance = null;
+ }
+
+ if (!this._webrtcProcessorFactory || !this.sendSignalFn) {
+ throw new Error('WebRTC processor factory and send signal function must be set');
+ }
+
+ this.sessionInstance = new MediaSignalingSession({
+ userId,
+ transport: (signal: ClientMediaSignal) => this.sendSignal(signal),
+ processorFactories: {
+ webrtc: (config: WebRTCProcessorConfig) => this.webrtcProcessorFactory(config)
+ },
+ mediaStreamFactory: (constraints: any) => mediaDevices.getUserMedia(constraints) as unknown as Promise,
+ randomStringFactory,
+ logger: new MediaCallLogger(),
+ timerProcessor: {
+ setInterval: (callback: () => void, interval: number) => BackgroundTimer.setInterval(callback, interval),
+ clearInterval: (interval: number) => BackgroundTimer.clearInterval(interval),
+ setTimeout: (callback: () => void, timeout: number) => BackgroundTimer.setTimeout(callback, timeout),
+ clearTimeout: (timeout: number) => BackgroundTimer.clearTimeout(timeout)
+ }
+ });
+
+ this.change();
+ return this.sessionInstance;
+ }
+
+ public getInstance(userId?: string): MediaSignalingSession | null {
+ if (!userId) {
+ throw new Error('User Id is required');
+ }
+
+ if (this.sessionInstance?.userId === userId) {
+ return this.sessionInstance;
+ }
+
+ return this.makeInstance(userId);
+ }
+
+ public setSendSignalFn(sendSignalFn: SignalTransport) {
+ this.sendSignalFn = sendSignalFn;
+ this.change();
+ }
+
+ public setWebRTCProcessorFactory(factory: (config: WebRTCProcessorConfig) => MediaCallWebRTCProcessor): void {
+ this._webrtcProcessorFactory = factory;
+ this.change();
+ }
+
+ public getCurrentInstance(): MediaSignalingSession | null {
+ return this.sessionInstance;
+ }
+}
+
+// TODO: change name
+export const mediaSessionStore = new MediaSessionStore();
diff --git a/app/lib/services/voip/parseStringToIceServers.ts b/app/lib/services/voip/parseStringToIceServers.ts
new file mode 100644
index 00000000000..0a339d4a84d
--- /dev/null
+++ b/app/lib/services/voip/parseStringToIceServers.ts
@@ -0,0 +1,24 @@
+import type { IceServer } from '../../../definitions/Voip';
+
+export const parseStringToIceServer = (server: string): IceServer => {
+ const credentials = server.trim().split('@');
+ const urls = credentials.pop() as string;
+ const [username, credential] = credentials.length === 1 ? credentials[0].split(':') : [];
+
+ return {
+ urls,
+ ...(username &&
+ credential && {
+ username: decodeURIComponent(username),
+ credential: decodeURIComponent(credential)
+ })
+ };
+};
+
+export const parseStringToIceServers = (string: string): IceServer[] => {
+ if (!string) {
+ return [];
+ }
+ const lines = string.trim() ? string.split(',') : [];
+ return lines.map(line => parseStringToIceServer(line));
+};
diff --git a/app/lib/store/index.ts b/app/lib/store/index.ts
index 3ae917484ac..25011f0f648 100644
--- a/app/lib/store/index.ts
+++ b/app/lib/store/index.ts
@@ -18,8 +18,8 @@ if (__DEV__) {
applyAppStateMiddleware(),
applyInternetStateMiddleware(),
applyMiddleware(reduxImmutableStateInvariant),
- applyMiddleware(sagaMiddleware),
- applyMiddleware(logger)
+ applyMiddleware(sagaMiddleware)
+ // applyMiddleware(logger)
);
} else {
sagaMiddleware = createSagaMiddleware();
diff --git a/app/sagas/login.js b/app/sagas/login.js
index 91dfe225f0c..b134e524013 100644
--- a/app/sagas/login.js
+++ b/app/sagas/login.js
@@ -3,6 +3,9 @@ import { call, cancel, delay, fork, put, race, select, take, takeLatest } from '
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { Q } from '@nozbe/watermelondb';
import * as Keychain from 'react-native-keychain';
+import RNCallKeep from 'react-native-callkeep';
+import { PermissionsAndroid } from 'react-native';
+import BackgroundTimer from 'react-native-background-timer';
import moment from 'moment';
import * as types from '../actions/actionsTypes';
@@ -42,6 +45,7 @@ import appNavigation from '../lib/navigation/appNavigation';
import { showActionSheetRef } from '../containers/ActionSheet';
import { SupportedVersionsWarning } from '../containers/SupportedVersions';
import { isIOS } from '../lib/methods/helpers';
+import { mediaSessionInstance } from '../lib/services/voip/MediaSessionInstance';
const getServer = state => state.server.server;
const loginWithPasswordCall = args => loginWithPassword(args);
@@ -214,6 +218,55 @@ const fetchUsersRoles = function* fetchRoomsFork() {
}
};
+function* initCallKeep() {
+ try {
+ const options = {
+ ios: {
+ appName: 'Rocket.Chat',
+ includesCallsInRecents: false
+ },
+ android: {
+ alertTitle: 'Permissions required',
+ alertDescription: 'This application needs to access your phone accounts',
+ cancelButton: 'Cancel',
+ okButton: 'Ok',
+ imageName: 'phone_account_icon',
+ additionalPermissions: [
+ PermissionsAndroid.PERMISSIONS.READ_PHONE_STATE,
+ PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
+ PermissionsAndroid.PERMISSIONS.CALL_PHONE
+ ],
+ // Required to get audio in background when using Android 11
+ foregroundService: {
+ channelId: 'chat.rocket.reactnative',
+ channelName: 'Rocket.Chat',
+ notificationTitle: 'Voice call is running on background'
+ }
+ }
+ };
+
+ RNCallKeep.setup(options);
+ RNCallKeep.canMakeMultipleCalls(false);
+
+ const start = Date.now();
+ setInterval(() => {
+ console.log('Timer fired after', Date.now() - start, 'ms');
+ }, 1000);
+ } catch (e) {
+ log(e);
+ }
+}
+
+const startVoipFork = function* startVoipFork() {
+ try {
+ yield call(initCallKeep);
+ const userId = yield select(state => state.login.user.id);
+ mediaSessionInstance.init(userId);
+ } catch (e) {
+ log(e);
+ }
+};
+
const handleLoginSuccess = function* handleLoginSuccess({ user }) {
try {
getUserPresence(user.id);
@@ -230,6 +283,8 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
yield fork(fetchEnterpriseModulesFork, { user });
yield fork(subscribeSettingsFork);
yield fork(fetchUsersRoles);
+ yield delay(1000);
+ yield fork(startVoipFork);
setLanguage(user?.language);
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 0adfeaba160..0c0ce8538b8 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -209,6 +209,7 @@ PODS:
- hermes-engine (0.79.4):
- hermes-engine/Pre-built (= 0.79.4)
- hermes-engine/Pre-built (0.79.4)
+ - JitsiWebRTC (124.0.2)
- libavif/core (0.11.1)
- libavif/libdav1d (0.11.1):
- libavif/core
@@ -1816,6 +1817,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
+ - react-native-webrtc (124.0.7):
+ - JitsiWebRTC (~> 124.0.0)
+ - React-Core
- react-native-webview (13.15.0):
- DoubleConversion
- glog
@@ -2712,6 +2716,7 @@ DEPENDENCIES:
- react-native-restart (from `../node_modules/react-native-restart`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
+ - react-native-webrtc (from `../node_modules/react-native-webrtc`)
- react-native-webview (from `../node_modules/react-native-webview`)
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`)
@@ -2783,6 +2788,7 @@ SPEC REPOS:
- GoogleAppMeasurement
- GoogleDataTransport
- GoogleUtilities
+ - JitsiWebRTC
- libavif
- libdav1d
- libwebp
@@ -2939,6 +2945,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-safe-area-context"
react-native-slider:
:path: "../node_modules/@react-native-community/slider"
+ react-native-webrtc:
+ :path: "../node_modules/react-native-webrtc"
react-native-webview:
:path: "../node_modules/react-native-webview"
React-NativeModulesApple:
@@ -3091,6 +3099,7 @@ SPEC CHECKSUMS:
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
hermes-engine: 8b5a5eb386b990287d072fd7b6f6ebd9544dd251
+ JitsiWebRTC: b47805ab5668be38e7ee60e2258f49badfe8e1d0
libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
@@ -3142,6 +3151,7 @@ SPEC CHECKSUMS:
react-native-restart: f6f591aeb40194c41b9b5013901f00e6cf7d0f29
react-native-safe-area-context: 5928d84c879db2f9eb6969ca70e68f58623dbf25
react-native-slider: 605e731593322c4bb2eb48d7d64e2e4dbf7cbd77
+ react-native-webrtc: e8f0ce746353adc2744a2b933645e1aeb41eaa74
react-native-webview: e28f476ea60826ef0b1d7297244db1dfbec74acd
React-NativeModulesApple: 5b234860053d0dd11f3442f38b99688ff1c9733b
React-oscompat: 472a446c740e39ee39cd57cd7bfd32177c763a2b
@@ -3172,7 +3182,7 @@ SPEC CHECKSUMS:
React-timing: 2d07431f1c1203c5b0aaa6dc7b5f503704519218
React-utils: 67cf7dcfc18aa4c56bec19e11886033bb057d9fa
ReactAppDependencyProvider: bf62814e0fde923f73fc64b7e82d76c63c284da9
- ReactCodegen: c51a63d05629675dd61caf58d1a093c4457972c0
+ ReactCodegen: df3ff45729335a27d1c85bed1787e79783289968
ReactCommon: 177fca841e97b2c0e288e86097b8be04c6e7ae36
RNBootSplash: 1280eeb18d887de0a45bb4923d4fc56f25c8b99c
RNCAsyncStorage: edb872909c88d8541c0bfade3f86cd7784a7c6b3
diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj
index 7f65bcc902d..0385f066b0b 100644
--- a/ios/RocketChatRN.xcodeproj/project.pbxproj
+++ b/ios/RocketChatRN.xcodeproj/project.pbxproj
@@ -1658,10 +1658,12 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh",
+ "${PODS_XCFRAMEWORKS_BUILD_DIR}/JitsiWebRTC/WebRTC.framework/WebRTC",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
runOnlyForDeploymentPostprocessing = 0;
@@ -2145,10 +2147,12 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh",
+ "${PODS_XCFRAMEWORKS_BUILD_DIR}/JitsiWebRTC/WebRTC.framework/WebRTC",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/package.json b/package.json
index f6a01974cf0..ffceda280af 100644
--- a/package.json
+++ b/package.json
@@ -56,6 +56,7 @@
"@react-navigation/elements": "^2.6.1",
"@react-navigation/native": "^7.1.16",
"@react-navigation/native-stack": "^7.3.23",
+ "@rocket.chat/media-signaling": "/Users/diegomello/Development/Work/Rocket.Chat/packages/media-signaling",
"@rocket.chat/message-parser": "^0.31.31",
"@rocket.chat/mobile-crypto": "RocketChat/rocket.chat-mobile-crypto",
"@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile",
@@ -98,6 +99,7 @@
"react-native-animatable": "1.3.3",
"react-native-background-timer": "2.4.1",
"react-native-bootsplash": "^6.3.8",
+ "react-native-callkeep": "^4.3.16",
"react-native-config-reader": "4.1.1",
"react-native-console-time-polyfill": "1.2.3",
"react-native-device-info": "11.1.0",
@@ -130,6 +132,7 @@
"react-native-svg": "^15.12.1",
"react-native-url-polyfill": "2.0.0",
"react-native-vector-icons": "9.2.0",
+ "react-native-webrtc": "^124.0.7",
"react-native-webview": "^13.15.0",
"react-redux": "8.0.5",
"reanimated-tab-view": "^0.3.0",
diff --git a/patches/@rocket.chat+sdk+1.3.3-mobile.patch b/patches/@rocket.chat+sdk+1.3.3-mobile.patch
new file mode 100644
index 00000000000..ab0a55a87d2
--- /dev/null
+++ b/patches/@rocket.chat+sdk+1.3.3-mobile.patch
@@ -0,0 +1,15 @@
+diff --git a/node_modules/@rocket.chat/sdk/lib/drivers/ddp.ts b/node_modules/@rocket.chat/sdk/lib/drivers/ddp.ts
+index 19d31ae..d5295b5 100644
+--- a/node_modules/@rocket.chat/sdk/lib/drivers/ddp.ts
++++ b/node_modules/@rocket.chat/sdk/lib/drivers/ddp.ts
+@@ -549,7 +549,9 @@ export class DDPDriver extends EventEmitter implements ISocket, IDriver {
+ 'uiInteraction',
+ 'e2ekeyRequest',
+ 'userData',
+- 'video-conference'
++ 'video-conference',
++ 'media-signal',
++ 'media-calls'
+ ].map(event => this.subscribe(topic, `${this.userId}/${event}`, false)))
+ }
+
diff --git a/yarn.lock b/yarn.lock
index b7f53c6362e..35e5f5764b3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4923,6 +4923,11 @@
resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.2.1.tgz#9403f51c17cae37edf870c6bc0c81c1ece5ccef8"
integrity sha512-1dgmkh+3so0+LlBWRhGA33ua4MYr7tUOj+a9Si28vUi0IUFNbff1T3sgpeDJI/LaC75bBYnQ0A3wXjn0OrRNBA==
+"@rocket.chat/emitter@~0.31.25":
+ version "0.31.25"
+ resolved "https://registry.yarnpkg.com/@rocket.chat/emitter/-/emitter-0.31.25.tgz#17dd7838dc988ebc5fcd96343c4431d5cef45845"
+ integrity sha512-hw5BpDlNwpYSb+K5X3DNMNUVEVXxmXugUPetGZGCWvntSVFsOjYuVEypoKW6vBBXSfqCBb0kN1npYcKEb4NFBw==
+
"@rocket.chat/eslint-config@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@rocket.chat/eslint-config/-/eslint-config-0.4.0.tgz#d648decd02ae739eac17a32e1630332a75318ea1"
@@ -4930,6 +4935,12 @@
dependencies:
eslint-plugin-import "^2.17.2"
+"@rocket.chat/media-signaling@file:../Rocket.Chat/packages/media-signaling":
+ version "0.0.1"
+ dependencies:
+ "@rocket.chat/emitter" "~0.31.25"
+ ajv "^8.17.1"
+
"@rocket.chat/message-parser@^0.31.31":
version "0.31.31"
resolved "https://registry.yarnpkg.com/@rocket.chat/message-parser/-/message-parser-0.31.31.tgz#9a3eea7602ac37387c6384577623865c8d536003"
@@ -5932,6 +5943,16 @@ ajv@^8.0.0, ajv@^8.6.3, ajv@^8.9.0:
require-from-string "^2.0.2"
uri-js "^4.2.2"
+ajv@^8.17.1:
+ version "8.17.1"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6"
+ integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
+ dependencies:
+ fast-deep-equal "^3.1.3"
+ fast-uri "^3.0.1"
+ json-schema-traverse "^1.0.0"
+ require-from-string "^2.0.2"
+
anser@^1.4.9:
version "1.4.10"
resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.10.tgz#befa3eddf282684bd03b63dcda3927aef8c2e35b"
@@ -6491,7 +6512,7 @@ bare-path@^2.0.0, bare-path@^2.1.0:
dependencies:
bare-os "^2.1.0"
-base64-js@^1.2.3, base64-js@^1.3.1, base64-js@^1.5.1:
+base64-js@1.5.1, base64-js@^1.2.3, base64-js@^1.3.1, base64-js@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@@ -7407,6 +7428,13 @@ debug@4, debug@^4.1.0, debug@^4.1.1:
dependencies:
ms "^2.1.1"
+debug@4.3.4, debug@^4.3.2, debug@^4.3.4:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
debug@^3.1.0, debug@^3.2.7:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
@@ -7421,13 +7449,6 @@ debug@^4.3.1:
dependencies:
ms "2.1.2"
-debug@^4.3.2, debug@^4.3.4:
- version "4.3.4"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
- integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
- dependencies:
- ms "2.1.2"
-
debug@^4.3.5, debug@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
@@ -8485,6 +8506,11 @@ event-pubsub@4.3.0:
resolved "https://registry.yarnpkg.com/event-pubsub/-/event-pubsub-4.3.0.tgz#f68d816bc29f1ec02c539dc58c8dd40ce72cb36e"
integrity sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==
+event-target-shim@6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-6.0.2.tgz#ea5348c3618ee8b62ff1d344f01908ee2b8a2b71"
+ integrity sha512-8q3LsZjRezbFZ2PN+uP+Q7pnHUMmAOziU2vA2OwoFaKIXxlxl38IylhSSgUorWu/rf4er67w0ikBqjBFk/pomA==
+
event-target-shim@^5.0.0, event-target-shim@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
@@ -8744,6 +8770,11 @@ fast-levenshtein@^2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
+fast-uri@^3.0.1:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa"
+ integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==
+
fast-xml-parser@^4.0.12, fast-xml-parser@^4.2.4:
version "4.4.1"
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f"
@@ -12886,6 +12917,11 @@ react-native-bootsplash@^6.3.8:
ts-dedent "^2.2.0"
xml-formatter "^3.6.5"
+react-native-callkeep@^4.3.16:
+ version "4.3.16"
+ resolved "https://registry.yarnpkg.com/react-native-callkeep/-/react-native-callkeep-4.3.16.tgz#56291796984b896113ef00f8b67ae3fe177baf70"
+ integrity sha512-aIxn02T5zW4jNPyzRdFGTWv6xD3Vy/1AkBMB6iYvWZEHWnfmgNGF0hELqg03Vbc2BNUhfqpu17aIydos+5Hurg==
+
react-native-config-reader@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/react-native-config-reader/-/react-native-config-reader-4.1.1.tgz#478b69e32adcc2e9a14f6ef5fa2cbbe012b9a27e"
@@ -13130,6 +13166,15 @@ react-native-vector-icons@9.2.0:
prop-types "^15.7.2"
yargs "^16.1.1"
+react-native-webrtc@^124.0.7:
+ version "124.0.7"
+ resolved "https://registry.yarnpkg.com/react-native-webrtc/-/react-native-webrtc-124.0.7.tgz#f50647a8eb3fae0ef29843eb1b5fe2c4ff75a56e"
+ integrity sha512-gnXPdbUS8IkKHq9WNaWptW/yy5s6nMyI6cNn90LXdobPVCgYSk6NA2uUGdT4c4J14BRgaFA95F+cR28tUPkMVA==
+ dependencies:
+ base64-js "1.5.1"
+ debug "4.3.4"
+ event-target-shim "6.0.2"
+
react-native-webview@^13.15.0:
version "13.15.0"
resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-13.15.0.tgz#b6d2f8d8dd65897db76659ddd8198d2c74ec5a79"