frindlymap/lib/composition/android_bootstrap.dart

103 lines
4.0 KiB
Dart

import 'dart:async';
import 'package:friendly_map/modules/app/app.dart';
import 'package:friendly_map/modules/discovery/discovery.dart';
import 'package:friendly_map/modules/keys/keys.dart';
import 'package:friendly_map/modules/platform_android/platform_android.dart';
import 'package:friendly_map/modules/transport/transport.dart';
import 'package:friendly_map/modules/ui/ui.dart';
/// 같은 네트워크 직접 연결이 쓰는 포트.
const int lanPort = 48766;
/// 보관함 서버 주소를 적어 두는 설정 이름. 비어 있으면 같은 네트워크 직접 연결만 쓴다.
const String mailboxUrlKey = 'mailbox_url';
/// 안드로이드용 조립: 어떤 부품(보안 저장소, 암호화 DB, 직접 연결, 보관함)을 쓸지 여기서만 정한다.
/// 다른 모듈은 서로의 구현을 모른다. 부품을 바꾸려면 이 파일의 해당 줄만 바꾸면 된다.
class AndroidBootstrap implements Bootstrap {
AndroidBootstrap() : _keys = LocalKeyProvider(AndroidSecureKeyStore());
final LocalKeyProvider _keys;
Timer? _refresh;
Timer? _location;
Timer? _events;
@override
Future<bool> hasKeys() => _keys.isSetUp();
@override
Future<String> createKeys() => _keys.setUpNew();
@override
Future<bool> restoreKeys(String text) async {
final tokens = text.split(RegExp(r'\s+')).where(RecoveryShares.looksLikeShare).toList();
if (tokens.isEmpty) return _keys.restore(text);
final phrase = await RecoveryShares.combine(tokens);
return phrase != null && await _keys.restore(phrase);
}
@override
Future<List<String>> splitRecovery(int n, int k) async {
final phrase = await _keys.showPhrase();
if (phrase == null) throw StateError('복구 코드가 없습니다');
return RecoveryShares.split(phrase, n: n, k: k);
}
@override
Future<String?> recoveryPhrase() => _keys.showPhrase();
@override
Future<FriendlyApp> openApp() async {
final storage = await openEncryptedStorage(_keys);
final identityKeys = IdentityKeys(_keys.derive);
final lan = LanDirectory(AndroidLanDiscovery());
// 내 전화번호는 내 정보를 정한 뒤에야 알 수 있어서, 연결은 앱이 시작될 때 만든다.
final transport = LateTransport(() async {
final me = await storage.settings.loadProfile();
final url = (await storage.settings.getText(mailboxUrlKey))?.trim() ?? '';
return CompositeTransport([
DirectTransport(
port: lanPort,
resolve: (phone) async {
final p = lan.find(phone);
return p == null ? null : DirectAddress(p.host, p.port);
},
),
if (me != null && url.isNotEmpty) MailboxTransport(baseUrl: url, myPhone: me.phone, token: await _keys.mailboxToken()),
]);
});
final app = FriendlyApp(AppDeps(
storage: storage,
transport: transport,
timeSource: transport,
cipher: SharedPayloadCipher(identityKeys),
identity: LocalIdentityProvider(identityKeys),
probe: AndroidStorageProbe(),
lan: lan,
location: AndroidLocationProvider(),
callLog: AndroidCallLog(),
phone: AndroidPhoneActions(),
notifier: AndroidNotifier(),
verifier: LatePhoneVerifier(() async {
final me = await storage.settings.loadProfile();
final url = (await storage.settings.getText(mailboxUrlKey))?.trim() ?? '';
if (me == null || url.isEmpty) return null;
return MailboxVerifier(baseUrl: url, myPhone: me.phone, token: await _keys.mailboxToken());
}),
))
..lanPort = lanPort;
// 보관함에 쌓인 봉투를 주기적으로 가져온다.
_refresh?.cancel();
_refresh = Timer.periodic(const Duration(seconds: 30), (_) => app.refresh().catchError((Object _) {}));
_location?.cancel();
_location = Timer.periodic(const Duration(minutes: 1), (_) => app.location.shareNow().catchError((Object _) => 0));
_events?.cancel();
_events = Timer.periodic(const Duration(hours: 6), (_) => app.events.reschedule().catchError((Object _) => 0));
return app;
}
}