remote add nostrconnect url login

This commit is contained in:
DASHU
2025-11-19 19:56:11 +08:00
parent 63c24e5294
commit 09cdfe436b
5 changed files with 163 additions and 43 deletions

View File

@@ -0,0 +1,83 @@
import 'package:nostr_sdk/utils/string_util.dart';
import 'package:nowser/data/remote_signing_info.dart';
class NostrconnectRemoteSigningInfo extends RemoteSigningInfo {
String? perms;
String? name;
String? url;
String? image;
NostrconnectRemoteSigningInfo({
super.id,
super.appId,
super.localPubkey,
super.remotePubkey,
super.remoteSignerKey,
super.relays,
super.secret,
super.createdAt,
super.updatedAt,
this.perms,
this.name,
this.url,
this.image,
});
NostrconnectRemoteSigningInfo.fromJson(Map<String, dynamic> json) {
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'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
perms = json['perms'];
name = json['name'];
url = json['url'];
image = json['image'];
}
// don't implement toJson method and when saving data it only save the super.toJson data.
// @override
// Map<String, dynamic> toJson() {
// final data = super.toJson();
// data['perms'] = perms;
// data['name'] = name;
// data['url'] = url;
// data['image'] = image;
// return data;
// }
static NostrconnectRemoteSigningInfo? parseNostrConnectUrl(
String nostrconnectUrlText) {
var uri = Uri.parse(nostrconnectUrlText);
var parsList = uri.queryParametersAll;
var pars = uri.queryParameters;
var localPubkey = uri.host;
var relays = parsList['relay'];
var secret = pars['secret'];
var perms = pars['perms'];
var name = pars['name'];
var url = pars['url'];
var image = pars['image'];
if (relays == null || relays.isEmpty || StringUtil.isBlank(secret)) {
return null;
}
return NostrconnectRemoteSigningInfo(
localPubkey: localPubkey,
relays: relays.join(","),
secret: secret,
perms: perms,
name: name,
url: url,
image: image,
);
}
}

View File

@@ -45,20 +45,4 @@ class RemoteSigningInfo {
data['updated_at'] = this.updatedAt; data['updated_at'] = this.updatedAt;
return data; 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(","),
);
}
} }

View File

