mirror of
https://github.com/haorendashu/nowser.git
synced 2025-12-17 09:54:19 +01:00
simple support for nip07
This commit is contained in:
@@ -1,15 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nostr_sdk/utils/string_util.dart';
|
||||
import 'package:nowser/component/auth_dialog/auth_dialog_base_componnet.dart';
|
||||
import 'package:nowser/const/connect_type.dart';
|
||||
import 'package:nowser/const/reasonable_permissions.dart';
|
||||
import 'package:nowser/data/app_db.dart';
|
||||
import 'package:nowser/main.dart';
|
||||
import 'package:nowser/util/router_util.dart';
|
||||
|
||||
import '../../const/base.dart';
|
||||
import '../../data/app.dart';
|
||||
|
||||
class AuthAppConnectDialog extends StatefulWidget {
|
||||
static void show(BuildContext context) {
|
||||
showDialog(
|
||||
App app;
|
||||
|
||||
AuthAppConnectDialog({required this.app});
|
||||
|
||||
static Future<App?> show(BuildContext context, App app) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AuthAppConnectDialog();
|
||||
return AuthAppConnectDialog(
|
||||
app: app,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -65,7 +77,12 @@ class _AuthAppConnectDialog extends State<AuthAppConnectDialog> {
|
||||
children: list,
|
||||
);
|
||||
|
||||
return AuthDialogBaseComponnet(title: "App Connect", child: child);
|
||||
return AuthDialogBaseComponnet(
|
||||
app: widget.app,
|
||||
title: "App Connect",
|
||||
onConfirm: onConfirm,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
void onConnectTypeChange(int? value) {
|
||||
@@ -75,4 +92,22 @@ class _AuthAppConnectDialog extends State<AuthAppConnectDialog> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onConfirm() async {
|
||||
var app = widget.app;
|
||||
app.connectType = connectType;
|
||||
if (StringUtil.isBlank(app.pubkey) && keyProvider.pubkeys.isNotEmpty) {
|
||||
app.pubkey = keyProvider.pubkeys.first;
|
||||
}
|
||||
if (connectType == ConnectType.REASONABLE) {
|
||||
app.alwaysAllow = ReasonablePermissions.text;
|
||||
}
|
||||
app.createdAt = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
app.updatedAt = app.createdAt;
|
||||
|
||||
if (await AppDB.insert(app) > 0) {
|
||||
await appProvider.reload();
|
||||
RouterUtil.back(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nostr_sdk/utils/string_util.dart';
|
||||
import 'package:nowser/const/app_type.dart';
|
||||
import 'package:nowser/const/base.dart';
|
||||
import 'package:nowser/data/app.dart';
|
||||
|
||||
import '../app/app_type_component.dart';
|
||||
|
||||
class AuthAppInfoComponent extends StatefulWidget {
|
||||
App app;
|
||||
|
||||
AuthAppInfoComponent({required this.app});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _AuthAppInfoComponent();
|
||||
@@ -16,6 +22,26 @@ class _AuthAppInfoComponent extends State<AuthAppInfoComponent> {
|
||||
Widget build(BuildContext context) {
|
||||
var themeData = Theme.of(context);
|
||||
|
||||
String? name;
|
||||
String? des;
|
||||
if (StringUtil.isNotBlank(widget.app.name)) {
|
||||
name = widget.app.name;
|
||||
des = widget.app.code;
|
||||
} else if (StringUtil.isBlank(widget.app.name)) {
|
||||
name = widget.app.code;
|
||||
}
|
||||
|
||||
List<Widget> rightList = [];
|
||||
if (StringUtil.isNotBlank(name)) {
|
||||
rightList.add(Text(
|
||||
name!,
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
));
|
||||
}
|
||||
if (StringUtil.isNotBlank(des)) {
|
||||
rightList.add(Text(des!));
|
||||
}
|
||||
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
@@ -24,12 +50,13 @@ class _AuthAppInfoComponent extends State<AuthAppInfoComponent> {
|
||||
height: 64,
|
||||
child: Card(
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(Base.BASE_PADDING_HALF),
|
||||
padding: const EdgeInsets.all(Base.BASE_PADDING_HALF),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(right: Base.BASE_PADDING_HALF),
|
||||
margin:
|
||||
const EdgeInsets.only(right: Base.BASE_PADDING_HALF),
|
||||
child: Icon(
|
||||
Icons.image,
|
||||
size: 46,
|
||||
@@ -38,13 +65,7 @@ class _AuthAppInfoComponent extends State<AuthAppInfoComponent> {
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"APP NAME",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text("This is App info des"),
|
||||
],
|
||||
children: rightList,
|
||||
)
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,14 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nostr_sdk/utils/string_util.dart';
|
||||
import 'package:nowser/component/auth_dialog/auth_dialog_base_componnet.dart';
|
||||
import 'package:nowser/const/auth_result.dart';
|
||||
import 'package:nowser/data/app.dart';
|
||||
import 'package:nowser/util/router_util.dart';
|
||||
|
||||
import '../../const/base.dart';
|
||||
|
||||
class AuthDialog extends StatefulWidget {
|
||||
static void show(BuildContext context) {
|
||||
showDialog(
|
||||
App app;
|
||||
|
||||
int authType;
|
||||
|
||||
int? eventKind;
|
||||
|
||||
String? authDetail;
|
||||
|
||||
AuthDialog({
|
||||
required this.app,
|
||||
required this.authType,
|
||||
this.eventKind,
|
||||
this.authDetail,
|
||||
});
|
||||
|
||||
static Future<int?> show(BuildContext context, App app, int authType,
|
||||
{int? eventKind, String? authDetail}) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AuthDialog();
|
||||
return AuthDialog(
|
||||
app: app,
|
||||
authType: authType,
|
||||
eventKind: eventKind,
|
||||
authDetail: authDetail,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -31,14 +56,21 @@ class _AuthDialog extends State<AuthDialog> {
|
||||
);
|
||||
var hintColor = themeData.hintColor;
|
||||
|
||||
// handle this title and des with widget.authType
|
||||
String authTitle = "Sign Event";
|
||||
String authDes = "Allow web.nostrmo.com to sign a authenticate event";
|
||||
authTitle = "AuthType ${widget.authType}";
|
||||
|
||||
List<Widget> list = [];
|
||||
list.add(Container(
|
||||
margin: baseMargin,
|
||||
child: Text(
|
||||
"Allow web.nostrmo.com to sign a authenticate event",
|
||||
authDes,
|
||||
),
|
||||
));
|
||||
|
||||
List<Widget> detailList = [];
|
||||
if (StringUtil.isNotBlank(widget.authDetail)) {
|
||||
var showDetailWidget = GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
@@ -53,8 +85,6 @@ class _AuthDialog extends State<AuthDialog> {
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
List<Widget> detailList = [];
|
||||
if (showDetail) {
|
||||
detailList.add(Container(
|
||||
height: 210,
|
||||
@@ -65,8 +95,7 @@ class _AuthDialog extends State<AuthDialog> {
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Text(
|
||||
"GoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGood"),
|
||||
child: Text(widget.authDetail!),
|
||||
),
|
||||
));
|
||||
} else {}
|
||||
@@ -74,6 +103,7 @@ class _AuthDialog extends State<AuthDialog> {
|
||||
margin: baseMargin,
|
||||
child: showDetailWidget,
|
||||
));
|
||||
}
|
||||
|
||||
list.add(Container(
|
||||
height: 250,
|
||||
@@ -90,6 +120,16 @@ class _AuthDialog extends State<AuthDialog> {
|
||||
children: list,
|
||||
);
|
||||
|
||||
return AuthDialogBaseComponnet(title: "Sign Event", child: child);
|
||||
return AuthDialogBaseComponnet(
|
||||
app: widget.app,
|
||||
title: authTitle,
|
||||
onConfirm: onConfirm,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
onConfirm() {
|
||||
print("auth dialog confirm!");
|
||||
RouterUtil.back(context, AuthResult.OK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nostr_sdk/nip19/nip19.dart';
|
||||
import 'package:nostr_sdk/utils/string_util.dart';
|
||||
import 'package:nowser/const/base.dart';
|
||||
import 'package:nowser/data/app.dart';
|
||||
import 'package:nowser/provider/key_provider.dart';
|
||||
import 'package:nowser/util/router_util.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../logo_component.dart';
|
||||
import 'auth_app_info_component.dart';
|
||||
|
||||
class AuthDialogBaseComponnet extends StatefulWidget {
|
||||
App app;
|
||||
|
||||
String title;
|
||||
|
||||
Widget child;
|
||||
|
||||
AuthDialogBaseComponnet({required this.title, required this.child});
|
||||
Function onConfirm;
|
||||
|
||||
Function(String)? onPubkeyChange;
|
||||
|
||||
AuthDialogBaseComponnet({
|
||||
required this.app,
|
||||
required this.title,
|
||||
required this.child,
|
||||
required this.onConfirm,
|
||||
this.onPubkeyChange,
|
||||
});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
@@ -30,11 +47,43 @@ class _AuthDialog extends State<AuthDialogBaseComponnet> {
|
||||
|
||||
List<Widget> list = [];
|
||||
|
||||
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(widget.app.pubkey) && pubkeys.isNotEmpty) {
|
||||
widget.app.pubkey = pubkeys.first;
|
||||
}
|
||||
|
||||
return DropdownButton<String>(
|
||||
items: items,
|
||||
onChanged: (String? value) {
|
||||
if (StringUtil.isNotBlank(value)) {
|
||||
widget.app.pubkey = value;
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
value: widget.app.pubkey,
|
||||
);
|
||||
}, selector: (context, provider) {
|
||||
return provider.pubkeys;
|
||||
});
|
||||
|
||||
var topWidget = Container(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
margin: const EdgeInsets.only(
|
||||
left: Base.BASE_PADDING,
|
||||
right: Base.BASE_PADDING,
|
||||
top: Base.BASE_PADDING_HALF,
|
||||
@@ -46,16 +95,7 @@ class _AuthDialog extends State<AuthDialogBaseComponnet> {
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(right: Base.BASE_PADDING_HALF),
|
||||
alignment: Alignment.centerRight,
|
||||
child: DropdownButton<String>(
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
child: Text("npubxxxxxx"),
|
||||
value: "npubxxxxxx",
|
||||
)
|
||||
],
|
||||
onChanged: (Object? value) {},
|
||||
value: "npubxxxxxx",
|
||||
),
|
||||
child: keyWidget,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -76,7 +116,9 @@ class _AuthDialog extends State<AuthDialogBaseComponnet> {
|
||||
|
||||
list.add(Container(
|
||||
margin: baseMargin,
|
||||
child: AuthAppInfoComponent(),
|
||||
child: AuthAppInfoComponent(
|
||||
app: widget.app,
|
||||
),
|
||||
));
|
||||
|
||||
list.add(Container(
|
||||
@@ -112,7 +154,12 @@ class _AuthDialog extends State<AuthDialogBaseComponnet> {
|
||||
margin: EdgeInsets.only(
|
||||
left: Base.BASE_PADDING_HALF,
|
||||
),
|
||||
child: FilledButton(onPressed: () {}, child: Text("Confirm")),
|
||||
child: FilledButton(
|
||||
onPressed: () {
|
||||
widget.onConfirm();
|
||||
},
|
||||
child: Text("Confirm"),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
||||
@@ -64,7 +64,7 @@ class _WebHomeComponent extends State<WebHomeComponent> {
|
||||
RouterUtil.router(context, RouterPath.WEB_TABS);
|
||||
}),
|
||||
wrapBottomBtn(const Icon(Icons.space_dashboard), onTap: () {
|
||||
AuthDialog.show(context);
|
||||
// AuthDialog.show(context);
|
||||
// AuthAppConnectDialog.show(context);
|
||||
}),
|
||||
wrapBottomBtn(const Icon(Icons.segment), onTap: () {
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:nostr_sdk/event.dart';
|
||||
import 'package:nostr_sdk/utils/string_util.dart';
|
||||
import 'package:nowser/component/webview/web_info.dart';
|
||||
import 'package:nowser/const/app_type.dart';
|
||||
import 'package:nowser/const/auth_type.dart';
|
||||
import 'package:nowser/main.dart';
|
||||
import 'package:nowser/util/permission_check_mixin.dart';
|
||||
|
||||
import '../../const/auth_result.dart';
|
||||
import '../../data/app.dart';
|
||||
|
||||
class WebViewComponent extends StatefulWidget {
|
||||
WebInfo webInfo;
|
||||
@@ -21,23 +34,479 @@ class WebViewComponent extends StatefulWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _WebViewComponent extends State<WebViewComponent> {
|
||||
class _WebViewComponent extends State<WebViewComponent>
|
||||
with PermissionCheckMixin {
|
||||
InAppWebViewController? webViewController;
|
||||
|
||||
late ContextMenu contextMenu;
|
||||
InAppWebViewSettings settings = InAppWebViewSettings(
|
||||
isInspectable: kDebugMode,
|
||||
mediaPlaybackRequiresUserGesture: false,
|
||||
allowsInlineMediaPlayback: true,
|
||||
iframeAllow: "camera; microphone",
|
||||
iframeAllowFullscreen: true);
|
||||
|
||||
PullToRefreshController? pullToRefreshController;
|
||||
|
||||
double progress = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
contextMenu = ContextMenu(
|
||||
menuItems: [
|
||||
ContextMenuItem(
|
||||
id: 1,
|
||||
title: "Special",
|
||||
action: () async {
|
||||
print("Menu item Special clicked!");
|
||||
print(await webViewController?.getSelectedText());
|
||||
await webViewController?.clearFocus();
|
||||
})
|
||||
],
|
||||
settings: ContextMenuSettings(hideDefaultSystemContextMenuItems: false),
|
||||
onCreateContextMenu: (hitTestResult) async {
|
||||
print("onCreateContextMenu");
|
||||
print(hitTestResult.extra);
|
||||
print(await webViewController?.getSelectedText());
|
||||
},
|
||||
onHideContextMenu: () {
|
||||
print("onHideContextMenu");
|
||||
},
|
||||
onContextMenuActionItemClicked: (contextMenuItemClicked) async {
|
||||
var id = contextMenuItemClicked.id;
|
||||
print("onContextMenuActionItemClicked: " +
|
||||
id.toString() +
|
||||
" " +
|
||||
contextMenuItemClicked.title);
|
||||
});
|
||||
|
||||
pullToRefreshController = kIsWeb ||
|
||||
![TargetPlatform.iOS, TargetPlatform.android]
|
||||
.contains(defaultTargetPlatform)
|
||||
? null
|
||||
: PullToRefreshController(
|
||||
settings: PullToRefreshSettings(
|
||||
color: Colors.blue,
|
||||
),
|
||||
onRefresh: () async {
|
||||
if (defaultTargetPlatform == TargetPlatform.android) {
|
||||
webViewController?.reload();
|
||||
} else if (defaultTargetPlatform == TargetPlatform.iOS ||
|
||||
defaultTargetPlatform == TargetPlatform.macOS) {
|
||||
webViewController?.loadUrl(
|
||||
urlRequest:
|
||||
URLRequest(url: await webViewController?.getUrl()));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: InAppWebView(
|
||||
child: Stack(
|
||||
children: [
|
||||
InAppWebView(
|
||||
initialUrlRequest: URLRequest(url: WebUri(widget.webInfo.url)),
|
||||
initialUserScripts: UnmodifiableListView<UserScript>([]),
|
||||
initialSettings: settings,
|
||||
contextMenu: contextMenu,
|
||||
pullToRefreshController: pullToRefreshController,
|
||||
onWebViewCreated: (controller) async {
|
||||
webViewController = controller;
|
||||
// initJSHandle(controller);
|
||||
initJSHandle(controller);
|
||||
widget.onWebViewCreated(widget.webInfo, controller);
|
||||
},
|
||||
onTitleChanged: (controller, title) {
|
||||
widget.onTitleChanged(widget.webInfo, controller, title);
|
||||
},
|
||||
onLoadStart: (controller, url) async {},
|
||||
onPermissionRequest: (controller, request) async {
|
||||
return PermissionResponse(
|
||||
resources: request.resources,
|
||||
action: PermissionResponseAction.GRANT);
|
||||
},
|
||||
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
||||
// var uri = navigationAction.request.url!;
|
||||
// if (uri.scheme == "lightning" &&
|
||||
// StringUtil.isNotBlank(uri.path)) {
|
||||
// var result =
|
||||
// await NIP07Dialog.show(context, NIP07Methods.lightning);
|
||||
// if (result == true) {
|
||||
// await LightningUtil.goToPay(context, uri.path);
|
||||
// }
|
||||
// return NavigationActionPolicy.CANCEL;
|
||||
// }
|
||||
|
||||
// if (uri.scheme == "nostr+walletconnect") {
|
||||
// webViewProvider.closeAndReturn(uri.toString());
|
||||
// return NavigationActionPolicy.CANCEL;
|
||||
// }
|
||||
|
||||
// if (![
|
||||
// "http",
|
||||
// "https",
|
||||
// "file",
|
||||
// "chrome",
|
||||
// "data",
|
||||
// "javascript",
|
||||
// "about"
|
||||
// ].contains(uri.scheme)) {
|
||||
// if (await canLaunchUrl(uri)) {
|
||||
// // Launch the App
|
||||
// await launchUrl(
|
||||
// uri,
|
||||
// );
|
||||
// // and cancel the request
|
||||
// return NavigationActionPolicy.CANCEL;
|
||||
// }
|
||||
// }
|
||||
|
||||
return NavigationActionPolicy.ALLOW;
|
||||
},
|
||||
onLoadStop: (controller, url) async {
|
||||
pullToRefreshController?.endRefreshing();
|
||||
addInitScript(controller);
|
||||
},
|
||||
onReceivedError: (controller, request, error) {
|
||||
pullToRefreshController?.endRefreshing();
|
||||
},
|
||||
onProgressChanged: (controller, progress) {
|
||||
if (progress == 100) {
|
||||
pullToRefreshController?.endRefreshing();
|
||||
}
|
||||
setState(() {
|
||||
this.progress = progress / 100;
|
||||
});
|
||||
},
|
||||
onUpdateVisitedHistory: (controller, url, isReload) {},
|
||||
onConsoleMessage: (controller, consoleMessage) {
|
||||
print(consoleMessage);
|
||||
},
|
||||
),
|
||||
progress < 1.0
|
||||
? LinearProgressIndicator(value: progress)
|
||||
: Container(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> nip07Reject(String resultId, String contnet) async {
|
||||
var script = "window.nostr.reject(\"$resultId\", \"${contnet}\");";
|
||||
await webViewController!.evaluateJavascript(source: script);
|
||||
}
|
||||
|
||||
void initJSHandle(InAppWebViewController controller) {
|
||||
controller.addJavaScriptHandler(
|
||||
handlerName: "Nowser_JS_getPublicKey",
|
||||
callback: (jsMsgs) async {
|
||||
var jsMsg = jsMsgs[0];
|
||||
// print("Nowser_JS_getPublicKey $jsMsg");
|
||||
var jsonObj = jsonDecode(jsMsg);
|
||||
var resultId = jsonObj["resultId"];
|
||||
|
||||
String? code = await getCode();
|
||||
if (code == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkPermission(context, AppType.WEB, code, AuthType.GET_PUBLIC_KEY,
|
||||
() {
|
||||
nip07Reject(resultId, "Forbid");
|
||||
}, (app, signer) {
|
||||
var pubkey = app.pubkey;
|
||||
var script = "window.nostr.callback(\"$resultId\", \"$pubkey\");";
|
||||
controller.evaluateJavascript(source: script);
|
||||
});
|
||||
},
|
||||
);
|
||||
controller.addJavaScriptHandler(
|
||||
handlerName: "Nowser_JS_signEvent",
|
||||
callback: (jsMsgs) async {
|
||||
var jsMsg = jsMsgs[0];
|
||||
// print("Nowser_JS_signEvent $jsMsg");
|
||||
var jsonObj = jsonDecode(jsMsg);
|
||||
var resultId = jsonObj["resultId"];
|
||||
var content = jsonObj["msg"];
|
||||
|
||||
String? code = await getCode();
|
||||
if (code == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var eventObj = jsonDecode(content);
|
||||
var eventKind = eventObj["kind"];
|
||||
if (eventKind is int) {
|
||||
checkPermission(context, AppType.WEB, code, AuthType.SIGN_EVENT,
|
||||
eventKind: eventKind, () {
|
||||
nip07Reject(resultId, "Forbid");
|
||||
}, (app, signer) async {
|
||||
var tags = eventObj["tags"];
|
||||
Event? event = Event(app.pubkey!, eventObj["kind"], tags ?? [],
|
||||
eventObj["content"]);
|
||||
event = await signer.signEvent(event);
|
||||
if (event == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var eventResultStr = jsonEncode(event.toJson());
|
||||
// TODO this method to handle " may be error
|
||||
eventResultStr = eventResultStr.replaceAll("\"", "\\\"");
|
||||
var script =
|
||||
"window.nostr.callback(\"$resultId\", JSON.parse(\"$eventResultStr\"));";
|
||||
webViewController!.evaluateJavascript(source: script);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
nip07Reject(resultId, "Sign fail");
|
||||
}
|
||||
},
|
||||
);
|
||||
controller.addJavaScriptHandler(
|
||||
handlerName: "Nowser_JS_getRelays",
|
||||
callback: (jsMsgs) async {
|
||||
var jsMsg = jsMsgs[0];
|
||||
// print("Nowser_JS_getRelays $jsMsg");
|
||||
var jsonObj = jsonDecode(jsMsg);
|
||||
var resultId = jsonObj["resultId"];
|
||||
|
||||
String? code = await getCode();
|
||||
if (code == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkPermission(context, AppType.WEB, code, AuthType.GET_RELAYS, () {
|
||||
nip07Reject(resultId, "Forbid");
|
||||
}, (app, signer) {
|
||||
// TODO handle getRelays
|
||||
// var app = appProvider.getApp(AppType.WEB, code);
|
||||
// if (app != null) {
|
||||
// var relayMaps = {};
|
||||
// var relayAddrs = relayProvider.relayAddrs;
|
||||
// for (var relayAddr in relayAddrs) {
|
||||
// relayMaps[relayAddr] = {"read": true, "write": true};
|
||||
// }
|
||||
// var resultStr = jsonEncode(relayMaps);
|
||||
// resultStr = resultStr.replaceAll("\"", "\\\"");
|
||||
// var script =
|
||||
// "window.nostr.callback(\"$resultId\", JSON.parse(\"$resultStr\"));";
|
||||
// webViewController!.evaluateJavascript(source: script);
|
||||
// }
|
||||
});
|
||||
},
|
||||
);
|
||||
controller.addJavaScriptHandler(
|
||||
handlerName: "Nowser_JS_nip04_encrypt",
|
||||
callback: (jsMsgs) async {
|
||||
var jsMsg = jsMsgs[0];
|
||||
// print("Nowser_JS_nip04_encrypt $jsMsg");
|
||||
var jsonObj = jsonDecode(jsMsg);
|
||||
var resultId = jsonObj["resultId"];
|
||||
var msg = jsonObj["msg"];
|
||||
if (msg != null && msg is Map) {
|
||||
var pubkey = msg["pubkey"];
|
||||
var plaintext = msg["plaintext"];
|
||||
|
||||
String? code = await getCode();
|
||||
if (code == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkPermission(context, AppType.WEB, code, AuthType.NIP04_ENCRYPT,
|
||||
() {
|
||||
nip07Reject(resultId, "Forbid");
|
||||
}, (app, signer) async {
|
||||
var resultStr = await signer.encrypt(pubkey, plaintext);
|
||||
if (StringUtil.isBlank(resultStr)) {
|
||||
return;
|
||||
}
|
||||
var script =
|
||||
"window.nostr.callback(\"$resultId\", \"$resultStr\");";
|
||||
webViewController!.evaluateJavascript(source: script);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
controller.addJavaScriptHandler(
|
||||
handlerName: "Nowser_JS_nip04_decrypt",
|
||||
callback: (jsMsgs) async {
|
||||
var jsMsg = jsMsgs[0];
|
||||
// print("Nowser_JS_nip04_decrypt $jsMsg");
|
||||
var jsonObj = jsonDecode(jsMsg.message);
|
||||
var resultId = jsonObj["resultId"];
|
||||
var msg = jsonObj["msg"];
|
||||
if (msg != null && msg is Map) {
|
||||
var pubkey = msg["pubkey"];
|
||||
var ciphertext = msg["ciphertext"];
|
||||
|
||||
String? code = await getCode();
|
||||
if (code == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkPermission(context, AppType.WEB, code, AuthType.NIP04_DECRYPT,
|
||||
() {
|
||||
nip07Reject(resultId, "Forbid");
|
||||
}, (app, signer) async {
|
||||
var app = appProvider.getApp(AppType.WEB, code);
|
||||
if (app != null) {
|
||||
var resultStr = await signer.decrypt(pubkey, ciphertext);
|
||||
if (StringUtil.isBlank(resultStr)) {
|
||||
return;
|
||||
}
|
||||
var script =
|
||||
"window.nostr.callback(\"$resultId\", \"$resultStr\");";
|
||||
webViewController!.evaluateJavascript(source: script);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
controller.addJavaScriptHandler(
|
||||
handlerName: "Nowser_JS_nip44_encrypt",
|
||||
callback: (jsMsgs) async {
|
||||
var jsMsg = jsMsgs[0];
|
||||
// print("Nowser_JS_nip04_encrypt $jsMsg");
|
||||
var jsonObj = jsonDecode(jsMsg);
|
||||
var resultId = jsonObj["resultId"];
|
||||
var msg = jsonObj["msg"];
|
||||
if (msg != null && msg is Map) {
|
||||
var pubkey = msg["pubkey"];
|
||||
var plaintext = msg["plaintext"];
|
||||
|
||||
String? code = await getCode();
|
||||
if (code == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkPermission(context, AppType.WEB, code, AuthType.NIP44_ENCRYPT,
|
||||
() {
|
||||
nip07Reject(resultId, "Forbid");
|
||||
}, (app, signer) async {
|
||||
var resultStr = await signer.nip44Encrypt(pubkey, plaintext);
|
||||
if (StringUtil.isBlank(resultStr)) {
|
||||
return;
|
||||
}
|
||||
var script =
|
||||
"window.nostr.callback(\"$resultId\", \"$resultStr\");";
|
||||
webViewController!.evaluateJavascript(source: script);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
controller.addJavaScriptHandler(
|
||||
handlerName: "Nowser_JS_nip44_decrypt",
|
||||
callback: (jsMsgs) async {
|
||||
var jsMsg = jsMsgs[0];
|
||||
// print("Nowser_JS_nip04_decrypt $jsMsg");
|
||||
var jsonObj = jsonDecode(jsMsg.message);
|
||||
var resultId = jsonObj["resultId"];
|
||||
var msg = jsonObj["msg"];
|
||||
if (msg != null && msg is Map) {
|
||||
var pubkey = msg["pubkey"];
|
||||
var ciphertext = msg["ciphertext"];
|
||||
|
||||
String? code = await getCode();
|
||||
if (code == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkPermission(context, AppType.WEB, code, AuthType.NIP44_DECRYPT,
|
||||
() {
|
||||
nip07Reject(resultId, "Forbid");
|
||||
}, (app, signer) async {
|
||||
var resultStr = await signer.nip44Decrypt(pubkey, ciphertext);
|
||||
if (StringUtil.isBlank(resultStr)) {
|
||||
return;
|
||||
}
|
||||
var script =
|
||||
"window.nostr.callback(\"$resultId\", \"$resultStr\");";
|
||||
webViewController!.evaluateJavascript(source: script);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void addInitScript(InAppWebViewController controller) {
|
||||
controller.evaluateJavascript(source: """
|
||||
window.nostr = {
|
||||
_call(channel, message) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var resultId = "callbackResult_" + Math.floor(Math.random() * 100000000);
|
||||
var arg = {"resultId": resultId};
|
||||
if (message) {
|
||||
arg["msg"] = message;
|
||||
}
|
||||
var argStr = JSON.stringify(arg);
|
||||
window.flutter_inappwebview
|
||||
.callHandler(channel, argStr);
|
||||
window.nostr._requests[resultId] = {resolve, reject}
|
||||
});
|
||||
},
|
||||
_requests: {},
|
||||
callback(resultId, message) {
|
||||
window.nostr._requests[resultId].resolve(message);
|
||||
},
|
||||
reject(resultId, message) {
|
||||
window.nostr._requests[resultId].reject(message);
|
||||
},
|
||||
async getPublicKey() {
|
||||
return window.nostr._call("Nowser_JS_getPublicKey");
|
||||
},
|
||||
async signEvent(event) {
|
||||
return window.nostr._call("Nowser_JS_signEvent", JSON.stringify(event));
|
||||
},
|
||||
async getRelays() {
|
||||
return window.nostr._call("Nowser_JS_getRelays");
|
||||
},
|
||||
nip04: {
|
||||
async encrypt(pubkey, plaintext) {
|
||||
return window.nostr._call("Nowser_JS_nip04_encrypt", {"pubkey": pubkey, "plaintext": plaintext});
|
||||
},
|
||||
async decrypt(pubkey, ciphertext) {
|
||||
return window.nostr._call("Nowser_JS_nip04_decrypt", {"pubkey": pubkey, "ciphertext": ciphertext});
|
||||
},
|
||||
},
|
||||
nip44: {
|
||||
async encrypt(pubkey, plaintext) {
|
||||
return window.nostr._call("Nowser_JS_nip44_encrypt", {"pubkey": pubkey, "plaintext": plaintext});
|
||||
},
|
||||
async decrypt(pubkey, ciphertext) {
|
||||
return window.nostr._call("Nowser_JS_nip44_decrypt", {"pubkey": pubkey, "ciphertext": ciphertext});
|
||||
},
|
||||
},
|
||||
};
|
||||
""");
|
||||
}
|
||||
|
||||
Future<String?> getCode() async {
|
||||
if (webViewController != null) {
|
||||
var url = await webViewController!.getUrl();
|
||||
if (url != null) {
|
||||
return url.host;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<App> getApp(int appType, String code) async {
|
||||
String? name;
|
||||
String? image;
|
||||
if (webViewController != null) {
|
||||
name = await webViewController!.getTitle();
|
||||
|
||||
var favicons = await webViewController!.getFavicons();
|
||||
if (favicons.isNotEmpty) {
|
||||
image = favicons.first.url.toString();
|
||||
}
|
||||
}
|
||||
return App(appType: appType, code: code, name: name, image: image);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
class AuthResult {
|
||||
static const int OK = 1;
|
||||
|
||||
static const int CANCEL = -1;
|
||||
static const int REJECT = -1;
|
||||
|
||||
static const int ASK = 0;
|
||||
}
|
||||
|
||||
3
lib/const/reasonable_permissions.dart
Normal file
3
lib/const/reasonable_permissions.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
class ReasonablePermissions {
|
||||
static const String text = "1;3;4;5;6;7,2-22242";
|
||||
}
|
||||
5
lib/const/zap_type.dart
Normal file
5
lib/const/zap_type.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
class ZapType {
|
||||
static const int LIGHTNING_APP = 1;
|
||||
|
||||
static const int NWC = 2;
|
||||
}
|
||||
@@ -1,25 +1,66 @@
|
||||
class App {
|
||||
int id;
|
||||
int? id;
|
||||
|
||||
String pubkey;
|
||||
String? pubkey;
|
||||
|
||||
int appType;
|
||||
int? appType;
|
||||
|
||||
String code;
|
||||
String? code;
|
||||
|
||||
String name;
|
||||
String? name;
|
||||
|
||||
String? image;
|
||||
|
||||
String? permissions;
|
||||
int? connectType;
|
||||
|
||||
App({
|
||||
required this.id,
|
||||
required this.pubkey,
|
||||
required this.appType,
|
||||
required this.code,
|
||||
required this.name,
|
||||
String? alwaysAllow;
|
||||
|
||||
String? alwaysReject;
|
||||
|
||||
int? createdAt;
|
||||
|
||||
int? updatedAt;
|
||||
|
||||
App(
|
||||
{this.id,
|
||||
this.pubkey,
|
||||
this.appType,
|
||||
this.code,
|
||||
this.name,
|
||||
this.image,
|
||||
this.permissions,
|
||||
});
|
||||
this.connectType,
|
||||
this.alwaysAllow,
|
||||
this.alwaysReject,
|
||||
this.createdAt,
|
||||
this.updatedAt});
|
||||
|
||||
App.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
pubkey = json['pubkey'];
|
||||
appType = json['app_type'];
|
||||
code = json['code'];
|
||||
name = json['name'];
|
||||
image = json['image'];
|
||||
connectType = json['connect_type'];
|
||||
alwaysAllow = json['always_allow'];
|
||||
alwaysReject = json['always_reject'];
|
||||
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['pubkey'] = this.pubkey;
|
||||
data['app_type'] = this.appType;
|
||||
data['code'] = this.code;
|
||||
data['name'] = this.name;
|
||||
data['image'] = this.image;
|
||||
data['connect_type'] = this.connectType;
|
||||
data['always_allow'] = this.alwaysAllow;
|
||||
data['always_reject'] = this.alwaysReject;
|
||||
data['created_at'] = this.createdAt;
|
||||
data['updated_at'] = this.updatedAt;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,41 @@
|
||||
class AppDB {}
|
||||
import 'package:nowser/data/db.dart';
|
||||
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
import 'app.dart';
|
||||
|
||||
class AppDB {
|
||||
static Future<List<App>> all() async {
|
||||
List<App> objs = [];
|
||||
var db = await DB.getCurrentDatabase();
|
||||
List<Map<String, dynamic>> list = await db.rawQuery("select * from app");
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var json = list[i];
|
||||
objs.add(App.fromJson(json));
|
||||
}
|
||||
return objs;
|
||||
}
|
||||
|
||||
static Future<App?> get(int id, {DatabaseExecutor? db}) async {
|
||||
db = await DB.getDB(db);
|
||||
var list = await db.query("app", where: "id = ?", whereArgs: [id]);
|
||||
if (list.isNotEmpty) {
|
||||
return App.fromJson(list[0]);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<int> insert(App o, {DatabaseExecutor? db}) async {
|
||||
db = await DB.getDB(db);
|
||||
return await db.insert("app", o.toJson());
|
||||
}
|
||||
|
||||
static Future update(App o, {DatabaseExecutor? db}) async {
|
||||
db = await DB.getDB(db);
|
||||
await db.update("app", o.toJson(), where: "id = ?", whereArgs: [o.pubkey]);
|
||||
}
|
||||
|
||||
static Future<void> delete(int id, {DatabaseExecutor? db}) async {
|
||||
db = await DB.getDB(db);
|
||||
db.execute("delete from app where id = ?");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
class AuthLog {
|
||||
int id;
|
||||
int? id;
|
||||
|
||||
int appId;
|
||||
int? appId;
|
||||
|
||||
int authType;
|
||||
int? authType;
|
||||
|
||||
int? eventKind;
|
||||
|
||||
@@ -11,18 +11,41 @@ class AuthLog {
|
||||
|
||||
String? content;
|
||||
|
||||
int authResult;
|
||||
int? authResult;
|
||||
|
||||
int createdAt;
|
||||
int? createdAt;
|
||||
|
||||
AuthLog({
|
||||
required this.id,
|
||||
required this.appId,
|
||||
required this.authType,
|
||||
AuthLog(
|
||||
{this.id,
|
||||
this.appId,
|
||||
this.authType,
|
||||
this.eventKind,
|
||||
this.title,
|
||||
this.content,
|
||||
required this.authResult,
|
||||
required this.createdAt,
|
||||
});
|
||||
this.authResult,
|
||||
this.createdAt});
|
||||
|
||||
AuthLog.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
appId = json['app_id'];
|
||||
authType = json['auth_type'];
|
||||
eventKind = json['event_kind'];
|
||||
title = json['title'];
|
||||
content = json['content'];
|
||||
authResult = json['auth_result'];
|
||||
createdAt = json['created_at'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['id'] = this.id;
|
||||
data['app_id'] = this.appId;
|
||||
data['auth_type'] = this.authType;
|
||||
data['event_kind'] = this.eventKind;
|
||||
data['title'] = this.title;
|
||||
data['content'] = this.content;
|
||||
data['auth_result'] = this.authResult;
|
||||
data['created_at'] = this.createdAt;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +36,13 @@ class DB {
|
||||
static Future<void> _onCreate(Database db, int version) async {
|
||||
// init db
|
||||
db.execute(
|
||||
"create table app(id integer not null constraint app_pk primary key autoincrement,pubkey text not null,app_type integer not null,code text not null,name text not null,image text,permissions text);");
|
||||
"create table app(id integer not null constraint app_pk primary key autoincrement,pubkey text not null,app_type integer not null,code text not null,name text,image text,connect_type integer not null,always_allow text,always_reject text,created_at integer not null,updated_at integer not null);");
|
||||
db.execute(
|
||||
"create table auth_log(id integer not null constraint auth_log_pk primary key autoincrement,app_id integer not null,auth_type integer not null,event_kind integer,title text,content text,auth_result integer not null,created_at integer not null);");
|
||||
db.execute("create index auth_log_index on auth_log (app_id);");
|
||||
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);");
|
||||
db.execute("create index zap_log_index on zap_log (app_id);");
|
||||
}
|
||||
|
||||
static Future<Database> getCurrentDatabase() async {
|
||||
|
||||
37
lib/data/zap_log.dart
Normal file
37
lib/data/zap_log.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
class ZapLog {
|
||||
int? id;
|
||||
|
||||
int? appId;
|
||||
|
||||
int? zapType;
|
||||
|
||||
int? num;
|
||||
|
||||
int? createdAt;
|
||||
|
||||
ZapLog({
|
||||
this.id,
|
||||
this.appId,
|
||||
this.zapType,
|
||||
this.num,
|
||||
this.createdAt,
|
||||
});
|
||||
|
||||
ZapLog.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
appId = json['app_id'];
|
||||
zapType = json['zap_type'];
|
||||
num = json['num'];
|
||||
createdAt = json['created_at'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['id'] = this.id;
|
||||
data['app_id'] = this.appId;
|
||||
data['zap_type'] = this.zapType;
|
||||
data['num'] = this.num;
|
||||
data['created_at'] = this.createdAt;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
1
lib/data/zap_log_db.dart
Normal file
1
lib/data/zap_log_db.dart
Normal file
@@ -0,0 +1 @@
|
||||
class ZapLogDB {}
|
||||
@@ -8,6 +8,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:nostr_sdk/utils/string_util.dart';
|
||||
import 'package:nowser/data/db.dart';
|
||||
import 'package:nowser/provider/app_provider.dart';
|
||||
import 'package:nowser/provider/key_provider.dart';
|
||||
import 'package:nowser/provider/web_provider.dart';
|
||||
import 'package:nowser/router/index/index_router.dart';
|
||||
@@ -31,12 +32,15 @@ late SettingProvider settingProvider;
|
||||
|
||||
late KeyProvider keyProvider;
|
||||
|
||||
late AppProvider appProvider;
|
||||
|
||||
late Map<String, WidgetBuilder> routes;
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
keyProvider = KeyProvider();
|
||||
appProvider = AppProvider();
|
||||
|
||||
var dataUtilTask = DataUtil.getInstance();
|
||||
var keyTask = keyProvider.init();
|
||||
@@ -44,7 +48,8 @@ Future<void> main() async {
|
||||
var dataFutureResultList = await Future.wait([dataUtilTask, keyTask, dbTask]);
|
||||
|
||||
var settingTask = SettingProvider.getInstance();
|
||||
var futureResultList = await Future.wait([settingTask]);
|
||||
var appTask = appProvider.reload();
|
||||
var futureResultList = await Future.wait([settingTask, appTask]);
|
||||
settingProvider = futureResultList[0] as SettingProvider;
|
||||
webProvider = WebProvider();
|
||||
|
||||
|
||||
101
lib/provider/app_provider.dart
Normal file
101
lib/provider/app_provider.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nostr_sdk/utils/string_util.dart';
|
||||
import 'package:nowser/const/connect_type.dart';
|
||||
import 'package:nowser/data/app_db.dart';
|
||||
|
||||
import '../const/auth_result.dart';
|
||||
import '../data/app.dart';
|
||||
|
||||
class AppProvider extends ChangeNotifier {
|
||||
List<App> _list = [];
|
||||
|
||||
Map<String, Map<String, int>> appPermissions = {};
|
||||
|
||||
Future<void> reload() async {
|
||||
appPermissions = {};
|
||||
var allApp = await AppDB.all();
|
||||
_list = allApp;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> add(App app) async {
|
||||
if (await AppDB.insert(app) > 0) {
|
||||
_list.add(app);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
String getAppCode(int appType, String code) {
|
||||
return "${appType}_$code";
|
||||
}
|
||||
|
||||
int checkPermission(int appType, String code, int authType,
|
||||
{int? eventKind}) {
|
||||
var app = getApp(appType, code);
|
||||
if (app != null) {
|
||||
if (app.connectType == ConnectType.FULLY_TRUST) {
|
||||
return AuthResult.OK;
|
||||
} else if (app.connectType == ConnectType.ALWAY_REJECT) {
|
||||
return AuthResult.REJECT;
|
||||
} else {
|
||||
var appCode = getAppCode(appType, code);
|
||||
var permissionsMap = appPermissions[appCode];
|
||||
if (permissionsMap == null) {
|
||||
permissionsMap = _getPermissionMap(app);
|
||||
appPermissions[appCode] = permissionsMap;
|
||||
}
|
||||
|
||||
var key = "$authType";
|
||||
if (eventKind != null) {
|
||||
key = "$key-$eventKind";
|
||||
}
|
||||
|
||||
var value = permissionsMap[key];
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return AuthResult.ASK;
|
||||
}
|
||||
|
||||
Map<String, int> _getPermissionMap(App app) {
|
||||
Map<String, int> m = {};
|
||||
_putPermissionMapValue(m, app.alwaysAllow, 1);
|
||||
_putPermissionMapValue(m, app.alwaysReject, -1);
|
||||
return m;
|
||||
}
|
||||
|
||||
void _putPermissionMapValue(
|
||||
Map<String, int> m, String? permissionText, int value) {
|
||||
if (StringUtil.isNotBlank(permissionText)) {
|
||||
var permissionStrs = permissionText!.split(";");
|
||||
for (var permissionStr in permissionStrs) {
|
||||
var strs = permissionStr.split("-");
|
||||
|
||||
var kindStr = strs[0];
|
||||
if (strs.length == 1) {
|
||||
m[kindStr] = value;
|
||||
} else if (strs.length > 1) {
|
||||
var eventKindsStr = strs[1];
|
||||
var eventKindStrs = eventKindsStr.split(",");
|
||||
for (var eventKindStr in eventKindStrs) {
|
||||
var key = "$kindStr-$eventKindStr";
|
||||
m[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
App? getApp(int appType, String code) {
|
||||
for (var app in _list) {
|
||||
if (app.appType == appType && app.code == code) {
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:nostr_sdk/client_utils/keys.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';
|
||||
|
||||
class KeyProvider extends ChangeNotifier {
|
||||
@@ -104,4 +106,13 @@ class KeyProvider extends ChangeNotifier {
|
||||
bool exist(String privateKey) {
|
||||
return keys.contains(privateKey);
|
||||
}
|
||||
|
||||
NostrSigner? getSigner(String pubkey) {
|
||||
var key = keysMap[pubkey];
|
||||
if (StringUtil.isNotBlank(key)) {
|
||||
return LocalNostrSigner(key!);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
83
lib/util/permission_check_mixin.dart
Normal file
83
lib/util/permission_check_mixin.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nostr_sdk/signer/nostr_signer.dart';
|
||||
import 'package:nowser/component/auth_dialog/auth_app_connect_dialog.dart';
|
||||
import 'package:nowser/component/auth_dialog/auth_dialog.dart';
|
||||
import 'package:nowser/const/auth_result.dart';
|
||||
import 'package:nowser/data/auth_log.dart';
|
||||
import 'package:nowser/main.dart';
|
||||
|
||||
import '../const/connect_type.dart';
|
||||
import '../data/app.dart';
|
||||
|
||||
mixin PermissionCheckMixin {
|
||||
Future<void> checkPermission(BuildContext context, int appType, String code,
|
||||
int authType, Function reject, Function(App, NostrSigner) confirm,
|
||||
{int? eventKind, String? authDetail}) async {
|
||||
var app = appProvider.getApp(appType, code);
|
||||
if (app == null) {
|
||||
// app is null, app connect
|
||||
var newApp = await getApp(appType, code);
|
||||
await AuthAppConnectDialog.show(context, newApp);
|
||||
// reload from provider
|
||||
app = appProvider.getApp(appType, code);
|
||||
}
|
||||
|
||||
if (app == null) {
|
||||
// not allow connect
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
var signer = getSigner(app.pubkey!);
|
||||
if (signer == null) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
if (app.connectType == ConnectType.FULLY_TRUST) {
|
||||
saveAuthLog(app, authType, eventKind, authDetail, AuthResult.OK);
|
||||
confirm(app, signer);
|
||||
return;
|
||||
} else if (app.connectType == ConnectType.REASONABLE) {
|
||||
var permissionCheckResult = appProvider
|
||||
.checkPermission(appType, code, authType, eventKind: eventKind);
|
||||
print("permissionCheckResult $permissionCheckResult");
|
||||
if (permissionCheckResult == AuthResult.OK) {
|
||||
saveAuthLog(app, authType, eventKind, authDetail, AuthResult.OK);
|
||||
confirm(app, signer);
|
||||
return;
|
||||
} else if (permissionCheckResult == AuthResult.REJECT) {
|
||||
saveAuthLog(app, authType, eventKind, authDetail, AuthResult.REJECT);
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
var authResult = await AuthDialog.show(context, app, authType,
|
||||
eventKind: eventKind, authDetail: authDetail);
|
||||
if (authResult == AuthResult.OK) {
|
||||
saveAuthLog(app, authType, eventKind, authDetail, AuthResult.OK);
|
||||
confirm(app, signer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
saveAuthLog(app, authType, eventKind, authDetail, AuthResult.REJECT);
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
void saveAuthLog(App app, int authType, int? eventKind, String? authDetail,
|
||||
int authResult) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// this method should override
|
||||
Future<App> getApp(int appType, String code) async {
|
||||
// TODO name, image
|
||||
return App(appType: appType, code: code);
|
||||
}
|
||||
|
||||
NostrSigner? getSigner(String pubkey) {
|
||||
return keyProvider.getSigner(pubkey);
|
||||
}
|
||||
}
|
||||
@@ -33,9 +33,9 @@ dependencies:
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
cupertino_icons: ^1.0.6
|
||||
|
||||
nostr_sdk:
|
||||
path: packages/nostr_sdk
|
||||
|
||||
receive_intent: ^0.2.5
|
||||
provider: ^6.1.2
|
||||
flutter_inappwebview: ^6.0.0
|
||||
|
||||
Reference in New Issue
Block a user