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: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/component/auth_dialog/auth_dialog_base_componnet.dart';
|
||||||
import 'package:nowser/const/connect_type.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 '../../const/base.dart';
|
||||||
|
import '../../data/app.dart';
|
||||||
|
|
||||||
class AuthAppConnectDialog extends StatefulWidget {
|
class AuthAppConnectDialog extends StatefulWidget {
|
||||||
static void show(BuildContext context) {
|
App app;
|
||||||
showDialog(
|
|
||||||
|
AuthAppConnectDialog({required this.app});
|
||||||
|
|
||||||
|
static Future<App?> show(BuildContext context, App app) {
|
||||||
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AuthAppConnectDialog();
|
return AuthAppConnectDialog(
|
||||||
|
app: app,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -65,7 +77,12 @@ class _AuthAppConnectDialog extends State<AuthAppConnectDialog> {
|
|||||||
children: list,
|
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) {
|
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:flutter/material.dart';
|
||||||
|
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/base.dart';
|
import 'package:nowser/const/base.dart';
|
||||||
|
import 'package:nowser/data/app.dart';
|
||||||
|
|
||||||
import '../app/app_type_component.dart';
|
import '../app/app_type_component.dart';
|
||||||
|
|
||||||
class AuthAppInfoComponent extends StatefulWidget {
|
class AuthAppInfoComponent extends StatefulWidget {
|
||||||
|
App app;
|
||||||
|
|
||||||
|
AuthAppInfoComponent({required this.app});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() {
|
State<StatefulWidget> createState() {
|
||||||
return _AuthAppInfoComponent();
|
return _AuthAppInfoComponent();
|
||||||
@@ -16,6 +22,26 @@ class _AuthAppInfoComponent extends State<AuthAppInfoComponent> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var themeData = Theme.of(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(
|
return Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
@@ -24,12 +50,13 @@ class _AuthAppInfoComponent extends State<AuthAppInfoComponent> {
|
|||||||
height: 64,
|
height: 64,
|
||||||
child: Card(
|
child: Card(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.all(Base.BASE_PADDING_HALF),
|
padding: const EdgeInsets.all(Base.BASE_PADDING_HALF),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
margin: EdgeInsets.only(right: Base.BASE_PADDING_HALF),
|
margin:
|
||||||
|
const EdgeInsets.only(right: Base.BASE_PADDING_HALF),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.image,
|
Icons.image,
|
||||||
size: 46,
|
size: 46,
|
||||||
@@ -38,13 +65,7 @@ class _AuthAppInfoComponent extends State<AuthAppInfoComponent> {
|
|||||||
Column(
|
Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: rightList,
|
||||||
Text(
|
|
||||||
"APP NAME",
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
Text("This is App info des"),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,14 +1,39 @@
|
|||||||
import 'package:flutter/material.dart';
|
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/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';
|
import '../../const/base.dart';
|
||||||
|
|
||||||
class AuthDialog extends StatefulWidget {
|
class AuthDialog extends StatefulWidget {
|
||||||
static void show(BuildContext context) {
|
App app;
|
||||||
showDialog(
|
|
||||||
|
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,
|
context: context,
|
||||||
builder: (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;
|
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<Widget> list = [];
|
||||||
list.add(Container(
|
list.add(Container(
|
||||||
margin: baseMargin,
|
margin: baseMargin,
|
||||||
child: Text(
|
child: Text(
|
||||||
"Allow web.nostrmo.com to sign a authenticate event",
|
authDes,
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
List<Widget> detailList = [];
|
||||||
|
if (StringUtil.isNotBlank(widget.authDetail)) {
|
||||||
var showDetailWidget = GestureDetector(
|
var showDetailWidget = GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -53,8 +85,6 @@ class _AuthDialog extends State<AuthDialog> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
List<Widget> detailList = [];
|
|
||||||
if (showDetail) {
|
if (showDetail) {
|
||||||
detailList.add(Container(
|
detailList.add(Container(
|
||||||
height: 210,
|
height: 210,
|
||||||
@@ -65,8 +95,7 @@ class _AuthDialog extends State<AuthDialog> {
|
|||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Text(
|
child: Text(widget.authDetail!),
|
||||||
"GoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGoodGood"),
|
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
} else {}
|
} else {}
|
||||||
@@ -74,6 +103,7 @@ class _AuthDialog extends State<AuthDialog> {
|
|||||||
margin: baseMargin,
|
margin: baseMargin,
|
||||||
child: showDetailWidget,
|
child: showDetailWidget,
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
list.add(Container(
|
list.add(Container(
|
||||||
height: 250,
|
height: 250,
|
||||||
@@ -90,6 +120,16 @@ class _AuthDialog extends State<AuthDialog> {
|
|||||||
children: list,
|
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: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/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:nowser/util/router_util.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../logo_component.dart';
|
import '../logo_component.dart';
|
||||||
import 'auth_app_info_component.dart';
|
import 'auth_app_info_component.dart';
|
||||||
|
|
||||||
class AuthDialogBaseComponnet extends StatefulWidget {
|
class AuthDialogBaseComponnet extends StatefulWidget {
|
||||||
|
App app;
|
||||||
|
|
||||||
String title;
|
String title;
|
||||||
|
|
||||||
Widget child;
|
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
|
@override
|
||||||
State<StatefulWidget> createState() {
|
State<StatefulWidget> createState() {
|
||||||
@@ -30,11 +47,43 @@ class _AuthDialog extends State<AuthDialogBaseComponnet> {
|
|||||||
|
|
||||||
List<Widget> list = [];
|
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(
|
var topWidget = Container(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
margin: EdgeInsets.only(
|
margin: const EdgeInsets.only(
|
||||||
left: Base.BASE_PADDING,
|
left: Base.BASE_PADDING,
|
||||||
right: Base.BASE_PADDING,
|
right: Base.BASE_PADDING,
|
||||||
top: Base.BASE_PADDING_HALF,
|
top: Base.BASE_PADDING_HALF,
|
||||||
@@ -46,16 +95,7 @@ class _AuthDialog extends State<AuthDialogBaseComponnet> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
margin: EdgeInsets.only(right: Base.BASE_PADDING_HALF),
|
margin: EdgeInsets.only(right: Base.BASE_PADDING_HALF),
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: DropdownButton<String>(
|
child: keyWidget,
|
||||||
items: [
|
|
||||||
DropdownMenuItem(
|
|
||||||
child: Text("npubxxxxxx"),
|
|
||||||
value: "npubxxxxxx",
|
|
||||||
)
|
|
||||||
],
|
|
||||||
onChanged: (Object? value) {},
|
|
||||||
value: "npubxxxxxx",
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -76,7 +116,9 @@ class _AuthDialog extends State<AuthDialogBaseComponnet> {
|
|||||||
|
|
||||||
list.add(Container(
|
list.add(Container(
|
||||||
margin: baseMargin,
|
margin: baseMargin,
|
||||||
child: AuthAppInfoComponent(),
|
child: AuthAppInfoComponent(
|
||||||
|
app: widget.app,
|
||||||
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
list.add(Container(
|
list.add(Container(
|
||||||
@@ -112,7 +154,12 @@ class _AuthDialog extends State<AuthDialogBaseComponnet> {
|
|||||||
margin: EdgeInsets.only(
|
margin: EdgeInsets.only(
|
||||||
left: Base.BASE_PADDING_HALF,
|
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);
|
RouterUtil.router(context, RouterPath.WEB_TABS);
|
||||||
}),
|
}),
|
||||||
wrapBottomBtn(const Icon(Icons.space_dashboard), onTap: () {
|
wrapBottomBtn(const Icon(Icons.space_dashboard), onTap: () {
|
||||||
AuthDialog.show(context);
|
// AuthDialog.show(context);
|
||||||
// AuthAppConnectDialog.show(context);
|
// AuthAppConnectDialog.show(context);
|
||||||
}),
|
}),
|
||||||
wrapBottomBtn(const Icon(Icons.segment), onTap: () {
|
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/material.dart';
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.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/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 {
|
class WebViewComponent extends StatefulWidget {
|
||||||
WebInfo webInfo;
|
WebInfo webInfo;
|
||||||
@@ -21,23 +34,479 @@ class WebViewComponent extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _WebViewComponent extends State<WebViewComponent> {
|
class _WebViewComponent extends State<WebViewComponent>
|
||||||
|
with PermissionCheckMixin {
|
||||||
InAppWebViewController? webViewController;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
child: InAppWebView(
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
InAppWebView(
|
||||||
initialUrlRequest: URLRequest(url: WebUri(widget.webInfo.url)),
|
initialUrlRequest: URLRequest(url: WebUri(widget.webInfo.url)),
|
||||||
|
initialUserScripts: UnmodifiableListView<UserScript>([]),
|
||||||
|
initialSettings: settings,
|
||||||
|
contextMenu: contextMenu,
|
||||||
|
pullToRefreshController: pullToRefreshController,
|
||||||
onWebViewCreated: (controller) async {
|
onWebViewCreated: (controller) async {
|
||||||
webViewController = controller;
|
webViewController = controller;
|
||||||
// initJSHandle(controller);
|
initJSHandle(controller);
|
||||||
widget.onWebViewCreated(widget.webInfo, controller);
|
widget.onWebViewCreated(widget.webInfo, controller);
|
||||||
},
|
},
|
||||||
onTitleChanged: (controller, title) {
|
onTitleChanged: (controller, title) {
|
||||||
widget.onTitleChanged(widget.webInfo, 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 {
|
class AuthResult {
|
||||||
static const int OK = 1;
|
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 {
|
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? image;
|
||||||
|
|
||||||
String? permissions;
|
int? connectType;
|
||||||
|
|
||||||
App({
|
String? alwaysAllow;
|
||||||
required this.id,
|
|
||||||
required this.pubkey,
|
String? alwaysReject;
|
||||||
required this.appType,
|
|
||||||
required this.code,
|
int? createdAt;
|
||||||
required this.name,
|
|
||||||
|
int? updatedAt;
|
||||||
|
|
||||||
|
App(
|
||||||
|
{this.id,
|
||||||
|
this.pubkey,
|
||||||
|
this.appType,
|
||||||
|
this.code,
|
||||||
|
this.name,
|
||||||
this.image,
|
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 {
|
class AuthLog {
|
||||||
int id;
|
int? id;
|
||||||
|
|
||||||
int appId;
|
int? appId;
|
||||||
|
|
||||||
int authType;
|
int? authType;
|
||||||
|
|
||||||
int? eventKind;
|
int? eventKind;
|
||||||
|
|
||||||
@@ -11,18 +11,41 @@ class AuthLog {
|
|||||||
|
|
||||||
String? content;
|
String? content;
|
||||||
|
|
||||||
int authResult;
|
int? authResult;
|
||||||
|
|
||||||
int createdAt;
|
int? createdAt;
|
||||||
|
|
||||||
AuthLog({
|
AuthLog(
|
||||||
required this.id,
|
{this.id,
|
||||||
required this.appId,
|
this.appId,
|
||||||
required this.authType,
|
this.authType,
|
||||||
this.eventKind,
|
this.eventKind,
|
||||||
this.title,
|
this.title,
|
||||||
this.content,
|
this.content,
|
||||||
required this.authResult,
|
this.authResult,
|
||||||
required this.createdAt,
|
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 {
|
static Future<void> _onCreate(Database db, int version) async {
|
||||||
// init db
|
// init db
|
||||||
db.execute(
|
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(
|
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);");
|
"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 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 {
|
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:google_fonts/google_fonts.dart';
|
||||||
import 'package:nostr_sdk/utils/string_util.dart';
|
import 'package:nostr_sdk/utils/string_util.dart';
|
||||||
import 'package:nowser/data/db.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/key_provider.dart';
|
||||||
import 'package:nowser/provider/web_provider.dart';
|
import 'package:nowser/provider/web_provider.dart';
|
||||||
import 'package:nowser/router/index/index_router.dart';
|
import 'package:nowser/router/index/index_router.dart';
|
||||||
@@ -31,12 +32,15 @@ late SettingProvider settingProvider;
|
|||||||
|
|
||||||
late KeyProvider keyProvider;
|
late KeyProvider keyProvider;
|
||||||
|
|
||||||
|
late AppProvider appProvider;
|
||||||
|
|
||||||
late Map<String, WidgetBuilder> routes;
|
late Map<String, WidgetBuilder> routes;
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
keyProvider = KeyProvider();
|
keyProvider = KeyProvider();
|
||||||
|
appProvider = AppProvider();
|
||||||
|
|
||||||
var dataUtilTask = DataUtil.getInstance();
|
var dataUtilTask = DataUtil.getInstance();
|
||||||
var keyTask = keyProvider.init();
|
var keyTask = keyProvider.init();
|
||||||
@@ -44,7 +48,8 @@ Future<void> main() async {
|
|||||||
var dataFutureResultList = await Future.wait([dataUtilTask, keyTask, dbTask]);
|
var dataFutureResultList = await Future.wait([dataUtilTask, keyTask, dbTask]);
|
||||||
|
|
||||||
var settingTask = SettingProvider.getInstance();
|
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;
|
settingProvider = futureResultList[0] as SettingProvider;
|
||||||
webProvider = WebProvider();
|
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/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';
|
||||||
|
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:nostr_sdk/utils/string_util.dart';
|
||||||
|
|
||||||
class KeyProvider extends ChangeNotifier {
|
class KeyProvider extends ChangeNotifier {
|
||||||
@@ -104,4 +106,13 @@ class KeyProvider extends ChangeNotifier {
|
|||||||
bool exist(String privateKey) {
|
bool exist(String privateKey) {
|
||||||
return keys.contains(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:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cupertino_icons: ^1.0.6
|
cupertino_icons: ^1.0.6
|
||||||
|
|
||||||
nostr_sdk:
|
nostr_sdk:
|
||||||
path: packages/nostr_sdk
|
path: packages/nostr_sdk
|
||||||
|
|
||||||
receive_intent: ^0.2.5
|
receive_intent: ^0.2.5
|
||||||
provider: ^6.1.2
|
provider: ^6.1.2
|
||||||
flutter_inappwebview: ^6.0.0
|
flutter_inappwebview: ^6.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user