Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update js dependency and migrate to web #680

Merged
merged 4 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions flutter_secure_storage_web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.2.0
- Migrate away from `html` to `web`
- Allow newer `js` versions to be used with this package

## 1.1.2
- Update Dart SDK Constraint to support <4.0.0 instead of <3.0.0.

Expand Down
46 changes: 25 additions & 21 deletions flutter_secure_storage_web/lib/flutter_secure_storage_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
library flutter_secure_storage_web;

import 'dart:convert';
import 'dart:html' as html;
import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'dart:js_util' as js_util;
import 'dart:typed_data';

import 'package:flutter_secure_storage_platform_interface/flutter_secure_storage_platform_interface.dart';
import 'package:flutter_secure_storage_web/src/subtle.dart' as crypto;
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:web/web.dart';

/// Web implementation of FlutterSecureStorage
class FlutterSecureStorageWeb extends FlutterSecureStoragePlatform {
Expand All @@ -26,7 +28,7 @@ class FlutterSecureStorageWeb extends FlutterSecureStoragePlatform {
required Map<String, String> options,
}) =>
Future.value(
html.window.localStorage.containsKey("${options[_publicKey]!}.$key"),
window.localStorage.has("${options[_publicKey]!}.$key"),
);

/// Deletes associated value for the given [key].
Expand All @@ -37,7 +39,7 @@ class FlutterSecureStorageWeb extends FlutterSecureStoragePlatform {
required String key,
required Map<String, String> options,
}) async {
html.window.localStorage.remove("${options[_publicKey]!}.$key");
window.localStorage.removeItem("${options[_publicKey]!}.$key");
}

/// Deletes all keys with associated values.
Expand All @@ -46,7 +48,7 @@ class FlutterSecureStorageWeb extends FlutterSecureStoragePlatform {
required Map<String, String> options,
}) =>
Future.sync(
() => html.window.localStorage.removeWhere((key, value) => true),
() => window.localStorage.clear(),
);

