Kaynağa Gözat

Add State package

Sasan Salamzadeh 1 yıl önce
ebeveyn
işleme
c0b47305f9

+ 3 - 0
state/.gitignore

@@ -0,0 +1,3 @@
+# https://dart.dev/guides/libraries/private-files
+# Created by `dart pub`
+.dart_tool/

+ 2 - 0
state/README.md

@@ -0,0 +1,2 @@
+A sample command-line application with an entrypoint in `bin/`, library code
+in `lib/`, and example unit test in `test/`.

+ 30 - 0
state/analysis_options.yaml

@@ -0,0 +1,30 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+#
+# This enables the 'recommended' set of lints from `package:lints`.
+# This set helps identify many issues that may lead to problems when running
+# or consuming Dart code, and enforces writing Dart using a single, idiomatic
+# style and format.
+#
+# If you want a smaller set of lints you can change this to specify
+# 'package:lints/core.yaml'. These are just the most critical lints
+# (the recommended set includes the core lints).
+# The core lints are also what is used by pub.dev for scoring packages.
+
+include: package:lints/recommended.yaml
+
+# Uncomment the following section to specify additional rules.
+
+# linter:
+#   rules:
+#     - camel_case_types
+
+# analyzer:
+#   exclude:
+#     - path/to/excluded/files/**
+
+# For more information about the core and recommended set of lints, see
+# https://dart.dev/go/core-lints
+
+# For additional information about configuring this file, see
+# https://dart.dev/guides/language/analysis-options

+ 13 - 0
state/lib/file_storage_base.dart

@@ -0,0 +1,13 @@
+abstract class FileStorageBase {
+  /// Get path to store persistent data.
+  Future<String> getDataPath();
+
+  /// Does file exist?
+  Future<bool> hasFile(String filename);
+
+  /// Reads from file into String.
+  Future<String?> readFile(String filename);
+
+  /// Writes to file.
+  Future writeFile(String filename, String contents);
+}

+ 56 - 0
state/lib/legacy/legacy_authenticator_item.dart

@@ -0,0 +1,56 @@
+import 'package:pb_authenticator_totp/totp.dart';
+import 'package:uuid/uuid.dart';
+
+class LegacyAuthenticatorItem {
+  /// Legacy GUID id
+  final String id;
+
+  final TotpItem totp;
+
+  LegacyAuthenticatorItem(this.id, this.totp);
+
+  static LegacyAuthenticatorItem newAuthenticatorItemFromUri(String uri) {
+    var id = Uuid().v4();
+    return LegacyAuthenticatorItem(id, TotpItem.fromUri(uri));
+  }
+
+  static LegacyAuthenticatorItem newAuthenticatorItem(TotpItem item) {
+    var id = Uuid().v4();
+    return LegacyAuthenticatorItem(id, item);
+  }
+
+  // static LegacyAuthenticatorItem newAuthenticatorItem(String secret,
+  //     [int digits = 6,
+  //     int period = 60,
+  //     OtpHashAlgorithm algorithm = OtpHashAlgorithm.sha1,
+  //     String issuer = "",
+  //     String accountName = ""]) {
+  //   var item = TotpItem(secret, digits, period, algorithm, issuer, accountName);
+  //   var id = Uuid().v4();
+  //   return LegacyAuthenticatorItem(id, item);
+  // }
+
+  /// Legacy decode.
+  LegacyAuthenticatorItem.fromMap(Map<String, dynamic> json)
+      : id = json['id'],
+        totp = TotpItem.fromJSON(json);
+
+  /// Legacy encode.
+  Map<String, dynamic> toMap() => {
+        'id': id,
+        'accountName': totp.accountName,
+        'issuer': totp.issuer,
+        'secret': totp.secret,
+        'digits': totp.digits,
+        'period': totp.period,
+        'algorithm': totp.algorithm.name
+      };
+
+  @override
+  bool operator ==(Object other) =>
+      identical(this, other) ||
+      other is LegacyAuthenticatorItem && id == other.id && totp == other.totp;
+
+  @override
+  int get hashCode => id.hashCode ^ totp.hashCode;
+}

+ 84 - 0
state/lib/legacy/legacy_repository.dart

