104 lines
4.0 KiB
Dart
104 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(),
|
|
contacts: AndroidContacts(),
|
|
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;
|
|
}
|
|
}
|