blob: 0b79b3655fed7cb4e3feb64b250d9a5ca3ced78d [file] [log] [blame]
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@JS()
library;
import 'dart:async';
import 'dart:convert';
// TODO: https://github.com/dart-lang/webdev/issues/2508
// ignore: deprecated_member_use
import 'dart:js_util';
// TODO: https://github.com/dart-lang/webdev/issues/2508
// ignore: deprecated_member_use
import 'package:js/js.dart';
import 'chrome_api.dart';
import 'data_serializers.dart';
import 'logger.dart';
import 'utils.dart';
enum StorageObject {
debugInfo,
devToolsOpener,
devToolsUri,
encodedUri,
isAuthenticated,
multipleAppsDetected;
Persistence get persistence => switch (this) {
StorageObject.debugInfo => Persistence.sessionOnly,
StorageObject.devToolsOpener => Persistence.acrossSessions,
StorageObject.devToolsUri => Persistence.sessionOnly,
StorageObject.encodedUri => Persistence.sessionOnly,
StorageObject.isAuthenticated => Persistence.sessionOnly,
StorageObject.multipleAppsDetected => Persistence.sessionOnly,
};
}
enum Persistence { sessionOnly, acrossSessions }
Future<bool> setStorageObject<T>({
required StorageObject type,
required T value,
int? tabId,
void Function()? callback,
}) {
final storageKey = _createStorageKey(type, tabId);
final json =
value is String ? value : jsonEncode(serializers.serialize(value));
final storageObj = <String, String>{storageKey: json};
final completer = Completer<bool>();
final storageArea = _getStorageArea(type.persistence);
storageArea.set(
jsify(storageObj),
allowInterop(() {
if (callback != null) {
callback();
}
debugLog('Set: $json', prefix: storageKey);
completer.complete(true);
}),
);
return completer.future;
}
Future<T?> fetchStorageObject<T>({required StorageObject type, int? tabId}) {
final storageKey = _createStorageKey(type, tabId);
final completer = Completer<T?>();
final storageArea = _getStorageArea(type.persistence);
storageArea.get(
[storageKey],
allowInterop((Object? storageObj) {
if (storageObj == null) {
debugWarn('Does not exist.', prefix: storageKey);
completer.complete(null);
return;
}
final json = getProperty(storageObj, storageKey) as String?;
if (json == null) {
debugWarn('Does not exist.', prefix: storageKey);
completer.complete(null);
} else {
debugLog('Fetched: $json', prefix: storageKey);
if (T == String) {
completer.complete(json as T);
} else {
final value = serializers.deserialize(jsonDecode(json)) as T;
completer.complete(value);
}
}
}),
);
return completer.future;
}
Future<List<T>> fetchAllStorageObjectsOfType<T>({required StorageObject type}) {
final completer = Completer<List<T>>();
final storageArea = _getStorageArea(type.persistence);
storageArea.get(
null,
allowInterop((Object? storageContents) {
if (storageContents == null) {
debugWarn('No storage objects of type exist.', prefix: type.name);
completer.complete([]);
return;
}
final allKeys = List<String>.from(objectKeys(storageContents));
final storageKeys = allKeys.where((key) => key.contains(type.name));
final result = <T>[];
for (final key in storageKeys) {
final json = getProperty(storageContents, key) as String?;
if (json != null) {
if (T == String) {
result.add(json as T);
} else {
result.add(serializers.deserialize(jsonDecode(json)) as T);
}
}
}
completer.complete(result);
}),
);
return completer.future;
}
Future<bool> removeStorageObject({required StorageObject type, int? tabId}) {
final storageKey = _createStorageKey(type, tabId);
final completer = Completer<bool>();
final storageArea = _getStorageArea(type.persistence);
storageArea.remove(
[storageKey],
allowInterop(() {
debugLog('Removed object.', prefix: storageKey);
completer.complete(true);
}),
);
return completer.future;
}
void interceptStorageChange<T>({
required Object storageObj,
required StorageObject expectedType,
required void Function(T? storageObj) changeHandler,
int? tabId,
}) {
try {
final expectedStorageKey = _createStorageKey(expectedType, tabId);
final isExpected = hasProperty(storageObj, expectedStorageKey);
if (!isExpected) return;
final objProp = getProperty(storageObj, expectedStorageKey);
final json = getProperty(objProp, 'newValue') as String?;
T? decodedObj;
if (json == null || T == String) {
decodedObj = json as T?;
} else {
decodedObj = serializers.deserialize(jsonDecode(json)) as T?;
}
debugLog('Intercepted $expectedStorageKey change: $json');
return changeHandler(decodedObj);
} catch (error) {
debugError(
'Error intercepting storage object with type $expectedType: $error',
);
}
}
StorageArea _getStorageArea(Persistence persistence) {
// MV2 extensions don't have access to session storage:
if (!isMV3) return chrome.storage.local;
return switch (persistence) {
Persistence.acrossSessions => chrome.storage.local,
Persistence.sessionOnly => chrome.storage.session,
};
}
String _createStorageKey(StorageObject type, int? tabId) {
if (tabId == null) return type.name;
return '$tabId-${type.name}';
}