mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 07:47:24 +00:00
cloudflare verification
This commit is contained in:
@@ -22,6 +22,7 @@ import 'package:venera/foundation/image_provider/cached_image.dart';
|
|||||||
import 'package:venera/foundation/local.dart';
|
import 'package:venera/foundation/local.dart';
|
||||||
import 'package:venera/foundation/res.dart';
|
import 'package:venera/foundation/res.dart';
|
||||||
import 'package:venera/foundation/state_controller.dart';
|
import 'package:venera/foundation/state_controller.dart';
|
||||||
|
import 'package:venera/network/cloudflare.dart';
|
||||||
import 'package:venera/pages/comic_page.dart';
|
import 'package:venera/pages/comic_page.dart';
|
||||||
import 'package:venera/pages/favorites/favorites_page.dart';
|
import 'package:venera/pages/favorites/favorites_page.dart';
|
||||||
import 'package:venera/utils/ext.dart';
|
import 'package:venera/utils/ext.dart';
|
||||||
|
@@ -16,6 +16,7 @@ class NetworkError extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var cfe = CloudflareException.fromString(message);
|
||||||
Widget body = Center(
|
Widget body = Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -41,7 +42,7 @@ class NetworkError extends StatelessWidget {
|
|||||||
height: 8,
|
height: 8,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
message,
|
cfe == null ? message : "Cloudflare verification required".tl,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
),
|
),
|
||||||
@@ -50,7 +51,17 @@ class NetworkError extends StatelessWidget {
|
|||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
if (retry != null)
|
if (retry != null)
|
||||||
FilledButton(onPressed: retry, child: Text('重试'.tl))
|
if (cfe != null)
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () => passCloudflare(
|
||||||
|
CloudflareException.fromString(message)!, retry!),
|
||||||
|
child: Text('Verify'.tl),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
FilledButton(
|
||||||
|
onPressed: retry,
|
||||||
|
child: Text('Retry'.tl),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@@ -21,6 +21,7 @@ import 'package:pointycastle/block/modes/ecb.dart';
|
|||||||
import 'package:pointycastle/block/modes/ofb.dart';
|
import 'package:pointycastle/block/modes/ofb.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
import 'package:venera/network/app_dio.dart';
|
import 'package:venera/network/app_dio.dart';
|
||||||
|
import 'package:venera/network/cloudflare.dart';
|
||||||
import 'package:venera/network/cookie_jar.dart';
|
import 'package:venera/network/cookie_jar.dart';
|
||||||
|
|
||||||
import 'comic_source/comic_source.dart';
|
import 'comic_source/comic_source.dart';
|
||||||
@@ -67,8 +68,7 @@ class JsEngine with _JSEngineApi{
|
|||||||
responseType: ResponseType.plain, validateStatus: (status) => true));
|
responseType: ResponseType.plain, validateStatus: (status) => true));
|
||||||
_cookieJar ??= SingleInstanceCookieJar.instance!;
|
_cookieJar ??= SingleInstanceCookieJar.instance!;
|
||||||
_dio!.interceptors.add(CookieManagerSql(_cookieJar!));
|
_dio!.interceptors.add(CookieManagerSql(_cookieJar!));
|
||||||
// TODO: Cloudflare Interceptor
|
_dio!.interceptors.add(CloudflareInterceptor());
|
||||||
// _dio!.interceptors.add(CloudflareInterceptor());
|
|
||||||
_closed = false;
|
_closed = false;
|
||||||
_engine = FlutterQjs();
|
_engine = FlutterQjs();
|
||||||
_engine!.dispatch();
|
_engine!.dispatch();
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
@@ -11,7 +12,10 @@ import 'foundation/app.dart';
|
|||||||
import 'foundation/appdata.dart';
|
import 'foundation/appdata.dart';
|
||||||
import 'init.dart';
|
import 'init.dart';
|
||||||
|
|
||||||
void main() {
|
void main(List<String> args) {
|
||||||
|
if (runWebViewTitleBarWidget(args)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
runZonedGuarded(() async {
|
runZonedGuarded(() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await init();
|
await init();
|
||||||
|
177
lib/network/cloudflare.dart
Normal file
177
lib/network/cloudflare.dart
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
import 'dart:io' as io;
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:venera/foundation/app.dart';
|
||||||
|
import 'package:venera/foundation/appdata.dart';
|
||||||
|
import 'package:venera/foundation/consts.dart';
|
||||||
|
import 'package:venera/pages/webview.dart';
|
||||||
|
|
||||||
|
import 'cookie_jar.dart';
|
||||||
|
|
||||||
|
class CloudflareException implements DioException {
|
||||||
|
final String url;
|
||||||
|
|
||||||
|
const CloudflareException(this.url);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return "CloudflareException: $url";
|
||||||
|
}
|
||||||
|
|
||||||
|
static CloudflareException? fromString(String message) {
|
||||||
|
var match = RegExp(r"CloudflareException: (.+)").firstMatch(message);
|
||||||
|
if (match == null) return null;
|
||||||
|
return CloudflareException(match.group(1)!);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
DioException copyWith(
|
||||||
|
{RequestOptions? requestOptions,
|
||||||
|
Response<dynamic>? response,
|
||||||
|
DioExceptionType? type,
|
||||||
|
Object? error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
String? message}) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object? get error => this;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get message => toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
RequestOptions get requestOptions => RequestOptions();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Response? get response => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
StackTrace get stackTrace => StackTrace.empty;
|
||||||
|
|
||||||
|
@override
|
||||||
|
DioExceptionType get type => DioExceptionType.badResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CloudflareInterceptor extends Interceptor {
|
||||||
|
@override
|
||||||
|
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||||
|
if(options.headers['cookie'].toString().contains('cf_clearance')) {
|
||||||
|
options.headers['user-agent'] = appdata.implicitData['ua'] ?? webUA;
|
||||||
|
}
|
||||||
|
handler.next(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
||||||
|
if (err.response?.statusCode == 403) {
|
||||||
|
handler.next(_check(err.response!) ?? err);
|
||||||
|
} else {
|
||||||
|
handler.next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||||
|
if (response.statusCode == 403) {
|
||||||
|
var err = _check(response);
|
||||||
|
if (err != null) {
|
||||||
|
handler.reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handler.next(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
CloudflareException? _check(Response response) {
|
||||||
|
if (response.headers['cf-mitigated']?.firstOrNull == "challenge") {
|
||||||
|
return CloudflareException(response.requestOptions.uri.toString());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void passCloudflare(CloudflareException e, void Function() onFinished) async {
|
||||||
|
var url = e.url;
|
||||||
|
var uri = Uri.parse(url);
|
||||||
|
|
||||||
|
void saveCookies(Map<String, String> cookies) {
|
||||||
|
var domain = uri.host;
|
||||||
|
var splits = domain.split('.');
|
||||||
|
if (splits.length > 1) {
|
||||||
|
domain = ".${splits[splits.length - 2]}.${splits[splits.length - 1]}";
|
||||||
|
}
|
||||||
|
SingleInstanceCookieJar.instance!.saveFromResponse(
|
||||||
|
uri,
|
||||||
|
List<io.Cookie>.generate(cookies.length, (index) {
|
||||||
|
var cookie = io.Cookie(
|
||||||
|
cookies.keys.elementAt(index), cookies.values.elementAt(index));
|
||||||
|
cookie.domain = domain;
|
||||||
|
return cookie;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (App.isDesktop && (await DesktopWebview.isAvailable())) {
|
||||||
|
var webview = DesktopWebview(
|
||||||
|
initialUrl: url,
|
||||||
|
onTitleChange: (title, controller) async {
|
||||||
|
var res = await controller.evaluateJavascript(
|
||||||
|
"document.head.innerHTML.includes('#challenge-success-text')");
|
||||||
|
if (res == 'false') {
|
||||||
|
var ua = controller.userAgent;
|
||||||
|
if (ua != null) {
|
||||||
|
appdata.implicitData['ua'] = ua;
|
||||||
|
appdata.writeImplicitData();
|
||||||
|
}
|
||||||
|
var cookiesMap = await controller.getCookies(url);
|
||||||
|
if(cookiesMap['cf_clearance'] == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saveCookies(cookiesMap);
|
||||||
|
controller.close();
|
||||||
|
onFinished();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
webview.open();
|
||||||
|
} else if (App.isMobile) {
|
||||||
|
await App.rootContext.to(
|
||||||
|
() => AppWebview(
|
||||||
|
initialUrl: url,
|
||||||
|
singlePage: true,
|
||||||
|
onTitleChange: (title, controller) async {
|
||||||
|
var res = await controller.platform.evaluateJavascript(
|
||||||
|
source:
|
||||||
|
"document.head.innerHTML.includes('#challenge-success-text')");
|
||||||
|
if (res == false) {
|
||||||
|
var ua = await controller.getUA();
|
||||||
|
if (ua != null) {
|
||||||
|
appdata.implicitData['ua'] = ua;
|
||||||
|
appdata.writeImplicitData();
|
||||||
|
}
|
||||||
|
var cookiesMap = await controller.getCookies(url) ?? {};
|
||||||
|
if(cookiesMap['cf_clearance'] == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saveCookies(cookiesMap);
|
||||||
|
App.rootPop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onStarted: (controller) async {
|
||||||
|
var ua = await controller.getUA();
|
||||||
|
if (ua != null) {
|
||||||
|
appdata.implicitData['ua'] = ua;
|
||||||
|
appdata.writeImplicitData();
|
||||||
|
}
|
||||||
|
var cookiesMap = await controller.getCookies(url) ?? {};
|
||||||
|
saveCookies(cookiesMap);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
onFinished();
|
||||||
|
} else {
|
||||||
|
App.rootContext.showMessage(message: "Unsupported device");
|
||||||
|
}
|
||||||
|
}
|
@@ -326,9 +326,6 @@ class _SettingsPageState extends State<SettingsPage> implements PopEntry {
|
|||||||
@override
|
@override
|
||||||
ValueListenable<bool> get canPopNotifier => canPop;
|
ValueListenable<bool> get canPopNotifier => canPop;
|
||||||
|
|
||||||
/*
|
|
||||||
flutter >=3.24.0 api
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onPopInvokedWithResult(bool didPop, result) {
|
void onPopInvokedWithResult(bool didPop, result) {
|
||||||
if (currentPage != -1) {
|
if (currentPage != -1) {
|
||||||
@@ -346,15 +343,4 @@ class _SettingsPageState extends State<SettingsPage> implements PopEntry {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
// flutter <3.24.0 api
|
|
||||||
@override
|
|
||||||
PopInvokedCallback? get onPopInvoked => (bool didPop) {
|
|
||||||
if (currentPage != -1) {
|
|
||||||
setState(() {
|
|
||||||
currentPage = -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
282
lib/pages/webview.dart
Normal file
282
lib/pages/webview.dart
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
import 'package:venera/components/components.dart';
|
||||||
|
import 'package:venera/foundation/app.dart';
|
||||||
|
import 'package:venera/network/app_dio.dart';
|
||||||
|
import 'package:venera/utils/ext.dart';
|
||||||
|
import 'package:venera/utils/translations.dart';
|
||||||
|
|
||||||
|
export 'package:flutter_inappwebview/flutter_inappwebview.dart' show WebUri, URLRequest;
|
||||||
|
|
||||||
|
extension WebviewExtension on InAppWebViewController{
|
||||||
|
Future<Map<String, String>?> getCookies(String url) async{
|
||||||
|
if(url.contains("https://")){
|
||||||
|
url.replaceAll("https://", "");
|
||||||
|
}
|
||||||
|
if(url[url.length-1] == '/'){
|
||||||
|
url = url.substring(0, url.length-1);
|
||||||
|
}
|
||||||
|
CookieManager cookieManager = CookieManager.instance();
|
||||||
|
final cookies = await cookieManager.getCookies(url: WebUri(url));
|
||||||
|
Map<String, String> res = {};
|
||||||
|
for(var cookie in cookies){
|
||||||
|
res[cookie.name] = cookie.value;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getUA() async{
|
||||||
|
var res = await evaluateJavascript(source: "navigator.userAgent");
|
||||||
|
if(res is String){
|
||||||
|
if(res[0] == "'" || res[0] == "\"") {
|
||||||
|
res = res.substring(1, res.length-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res is String ? res : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppWebview extends StatefulWidget {
|
||||||
|
const AppWebview({required this.initialUrl, this.onTitleChange,
|
||||||
|
this.onNavigation, this.singlePage = false, this.onStarted, super.key});
|
||||||
|
|
||||||
|
final String initialUrl;
|
||||||
|
|
||||||
|
final void Function(String title, InAppWebViewController controller)? onTitleChange;
|
||||||
|
|
||||||
|
final bool Function(String url)? onNavigation;
|
||||||
|
|
||||||
|
final void Function(InAppWebViewController controller)? onStarted;
|
||||||
|
|
||||||
|
final bool singlePage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AppWebview> createState() => _AppWebviewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppWebviewState extends State<AppWebview> {
|
||||||
|
InAppWebViewController? controller;
|
||||||
|
|
||||||
|
String title = "Webview";
|
||||||
|
|
||||||
|
double _progress = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final actions = [
|
||||||
|
Tooltip(
|
||||||
|
message: "More",
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(Icons.more_horiz),
|
||||||
|
onPressed: (){
|
||||||
|
showMenu(context: context, position: RelativeRect.fromLTRB(
|
||||||
|
MediaQuery.of(context).size.width,
|
||||||
|
0,
|
||||||
|
MediaQuery.of(context).size.width,
|
||||||
|
0
|
||||||
|
), items: [
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Text("Open in browser".tl),
|
||||||
|
onTap: () async => launchUrlString((await controller?.getUrl())!.path),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Text("Copy link".tl),
|
||||||
|
onTap: () async => Clipboard.setData(ClipboardData(text: (await controller?.getUrl())!.path)),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Text("Reload".tl),
|
||||||
|
onTap: () => controller?.reload(),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
Widget body = InAppWebView(
|
||||||
|
initialUrlRequest: URLRequest(url: WebUri(widget.initialUrl)),
|
||||||
|
onTitleChanged: (c, t){
|
||||||
|
if(mounted){
|
||||||
|
setState(() {
|
||||||
|
title = t ?? "Webview";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
widget.onTitleChange?.call(title, controller!);
|
||||||
|
},
|
||||||
|
shouldOverrideUrlLoading: (c, r) async {
|
||||||
|
var res = widget.onNavigation?.call(r.request.url?.toString() ?? "") ?? false;
|
||||||
|
if(res) {
|
||||||
|
return NavigationActionPolicy.CANCEL;
|
||||||
|
} else {
|
||||||
|
return NavigationActionPolicy.ALLOW;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onWebViewCreated: (c){
|
||||||
|
controller = c;
|
||||||
|
widget.onStarted?.call(c);
|
||||||
|
},
|
||||||
|
onProgressChanged: (c, p){
|
||||||
|
if(mounted){
|
||||||
|
setState(() {
|
||||||
|
_progress = p / 100;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
body = Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(child: body),
|
||||||
|
if(_progress < 1.0)
|
||||||
|
const Positioned.fill(child: Center(
|
||||||
|
child: CircularProgressIndicator()))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: Appbar(
|
||||||
|
title: Text(title, maxLines: 1, overflow: TextOverflow.ellipsis,),
|
||||||
|
actions: actions,
|
||||||
|
),
|
||||||
|
body: body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DesktopWebview {
|
||||||
|
static Future<bool> isAvailable() => WebviewWindow.isWebviewAvailable();
|
||||||
|
|
||||||
|
final String initialUrl;
|
||||||
|
|
||||||
|
final void Function(String title, DesktopWebview controller)? onTitleChange;
|
||||||
|
|
||||||
|
final void Function(String url, DesktopWebview webview)? onNavigation;
|
||||||
|
|
||||||
|
final void Function(DesktopWebview controller)? onStarted;
|
||||||
|
|
||||||
|
final void Function()? onClose;
|
||||||
|
|
||||||
|
DesktopWebview({
|
||||||
|
required this.initialUrl,
|
||||||
|
this.onTitleChange,
|
||||||
|
this.onNavigation,
|
||||||
|
this.onStarted,
|
||||||
|
this.onClose
|
||||||
|
});
|
||||||
|
|
||||||
|
Webview? _webview;
|
||||||
|
|
||||||
|
String? _ua;
|
||||||
|
|
||||||
|
String? title;
|
||||||
|
|
||||||
|
void onMessage(String message) {
|
||||||
|
var json = jsonDecode(message);
|
||||||
|
if(json is Map){
|
||||||
|
if(json["id"] == "document_created"){
|
||||||
|
title = json["data"]["title"];
|
||||||
|
_ua = json["data"]["ua"];
|
||||||
|
onTitleChange?.call(title!, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String? get userAgent => _ua;
|
||||||
|
|
||||||
|
Timer? timer;
|
||||||
|
|
||||||
|
void _runTimer() {
|
||||||
|
timer ??= Timer.periodic(const Duration(seconds: 2), (t) async {
|
||||||
|
const js = '''
|
||||||
|
function collect() {
|
||||||
|
if(document.readyState === 'loading') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
let data = {
|
||||||
|
id: "document_created",
|
||||||
|
data: {
|
||||||
|
title: document.title,
|
||||||
|
url: location.href,
|
||||||
|
ua: navigator.userAgent
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
collect();
|
||||||
|
''';
|
||||||
|
if(_webview != null) {
|
||||||
|
onMessage(await evaluateJavascript(js) ?? '');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void open() async {
|
||||||
|
_webview = await WebviewWindow.create(configuration: CreateConfiguration(
|
||||||
|
useWindowPositionAndSize: true,
|
||||||
|
userDataFolderWindows: "${App.dataPath}\\webview",
|
||||||
|
title: "webview",
|
||||||
|
proxy: AppDio.proxy,
|
||||||
|
));
|
||||||
|
_webview!.addOnWebMessageReceivedCallback(onMessage);
|
||||||
|
_webview!.setOnNavigation((s) => onNavigation?.call(s, this));
|
||||||
|
_webview!.launch(initialUrl, triggerOnUrlRequestEvent: false);
|
||||||
|
_runTimer();
|
||||||
|
_webview!.onClose.then((value) {
|
||||||
|
_webview = null;
|
||||||
|
timer?.cancel();
|
||||||
|
timer = null;
|
||||||
|
onClose?.call();
|
||||||
|
});
|
||||||
|
Future.delayed(const Duration(milliseconds: 200), () {
|
||||||
|
onStarted?.call(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> evaluateJavascript(String source) {
|
||||||
|
return _webview!.evaluateJavaScript(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>> getCookies(String url) async{
|
||||||
|
var allCookies = await _webview!.getAllCookies();
|
||||||
|
var res = <String, String>{};
|
||||||
|
for(var c in allCookies) {
|
||||||
|
if(_cookieMatch(url, c.domain)){
|
||||||
|
res[_removeCode0(c.name)] = _removeCode0(c.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _removeCode0(String s) {
|
||||||
|
var codeUints = List<int>.from(s.codeUnits);
|
||||||
|
codeUints.removeWhere((e) => e == 0);
|
||||||
|
return String.fromCharCodes(codeUints);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _cookieMatch(String url, String domain) {
|
||||||
|
domain = _removeCode0(domain);
|
||||||
|
var host = Uri.parse(url).host;
|
||||||
|
var acceptedHost = _getAcceptedDomains(host);
|
||||||
|
return acceptedHost.contains(domain.removeAllBlank);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _getAcceptedDomains(String host) {
|
||||||
|
var acceptedDomains = <String>[host];
|
||||||
|
var hostParts = host.split(".");
|
||||||
|
for (var i = 0; i < hostParts.length - 1; i++) {
|
||||||
|
acceptedDomains.add(".${hostParts.sublist(i).join(".")}");
|
||||||
|
}
|
||||||
|
return acceptedDomains;
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
_webview?.close();
|
||||||
|
_webview = null;
|
||||||
|
}
|
||||||
|
}
|
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <desktop_webview_window/desktop_webview_window_plugin.h>
|
||||||
#include <flutter_qjs/flutter_qjs_plugin.h>
|
#include <flutter_qjs/flutter_qjs_plugin.h>
|
||||||
#include <screen_retriever/screen_retriever_plugin.h>
|
#include <screen_retriever/screen_retriever_plugin.h>
|
||||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||||
@@ -13,6 +14,9 @@
|
|||||||
#include <window_manager/window_manager_plugin.h>
|
#include <window_manager/window_manager_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
|
g_autoptr(FlPluginRegistrar) desktop_webview_window_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopWebviewWindowPlugin");
|
||||||
|
desktop_webview_window_plugin_register_with_registrar(desktop_webview_window_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) flutter_qjs_registrar =
|
g_autoptr(FlPluginRegistrar) flutter_qjs_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterQjsPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterQjsPlugin");
|
||||||
flutter_qjs_plugin_register_with_registrar(flutter_qjs_registrar);
|
flutter_qjs_plugin_register_with_registrar(flutter_qjs_registrar);
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
desktop_webview_window
|
||||||
flutter_qjs
|
flutter_qjs
|
||||||
screen_retriever
|
screen_retriever
|
||||||
sqlite3_flutter_libs
|
sqlite3_flutter_libs
|
||||||
|
@@ -5,6 +5,8 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import desktop_webview_window
|
||||||
|
import flutter_inappwebview_macos
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import screen_retriever
|
import screen_retriever
|
||||||
import share_plus
|
import share_plus
|
||||||
@@ -13,6 +15,8 @@ import url_launcher_macos
|
|||||||
import window_manager
|
import window_manager
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
DesktopWebviewWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWebviewWindowPlugin"))
|
||||||
|
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
|
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
|
||||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
|
101
pubspec.lock
101
pubspec.lock
@@ -73,6 +73,15 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
|
desktop_webview_window:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "packages/desktop_webview_window"
|
||||||
|
ref: HEAD
|
||||||
|
resolved-ref: b8f7e94c576acf4ca3dce5b9f8fb8076e5eaca5e
|
||||||
|
url: "https://github.com/wgh136/flutter_desktop_webview"
|
||||||
|
source: git
|
||||||
|
version: "0.2.4"
|
||||||
dio:
|
dio:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -134,6 +143,70 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_inappwebview:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview
|
||||||
|
sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.5"
|
||||||
|
flutter_inappwebview_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_android
|
||||||
|
sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
flutter_inappwebview_internal_annotations:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_internal_annotations
|
||||||
|
sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
flutter_inappwebview_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_ios
|
||||||
|
sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
flutter_inappwebview_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_macos
|
||||||
|
sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
flutter_inappwebview_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_platform_interface
|
||||||
|
sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0+1"
|
||||||
|
flutter_inappwebview_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_web
|
||||||
|
sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
flutter_inappwebview_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_windows
|
||||||
|
sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.0"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -218,18 +291,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.4"
|
version: "10.0.5"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.5"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -258,18 +331,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.12.0"
|
version: "1.15.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -473,10 +546,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.0"
|
version: "0.7.2"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -569,10 +642,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.1"
|
version: "14.2.5"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -614,5 +687,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.4.4 <4.0.0"
|
dart: ">=3.5.0 <4.0.0"
|
||||||
flutter: ">=3.22.3"
|
flutter: ">=3.24.3"
|
||||||
|
@@ -5,8 +5,8 @@ publish_to: 'none'
|
|||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.4.4 <4.0.0'
|
sdk: '>=3.5.0 <4.0.0'
|
||||||
flutter: 3.22.3
|
flutter: 3.24.3
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@@ -43,6 +43,11 @@ dependencies:
|
|||||||
flutter_reorderable_grid_view: 5.0.1
|
flutter_reorderable_grid_view: 5.0.1
|
||||||
yaml: any
|
yaml: any
|
||||||
uuid: ^4.5.1
|
uuid: ^4.5.1
|
||||||
|
desktop_webview_window:
|
||||||
|
git:
|
||||||
|
url: https://github.com/wgh136/flutter_desktop_webview
|
||||||
|
path: packages/desktop_webview_window
|
||||||
|
flutter_inappwebview: ^6.1.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <desktop_webview_window/desktop_webview_window_plugin.h>
|
||||||
|
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
|
||||||
#include <flutter_qjs/flutter_qjs_plugin.h>
|
#include <flutter_qjs/flutter_qjs_plugin.h>
|
||||||
#include <screen_retriever/screen_retriever_plugin.h>
|
#include <screen_retriever/screen_retriever_plugin.h>
|
||||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||||
@@ -14,6 +16,10 @@
|
|||||||
#include <window_manager/window_manager_plugin.h>
|
#include <window_manager/window_manager_plugin.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
DesktopWebviewWindowPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("DesktopWebviewWindowPlugin"));
|
||||||
|
FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
|
||||||
FlutterQjsPluginRegisterWithRegistrar(
|
FlutterQjsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterQjsPlugin"));
|
registry->GetRegistrarForPlugin("FlutterQjsPlugin"));
|
||||||
ScreenRetrieverPluginRegisterWithRegistrar(
|
ScreenRetrieverPluginRegisterWithRegistrar(
|
||||||
|
@@ -3,6 +3,8 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
desktop_webview_window
|
||||||
|
flutter_inappwebview_windows
|
||||||
flutter_qjs
|
flutter_qjs
|
||||||
screen_retriever
|
screen_retriever
|
||||||
share_plus
|
share_plus
|
||||||
|
Reference in New Issue
Block a user