@@ -0,0 +1,84 @@
+import 'dart:async' show Future;
+import 'dart:convert' show json;
+import 'package:pb_authenticator_state/file_storage_base.dart';
+import 'package:pb_authenticator_totp/totp.dart';
+
+import './legacy_authenticator_item.dart';
+import '../repository/repository_base.dart' show RepositoryBase;
+
+/// Is used to load/save state to JSON file.
+///
+/// Currently uses FileStorage to do so.
+class LegacyRepository implements RepositoryBase<LegacyAuthenticatorItem> {
+  LegacyRepository(this._fileStorage);
+
+  // Current file version
+  static const _currentVersion = 1;
+
+  // Filename of JSON
+  static const _stateFilename = "items.json";
+
+  // Used for loading/saving files
+  final FileStorageBase _fileStorage;
+
+  // Items
+  List<LegacyAuthenticatorItem> _items = [];
+
+  Future<bool> hasState() {
+    return _fileStorage.hasFile(_stateFilename);
+  }
+
+  /// Loads state from storage.
+  @override
+  Future<List<LegacyAuthenticatorItem>> loadItems() async {
+    // If file doesn't exist, then initialise empty state
+
+    // Load/decode file
+    var str = await _fileStorage.readFile(_stateFilename);
+    if (str == null) {
+      return [];
+    }
+
+    Map<String, dynamic> decoded = json.decode(str);
+    var version = decoded['version'];
+
+    // If version is not current, bring it up to current
+    if (version != _currentVersion) {
+      throw Exception("Unknown file version");
+    }
+
+    // Decode and return items
+    // Adapted from: https://stackoverflow.com/questions/50360443
+    var itemsDecoded = decoded['items'];
+    var items = (itemsDecoded as List)
+        .map((i) => LegacyAuthenticatorItem.fromMap(i))
+        .toList();
+    _items = items;
+    return items;
+  }
+
+  /// Saves [state] to storage.
+  @override
+  Future replaceItems(List<LegacyAuthenticatorItem> state) async {
+    return await _saveItems(state);
+  }
+
+  @override
+  Future<LegacyAuthenticatorItem> addItem(TotpItem item) async {
+    var legacyAuthenticatorItem =
+        LegacyAuthenticatorItem.newAuthenticatorItem(item);
+    _items.add(legacyAuthenticatorItem);
+    await _saveItems(_items);
+    return legacyAuthenticatorItem;
+  }
+
+  Future _saveItems(List<LegacyAuthenticatorItem> items) async {
+    // Encode
+    var itemsMapped = items.map((i) => i.toMap()).toList();
+    var version = _currentVersion;
+    var str = json.encode({'items': itemsMapped, 'version': version});
+
+    // Save to file
+    return await _fileStorage.writeFile(_stateFilename, str).then((_) => {});
+  }
+}

+ 38 - 0
state/lib/repository/repository.dart

@@ -0,0 +1,38 @@
+import 'dart:async' show Future;
+
+import 'package:pb_authenticator_totp/totp.dart';
+
+import '../file_storage_base.dart';
+import '../legacy/legacy_authenticator_item.dart';
+import './repository_base.dart' show RepositoryBase;
+import '../legacy/legacy_repository.dart' show LegacyRepository;
+
+/// Wrapper for repository.
+class Repository implements RepositoryBase<LegacyAuthenticatorItem> {
+  late RepositoryBase<LegacyAuthenticatorItem> _legacyRepository;
+  late FileStorageBase _fileStorage;
+
+  Repository(fileStorage) {
+    _legacyRepository = LegacyRepository(fileStorage);
+    _fileStorage = fileStorage;
+  }
+
+  @override
+  Future<List<LegacyAuthenticatorItem>> loadItems() async {
+    // Open the database and store the reference.
+    // join(await _fileStorage.getDataPath(), 'main.db')
+    _fileStorage.getDataPath();
+
+    return _legacyRepository.loadItems();
+  }
+
+  @override
+  Future replaceItems(List<LegacyAuthenticatorItem> items) async {
+    return _legacyRepository.replaceItems(items);
+  }
+
+  @override
+  Future<LegacyAuthenticatorItem> addItem(TotpItem item) async {
+    return _legacyRepository.addItem(item);
+  }
+}

+ 15 - 0
state/lib/repository/repository_base.dart

@@ -0,0 +1,15 @@
+import 'dart:async' show Future;
+
+import 'package:pb_authenticator_totp/totp.dart';
+
+/// Abstract class for loading/saving state from storage.
+abstract class RepositoryBase<T> {
+  /// Load list of items from repository.
+  Future<List<T>> loadItems();
+
+  /// Replace list of items.
+  Future replaceItems(List<T> state);
+
+  /// Add an item to the list.
+  Future<T> addItem(TotpItem item);
+}

+ 4 - 0
state/lib/state.dart

@@ -0,0 +1,4 @@
+export 'repository/repository_base.dart';
+export 'repository/repository.dart';
+export 'legacy/legacy_authenticator_item.dart';
+export 'file_storage_base.dart';

+ 492 - 0
state/pubspec.lock

@@ -0,0 +1,492 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+  _fe_analyzer_shared:
+    dependency: transitive
+    description:
+      name: _fe_analyzer_shared
+      sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
+      url: "https://pub.dev"
+    source: hosted
+    version: "76.0.0"
+  _macros:
+    dependency: transitive
+    description: dart
+    source: sdk
+    version: "0.3.3"
+  analyzer:
+    dependency: transitive
+    description:
+      name: analyzer
+      sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "6.11.0"
+  args:
+    dependency: transitive
+    description:
+      name: args
+      sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.5.0"
+  async:
+    dependency: transitive
+    description:
+      name: async
+      sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.11.0"
+  boolean_selector:
+    dependency: transitive
+    description:
+      name: boolean_selector
+      sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.1"
+  characters:
+    dependency: transitive
+    description:
+      name: characters
+      sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.4.0"
+  collection:
+    dependency: transitive
+    description:
+      name: collection
+      sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.19.1"
+  convert:
+    dependency: transitive
+    description:
+      name: convert
+      sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.1"
+  coverage:
+    dependency: transitive
+    description:
+      name: coverage
+      sha256: c1fb2dce3c0085f39dc72668e85f8e0210ec7de05345821ff58530567df345a5
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.9.2"
+  crypto:
+    dependency: transitive
+    description:
+      name: crypto
+      sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.5"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "7.0.0"
+  fixnum:
+    dependency: transitive
+    description:
+      name: fixnum
+      sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.1.0"
+  flutter:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  frontend_server_client:
+    dependency: transitive
+    description:
+      name: frontend_server_client
+      sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.0.0"
+  glob:
+    dependency: transitive
+    description:
+      name: glob
+      sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.2"
+  http_multi_server:
+    dependency: transitive
+    description:
+      name: http_multi_server
+      sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.2.1"
+  http_parser:
+    dependency: transitive
+    description:
+      name: http_parser
+      sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.0.2"
+  io:
+    dependency: transitive
+    description:
+      name: io
+      sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.4"
+  js:
+    dependency: transitive
+    description:
+      name: js
+      sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.7.1"
+  lints:
+    dependency: "direct dev"
+    description:
+      name: lints
+      sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.0.0"
+  logging:
+    dependency: transitive
+    description:
+      name: logging
+      sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.2.0"
+  macros:
+    dependency: transitive
+    description:
+      name: macros
+      sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.1.3-main.0"
+  matcher:
+    dependency: transitive
+    description:
+      name: matcher
+      sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.12.16+1"
+  material_color_utilities:
+    dependency: transitive
+    description:
+      name: material_color_utilities
+      sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.11.1"
+  meta:
+    dependency: transitive
+    description:
+      name: meta
+      sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.16.0"
+  mime:
+    dependency: transitive
+    description:
+      name: mime
+      sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.6"
+  node_preamble:
+    dependency: transitive
+    description:
+      name: node_preamble
+      sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.0.2"
+  package_config:
+    dependency: transitive
+    description:
+      name: package_config
+      sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.0"
+  path:
+    dependency: transitive
+    description:
+      name: path
+      sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.9.0"
+  pb_authenticator_totp:
+    dependency: "direct main"
+    description:
+      path: "../totp"
+      relative: true
+    source: path
+    version: "1.0.0"
+  pool:
+    dependency: transitive
+    description:
+      name: pool
+      sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.5.1"
+  pub_semver:
+    dependency: transitive
+    description:
+      name: pub_semver
+      sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.4"
+  shelf:
+    dependency: transitive
+    description:
+      name: shelf
+      sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.4.1"
+  shelf_packages_handler:
+    dependency: transitive
+    description:
+      name: shelf_packages_handler
+      sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.2"
+  shelf_static:
+    dependency: transitive
+    description:
+      name: shelf_static
+      sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.1.3"
+  shelf_web_socket:
+    dependency: transitive
+    description:
+      name: shelf_web_socket
+      sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.0.0"
+  sky_engine:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  source_map_stack_trace:
+    dependency: transitive
+    description:
+      name: source_map_stack_trace
+      sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.2"
+  source_maps:
+    dependency: transitive
+    description:
+      name: source_maps
+      sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.10.12"
+  source_span:
+    dependency: transitive
+    description:
+      name: source_span
+      sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.10.0"
+  sprintf:
+    dependency: transitive
+    description:
+      name: sprintf
+      sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
+      url: "https://pub.dev"
+    source: hosted
+    version: "7.0.0"
+  sqflite:
+    dependency: "direct main"
+    description:
+      name: sqflite
+      sha256: ff5a2436ef8ebdfda748fbfe957f9981524cb5ff11e7bafa8c42771840e8a788
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.3.3+2"
+  sqflite_common:
+    dependency: transitive
+    description:
+      name: sqflite_common
+      sha256: "2d8e607db72e9cb7748c9c6e739e2c9618320a5517de693d5a24609c4671b1a4"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.5.4+4"
+  stack_trace:
+    dependency: transitive
+    description:
+      name: stack_trace
+      sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.12.0"
+  stream_channel:
+    dependency: transitive
+    description:
+      name: stream_channel
+      sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.2"
+  string_scanner:
+    dependency: transitive
+    description:
+      name: string_scanner
+      sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.3.0"
+  synchronized:
+    dependency: transitive
+    description:
+      name: synchronized
+      sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.3.0+3"
+  term_glyph:
+    dependency: transitive
+    description:
+      name: term_glyph
+      sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.2.1"
+  test:
+    dependency: "direct dev"
+    description:
+      name: test
+      sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.25.8"
+  test_api:
+    dependency: transitive
+    description:
+      name: test_api
+      sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.7.3"
+  test_core:
+    dependency: transitive
+    description:
+      name: test_core
+      sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.6.5"
+  typed_data:
+    dependency: transitive
+    description:
+      name: typed_data
+      sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.3.2"
+  uuid:
+    dependency: "direct main"
+    description:
+      name: uuid
+      sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.4.0"
+  vector_math:
+    dependency: transitive
+    description:
+      name: vector_math
+      sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.4"
+  vm_service:
+    dependency: transitive
+    description:
+      name: vm_service
+      sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
+      url: "https://pub.dev"
+    source: hosted
+    version: "14.3.0"
+  watcher:
+    dependency: transitive
+    description:
+      name: watcher
+      sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.1.0"
+  web:
+    dependency: transitive
+    description:
+      name: web
+      sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.1.0"
+  web_socket:
+    dependency: transitive
+    description:
+      name: web_socket
+      sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.1.6"
+  web_socket_channel:
+    dependency: transitive
+    description:
+      name: web_socket_channel
+      sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.1"
+  webkit_inspection_protocol:
+    dependency: transitive
+    description:
+      name: webkit_inspection_protocol
+      sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.2.1"
+  yaml:
+    dependency: transitive
+    description:
+      name: yaml
+      sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.2"
+sdks:
+  dart: ">=3.7.0-0 <4.0.0"
+  flutter: ">=3.7.0"

+ 20 - 0
state/pubspec.yaml

@@ -0,0 +1,20 @@
+name: pb_authenticator_state
+description: State for PB Authenticator.
+version: 1.0.0
+publish_to: none
+
+environment:
+  sdk: '>=3.3.0 <4.0.0'
+
+dependencies:
+  uuid: 4.4.0
+
+  # Internal
+  pb_authenticator_totp:
+    path: ../totp
+
+  sqflite: ^2.3.3
+
+dev_dependencies:
+  lints: ^4.0.0
+  test: ^1.25.2

+ 42 - 0
state/test/legacy_repository_test.dart

@@ -0,0 +1,42 @@
+import 'package:pb_authenticator_state/legacy/legacy_repository.dart';
+import 'package:pb_authenticator_state/state.dart';
+import 'package:pb_authenticator_totp/totp.dart';
+import 'package:test/test.dart';
+
+import 'test_file_storage.dart';
+
+void main() {
+  test('Has State?', () async {
+    var repository = LegacyRepository(TestFileStorage());
+    expect(await repository.hasState(), false);
+  });
+
+  test('Load', () async {
+    var repository = LegacyRepository(TestFileStorage());
+    var items = await repository.loadItems();
+    expect(items.length, 0);
+  });
+
+  test('Add Item', () async {
+    var repository = LegacyRepository(TestFileStorage());
+    var item = TotpItem("ABCDEF");
+    await repository.addItem(item);
+
+    var items = await repository.loadItems();
+    expect(items.length, 1);
+    expect(items[0].totp, item);
+  });
+
+  test('Replace items', () async {
+    var repository = LegacyRepository(TestFileStorage());
+    var item = TotpItem("A");
+    await repository.addItem(item);
+
+    var item2 = LegacyAuthenticatorItem("id", TotpItem("A"));
+    await repository.replaceItems([item2]);
+
+    var newItems = await repository.loadItems();
+    expect(newItems.length, 1);
+    expect(newItems[0], item2);
+  });
+}

+ 26 - 0
state/test/test_file_storage.dart

@@ -0,0 +1,26 @@
+import 'package:pb_authenticator_state/state.dart';
+
+class TestFileStorage implements FileStorageBase {
+  String? memoryFile;
+
+  @override
+  Future<String> getDataPath() {
+    return Future.value(".");
+  }
+
+  @override
+  Future<bool> hasFile(String filename) {
+    return Future.value(memoryFile != null);
+  }
+
+  @override
+  Future<String?> readFile(String filename) {
+    return Future.value(memoryFile);
+  }
+
+  @override
+  Future writeFile(String filename, String contents) {
+    memoryFile = contents;
+    return Future.value();
+  }
+}