diff --git a/ci/package.json b/ci/package.json
index 43bbd365..50e995a4 100644
--- a/ci/package.json
+++ b/ci/package.json
@@ -12,7 +12,7 @@
},
"dependencies": {
"@livekit/react-native": "*",
- "@livekit/react-native-webrtc": "^137.0.1",
+ "@livekit/react-native-webrtc": "^137.0.2",
"livekit-client": "^2.15.4",
"react": "18.2.0",
"react-native": "0.74.2"
diff --git a/ci/yarn.lock b/ci/yarn.lock
index 270823c7..ea143b77 100644
--- a/ci/yarn.lock
+++ b/ci/yarn.lock
@@ -2174,16 +2174,16 @@ __metadata:
languageName: node
linkType: hard
-"@livekit/react-native-webrtc@npm:^137.0.1":
- version: 137.0.1
- resolution: "@livekit/react-native-webrtc@npm:137.0.1"
+"@livekit/react-native-webrtc@npm:^137.0.2":
+ version: 137.0.2
+ resolution: "@livekit/react-native-webrtc@npm:137.0.2"
dependencies:
base64-js: 1.5.1
debug: 4.3.4
event-target-shim: 6.0.2
peerDependencies:
react-native: ">=0.60.0"
- checksum: 3341aafe7d9d1deb345e47247faa020b2cb5f1adbded135a244f0f9285eed720c9f62325bbce0e21c1da755ae24ae6c69eb07728cece709560f615908bde43a9
+ checksum: c23467701c7dfb74e3b223dd955b20ba1586c2186383f5b2595fff9b65a36b2db0b660de9935e009b69bc5e0bbff047d0f598495f8464daae8db5f8d9f9b7c24
languageName: node
linkType: hard
@@ -3872,7 +3872,7 @@ __metadata:
"@babel/preset-env": ^7.20.0
"@babel/runtime": ^7.20.0
"@livekit/react-native": "*"
- "@livekit/react-native-webrtc": ^137.0.1
+ "@livekit/react-native-webrtc": ^137.0.2
"@react-native/babel-preset": 0.74.84
"@react-native/eslint-config": 0.74.84
"@react-native/metro-config": 0.74.84
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index d552b8de..802b4d35 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -29,9 +29,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- - livekit-react-native-webrtc (137.0.1):
+ - livekit-react-native-webrtc (137.0.2):
- React-Core
- - WebRTC-SDK (= 137.7151.02)
+ - WebRTC-SDK (= 137.7151.04)
- RCT-Folly (2024.01.01.00):
- boost
- DoubleConversion
@@ -1220,7 +1220,7 @@ PODS:
- ReactCommon/turbomodule/core
- Yoga
- SocketRocket (0.7.0)
- - WebRTC-SDK (137.7151.02)
+ - WebRTC-SDK (137.7151.04)
- Yoga (0.0.0)
DEPENDENCIES:
@@ -1426,7 +1426,7 @@ SPEC CHECKSUMS:
glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f
hermes-engine: 01d3e052018c2a13937aca1860fbedbccd4a41b7
livekit-react-native: 22180f283c63416a81f8765555fccc7a33f0a044
- livekit-react-native-webrtc: 0ffe5a13d196f65d717f958a111399f4f6383102
+ livekit-react-native-webrtc: 3bb1be767c4e489f69bca662eba1c5e7b1e1be0d
RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47
RCTDeprecation: b03c35057846b685b3ccadc9bfe43e349989cdb2
RCTRequired: 194626909cfa8d39ca6663138c417bc6c431648c
@@ -1480,7 +1480,7 @@ SPEC CHECKSUMS:
RNCAsyncStorage: 0c357f3156fcb16c8589ede67cc036330b6698ca
RNScreens: b32a9ff15bea7fcdbe5dff6477bc503f792b1208
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
- WebRTC-SDK: d20de357dcbf7c9696b124b39f3ff62125107e4b
+ WebRTC-SDK: 40d4f5ba05cadff14e4db5614aec402a633f007e
Yoga: ae3c32c514802d30f687a04a6a35b348506d411f
PODFILE CHECKSUM: b5aad0c7d12b2ea501eb822f98f00ca01d154bd9
diff --git a/example/package.json b/example/package.json
index b6fa8297..812757d3 100644
--- a/example/package.json
+++ b/example/package.json
@@ -10,7 +10,7 @@
"postinstall": "patch-package"
},
"dependencies": {
- "@livekit/react-native-webrtc": "^137.0.1",
+ "@livekit/react-native-webrtc": "^137.0.2",
"@react-native-async-storage/async-storage": "^1.17.10",
"@react-navigation/native": "^6.0.8",
"@react-navigation/native-stack": "^6.5.0",
diff --git a/example/src/PreJoinPage.tsx b/example/src/PreJoinPage.tsx
index 27afccf1..77fb3930 100644
--- a/example/src/PreJoinPage.tsx
+++ b/example/src/PreJoinPage.tsx
@@ -14,8 +14,9 @@ import type { RootStackParamList } from './App';
import { useTheme } from '@react-navigation/native';
import AsyncStorage from '@react-native-async-storage/async-storage';
-const DEFAULT_URL = 'wss://example.livekit.cloud';
-const DEFAULT_TOKEN = 'your-token-here';
+const DEFAULT_URL = 'ws://192.168.11.3:7880';
+const DEFAULT_TOKEN =
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjA1MjM4NTksImlkZW50aXR5IjoicGhvbmUiLCJpc3MiOiJBUElUTFdySzh0YndyNDciLCJuYmYiOjE3NTc5MzE4NTksInN1YiI6InBob25lIiwidmlkZW8iOnsicm9vbSI6Im15cm9vbSIsInJvb21Kb2luIjp0cnVlfX0.jpvzL9Mcqu1tS3dpITO-ffAyjzZtEvnq_p9ehD5B7RM';
const DEFAULT_E2EE = false;
const DEFAULT_E2EE_KEY = '';
diff --git a/example/src/RoomPage.tsx b/example/src/RoomPage.tsx
index a4b14b0e..de45e930 100644
--- a/example/src/RoomPage.tsx
+++ b/example/src/RoomPage.tsx
@@ -67,7 +67,10 @@ export const RoomPage = ({
};
}, []);
- let { e2eeManager } = useRNE2EEManager({ sharedKey: e2eeKey });
+ let { e2eeManager } = useRNE2EEManager({
+ sharedKey: e2eeKey,
+ dataChannelEncryption: true,
+ });
let e2eeOptions = e2ee ? { e2eeManager } : undefined;
return (
@@ -79,7 +82,7 @@ export const RoomPage = ({
adaptiveStream: { pixelDensity: 'screen' },
e2ee: e2eeOptions,
}}
- audio={true}
+ audio={false}
video={true}
>
@@ -107,24 +110,22 @@ const RoomView = ({ navigation, e2ee }: RoomViewProps) => {
}, [room, e2ee]);
useIOSAudioManagement(room, true);
- // Setup room listeners
- const { send } = useDataChannel(
- (dataMessage: ReceivedDataMessage) => {
- //@ts-ignore
- let decoder = new TextDecoder('utf-8');
- let message = decoder.decode(dataMessage.payload);
+ // Setup room listeners
+ useEffect(() => {
+ room.registerTextStreamHandler('lk.chat', async (reader, participant) => {
+ let message = await reader.readAll();
let title = 'Received Message';
- if (dataMessage.from != null) {
- title = 'Received Message from ' + dataMessage.from?.identity;
+ if (participant != null) {
+ title = 'Received Message from ' + participant.identity;
}
Toast.show({
type: 'success',
text1: title,
text2: message,
});
- }
- );
+ });
+ }, [room]);
const tracks = useTracks(
[
@@ -229,17 +230,14 @@ const RoomView = ({ navigation, e2ee }: RoomViewProps) => {
localParticipant.setScreenShareEnabled(enabled);
}
}}
- sendData={(message: string) => {
+ sendData={async (message: string) => {
Toast.show({
type: 'success',
text1: 'Sending Message',
text2: message,
});
- //@ts-ignore
- let encoder = new TextEncoder();
- let encodedData = encoder.encode(message);
- send(encodedData, { reliable: true });
+ room.localParticipant.sendText(message, { topic: 'lk.chat' });
}}
onSimulate={(scenario) => {
room.simulateScenario(scenario);
diff --git a/example/yarn.lock b/example/yarn.lock
index e2da58a4..f0270af8 100644
--- a/example/yarn.lock
+++ b/example/yarn.lock
@@ -2172,16 +2172,16 @@ __metadata:
languageName: node
linkType: hard
-"@livekit/react-native-webrtc@npm:^137.0.1":
- version: 137.0.1
- resolution: "@livekit/react-native-webrtc@npm:137.0.1"
+"@livekit/react-native-webrtc@npm:^137.0.2":
+ version: 137.0.2
+ resolution: "@livekit/react-native-webrtc@npm:137.0.2"
dependencies:
base64-js: 1.5.1
debug: 4.3.4
event-target-shim: 6.0.2
peerDependencies:
react-native: ">=0.60.0"
- checksum: 3341aafe7d9d1deb345e47247faa020b2cb5f1adbded135a244f0f9285eed720c9f62325bbce0e21c1da755ae24ae6c69eb07728cece709560f615908bde43a9
+ checksum: c23467701c7dfb74e3b223dd955b20ba1586c2186383f5b2595fff9b65a36b2db0b660de9935e009b69bc5e0bbff047d0f598495f8464daae8db5f8d9f9b7c24
languageName: node
linkType: hard
@@ -6103,8 +6103,8 @@ __metadata:
linkType: hard
"livekit-client@npm:^2.15.4":
- version: 2.15.4
- resolution: "livekit-client@npm:2.15.4"
+ version: 2.15.7
+ resolution: "livekit-client@npm:2.15.7"
dependencies:
"@livekit/mutex": 1.1.1
"@livekit/protocol": 1.39.3
@@ -6117,7 +6117,7 @@ __metadata:
webrtc-adapter: ^9.0.1
peerDependencies:
"@types/dom-mediacapture-record": ^1
- checksum: 7ae2e2f5326c8f7925b359277fd12ddeefb0bc5069b0563a9e184f2b860351bf91733c1fa5fd7b65467b643776368058d485af888e2771fd68c61c409b950e05
+ checksum: e9740963eef9ddb7f50604d2918b7ca9e696c1d190b968cbf2cb75997b1e0d4fce25c795ae60b1c92785cd9a679b6d606688092f31a6a1aaefc27ec92fc87f0f
languageName: node
linkType: hard
@@ -6128,7 +6128,7 @@ __metadata:
"@babel/core": ^7.20.0
"@babel/preset-env": ^7.20.0
"@babel/runtime": ^7.20.0
- "@livekit/react-native-webrtc": ^137.0.1
+ "@livekit/react-native-webrtc": ^137.0.2
"@react-native-async-storage/async-storage": ^1.17.10
"@react-native/babel-preset": 0.74.84
"@react-native/eslint-config": 0.74.84
diff --git a/package.json b/package.json
index 03a389c3..f78ecc60 100644
--- a/package.json
+++ b/package.json
@@ -43,12 +43,13 @@
],
"dependencies": {
"@livekit/components-react": "^2.8.1",
+ "@livekit/mutex": "^1.1.1",
"array.prototype.at": "^1.1.1",
"event-target-shim": "6.0.2",
"events": "^3.3.0",
"loglevel": "^1.8.0",
"promise.allsettled": "^1.0.5",
- "react-native-quick-base64": "2.1.1",
+ "react-native-quick-base64": "^2.2.1",
"react-native-url-polyfill": "^1.3.0",
"typed-emitter": "^2.1.0",
"web-streams-polyfill": "^4.1.0",
@@ -59,7 +60,7 @@
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.0",
"@commitlint/config-conventional": "^16.2.1",
- "@livekit/react-native-webrtc": "^137.0.1",
+ "@livekit/react-native-webrtc": "^137.0.2",
"@react-native/babel-preset": "0.74.84",
"@react-native/eslint-config": "0.74.84",
"@react-native/metro-config": "0.74.84",
@@ -86,7 +87,7 @@
"typescript": "5.0.4"
},
"peerDependencies": {
- "@livekit/react-native-webrtc": "^137.0.1",
+ "@livekit/react-native-webrtc": "^137.0.2",
"livekit-client": "^2.15.4",
"react": "*",
"react-native": "*"
diff --git a/src/e2ee/RNE2EEManager.ts b/src/e2ee/RNE2EEManager.ts
index b7c0eef1..186fd0ef 100644
--- a/src/e2ee/RNE2EEManager.ts
+++ b/src/e2ee/RNE2EEManager.ts
@@ -1,7 +1,10 @@
import {
+ RTCDataPacketCryptor,
+ RTCDataPacketCryptorFactory,
RTCFrameCryptorAlgorithm,
RTCFrameCryptorFactory,
RTCRtpReceiver,
+ type RTCEncryptedPacket,
type RTCFrameCryptor,
type RTCRtpSender,
} from '@livekit/react-native-webrtc';
@@ -16,6 +19,9 @@ import {
type BaseE2EEManager,
type E2EEManagerCallbacks,
EncryptionEvent,
+ type DecryptDataResponseMessage,
+ type EncryptDataResponseMessage,
+ Mutex,
} from 'livekit-client';
import type RNKeyProvider from './RNKeyProvider';
import type RTCEngine from 'livekit-client/dist/src/room/RTCEngine';
@@ -36,11 +42,32 @@ export default class RNE2EEManager
RTCFrameCryptorAlgorithm.kAesGcm;
private encryptionEnabled: boolean = false;
+ private dataChannelEncryptionEnabled: boolean = false;
- constructor(keyProvider: RNKeyProvider) {
+ private dataPacketCryptorLock = new Mutex();
+ private dataPacketCryptor: RTCDataPacketCryptor | undefined = undefined;
+ constructor(
+ keyProvider: RNKeyProvider,
+ dcEncryptionEnabled: boolean = false
+ ) {
super();
this.keyProvider = keyProvider;
this.encryptionEnabled = false;
+ this.dataChannelEncryptionEnabled = dcEncryptionEnabled;
+ }
+
+ get isEnabled(): boolean {
+ return this.encryptionEnabled;
+ }
+ get isDataChannelEncryptionEnabled(): boolean {
+ console.log(
+ 'isDataChannelEncryptionEnabled?',
+ this.isEnabled,
+ this.encryptionEnabled,
+ this.dataChannelEncryptionEnabled,
+ this.isEnabled && this.dataChannelEncryptionEnabled
+ );
+ return this.isEnabled && this.dataChannelEncryptionEnabled;
}
setup(room: Room) {
@@ -78,7 +105,16 @@ export default class RNE2EEManager
await frameCryptor.dispose();
}
}
- );
+ )
+ .on(RoomEvent.SignalConnected, () => {
+ if (!this.room) {
+ throw new TypeError(`expected room to be present on signal connect`);
+ }
+ this.setParticipantCryptorEnabled(
+ this.room.localParticipant.isE2EEEnabled,
+ this.room.localParticipant.identity
+ );
+ });
}
private async setupE2EESender(
@@ -133,6 +169,91 @@ export default class RNE2EEManager
this.keyProvider.setSifTrailer(trailer);
}
+ private async getDataPacketCryptor(): Promise {
+ let dataPacketCryptor = this.dataPacketCryptor;
+ if (dataPacketCryptor) {
+ return dataPacketCryptor;
+ }
+
+ let unlock = await this.dataPacketCryptorLock.lock();
+
+ try {
+ dataPacketCryptor = this.dataPacketCryptor;
+ if (dataPacketCryptor) {
+ return dataPacketCryptor;
+ }
+
+ dataPacketCryptor =
+ await RTCDataPacketCryptorFactory.createDataPacketCryptor(
+ this.algorithm,
+ this.keyProvider.rtcKeyProvider
+ );
+
+ this.dataPacketCryptor = dataPacketCryptor;
+ return dataPacketCryptor;
+ } finally {
+ unlock();
+ }
+ }
+ async encryptData(
+ data: Uint8Array
+ ): Promise {
+ let room = this.room;
+ if (!room) {
+ throw new Error("e2eemanager isn't setup with room!");
+ }
+
+ let participantId = room.localParticipant.identity;
+
+ let dataPacketCryptor = await this.getDataPacketCryptor();
+
+ let encryptedPacket = await dataPacketCryptor.encrypt(
+ participantId,
+ this.keyProvider.getLatestKeyIndex(participantId),
+ data
+ );
+
+ if (!encryptedPacket) {
+ throw new Error('encryption for packet failed');
+ }
+ return {
+ uuid: '', //not used
+ payload: encryptedPacket.payload,
+ iv: encryptedPacket.iv,
+ keyIndex: encryptedPacket.keyIndex,
+ };
+ }
+
+ async handleEncryptedData(
+ payload: Uint8Array,
+ iv: Uint8Array,
+ participantIdentity: string,
+ keyIndex: number
+ ): Promise<
+ DecryptDataResponseMessage['data'] | EncryptDataResponseMessage['data']
+ > {
+ let packet = {
+ payload,
+ iv,
+ keyIndex,
+ } satisfies RTCEncryptedPacket;
+
+ let dataPacketCryptor = await this.getDataPacketCryptor();
+ let decryptedData = await dataPacketCryptor.decrypt(
+ participantIdentity,
+ packet
+ );
+
+ if (!decryptedData) {
+ throw new Error('decryption for packet failed');
+ }
+
+ return {
+ uuid: '', //not used
+ payload: decryptedData,
+ } satisfies DecryptDataResponseMessage['data'];
+ }
+
// Utility methods
//////////////////////
@@ -171,11 +292,13 @@ export default class RNE2EEManager
enabled: boolean,
participantIdentity: string
): void {
+ console.log('setParticipantCryptorEnabled', enabled, participantIdentity);
if (
this.encryptionEnabled !== enabled &&
participantIdentity === this.room?.localParticipant.identity
) {
this.encryptionEnabled = enabled;
+ console.log('setting encryption enabled to ', enabled);
this.emit(
EncryptionEvent.ParticipantEncryptionStatusChanged,
enabled,
diff --git a/src/hooks/useE2EEManager.ts b/src/hooks/useE2EEManager.ts
index 49b551e0..dbc90b9d 100644
--- a/src/hooks/useE2EEManager.ts
+++ b/src/hooks/useE2EEManager.ts
@@ -6,6 +6,7 @@ import type { RNKeyProviderOptions } from '../e2ee/RNKeyProvider';
export type UseRNE2EEManagerOptions = {
keyProviderOptions?: RNKeyProviderOptions;
sharedKey: string | Uint8Array;
+ dataChannelEncryption?: boolean;
};
export interface RNE2EEManagerState {
@@ -22,7 +23,9 @@ export function useRNE2EEManager(
let [keyProvider] = useState(
() => new RNKeyProvider(options.keyProviderOptions ?? {})
);
- let [e2eeManager] = useState(() => new RNE2EEManager(keyProvider));
+ let [e2eeManager] = useState(
+ () => new RNE2EEManager(keyProvider, options.dataChannelEncryption ?? false)
+ );
useEffect(() => {
let setup = async () => {
diff --git a/yarn.lock b/yarn.lock
index 0cdc1e5d..746712f9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2417,7 +2417,7 @@ __metadata:
languageName: node
linkType: hard
-"@livekit/mutex@npm:1.1.1":
+"@livekit/mutex@npm:1.1.1, @livekit/mutex@npm:^1.1.1":
version: 1.1.1
resolution: "@livekit/mutex@npm:1.1.1"
checksum: 44a31eb7a913357ffb57d04eaa20f7507c0a659638c6dfaba9a413c21a3397aa351497f7c77bca3c06a29ac2cbe83698a5f96b9230012a24b86ac8366e9b8666
@@ -2433,16 +2433,16 @@ __metadata:
languageName: node
linkType: hard
-"@livekit/react-native-webrtc@npm:^137.0.1":
- version: 137.0.1
- resolution: "@livekit/react-native-webrtc@npm:137.0.1"
+"@livekit/react-native-webrtc@npm:^137.0.2":
+ version: 137.0.2
+ resolution: "@livekit/react-native-webrtc@npm:137.0.2"
dependencies:
base64-js: 1.5.1
debug: 4.3.4
event-target-shim: 6.0.2
peerDependencies:
react-native: ">=0.60.0"
- checksum: 3341aafe7d9d1deb345e47247faa020b2cb5f1adbded135a244f0f9285eed720c9f62325bbce0e21c1da755ae24ae6c69eb07728cece709560f615908bde43a9
+ checksum: c23467701c7dfb74e3b223dd955b20ba1586c2186383f5b2595fff9b65a36b2db0b660de9935e009b69bc5e0bbff047d0f598495f8464daae8db5f8d9f9b7c24
languageName: node
linkType: hard
@@ -2455,7 +2455,8 @@ __metadata:
"@babel/runtime": ^7.20.0
"@commitlint/config-conventional": ^16.2.1
"@livekit/components-react": ^2.8.1
- "@livekit/react-native-webrtc": ^137.0.1
+ "@livekit/mutex": ^1.1.1
+ "@livekit/react-native-webrtc": ^137.0.2
"@react-native/babel-preset": 0.74.84
"@react-native/eslint-config": 0.74.84
"@react-native/metro-config": 0.74.84
@@ -2482,7 +2483,7 @@ __metadata:
react: 18.2.0
react-native: 0.74.2
react-native-builder-bob: ^0.18.2
- react-native-quick-base64: 2.1.1
+ react-native-quick-base64: ^2.2.1
react-native-url-polyfill: ^1.3.0
release-it: ^14.2.2
typed-emitter: ^2.1.0
@@ -2491,7 +2492,7 @@ __metadata:
web-streams-polyfill: ^4.1.0
well-known-symbols: ^4.1.0
peerDependencies:
- "@livekit/react-native-webrtc": ^137.0.1
+ "@livekit/react-native-webrtc": ^137.0.2
livekit-client: ^2.15.4
react: "*"
react-native: "*"
@@ -8800,8 +8801,8 @@ __metadata:
linkType: hard
"livekit-client@npm:^2.15.4":
- version: 2.15.4
- resolution: "livekit-client@npm:2.15.4"
+ version: 2.15.7
+ resolution: "livekit-client@npm:2.15.7"
dependencies:
"@livekit/mutex": 1.1.1
"@livekit/protocol": 1.39.3
@@ -8814,7 +8815,7 @@ __metadata:
webrtc-adapter: ^9.0.1
peerDependencies:
"@types/dom-mediacapture-record": ^1
- checksum: 7ae2e2f5326c8f7925b359277fd12ddeefb0bc5069b0563a9e184f2b860351bf91733c1fa5fd7b65467b643776368058d485af888e2771fd68c61c409b950e05
+ checksum: e9740963eef9ddb7f50604d2918b7ca9e696c1d190b968cbf2cb75997b1e0d4fce25c795ae60b1c92785cd9a679b6d606688092f31a6a1aaefc27ec92fc87f0f
languageName: node
linkType: hard
@@ -10730,15 +10731,13 @@ __metadata:
languageName: node
linkType: hard
-"react-native-quick-base64@npm:2.1.1":
- version: 2.1.1
- resolution: "react-native-quick-base64@npm:2.1.1"
- dependencies:
- base64-js: ^1.5.1
+"react-native-quick-base64@npm:^2.2.1":
+ version: 2.2.2
+ resolution: "react-native-quick-base64@npm:2.2.2"
peerDependencies:
react: "*"
react-native: "*"
- checksum: 13c374b20153418de7c89001ba9c1313aa0e6f1cbb4a829077bd8d4ed88b159e31f3be9a1ed96ac9d1c647be6fafca00aceaf59379bdf2e8ea029907f944dd4c
+ checksum: 51cd8df0b7f78d4c10996f157927cc075b8a9e85f7d0e48790a4247a033ca335e440c96572969fb6f6b49135d51dbf87c6d659fba509b51b9237957d41a6c580
languageName: node
linkType: hard