mirror of
https://github.com/haorendashu/nowser.git
synced 2026-01-31 15:14:27 +01:00
some code for remote signing
This commit is contained in:
65
lib/component/qrscanner.dart
Normal file
65
lib/component/qrscanner.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qr_code_scanner/qr_code_scanner.dart';
|
||||
|
||||
import '../util/router_util.dart';
|
||||
|
||||
class QRScanner extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _QRScanner();
|
||||
}
|
||||
|
||||
static Future<String> show(BuildContext context) async {
|
||||
return await RouterUtil.push(context, MaterialPageRoute(builder: (context) {
|
||||
return QRScanner();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class _QRScanner extends State<QRScanner> {
|
||||
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
|
||||
Barcode? result;
|
||||
QRViewController? controller;
|
||||
|
||||
// In order to get hot reload to work we need to pause the camera if the platform
|
||||
// is android, or resume the camera if the platform is iOS.
|
||||
@override
|
||||
void reassemble() {
|
||||
super.reassemble();
|
||||
if (Platform.isAndroid) {
|
||||
controller!.pauseCamera();
|
||||
} else if (Platform.isIOS) {
|
||||
controller!.resumeCamera();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: QRView(
|
||||
key: qrKey,
|
||||
onQRViewCreated: _onQRViewCreated,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool scanComplete = false;
|
||||
|
||||
void _onQRViewCreated(QRViewController controller) {
|
||||
this.controller = controller;
|
||||
controller.scannedDataStream.listen((scanData) {
|
||||
if (!scanComplete) {
|
||||
scanComplete = true;
|
||||
RouterUtil.back(context, scanData.code);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -4,4 +4,6 @@ class RouterPath {
|
||||
static const String WEB_URL_INPUT = "/webUrlInput";
|
||||
static const String ME = "/me";
|
||||
static const String KEYS = "/keys";
|
||||
static const String APPS = "/apps";
|
||||
static const String ADD_REMOTE_APP = "/addRemoteApp";
|
||||
}
|
||||
|
||||
60
lib/data/remote_signing_info.dart
Normal file
60
lib/data/remote_signing_info.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
class RemoteSigningInfo {
|
||||
int? id;
|
||||
int? appId;
|
||||
String? localPubkey;
|
||||
String? remoteSignerKey;
|
||||
String? relays;
|
||||
String? secret;
|
||||
int? createdAt;
|
||||
int? updatedAt;
|
||||
|
||||
RemoteSigningInfo(
|
||||
{this.id,
|
||||
this.appId,
|
||||
this.localPubkey,
|
||||
this.remoteSignerKey,
|
||||
this.relays,
|
||||
this.secret,
|
||||
this.createdAt,
|
||||
this.updatedAt});
|
||||
|
||||
RemoteSigningInfo.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
appId = json['app_id'];
|
||||
localPubkey = json['local_pubkey'];
|
||||
remoteSignerKey = json['remote_signer_key'];
|
||||
relays = json['relays'];
|
||||
secret = json['secret'];
|
||||
createdAt = json['created_at'];
|
||||
updatedAt = json['updated_at'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['id'] = this.id;
|
||||
data['app_id'] = this.appId;
|
||||
data['local_pubkey'] = this.localPubkey;
|
||||
data['remote_signer_key'] = this.remoteSignerKey;
|
||||
data['relays'] = this.relays;
|
||||
data['secret'] = this.secret;
|
||||
data['created_at'] = this.createdAt;
|
||||
data['updated_at'] = this.updatedAt;
|
||||
return data;
|
||||
}
|
||||
|
||||
static RemoteSigningInfo? parseNostrConnectUrl(String nostrconnectUrlText) {
|
||||
var uri = Uri.parse(nostrconnectUrlText);
|
||||
var pars = uri.queryParametersAll;
|
||||
var localPubkey = uri.host;
|
||||
|
||||
var relays = pars["relay"];
|
||||
if (relays == null || relays.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return RemoteSigningInfo(
|
||||
localPubkey: localPubkey,
|
||||
relays: relays.join(","),
|
||||
);
|
||||
}
|
||||
}
|
||||
23
lib/data/remote_signing_info_db.dart
Normal file
23
lib/data/remote_signing_info_db.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'package:nowser/data/remote_signing_info.dart';
|
||||
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'db.dart';
|
||||
|
||||
class RemoteSigningInfoDB {
|
||||
static Future<int> insert(RemoteSigningInfo o, {DatabaseExecutor? db}) async {
|
||||
db = await DB.getDB(db);
|
||||
return await db.insert("remote_signing_info", o.toJson());
|
||||
}
|
||||
|
||||
static Future<RemoteSigningInfo?> getByAppId(int appId,
|
||||
{DatabaseExecutor? db}) async {
|
||||
db = await DB.getDB(db);
|
||||
List<Map<String, dynamic>> list = await db.rawQuery(
|
||||
"select * from remote_signing_info where app_id = ?", [appId]);
|
||||
if (list.isNotEmpty) {
|
||||
return RemoteSigningInfo.fromJson(list.first);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
122
lib/main.dart
122
lib/main.dart
@@ -13,6 +13,8 @@ import 'package:nowser/provider/app_provider.dart';
|
||||
import 'package:nowser/provider/key_provider.dart';
|
||||
import 'package:nowser/provider/permission_check_mixin.dart';
|
||||
import 'package:nowser/provider/web_provider.dart';
|
||||
import 'package:nowser/router/apps/add_remote_app_router.dart';
|
||||
import 'package:nowser/router/apps/apps_router.dart';
|
||||
import 'package:nowser/router/index/index_router.dart';
|
||||
import 'package:nowser/router/keys/keys_router.dart';
|
||||
import 'package:nowser/router/me/me_router.dart';
|
||||
@@ -26,6 +28,7 @@ import 'const/colors.dart';
|
||||
import 'const/router_path.dart';
|
||||
import 'generated/l10n.dart';
|
||||
import 'provider/data_util.dart';
|
||||
import 'provider/remote_signing_provider.dart';
|
||||
import 'provider/setting_provider.dart';
|
||||
import 'util/colors_util.dart';
|
||||
|
||||
@@ -37,6 +40,8 @@ late KeyProvider keyProvider;
|
||||
|
||||
late AppProvider appProvider;
|
||||
|
||||
late RemoteSigningProvider remoteSigningProvider;
|
||||
|
||||
late Map<String, WidgetBuilder> routes;
|
||||
|
||||
Future<void> main() async {
|
||||
@@ -55,6 +60,7 @@ Future<void> main() async {
|
||||
var futureResultList = await Future.wait([settingTask, appTask]);
|
||||
settingProvider = futureResultList[0] as SettingProvider;
|
||||
webProvider = WebProvider();
|
||||
remoteSigningProvider = RemoteSigningProvider();
|
||||
|
||||
runApp(MyApp());
|
||||
}
|
||||
@@ -92,6 +98,8 @@ class _MyApp extends State<MyApp> {
|
||||
RouterPath.WEB_URL_INPUT: (context) => WebUrlInputRouter(),
|
||||
RouterPath.ME: (context) => MeRouter(),
|
||||
RouterPath.KEYS: (context) => KeysRouter(),
|
||||
RouterPath.APPS: (context) => AppsRouter(),
|
||||
RouterPath.ADD_REMOTE_APP: (context) => AddRemoteAppRouter(),
|
||||
};
|
||||
|
||||
return MultiProvider(
|
||||
@@ -105,6 +113,9 @@ class _MyApp extends State<MyApp> {
|
||||
ListenableProvider<AppProvider>.value(
|
||||
value: appProvider,
|
||||
),
|
||||
ListenableProvider<RemoteSigningProvider>.value(
|
||||
value: remoteSigningProvider,
|
||||
),
|
||||
],
|
||||
child: MaterialApp(
|
||||
builder: BotToastInit(),
|
||||
@@ -283,114 +294,3 @@ Color _getMainColor() {
|
||||
}
|
||||
return color500;
|
||||
}
|
||||
|
||||
|
||||
// class MyApp extends StatelessWidget {
|
||||
// const MyApp({super.key});
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return MaterialApp(
|
||||
// title: 'Flutter Demo',
|
||||
// theme: ThemeData(
|
||||
// colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
// useMaterial3: true,
|
||||
// ),
|
||||
// home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// class MyHomePage extends StatefulWidget {
|
||||
// const MyHomePage({super.key, required this.title});
|
||||
|
||||
// final String title;
|
||||
|
||||
// @override
|
||||
// State<MyHomePage> createState() => _MyHomePageState();
|
||||
// }
|
||||
|
||||
// class _MyHomePageState extends State<MyHomePage> {
|
||||
// void _incrementCounter() {
|
||||
// // setState(() {
|
||||
// // _counter++;
|
||||
// // });
|
||||
|
||||
// index++;
|
||||
// index %= 3;
|
||||
// setState(() {});
|
||||
// }
|
||||
|
||||
// StreamSubscription? _sub;
|
||||
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// _sub = receiveIntent.ReceiveIntent.receivedIntentStream.listen(
|
||||
// (receiveIntent.Intent? intent) {
|
||||
// log("receive intent!!!!!");
|
||||
// log(intent.toString());
|
||||
// }, onError: (err) {
|
||||
// print("listen error ");
|
||||
// print(err);
|
||||
// });
|
||||
// print("listen begin!");
|
||||
|
||||
// test();
|
||||
// }
|
||||
|
||||
// Future<void> test() async {
|
||||
// try {
|
||||
// final receivedIntent =
|
||||
// await receiveIntent.ReceiveIntent.getInitialIntent();
|
||||
// print(receivedIntent);
|
||||
// // Validate receivedIntent and warn the user, if it is not correct,
|
||||
// // but keep in mind it could be `null` or "empty"(`receivedIntent.isNull`).
|
||||
// } catch (e) {
|
||||
// // Handle exception
|
||||
// }
|
||||
// }
|
||||
|
||||
// int index = 0;
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Scaffold(
|
||||
// appBar: AppBar(
|
||||
// backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
// title: Text(widget.title),
|
||||
// ),
|
||||
// body: IndexedStack(
|
||||
// index: index,
|
||||
// children: [
|
||||
// InAppWebView(
|
||||
// initialUrlRequest: URLRequest(
|
||||
// url: WebUri(
|
||||
// "https://xueqiu.com/",
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// InAppWebView(
|
||||
// initialUrlRequest: URLRequest(
|
||||
// url: WebUri(
|
||||
// "https://inappwebview.dev/",
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// InAppWebView(
|
||||
// initialUrlRequest: URLRequest(
|
||||
// url: WebUri(
|
||||
// "https://github.com/",
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// floatingActionButton: FloatingActionButton(
|
||||
// onPressed: _incrementCounter,
|
||||
// tooltip: 'Increment',
|
||||
// child: const Icon(Icons.add),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nostr_sdk/utils/string_util.dart';
|
||||
import 'package:nowser/const/app_type.dart';
|
||||
import 'package:nowser/const/connect_type.dart';
|
||||
import 'package:nowser/data/app_db.dart';
|
||||
|
||||
@@ -11,6 +12,16 @@ class AppProvider extends ChangeNotifier {
|
||||
|
||||
List<App> get appList => _list;
|
||||
|
||||
List<App> remoteAppList() {
|
||||
List<App> apps = [];
|
||||
for (var app in _list) {
|
||||
if (app.appType == AppType.REMOTE) {
|
||||
apps.add(app);
|
||||
}
|
||||
}
|
||||
return apps;
|
||||
}
|
||||
|
||||
Map<int, App> _appMap = {};
|
||||
|
||||
Map<String, Map<String, int>> appPermissions = {};
|
||||
|
||||
316
lib/provider/remote_signing_provider.dart
Normal file
316
lib/provider/remote_signing_provider.dart
Normal file
@@ -0,0 +1,316 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nostr_sdk/client_utils/keys.dart';
|
||||
import 'package:nostr_sdk/event.dart';
|
||||
import 'package:nostr_sdk/event_kind.dart';
|
||||
import 'package:nostr_sdk/filter.dart';
|
||||
import 'package:nostr_sdk/nip46/nostr_remote_request.dart';
|
||||
import 'package:nostr_sdk/nip46/nostr_remote_response.dart';
|
||||
import 'package:nostr_sdk/relay/relay.dart';
|
||||
import 'package:nostr_sdk/relay/relay_isolate.dart';
|
||||
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/auth_type.dart';
|
||||
import 'package:nowser/data/app.dart';
|
||||
import 'package:nowser/provider/permission_check_mixin.dart';
|
||||
|
||||
import '../data/remote_signing_info.dart';
|
||||
import '../data/remote_signing_info_db.dart';
|
||||
import '../main.dart';
|
||||
|
||||
class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin {
|
||||
BuildContext? context;
|
||||
|
||||
void updateContext(BuildContext _context) {
|
||||
context = _context;
|
||||
}
|
||||
|
||||
// k - v ==> localPubkey - Relay
|
||||
Map<String, List<Relay>> relayMap = {};
|
||||
|
||||
// localPubkey - RemoteSigningInfo
|
||||
Map<String, RemoteSigningInfo> remoteSigningInfoMap = {};
|
||||
|
||||
// localPubkey - App
|
||||
Map<String, App> appMap = {};
|
||||
|
||||
Future<void> reload() async {
|
||||
relayMap = {};
|
||||
remoteSigningInfoMap = {};
|
||||
appMap = {};
|
||||
}
|
||||
|
||||
Future<void> load() async {
|
||||
var remoteAppList = appProvider.remoteAppList();
|
||||
for (var remoteApp in remoteAppList) {
|
||||
await add(remoteApp);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> add(App remoteApp) async {
|
||||
var remoteSigningInfo = await RemoteSigningInfoDB.getByAppId(remoteApp.id!);
|
||||
if (remoteSigningInfo != null &&
|
||||
StringUtil.isNotBlank(remoteSigningInfo.remoteSignerKey) &&
|
||||
StringUtil.isNotBlank(remoteSigningInfo.localPubkey) &&
|
||||
StringUtil.isNotBlank(remoteSigningInfo.relays)) {
|
||||
var localPubkey = remoteSigningInfo.localPubkey!;
|
||||
var pubkey = 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(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;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onRequest(
|
||||
Relay relay,
|
||||
NostrRemoteRequest request,
|
||||
RemoteSigningInfo remoteSigningInfo,
|
||||
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!;
|
||||
|
||||
NostrRemoteResponse? response;
|
||||
if (request.method == "ping") {
|
||||
response = NostrRemoteResponse(request.id, "pong");
|
||||
|
||||
sendResponse(relay, response, signer, localPubkey, remoteSignerPubkey!);
|
||||
} else {
|
||||
int? eventKind;
|
||||
String? authDetail;
|
||||
int authType = AuthType.GET_PUBLIC_KEY;
|
||||
dynamic eventObj;
|
||||
|
||||
if (request.method == "sign_event") {
|
||||
authType = AuthType.SIGN_EVENT;
|
||||
authDetail = request.params[0];
|
||||
|
||||
eventObj = jsonDecode(authDetail);
|
||||
eventKind = eventObj["kind"];
|
||||
} else if (request.method == "get_relays") {
|
||||
authType = AuthType.GET_RELAYS;
|
||||
} else if (request.method == "get_public_key") {
|
||||
authType = AuthType.GET_PUBLIC_KEY;
|
||||
} else if (request.method == "nip04_encrypt") {
|
||||
authType = AuthType.NIP04_ENCRYPT;
|
||||
authDetail = request.params[1];
|
||||
} else if (request.method == "nip04_decrypt") {
|
||||
authType = AuthType.NIP04_DECRYPT;
|
||||
authDetail = request.params[1];
|
||||
} else if (request.method == "nip44_encrypt") {
|
||||
authType = AuthType.NIP44_ENCRYPT;
|
||||
authDetail = request.params[1];
|
||||
} else if (request.method == "nip44_decrypt") {
|
||||
authType = AuthType.NIP44_DECRYPT;
|
||||
authDetail = request.params[1];
|
||||
}
|
||||
|
||||
checkPermission(context!, appType, code, authType,
|
||||
eventKind: eventKind, authDetail: authDetail, (app) {
|
||||
response = NostrRemoteResponse(request.id, "", error: "forbid");
|
||||
sendResponse(relay, response, signer, localPubkey, remoteSignerPubkey!);
|
||||
}, (app, signer) async {
|
||||
if (request.method == "sign_event") {
|
||||
var tags = eventObj["tags"];
|
||||
Event? event = Event(
|
||||
app.pubkey!, eventObj["kind"], tags ?? [], eventObj["content"],
|
||||
createdAt: eventObj["created_at"]);
|
||||
log(jsonEncode(event.toJson()));
|
||||
event = await signer.signEvent(event);
|
||||
if (event == null) {
|
||||
log("sign event fail");
|
||||
return;
|
||||
}
|
||||
|
||||
response =
|
||||
NostrRemoteResponse(request.id, jsonEncode(event.toJson()));
|
||||
} else if (request.method == "get_relays") {
|
||||
// TODO
|
||||
} else if (request.method == "get_public_key") {
|
||||
var pubkey = await signer.getPublicKey();
|
||||
response = NostrRemoteResponse(request.id, pubkey!);
|
||||
} else if (request.method == "nip04_encrypt") {
|
||||
var text = await signer.encrypt(localPubkey, authDetail);
|
||||
response = NostrRemoteResponse(request.id, text!);
|
||||
} else if (request.method == "nip04_decrypt") {
|
||||
var text = await signer.decrypt(localPubkey, authDetail);
|
||||
response = NostrRemoteResponse(request.id, text!);
|
||||
} else if (request.method == "nip44_encrypt") {
|
||||
var text = await signer.nip44Encrypt(localPubkey, authDetail);
|
||||
response = NostrRemoteResponse(request.id, text!);
|
||||
} else if (request.method == "nip44_decrypt") {
|
||||
var text = await signer.nip44Decrypt(localPubkey, authDetail);
|
||||
response = NostrRemoteResponse(request.id, text!);
|
||||
}
|
||||
|
||||
sendResponse(relay, response, signer, localPubkey, remoteSignerPubkey!);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendResponse(Relay relay, NostrRemoteResponse? response,
|
||||
NostrSigner signer, String localPubkey, String remoteSignerPubkey) async {
|
||||
if (response != null) {
|
||||
var result = await response.encrypt(signer, localPubkey);
|
||||
var event = Event(
|
||||
remoteSignerPubkey!,
|
||||
EventKind.NOSTR_REMOTE_SIGNING,
|
||||
[
|
||||
["p", localPubkey]
|
||||
],
|
||||
result!);
|
||||
|
||||
relay.send(["event", event.toJson()]);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onEvent(Relay relay, List<dynamic> json) async {
|
||||
var localPubkey = relay.relayStatus.addr;
|
||||
var remoteSigningInfo = remoteSigningInfoMap[localPubkey];
|
||||
if (remoteSigningInfo == null) {
|
||||
print("remoteSigningInfo is null");
|
||||
return;
|
||||
}
|
||||
var remotePubkey = getPublicKey(remoteSigningInfo.remoteSignerKey!);
|
||||
var nostrSigner = LocalNostrSigner(remoteSigningInfo.remoteSignerKey!);
|
||||
|
||||
final messageType = json[0];
|
||||
if (messageType == 'EVENT') {
|
||||
try {
|
||||
final event = Event.fromJson(json[2]);
|
||||
|
||||
// add some statistics
|
||||
relay.relayStatus.noteReceive();
|
||||
|
||||
event.sources.add(relay.url);
|
||||
|
||||
if (event.kind == EventKind.NOSTR_REMOTE_SIGNING) {
|
||||
var request = await NostrRemoteRequest.decrypt(
|
||||
event.content, nostrSigner, localPubkey);
|
||||
if (request != null) {}
|
||||
}
|
||||
} catch (err) {
|
||||
log(err.toString());
|
||||
}
|
||||
} else if (messageType == 'EOSE') {
|
||||
} else if (messageType == "NOTICE") {
|
||||
} else if (messageType == "AUTH") {
|
||||
// auth needed
|
||||
if (json.length < 2) {
|
||||
log("AUTH result not right.");
|
||||
return;
|
||||
}
|
||||
|
||||
final challenge = json[1] as String;
|
||||
var tags = [
|
||||
["relay", relay.url],
|
||||
["challenge", challenge]
|
||||
];
|
||||
Event? event = Event(remotePubkey, EventKind.AUTHENTICATION, tags, "");
|
||||
event = await nostrSigner.signEvent(event);
|
||||
if (event != null) {
|
||||
relay.send(["AUTH", event.toJson()], forceSend: true);
|
||||
|
||||
relay.relayStatus.authed = true;
|
||||
|
||||
if (relay.pendingAuthedMessages.isNotEmpty) {
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
for (var message in relay.pendingAuthedMessages) {
|
||||
relay.send(message);
|
||||
}
|
||||
relay.pendingAuthedMessages.clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The below code is design for relay n - remoteSignerKey n
|
||||
*/
|
||||
|
||||
// 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);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
326
lib/router/apps/add_remote_app_router.dart
Normal file
326
lib/router/apps/add_remote_app_router.dart
Normal file
@@ -0,0 +1,326 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:nostr_sdk/client_utils/keys.dart';
|
||||
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/data/remote_signing_info.dart';
|
||||
import 'package:nowser/data/remote_signing_info_db.dart';
|
||||
import 'package:nowser/util/router_util.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../component/appbar_back_btn_component.dart';
|
||||
import '../../const/base.dart';
|
||||
import '../../provider/key_provider.dart';
|
||||
|
||||
class AddRemoteAppRouter extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _AddRemoteAppRouter();
|
||||
}
|
||||
}
|
||||
|
||||
class _AddRemoteAppRouter extends State<AddRemoteAppRouter> {
|
||||
TextEditingController nostrconnectConn = TextEditingController();
|
||||
|
||||
TextEditingController bunkerConn = TextEditingController();
|
||||
|
||||
bool editBunker = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
relayAddrController.addListener(() {
|
||||
reloadBunker();
|
||||
});
|
||||
secretController.addListener(() {
|
||||
reloadBunker();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var themeData = Theme.of(context);
|
||||
var textColor = themeData.textTheme.bodyMedium!.color;
|
||||
var mainColor = themeData.primaryColor;
|
||||
|
||||
if (StringUtil.isBlank(bunkerConn.text)) {
|
||||
refreshBunkerUrl();
|
||||
}
|
||||
|
||||
Widget bunkerShowmoreBtn = Container(
|
||||
alignment: Alignment.center,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
editBunker = true;
|
||||
});
|
||||
},
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.expand_more),
|
||||
Text("Edit"),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
if (editBunker) {
|
||||
bunkerShowmoreBtn = Container(
|
||||
alignment: Alignment.center,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
editBunker = false;
|
||||
});
|
||||
},
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.expand_less),
|
||||
Text("Close Edit"),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget bunkerWidget = Container();
|
||||
if (editBunker) {
|
||||
bunkerWidget = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Relay",
|
||||
),
|
||||
controller: relayAddrController,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Secret",
|
||||
),
|
||||
controller: secretController,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
var main = DefaultTabController(
|
||||
length: 2,
|
||||
initialIndex: 1,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
height: 60,
|
||||
child: TabBar(
|
||||
labelColor: textColor,
|
||||
unselectedLabelColor: textColor,
|
||||
indicatorColor: mainColor,
|
||||
tabs: [
|
||||
Text(
|
||||
"Connect by\nnostrconnect:// url",
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(
|
||||
"Connect by\nbunker:// url",
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
child: TabBarView(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(Base.BASE_PADDING),
|
||||
child: ListView(
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: Base.BASE_PADDING_HALF),
|
||||
child: TextField(
|
||||
controller: nostrconnectConn,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
maxLines: 4,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: Base.BASE_PADDING_HALF),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Container()),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
scanNostrConnectQRCode();
|
||||
},
|
||||
icon: Icon(Icons.qr_code_scanner),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: Base.BASE_PADDING),
|
||||
width: double.infinity,
|
||||
child: FilledButton(
|
||||
onPressed: () {},
|
||||
child: Text("Confirm"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(Base.BASE_PADDING),
|
||||
child: ListView(
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: Base.BASE_PADDING_HALF),
|
||||
child: TextField(
|
||||
controller: bunkerConn,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
readOnly: true,
|
||||
maxLines: 4,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: Base.BASE_PADDING_HALF),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Container()),
|
||||
IconButton(
|
||||
onPressed: refreshBunkerUrl,
|
||||
icon: Icon(Icons.refresh)),
|
||||
// IconButton(
|
||||
// onPressed: showBunkerUrlQRCode,
|
||||
// icon: Icon(Icons.qr_code)),
|
||||
IconButton(
|
||||
onPressed: copyBunkerUrl,
|
||||
icon: Icon(Icons.copy)),
|
||||
],
|
||||
),
|
||||
),
|
||||
bunkerShowmoreBtn,
|
||||
bunkerWidget,
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: Base.BASE_PADDING),
|
||||
width: double.infinity,
|
||||
child: FilledButton(
|
||||
onPressed: confirmBunkerUrl,
|
||||
child: Text("Confirm"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: AppbarBackBtnComponent(),
|
||||
title: Text(
|
||||
"Add Remote App",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: themeData.textTheme.bodyLarge!.fontSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
padding: const EdgeInsets.all(Base.BASE_PADDING),
|
||||
child: main,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> scanNostrConnectQRCode() async {
|
||||
var value = await QRScanner.show(context);
|
||||
if (StringUtil.isNotBlank(value)) {
|
||||
nostrconnectConn.text = value;
|
||||
}
|
||||
}
|
||||
|
||||
void confirmNostrConnect() {
|
||||
var remoteSigningInfo =
|
||||
RemoteSigningInfo.parseNostrConnectUrl(nostrconnectConn.text);
|
||||
if (remoteSigningInfo == null) {
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
|
||||
remoteSigningInfo.remoteSignerKey = generatePrivateKey();
|
||||
remoteSigningInfo.createdAt = DateTime.now().millisecondsSinceEpoch ~/ 10;
|
||||
remoteSigningInfo.updatedAt = remoteSigningInfo.createdAt;
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
TextEditingController remoteSignerKeyController = TextEditingController();
|
||||
|
||||
TextEditingController relayAddrController = TextEditingController();
|
||||
|
||||
TextEditingController secretController = TextEditingController();
|
||||
|
||||
void refreshBunkerUrl() {
|
||||
remoteSignerKeyController.text = generatePrivateKey();
|
||||
secretController.text = StringUtil.rndNameStr(20);
|
||||
relayAddrController.text = "wss://relay.nsec.app";
|
||||
|
||||
reloadBunker();
|
||||
}
|
||||
|
||||
// void showBunkerUrlQRCode() {}
|
||||
|
||||
void copyBunkerUrl() {
|
||||
Clipboard.setData(ClipboardData(text: bunkerConn.text)).then((_) {
|
||||
// TODO
|
||||
// BotToast.showText(text: S.of(context).Copy_success);
|
||||
});
|
||||
}
|
||||
|
||||
void confirmBunkerUrl() {
|
||||
var remoteSignerKey = remoteSignerKeyController.text;
|
||||
var relays = [relayAddrController.text];
|
||||
|
||||
var remoteSigningInfo = RemoteSigningInfo(
|
||||
remoteSignerKey: remoteSignerKey,
|
||||
relays: relays.join(","),
|
||||
secret: secretController.text,
|
||||
createdAt: DateTime.now().millisecondsSinceEpoch ~/ 10,
|
||||
);
|
||||
remoteSigningInfo.updatedAt = remoteSigningInfo.createdAt;
|
||||
|
||||
RemoteSigningInfoDB.insert(remoteSigningInfo);
|
||||
RouterUtil.back(context);
|
||||
}
|
||||
|
||||
void reloadBunker() {
|
||||
var remoteSignerKey = remoteSignerKeyController.text;
|
||||
var remoteSignerPubkey = getPublicKey(remoteSignerKey);
|
||||
|
||||
var nostrRemoteSignerInfo = NostrRemoteSignerInfo(
|
||||
remoteUserPubkey: remoteSignerPubkey,
|
||||
relays: [relayAddrController.text],
|
||||
optionalSecret: secretController.text,
|
||||
);
|
||||
bunkerConn.text = nostrRemoteSignerInfo.toString();
|
||||
}
|
||||
}
|
||||
63
lib/router/apps/apps_router.dart
Normal file
63
lib/router/apps/apps_router.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nowser/const/router_path.dart';
|
||||
import 'package:nowser/provider/app_provider.dart';
|
||||
import 'package:nowser/util/router_util.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../component/appbar_back_btn_component.dart';
|
||||
import '../../const/base.dart';
|
||||
import '../me/me_router_app_item_component.dart';
|
||||
|
||||
class AppsRouter extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _AppsRouter();
|
||||
}
|
||||
}
|
||||
|
||||
class _AppsRouter extends State<AppsRouter> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var themeData = Theme.of(context);
|
||||
var _appProvider = Provider.of<AppProvider>(context);
|
||||
var appList = _appProvider.appList;
|
||||
|
||||
var main = ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
if (index >= appList.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var app = appList[index];
|
||||
return Container(
|
||||
child: MeRouterAppItemComponent(app),
|
||||
);
|
||||
},
|
||||
itemCount: appList.length,
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: AppbarBackBtnComponent(),
|
||||
title: Text(
|
||||
"Apps Manager",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: themeData.textTheme.bodyLarge!.fontSize,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
RouterUtil.router(context, RouterPath.ADD_REMOTE_APP);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(Base.BASE_PADDING),
|
||||
child: Icon(Icons.add),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ class _IndexRouter extends State<IndexRouter>
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
handleInitialIntent(context);
|
||||
});
|
||||
|
||||
remoteSigningProvider.updateContext(context);
|
||||
webProvider.checkBlank();
|
||||
|
||||
var main = Selector<WebProvider, WebNumInfo>(
|
||||
|
||||
@@ -170,12 +170,30 @@ class _MeRouter extends CustState<MeRouter> {
|
||||
));
|
||||
appWidgetList.add(Divider());
|
||||
}
|
||||
if (appWidgetList.isEmpty) {
|
||||
appWidgetList.add(GestureDetector(
|
||||
onTap: () {
|
||||
RouterUtil.router(context, RouterPath.APPS);
|
||||
},
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: Container(
|
||||
child: Text("no apps now"),
|
||||
),
|
||||
));
|
||||
appWidgetList.add(Divider());
|
||||
}
|
||||
appWidgetList.add(Container(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
"Show more apps",
|
||||
style: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
RouterUtil.router(context, RouterPath.APPS);
|
||||
},
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: Text(
|
||||
"Show more apps",
|
||||
style: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
@@ -203,6 +221,12 @@ class _MeRouter extends CustState<MeRouter> {
|
||||
));
|
||||
logList.add(Divider());
|
||||
}
|
||||
if (logList.isEmpty) {
|
||||
logList.add(Container(
|
||||
child: Text("no logs now"),
|
||||
));
|
||||
logList.add(Divider());
|
||||
}
|
||||
logList.add(Container(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
|
||||
Submodule packages/nostr_sdk updated: 19d579028a...5991c28ae8
24
pubspec.lock
24
pubspec.lock
@@ -628,6 +628,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
pretty_qr_code:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: pretty_qr_code
|
||||
sha256: cbdb4af29da1c1fa21dd76f809646c591320ab9e435d3b0eab867492d43607d5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
process_run:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -652,6 +660,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: qr
|
||||
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
qr_code_scanner:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: qr_code_scanner
|
||||
sha256: f23b68d893505a424f0bd2e324ebea71ed88465d572d26bb8d2e78a4749591fd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
receive_intent:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -46,6 +46,8 @@ dependencies:
|
||||
google_fonts: ^6.2.1
|
||||
flutter_font_picker: ^1.4.0
|
||||
flutter_secure_storage: ^9.2.2
|
||||
pretty_qr_code: ^3.3.0
|
||||
qr_code_scanner: ^1.0.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user