nowser add contentresolver support

This commit is contained in:
DASHU
2024-10-09 02:05:05 +08:00
parent a0c0ec78b8
commit 6660cb2805
13 changed files with 310 additions and 27 deletions

View File

@@ -30,11 +30,15 @@ if (flutterVersionName == null) {
android {
namespace = "com.github.haorendashu.nowser"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
ndkVersion = "26.1.10909125"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
defaultConfig {

View File

@@ -39,11 +39,17 @@
<data android:scheme="nostrsigner" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<provider
android:name="com.github.haorendashu.nowser.NowserSignerProvider"
android:authorities="com.github.haorendashu.nowser.SIGN_EVENT;com.github.haorendashu.nowser.NIP04_ENCRYPT;com.github.haorendashu.nowser.NIP04_DECRYPT;com.github.haorendashu.nowser.NIP44_ENCRYPT;com.github.haorendashu.nowser.NIP44_DECRYPT;com.github.haorendashu.nowser.GET_PUBLIC_KEY"
android:exported="true" />
</application>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

View File

@@ -0,0 +1,20 @@
package com.github.haorendashu.nowser;
import com.nt4f04und.android_content_provider.AndroidContentProvider;
import org.jetbrains.annotations.NotNull;
public class NowserSignerProvider extends AndroidContentProvider {
@NotNull
@Override
public String getAuthority() {
return "com.github.haorendashu.nowser.SIGN_EVENT;com.github.haorendashu.nowser.NIP04_ENCRYPT;com.github.haorendashu.nowser.NIP04_DECRYPT;com.github.haorendashu.nowser.NIP44_ENCRYPT;com.github.haorendashu.nowser.NIP44_DECRYPT;com.github.haorendashu.nowser.GET_PUBLIC_KEY";
}
@NotNull
@Override
public String getEntrypointName() {
return "nowserSignerProviderEntrypoint";
}
}

View File

@@ -1,5 +1,13 @@
package com.github.haorendashu.nowser
import android.content.Context
import com.nt4f04und.android_content_provider.AndroidContentProvider
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
class MainActivity: FlutterActivity()
class MainActivity: FlutterActivity() {
override fun provideFlutterEngine(context: Context): FlutterEngine? {
return AndroidContentProvider.getFlutterEngineGroup(this)
.createAndRunDefaultEngine(this)
}
}

View File

@@ -6,6 +6,17 @@ allprojects {
}
rootProject.buildDir = "../build"
subprojects {
afterEvaluate { project ->
if (project.hasProperty('android')) {
project.android {
if (namespace == null) {
namespace project.group
}
}
}
}
}
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}

View File

@@ -1,3 +1,7 @@
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
kotlin.jvm.target.validation.mode = IGNORE

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip

View File

@@ -18,8 +18,8 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
id "com.android.application" version '8.1.1' apply false
id "org.jetbrains.kotlin.android" version "1.8.10" apply false
}
include ":app"

View File

@@ -34,6 +34,7 @@ import 'const/base.dart';
import 'const/colors.dart';
import 'const/router_path.dart';
import 'generated/l10n.dart';
import 'provider/android_signer_content_resolver_provider.dart';
import 'provider/data_util.dart';
import 'provider/remote_signing_provider.dart';
import 'provider/setting_provider.dart';
@@ -78,6 +79,12 @@ Future<void> main() async {
databaseFactory = databaseFactoryFfi;
}
await doInit();
runApp(MyApp());
}
Future<void> doInit() async {
keyProvider = KeyProvider();
appProvider = AppProvider();
@@ -92,8 +99,6 @@ Future<void> main() async {
settingProvider = futureResultList[0] as SettingProvider;
webProvider = WebProvider();
remoteSigningProvider = RemoteSigningProvider();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@@ -330,3 +335,11 @@ Color _getMainColor() {
}
return color500;
}
@pragma('vm:entry-point')
Future<void> nowserSignerProviderEntrypoint() async {
// if we call content resolver this should init first, to receive request.
// so, doInit() method move to query method.
AndroidSignerContentResolverProvider(
'com.github.haorendashu.nowser.SIGN_EVENT;com.github.haorendashu.nowser.NIP04_ENCRYPT;com.github.haorendashu.nowser.NIP04_DECRYPT;com.github.haorendashu.nowser.NIP44_ENCRYPT;com.github.haorendashu.nowser.NIP44_DECRYPT;com.github.haorendashu.nowser.GET_PUBLIC_KEY');
}

View File

@@ -0,0 +1,179 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:android_content_provider/android_content_provider.dart';
import 'package:nostr_sdk/event.dart';
import 'package:nostr_sdk/nip19/nip19.dart';
import 'package:nostr_sdk/signer/nostr_signer.dart';
import 'package:nostr_sdk/utils/string_util.dart';
import 'package:nowser/const/app_type.dart';
import 'package:nowser/provider/permission_check_mixin.dart';
import '../const/auth_result.dart';
import '../const/auth_type.dart';
import '../data/app.dart';
import '../main.dart';
class AndroidSignerContentResolverProvider extends AndroidContentProvider
with PermissionCheckMixin {
AndroidSignerContentResolverProvider(super.authority);
@override
Future<int> delete(
String uri, String? selection, List<String>? selectionArgs) async {
return 0;
}
@override
Future<String?> getType(String uri) async {
return null;
}
@override
Future<String?> insert(String uri, ContentValues? values) async {
return null;
}
bool inited = false;
@override
Future<CursorData?> query(String uri, List<String>? projection,
String? selection, List<String>? selectionArgs, String? sortOrder) async {
if (!inited) {
await doInit();
inited = true;
}
var authTypeStr =
uri.replaceAll("content://com.github.haorendashu.nowser.", "");
String authDetail = "";
String? pubkey;
String? currentUser;
if (projection != null && projection.isNotEmpty) {
authDetail = projection.first;
if (projection.length > 1) {
pubkey = projection[1];
}
if (projection.length > 2) {
currentUser = projection[2];
}
}
var authType = AuthType.GET_PUBLIC_KEY;
if (authTypeStr == "GET_PUBLIC_KEY") {
authType = AuthType.GET_PUBLIC_KEY;
} else if (authTypeStr == "SIGN_EVENT") {
authType = AuthType.SIGN_EVENT;
} else if (authTypeStr == "GET_RELAYS") {
authType = AuthType.GET_RELAYS;
} else if (authTypeStr == "NIP04_ENCRYPT") {
authType = AuthType.NIP04_ENCRYPT;
} else if (authTypeStr == "NIP04_DECRYPT") {
authType = AuthType.NIP04_DECRYPT;
} else if (authTypeStr == "NIP44_ENCRYPT") {
authType = AuthType.NIP44_ENCRYPT;
} else if (authTypeStr == "NIP44_DECRYPT") {
authType = AuthType.NIP44_DECRYPT;
}
int appType = AppType.ANDROID_APP;
String code = "com.github.haorendashu.nostrmo";
int? eventKind;
dynamic eventObj;
if (authType == AuthType.SIGN_EVENT) {
eventObj = jsonDecode(authDetail);
if (eventObj != null) {
eventKind = eventObj["kind"];
}
}
App? app;
NostrSigner? signer;
var complete = Completer();
rejectFunc(_app) {
saveAuthLog(_app, authType, eventKind, authDetail, AuthResult.REJECT);
complete.complete();
}
confirmFunc(_app, _signer) {
app = _app;
signer = _signer;
complete.complete();
}
checkPermission(null, appType, code, authType, rejectFunc, confirmFunc,
eventKind: eventKind, authDetail: authDetail);
await complete.future;
if (signer == null || app == null) {
return null;
}
MatrixCursorData? data;
if (authType == AuthType.GET_PUBLIC_KEY) {
// TODO should handle permissions
// var permissions = extra["permissions"];
var pubkey = await signer!.getPublicKey();
data =
MatrixCursorData(columnNames: ["signature"], notificationUris: [uri]);
data.addRow([Nip19.encodePubKey(pubkey!)]);
} else if (authType == AuthType.SIGN_EVENT) {
var tags = eventObj["tags"];
Event? event = Event(
app!.pubkey!, eventObj["kind"], tags ?? [], eventObj["content"],
createdAt: eventObj["created_at"]);
log(jsonEncode(event.toJson()));
event = await signer!.signEvent(event);
if (event == null) {
log("sign event fail");
return null;
}
log("sig ${event.sig}");
data = MatrixCursorData(
columnNames: ["signature", "event"], notificationUris: [uri]);
data.addRow([event.sig, jsonEncode(event.toJson())]);
} else if (authType == AuthType.GET_RELAYS) {
// TODO
} else if (authType == AuthType.NIP04_ENCRYPT) {
var result = await signer!.encrypt(pubkey, authDetail);
if (StringUtil.isNotBlank(result)) {
data = MatrixCursorData(
columnNames: ["signature"], notificationUris: [uri]);
data.addRow([result]);
}
} else if (authType == AuthType.NIP04_DECRYPT) {
var result = await signer!.decrypt(pubkey, authDetail);
if (StringUtil.isNotBlank(result)) {
data = MatrixCursorData(
columnNames: ["signature"], notificationUris: [uri]);
data.addRow([result]);
}
} else if (authType == AuthType.NIP44_ENCRYPT) {
var result = await signer!.nip44Encrypt(pubkey, authDetail);
if (StringUtil.isNotBlank(result)) {
data = MatrixCursorData(
columnNames: ["signature"], notificationUris: [uri]);
data.addRow([result]);
}
} else if (authType == AuthType.NIP44_DECRYPT) {
var result = await signer!.nip44Decrypt(pubkey, authDetail);
if (StringUtil.isNotBlank(result)) {
data = MatrixCursorData(
columnNames: ["signature"], notificationUris: [uri]);
data.addRow([result]);
}
}
return data;
}
@override
Future<int> update(String uri, ContentValues? values, String? selection,
List<String>? selectionArgs) async {
return 0;
}
}

View File

@@ -12,12 +12,14 @@ import '../const/connect_type.dart';
import '../data/app.dart';
mixin PermissionCheckMixin {
Future<void> checkPermission(BuildContext context, int appType, String code,
Future<void> checkPermission(BuildContext? context, int appType, String code,
int authType, Function(App?) reject, Function(App, NostrSigner) confirm,
{int? eventKind, String? authDetail}) async {
if (keyProvider.keys.isEmpty) {
// should add a key first
await UserLoginDialog.show(context);
if (context != null) {
await UserLoginDialog.show(context);
}
if (keyProvider.keys.isEmpty) {
return;
}
@@ -27,7 +29,9 @@ mixin PermissionCheckMixin {
if (app == null) {
// app is null, app connect
var newApp = await getApp(appType, code);
await AuthAppConnectDialog.show(context, newApp);
if (context != null) {
await AuthAppConnectDialog.show(context, newApp);
}
// reload from provider
app = appProvider.getApp(appType, code);
}
@@ -63,12 +67,14 @@ mixin PermissionCheckMixin {
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;
if (context != null) {
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;
}
}
}

View File

@@ -12,7 +12,7 @@ import flutter_secure_storage_macos
import path_provider_foundation
import screen_retriever
import shared_preferences_foundation
import sqflite
import sqflite_darwin
import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {

View File

@@ -1,6 +1,14 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
android_content_provider:
dependency: transitive
description:
name: android_content_provider
sha256: c39dae7c8a520e11d210690d1340bbc86c871271b3f501a67724baff2f7da167
url: "https://pub.dev"
source: hosted
version: "0.4.2"
archive:
dependency: transitive
description:
@@ -853,34 +861,58 @@ packages:
dependency: transitive
description:
name: sqflite
sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d
sha256: "79a297dc3cc137e758c6a4baf83342b039e5a6d2436fcdf3f96a00adaaf2ad62"
url: "https://pub.dev"
source: hosted
version: "2.3.3+1"
version: "2.4.0"
sqflite_android:
dependency: transitive
description:
name: sqflite_android
sha256: "11821baf1d1bd3d71150d9b0d4c2e22cf680858c71add2113c919ca55cf9dc56"
url: "https://pub.dev"
source: hosted
version: "2.4.0-1"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4"
sha256: "4468b24876d673418a7b7147e5a08a715b4998a7ae69227acafaab762e0e5490"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
version: "2.5.4+5"
sqflite_common_ffi:
dependency: transitive
description:
name: sqflite_common_ffi
sha256: "4d6137c29e930d6e4a8ff373989dd9de7bac12e3bc87bce950f6e844e8ad3bb5"
sha256: a6057d4c87e9260ba1ec436ebac24760a110589b9c0a859e128842eb69a7ef04
url: "https://pub.dev"
source: hosted
version: "2.3.3"
version: "2.3.3+1"
sqflite_common_ffi_web:
dependency: transitive
description:
name: sqflite_common_ffi_web
sha256: e9d1cb35a5ff7c43072968ed734e0a1a859564fd2b2c8654e0c6244a57dc82a8
sha256: f540ad769e5fd31aabe77bfa6e774fdd36145a83e33cdc39239f310c5f8559c3
url: "https://pub.dev"
source: hosted
version: "0.4.4"
version: "0.4.5+3"
sqflite_darwin:
dependency: transitive
description:
name: sqflite_darwin
sha256: "5e325c925cbd63f27e0e538aed018a40852325e590b5d83165181e492d272f9b"
url: "https://pub.dev"
source: hosted
version: "2.4.1-0"
sqflite_platform_interface:
dependency: transitive
description:
name: sqflite_platform_interface
sha256: b62ab81e1284341783222aefbbb44f984ebf4663d672ae10408c9a8ddab4bfb6
url: "https://pub.dev"
source: hosted
version: "2.4.0-0"
sqlite3:
dependency: transitive
description: