account work

This commit is contained in:
Christien Rioux 2023-07-16 21:41:40 -04:00
parent f44fdb8eeb
commit 90fc2e5f85
9 changed files with 422 additions and 25 deletions

View File

@ -4,6 +4,19 @@ import 'package:veilid/veilid.dart';
part 'identity.freezed.dart';
part 'identity.g.dart';
// AccountOwnerInfo is the key and owner info for the account dht key that is
// stored in the identity key
@freezed
class AccountOwnerInfo with _$AccountOwnerInfo {
const factory AccountOwnerInfo({
// Top level account keys and secrets
required Map<String, TypedKeyPair> accountKeyPairs,
}) = _AccountOwnerInfo;
factory AccountOwnerInfo.fromJson(Map<String, dynamic> json) =>
_$AccountOwnerInfoFromJson(json);
}
// Identity Key points to accounts associated with this identity
// accounts field has a map of service name or uuid to account key pairs
// DHT Schema: DFLT(1)
@ -55,6 +68,16 @@ class IdentityMaster with _$IdentityMaster {
_$IdentityMasterFromJson(json);
}
extension IdentityMasterExtension on IdentityMaster {
KeyPair identityWriter(SecretKey secret) {
return KeyPair(key: identityPublicKey, secret: secret);
}
KeyPair masterWriter(SecretKey secret) {
return KeyPair(key: masterPublicKey, secret: secret);
}
}
// Identity Master with secrets
// Not freezed because we never persist this class in its entirety
class IdentityMasterWithSecrets {

View File

@ -14,6 +14,157 @@ T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
AccountOwnerInfo _$AccountOwnerInfoFromJson(Map<String, dynamic> json) {
return _AccountOwnerInfo.fromJson(json);
}
/// @nodoc
mixin _$AccountOwnerInfo {
// Top level account keys and secrets
Map<String, TypedKeyPair> get accountKeyPairs =>
throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$AccountOwnerInfoCopyWith<AccountOwnerInfo> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $AccountOwnerInfoCopyWith<$Res> {
factory $AccountOwnerInfoCopyWith(
AccountOwnerInfo value, $Res Function(AccountOwnerInfo) then) =
_$AccountOwnerInfoCopyWithImpl<$Res, AccountOwnerInfo>;
@useResult
$Res call({Map<String, TypedKeyPair> accountKeyPairs});
}
/// @nodoc
class _$AccountOwnerInfoCopyWithImpl<$Res, $Val extends AccountOwnerInfo>
implements $AccountOwnerInfoCopyWith<$Res> {
_$AccountOwnerInfoCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? accountKeyPairs = null,
}) {
return _then(_value.copyWith(
accountKeyPairs: null == accountKeyPairs
? _value.accountKeyPairs
: accountKeyPairs // ignore: cast_nullable_to_non_nullable
as Map<String, TypedKeyPair>,
) as $Val);
}
}
/// @nodoc
abstract class _$$_AccountOwnerInfoCopyWith<$Res>
implements $AccountOwnerInfoCopyWith<$Res> {
factory _$$_AccountOwnerInfoCopyWith(
_$_AccountOwnerInfo value, $Res Function(_$_AccountOwnerInfo) then) =
__$$_AccountOwnerInfoCopyWithImpl<$Res>;
@override
@useResult
$Res call({Map<String, TypedKeyPair> accountKeyPairs});
}
/// @nodoc
class __$$_AccountOwnerInfoCopyWithImpl<$Res>
extends _$AccountOwnerInfoCopyWithImpl<$Res, _$_AccountOwnerInfo>
implements _$$_AccountOwnerInfoCopyWith<$Res> {
__$$_AccountOwnerInfoCopyWithImpl(
_$_AccountOwnerInfo _value, $Res Function(_$_AccountOwnerInfo) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? accountKeyPairs = null,
}) {
return _then(_$_AccountOwnerInfo(
accountKeyPairs: null == accountKeyPairs
? _value._accountKeyPairs
: accountKeyPairs // ignore: cast_nullable_to_non_nullable
as Map<String, TypedKeyPair>,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_AccountOwnerInfo implements _AccountOwnerInfo {
const _$_AccountOwnerInfo(
{required final Map<String, TypedKeyPair> accountKeyPairs})
: _accountKeyPairs = accountKeyPairs;
factory _$_AccountOwnerInfo.fromJson(Map<String, dynamic> json) =>
_$$_AccountOwnerInfoFromJson(json);
// Top level account keys and secrets
final Map<String, TypedKeyPair> _accountKeyPairs;
// Top level account keys and secrets
@override
Map<String, TypedKeyPair> get accountKeyPairs {
if (_accountKeyPairs is EqualUnmodifiableMapView) return _accountKeyPairs;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_accountKeyPairs);
}
@override
String toString() {
return 'AccountOwnerInfo(accountKeyPairs: $accountKeyPairs)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_AccountOwnerInfo &&
const DeepCollectionEquality()
.equals(other._accountKeyPairs, _accountKeyPairs));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, const DeepCollectionEquality().hash(_accountKeyPairs));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_AccountOwnerInfoCopyWith<_$_AccountOwnerInfo> get copyWith =>
__$$_AccountOwnerInfoCopyWithImpl<_$_AccountOwnerInfo>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_AccountOwnerInfoToJson(
this,
);
}
}
abstract class _AccountOwnerInfo implements AccountOwnerInfo {
const factory _AccountOwnerInfo(
{required final Map<String, TypedKeyPair> accountKeyPairs}) =
_$_AccountOwnerInfo;
factory _AccountOwnerInfo.fromJson(Map<String, dynamic> json) =
_$_AccountOwnerInfo.fromJson;
@override // Top level account keys and secrets
Map<String, TypedKeyPair> get accountKeyPairs;
@override
@JsonKey(ignore: true)
_$$_AccountOwnerInfoCopyWith<_$_AccountOwnerInfo> get copyWith =>
throw _privateConstructorUsedError;
}
Identity _$IdentityFromJson(Map<String, dynamic> json) {
return _Identity.fromJson(json);
}

