@livekit/react-native 2.4.3 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/android/local.properties +8 -0
- package/lib/commonjs/e2ee/RNE2EEManager.js +115 -0
- package/lib/commonjs/e2ee/RNE2EEManager.js.map +1 -0
- package/lib/commonjs/e2ee/RNKeyProvider.js +104 -0
- package/lib/commonjs/e2ee/RNKeyProvider.js.map +1 -0
- package/lib/commonjs/hooks/useE2EEManager.js +38 -0
- package/lib/commonjs/hooks/useE2EEManager.js.map +1 -0
- package/lib/commonjs/hooks.js +42 -0
- package/lib/commonjs/hooks.js.map +1 -1
- package/lib/commonjs/index.js +18 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/e2ee/RNE2EEManager.js +107 -0
- package/lib/module/e2ee/RNE2EEManager.js.map +1 -0
- package/lib/module/e2ee/RNKeyProvider.js +97 -0
- package/lib/module/e2ee/RNKeyProvider.js.map +1 -0
- package/lib/module/hooks/useE2EEManager.js +31 -0
- package/lib/module/hooks/useE2EEManager.js.map +1 -0
- package/lib/module/hooks.js +1 -0
- package/lib/module/hooks.js.map +1 -1
- package/lib/module/index.js +4 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/lib/commonjs/e2ee/RNE2EEManager.d.ts +23 -0
- package/lib/typescript/lib/commonjs/e2ee/RNKeyProvider.d.ts +33 -0
- package/lib/typescript/lib/commonjs/hooks/useE2EEManager.d.ts +8 -0
- package/lib/typescript/lib/commonjs/index.d.ts +2 -0
- package/lib/typescript/lib/module/e2ee/RNE2EEManager.d.ts +21 -0
- package/lib/typescript/lib/module/e2ee/RNKeyProvider.d.ts +30 -0
- package/lib/typescript/lib/module/hooks/useE2EEManager.d.ts +9 -0
- package/lib/typescript/lib/module/hooks.d.ts +1 -0
- package/lib/typescript/lib/module/index.d.ts +3 -1
- package/lib/typescript/src/e2ee/RNE2EEManager.d.ts +27 -0
- package/lib/typescript/src/e2ee/RNKeyProvider.d.ts +35 -0
- package/lib/typescript/src/hooks/useE2EEManager.d.ts +15 -0
- package/lib/typescript/src/hooks.d.ts +2 -0
- package/lib/typescript/src/index.d.ts +4 -2
- package/package.json +6 -4
- package/src/e2ee/RNE2EEManager.ts +199 -0
- package/src/e2ee/RNKeyProvider.ts +116 -0
- package/src/hooks/useE2EEManager.ts +49 -0
- package/src/hooks.ts +2 -0
- package/src/index.tsx +5 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RTCFrameCryptorAlgorithm,
|
|
3
|
+
RTCFrameCryptorFactory,
|
|
4
|
+
RTCRtpReceiver,
|
|
5
|
+
type RTCFrameCryptor,
|
|
6
|
+
type RTCRtpSender,
|
|
7
|
+
} from '@livekit/react-native-webrtc';
|
|
8
|
+
import {
|
|
9
|
+
LocalParticipant,
|
|
10
|
+
LocalTrackPublication,
|
|
11
|
+
ParticipantEvent,
|
|
12
|
+
RemoteParticipant,
|
|
13
|
+
RemoteTrackPublication,
|
|
14
|
+
RoomEvent,
|
|
15
|
+
type Room,
|
|
16
|
+
type BaseE2EEManager,
|
|
17
|
+
type E2EEManagerCallbacks,
|
|
18
|
+
EncryptionEvent,
|
|
19
|
+
} from 'livekit-client';
|
|
20
|
+
import type RNKeyProvider from './RNKeyProvider';
|
|
21
|
+
import type RTCEngine from 'livekit-client/dist/src/room/RTCEngine';
|
|
22
|
+
import EventEmitter from 'events';
|
|
23
|
+
import type TypedEventEmitter from 'typed-emitter';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @experimental
|
|
27
|
+
*/
|
|
28
|
+
export default class RNE2EEManager
|
|
29
|
+
extends (EventEmitter as new () => TypedEventEmitter<E2EEManagerCallbacks>)
|
|
30
|
+
implements BaseE2EEManager
|
|
31
|
+
{
|
|
32
|
+
private room?: Room;
|
|
33
|
+
private frameCryptors: Map<string, RTCFrameCryptor> = new Map();
|
|
34
|
+
private keyProvider: RNKeyProvider;
|
|
35
|
+
private algorithm: RTCFrameCryptorAlgorithm =
|
|
36
|
+
RTCFrameCryptorAlgorithm.kAesGcm;
|
|
37
|
+
|
|
38
|
+
private encryptionEnabled: boolean = false;
|
|
39
|
+
|
|
40
|
+
constructor(keyProvider: RNKeyProvider) {
|
|
41
|
+
super();
|
|
42
|
+
this.keyProvider = keyProvider;
|
|
43
|
+
this.encryptionEnabled = false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setup(room: Room) {
|
|
47
|
+
if (this.room !== room) {
|
|
48
|
+
this.room = room;
|
|
49
|
+
this.setupEventListeners(room);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private setupEventListeners(room: Room) {
|
|
54
|
+
room.localParticipant
|
|
55
|
+
.on(ParticipantEvent.LocalTrackPublished, async (publication) => {
|
|
56
|
+
this.setupE2EESender(publication, room.localParticipant);
|
|
57
|
+
})
|
|
58
|
+
.on(ParticipantEvent.LocalTrackUnpublished, async (publication) => {
|
|
59
|
+
let frameCryptor = this.findTrackCryptor(publication.trackSid);
|
|
60
|
+
if (frameCryptor) {
|
|
61
|
+
this.frameCryptors.delete(publication.trackSid);
|
|
62
|
+
await frameCryptor.setEnabled(false);
|
|
63
|
+
await frameCryptor.dispose();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
room
|
|
68
|
+
.on(RoomEvent.TrackSubscribed, (_track, pub, participant) => {
|
|
69
|
+
this.setupE2EEReceiver(pub, participant);
|
|
70
|
+
})
|
|
71
|
+
.on(
|
|
72
|
+
RoomEvent.TrackUnsubscribed,
|
|
73
|
+
async (_track, publication, _participant) => {
|
|
74
|
+
let frameCryptor = this.findTrackCryptor(publication.trackSid);
|
|
75
|
+
if (frameCryptor) {
|
|
76
|
+
this.frameCryptors.delete(publication.trackSid);
|
|
77
|
+
await frameCryptor.setEnabled(false);
|
|
78
|
+
await frameCryptor.dispose();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private async setupE2EESender(
|
|
85
|
+
publication: LocalTrackPublication,
|
|
86
|
+
participant: LocalParticipant
|
|
87
|
+
) {
|
|
88
|
+
if (!publication.isEncrypted) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
var frameCryptor = this.findTrackCryptor(publication.trackSid);
|
|
93
|
+
|
|
94
|
+
if (!frameCryptor) {
|
|
95
|
+
frameCryptor = this.createFrameCryptorForSender(
|
|
96
|
+
publication.track!.sender,
|
|
97
|
+
participant.identity
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
this.frameCryptors.set(publication.trackSid, frameCryptor);
|
|
101
|
+
frameCryptor.setEnabled(true);
|
|
102
|
+
frameCryptor.setKeyIndex(
|
|
103
|
+
this.keyProvider.getLatestKeyIndex(participant.identity)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private async setupE2EEReceiver(
|
|
109
|
+
publication: RemoteTrackPublication,
|
|
110
|
+
participant: RemoteParticipant
|
|
111
|
+
) {
|
|
112
|
+
if (!publication.isEncrypted) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
var frameCryptor = this.findTrackCryptor(publication.trackSid);
|
|
117
|
+
|
|
118
|
+
if (!frameCryptor) {
|
|
119
|
+
frameCryptor = this.createFrameCryptorForReceiver(
|
|
120
|
+
publication.track!.receiver,
|
|
121
|
+
participant.identity
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
this.frameCryptors.set(publication.trackSid, frameCryptor);
|
|
125
|
+
frameCryptor.setEnabled(true);
|
|
126
|
+
frameCryptor.setKeyIndex(
|
|
127
|
+
this.keyProvider.getLatestKeyIndex(participant.identity)
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
setSifTrailer(trailer: Uint8Array): void {
|
|
133
|
+
this.keyProvider.setSifTrailer(trailer);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Utility methods
|
|
137
|
+
//////////////////////
|
|
138
|
+
|
|
139
|
+
private findTrackCryptor(trackId: string): RTCFrameCryptor | undefined {
|
|
140
|
+
return this.frameCryptors.get(trackId);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private createFrameCryptorForSender(
|
|
144
|
+
sender: RTCRtpSender,
|
|
145
|
+
participantId: string
|
|
146
|
+
): RTCFrameCryptor {
|
|
147
|
+
return RTCFrameCryptorFactory.createFrameCryptorForRtpSender(
|
|
148
|
+
participantId,
|
|
149
|
+
sender,
|
|
150
|
+
this.algorithm,
|
|
151
|
+
this.keyProvider.rtcKeyProvider
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private createFrameCryptorForReceiver(
|
|
156
|
+
receiver: RTCRtpReceiver,
|
|
157
|
+
participantId: string
|
|
158
|
+
): RTCFrameCryptor {
|
|
159
|
+
return RTCFrameCryptorFactory.createFrameCryptorForRtpReceiver(
|
|
160
|
+
participantId,
|
|
161
|
+
receiver,
|
|
162
|
+
this.algorithm,
|
|
163
|
+
this.keyProvider.rtcKeyProvider
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
setupEngine(_engine: RTCEngine): void {
|
|
168
|
+
// No-op
|
|
169
|
+
}
|
|
170
|
+
setParticipantCryptorEnabled(
|
|
171
|
+
enabled: boolean,
|
|
172
|
+
participantIdentity: string
|
|
173
|
+
): void {
|
|
174
|
+
if (
|
|
175
|
+
this.encryptionEnabled !== enabled &&
|
|
176
|
+
participantIdentity === this.room?.localParticipant.identity
|
|
177
|
+
) {
|
|
178
|
+
this.encryptionEnabled = enabled;
|
|
179
|
+
this.emit(
|
|
180
|
+
EncryptionEvent.ParticipantEncryptionStatusChanged,
|
|
181
|
+
enabled,
|
|
182
|
+
this.room!.localParticipant
|
|
183
|
+
);
|
|
184
|
+
} else {
|
|
185
|
+
const participant =
|
|
186
|
+
this.room?.getParticipantByIdentity(participantIdentity);
|
|
187
|
+
if (!participant) {
|
|
188
|
+
throw TypeError(
|
|
189
|
+
`couldn't set encryption status, participant not found ${participantIdentity}`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
this.emit(
|
|
193
|
+
EncryptionEvent.ParticipantEncryptionStatusChanged,
|
|
194
|
+
enabled,
|
|
195
|
+
participant
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { BaseKeyProvider, type KeyProviderOptions } from 'livekit-client';
|
|
2
|
+
import {
|
|
3
|
+
RTCFrameCryptorFactory,
|
|
4
|
+
RTCKeyProvider,
|
|
5
|
+
type RTCKeyProviderOptions,
|
|
6
|
+
} from '@livekit/react-native-webrtc';
|
|
7
|
+
|
|
8
|
+
const defaultRatchetSalt = 'LKFrameEncryptionKey';
|
|
9
|
+
const defaultMagicBytes = 'LK-ROCKS';
|
|
10
|
+
const defaultRatchetWindowSize = 16;
|
|
11
|
+
const defaultFailureTolerance = -1;
|
|
12
|
+
const defaultKeyRingSize = 16;
|
|
13
|
+
const defaultDiscardFrameWhenCryptorNotReady = false;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Options for construction an RNKeyProvider
|
|
17
|
+
*/
|
|
18
|
+
export type RNKeyProviderOptions = KeyProviderOptions & {
|
|
19
|
+
uncryptedMagicBytes?: string | Uint8Array;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @experimental
|
|
24
|
+
*/
|
|
25
|
+
export default class RNKeyProvider extends BaseKeyProvider {
|
|
26
|
+
private latestSetIndex: Map<string, number> = new Map();
|
|
27
|
+
private nativeKeyProvider: RTCKeyProvider;
|
|
28
|
+
|
|
29
|
+
constructor(options: Partial<RNKeyProviderOptions>) {
|
|
30
|
+
const opts: RTCKeyProviderOptions & KeyProviderOptions = {
|
|
31
|
+
sharedKey: options.sharedKey ?? true,
|
|
32
|
+
ratchetSalt: options.ratchetSalt ?? defaultRatchetSalt,
|
|
33
|
+
ratchetWindowSize: options.ratchetWindowSize ?? defaultRatchetWindowSize,
|
|
34
|
+
failureTolerance: options.failureTolerance ?? defaultFailureTolerance,
|
|
35
|
+
keyRingSize: options.keyringSize ?? defaultKeyRingSize,
|
|
36
|
+
keyringSize: options.keyringSize ?? defaultKeyRingSize,
|
|
37
|
+
discardFrameWhenCryptorNotReady: defaultDiscardFrameWhenCryptorNotReady,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
let magicBytes = options.uncryptedMagicBytes ?? defaultMagicBytes;
|
|
41
|
+
if (typeof magicBytes === 'string') {
|
|
42
|
+
magicBytes = new TextEncoder().encode(magicBytes);
|
|
43
|
+
}
|
|
44
|
+
opts.uncryptedMagicBytes = magicBytes;
|
|
45
|
+
|
|
46
|
+
super(opts);
|
|
47
|
+
|
|
48
|
+
this.nativeKeyProvider =
|
|
49
|
+
RTCFrameCryptorFactory.createDefaultKeyProvider(opts);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getLatestKeyIndex(participantId: string) {
|
|
53
|
+
return this.latestSetIndex.get(participantId) ?? 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Accepts a passphrase that's used to create the crypto keys.
|
|
58
|
+
* @param key
|
|
59
|
+
*/
|
|
60
|
+
async setSharedKey(key: string | Uint8Array, keyIndex?: number) {
|
|
61
|
+
return this.nativeKeyProvider.setSharedKey(key, keyIndex);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async ratchetSharedKey(keyIndex?: number) {
|
|
65
|
+
this.nativeKeyProvider.ratchetSharedKey(keyIndex);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Accepts a passphrase that's used to create the crypto keys for a participant's stream.
|
|
70
|
+
* @param key
|
|
71
|
+
*/
|
|
72
|
+
async setKey(
|
|
73
|
+
participantId: string,
|
|
74
|
+
key: string | Uint8Array,
|
|
75
|
+
keyIndex?: number
|
|
76
|
+
) {
|
|
77
|
+
if (this.getOptions().sharedKey) {
|
|
78
|
+
return this.setSharedKey(key, keyIndex);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.latestSetIndex.set(participantId, keyIndex ?? 0);
|
|
82
|
+
return this.nativeKeyProvider.setKey(participantId, key, keyIndex);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
override async ratchetKey(participantIdentity?: string, keyIndex?: number) {
|
|
86
|
+
if (!this.getOptions().sharedKey && participantIdentity) {
|
|
87
|
+
this.nativeKeyProvider.ratchetKey(participantIdentity, keyIndex);
|
|
88
|
+
} else {
|
|
89
|
+
this.ratchetSharedKey(keyIndex);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async setSifTrailer(trailer: Uint8Array) {
|
|
94
|
+
return this.nativeKeyProvider.setSifTrailer(trailer);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
get rtcKeyProvider() {
|
|
101
|
+
return this.nativeKeyProvider;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
dispose() {
|
|
105
|
+
this.nativeKeyProvider.dispose();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// /**
|
|
110
|
+
// * Define the `onxxx` event handlers.
|
|
111
|
+
// */
|
|
112
|
+
// const proto = RNExternalE2EEKeyProvider.prototype;
|
|
113
|
+
|
|
114
|
+
// defineEventAttribute(proto, KeyProviderEvent.SetKey);
|
|
115
|
+
// defineEventAttribute(proto, KeyProviderEvent.RatchetRequest);
|
|
116
|
+
// defineEventAttribute(proto, KeyProviderEvent.KeyRatcheted);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import RNE2EEManager from '../e2ee/RNE2EEManager';
|
|
2
|
+
import { log, RNKeyProvider } from '..';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import type { RNKeyProviderOptions } from '../e2ee/RNKeyProvider';
|
|
5
|
+
|
|
6
|
+
export type UseRNE2EEManagerOptions = {
|
|
7
|
+
keyProviderOptions?: RNKeyProviderOptions;
|
|
8
|
+
sharedKey: string | Uint8Array;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export interface RNE2EEManagerState {
|
|
12
|
+
keyProvider: RNKeyProvider;
|
|
13
|
+
e2eeManager: RNE2EEManager;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @experimental
|
|
18
|
+
*/
|
|
19
|
+
export function useRNE2EEManager(
|
|
20
|
+
options: UseRNE2EEManagerOptions
|
|
21
|
+
): RNE2EEManagerState {
|
|
22
|
+
let [keyProvider] = useState(
|
|
23
|
+
() => new RNKeyProvider(options.keyProviderOptions ?? {})
|
|
24
|
+
);
|
|
25
|
+
let [e2eeManager] = useState(() => new RNE2EEManager(keyProvider));
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
let setup = async () => {
|
|
29
|
+
try {
|
|
30
|
+
await keyProvider.setSharedKey(options.sharedKey);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
log.warn('unable to set shared key', error);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
setup();
|
|
36
|
+
return () => {};
|
|
37
|
+
}, [keyProvider, options.sharedKey]);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
return () => {
|
|
41
|
+
keyProvider.dispose();
|
|
42
|
+
};
|
|
43
|
+
}, [keyProvider]);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
keyProvider,
|
|
47
|
+
e2eeManager,
|
|
48
|
+
};
|
|
49
|
+
}
|
package/src/hooks.ts
CHANGED
package/src/index.tsx
CHANGED
|
@@ -15,6 +15,8 @@ import type { AudioConfiguration } from './audio/AudioSession';
|
|
|
15
15
|
import { PixelRatio, Platform } from 'react-native';
|
|
16
16
|
import { type LiveKitReactNativeInfo } from 'livekit-client';
|
|
17
17
|
import type { LogLevel, SetLogLevelOptions } from './logger';
|
|
18
|
+
import RNE2EEManager from './e2ee/RNE2EEManager';
|
|
19
|
+
import RNKeyProvider, { type RNKeyProviderOptions } from './e2ee/RNKeyProvider';
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Registers the required globals needed for LiveKit to work.
|
|
@@ -107,6 +109,8 @@ export * from './audio/AudioManager';
|
|
|
107
109
|
|
|
108
110
|
export {
|
|
109
111
|
AudioSession,
|
|
112
|
+
RNE2EEManager,
|
|
113
|
+
RNKeyProvider,
|
|
110
114
|
AndroidAudioTypePresets,
|
|
111
115
|
getDefaultAppleAudioConfigurationForMode,
|
|
112
116
|
};
|
|
@@ -120,4 +124,5 @@ export type {
|
|
|
120
124
|
AudioTrackState,
|
|
121
125
|
LogLevel,
|
|
122
126
|
SetLogLevelOptions,
|
|
127
|
+
RNKeyProviderOptions,
|
|
123
128
|
};
|