diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fdbf986..6bd6e3e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 0.5.9 +* Added support for `randomUUID`. + # 0.5.8 * All classes previously annotated `@sealed` are now `final`! * Migrate from Gradle Imperative Apply to [Gradle Plugin DSL](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply). diff --git a/lib/src/crypto_subtle.dart b/lib/src/crypto_subtle.dart index 672fcdaf..7efbf982 100644 --- a/lib/src/crypto_subtle.dart +++ b/lib/src/crypto_subtle.dart @@ -64,6 +64,9 @@ extension type JSCrypto(JSObject _) implements JSObject { /// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues external JSTypedArray getRandomValues(JSTypedArray array); + + /// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID + external JSString randomUUID(); } /// The `window.crypto.subtle` object. @@ -400,6 +403,10 @@ TypedData getRandomValues(TypedData array) { return array; } +String randomUUID() { + return window.crypto.randomUUID().toDart; +} + Future decrypt( Algorithm algorithm, JSCryptoKey key, diff --git a/lib/src/impl_ffi/impl_ffi.random.dart b/lib/src/impl_ffi/impl_ffi.random.dart index 569e9ef6..40b7c393 100644 --- a/lib/src/impl_ffi/impl_ffi.random.dart +++ b/lib/src/impl_ffi/impl_ffi.random.dart @@ -30,4 +30,28 @@ final class _RandomImpl implements RandomImpl { dest.setAll(0, out.asTypedList(dest.length)); }); } + + @override + String randomUUID() { + return _Scope.sync((scope) { + final out = scope(16); + _checkOp(ssl.RAND_bytes(out, 16) == 1); + + var bytes = out.asTypedList(16); + + // Set the 4 most significant bits of bytes[6] to 0100. + bytes[6] = (bytes[6] & 0x0F) | 0x40; + + // Set the 2 most significant bits of bytes[8] to 10. + bytes[8] = (bytes[8] & 0x3F) | 0x80; + + return bytes + .map((e) => e.toRadixString(16).padLeft(2, '0')) + .join() + .replaceAllMapped( + RegExp(r'(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})'), + (m) => '${m[1]}-${m[2]}-${m[3]}-${m[4]}-${m[5]}', + ); + }); + } } diff --git a/lib/src/impl_interface/impl_interface.random.dart b/lib/src/impl_interface/impl_interface.random.dart index f6c0e9a9..19905582 100644 --- a/lib/src/impl_interface/impl_interface.random.dart +++ b/lib/src/impl_interface/impl_interface.random.dart @@ -16,4 +16,5 @@ part of 'impl_interface.dart'; abstract interface class RandomImpl { void fillRandomBytes(TypedData destination); + String randomUUID(); } diff --git a/lib/src/impl_js/impl_js.random.dart b/lib/src/impl_js/impl_js.random.dart index a5355b33..d8d31cce 100644 --- a/lib/src/impl_js/impl_js.random.dart +++ b/lib/src/impl_js/impl_js.random.dart @@ -32,4 +32,18 @@ final class _RandomImpl implements RandomImpl { throw _translateJavaScriptException(); } } + + @override + String randomUUID() { + try { + return subtle.randomUUID(); + } on Error catch (e) { + final errorName = e.toString(); + if (errorName != 'JavaScriptError') { + rethrow; + } + + throw _translateJavaScriptException(); + } + } } diff --git a/lib/src/impl_stub/impl_stub.random.dart b/lib/src/impl_stub/impl_stub.random.dart index c7f13fa8..0abc0725 100644 --- a/lib/src/impl_stub/impl_stub.random.dart +++ b/lib/src/impl_stub/impl_stub.random.dart @@ -21,4 +21,9 @@ final class _RandomImpl implements RandomImpl { void fillRandomBytes(TypedData destination) { throw UnimplementedError('Not implemented'); } + + @override + String randomUUID() { + throw UnimplementedError('Not implemented'); + } } diff --git a/lib/src/testing/webcrypto/random.dart b/lib/src/testing/webcrypto/random.dart index c1e22a8e..96db1bf0 100644 --- a/lib/src/testing/webcrypto/random.dart +++ b/lib/src/testing/webcrypto/random.dart @@ -24,6 +24,14 @@ void isNotAllZero(TypedData data) { check(data.buffer.asUint8List().any((b) => b != 0)); } +void isValidUUID(String uuid) { + check(uuid.length == 36); + check(uuid[8] == '-'); + check(uuid[13] == '-'); + check(uuid[18] == '-'); + check(uuid[23] == '-'); +} + void main() => tests().runTests(); /// Tests, exported for use in `../run_all_tests.dart`. @@ -83,5 +91,16 @@ List<({String name, Future Function() test})> tests() { isNotAllZero(data); }); + test('randomUUID: 100 UUIDs', () async { + final uuids = {}; + for (var i = 0; i < 100; i++) { + final uuid = randomUUID(); + isValidUUID(uuid); + check(uuids.add(uuid)); + } + + check(uuids.length == 100); + }); + return tests; } diff --git a/lib/src/webcrypto/webcrypto.random.dart b/lib/src/webcrypto/webcrypto.random.dart index 57a4d86b..71563455 100644 --- a/lib/src/webcrypto/webcrypto.random.dart +++ b/lib/src/webcrypto/webcrypto.random.dart @@ -48,3 +48,18 @@ void fillRandomBytes( webCryptImpl.random.fillRandomBytes(destination); } + +/// Generate a cryptographically random UUID. +/// +/// **Example** +/// ```dart +/// import 'package:webcrypto/webcrypto.dart'; +/// +/// // Generate a random UUID. +/// void main() { +/// final uuid = randomUUID(); +/// print(uuid); +/// } +/// +/// ``` +String randomUUID() => webCryptImpl.random.randomUUID();