@livekit/react-native 2.4.3 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +7 -7
  2. package/android/local.properties +8 -0
  3. package/lib/commonjs/e2ee/RNE2EEManager.js +115 -0
  4. package/lib/commonjs/e2ee/RNE2EEManager.js.map +1 -0
  5. package/lib/commonjs/e2ee/RNKeyProvider.js +104 -0
  6. package/lib/commonjs/e2ee/RNKeyProvider.js.map +1 -0
  7. package/lib/commonjs/hooks/useE2EEManager.js +38 -0
  8. package/lib/commonjs/hooks/useE2EEManager.js.map +1 -0
  9. package/lib/commonjs/hooks.js +42 -0
  10. package/lib/commonjs/hooks.js.map +1 -1
  11. package/lib/commonjs/index.js +18 -1
  12. package/lib/commonjs/index.js.map +1 -1
  13. package/lib/module/e2ee/RNE2EEManager.js +107 -0
  14. package/lib/module/e2ee/RNE2EEManager.js.map +1 -0
  15. package/lib/module/e2ee/RNKeyProvider.js +97 -0
  16. package/lib/module/e2ee/RNKeyProvider.js.map +1 -0
  17. package/lib/module/hooks/useE2EEManager.js +31 -0
  18. package/lib/module/hooks/useE2EEManager.js.map +1 -0
  19. package/lib/module/hooks.js +1 -0
  20. package/lib/module/hooks.js.map +1 -1
  21. package/lib/module/index.js +4 -1
  22. package/lib/module/index.js.map +1 -1
  23. package/lib/typescript/lib/commonjs/e2ee/RNE2EEManager.d.ts +23 -0
  24. package/lib/typescript/lib/commonjs/e2ee/RNKeyProvider.d.ts +33 -0
  25. package/lib/typescript/lib/commonjs/hooks/useE2EEManager.d.ts +8 -0
  26. package/lib/typescript/lib/commonjs/index.d.ts +2 -0
  27. package/lib/typescript/lib/module/e2ee/RNE2EEManager.d.ts +21 -0
  28. package/lib/typescript/lib/module/e2ee/RNKeyProvider.d.ts +30 -0
  29. package/lib/typescript/lib/module/hooks/useE2EEManager.d.ts +9 -0
  30. package/lib/typescript/lib/module/hooks.d.ts +1 -0
  31. package/lib/typescript/lib/module/index.d.ts +3 -1
  32. package/lib/typescript/src/e2ee/RNE2EEManager.d.ts +27 -0
  33. package/lib/typescript/src/e2ee/RNKeyProvider.d.ts +35 -0
  34. package/lib/typescript/src/hooks/useE2EEManager.d.ts +15 -0
  35. package/lib/typescript/src/hooks.d.ts +2 -0
  36. package/lib/typescript/src/index.d.ts +4 -2
  37. package/package.json +5 -4
  38. package/src/e2ee/RNE2EEManager.ts +199 -0
  39. package/src/e2ee/RNKeyProvider.ts +116 -0
  40. package/src/hooks/useE2EEManager.ts +49 -0
  41. package/src/hooks.ts +2 -0
  42. 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
@@ -41,3 +41,5 @@ export type {
41
41
  } from '@livekit/components-react';
42
42
 
43
43
  export type { ReceivedDataMessage } from '@livekit/components-core';
44
+ export * from './hooks/useE2EEManager';
45
+ export type { UseRNE2EEManagerOptions } from './hooks/useE2EEManager';
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
  };