@@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:nostr_sdk/client_utils/keys.dart'; import 'package:nostr_sdk/client_utils/keys.dart';
@@ -9,6 +10,7 @@ import 'package:nostr_sdk/event_kind.dart';
import 'package:nostr_sdk/filter.dart'; import 'package:nostr_sdk/filter.dart';
import 'package:nostr_sdk/nip46/nostr_remote_request.dart'; import 'package:nostr_sdk/nip46/nostr_remote_request.dart';
import 'package:nostr_sdk/nip46/nostr_remote_response.dart'; import 'package:nostr_sdk/nip46/nostr_remote_response.dart';
import 'package:nostr_sdk/relay/client_connected.dart';
import 'package:nostr_sdk/relay/relay.dart'; import 'package:nostr_sdk/relay/relay.dart';
import 'package:nostr_sdk/relay/relay_isolate.dart'; import 'package:nostr_sdk/relay/relay_isolate.dart';
import 'package:nostr_sdk/relay/relay_status.dart'; import 'package:nostr_sdk/relay/relay_status.dart';
@@ -19,6 +21,7 @@ import 'package:nostr_sdk/utils/string_util.dart';
import 'package:nowser/const/app_type.dart'; import 'package:nowser/const/app_type.dart';
import 'package:nowser/const/auth_type.dart'; import 'package:nowser/const/auth_type.dart';
import 'package:nowser/data/app.dart'; import 'package:nowser/data/app.dart';
import 'package:nowser/data/nostrconnect_remote_signing_info.dart';
import 'package:nowser/provider/permission_check_mixin.dart'; import 'package:nowser/provider/permission_check_mixin.dart';
import 'package:relay_sdk/network/memory/mem_relay_client.dart'; import 'package:relay_sdk/network/memory/mem_relay_client.dart';
@@ -77,7 +80,7 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin {
return str!; return str!;
} }
Future<void> addRemoteApp(App remoteApp, String encryptKey) async { Future<List<Relay>?> addRemoteApp(App remoteApp, String encryptKey) async {
var remoteSigningInfo = var remoteSigningInfo =
await RemoteSigningInfoDB.getByAppId(remoteApp.id!, encryptKey); await RemoteSigningInfoDB.getByAppId(remoteApp.id!, encryptKey);
if (remoteSigningInfo != null && if (remoteSigningInfo != null &&
@@ -91,6 +94,8 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin {
remoteSigningInfoMap[remoteSignerPubkey] = remoteSigningInfo; remoteSigningInfoMap[remoteSignerPubkey] = remoteSigningInfo;
appMap[remoteSignerPubkey] = remoteApp; appMap[remoteSignerPubkey] = remoteApp;
relayMap[remoteSignerPubkey] = relays; relayMap[remoteSignerPubkey] = relays;
return relays;
} }
} }
@@ -313,7 +318,12 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin {
if (event != null) { if (event != null) {
for (var relay in relays) { for (var relay in relays) {
relay.send(["EVENT", event.toJson()]); if (relay.relayStatus.connected == ClientConneccted.CONNECTED) {
relay.send(["EVENT", event.toJson()]);
} else {
relay.pendingAuthedMessages.add(["EVENT", event.toJson()]);
relay.pendingMessages.add(["EVENT", event.toJson()]);
}
} }
} }
} }
@@ -400,11 +410,49 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin {
} }
} }
Future<bool> addNostrconnectRemoteSigningInfo(
NostrconnectRemoteSigningInfo remoteSigningInfo) async {
NostrSigner signerSigner =
LocalNostrSigner(remoteSigningInfo.remoteSignerKey!);
var remoteSignerPubkey = getPublicKey(remoteSigningInfo.remoteSignerKey!);
var appType = AppType.REMOTE;
var code = remoteSignerPubkey;
App? app = await getApp(appType, code);
app.name = remoteSigningInfo.name;
if (StringUtil.isBlank(app.name)) {
app.name = remoteSigningInfo.url;
}
app.pubkey = remoteSigningInfo.remotePubkey;
app.image = remoteSigningInfo.image;
await AuthAppConnectDialog.show(context!, app);
app = appProvider.getApp(appType, code);
if (app == null) {
BotToast.showText(text: "Remote App connect fail.");
return false;
}
remoteSigningInfo.appId = app.id;
remoteSigningInfo.updatedAt = DateTime.now().millisecondsSinceEpoch ~/ 1000;
await RemoteSigningInfoDB.insert(remoteSigningInfo, encryptKey);
var relays = await addRemoteApp(app, encryptKey);
if (relays != null) {
var response = NostrRemoteResponse(
StringUtil.rndNameStr(12), remoteSigningInfo.secret!);
await sendResponse(relays, response, signerSigner,
remoteSigningInfo.localPubkey!, remoteSignerPubkey);
}
return true;
}
List<RemoteSigningInfo> _penddingRemoteApps = []; List<RemoteSigningInfo> _penddingRemoteApps = [];
List<RemoteSigningInfo> get penddingRemoteApps => _penddingRemoteApps; List<RemoteSigningInfo> get penddingRemoteApps => _penddingRemoteApps;
Future<void> saveRemoteSigningInfo( Future<void> saveBunkerRemoteSigningInfo(
RemoteSigningInfo remoteSigningInfo) async { RemoteSigningInfo remoteSigningInfo) async {
await RemoteSigningInfoDB.insert(remoteSigningInfo, encryptKey); await RemoteSigningInfoDB.insert(remoteSigningInfo, encryptKey);
await reloadPenddingRemoteApps(); await reloadPenddingRemoteApps();

View File

@@ -4,11 +4,13 @@ import 'package:flutter/services.dart';
import 'package:nostr_sdk/client_utils/keys.dart'; import 'package:nostr_sdk/client_utils/keys.dart';
import 'package:nostr_sdk/nip19/nip19.dart'; import 'package:nostr_sdk/nip19/nip19.dart';
import 'package:nostr_sdk/nip46/nostr_remote_signer_info.dart'; import 'package:nostr_sdk/nip46/nostr_remote_signer_info.dart';
import 'package:nostr_sdk/utils/platform_util.dart';
import 'package:nostr_sdk/utils/string_util.dart'; import 'package:nostr_sdk/utils/string_util.dart';
import 'package:nowser/component/cust_state.dart'; import 'package:nowser/component/cust_state.dart';
import 'package:nowser/component/qrscanner.dart'; import 'package:nowser/component/qrscanner.dart';
import 'package:nowser/component/simple_qrcode_dialog.dart'; import 'package:nowser/component/simple_qrcode_dialog.dart';
import 'package:nowser/component/user/user_name_component.dart'; import 'package:nowser/component/user/user_name_component.dart';
import 'package:nowser/data/nostrconnect_remote_signing_info.dart';
import 'package:nowser/data/remote_signing_info.dart'; import 'package:nowser/data/remote_signing_info.dart';
import 'package:nowser/data/remote_signing_info_db.dart'; import 'package:nowser/data/remote_signing_info_db.dart';
import 'package:nowser/main.dart'; import 'package:nowser/main.dart';
@@ -205,14 +207,9 @@ class _AddRemoteAppRouter extends CustState<AddRemoteAppRouter> {
unselectedLabelColor: textColor, unselectedLabelColor: textColor,
indicatorColor: mainColor, indicatorColor: mainColor,
tabs: [ tabs: [
GestureDetector( Text(
child: Text( "${s.Connect_by}\nnostrconnect:// url",
"${s.Connect_by}\nnostrconnect:// url", textAlign: TextAlign.center,
textAlign: TextAlign.center,
),
onTap: () {
BotToast.showText(text: s.Comming_soon);
},
), ),
Text( Text(
"${s.Connect_by}\nbunker:// url", "${s.Connect_by}\nbunker:// url",
@@ -246,12 +243,14 @@ class _AddRemoteAppRouter extends CustState<AddRemoteAppRouter> {
child: Row( child: Row(
children: [ children: [
Expanded(child: Container()), Expanded(child: Container()),
IconButton( PlatformUtil.isAndroid() || PlatformUtil.isIOS()
onPressed: () { ? IconButton(
scanNostrConnectQRCode(); onPressed: () {
}, scanNostrConnectQRCode();
icon: const Icon(Icons.qr_code_scanner), },
), icon: const Icon(Icons.qr_code_scanner),
)
: Container(),
], ],
), ),
), ),
@@ -259,9 +258,7 @@ class _AddRemoteAppRouter extends CustState<AddRemoteAppRouter> {
margin: const EdgeInsets.only(top: Base.BASE_PADDING), margin: const EdgeInsets.only(top: Base.BASE_PADDING),
width: double.infinity, width: double.infinity,
child: FilledButton( child: FilledButton(
onPressed: () { onPressed: confirmNostrConnect,
BotToast.showText(text: s.Comming_soon);
},
child: Text(s.Confirm), child: Text(s.Confirm),
), ),
), ),
@@ -347,19 +344,27 @@ class _AddRemoteAppRouter extends CustState<AddRemoteAppRouter> {
String remoteSignerKey = generatePrivateKey(); String remoteSignerKey = generatePrivateKey();
void confirmNostrConnect() { Future<void> confirmNostrConnect() async {
var remoteSigningInfo = var remoteSigningInfo = NostrconnectRemoteSigningInfo.parseNostrConnectUrl(
RemoteSigningInfo.parseNostrConnectUrl(nostrconnectConn.text); nostrconnectConn.text.trim());
if (remoteSigningInfo == null) { if (remoteSigningInfo == null) {
// TODO BotToast.showText(text: 'Invalid nostrconnect url');
return; return;
} }
remoteSigningInfo.remotePubkey = pubkey;
remoteSigningInfo.remoteSignerKey = remoteSignerKey; remoteSigningInfo.remoteSignerKey = remoteSignerKey;
remoteSigningInfo.createdAt = DateTime.now().millisecondsSinceEpoch ~/ 1000; remoteSigningInfo.createdAt = DateTime.now().millisecondsSinceEpoch ~/ 1000;
remoteSigningInfo.updatedAt = remoteSigningInfo.createdAt; remoteSigningInfo.updatedAt = remoteSigningInfo.createdAt;
// TODO var result = await remoteSigningProvider
.addNostrconnectRemoteSigningInfo(remoteSigningInfo);
if (result) {
BotToast.showText(text: 'Add remote app success');
RouterUtil.back(context);
} else {
BotToast.showText(text: 'Add remote app fail');
}
} }
TextEditingController relayAddrController = TextEditingController(); TextEditingController relayAddrController = TextEditingController();
@@ -412,7 +417,7 @@ class _AddRemoteAppRouter extends CustState<AddRemoteAppRouter> {
); );
remoteSigningInfo.updatedAt = remoteSigningInfo.createdAt; remoteSigningInfo.updatedAt = remoteSigningInfo.createdAt;
remoteSigningProvider.saveRemoteSigningInfo(remoteSigningInfo); remoteSigningProvider.saveBunkerRemoteSigningInfo(remoteSigningInfo);
RouterUtil.back(context); RouterUtil.back(context);
} }