/// Encrypts and saves the [key] with the given [value].
Expand All @@ -58,7 +60,7 @@ class FlutterSecureStorageWeb extends FlutterSecureStoragePlatform {
required String key,
required Map<String, String> options,
}) async {
final value = html.window.localStorage["${options[_publicKey]!}.$key"];
final value = window.localStorage["${options[_publicKey]!}.$key"];

return _decryptValue(value, options);
}
Expand All @@ -70,19 +72,19 @@ class FlutterSecureStorageWeb extends FlutterSecureStoragePlatform {
}) async {
final map = <String, String>{};
final prefix = "${options[_publicKey]!}.";
for (int j = 0; j < html.window.localStorage.length; j++) {
final entry = html.window.localStorage.entries.elementAt(j);
if (!entry.key.startsWith(prefix)) {
for (int j = 0; j < window.localStorage.length; j++) {
final key = window.localStorage.key(j) ?? '';
if (!key.startsWith(prefix)) {
continue;
}

final value = await _decryptValue(entry.value, options);
final value = await _decryptValue(window.localStorage[key], options);

if (value == null) {
continue;
}

map[entry.key.substring(prefix.length)] = value;
map[key.substring(prefix.length)] = value;
}

return map;
Expand All @@ -91,29 +93,29 @@ class FlutterSecureStorageWeb extends FlutterSecureStoragePlatform {
crypto.Algorithm _getAlgorithm(Uint8List iv) =>
crypto.Algorithm(name: 'AES-GCM', length: 256, iv: iv);

Future<html.CryptoKey> _getEncryptionKey(
Future<CryptoKey> _getEncryptionKey(
crypto.Algorithm algorithm,
Map<String, String> options,
) async {
late html.CryptoKey encryptionKey;
late CryptoKey encryptionKey;
final key = options[_publicKey]!;

if (html.window.localStorage.containsKey(key)) {
final jwk = base64Decode(html.window.localStorage[key]!);
if (window.localStorage.has(key)) {
final jwk = base64Decode(window.localStorage[key]!);

encryptionKey = await js_util.promiseToFuture<html.CryptoKey>(
encryptionKey = await js_util.promiseToFuture<CryptoKey>(
crypto.importKey("raw", jwk, algorithm, false, ["encrypt", "decrypt"]),
);
} else {
//final crypto.getRandomValues(Uint8List(256));

encryptionKey = await js_util.promiseToFuture<html.CryptoKey>(
encryptionKey = await js_util.promiseToFuture<CryptoKey>(
crypto.generateKey(algorithm, true, ["encrypt", "decrypt"]),
);

final jsonWebKey = await js_util
.promiseToFuture<ByteBuffer>(crypto.exportKey("raw", encryptionKey));
html.window.localStorage[key] = base64Encode(jsonWebKey.asUint8List());
window.localStorage[key] = base64Encode(jsonWebKey.asUint8List());
}

return encryptionKey;
Expand All @@ -129,8 +131,10 @@ class FlutterSecureStorageWeb extends FlutterSecureStoragePlatform {
required String value,
required Map<String, String> options,
}) async {
final iv =
html.window.crypto!.getRandomValues(Uint8List(12)).buffer.asUint8List();
final iv = (window.crypto.getRandomValues(Uint8List(12).toJS).dartify()!
as Uint8List)
.buffer
.asUint8List();

final algorithm = _getAlgorithm(iv);

Expand All @@ -149,7 +153,7 @@ class FlutterSecureStorageWeb extends FlutterSecureStoragePlatform {
final encoded =
"${base64Encode(iv)}.${base64Encode(encryptedContent.asUint8List())}";

html.window.localStorage["${options[_publicKey]!}.$key"] = encoded;
window.localStorage["${options[_publicKey]!}.$key"] = encoded;
}

Future<String?> _decryptValue(
Expand Down Expand Up @@ -187,5 +191,5 @@ class FlutterSecureStorageWeb extends FlutterSecureStoragePlatform {

@override
Stream<bool> get onCupertinoProtectedDataAvailabilityChanged =>
Stream.empty();
const Stream.empty();
}
2 changes: 1 addition & 1 deletion flutter_secure_storage_web/lib/src/subtle.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
library common;

import 'dart:convert' show jsonDecode;
import 'dart:html';
import 'dart:js_util' as js_util;
import 'dart:typed_data';

import 'package:flutter_secure_storage_web/src/jsonwebkey.dart' show JsonWebKey;
import 'package:js/js.dart';
import 'package:web/web.dart' hide JsonWebKey;

export 'jsonwebkey.dart' show JsonWebKey;

Expand Down
5 changes: 3 additions & 2 deletions flutter_secure_storage_web/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: flutter_secure_storage_web
description: Web implementation of flutter_secure_storage. Use flutter_secure_storage for the full flutter package.
repository: https://github.com/mogol/flutter_secure_storage
version: 1.1.2
version: 1.2.0

environment:
sdk: ">=2.12.0 <4.0.0"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything looks good, however the only thing that's a bit annoying is that the web package is available from dart 3.3, which is basically the latest flutter version, meaning that if we publish this, everyone is required to run the latest Flutter version.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is true. I have pushed the constraints for web down as far as possible. Older versions lack features that are (IMHO) critical for the functionality of flutter_secure_storage. Maybe, the constraints could be pushed even lower than that using some hacky workarounds, but I am unsure.
This is also one of the reasons why it makes sense to apply this PR (and #698 for that matter) only after a major version bump

Expand All @@ -13,7 +13,8 @@ dependencies:
flutter_secure_storage_platform_interface: ^1.0.1
flutter_web_plugins:
sdk: flutter
js: ^0.6.3
js: '>=0.6.3 <1.0.0'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
js: '>=0.6.3 <1.0.0'

The migration to the new js-interop approach also means getting rid of the package:js.

https://dart.dev/interop/js-interop/package-web

See also these comments firebase/flutterfire#12027 (comment) from Kevin Moore (Flutter Web PM)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, thanks for reminding me about this... Currently, I don't have enough free time to also address this, but hopefully I will do so soon!

Copy link

@IchordeDionysos IchordeDionysos Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries 😌 I hear your struggle to find time to maintain/contribute to open-source projects!

web: '>=0.5.0 <1.0.0'

dev_dependencies:
lint: ^2.0.0
Expand Down
Loading