diff --git a/.gitmodules b/.gitmodules index 9a02209..5e07447 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "packages/nostr_sdk"] path = packages/nostr_sdk url = git@github.com:haorendashu/nostr_sdk.git +[submodule "packages/relay_sdk"] + path = packages/relay_sdk + url = git@github.com:haorendashu/relay_sdk.git diff --git a/lib/main.dart b/lib/main.dart index c9fb0e2..4325482 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -11,6 +12,7 @@ import 'package:nostr_sdk/utils/string_util.dart'; import 'package:nowser/data/db.dart'; import 'package:nowser/provider/android_signer_mixin.dart'; import 'package:nowser/provider/app_provider.dart'; +import 'package:nowser/provider/build_in_relay_provider.dart'; import 'package:nowser/provider/key_provider.dart'; import 'package:nowser/provider/permission_check_mixin.dart'; import 'package:nowser/provider/web_provider.dart'; @@ -52,8 +54,13 @@ late RemoteSigningProvider remoteSigningProvider; late Map routes; +late RootIsolateToken rootIsolateToken; + +late BuildInRelayProvider buildInRelayProvider; + Future main() async { WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); + rootIsolateToken = RootIsolateToken.instance!; if (!PlatformUtil.isWeb() && PlatformUtil.isPC()) { await windowManager.ensureInitialized(); @@ -87,6 +94,7 @@ Future main() async { Future doInit() async { keyProvider = KeyProvider(); appProvider = AppProvider(); + buildInRelayProvider = BuildInRelayProvider(); var dataUtilTask = DataUtil.getInstance(); var keyTask = keyProvider.init(); @@ -156,6 +164,9 @@ class _MyApp extends State { ListenableProvider.value( value: remoteSigningProvider, ), + ListenableProvider.value( + value: buildInRelayProvider, + ), ], child: MaterialApp( builder: BotToastInit(), diff --git a/lib/provider/build_in_relay_provider.dart b/lib/provider/build_in_relay_provider.dart new file mode 100644 index 0000000..ead84be --- /dev/null +++ b/lib/provider/build_in_relay_provider.dart @@ -0,0 +1,128 @@ +import 'package:bot_toast/bot_toast.dart'; +import 'package:flutter/material.dart'; +import 'package:nostr_sdk/relay/relay_info.dart'; +import 'package:nowser/const/base.dart'; +import 'package:relay_sdk/network/connection.dart'; +import 'package:relay_sdk/network/memory/mem_relay_client.dart'; +import 'package:relay_sdk/relay_manager.dart'; + +import '../main.dart'; +import '../util/ip_util.dart'; + +class BuildInRelayProvider extends ChangeNotifier { + String ip = "127.0.0.1"; + + static int port = 4870; + + RelayInfo relayInfo = RelayInfo( + "${Base.APP_NAME}'s build-in relay", + "This is a build-in relay for Nowser. It don't save any message and it's designed for NWC.", + "29320975df855fe34a7b45ada2421e2c741c37c0136901fe477133a91eb18b07", + "29320975df855fe34a7b45ada2421e2c741c37c0136901fe477133a91eb18b07", + ["47"], + Base.APP_NAME, + Base.VERSION_NAME); + + RelayManager? _relayManager; + + bool isRunning() { + if (_relayManager != null) { + return true; + } + + return false; + } + + Future start() async { + if (isRunning()) { + return; + } + + String? localIp = await IpUtil.getIp(); + if (localIp != null) { + ip = localIp; + } + + try { + var rm = _getRelayManager(); + await rm.start(relayInfo, port); + + await Future.delayed(const Duration(seconds: 10)); + + if (_penddingMemRelayClients.isNotEmpty) { + for (var memRelayClient in _penddingMemRelayClients) { + rm.addMemClient(memRelayClient); + try { + memRelayClient.onConnected(); + } catch (e) {} + } + } + _penddingMemRelayClients.clear(); + } catch (e) { + print(e); + BotToast.showText(text: "Start server fail."); + if (_relayManager != null) { + try { + _relayManager!.stop(); + } catch (e) {} + } + _relayManager = null; + } + + notifyListeners(); + } + + void stop() { + if (_relayManager != null) { + _relayManager!.stop(); + } + + _relayManager = null; + + notifyListeners(); + } + + RelayManager _getRelayManager() { + if (_relayManager == null) { + _relayManager = RelayManager(rootIsolateToken); + _relayManager!.openFilterCheck = true; + _relayManager!.openDB = false; + // _relayManager!.trafficCounter = trafficCounterProvider; + // _relayManager!.networkLogsManager = networkLogProvider; + _relayManager!.rootIsolateToken = rootIsolateToken; + _relayManager!.connectionListener = connectionListener; + } + + return _relayManager!; + } + + int connectionNum() { + if (_relayManager != null) { + return _relayManager!.getConnections().length; + } + + return 0; + } + + List getConnections() { + if (_relayManager != null) { + return _relayManager!.getConnections(); + } + + return []; + } + + void connectionListener() { + notifyListeners(); + } + + List _penddingMemRelayClients = []; + + void addMemClient(MemRelayClient memRelayClient) { + if (isRunning()) { + _relayManager!.addMemClient(memRelayClient); + } else { + _penddingMemRelayClients.add(memRelayClient); + } + } +} diff --git a/lib/provider/remote_signing_provider.dart b/lib/provider/remote_signing_provider.dart index ef2b4de..e33e7cc 100644 --- a/lib/provider/remote_signing_provider.dart +++ b/lib/provider/remote_signing_provider.dart @@ -18,11 +18,13 @@ 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 'package:relay_sdk/network/memory/mem_relay_client.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'; +import 'build_in_relay_provider.dart'; class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin { BuildContext? context; @@ -77,8 +79,18 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin { List relays = []; for (var relayAddr in relayAddrs) { - // use pubkey relace with - var relay = RelayIsolate(relayAddr, RelayStatus(remoteSignerPubkey)); + bool isLocalRelay = false; + Relay? relay; + var relayStatus = RelayStatus(remoteSignerPubkey); + if (relayAddr == "ws://localhost:${BuildInRelayProvider.port}" || + relayAddr == "ws://127.0.0.1:${BuildInRelayProvider.port}") { + // use pubkey relace with + relay = MemRelayClient(relayAddr, relayStatus); + isLocalRelay = true; + } else { + // use pubkey relace with + relay = RelayIsolate(relayAddr, relayStatus); + } var filter = Filter( p: [remoteSignerPubkey], @@ -89,7 +101,11 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin { .add(["REQ", StringUtil.rndNameStr(10), filter.toJson()]); relay.onMessage = _onEvent; - relay.connect(); + if (isLocalRelay) { + buildInRelayProvider.addMemClient(relay as MemRelayClient); + } else { + relay.connect(); + } relays.add(relay); } @@ -122,7 +138,7 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin { if (request.params.length <= 1) { response = NostrRemoteResponse(request.id, "", error: "params error"); } else { - if (request.params[0] == remoteSigningInfo.remotePubkey && + if (request.params[0] == remoteSignerPubkey && request.params[1] == remoteSigningInfo.secret) { // check pass, init app var newApp = await getApp(appType, code); @@ -182,6 +198,7 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin { int? eventKind; String? authDetail; + String? thirdPartyPubkey; int authType = AuthType.GET_PUBLIC_KEY; dynamic eventObj; @@ -197,15 +214,19 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin { authType = AuthType.GET_PUBLIC_KEY; } else if (request.method == "nip04_encrypt") { authType = AuthType.NIP04_ENCRYPT; + thirdPartyPubkey = request.params[0]; authDetail = request.params[1]; } else if (request.method == "nip04_decrypt") { authType = AuthType.NIP04_DECRYPT; + thirdPartyPubkey = request.params[0]; authDetail = request.params[1]; } else if (request.method == "nip44_encrypt") { authType = AuthType.NIP44_ENCRYPT; + thirdPartyPubkey = request.params[0]; authDetail = request.params[1]; } else if (request.method == "nip44_decrypt") { authType = AuthType.NIP44_DECRYPT; + thirdPartyPubkey = request.params[0]; authDetail = request.params[1]; } @@ -235,16 +256,16 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin { var pubkey = await signer.getPublicKey(); response = NostrRemoteResponse(request.id, pubkey!); } else if (request.method == "nip04_encrypt") { - var text = await signer.encrypt(localPubkey, authDetail); + var text = await signer.encrypt(thirdPartyPubkey, authDetail); response = NostrRemoteResponse(request.id, text!); } else if (request.method == "nip04_decrypt") { - var text = await signer.decrypt(localPubkey, authDetail); + var text = await signer.decrypt(thirdPartyPubkey, authDetail); response = NostrRemoteResponse(request.id, text!); } else if (request.method == "nip44_encrypt") { - var text = await signer.nip44Encrypt(localPubkey, authDetail); + var text = await signer.nip44Encrypt(thirdPartyPubkey, authDetail); response = NostrRemoteResponse(request.id, text!); } else if (request.method == "nip44_decrypt") { - var text = await signer.nip44Decrypt(localPubkey, authDetail); + var text = await signer.nip44Decrypt(thirdPartyPubkey, authDetail); response = NostrRemoteResponse(request.id, text!); } @@ -257,6 +278,8 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin { Future sendResponse(List relays, NostrRemoteResponse? response, NostrSigner signer, String localPubkey, String remoteSignerPubkey) async { if (response != null) { + // print("response:"); + // print(response.toString()); var result = await response.encrypt(signer, localPubkey); Event? event = Event( remoteSignerPubkey, @@ -267,10 +290,7 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin { result!); event = await signer.signEvent(event); - var signerPubkey = await signer.getPublicKey(); - // print("response:"); if (event != null) { - // print(event.toJson()); for (var relay in relays) { relay.send(["EVENT", event.toJson()]); } @@ -290,7 +310,7 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin { print("remoteSigningInfo is null"); return; } - var nostrSigner = LocalNostrSigner(remoteSigningInfo.remoteSignerKey!); + var remoteSigner = LocalNostrSigner(remoteSigningInfo.remoteSignerKey!); var signer = keyProvider.getSigner(remoteSigningInfo.remotePubkey!); if (signer == null) { return; @@ -309,7 +329,7 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin { if (event.kind == EventKind.NOSTR_REMOTE_SIGNING) { if (handledIds[event.id] == null) { var request = await NostrRemoteRequest.decrypt( - event.content, signer, event.pubkey); + event.content, remoteSigner, event.pubkey); var relays = relayMap[remoteSignerPubkey]; if (relays == null || relays.isEmpty) { relays = [relay]; @@ -341,7 +361,7 @@ class RemoteSigningProvider extends ChangeNotifier with PermissionCheckMixin { ]; Event? event = Event(remoteSignerPubkey, EventKind.AUTHENTICATION, tags, ""); - event = await nostrSigner.signEvent(event); + event = await remoteSigner.signEvent(event); if (event != null) { relay.send(["AUTH", event.toJson()], forceSend: true); diff --git a/lib/router/apps/add_remote_app_router.dart b/lib/router/apps/add_remote_app_router.dart index 4165064..0f5f0ec 100644 --- a/lib/router/apps/add_remote_app_router.dart +++ b/lib/router/apps/add_remote_app_router.dart @@ -5,11 +5,14 @@ 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/cust_state.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'; +import 'package:nowser/provider/build_in_relay_provider.dart'; +import 'package:nowser/util/ip_util.dart'; import 'package:nowser/util/router_util.dart'; import 'package:provider/provider.dart'; @@ -25,15 +28,19 @@ class AddRemoteAppRouter extends StatefulWidget { } } -class _AddRemoteAppRouter extends State { +class _AddRemoteAppRouter extends CustState { TextEditingController nostrconnectConn = TextEditingController(); TextEditingController bunkerConn = TextEditingController(); bool editBunker = false; + bool localRelay = false; + String? pubkey; + String? localIp; + @override void initState() { super.initState(); @@ -47,7 +54,12 @@ class _AddRemoteAppRouter extends State { } @override - Widget build(BuildContext context) { + Future onReady(BuildContext context) async { + localIp = await IpUtil.getIp(); + } + + @override + Widget doBuild(BuildContext context) { var themeData = Theme.of(context); var textColor = themeData.textTheme.bodyMedium!.color; var mainColor = themeData.primaryColor; @@ -135,10 +147,28 @@ class _AddRemoteAppRouter extends State { bunkerWidget = Column( mainAxisSize: MainAxisSize.min, children: [ + Container( + child: Row( + children: [ + Container( + margin: EdgeInsets.only( + right: Base.BASE_PADDING_HALF, + ), + child: Text("Local Relay:"), + ), + Checkbox( + value: localRelay, + onChanged: onLocalRelayChange, + ), + Expanded(child: Container()), + ], + ), + ), Container( child: TextField( decoration: InputDecoration( labelText: "Relay", + enabled: !localRelay, ), controller: relayAddrController, ), @@ -328,12 +358,17 @@ class _AddRemoteAppRouter extends State { TextEditingController relayAddrController = TextEditingController(); + static const String DEFAULT_RELAY = "wss://relay.nsec.app"; + TextEditingController secretController = TextEditingController(); void refreshBunkerUrl() { remoteSignerKey = generatePrivateKey(); secretController.text = StringUtil.rndNameStr(20); - relayAddrController.text = "wss://relay.nsec.app"; + if (StringUtil.isBlank(localIp) || + !relayAddrController.text.contains(localIp!)) { + relayAddrController.text = DEFAULT_RELAY; + } reloadBunker(); } @@ -354,7 +389,12 @@ class _AddRemoteAppRouter extends State { return; } - var relays = [relayAddrController.text]; + List relays = []; + if (localRelay) { + relays.add("ws://127.0.0.1:${BuildInRelayProvider.port}"); + } else { + relays.add(relayAddrController.text); + } var remoteSigningInfo = RemoteSigningInfo( remotePubkey: pubkey, @@ -377,4 +417,19 @@ class _AddRemoteAppRouter extends State { ); bunkerConn.text = nostrRemoteSignerInfo.toString(); } + + void onLocalRelayChange(bool? v) { + if (v == true) { + relayAddrController.text = "ws://${localIp}:${BuildInRelayProvider.port}"; + } else if (v == false) { + relayAddrController.text = DEFAULT_RELAY; + } + + if (v != null) { + setState(() { + localRelay = v; + }); + reloadBunker(); + } + } } diff --git a/lib/router/index/index_router.dart b/lib/router/index/index_router.dart index cc7dc73..a683481 100644 --- a/lib/router/index/index_router.dart +++ b/lib/router/index/index_router.dart @@ -37,6 +37,9 @@ class _IndexRouter extends CustState Future onReady(BuildContext context) async { await remoteSigningProvider.reload(); await remoteSigningProvider.reloadPenddingRemoteApps(); + + // start build-in + buildInRelayProvider.start(); } @override diff --git a/lib/util/ip_util.dart b/lib/util/ip_util.dart new file mode 100644 index 0000000..c8c017f --- /dev/null +++ b/lib/util/ip_util.dart @@ -0,0 +1,19 @@ +import 'dart:io'; + +class IpUtil { + static Future getIp() async { + var ips = await NetworkInterface.list(); + for (var interface in ips) { + print('== Interface: ${interface.name} =='); + if (interface.name == "WLAN") { + for (var addr in interface.addresses) { + print( + '${addr.address} ${addr.host} ${addr.isLoopback} ${addr.rawAddress} ${addr.type.name}'); + return addr.address; + } + } + } + + return ips.first.addresses.first.address; + } +} diff --git a/packages/relay_sdk b/packages/relay_sdk new file mode 160000 index 0000000..bef0b65 --- /dev/null +++ b/packages/relay_sdk @@ -0,0 +1 @@ +Subproject commit bef0b65fec2a61438fdbc1e39ab4c3b5308032fd diff --git a/pubspec.lock b/pubspec.lock index bab22cd..09bfe3f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -764,6 +764,13 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.5" + relay_sdk: + dependency: "direct main" + description: + path: "packages/relay_sdk" + relative: true + source: path + version: "0.0.1" rxdart: dependency: transitive description: @@ -1090,5 +1097,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.5.0 <4.0.0" + dart: ">=3.5.3 <4.0.0" flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 92675eb..3682515 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,8 @@ dependencies: cupertino_icons: ^1.0.6 nostr_sdk: path: packages/nostr_sdk + relay_sdk: + path: packages/relay_sdk receive_intent: ^0.2.5 provider: ^6.1.2