mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-15 04:43:41 +00:00
extension host integration
This commit is contained in:
1 parent
fb38253045
commit
509e5c9f5e
18 files changed
+506
-107
No files matched your search
@@ -16,7 +16,6 @@ import '../../../../api/model/extension.dart';
|
||||
import '../../../../api/model/install_extension.dart';
|
||||
import '../../../../api/model/switch_extension.dart';
|
||||
import '../../../../api/model/update_extension_settings.dart';
|
||||
import '../../../../util/mac_secure_util.dart';
|
||||
import '../../../../util/message.dart';
|
||||
import '../../../../util/util.dart';
|
||||
import '../../../views/icon_button_loading.dart';
|
||||
@@ -96,7 +95,6 @@ class ExtensionView extends GetView<ExtensionController> {
|
||||
var dir =
|
||||
await FilePicker.platform.getDirectoryPath();
|
||||
if (dir != null) {
|
||||
MacSecureUtil.saveBookmark(dir);
|
||||
try {
|
||||
await installExtension(
|
||||
InstallExtension(devMode: true, url: dir));
|
||||
|
||||
@@ -9,7 +9,6 @@ import 'package:path_provider/path_provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:toggle_switch/toggle_switch.dart';
|
||||
|
||||
import '../../util/mac_secure_util.dart';
|
||||
import '../../util/message.dart';
|
||||
import '../../util/util.dart';
|
||||
|
||||
@@ -42,7 +41,6 @@ class _DirectorySelectorState extends State<DirectorySelector> {
|
||||
var dir = await FilePicker.platform.getDirectoryPath();
|
||||
if (dir != null) {
|
||||
widget.controller.text = dir;
|
||||
MacSecureUtil.saveBookmark(dir);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import 'app/modules/app/views/app_view.dart';
|
||||
import 'core/libgopeed_boot.dart';
|
||||
import 'database/database.dart';
|
||||
import 'i18n/message.dart';
|
||||
import 'util/browser_extension_host/browser_extension_host.dart';
|
||||
import 'util/locale_manager.dart';
|
||||
import 'util/log_util.dart';
|
||||
import 'util/mac_secure_util.dart';
|
||||
import 'util/package_info.dart';
|
||||
import 'util/scheme_register/scheme_register.dart';
|
||||
import 'util/util.dart';
|
||||
@@ -91,9 +91,16 @@ Future<void> init(Args args) async {
|
||||
logger.e("register scheme fail", e);
|
||||
}
|
||||
|
||||
for (final browser in Browser.values) {
|
||||
try {
|
||||
await installManifest(browser);
|
||||
} catch (e) {
|
||||
logger.e("browser [${browser.name}] extension host integration fail", e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await controller.loadDownloaderConfig();
|
||||
MacSecureUtil.loadBookmark();
|
||||
} catch (e) {
|
||||
logger.e("load config fail", e);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import 'browser_extension_host_stub.dart'
|
||||
if (dart.library.io) 'entry/browser_extension_host_native.dart';
|
||||
|
||||
enum Browser { chrome, edge, firefox }
|
||||
|
||||
/// Check if specified browser is installed
|
||||
Future<bool> checkBrowserInstalled(Browser browser) =>
|
||||
doCheckBrowserInstalled(browser);
|
||||
|
||||
/// Check if browser extension manifest is properly installed
|
||||
Future<bool> checkManifestInstalled(Browser browser) =>
|
||||
doCheckManifestInstalled(browser);
|
||||
|
||||
/// Install browser extension manifest
|
||||
Future<void> installManifest(Browser browser) => doInstallManifest(browser);
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'browser_extension_host.dart';
|
||||
|
||||
Future<bool> doCheckBrowserInstalled(Browser browser) =>
|
||||
throw UnimplementedError();
|
||||
Future<bool> doCheckManifestInstalled(Browser browser) =>
|
||||
throw UnimplementedError();
|
||||
Future<void> doInstallManifest(Browser browser) => throw UnimplementedError();
|
||||
@@ -0,0 +1,275 @@
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:win32_registry/win32_registry.dart';
|
||||
|
||||
import '../../win32.dart';
|
||||
import '../browser_extension_host.dart';
|
||||
|
||||
const _hostName = 'com.gopeed.gopeed';
|
||||
const _chromeExtensionId = 'mijpgljlfcapndmchhjffkpckknofcnd';
|
||||
const _edgeExtensionId = 'dkajnckekendchdleoaenoophcobooce';
|
||||
const _firefoxExtensionId = '{c5d69a8f-2ed0-46a7-afa4-b3a00dc58088}';
|
||||
|
||||
// Windows NativeMessagingHosts registry constants
|
||||
const _chromeNativeHostsKey = r'Software\Google\Chrome\NativeMessagingHosts';
|
||||
const _edgeNativeHostsKey = r'Software\Microsoft\Edge\NativeMessagingHosts';
|
||||
const _firefoxNativeHostsKey = r'Software\Mozilla\NativeMessagingHosts';
|
||||
|
||||
/// Check if specified browser is installed
|
||||
Future<bool> doCheckBrowserInstalled(Browser browser) async {
|
||||
if (Platform.isWindows) {
|
||||
switch (browser) {
|
||||
case Browser.chrome:
|
||||
return await _checkWindowsRegistry(
|
||||
r'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe') ||
|
||||
await _checkWindowsRegistry(
|
||||
r'SOFTWARE\WOW6432Node\Google\Chrome') ||
|
||||
await _checkWindowsExecutable(browser);
|
||||
case Browser.edge:
|
||||
return await _checkWindowsRegistry(
|
||||
r'SOFTWARE\Microsoft\Edge\BLBeacon') ||
|
||||
await _checkWindowsRegistry(
|
||||
r'SOFTWARE\WOW6432Node\Microsoft\Edge\BLBeacon') ||
|
||||
await _checkWindowsExecutable(browser);
|
||||
case Browser.firefox:
|
||||
return await _checkWindowsRegistry(r'SOFTWARE\Mozilla\Firefox') ||
|
||||
await _checkWindowsRegistry(
|
||||
r'SOFTWARE\WOW6432Node\Mozilla\Firefox') ||
|
||||
await _checkWindowsExecutable(browser);
|
||||
}
|
||||
} else {
|
||||
return await _checkUnixExecutable(browser);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if browser extension manifest is properly installed
|
||||
Future<bool> doCheckManifestInstalled(Browser browser) async {
|
||||
if (await checkBrowserInstalled(browser) == false) return false;
|
||||
|
||||
final manifestPath = _getManifestPath(browser);
|
||||
if (manifestPath == null) return false;
|
||||
|
||||
if (Platform.isWindows) {
|
||||
final regKey = _getWindowsRegistryKey(browser);
|
||||
if (!checkRegistry('$regKey\\$_hostName', '', manifestPath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!await File(manifestPath).exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final existingContent = await File(manifestPath).readAsString();
|
||||
final expectedContent = _getManifestContent();
|
||||
return existingContent == expectedContent;
|
||||
}
|
||||
|
||||
/// Install browser extension manifest
|
||||
Future<void> doInstallManifest(Browser browser) async {
|
||||
if (await checkBrowserInstalled(browser) == false) return;
|
||||
if (await checkManifestInstalled(browser)) return;
|
||||
|
||||
final manifestPath = _getManifestPath(browser)!;
|
||||
final manifestContent = _getManifestContent();
|
||||
final manifestDir = path.dirname(manifestPath);
|
||||
await Directory(manifestDir).create(recursive: true);
|
||||
await File(manifestPath).writeAsString(manifestContent);
|
||||
|
||||
if (Platform.isWindows) {
|
||||
final regKey = _getWindowsRegistryKey(browser);
|
||||
upsertRegistry('$regKey\\$_hostName', '', manifestPath);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _checkWindowsExecutable(Browser browser) async {
|
||||
final paths = _getWindowsExecutablePaths(browser);
|
||||
for (var execPath in paths) {
|
||||
if (await File(execPath).exists()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
List<String> _getWindowsExecutablePaths(Browser browser) {
|
||||
final programFiles = Platform.environment['PROGRAMFILES'];
|
||||
final programFilesX86 = Platform.environment['PROGRAMFILES(X86)'];
|
||||
final localAppData = Platform.environment['LOCALAPPDATA'];
|
||||
|
||||
switch (browser) {
|
||||
case Browser.chrome:
|
||||
return [
|
||||
if (programFiles != null)
|
||||
path.join(
|
||||
programFiles, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
||||
if (programFilesX86 != null)
|
||||
path.join(
|
||||
programFilesX86, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
||||
if (localAppData != null)
|
||||
path.join(
|
||||
localAppData, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
||||
];
|
||||
case Browser.edge:
|
||||
return [
|
||||
if (programFiles != null)
|
||||
path.join(
|
||||
programFiles, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
|
||||
if (programFilesX86 != null)
|
||||
path.join(programFilesX86, 'Microsoft', 'Edge', 'Application',
|
||||
'msedge.exe'),
|
||||
if (localAppData != null)
|
||||
path.join(
|
||||
localAppData, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
|
||||
];
|
||||
case Browser.firefox:
|
||||
return [
|
||||
if (programFiles != null)
|
||||
path.join(programFiles, 'Mozilla Firefox', 'firefox.exe'),
|
||||
if (programFilesX86 != null)
|
||||
path.join(programFilesX86, 'Mozilla Firefox', 'firefox.exe'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _checkUnixExecutable(Browser browser) async {
|
||||
final paths = _getUnixExecutablePaths(browser);
|
||||
for (var execPath in paths) {
|
||||
execPath = execPath.replaceAll('~', Platform.environment['HOME'] ?? '');
|
||||
if (await Directory(execPath).exists() || await File(execPath).exists()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
List<String> _getUnixExecutablePaths(Browser browser) {
|
||||
if (Platform.isMacOS) {
|
||||
switch (browser) {
|
||||
case Browser.chrome:
|
||||
return [
|
||||
'/Applications/Google Chrome.app',
|
||||
'~/Applications/Google Chrome.app',
|
||||
'/Users/${Platform.environment['USER']}/Applications/Google Chrome.app'
|
||||
];
|
||||
case Browser.edge:
|
||||
return [
|
||||
'/Applications/Microsoft Edge.app',
|
||||
'~/Applications/Microsoft Edge.app',
|
||||
'/Users/${Platform.environment['USER']}/Applications/Microsoft Edge.app'
|
||||
];
|
||||
case Browser.firefox:
|
||||
return [
|
||||
'/Applications/Firefox.app',
|
||||
'~/Applications/Firefox.app',
|
||||
'/Users/${Platform.environment['USER']}/Applications/Firefox.app'
|
||||
];
|
||||
}
|
||||
} else {
|
||||
switch (browser) {
|
||||
case Browser.chrome:
|
||||
return [
|
||||
'/usr/bin/google-chrome',
|
||||
'/usr/bin/google-chrome-stable',
|
||||
'/usr/bin/chrome',
|
||||
'/snap/bin/google-chrome',
|
||||
'/opt/google/chrome/google-chrome'
|
||||
];
|
||||
case Browser.edge:
|
||||
return [
|
||||
'/usr/bin/microsoft-edge',
|
||||
'/usr/bin/microsoft-edge-stable',
|
||||
'/snap/bin/microsoft-edge',
|
||||
'/opt/microsoft/msedge/msedge'
|
||||
];
|
||||
case Browser.firefox:
|
||||
return [
|
||||
'/usr/bin/firefox',
|
||||
'/snap/bin/firefox',
|
||||
'/usr/lib/firefox/firefox',
|
||||
'/opt/firefox/firefox'
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String? _getManifestPath(Browser browser) {
|
||||
if (Platform.isWindows) {
|
||||
final execPath = Platform.resolvedExecutable;
|
||||
final execDir = path.dirname(execPath);
|
||||
return path.join(execDir, '$_hostName.json');
|
||||
}
|
||||
|
||||
final home =
|
||||
Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'];
|
||||
if (home == null) return null;
|
||||
|
||||
if (Platform.isMacOS) {
|
||||
switch (browser) {
|
||||
case Browser.chrome:
|
||||
return path.join(home, 'Library', 'Application Support', 'Google',
|
||||
'Chrome', 'NativeMessagingHosts', '$_hostName.json');
|
||||
case Browser.edge:
|
||||
return path.join(home, 'Library', 'Application Support',
|
||||
'Microsoft Edge', 'NativeMessagingHosts', '$_hostName.json');
|
||||
case Browser.firefox:
|
||||
return path.join(home, 'Library', 'Application Support', 'Mozilla',
|
||||
'NativeMessagingHosts', '$_hostName.json');
|
||||
}
|
||||
} else if (Platform.isLinux) {
|
||||
switch (browser) {
|
||||
case Browser.chrome:
|
||||
return path.join(home, '.config', 'google-chrome',
|
||||
'NativeMessagingHosts', '$_hostName.json');
|
||||
case Browser.edge:
|
||||
return path.join(home, '.config', 'microsoft-edge',
|
||||
'NativeMessagingHosts', '$_hostName.json');
|
||||
case Browser.firefox:
|
||||
return path.join(
|
||||
home, '.mozilla', 'native-messaging-hosts', '$_hostName.json');
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<bool> _checkWindowsRegistry(String keyPath) async {
|
||||
try {
|
||||
final key = Registry.openPath(RegistryHive.localMachine, path: keyPath);
|
||||
key.close();
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String _getManifestContent() {
|
||||
final execPath = Platform.resolvedExecutable;
|
||||
final execDir = path.dirname(execPath);
|
||||
final hostPath =
|
||||
path.join(execDir, 'host') + (Platform.isWindows ? '.exe' : '');
|
||||
final manifest = {
|
||||
'name': _hostName,
|
||||
'description': 'Gopeed browser extension host',
|
||||
'path': hostPath,
|
||||
'type': 'stdio',
|
||||
'allowed_origins': [
|
||||
'chrome-extension://gjddllnejledbfaeondocjpejpamclkk/',
|
||||
'chrome-extension://$_chromeExtensionId/',
|
||||
'chrome-extension://$_edgeExtensionId/',
|
||||
],
|
||||
'allowed_extensions': [_firefoxExtensionId],
|
||||
};
|
||||
return const JsonEncoder.withIndent(' ').convert(manifest);
|
||||
}
|
||||
|
||||
String _getWindowsRegistryKey(Browser browser) {
|
||||
switch (browser) {
|
||||
case Browser.chrome:
|
||||
return _chromeNativeHostsKey;
|
||||
case Browser.edge:
|
||||
return _edgeNativeHostsKey;
|
||||
case Browser.firefox:
|
||||
return _firefoxNativeHostsKey;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:macos_secure_bookmarks/macos_secure_bookmarks.dart';
|
||||
|
||||
import '../database/database.dart';
|
||||
import 'util.dart';
|
||||
|
||||
class MacSecureUtil {
|
||||
static Future<void> saveBookmark(String dir) async {
|
||||
if (!Util.isMacos()) {
|
||||
return;
|
||||
}
|
||||
final secureBookmarks = SecureBookmarks();
|
||||
final bookmark = await secureBookmarks.bookmark(Directory(dir));
|
||||
Database.instance.saveBookmark(MapEntry(dir, bookmark));
|
||||
}
|
||||
|
||||
static Future<void> loadBookmark() async {
|
||||
if (!Util.isMacos()) {
|
||||
return;
|
||||
}
|
||||
final secureBookmarks = SecureBookmarks();
|
||||
final bookmark = Database.instance.getBookmark() ?? {};
|
||||
bookmark.forEach((_, v) async {
|
||||
final resolvedFile =
|
||||
await secureBookmarks.resolveBookmark(v, isDirectory: true);
|
||||
await secureBookmarks.startAccessingSecurityScopedResource(resolvedFile);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,19 @@ import 'dart:io';
|
||||
import 'package:win32_registry/win32_registry.dart';
|
||||
|
||||
import '../../util.dart';
|
||||
import '../../win32.dart';
|
||||
|
||||
doRegisterUrlScheme(String scheme) {
|
||||
if (Util.isWindows()) {
|
||||
final schemeKey = 'Software\\Classes\\$scheme';
|
||||
final appPath = Platform.resolvedExecutable;
|
||||
|
||||
_upsertRegistry(
|
||||
upsertRegistry(
|
||||
schemeKey,
|
||||
'URL Protocol',
|
||||
'',
|
||||
);
|
||||
_upsertRegistry(
|
||||
upsertRegistry(
|
||||
'$schemeKey\\shell\\open\\command',
|
||||
'',
|
||||
'"$appPath" "%1"',
|
||||
@@ -43,22 +44,22 @@ doRegisterDefaultTorrentClient() {
|
||||
final appPath = Platform.resolvedExecutable;
|
||||
final iconPath =
|
||||
'${File(appPath).parent.path}\\data\\flutter_assets\\assets\\tray_icon\\icon.ico';
|
||||
_upsertRegistry(
|
||||
upsertRegistry(
|
||||
_torrentRegKey,
|
||||
'',
|
||||
_torrentRegValue,
|
||||
);
|
||||
_upsertRegistry(
|
||||
upsertRegistry(
|
||||
_torrentAppRegKey,
|
||||
'',
|
||||
'Torrent file',
|
||||
);
|
||||
_upsertRegistry(
|
||||
upsertRegistry(
|
||||
'$_torrentAppRegKey\\DefaultIcon',
|
||||
'',
|
||||
iconPath,
|
||||
);
|
||||
_upsertRegistry(
|
||||
upsertRegistry(
|
||||
'$_torrentAppRegKey\\shell\\open\\command',
|
||||
'',
|
||||
'"$appPath" "file:///%1"',
|
||||
@@ -74,20 +75,3 @@ doUnregisterDefaultTorrentClient() {
|
||||
Registry.currentUser.deleteKey(_torrentAppRegKey, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
// Add Windows registry key and value if not exists
|
||||
_upsertRegistry(String keyPath, String valueName, String value) {
|
||||
RegistryKey regKey;
|
||||
try {
|
||||
regKey = Registry.openPath(RegistryHive.currentUser,
|
||||
path: keyPath, desiredAccessRights: AccessRights.allAccess);
|
||||
} catch (e) {
|
||||
regKey = Registry.currentUser.createKey(keyPath);
|
||||
}
|
||||
|
||||
if (regKey.getValueAsString(valueName) != value) {
|
||||
regKey
|
||||
.createValue(RegistryValue(valueName, RegistryValueType.string, value));
|
||||
}
|
||||
regKey.close();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:win32_registry/win32_registry.dart';
|
||||
|
||||
/// Check registry key
|
||||
/// If the key does not exist or the value is different, return false
|
||||
checkRegistry(String keyPath, String valueName, String value) {
|
||||
RegistryKey regKey;
|
||||
try {
|
||||
regKey = Registry.openPath(RegistryHive.currentUser, path: keyPath);
|
||||
return regKey.getValueAsString(valueName) == value;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Upsert registry key
|
||||
/// If the key does not exist, create it
|
||||
/// If the value does not exist or is different, update it
|
||||
upsertRegistry(String keyPath, String valueName, String value) {
|
||||
RegistryKey regKey;
|
||||
try {
|
||||
regKey = Registry.openPath(RegistryHive.currentUser,
|
||||
path: keyPath, desiredAccessRights: AccessRights.allAccess);
|
||||
} catch (e) {
|
||||
regKey = Registry.currentUser.createKey(keyPath);
|
||||
}
|
||||
|
||||
if (regKey.getValueAsString(valueName) != value) {
|
||||
regKey
|
||||
.createValue(RegistryValue(valueName, RegistryValueType.string, value));
|
||||
}
|
||||
regKey.close();
|
||||
}
|
||||
Reference in new issue
Block a user