feat(useragent): initial connection impl

This commit is contained in:
hdbg
2026-03-15 16:53:49 +01:00
parent 27836beb75
commit c61a9e30ac
28 changed files with 688 additions and 225 deletions

View File

@@ -16,5 +16,5 @@ sources = ['protobufs/*.proto']
outputs = ['useragent/lib/proto/*'] outputs = ['useragent/lib/proto/*']
run = ''' run = '''
dart pub global activate protoc_plugin && \ dart pub global activate protoc_plugin && \
protoc --dart_out=useragent/lib/proto --proto_path=protobufs/ protobufs/*.proto protoc --dart_out=grpc:useragent/lib/proto --proto_path=protobufs/ protobufs/*.proto
''' '''

View File

@@ -1,3 +1,132 @@
import 'dart:async';
import 'dart:convert';
import 'package:arbiter/features/connection/server_info_storage.dart';
import 'package:arbiter/features/identity/pk_manager.dart';
import 'package:arbiter/proto/arbiter.pbgrpc.dart';
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:grpc/grpc.dart';
import 'package:mtcore/markettakers.dart';
class Connection {
final ClientChannel channel;
final StreamController<UserAgentRequest> _tx;
final StreamIterator<UserAgentResponse> _rx;
Connection({
required this.channel,
required StreamController<UserAgentRequest> tx,
required ResponseStream<UserAgentResponse> rx,
}) : _tx = tx,
_rx = StreamIterator(rx);
Future<void> send(UserAgentRequest request) async {
_tx.add(request);
}
Future<UserAgentResponse> receive() async {
await _rx.moveNext();
return _rx.current;
}
Future<void> close() async {
await _tx.close();
await channel.shutdown();
}
}
Future<Connection> _connect(StoredServerInfo serverInfo) async {
final channel = ClientChannel(
serverInfo.address,
port: serverInfo.port,
options: ChannelOptions(
connectTimeout: const Duration(seconds: 10),
credentials: ChannelCredentials.secure(
onBadCertificate: (cert, host) {
return true;
},
),
),
);
final client = ArbiterServiceClient(channel);
final tx = StreamController<UserAgentRequest>();
final rx = client.userAgent(tx.stream);
return Connection(channel: channel, tx: tx, rx: rx);
}
List<int> formatChallenge(AuthChallenge challenge, List<int> pubkey) {
final encodedPubkey = base64Encode(pubkey);
final payload = "${challenge.nonce}:$encodedPubkey";
return utf8.encode(payload);
}
Future<Connection> connectAndAuthorize(
StoredServerInfo serverInfo,
KeyHandle key, {
String? bootstrapToken,
}) async {
try {
final connection = await _connect(serverInfo);
talker.info(
'Connected to server at ${serverInfo.address}:${serverInfo.port}',
);
final pubkey = await key.getPublicKey();
final req = AuthChallengeRequest(
pubkey: pubkey,
bootstrapToken: bootstrapToken,
keyType: switch (key.alg) {
KeyAlgorithm.rsa => KeyType.KEY_TYPE_RSA,
KeyAlgorithm.ecdsa => KeyType.KEY_TYPE_ECDSA_SECP256K1,
KeyAlgorithm.ed25519 => KeyType.KEY_TYPE_ED25519,
},
);
await connection.send(UserAgentRequest(authChallengeRequest: req));
talker.info(
"Sent auth challenge request with pubkey ${base64Encode(pubkey)}",
);
class Connection {} final response = await connection.receive();
talker.info(
'Received response from server, checking for auth challenge...',
);
if (!response.hasAuthChallenge()) {
throw Exception(
'Expected AuthChallengeResponse, got ${response.whichPayload()}',
);
}
final challenge = formatChallenge(response.authChallenge, pubkey);
talker.info(
'Received auth challenge, signing with key ${base64Encode(pubkey)}',
);
final signature = await key.sign(challenge);
final solutionReq = AuthChallengeSolution(signature: signature);
await connection.send(UserAgentRequest(authChallengeSolution: solutionReq));
talker.info('Sent auth challenge solution, waiting for server response...');
final solutionResponse = await connection.receive();
if (!solutionResponse.hasAuthOk()) {
throw Exception(
'Expected AuthChallengeSolutionResponse, got ${solutionResponse.whichPayload()}',
);
}
talker.info('Authentication successful, connection established');
return connection;
} catch (e) {
throw Exception('Failed to connect to server: $e');
}
}

View File

@@ -1,7 +1,11 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:json_annotation/json_annotation.dart';
part 'server_info_storage.g.dart';
@JsonSerializable()
class StoredServerInfo { class StoredServerInfo {
const StoredServerInfo({ const StoredServerInfo({
required this.address, required this.address,
@@ -13,19 +17,9 @@ class StoredServerInfo {
final int port; final int port;
final String caCertFingerprint; final String caCertFingerprint;
Map<String, dynamic> toJson() => { factory StoredServerInfo.fromJson(Map<String, dynamic> json) => _$StoredServerInfoFromJson(json);
'address': address, Map<String, dynamic> toJson() => _$StoredServerInfoToJson(this);
'port': port,
'caCertFingerprint': caCertFingerprint,
};
factory StoredServerInfo.fromJson(Map<String, dynamic> json) {
return StoredServerInfo(
address: json['address'] as String,
port: json['port'] as int,
caCertFingerprint: json['caCertFingerprint'] as String,
);
}
} }
abstract class ServerInfoStorage { abstract class ServerInfoStorage {

View File

@@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'server_info_storage.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
StoredServerInfo _$StoredServerInfoFromJson(Map<String, dynamic> json) =>
StoredServerInfo(
address: json['address'] as String,
port: (json['port'] as num).toInt(),
caCertFingerprint: json['caCertFingerprint'] as String,
);
Map<String, dynamic> _$StoredServerInfoToJson(StoredServerInfo instance) =>
<String, dynamic>{
'address': instance.address,
'port': instance.port,
'caCertFingerprint': instance.caCertFingerprint,
};

View File

@@ -2,7 +2,7 @@ import 'dart:convert';
import 'package:cryptography/cryptography.dart'; import 'package:cryptography/cryptography.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:arbiter/features/pk_manager.dart'; import 'package:arbiter/features/identity/pk_manager.dart';
final storage = FlutterSecureStorage( final storage = FlutterSecureStorage(
aOptions: AndroidOptions.biometric( aOptions: AndroidOptions.biometric(
@@ -16,7 +16,6 @@ final storage = FlutterSecureStorage(
synchronizable: false, synchronizable: false,
accessControlFlags: [ accessControlFlags: [
AccessControlFlag.userPresence, AccessControlFlag.userPresence,
AccessControlFlag.privateKeyUsage,
], ],
usesDataProtectionKeychain: true, usesDataProtectionKeychain: true,
), ),

View File

@@ -10,14 +10,10 @@
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes // ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports // ignore_for_file: non_constant_identifier_names, prefer_relative_imports
import 'dart:async' as $async;
import 'dart:core' as $core; import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb; import 'package:protobuf/protobuf.dart' as $pb;
import 'client.pb.dart' as $0;
import 'user_agent.pb.dart' as $1;
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
class ServerInfo extends $pb.GeneratedMessage { class ServerInfo extends $pb.GeneratedMessage {
@@ -86,21 +82,6 @@ class ServerInfo extends $pb.GeneratedMessage {
void clearCertPublicKey() => $_clearField(2); void clearCertPublicKey() => $_clearField(2);
} }
class ArbiterServiceApi {
final $pb.RpcClient _client;
ArbiterServiceApi(this._client);
$async.Future<$0.ClientResponse> client(
$pb.ClientContext? ctx, $0.ClientRequest request) =>
_client.invoke<$0.ClientResponse>(
ctx, 'ArbiterService', 'Client', request, $0.ClientResponse());
$async.Future<$1.UserAgentResponse> userAgent(
$pb.ClientContext? ctx, $1.UserAgentRequest request) =>
_client.invoke<$1.UserAgentResponse>(
ctx, 'ArbiterService', 'UserAgent', request, $1.UserAgentResponse());
}
const $core.bool _omitFieldNames = const $core.bool _omitFieldNames =
$core.bool.fromEnvironment('protobuf.omit_field_names'); $core.bool.fromEnvironment('protobuf.omit_field_names');
const $core.bool _omitMessageNames = const $core.bool _omitMessageNames =

View File

@@ -0,0 +1,90 @@
// This is a generated file - do not edit.
//
// Generated from arbiter.proto.
// @dart = 3.3
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
import 'dart:async' as $async;
import 'dart:core' as $core;
import 'package:grpc/service_api.dart' as $grpc;
import 'package:protobuf/protobuf.dart' as $pb;
import 'client.pb.dart' as $0;
import 'user_agent.pb.dart' as $1;
export 'arbiter.pb.dart';
@$pb.GrpcServiceName('arbiter.ArbiterService')
class ArbiterServiceClient extends $grpc.Client {
/// The hostname for this service.
static const $core.String defaultHost = '';
/// OAuth scopes needed for the client.
static const $core.List<$core.String> oauthScopes = [
'',
];
ArbiterServiceClient(super.channel, {super.options, super.interceptors});
$grpc.ResponseStream<$0.ClientResponse> client(
$async.Stream<$0.ClientRequest> request, {
$grpc.CallOptions? options,
}) {
return $createStreamingCall(_$client, request, options: options);
}
$grpc.ResponseStream<$1.UserAgentResponse> userAgent(
$async.Stream<$1.UserAgentRequest> request, {
$grpc.CallOptions? options,
}) {
return $createStreamingCall(_$userAgent, request, options: options);
}
// method descriptors
static final _$client =
$grpc.ClientMethod<$0.ClientRequest, $0.ClientResponse>(
'/arbiter.ArbiterService/Client',
($0.ClientRequest value) => value.writeToBuffer(),
$0.ClientResponse.fromBuffer);
static final _$userAgent =
$grpc.ClientMethod<$1.UserAgentRequest, $1.UserAgentResponse>(
'/arbiter.ArbiterService/UserAgent',
($1.UserAgentRequest value) => value.writeToBuffer(),
$1.UserAgentResponse.fromBuffer);
}
@$pb.GrpcServiceName('arbiter.ArbiterService')
abstract class ArbiterServiceBase extends $grpc.Service {
$core.String get $name => 'arbiter.ArbiterService';
ArbiterServiceBase() {
$addMethod($grpc.ServiceMethod<$0.ClientRequest, $0.ClientResponse>(
'Client',
client,
true,
true,
($core.List<$core.int> value) => $0.ClientRequest.fromBuffer(value),
($0.ClientResponse value) => value.writeToBuffer()));
$addMethod($grpc.ServiceMethod<$1.UserAgentRequest, $1.UserAgentResponse>(
'UserAgent',
userAgent,
true,
true,
($core.List<$core.int> value) => $1.UserAgentRequest.fromBuffer(value),
($1.UserAgentResponse value) => value.writeToBuffer()));
}
$async.Stream<$0.ClientResponse> client(
$grpc.ServiceCall call, $async.Stream<$0.ClientRequest> request);
$async.Stream<$1.UserAgentResponse> userAgent(
$grpc.ServiceCall call, $async.Stream<$1.UserAgentRequest> request);
}

View File

@@ -15,15 +15,6 @@ import 'dart:convert' as $convert;
import 'dart:core' as $core; import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data; import 'dart:typed_data' as $typed_data;
import 'package:protobuf/well_known_types/google/protobuf/empty.pbjson.dart'
as $3;
import 'package:protobuf/well_known_types/google/protobuf/timestamp.pbjson.dart'
as $4;
import 'client.pbjson.dart' as $0;
import 'evm.pbjson.dart' as $2;
import 'user_agent.pbjson.dart' as $1;
@$core.Deprecated('Use serverInfoDescriptor instead') @$core.Deprecated('Use serverInfoDescriptor instead')
const ServerInfo$json = { const ServerInfo$json = {
'1': 'ServerInfo', '1': 'ServerInfo',
@@ -37,88 +28,3 @@ const ServerInfo$json = {
final $typed_data.Uint8List serverInfoDescriptor = $convert.base64Decode( final $typed_data.Uint8List serverInfoDescriptor = $convert.base64Decode(
'CgpTZXJ2ZXJJbmZvEhgKB3ZlcnNpb24YASABKAlSB3ZlcnNpb24SJgoPY2VydF9wdWJsaWNfa2' 'CgpTZXJ2ZXJJbmZvEhgKB3ZlcnNpb24YASABKAlSB3ZlcnNpb24SJgoPY2VydF9wdWJsaWNfa2'
'V5GAIgASgMUg1jZXJ0UHVibGljS2V5'); 'V5GAIgASgMUg1jZXJ0UHVibGljS2V5');
const $core.Map<$core.String, $core.dynamic> ArbiterServiceBase$json = {
'1': 'ArbiterService',
'2': [
{
'1': 'Client',
'2': '.arbiter.client.ClientRequest',
'3': '.arbiter.client.ClientResponse',
'5': true,
'6': true
},
{
'1': 'UserAgent',
'2': '.arbiter.user_agent.UserAgentRequest',
'3': '.arbiter.user_agent.UserAgentResponse',
'5': true,
'6': true
},
],
};
@$core.Deprecated('Use arbiterServiceDescriptor instead')
const $core.Map<$core.String, $core.Map<$core.String, $core.dynamic>>
ArbiterServiceBase$messageJson = {
'.arbiter.client.ClientRequest': $0.ClientRequest$json,
'.arbiter.client.AuthChallengeRequest': $0.AuthChallengeRequest$json,
'.arbiter.client.AuthChallengeSolution': $0.AuthChallengeSolution$json,
'.arbiter.client.ClientResponse': $0.ClientResponse$json,
'.arbiter.client.AuthChallenge': $0.AuthChallenge$json,
'.arbiter.client.AuthOk': $0.AuthOk$json,
'.arbiter.evm.EvmSignTransactionResponse': $2.EvmSignTransactionResponse$json,
'.arbiter.evm.TransactionEvalError': $2.TransactionEvalError$json,
'.google.protobuf.Empty': $3.Empty$json,
'.arbiter.evm.NoMatchingGrantError': $2.NoMatchingGrantError$json,
'.arbiter.evm.SpecificMeaning': $2.SpecificMeaning$json,
'.arbiter.evm.EtherTransferMeaning': $2.EtherTransferMeaning$json,
'.arbiter.evm.TokenTransferMeaning': $2.TokenTransferMeaning$json,
'.arbiter.evm.TokenInfo': $2.TokenInfo$json,
'.arbiter.evm.PolicyViolationsError': $2.PolicyViolationsError$json,
'.arbiter.evm.EvalViolation': $2.EvalViolation$json,
'.arbiter.evm.GasLimitExceededViolation': $2.GasLimitExceededViolation$json,
'.arbiter.evm.EvmAnalyzeTransactionResponse':
$2.EvmAnalyzeTransactionResponse$json,
'.arbiter.client.ClientConnectError': $0.ClientConnectError$json,
'.arbiter.user_agent.UserAgentRequest': $1.UserAgentRequest$json,
'.arbiter.user_agent.AuthChallengeRequest': $1.AuthChallengeRequest$json,
'.arbiter.user_agent.AuthChallengeSolution': $1.AuthChallengeSolution$json,
'.arbiter.user_agent.UnsealStart': $1.UnsealStart$json,
'.arbiter.user_agent.UnsealEncryptedKey': $1.UnsealEncryptedKey$json,
'.arbiter.evm.EvmGrantCreateRequest': $2.EvmGrantCreateRequest$json,
'.arbiter.evm.SharedSettings': $2.SharedSettings$json,
'.google.protobuf.Timestamp': $4.Timestamp$json,
'.arbiter.evm.TransactionRateLimit': $2.TransactionRateLimit$json,
'.arbiter.evm.SpecificGrant': $2.SpecificGrant$json,
'.arbiter.evm.EtherTransferSettings': $2.EtherTransferSettings$json,
'.arbiter.evm.VolumeRateLimit': $2.VolumeRateLimit$json,
'.arbiter.evm.TokenTransferSettings': $2.TokenTransferSettings$json,
'.arbiter.evm.EvmGrantDeleteRequest': $2.EvmGrantDeleteRequest$json,
'.arbiter.evm.EvmGrantListRequest': $2.EvmGrantListRequest$json,
'.arbiter.user_agent.ClientConnectionResponse':
$1.ClientConnectionResponse$json,
'.arbiter.user_agent.UserAgentResponse': $1.UserAgentResponse$json,
'.arbiter.user_agent.AuthChallenge': $1.AuthChallenge$json,
'.arbiter.user_agent.AuthOk': $1.AuthOk$json,
'.arbiter.user_agent.UnsealStartResponse': $1.UnsealStartResponse$json,
'.arbiter.evm.WalletCreateResponse': $2.WalletCreateResponse$json,
'.arbiter.evm.WalletEntry': $2.WalletEntry$json,
'.arbiter.evm.WalletListResponse': $2.WalletListResponse$json,
'.arbiter.evm.WalletList': $2.WalletList$json,
'.arbiter.evm.EvmGrantCreateResponse': $2.EvmGrantCreateResponse$json,
'.arbiter.evm.EvmGrantDeleteResponse': $2.EvmGrantDeleteResponse$json,
'.arbiter.evm.EvmGrantListResponse': $2.EvmGrantListResponse$json,
'.arbiter.evm.EvmGrantList': $2.EvmGrantList$json,
'.arbiter.evm.GrantEntry': $2.GrantEntry$json,
'.arbiter.user_agent.ClientConnectionRequest':
$1.ClientConnectionRequest$json,
'.arbiter.user_agent.ClientConnectionCancel': $1.ClientConnectionCancel$json,
};
/// Descriptor for `ArbiterService`. Decode as a `google.protobuf.ServiceDescriptorProto`.
final $typed_data.Uint8List arbiterServiceDescriptor = $convert.base64Decode(
'Cg5BcmJpdGVyU2VydmljZRJLCgZDbGllbnQSHS5hcmJpdGVyLmNsaWVudC5DbGllbnRSZXF1ZX'
'N0Gh4uYXJiaXRlci5jbGllbnQuQ2xpZW50UmVzcG9uc2UoATABElwKCVVzZXJBZ2VudBIkLmFy'
'Yml0ZXIudXNlcl9hZ2VudC5Vc2VyQWdlbnRSZXF1ZXN0GiUuYXJiaXRlci51c2VyX2FnZW50Ll'
'VzZXJBZ2VudFJlc3BvbnNlKAEwAQ==');

View File

@@ -1,56 +0,0 @@
// This is a generated file - do not edit.
//
// Generated from arbiter.proto.
// @dart = 3.3
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
import 'dart:async' as $async;
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
import 'arbiter.pbjson.dart';
import 'client.pb.dart' as $0;
import 'user_agent.pb.dart' as $1;
export 'arbiter.pb.dart';
abstract class ArbiterServiceBase extends $pb.GeneratedService {
$async.Future<$0.ClientResponse> client(
$pb.ServerContext ctx, $0.ClientRequest request);
$async.Future<$1.UserAgentResponse> userAgent(
$pb.ServerContext ctx, $1.UserAgentRequest request);
$pb.GeneratedMessage createRequest($core.String methodName) {
switch (methodName) {
case 'Client':
return $0.ClientRequest();
case 'UserAgent':
return $1.UserAgentRequest();
default:
throw $core.ArgumentError('Unknown method: $methodName');
}
}
$async.Future<$pb.GeneratedMessage> handleCall($pb.ServerContext ctx,
$core.String methodName, $pb.GeneratedMessage request) {
switch (methodName) {
case 'Client':
return client(ctx, request as $0.ClientRequest);
case 'UserAgent':
return userAgent(ctx, request as $1.UserAgentRequest);
default:
throw $core.ArgumentError('Unknown method: $methodName');
}
}
$core.Map<$core.String, $core.dynamic> get $json => ArbiterServiceBase$json;
$core.Map<$core.String, $core.Map<$core.String, $core.dynamic>>
get $messageJson => ArbiterServiceBase$messageJson;
}

View File

@@ -26,10 +26,12 @@ class AuthChallengeRequest extends $pb.GeneratedMessage {
factory AuthChallengeRequest({ factory AuthChallengeRequest({
$core.List<$core.int>? pubkey, $core.List<$core.int>? pubkey,
$core.String? bootstrapToken, $core.String? bootstrapToken,
KeyType? keyType,
}) { }) {
final result = create(); final result = create();
if (pubkey != null) result.pubkey = pubkey; if (pubkey != null) result.pubkey = pubkey;
if (bootstrapToken != null) result.bootstrapToken = bootstrapToken; if (bootstrapToken != null) result.bootstrapToken = bootstrapToken;
if (keyType != null) result.keyType = keyType;
return result; return result;
} }
@@ -50,6 +52,8 @@ class AuthChallengeRequest extends $pb.GeneratedMessage {
..a<$core.List<$core.int>>( ..a<$core.List<$core.int>>(
1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY) 1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY)
..aOS(2, _omitFieldNames ? '' : 'bootstrapToken') ..aOS(2, _omitFieldNames ? '' : 'bootstrapToken')
..aE<KeyType>(3, _omitFieldNames ? '' : 'keyType',
enumValues: KeyType.values)
..hasRequiredFields = false; ..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@@ -88,6 +92,15 @@ class AuthChallengeRequest extends $pb.GeneratedMessage {
$core.bool hasBootstrapToken() => $_has(1); $core.bool hasBootstrapToken() => $_has(1);
@$pb.TagNumber(2) @$pb.TagNumber(2)
void clearBootstrapToken() => $_clearField(2); void clearBootstrapToken() => $_clearField(2);
@$pb.TagNumber(3)
KeyType get keyType => $_getN(2);
@$pb.TagNumber(3)
set keyType(KeyType value) => $_setField(3, value);
@$pb.TagNumber(3)
$core.bool hasKeyType() => $_has(2);
@$pb.TagNumber(3)
void clearKeyType() => $_clearField(3);
} }
class AuthChallenge extends $pb.GeneratedMessage { class AuthChallenge extends $pb.GeneratedMessage {

View File

@@ -14,6 +14,31 @@ import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb; import 'package:protobuf/protobuf.dart' as $pb;
class KeyType extends $pb.ProtobufEnum {
static const KeyType KEY_TYPE_UNSPECIFIED =
KeyType._(0, _omitEnumNames ? '' : 'KEY_TYPE_UNSPECIFIED');
static const KeyType KEY_TYPE_ED25519 =
KeyType._(1, _omitEnumNames ? '' : 'KEY_TYPE_ED25519');
static const KeyType KEY_TYPE_ECDSA_SECP256K1 =
KeyType._(2, _omitEnumNames ? '' : 'KEY_TYPE_ECDSA_SECP256K1');
static const KeyType KEY_TYPE_RSA =
KeyType._(3, _omitEnumNames ? '' : 'KEY_TYPE_RSA');
static const $core.List<KeyType> values = <KeyType>[
KEY_TYPE_UNSPECIFIED,
KEY_TYPE_ED25519,
KEY_TYPE_ECDSA_SECP256K1,
KEY_TYPE_RSA,
];
static final $core.List<KeyType?> _byValue =
$pb.ProtobufEnum.$_initByValueList(values, 3);
static KeyType? valueOf($core.int value) =>
value < 0 || value >= _byValue.length ? null : _byValue[value];
const KeyType._(super.value, super.name);
}
class UnsealResult extends $pb.ProtobufEnum { class UnsealResult extends $pb.ProtobufEnum {
static const UnsealResult UNSEAL_RESULT_UNSPECIFIED = static const UnsealResult UNSEAL_RESULT_UNSPECIFIED =
UnsealResult._(0, _omitEnumNames ? '' : 'UNSEAL_RESULT_UNSPECIFIED'); UnsealResult._(0, _omitEnumNames ? '' : 'UNSEAL_RESULT_UNSPECIFIED');

View File

@@ -15,6 +15,22 @@ import 'dart:convert' as $convert;
import 'dart:core' as $core; import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data; import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use keyTypeDescriptor instead')
const KeyType$json = {
'1': 'KeyType',
'2': [
{'1': 'KEY_TYPE_UNSPECIFIED', '2': 0},
{'1': 'KEY_TYPE_ED25519', '2': 1},
{'1': 'KEY_TYPE_ECDSA_SECP256K1', '2': 2},
{'1': 'KEY_TYPE_RSA', '2': 3},
],
};
/// Descriptor for `KeyType`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List keyTypeDescriptor = $convert.base64Decode(
'CgdLZXlUeXBlEhgKFEtFWV9UWVBFX1VOU1BFQ0lGSUVEEAASFAoQS0VZX1RZUEVfRUQyNTUxOR'
'ABEhwKGEtFWV9UWVBFX0VDRFNBX1NFQ1AyNTZLMRACEhAKDEtFWV9UWVBFX1JTQRAD');
@$core.Deprecated('Use unsealResultDescriptor instead') @$core.Deprecated('Use unsealResultDescriptor instead')
const UnsealResult$json = { const UnsealResult$json = {
'1': 'UnsealResult', '1': 'UnsealResult',
@@ -64,6 +80,14 @@ const AuthChallengeRequest$json = {
'10': 'bootstrapToken', '10': 'bootstrapToken',
'17': true '17': true
}, },
{
'1': 'key_type',
'3': 3,
'4': 1,
'5': 14,
'6': '.arbiter.user_agent.KeyType',
'10': 'keyType'
},
], ],
'8': [ '8': [
{'1': '_bootstrap_token'}, {'1': '_bootstrap_token'},
@@ -73,8 +97,9 @@ const AuthChallengeRequest$json = {
/// Descriptor for `AuthChallengeRequest`. Decode as a `google.protobuf.DescriptorProto`. /// Descriptor for `AuthChallengeRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List authChallengeRequestDescriptor = $convert.base64Decode( final $typed_data.Uint8List authChallengeRequestDescriptor = $convert.base64Decode(
'ChRBdXRoQ2hhbGxlbmdlUmVxdWVzdBIWCgZwdWJrZXkYASABKAxSBnB1YmtleRIsCg9ib290c3' 'ChRBdXRoQ2hhbGxlbmdlUmVxdWVzdBIWCgZwdWJrZXkYASABKAxSBnB1YmtleRIsCg9ib290c3'
'RyYXBfdG9rZW4YAiABKAlIAFIOYm9vdHN0cmFwVG9rZW6IAQFCEgoQX2Jvb3RzdHJhcF90b2tl' 'RyYXBfdG9rZW4YAiABKAlIAFIOYm9vdHN0cmFwVG9rZW6IAQESNgoIa2V5X3R5cGUYAyABKA4y'
'bg=='); 'Gy5hcmJpdGVyLnVzZXJfYWdlbnQuS2V5VHlwZVIHa2V5VHlwZUISChBfYm9vdHN0cmFwX3Rva2'
'Vu');
@$core.Deprecated('Use authChallengeDescriptor instead') @$core.Deprecated('Use authChallengeDescriptor instead')
const AuthChallenge$json = { const AuthChallenge$json = {

View File

@@ -0,0 +1,22 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'bootstrap_token.g.dart';
@Riverpod(keepAlive: true)
class BootstrapToken extends _$BootstrapToken {
String? build() {
return null;
}
void set(String token) {
state = token;
}
String? take() {
final token = state;
state = null;
return token;
}
}

View File

@@ -0,0 +1,62 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'bootstrap_token.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(BootstrapToken)
final bootstrapTokenProvider = BootstrapTokenProvider._();
final class BootstrapTokenProvider
extends $NotifierProvider<BootstrapToken, String?> {
BootstrapTokenProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'bootstrapTokenProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$bootstrapTokenHash();
@$internal
@override
BootstrapToken create() => BootstrapToken();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(String? value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<String?>(value),
);
}
}
String _$bootstrapTokenHash() => r'a59e679ab0561ed2ab4148660499891571d439db';
abstract class _$BootstrapToken extends $Notifier<String?> {
String? build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<String?, String?>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<String?, String?>,
String?,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}

View File

@@ -0,0 +1,39 @@
import 'package:arbiter/features/connection/connection.dart';
import 'package:arbiter/providers/connection/bootstrap_token.dart';
import 'package:arbiter/providers/key.dart';
import 'package:arbiter/providers/server_info.dart';
import 'package:mtcore/markettakers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'connection_manager.g.dart';
@Riverpod(keepAlive: true)
class ConnectionManager extends _$ConnectionManager {
@override
Future<Connection?> build() async {
final serverInfo = await ref.watch(serverInfoProvider.future);
final key = await ref.watch(keyProvider.future);
final token = ref.watch(bootstrapTokenProvider);
if (serverInfo == null || key == null) {
return null;
}
final Connection connection;
try {
connection = await connectAndAuthorize(serverInfo, key, bootstrapToken: token);
} catch (e) {
talker.handle(e);
rethrow;
}
ref.onDispose(() {
final connection = state.asData?.value;
if (connection != null) {
connection.close();
}
});
return connection;
}
}

View File

@@ -0,0 +1,54 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'connection_manager.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(ConnectionManager)
final connectionManagerProvider = ConnectionManagerProvider._();
final class ConnectionManagerProvider
extends $AsyncNotifierProvider<ConnectionManager, Connection?> {
ConnectionManagerProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'connectionManagerProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$connectionManagerHash();
@$internal
@override
ConnectionManager create() => ConnectionManager();
}
String _$connectionManagerHash() => r'8923346dff75a9a06127c71a0a39ca65d9733d8c';
abstract class _$ConnectionManager extends $AsyncNotifier<Connection?> {
FutureOr<Connection?> build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<AsyncValue<Connection?>, Connection?>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AsyncValue<Connection?>, Connection?>,
AsyncValue<Connection?>,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}

View File

@@ -1,7 +1,7 @@
import 'package:mtcore/markettakers.dart'; import 'package:mtcore/markettakers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:arbiter/features/pk_manager.dart'; import 'package:arbiter/features/identity/pk_manager.dart';
import 'package:arbiter/features/simple_ed25519.dart'; import 'package:arbiter/features/identity/simple_ed25519.dart';
part 'key.g.dart'; part 'key.g.dart';

View File

@@ -1,4 +1,4 @@
import 'package:arbiter/features/server_info_storage.dart'; import 'package:arbiter/features/connection/server_info_storage.dart';
import 'package:cryptography/cryptography.dart'; import 'package:cryptography/cryptography.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';

View File

@@ -8,6 +8,7 @@ class Router extends RootStackRouter {
List<AutoRoute> get routes => [ List<AutoRoute> get routes => [
AutoRoute(page: Bootstrap.page, path: '/bootstrap', initial: true), AutoRoute(page: Bootstrap.page, path: '/bootstrap', initial: true),
AutoRoute(page: ServerInfoSetupRoute.page, path: '/server-info'), AutoRoute(page: ServerInfoSetupRoute.page, path: '/server-info'),
AutoRoute(page: ServerConnectionRoute.page, path: '/server-connection'),
AutoRoute( AutoRoute(
page: DashboardRouter.page, page: DashboardRouter.page,

View File

@@ -13,18 +13,20 @@ import 'package:arbiter/screens/bootstrap.dart' as _i2;
import 'package:arbiter/screens/dashboard.dart' as _i4; import 'package:arbiter/screens/dashboard.dart' as _i4;
import 'package:arbiter/screens/dashboard/about.dart' as _i1; import 'package:arbiter/screens/dashboard/about.dart' as _i1;
import 'package:arbiter/screens/dashboard/calc.dart' as _i3; import 'package:arbiter/screens/dashboard/calc.dart' as _i3;
import 'package:arbiter/screens/server_info_setup.dart' as _i5; import 'package:arbiter/screens/server_connection.dart' as _i5;
import 'package:auto_route/auto_route.dart' as _i6; import 'package:arbiter/screens/server_info_setup.dart' as _i6;
import 'package:auto_route/auto_route.dart' as _i7;
import 'package:flutter/material.dart' as _i8;
/// generated route for /// generated route for
/// [_i1.AboutScreen] /// [_i1.AboutScreen]
class AboutRoute extends _i6.PageRouteInfo<void> { class AboutRoute extends _i7.PageRouteInfo<void> {
const AboutRoute({List<_i6.PageRouteInfo>? children}) const AboutRoute({List<_i7.PageRouteInfo>? children})
: super(AboutRoute.name, initialChildren: children); : super(AboutRoute.name, initialChildren: children);
static const String name = 'AboutRoute'; static const String name = 'AboutRoute';
static _i6.PageInfo page = _i6.PageInfo( static _i7.PageInfo page = _i7.PageInfo(
name, name,
builder: (data) { builder: (data) {
return const _i1.AboutScreen(); return const _i1.AboutScreen();
@@ -34,13 +36,13 @@ class AboutRoute extends _i6.PageRouteInfo<void> {
/// generated route for /// generated route for
/// [_i2.Bootstrap] /// [_i2.Bootstrap]
class Bootstrap extends _i6.PageRouteInfo<void> { class Bootstrap extends _i7.PageRouteInfo<void> {
const Bootstrap({List<_i6.PageRouteInfo>? children}) const Bootstrap({List<_i7.PageRouteInfo>? children})
: super(Bootstrap.name, initialChildren: children); : super(Bootstrap.name, initialChildren: children);
static const String name = 'Bootstrap'; static const String name = 'Bootstrap';
static _i6.PageInfo page = _i6.PageInfo( static _i7.PageInfo page = _i7.PageInfo(
name, name,
builder: (data) { builder: (data) {
return const _i2.Bootstrap(); return const _i2.Bootstrap();
@@ -50,13 +52,13 @@ class Bootstrap extends _i6.PageRouteInfo<void> {
/// generated route for /// generated route for
/// [_i3.CalcScreen] /// [_i3.CalcScreen]
class CalcRoute extends _i6.PageRouteInfo<void> { class CalcRoute extends _i7.PageRouteInfo<void> {
const CalcRoute({List<_i6.PageRouteInfo>? children}) const CalcRoute({List<_i7.PageRouteInfo>? children})
: super(CalcRoute.name, initialChildren: children); : super(CalcRoute.name, initialChildren: children);
static const String name = 'CalcRoute'; static const String name = 'CalcRoute';
static _i6.PageInfo page = _i6.PageInfo( static _i7.PageInfo page = _i7.PageInfo(
name, name,
builder: (data) { builder: (data) {
return const _i3.CalcScreen(); return const _i3.CalcScreen();
@@ -66,13 +68,13 @@ class CalcRoute extends _i6.PageRouteInfo<void> {
/// generated route for /// generated route for
/// [_i4.DashboardRouter] /// [_i4.DashboardRouter]
class DashboardRouter extends _i6.PageRouteInfo<void> { class DashboardRouter extends _i7.PageRouteInfo<void> {
const DashboardRouter({List<_i6.PageRouteInfo>? children}) const DashboardRouter({List<_i7.PageRouteInfo>? children})
: super(DashboardRouter.name, initialChildren: children); : super(DashboardRouter.name, initialChildren: children);
static const String name = 'DashboardRouter'; static const String name = 'DashboardRouter';
static _i6.PageInfo page = _i6.PageInfo( static _i7.PageInfo page = _i7.PageInfo(
name, name,
builder: (data) { builder: (data) {
return const _i4.DashboardRouter(); return const _i4.DashboardRouter();
@@ -81,17 +83,70 @@ class DashboardRouter extends _i6.PageRouteInfo<void> {
} }
/// generated route for /// generated route for
/// [_i5.ServerInfoSetupScreen] /// [_i5.ServerConnectionScreen]
class ServerInfoSetupRoute extends _i6.PageRouteInfo<void> { class ServerConnectionRoute
const ServerInfoSetupRoute({List<_i6.PageRouteInfo>? children}) extends _i7.PageRouteInfo<ServerConnectionRouteArgs> {
ServerConnectionRoute({
_i8.Key? key,
String? arbiterUrl,
List<_i7.PageRouteInfo>? children,
}) : super(
ServerConnectionRoute.name,
args: ServerConnectionRouteArgs(key: key, arbiterUrl: arbiterUrl),
initialChildren: children,
);
static const String name = 'ServerConnectionRoute';
static _i7.PageInfo page = _i7.PageInfo(
name,
builder: (data) {
final args = data.argsAs<ServerConnectionRouteArgs>(
orElse: () => const ServerConnectionRouteArgs(),
);
return _i5.ServerConnectionScreen(
key: args.key,
arbiterUrl: args.arbiterUrl,
);
},
);
}
class ServerConnectionRouteArgs {
const ServerConnectionRouteArgs({this.key, this.arbiterUrl});
final _i8.Key? key;
final String? arbiterUrl;
@override
String toString() {
return 'ServerConnectionRouteArgs{key: $key, arbiterUrl: $arbiterUrl}';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is! ServerConnectionRouteArgs) return false;
return key == other.key && arbiterUrl == other.arbiterUrl;
}
@override
int get hashCode => key.hashCode ^ arbiterUrl.hashCode;
}
/// generated route for
/// [_i6.ServerInfoSetupScreen]
class ServerInfoSetupRoute extends _i7.PageRouteInfo<void> {
const ServerInfoSetupRoute({List<_i7.PageRouteInfo>? children})
: super(ServerInfoSetupRoute.name, initialChildren: children); : super(ServerInfoSetupRoute.name, initialChildren: children);
static const String name = 'ServerInfoSetupRoute'; static const String name = 'ServerInfoSetupRoute';
static _i6.PageInfo page = _i6.PageInfo( static _i7.PageInfo page = _i7.PageInfo(
name, name,
builder: (data) { builder: (data) {
return const _i5.ServerInfoSetupScreen(); return const _i6.ServerInfoSetupScreen();
}, },
); );
} }

View File

@@ -0,0 +1,60 @@
import 'package:arbiter/providers/connection/connection_manager.dart';
import 'package:arbiter/router.gr.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:sizer/sizer.dart';
@RoutePage()
class ServerConnectionScreen extends HookConsumerWidget {
const ServerConnectionScreen({super.key, this.arbiterUrl});
final String? arbiterUrl;
@override
Widget build(BuildContext context, WidgetRef ref) {
final connectionState = ref.watch(connectionManagerProvider);
if (connectionState.value != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
context.router.replace(const DashboardRouter());
});
}
final body = switch (connectionState) {
AsyncLoading() => const CircularProgressIndicator(),
AsyncError(:final error) => Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Connection failed',
style: Theme.of(context).textTheme.headlineSmall,
textAlign: TextAlign.center,
),
SizedBox(height: 1.5.h),
Text('$error', textAlign: TextAlign.center),
SizedBox(height: 2.h),
TextButton(
onPressed: () {
context.router.replace(const ServerInfoSetupRoute());
},
child: const Text('Back to server setup'),
),
],
),
_ => const CircularProgressIndicator(),
};
return Scaffold(
appBar: AppBar(title: const Text('Connecting')),
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 8.w),
child: body,
),
),
);
}
}

View File

@@ -1,5 +1,6 @@
import 'package:arbiter/features/arbiter_url.dart'; import 'package:arbiter/features/connection/arbiter_url.dart';
import 'package:arbiter/features/server_info_storage.dart'; import 'package:arbiter/features/connection/server_info_storage.dart';
import 'package:arbiter/providers/connection/bootstrap_token.dart';
import 'package:arbiter/providers/server_info.dart'; import 'package:arbiter/providers/server_info.dart';
import 'package:arbiter/router.gr.dart'; import 'package:arbiter/router.gr.dart';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
@@ -25,7 +26,7 @@ class ServerInfoSetupScreen extends HookConsumerWidget {
if (serverInfo != null) { if (serverInfo != null) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.mounted) { if (context.mounted) {
context.router.replace(const DashboardRouter()); context.router.replace(ServerConnectionRoute());
} }
}); });
} }
@@ -38,6 +39,14 @@ class ServerInfoSetupScreen extends HookConsumerWidget {
try { try {
final arbiterUrl = ArbiterUrl.parse(controller.text.trim()); final arbiterUrl = ArbiterUrl.parse(controller.text.trim());
// set token before triggering reconnection by updating server info
if (arbiterUrl.bootstrapToken != null) {
ref
.read(bootstrapTokenProvider.notifier)
.set(arbiterUrl.bootstrapToken!);
}
await ref await ref
.read(serverInfoProvider.notifier) .read(serverInfoProvider.notifier)
.save( .save(
@@ -47,7 +56,9 @@ class ServerInfoSetupScreen extends HookConsumerWidget {
); );
if (context.mounted) { if (context.mounted) {
context.router.replace(const DashboardRouter()); context.router.replace(
ServerConnectionRoute(arbiterUrl: controller.text.trim()),
);
} }
} on FormatException catch (error) { } on FormatException catch (error) {
errorText.value = error.message; errorText.value = error.message;
@@ -94,13 +105,15 @@ class ServerInfoSetupScreen extends HookConsumerWidget {
final options = [ final options = [
const _OptionCard( const _OptionCard(
title: 'Local', title: 'Local',
subtitle: 'Will start and connect to a local service in a future update.', subtitle:
'Will start and connect to a local service in a future update.',
enabled: false, enabled: false,
child: SizedBox.shrink(), child: SizedBox.shrink(),
), ),
_OptionCard( _OptionCard(
title: 'Remote', title: 'Remote',
subtitle: 'Paste an Arbiter URL to store the server address, port, and CA fingerprint.', subtitle:
'Paste an Arbiter URL to store the server address, port, and CA fingerprint.',
child: _RemoteConnectionForm( child: _RemoteConnectionForm(
controller: controller, controller: controller,
errorText: errorText.value, errorText: errorText.value,
@@ -271,10 +284,7 @@ class _OptionCard extends StatelessWidget {
), ),
SizedBox(height: 1.h), SizedBox(height: 1.h),
Text(subtitle, style: theme.textTheme.bodyMedium), Text(subtitle, style: theme.textTheme.bodyMedium),
if (enabled) ...[ if (enabled) ...[SizedBox(height: 2.h), child],
SizedBox(height: 2.h),
child,
],
], ],
), ),
), ),

View File

@@ -4,5 +4,7 @@
<dict> <dict>
<key>keychain-access-groups</key> <key>keychain-access-groups</key>
<array/> <array/>
<key>com.apple.security.network.client</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@@ -6,5 +6,7 @@
<true/> <true/>
<key>keychain-access-groups</key> <key>keychain-access-groups</key>
<array/> <array/>
<key>com.apple.security.network.client</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@@ -218,7 +218,7 @@ packages:
source: hosted source: hosted
version: "0.3.5+2" version: "0.3.5+2"
crypto: crypto:
dependency: transitive dependency: "direct main"
description: description:
name: crypto name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
@@ -401,8 +401,16 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
freezed:
dependency: "direct dev"
description:
name: freezed
sha256: "13065f10e135263a4f5a4391b79a8efc5fb8106f8dd555a9e49b750b45393d77"
url: "https://pub.dev"
source: hosted
version: "3.2.3"
freezed_annotation: freezed_annotation:
dependency: transitive dependency: "direct main"
description: description:
name: freezed_annotation name: freezed_annotation
sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
@@ -538,13 +546,21 @@ packages:
source: hosted source: hosted
version: "0.7.2" version: "0.7.2"
json_annotation: json_annotation:
dependency: transitive dependency: "direct main"
description: description:
name: json_annotation name: json_annotation
sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.11.0" version: "4.9.0"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
sha256: c5b2ee75210a0f263c6c7b9eeea80553dbae96ea1bf57f02484e806a3ffdffa3
url: "https://pub.dev"
source: hosted
version: "6.11.2"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@@ -918,6 +934,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.1" version: "4.2.1"
source_helper:
dependency: transitive
description:
name: source_helper
sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723"
url: "https://pub.dev"
source: hosted
version: "1.3.8"
source_map_stack_trace: source_map_stack_trace:
dependency: transitive dependency: transitive
description: description:

View File

@@ -22,6 +22,7 @@ dependencies:
hosted: https://git.markettakers.org/api/packages/MarketTakers/pub/ hosted: https://git.markettakers.org/api/packages/MarketTakers/pub/
version: ^1.0.6 version: ^1.0.6
cryptography: ^2.9.0 cryptography: ^2.9.0
crypto: ^3.0.6
flutter_secure_storage: ^10.0.0 flutter_secure_storage: ^10.0.0
cryptography_flutter: ^2.3.4 cryptography_flutter: ^2.3.4
riverpod_annotation: ^4.0.0 riverpod_annotation: ^4.0.0
@@ -30,6 +31,8 @@ dependencies:
flutter_hooks: ^0.21.3+1 flutter_hooks: ^0.21.3+1
auto_route: ^11.1.0 auto_route: ^11.1.0
protobuf: ^6.0.0 protobuf: ^6.0.0
freezed_annotation: ^3.1.0
json_annotation: ^4.9.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -39,6 +42,8 @@ dev_dependencies:
riverpod_generator: ^4.0.0+1 riverpod_generator: ^4.0.0+1
build_runner: ^2.12.2 build_runner: ^2.12.2
auto_route_generator: ^10.4.0 auto_route_generator: ^10.4.0
freezed: ^3.2.3
json_serializable: ^6.11.2
flutter: flutter:
uses-material-design: true uses-material-design: true