View File

@ -6,6 +6,19 @@ part of 'identity.dart';
// JsonSerializableGenerator
// **************************************************************************
_$_AccountOwnerInfo _$$_AccountOwnerInfoFromJson(Map<String, dynamic> json) =>
_$_AccountOwnerInfo(
accountKeyPairs: (json['account_key_pairs'] as Map<String, dynamic>).map(
(k, e) => MapEntry(k, TypedKeyPair.fromJson(e)),
),
);
Map<String, dynamic> _$$_AccountOwnerInfoToJson(_$_AccountOwnerInfo instance) =>
<String, dynamic>{
'account_key_pairs':
instance.accountKeyPairs.map((k, e) => MapEntry(k, e.toJson())),
};
_$_Identity _$$_IdentityFromJson(Map<String, dynamic> json) => _$_Identity(
accountKeyPairs: (json['account_key_pairs'] as Map<String, dynamic>).map(
(k, e) => MapEntry(k, TypedKeyPair.fromJson(e)),

View File

@ -25,6 +25,9 @@ mixin _$LocalAccount {
throw _privateConstructorUsedError; // The encrypted identity secret that goes with the identityPublicKey
@Uint8ListJsonConverter()
Uint8List get identitySecretKeyBytes =>
throw _privateConstructorUsedError; // The salt for the identity secret key encryption
@Uint8ListJsonConverter()
Uint8List get identitySecretSaltBytes =>
throw _privateConstructorUsedError; // The kind of encryption input used on the account
EncryptionKeyType get encryptionKeyType =>
throw _privateConstructorUsedError; // If account is not hidden, password can be retrieved via
@ -48,6 +51,7 @@ abstract class $LocalAccountCopyWith<$Res> {
$Res call(
{IdentityMaster identityMaster,
@Uint8ListJsonConverter() Uint8List identitySecretKeyBytes,
@Uint8ListJsonConverter() Uint8List identitySecretSaltBytes,
EncryptionKeyType encryptionKeyType,
bool biometricsEnabled,
bool hiddenAccount});
@ -70,6 +74,7 @@ class _$LocalAccountCopyWithImpl<$Res, $Val extends LocalAccount>
$Res call({
Object? identityMaster = null,
Object? identitySecretKeyBytes = null,
Object? identitySecretSaltBytes = null,
Object? encryptionKeyType = null,
Object? biometricsEnabled = null,
Object? hiddenAccount = null,
@ -83,6 +88,10 @@ class _$LocalAccountCopyWithImpl<$Res, $Val extends LocalAccount>
? _value.identitySecretKeyBytes
: identitySecretKeyBytes // ignore: cast_nullable_to_non_nullable
as Uint8List,
identitySecretSaltBytes: null == identitySecretSaltBytes
? _value.identitySecretSaltBytes
: identitySecretSaltBytes // ignore: cast_nullable_to_non_nullable
as Uint8List,
encryptionKeyType: null == encryptionKeyType
? _value.encryptionKeyType
: encryptionKeyType // ignore: cast_nullable_to_non_nullable
@ -118,6 +127,7 @@ abstract class _$$_LocalAccountCopyWith<$Res>
$Res call(
{IdentityMaster identityMaster,
@Uint8ListJsonConverter() Uint8List identitySecretKeyBytes,
@Uint8ListJsonConverter() Uint8List identitySecretSaltBytes,
EncryptionKeyType encryptionKeyType,
bool biometricsEnabled,
bool hiddenAccount});
@ -139,6 +149,7 @@ class __$$_LocalAccountCopyWithImpl<$Res>
$Res call({
Object? identityMaster = null,
Object? identitySecretKeyBytes = null,
Object? identitySecretSaltBytes = null,
Object? encryptionKeyType = null,
Object? biometricsEnabled = null,
Object? hiddenAccount = null,
@ -152,6 +163,10 @@ class __$$_LocalAccountCopyWithImpl<$Res>
? _value.identitySecretKeyBytes
: identitySecretKeyBytes // ignore: cast_nullable_to_non_nullable
as Uint8List,
identitySecretSaltBytes: null == identitySecretSaltBytes
? _value.identitySecretSaltBytes
: identitySecretSaltBytes // ignore: cast_nullable_to_non_nullable
as Uint8List,
encryptionKeyType: null == encryptionKeyType
? _value.encryptionKeyType
: encryptionKeyType // ignore: cast_nullable_to_non_nullable
@ -174,6 +189,7 @@ class _$_LocalAccount implements _LocalAccount {
const _$_LocalAccount(
{required this.identityMaster,
@Uint8ListJsonConverter() required this.identitySecretKeyBytes,
@Uint8ListJsonConverter() required this.identitySecretSaltBytes,
required this.encryptionKeyType,
required this.biometricsEnabled,
required this.hiddenAccount});
@ -188,6 +204,10 @@ class _$_LocalAccount implements _LocalAccount {
@override
@Uint8ListJsonConverter()
final Uint8List identitySecretKeyBytes;
// The salt for the identity secret key encryption
@override
@Uint8ListJsonConverter()
final Uint8List identitySecretSaltBytes;
// The kind of encryption input used on the account
@override
final EncryptionKeyType encryptionKeyType;
@ -201,7 +221,7 @@ class _$_LocalAccount implements _LocalAccount {
@override
String toString() {
return 'LocalAccount(identityMaster: $identityMaster, identitySecretKeyBytes: $identitySecretKeyBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount)';
return 'LocalAccount(identityMaster: $identityMaster, identitySecretKeyBytes: $identitySecretKeyBytes, identitySecretSaltBytes: $identitySecretSaltBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount)';
}
@override
@ -213,6 +233,8 @@ class _$_LocalAccount implements _LocalAccount {
other.identityMaster == identityMaster) &&
const DeepCollectionEquality()
.equals(other.identitySecretKeyBytes, identitySecretKeyBytes) &&
const DeepCollectionEquality().equals(
other.identitySecretSaltBytes, identitySecretSaltBytes) &&
(identical(other.encryptionKeyType, encryptionKeyType) ||
other.encryptionKeyType == encryptionKeyType) &&
(identical(other.biometricsEnabled, biometricsEnabled) ||
@ -227,6 +249,7 @@ class _$_LocalAccount implements _LocalAccount {
runtimeType,
identityMaster,
const DeepCollectionEquality().hash(identitySecretKeyBytes),
const DeepCollectionEquality().hash(identitySecretSaltBytes),
encryptionKeyType,
biometricsEnabled,
hiddenAccount);
@ -249,6 +272,8 @@ abstract class _LocalAccount implements LocalAccount {
const factory _LocalAccount(
{required final IdentityMaster identityMaster,
@Uint8ListJsonConverter() required final Uint8List identitySecretKeyBytes,
@Uint8ListJsonConverter()
required final Uint8List identitySecretSaltBytes,
required final EncryptionKeyType encryptionKeyType,
required final bool biometricsEnabled,
required final bool hiddenAccount}) = _$_LocalAccount;
@ -261,6 +286,9 @@ abstract class _LocalAccount implements LocalAccount {
@override // The encrypted identity secret that goes with the identityPublicKey
@Uint8ListJsonConverter()
Uint8List get identitySecretKeyBytes;
@override // The salt for the identity secret key encryption
@Uint8ListJsonConverter()
Uint8List get identitySecretSaltBytes;
@override // The kind of encryption input used on the account
EncryptionKeyType get encryptionKeyType;
@override // If account is not hidden, password can be retrieved via

View File

@ -12,6 +12,8 @@ _$_LocalAccount _$$_LocalAccountFromJson(Map<String, dynamic> json) =>
json['identity_master'] as Map<String, dynamic>),
identitySecretKeyBytes: const Uint8ListJsonConverter()
.fromJson(json['identity_secret_key_bytes'] as String),
identitySecretSaltBytes: const Uint8ListJsonConverter()
.fromJson(json['identity_secret_salt_bytes'] as String),
encryptionKeyType:
EncryptionKeyType.fromJson(json['encryption_key_type'] as String),
biometricsEnabled: json['biometrics_enabled'] as bool,
@ -23,6 +25,8 @@ Map<String, dynamic> _$$_LocalAccountToJson(_$_LocalAccount instance) =>
'identity_master': instance.identityMaster.toJson(),
'identity_secret_key_bytes': const Uint8ListJsonConverter()
.toJson(instance.identitySecretKeyBytes),
'identity_secret_salt_bytes': const Uint8ListJsonConverter()
.toJson(instance.identitySecretSaltBytes),
'encryption_key_type': instance.encryptionKeyType.toJson(),
'biometrics_enabled': instance.biometricsEnabled,
'hidden_account': instance.hiddenAccount,

View File

@ -1 +1,126 @@
export 'proto/veilidchat.pb.dart';
import 'dart:typed_data';
import 'package:veilid/veilid.dart';
import 'proto/veilidchat.pb.dart' as proto;
/// CryptoKey protobuf marshaling
///
extension CryptoKeyProto on CryptoKey {
proto.CryptoKey toProto() {
final b = decode();
final out = proto.CryptoKey();
out.u0 = b[0];
out.u1 = b[1];
out.u2 = b[2];
out.u3 = b[3];
out.u4 = b[4];
out.u5 = b[5];
out.u6 = b[6];
out.u7 = b[7];
return out;
}
static CryptoKey fromProto(proto.CryptoKey p) {
final b = Uint8List(8);
b[0] = p.u0;
b[1] = p.u1;
b[2] = p.u2;
b[3] = p.u3;
b[4] = p.u4;
b[5] = p.u5;
b[6] = p.u6;
b[7] = p.u7;
return CryptoKey.fromBytes(b);
}
}
/// Signature protobuf marshaling
///
extension SignatureProto on Signature {
proto.Signature toProto() {
final b = decode();
final out = proto.Signature();
out.u0 = b[0];
out.u1 = b[1];
out.u2 = b[2];
out.u3 = b[3];
out.u4 = b[4];
out.u5 = b[5];
out.u6 = b[6];
out.u7 = b[7];
out.u8 = b[8];
out.u9 = b[9];
out.u10 = b[10];
out.u11 = b[11];
out.u12 = b[12];
out.u13 = b[13];
out.u14 = b[14];
out.u15 = b[15];
return out;
}
static Signature fromProto(proto.Signature p) {
final b = Uint8List(16);
b[0] = p.u0;
b[1] = p.u1;
b[2] = p.u2;
b[3] = p.u3;
b[4] = p.u4;
b[5] = p.u5;
b[6] = p.u6;
b[7] = p.u7;
b[8] = p.u8;
b[9] = p.u9;
b[10] = p.u10;
b[11] = p.u11;
b[12] = p.u12;
b[13] = p.u13;
b[14] = p.u14;
b[15] = p.u15;
return Signature.fromBytes(b);
}
}
/// Nonce protobuf marshaling
///
extension NonceProto on Nonce {
proto.Signature toProto() {
final b = decode();
final out = proto.Signature();
out.u0 = b[0];
out.u1 = b[1];
out.u2 = b[2];
out.u3 = b[3];
out.u4 = b[4];
out.u5 = b[5];
return out;
}
static Nonce fromProto(proto.Nonce p) {
final b = Uint8List(6);
b[0] = p.u0;
b[1] = p.u1;
b[2] = p.u2;
b[3] = p.u3;
b[4] = p.u4;
b[5] = p.u5;
return Nonce.fromBytes(b);
}
}
/// TypedKey protobuf marshaling
///
extension TypedKeyProto on TypedKey {
proto.TypedKey toProto() {
final out = proto.TypedKey();
out.kind = kind;
out.value = value.toProto();
return out;
}
static TypedKey fromProto(proto.TypedKey p) {
return TypedKey(kind: p.kind, value: CryptoKeyProto.fromProto(p.value));
}
}

View File

@ -4,6 +4,7 @@ import 'dart:typed_data';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:veilid/veilid.dart';
import 'package:veilidchat/tools/tools.dart';
import '../entities/entities.dart';
import '../entities/proto.dart' as proto;
@ -80,7 +81,7 @@ class LocalAccountManager {
Uint8List.fromList(utf8.encode(jsonEncode(identityMaster)));
await dhtctx.setDHTValue(masterRecordKey, 0, identityMasterBytes);
// Write empty identity to account map
// Write empty identity to identity dht key
const identity = Identity(accountKeyPairs: {});
final identityBytes =
Uint8List.fromList(utf8.encode(jsonEncode(identity)));
@ -101,12 +102,32 @@ class LocalAccountManager {
}
}
Future<void> updateIdentityKey(
VeilidRoutingContext dhtctx,
TypedKey identityRecordKey,
TypedKey accountRecordKey,
KeyPair accountRecordOwner) async {
// Get existing identity key
ValueData? identityValueData =
await dhtctx.getDHTValue(identityRecordKey, 0, true);
if (identityValueData == null) {
throw const FormatException("Identity does not exist");
}
var identity = identityValueData.readJsonData(Identity.fromJson);
xxx make back to bytes function and do update loop and maybe make that a tool function too (consistentUpdate)
// Update identity key to include account
const identity = Identity(accountKeyPairs: {});
final identityBytes = Uint8List.fromList(utf8.encode(jsonEncode(identity)));
await dhtctx.setDHTValue(identityRec.key, 0, identityBytes);
}
/// Creates a new account associated with master identity
Future<LocalAccount> newAccount(
IdentityMaster identityMaster,
SecretKey identitySecret,
EncryptionKeyType encryptionKeyType,
String encryptionKey) async {
String encryptionKey,
proto.Account account) async {
// Encrypt identitySecret with key
final cs = await Veilid.instance.bestCryptoSystem();
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
@ -126,7 +147,39 @@ class LocalAccountManager {
hiddenAccount: false,
);
// Push
// Add account with profile to DHT
final dhtctx = (await Veilid.instance.routingContext())
.withPrivacy()
.withSequencing(Sequencing.ensureOrdered);
DHTRecordDescriptor? identityRec;
DHTRecordDescriptor? accountRec;
try {
identityRec = await dhtctx.openDHTRecord(identityMaster.identityRecordKey,
identityMaster.identityWriter(identitySecret));
accountRec = await dhtctx.createDHTRecord(const DHTSchema.dflt(oCnt: 1));
final crypto = await Veilid.instance.bestCryptoSystem();
assert(identityRec.key.kind == crypto.kind());
assert(accountRec.key.kind == crypto.kind());
// Write account key
assert(await dhtctx.setDHTValue(
accountRec.key, 0, account.writeToBuffer()) ==
null);
// Update identity key to include account
await updateIdentityKey(dhtctx, identityRec.key, accountRec.key,
KeyPair(key: accountRec.owner, secret: accountRec.ownerSecret!));
} catch (e) {
if (identityRec != null) {
await dhtctx.closeDHTRecord(identityRec.key);
}
if (accountRec != null) {
await dhtctx.deleteDHTRecord(accountRec.key);
}
rethrow;
}
// Add local account object to internal store
// Return local account object
return localAccount;

View File

@ -1 +1,9 @@
export 'external_stream_state.dart';
import 'package:veilid/veilid.dart';
import 'dart:convert';
extension FromValueDataJsonExt on ValueData {
T readJsonData<T>(T Function(Map<String, dynamic>) fromJson) {
return fromJson(jsonDecode(utf8.decode(data)));
}
}

View File

@ -361,18 +361,18 @@ packages:
dependency: "direct dev"
description:
name: freezed
sha256: a9520490532087cf38bf3f7de478ab6ebeb5f68bb1eb2641546d92719b224445
sha256: "2df89855fe181baae3b6d714dc3c4317acf4fccd495a6f36e5e00f24144c6c3b"
url: "https://pub.dev"
source: hosted
version: "2.3.5"
version: "2.4.1"
freezed_annotation:
dependency: "direct main"
description:
name: freezed_annotation
sha256: aeac15850ef1b38ee368d4c53ba9a847e900bb2c53a4db3f6881cbb3cb684338
sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.4.1"
frontend_server_client:
dependency: transitive
description:
@ -401,10 +401,10 @@ packages:
dependency: "direct main"
description:
name: go_router
sha256: df5bc28ec7d2e2a82ece59fb33b3a0f3b3eaaae386bf32040f92339820d954b6
sha256: b33a88c67816312597e5e0f5906c5139a0b9bd9bb137346e872c788da7af8ea0
url: "https://pub.dev"
source: hosted
version: "9.0.1"
version: "9.0.3"
graphs:
dependency: transitive
description:
@ -569,10 +569,10 @@ packages:
dependency: transitive
description:
name: path_provider_foundation
sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3"
sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
version: "2.2.4"
path_provider_linux:
dependency: transitive
description:
@ -621,14 +621,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.1"
process:
dependency: transitive
description:
name: process
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
url: "https://pub.dev"
source: hosted
version: "4.2.4"
protobuf:
dependency: "direct main"
description:
@ -721,10 +713,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "0dc5c49ad8a05ed358b991b60c7b0ba1a14e16dae58af9b420d6b9e82dc024ab"
sha256: f39696b83e844923b642ce9dd4bd31736c17e697f6731a5adf445b1274cf3cd4
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.3.2"
shared_preferences_linux:
dependency: transitive
description:
@ -912,7 +904,7 @@ packages:
path: "../veilid/veilid-flutter"
relative: true
source: path
version: "0.0.1"
version: "0.1.1"
watcher:
dependency: transitive
description:
@ -941,10 +933,10 @@ packages:
dependency: transitive
description:
name: xdg_directories
sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1
sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff
url: "https://pub.dev"
source: hosted
version: "1.0.0"
version: "1.0.1"
yaml:
dependency: transitive
description: