diff --git a/CHANGELOG.md b/CHANGELOG.md index e74d924..a5e5d3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog -------------------------------------------- +[1.5.3+hotfix.5] - 2025-08-11 + +* fixed E2EE bug for Chrome rejoin. + [1.5.3+hotfix.4] - 2025-07-29 * fixed E2EE issue for Chrome. diff --git a/lib/src/frame_cryptor_impl.dart b/lib/src/frame_cryptor_impl.dart index eb449e7..4445f42 100644 --- a/lib/src/frame_cryptor_impl.dart +++ b/lib/src/frame_cryptor_impl.dart @@ -14,6 +14,11 @@ import 'rtc_rtp_receiver_impl.dart'; import 'rtc_rtp_sender_impl.dart'; import 'utils.dart'; +extension type RTCInsertableStreams._(JSObject _) implements JSObject { + external web.WritableStream get writable; + external web.ReadableStream get readable; +} + class WorkerResponse { WorkerResponse(this.msgId, this.data); String msgId; @@ -22,8 +27,14 @@ class WorkerResponse { class FrameCryptorImpl extends FrameCryptor { FrameCryptorImpl( - this._factory, this.worker, this._participantId, this._trackId, - {this.jsSender, this.jsReceiver, required this.keyProvider}); + this._factory, + this.worker, + this._participantId, + this._trackId, { + this.jsSender, + this.jsReceiver, + required this.keyProvider, + }); web.Worker worker; bool _enabled = false; int _keyIndex = 0; @@ -37,11 +48,10 @@ class FrameCryptorImpl extends FrameCryptor { @override Future dispose() async { var msgId = randomString(12); - worker.postMessage({ - 'msgType': 'dispose', - 'msgId': msgId, - 'trackId': _trackId, - }.jsify()); + worker.postMessage( + {'msgType': 'dispose', 'msgId': msgId, 'trackId': _trackId}.jsify(), + ); + _enabled = false; _factory.removeFrameCryptor(_trackId); return; } @@ -60,12 +70,14 @@ class FrameCryptorImpl extends FrameCryptor { @override Future setEnabled(bool enabled) async { var msgId = randomString(12); - worker.postMessage({ - 'msgType': 'enable', - 'msgId': msgId, - 'trackId': _trackId, - 'enabled': enabled - }.jsify()); + worker.postMessage( + { + 'msgType': 'enable', + 'msgId': msgId, + 'trackId': _trackId, + 'enabled': enabled, + }.jsify(), + ); _enabled = enabled; return true; } @@ -73,12 +85,14 @@ class FrameCryptorImpl extends FrameCryptor { @override Future setKeyIndex(int index) async { var msgId = randomString(12); - worker.postMessage({ - 'msgType': 'setKeyIndex', - 'msgId': msgId, - 'trackId': _trackId, - 'index': index, - }.jsify()); + worker.postMessage( + { + 'msgType': 'setKeyIndex', + 'msgId': msgId, + 'trackId': _trackId, + 'index': index, + }.jsify(), + ); _keyIndex = index; return true; } @@ -86,12 +100,14 @@ class FrameCryptorImpl extends FrameCryptor { @override Future updateCodec(String codec) async { var msgId = randomString(12); - worker.postMessage({ - 'msgType': 'updateCodec', - 'msgId': msgId, - 'trackId': _trackId, - 'codec': codec, - }.jsify()); + worker.postMessage( + { + 'msgType': 'updateCodec', + 'msgId': msgId, + 'trackId': _trackId, + 'codec': codec, + }.jsify(), + ); } } @@ -108,64 +124,73 @@ class KeyProviderImpl implements KeyProvider { Future init() async { var msgId = randomString(12); - worker.postMessage({ - 'msgType': 'keyProviderInit', - 'msgId': msgId, - 'keyProviderId': id, - 'keyOptions': { - 'sharedKey': options.sharedKey, - 'ratchetSalt': base64Encode(options.ratchetSalt), - 'ratchetWindowSize': options.ratchetWindowSize, - 'failureTolerance': options.failureTolerance, - if (options.uncryptedMagicBytes != null) - 'uncryptedMagicBytes': base64Encode(options.uncryptedMagicBytes!), - 'keyRingSize': options.keyRingSize, - 'discardFrameWhenCryptorNotReady': - options.discardFrameWhenCryptorNotReady, - }, - }.jsify()); + worker.postMessage( + { + 'msgType': 'keyProviderInit', + 'msgId': msgId, + 'keyProviderId': id, + 'keyOptions': { + 'sharedKey': options.sharedKey, + 'ratchetSalt': base64Encode(options.ratchetSalt), + 'ratchetWindowSize': options.ratchetWindowSize, + 'failureTolerance': options.failureTolerance, + if (options.uncryptedMagicBytes != null) + 'uncryptedMagicBytes': base64Encode(options.uncryptedMagicBytes!), + 'keyRingSize': options.keyRingSize, + 'discardFrameWhenCryptorNotReady': + options.discardFrameWhenCryptorNotReady, + }, + }.jsify(), + ); await events.waitFor( - filter: (event) { - logger.fine('waiting for init on msg: $msgId'); - return event.msgId == msgId; - }, - duration: Duration(seconds: 15)); + filter: (event) { + logger.fine('waiting for init on msg: $msgId'); + return event.msgId == msgId; + }, + duration: Duration(seconds: 15), + ); } @override Future dispose() async { var msgId = randomString(12); - worker.postMessage({ - 'msgType': 'keyProviderDispose', - 'msgId': msgId, - 'keyProviderId': id, - }.jsify()); + worker.postMessage( + { + 'msgType': 'keyProviderDispose', + 'msgId': msgId, + 'keyProviderId': id, + }.jsify(), + ); await events.waitFor( - filter: (event) { - logger.fine('waiting for dispose on msg: $msgId'); - return event.msgId == msgId; - }, - duration: Duration(seconds: 15)); + filter: (event) { + logger.fine('waiting for dispose on msg: $msgId'); + return event.msgId == msgId; + }, + duration: Duration(seconds: 15), + ); _keys.clear(); } @override - Future setKey( - {required String participantId, - required int index, - required Uint8List key}) async { + Future setKey({ + required String participantId, + required int index, + required Uint8List key, + }) async { var msgId = randomString(12); - worker.postMessage({ - 'msgType': 'setKey', - 'msgId': msgId, - 'keyProviderId': id, - 'participantId': participantId, - 'keyIndex': index, - 'key': base64Encode(key), - }.jsify()); + worker.postMessage( + { + 'msgType': 'setKey', + 'msgId': msgId, + 'keyProviderId': id, + 'participantId': participantId, + 'keyIndex': index, + 'key': base64Encode(key), + }.jsify(), + ); await events.waitFor( filter: (event) { @@ -185,45 +210,55 @@ class KeyProviderImpl implements KeyProvider { } @override - Future ratchetKey( - {required String participantId, required int index}) async { + Future ratchetKey({ + required String participantId, + required int index, + }) async { var msgId = randomString(12); - worker.postMessage({ - 'msgType': 'ratchetKey', - 'msgId': msgId, - 'keyProviderId': id, - 'participantId': participantId, - 'keyIndex': index, - }.jsify()); + worker.postMessage( + { + 'msgType': 'ratchetKey', + 'msgId': msgId, + 'keyProviderId': id, + 'participantId': participantId, + 'keyIndex': index, + }.jsify(), + ); var res = await events.waitFor( - filter: (event) { - logger.fine('waiting for ratchetKey on msg: $msgId'); - return event.msgId == msgId; - }, - duration: Duration(seconds: 15)); + filter: (event) { + logger.fine('waiting for ratchetKey on msg: $msgId'); + return event.msgId == msgId; + }, + duration: Duration(seconds: 15), + ); return base64Decode(res.data['newKey']); } @override - Future exportKey( - {required String participantId, required int index}) async { + Future exportKey({ + required String participantId, + required int index, + }) async { var msgId = randomString(12); - worker.postMessage({ - 'msgType': 'exportKey', - 'msgId': msgId, - 'keyProviderId': id, - 'participantId': participantId, - 'keyIndex': index, - }.jsify()); + worker.postMessage( + { + 'msgType': 'exportKey', + 'msgId': msgId, + 'keyProviderId': id, + 'participantId': participantId, + 'keyIndex': index, + }.jsify(), + ); var res = await events.waitFor( - filter: (event) { - logger.fine('waiting for exportKey on msg: $msgId'); - return event.msgId == msgId; - }, - duration: Duration(seconds: 15)); + filter: (event) { + logger.fine('waiting for exportKey on msg: $msgId'); + return event.msgId == msgId; + }, + duration: Duration(seconds: 15), + ); return base64Decode(res.data['exportedKey']); } @@ -231,19 +266,22 @@ class KeyProviderImpl implements KeyProvider { @override Future exportSharedKey({int index = 0}) async { var msgId = randomString(12); - worker.postMessage({ - 'msgType': 'exportSharedKey', - 'msgId': msgId, - 'keyProviderId': id, - 'keyIndex': index, - }.jsify()); + worker.postMessage( + { + 'msgType': 'exportSharedKey', + 'msgId': msgId, + 'keyProviderId': id, + 'keyIndex': index, + }.jsify(), + ); var res = await events.waitFor( - filter: (event) { - logger.fine('waiting for exportSharedKey on msg: $msgId'); - return event.msgId == msgId; - }, - duration: Duration(seconds: 15)); + filter: (event) { + logger.fine('waiting for exportSharedKey on msg: $msgId'); + return event.msgId == msgId; + }, + duration: Duration(seconds: 15), + ); return base64Decode(res.data['exportedKey']); } @@ -251,18 +289,21 @@ class KeyProviderImpl implements KeyProvider { @override Future ratchetSharedKey({int index = 0}) async { var msgId = randomString(12); - worker.postMessage({ - 'msgType': 'ratchetSharedKey', - 'msgId': msgId, - 'keyProviderId': id, - 'keyIndex': index, - }.jsify()); + worker.postMessage( + { + 'msgType': 'ratchetSharedKey', + 'msgId': msgId, + 'keyProviderId': id, + 'keyIndex': index, + }.jsify(), + ); var res = await events.waitFor( - filter: (event) { - logger.fine('waiting for ratchetSharedKey on msg: $msgId'); - return event.msgId == msgId; - }, - duration: Duration(seconds: 15)); + filter: (event) { + logger.fine('waiting for ratchetSharedKey on msg: $msgId'); + return event.msgId == msgId; + }, + duration: Duration(seconds: 15), + ); return base64Decode(res.data['newKey']); } @@ -270,38 +311,44 @@ class KeyProviderImpl implements KeyProvider { @override Future setSharedKey({required Uint8List key, int index = 0}) async { var msgId = randomString(12); - worker.postMessage({ - 'msgType': 'setSharedKey', - 'msgId': msgId, - 'keyProviderId': id, - 'keyIndex': index, - 'key': base64Encode(key), - }.jsify()); + worker.postMessage( + { + 'msgType': 'setSharedKey', + 'msgId': msgId, + 'keyProviderId': id, + 'keyIndex': index, + 'key': base64Encode(key), + }.jsify(), + ); await events.waitFor( - filter: (event) { - logger.fine('waiting for setSharedKey on msg: $msgId'); - return event.msgId == msgId; - }, - duration: Duration(seconds: 15)); + filter: (event) { + logger.fine('waiting for setSharedKey on msg: $msgId'); + return event.msgId == msgId; + }, + duration: Duration(seconds: 15), + ); } @override Future setSifTrailer({required Uint8List trailer}) async { var msgId = randomString(12); - worker.postMessage({ - 'msgType': 'setSifTrailer', - 'msgId': msgId, - 'keyProviderId': id, - 'sifTrailer': base64Encode(trailer), - }.jsify()); + worker.postMessage( + { + 'msgType': 'setSifTrailer', + 'msgId': msgId, + 'keyProviderId': id, + 'sifTrailer': base64Encode(trailer), + }.jsify(), + ); await events.waitFor( - filter: (event) { - logger.fine('waiting for setSifTrailer on msg: $msgId'); - return event.msgId == msgId; - }, - duration: Duration(seconds: 15)); + filter: (event) { + logger.fine('waiting for setSifTrailer on msg: $msgId'); + return event.msgId == msgId; + }, + duration: Duration(seconds: 15), + ); } } @@ -323,7 +370,8 @@ class FrameCryptorFactoryImpl implements FrameCryptorFactory { var trackId = data['trackId']; var participantId = data['participantId']; var frameCryptor = _frameCryptors.values.firstWhereOrNull( - (element) => (element as FrameCryptorImpl).trackId == trackId); + (element) => (element as FrameCryptorImpl).trackId == trackId, + ); var state = data['state']; var frameCryptorState = FrameCryptorState.FrameCryptorStateNew; switch (state) { @@ -350,8 +398,10 @@ class FrameCryptorFactoryImpl implements FrameCryptorFactory { FrameCryptorState.FrameCryptorStateKeyRatcheted; break; } - frameCryptor?.onFrameCryptorStateChanged - ?.call(participantId, frameCryptorState); + frameCryptor?.onFrameCryptorStateChanged?.call( + participantId, + frameCryptorState, + ); } } }; @@ -373,19 +423,25 @@ class FrameCryptorFactoryImpl implements FrameCryptorFactory { @override Future createDefaultKeyProvider( - KeyProviderOptions options) async { - var keyProvider = - KeyProviderImpl(randomString(12), worker, options, events); + KeyProviderOptions options, + ) async { + var keyProvider = KeyProviderImpl( + randomString(12), + worker, + options, + events, + ); await keyProvider.init(); return keyProvider; } @override - Future createFrameCryptorForRtpReceiver( - {required String participantId, - required RTCRtpReceiver receiver, - required Algorithm algorithm, - required KeyProvider keyProvider}) { + Future createFrameCryptorForRtpReceiver({ + required String participantId, + required RTCRtpReceiver receiver, + required Algorithm algorithm, + required KeyProvider keyProvider, + }) async { var jsReceiver = (receiver as RTCRtpReceiverWeb).jsRtpReceiver; var trackId = jsReceiver.track.id; @@ -405,15 +461,22 @@ class FrameCryptorFactoryImpl implements FrameCryptorFactory { jsReceiver.transform = web.RTCRtpScriptTransform(worker, options.jsify()); } else { - var exist = false; - final streams = - jsReceiver.callMethod('createEncodedStreams'.toJS); - final readable = - streams.getProperty('readable'.toJS) as web.ReadableStream; - final writable = - streams.getProperty('writable'.toJS) as web.WritableStream; + RTCInsertableStreams? insertableStreams = jsReceiver + .getProperty('insertableStreams'.toJS) as RTCInsertableStreams?; + + var exist = insertableStreams != null; + if (insertableStreams == null) { + insertableStreams = jsReceiver.callMethod( + 'createEncodedStreams'.toJS, + ); + jsReceiver.setProperty('insertableStreams'.toJS, insertableStreams); + } + + var readable = insertableStreams!.readable; + var writable = insertableStreams!.writable; var msgId = randomString(12); + try { worker.postMessage( { @@ -426,7 +489,7 @@ class FrameCryptorFactoryImpl implements FrameCryptorFactory { 'trackId': trackId, 'options': keyProvider.options.toJson(), 'readableStream': readable, - 'writableStream': writable + 'writableStream': writable, }.jsify(), [readable, writable] as JSObject, ); @@ -436,18 +499,24 @@ class FrameCryptorFactoryImpl implements FrameCryptorFactory { } } FrameCryptor cryptor = FrameCryptorImpl( - this, worker, participantId, trackId, - jsReceiver: jsReceiver, keyProvider: keyProvider); + this, + worker, + participantId, + trackId, + jsReceiver: jsReceiver, + keyProvider: keyProvider, + ); _frameCryptors[trackId] = cryptor; return Future.value(cryptor); } @override - Future createFrameCryptorForRtpSender( - {required String participantId, - required RTCRtpSender sender, - required Algorithm algorithm, - required KeyProvider keyProvider}) { + Future createFrameCryptorForRtpSender({ + required String participantId, + required RTCRtpSender sender, + required Algorithm algorithm, + required KeyProvider keyProvider, + }) { var jsSender = (sender as RTCRtpSenderWeb).jsRtpSender; var trackId = jsSender.track?.id ?? sender.senderId; var kind = jsSender.track!.kind; @@ -467,16 +536,22 @@ class FrameCryptorFactoryImpl implements FrameCryptorFactory { print('object: ${options['keyProviderId']}'); jsSender.transform = web.RTCRtpScriptTransform(worker, options.jsify()); } else { - var exist = false; - final streams = - jsSender.callMethod('createEncodedStreams'.toJS); - final readable = - streams.getProperty('readable'.toJS) as web.ReadableStream; - final writable = - streams.getProperty('writable'.toJS) as web.WritableStream; + RTCInsertableStreams? insertableStreams = jsSender + .getProperty('insertableStreams'.toJS) as RTCInsertableStreams?; - var msgId = randomString(12); + var exist = insertableStreams != null; + + if (insertableStreams == null) { + insertableStreams = jsSender.callMethod( + 'createEncodedStreams'.toJS, + ); + jsSender.setProperty('insertableStreams'.toJS, insertableStreams); + } + + var readable = insertableStreams!.readable; + var writable = insertableStreams!.writable; + var msgId = randomString(12); try { worker.postMessage( { @@ -489,7 +564,7 @@ class FrameCryptorFactoryImpl implements FrameCryptorFactory { 'trackId': trackId, 'options': keyProvider.options.toJson(), 'readableStream': readable, - 'writableStream': writable + 'writableStream': writable, }.jsify(), [readable, writable] as JSObject, ); @@ -499,8 +574,13 @@ class FrameCryptorFactoryImpl implements FrameCryptorFactory { } } FrameCryptor cryptor = FrameCryptorImpl( - this, worker, participantId, trackId, - jsSender: jsSender, keyProvider: keyProvider); + this, + worker, + participantId, + trackId, + jsSender: jsSender, + keyProvider: keyProvider, + ); _frameCryptors[trackId] = cryptor; return Future.value(cryptor); diff --git a/pubspec.yaml b/pubspec.yaml index 835c297..eac0c1c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: dart_webrtc description: Use the dart/js library to re-wrap the webrtc js interface of the browser, to adapted common browsers. -version: 1.5.3+hotfix.4 +version: 1.5.3+hotfix.5 homepage: https://github.com/flutter-webrtc/dart-webrtc environment: