remote signing simple support

This commit is contained in:
DASHU
2024-09-11 16:15:55 +08:00
parent f34d10093b
commit bdf1042307
10 changed files with 294 additions and 127 deletions

View File

@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:nowser/const/base.dart';
import 'package:pretty_qr_code/pretty_qr_code.dart';
class SimpleQrcodeDialog extends StatefulWidget {
String data;
SimpleQrcodeDialog(this.data);
static Future<void> show(BuildContext context, String date) async {
return await showDialog<void>(
context: context,
builder: (_context) {
return SimpleQrcodeDialog(date);
});
}
@override
State<StatefulWidget> createState() {
return _SimpleQrcodeDialog();
}
}
class _SimpleQrcodeDialog extends State<SimpleQrcodeDialog> {
@override
Widget build(BuildContext context) {
return Dialog(
child: Container(
padding: EdgeInsets.all(Base.BASE_PADDING * 2),
child: PrettyQrView.data(
data: widget.data,
// decoration: const PrettyQrDecoration(
// image: PrettyQrDecorationImage(
// image: AssetImage('images/flutter.png'),
// ),
// ),
),
),
);
}
}

View File

@@ -43,7 +43,7 @@ class DB {
db.execute("create index auth_log_index on auth_log (app_id);");
db.execute(
"create table remote_signing_info(id integer not null constraint remote_signing_info_pk primary key autoincrement,app_id integer,local_pubkey text,remote_signer_key text,relays text,secret text,created_at integer,updated_at integer);");
"create table remote_signing_info(id integer not null constraint remote_signing_info_pk primary key autoincrement,app_id integer,local_pubkey text,remote_pubkey text,remote_signer_key text,relays text,secret text,created_at integer,updated_at integer);");
db.execute(
"create table zap_log(id integer not null constraint zap_log_pk primary key autoincrement,app_id integer not null constraint zap_log_index unique,zap_type integer not null,num integer not null,created_at integer not null);");

View File

@@ -2,6 +2,7 @@ class RemoteSigningInfo {
int? id;
int? appId;
String? localPubkey;
String? remotePubkey;
String? remoteSignerKey;
String? relays;
String? secret;
@@ -12,6 +13,7 @@ class RemoteSigningInfo {
{this.id,
this.appId,
this.localPubkey,
this.remotePubkey,
this.remoteSignerKey,
this.relays,
this.secret,
@@ -22,6 +24,7 @@ class RemoteSigningInfo {
id = json['id'];
appId = json['app_id'];
localPubkey = json['local_pubkey'];
remotePubkey = json['remote_pubkey'];
remoteSignerKey = json['remote_signer_key'];
relays = json['relays'];
secret = json['secret'];
@@ -34,6 +37,7 @@ class RemoteSigningInfo {
data['id'] = this.id;
data['app_id'] = this.appId;
data['local_pubkey'] = this.localPubkey;
data['remote_pubkey'] = this.remotePubkey;
data['remote_signer_key'] = this.remoteSignerKey;
data['relays'] = this.relays;
data['secret'] = this.secret;

View File

@@ -39,4 +39,10 @@ class RemoteSigningInfoDB {
return objs;
}
static Future<int> update(RemoteSigningInfo o, {DatabaseExecutor? db}) async {
db = await DB.getDB(db);
return await db.update("remote_signing_info", o.toJson(),
where: "id = ?", whereArgs: [o.id]);
}
}

View File

@@ -61,8 +61,6 @@ Future<void> main() async {
settingProvider = futureResultList[0] as SettingProvider;
webProvider = WebProvider();
remoteSigningProvider = RemoteSigningProvider();
remoteSigningProvider.reload();
remoteSigningProvider.reloadPenddingRemoteApps();
runApp(MyApp());
}

View File

@@ -14,10 +14,12 @@ import 'package:nostr_sdk/relay/relay_status.dart';
import 'package:nostr_sdk/signer/local_nostr_signer.dart';
import 'package:nostr_sdk/signer/nostr_signer.dart';
import 'package:nostr_sdk/utils/string_util.dart';
import 'package:nowser/const/app_type.dart';
import 'package:nowser/const/auth_type.dart';
import 'package:nowser/data/app.dart';
import 'package:nowser/provider/permission_check_mixin.dart';
import '../component/auth_dialog/auth_app_connect_dialog.dart';
import '../data/remote_signing_info.dart';
import '../data/remote_signing_info_db.dart';
import '../main.dart';
@@ -29,75 +31,157 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin {
context = _context;
}
// localPubkey - Relay
// remoteSignerPubkey - Relay
Map<String, List<Relay>> relayMap = {};
// localPubkey - RemoteSigningInfo
// remoteSignerPubkey - RemoteSigningInfo
Map<String, RemoteSigningInfo> remoteSigningInfoMap = {};
// localPubkey - App
// remoteSignerPubkey - App
Map<String, App> appMap = {};
Future<void> reload() async {
relayMap = {};
remoteSigningInfoMap = {};
appMap = {};
load();
}
Future<void> load() async {
var remoteAppList = appProvider.remoteAppList();
for (var remoteApp in remoteAppList) {
await add(remoteApp);
await addRemoteApp(remoteApp);
}
}
Future<void> add(App remoteApp) async {
Future<void> addRemoteApp(App remoteApp) async {
var remoteSigningInfo = await RemoteSigningInfoDB.getByAppId(remoteApp.id!);
if (remoteSigningInfo != null &&
StringUtil.isNotBlank(remoteSigningInfo.remoteSignerKey) &&
StringUtil.isNotBlank(remoteSigningInfo.remotePubkey) &&
StringUtil.isNotBlank(remoteSigningInfo.localPubkey) &&
StringUtil.isNotBlank(remoteSigningInfo.relays)) {
var localPubkey = remoteSigningInfo.localPubkey!;
var pubkey = getPublicKey(remoteSigningInfo.remoteSignerKey!);
var relayAddrs = remoteSigningInfo.relays!.split(",");
var relays = connectToRelay(remoteSigningInfo);
List<Relay> relays = [];
for (var relayAddr in relayAddrs) {
// use pubkey relace with
var relay = RelayIsolate(relayAddr, RelayStatus(localPubkey));
var filter = Filter(p: [pubkey]);
relay.pendingAuthedMessages
.add(["REQ", StringUtil.rndNameStr(10), filter.toJson()]);
relay.onMessage = _onEvent;
relay.connect();
relays.add(relay);
}
relayMap[localPubkey] = relays;
remoteSigningInfoMap[localPubkey] = remoteSigningInfo;
var remoteSignerPubkey = getPublicKey(remoteSigningInfo.remoteSignerKey!);
remoteSigningInfoMap[remoteSignerPubkey] = remoteSigningInfo;
appMap[remoteSignerPubkey] = remoteApp;
relayMap[remoteSignerPubkey] = relays;
}
}
List<Relay> connectToRelay(RemoteSigningInfo remoteSigningInfo) {
var remoteSignerPubkey = getPublicKey(remoteSigningInfo.remoteSignerKey!);
var relayAddrs = remoteSigningInfo.relays!.split(",");
List<Relay> relays = [];
for (var relayAddr in relayAddrs) {
// use pubkey relace with
var relay = RelayIsolate(relayAddr, RelayStatus(remoteSignerPubkey));
var filter = Filter(
p: [remoteSigningInfo.remotePubkey!],
since: DateTime.now().millisecondsSinceEpoch ~/ 1000);
relay.pendingAuthedMessages
.add(["REQ", StringUtil.rndNameStr(10), filter.toJson()]);
relay.pendingMessages
.add(["REQ", StringUtil.rndNameStr(10), filter.toJson()]);
relay.onMessage = _onEvent;
relay.connect();
relays.add(relay);
print("connected to relay ${relay.url}");
}
return relays;
}
Future<void> onRequest(
Relay relay,
NostrRemoteRequest request,
RemoteSigningInfo remoteSigningInfo,
App app,
String localPubkey,
App? app,
) async {
String localPubkey = remoteSigningInfo.localPubkey!;
NostrSigner signer = LocalNostrSigner(remoteSigningInfo.remoteSignerKey!);
var remoteSignerPubkey = await signer.getPublicKey();
var appType = app.appType!;
var code = app.code!;
NostrSigner signerSigner =
LocalNostrSigner(remoteSigningInfo.remoteSignerKey!);
var remoteSignerPubkey = getPublicKey(remoteSigningInfo.remoteSignerKey!);
var appType = AppType.REMOTE;
var code = remoteSignerPubkey;
NostrRemoteResponse? response;
if (request.method == "ping") {
response = NostrRemoteResponse(request.id, "pong");
sendResponse(relay, response, signer, localPubkey, remoteSignerPubkey!);
sendResponse(
relay, response, signerSigner, localPubkey, remoteSignerPubkey);
} else if (request.method == "connect") {
if (app != null) {
response = NostrRemoteResponse(request.id, "ack");
} else {
if (request.params.length <= 1) {
response = NostrRemoteResponse(request.id, "", error: "params error");
} else {
if (request.params[0] == remoteSigningInfo.remotePubkey &&
request.params[1] == remoteSigningInfo.secret) {
// check pass, init app
var newApp = await getApp(appType, code);
await AuthAppConnectDialog.show(context!, newApp);
// reload from provider
app = appProvider.getApp(appType, code);
if (app == null) {
response =
NostrRemoteResponse(request.id, "", error: "connect fail");
} else {
remoteSigningInfo.appId = app.id;
remoteSigningInfo.localPubkey = localPubkey;
remoteSigningInfo.updatedAt =
DateTime.now().millisecondsSinceEpoch ~/ 1000;
RemoteSigningInfoDB.update(remoteSigningInfo);
remoteSigningInfoMap[remoteSignerPubkey] = remoteSigningInfo;
appMap[remoteSignerPubkey] = app;
_penddingRemoteApps.removeWhere((rsi) {
if (rsi.id == remoteSigningInfo.id) {
return true;
}
return false;
});
response = NostrRemoteResponse(request.id, "ack");
}
} else {
response = NostrRemoteResponse(request.id, "",
error: "connect check fail");
}
}
}
if (response != null) {
sendResponse(
relay, response, signerSigner, localPubkey, remoteSignerPubkey);
}
} else {
if (remoteSigningInfo.localPubkey != localPubkey) {
// Remote signing should connect first.
response = NostrRemoteResponse(request.id, "",
error: "Local pubkey not allow.");
sendResponse(
relay, response, signerSigner, localPubkey, remoteSignerPubkey);
return;
}
if (app == null) {
// Remote signing should connect first.
response = NostrRemoteResponse(request.id, "",
error: "Remote signing should connect first.");
sendResponse(
relay, response, signerSigner, localPubkey, remoteSignerPubkey);
return;
}
int? eventKind;
String? authDetail;
int authType = AuthType.GET_PUBLIC_KEY;
@@ -130,7 +214,8 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin {
checkPermission(context!, appType, code, authType,
eventKind: eventKind, authDetail: authDetail, (app) {
response = NostrRemoteResponse(request.id, "", error: "forbid");
sendResponse(relay, response, signer, localPubkey, remoteSignerPubkey!);
sendResponse(
relay, response, signerSigner, localPubkey, remoteSignerPubkey);
}, (app, signer) async {
if (request.method == "sign_event") {
var tags = eventObj["tags"];
@@ -165,7 +250,8 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin {
response = NostrRemoteResponse(request.id, text!);
}
sendResponse(relay, response, signer, localPubkey, remoteSignerPubkey!);
sendResponse(
relay, response, signerSigner, localPubkey, remoteSignerPubkey);
});
}
}
@@ -174,27 +260,45 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin {
NostrSigner signer, String localPubkey, String remoteSignerPubkey) async {
if (response != null) {
var result = await response.encrypt(signer, localPubkey);
var event = Event(
remoteSignerPubkey!,
Event? event = Event(
remoteSignerPubkey,
EventKind.NOSTR_REMOTE_SIGNING,
[
["p", localPubkey]
],
result!);
event = await signer.signEvent(event);
relay.send(["event", event.toJson()]);
var signerPubkey = await signer.getPublicKey();
print("signerPubkey $signerPubkey");
print("response:");
if (event != null) {
print(event.toJson());
print(event.isValid);
print(event.isSigned);
relay.send(["EVENT", event.toJson()]);
} else {
print("null");
}
}
}
Future<void> _onEvent(Relay relay, List<dynamic> json) async {
var localPubkey = relay.relayStatus.addr;
var remoteSigningInfo = remoteSigningInfoMap[localPubkey];
print("request");
print(json);
var remoteSignerPubkey = relay.relayStatus.addr;
var remoteSigningInfo = remoteSigningInfoMap[remoteSignerPubkey];
if (remoteSigningInfo == null) {
print("remoteSigningInfo is null");
return;
}
var remotePubkey = getPublicKey(remoteSigningInfo.remoteSignerKey!);
var nostrSigner = LocalNostrSigner(remoteSigningInfo.remoteSignerKey!);
var signer = keyProvider.getSigner(remoteSigningInfo.remotePubkey!);
if (signer == null) {
return;
}
final messageType = json[0];
if (messageType == 'EVENT') {
@@ -208,8 +312,11 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin {
if (event.kind == EventKind.NOSTR_REMOTE_SIGNING) {
var request = await NostrRemoteRequest.decrypt(
event.content, nostrSigner, localPubkey);
if (request != null) {}
event.content, signer, event.pubkey);
if (request != null) {
onRequest(relay, request, remoteSigningInfo, event.pubkey,
appMap[remoteSignerPubkey]);
}
}
} catch (err) {
log(err.toString());
@@ -228,7 +335,8 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin {
["relay", relay.url],
["challenge", challenge]
];
Event? event = Event(remotePubkey, EventKind.AUTHENTICATION, tags, "");
Event? event =
Event(remoteSignerPubkey, EventKind.AUTHENTICATION, tags, "");
event = await nostrSigner.signEvent(event);
if (event != null) {
relay.send(["AUTH", event.toJson()], forceSend: true);
@@ -251,7 +359,7 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin {
List<RemoteSigningInfo> get penddingRemoteApps => _penddingRemoteApps;
void addRemoteSigningInfo(RemoteSigningInfo remoteSigningInfo) {
void saveRemoteSigningInfo(RemoteSigningInfo remoteSigningInfo) {
RemoteSigningInfoDB.insert(remoteSigningInfo);
reloadPenddingRemoteApps();
notifyListeners();
@@ -261,72 +369,22 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin {
var list = await RemoteSigningInfoDB.penddingRemoteSigningInfo();
_penddingRemoteApps = list;
notifyListeners();
connectPenddingRemoteApp();
}
/**
* The below code is design for relay n - remoteSignerKey n
*/
void connectPenddingRemoteApp() {
for (var remoteSigningInfo in _penddingRemoteApps) {
if (StringUtil.isNotBlank(remoteSigningInfo.remoteSignerKey) &&
StringUtil.isNotBlank(remoteSigningInfo.remotePubkey) &&
StringUtil.isNotBlank(remoteSigningInfo.relays)) {
var relays = connectToRelay(remoteSigningInfo);
// Map<String, RelayStatus> relayStatusMap = {};
// // relayAddr - Relay
// Map<String, Relay> relayMap = {};
// // remoteSignerPubkey - RemoteSigningInfo
// Map<String, RemoteSigningInfo> remoteSigningInfoMap = {};
// Map<String, List<String>> relayToRemoteSignerPubkeys = {};
// Map<String, List<String>> remoteSignerPubkeyToRelays = {};
// /// relay - remoteSignerKey > n - n
// /// ==>
// /// relay - remoteSignerKey > 1 - n
// /// remoteSigner - relay > 1 - n
// Future<void> load() async {
// var remoteAppList = appProvider.remoteAppList();
// for (var remoteApp in remoteAppList) {
// var remoteSigningInfo =
// await RemoteSigningInfoDB.getByAppId(remoteApp.id!);
// if (remoteSigningInfo != null &&
// StringUtil.isNotBlank(remoteSigningInfo.remoteSignerKey) &&
// StringUtil.isNotBlank(remoteSigningInfo.localPubkey) &&
// StringUtil.isNotBlank(remoteSigningInfo.relays)) {
// var pubkey = getPublicKey(remoteSigningInfo.secret!);
// var relays = remoteSigningInfo.relays!.split(",");
// remoteSignerPubkeyToRelays[pubkey] = relays;
// remoteSigningInfoMap[pubkey] = remoteSigningInfo;
// for (var relay in relays) {
// relay = RelayAddrUtil.handle(relay);
// var pubkeys = relayToRemoteSignerPubkeys[relay];
// if (pubkeys == null) {
// pubkeys = [];
// relayToRemoteSignerPubkeys[relay] = pubkeys;
// }
// if (!pubkeys.contains(pubkey)) {
// pubkeys.add(pubkey);
// }
// }
// }
// }
// // data handle complete, begin to init relays
// for (var entry in relayToRemoteSignerPubkeys.entries) {
// var relayAddr = entry.key;
// var pubkeys = entry.value;
// var relayStatus = RelayStatus(relayAddr);
// relayStatusMap[relayAddr] = relayStatus;
// var relay = RelayIsolate(relayAddr, relayStatus);
// relayMap[relayAddr] = relay;
// // var filter = Filter(
// // p: pubkeys, since: DateTime.now().millisecondsSinceEpoch ~/ 1000);
// }
// }
var remoteSignerPubkey =
getPublicKey(remoteSigningInfo.remoteSignerKey!);
remoteSigningInfoMap[remoteSignerPubkey] = remoteSigningInfo;
relayMap[remoteSignerPubkey] = relays;
}
}
}
}

View File

@@ -1,3 +1,4 @@
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:nostr_sdk/client_utils/keys.dart';
@@ -5,6 +6,7 @@ import 'package:nostr_sdk/nip19/nip19.dart';
import 'package:nostr_sdk/nip46/nostr_remote_signer_info.dart';
import 'package:nostr_sdk/utils/string_util.dart';
import 'package:nowser/component/qrscanner.dart';
import 'package:nowser/component/simple_qrcode_dialog.dart';
import 'package:nowser/data/remote_signing_info.dart';
import 'package:nowser/data/remote_signing_info_db.dart';
import 'package:nowser/main.dart';
@@ -13,6 +15,7 @@ import 'package:provider/provider.dart';
import '../../component/appbar_back_btn_component.dart';
import '../../const/base.dart';
import '../../generated/l10n.dart';
import '../../provider/key_provider.dart';
class AddRemoteAppRouter extends StatefulWidget {
@@ -29,6 +32,8 @@ class _AddRemoteAppRouter extends State<AddRemoteAppRouter> {
bool editBunker = false;
String? pubkey;
@override
void initState() {
super.initState();
@@ -47,6 +52,41 @@ class _AddRemoteAppRouter extends State<AddRemoteAppRouter> {
var textColor = themeData.textTheme.bodyMedium!.color;
var mainColor = themeData.primaryColor;
var keyWidget =
Selector<KeyProvider, List<String>>(builder: (context, pubkeys, child) {
List<DropdownMenuItem<String>> items = [];
for (var pubkey in pubkeys) {
items.add(DropdownMenuItem(
value: pubkey,
child: Text(
Nip19.encodePubKey(pubkey),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
));
}
if (StringUtil.isBlank(pubkey) && pubkeys.isNotEmpty) {
pubkey = pubkeys.first;
refreshBunkerUrl();
}
return DropdownButton<String>(
items: items,
isExpanded: true,
onChanged: (String? value) {
if (StringUtil.isNotBlank(value)) {
pubkey = value;
refreshBunkerUrl();
setState(() {});
}
},
value: pubkey,
);
}, selector: (context, provider) {
return provider.pubkeys;
});
if (StringUtil.isBlank(bunkerConn.text)) {
refreshBunkerUrl();
}
@@ -121,6 +161,9 @@ class _AddRemoteAppRouter extends State<AddRemoteAppRouter> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
child: keyWidget,
),
Container(
height: 60,
child: TabBar(
@@ -205,9 +248,9 @@ class _AddRemoteAppRouter extends State<AddRemoteAppRouter> {
IconButton(
onPressed: refreshBunkerUrl,
icon: Icon(Icons.refresh)),
// IconButton(
// onPressed: showBunkerUrlQRCode,
// icon: Icon(Icons.qr_code)),
IconButton(
onPressed: showBunkerUrlQRCode,
icon: Icon(Icons.qr_code)),
IconButton(
onPressed: copyBunkerUrl,
icon: Icon(Icons.copy)),
@@ -288,20 +331,27 @@ class _AddRemoteAppRouter extends State<AddRemoteAppRouter> {
reloadBunker();
}
// void showBunkerUrlQRCode() {}
void showBunkerUrlQRCode() {
reloadBunker();
SimpleQrcodeDialog.show(context, bunkerConn.text);
}
void copyBunkerUrl() {
Clipboard.setData(ClipboardData(text: bunkerConn.text)).then((_) {
// TODO
// BotToast.showText(text: S.of(context).Copy_success);
BotToast.showText(text: "Copy success");
});
}
void confirmBunkerUrl() {
if (StringUtil.isBlank(pubkey)) {
return;
}
var remoteSignerKey = remoteSignerKeyController.text;
var relays = [relayAddrController.text];
var remoteSigningInfo = RemoteSigningInfo(
remotePubkey: pubkey,
remoteSignerKey: remoteSignerKey,
relays: relays.join(","),
secret: secretController.text,
@@ -309,16 +359,17 @@ class _AddRemoteAppRouter extends State<AddRemoteAppRouter> {
);
remoteSigningInfo.updatedAt = remoteSigningInfo.createdAt;
remoteSigningProvider.addRemoteSigningInfo(remoteSigningInfo);
remoteSigningProvider.saveRemoteSigningInfo(remoteSigningInfo);
RouterUtil.back(context);
}
void reloadBunker() {
var remoteSignerKey = remoteSignerKeyController.text;
var remoteSignerPubkey = getPublicKey(remoteSignerKey);
if (StringUtil.isBlank(pubkey)) {
return;
}
var nostrRemoteSignerInfo = NostrRemoteSignerInfo(
remoteUserPubkey: remoteSignerPubkey,
remoteUserPubkey: pubkey!,
relays: [relayAddrController.text],
optionalSecret: secretController.text,
);

View File

@@ -74,7 +74,9 @@ class _AppsRouter extends CustState<AppsRouter> {
var length = penddingList.length;
for (var i = 0; i < length; i++) {
var remoteSigningInfo = penddingList[i];
var pubkey = getPublicKey(remoteSigningInfo.remoteSignerKey!);
if (StringUtil.isBlank(remoteSigningInfo.remotePubkey)) {
continue;
}
String connectUrlType = "bunker";
if (StringUtil.isNotBlank(remoteSigningInfo.localPubkey)) {
@@ -84,10 +86,10 @@ class _AppsRouter extends CustState<AppsRouter> {
widgets.add(Container(
child: Row(
children: [
Text(Nip19.encodeSimplePubKey(pubkey)),
Text(Nip19.encodeSimplePubKey(remoteSigningInfo.remotePubkey!)),
Expanded(
child: Container(
margin: EdgeInsets.only(left: Base.BASE_PADDING_HALF),
margin: const EdgeInsets.only(left: Base.BASE_PADDING_HALF),
child: Text(
DateFormatUtil.format(
remoteSigningInfo.createdAt!,

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:nostr_sdk/utils/platform_util.dart';
import 'package:nowser/component/cust_state.dart';
import 'package:nowser/main.dart';
import 'package:nowser/provider/web_provider.dart';
import 'package:nowser/router/index/index_web_component.dart';
@@ -16,12 +17,18 @@ class IndexRouter extends StatefulWidget {
}
}
class _IndexRouter extends State<IndexRouter>
class _IndexRouter extends CustState<IndexRouter>
with PermissionCheckMixin, AndroidSignerMixin {
Map<String, WebViewComponent> webViewComponentMap = {};
@override
Widget build(BuildContext context) {
Future<void> onReady(BuildContext context) async {
await remoteSigningProvider.reload();
await remoteSigningProvider.reloadPenddingRemoteApps();
}
@override
Widget doBuild(BuildContext context) {
if (PlatformUtil.isAndroid()) {
WidgetsBinding.instance.addPostFrameCallback((_) {
handleInitialIntent(context);