diff --git a/lib/models/hum_hub.dart b/lib/models/hum_hub.dart index b97185a..b1d9fa5 100644 --- a/lib/models/hum_hub.dart +++ b/lib/models/hum_hub.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:humhub/models/manifest.dart'; import 'package:humhub/util/api_provider.dart'; @@ -7,18 +9,25 @@ enum RedirectAction { opener, webView } class HumHub { Manifest? manifest; bool isHideDialog; + String? randomHash; - HumHub({this.manifest, this.isHideDialog = false}); + HumHub({ + this.manifest, + this.isHideDialog = false, + this.randomHash, + }); Map toJson() => { 'manifest': manifest != null ? manifest!.toJson() : null, 'isHideDialog': isHideDialog, + 'randomHash': randomHash, }; factory HumHub.fromJson(Map json) { return HumHub( manifest: Manifest.fromJson(json['manifest']), isHideDialog: json['isHideDialog'] as bool, + randomHash: json['randomHash'], ); } @@ -39,4 +48,11 @@ class HumHub { return RedirectAction.webView; } } + + static String generateHash(int length) { + final random = Random.secure(); + const characters = '0123456789abcdef'; + return List.generate( + length, (_) => characters[random.nextInt(characters.length)]).join(); + } } diff --git a/lib/pages/opener.dart b/lib/pages/opener.dart index c6ee49b..6a17a9b 100644 --- a/lib/pages/opener.dart +++ b/lib/pages/opener.dart @@ -119,9 +119,14 @@ class OpenerState extends ConsumerState { } else { Manifest manifest = asyncData.value!; // Set the manifestStateProvider with the manifest value so that it's globally accessible + // Generate hash and save it to store + String lastUrl = await ref.read(humHubProvider).getLastUrl(); + String currentUrl = urlTextController.text; + String hash = HumHub.generateHash(32); + if (lastUrl == currentUrl) hash = ref.read(humHubProvider).randomHash ?? hash; ref .read(humHubProvider) - .setInstance(HumHub(manifest: manifest)); + .setInstance(HumHub(manifest: manifest, randomHash: hash)); redirect(); } } diff --git a/lib/pages/web_view.dart b/lib/pages/web_view.dart index 155afcf..2933385 100644 --- a/lib/pages/web_view.dart +++ b/lib/pages/web_view.dart @@ -1,15 +1,18 @@ import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:humhub/pages/opener.dart'; +import 'package:humhub/util/const.dart'; import 'package:humhub/util/extensions.dart'; import 'package:humhub/models/manifest.dart'; import 'package:humhub/util/providers.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:webview_flutter/webview_flutter.dart'; +import '../models/hum_hub.dart'; + + class WebViewApp extends ConsumerStatefulWidget { final Manifest manifest; const WebViewApp({super.key, required this.manifest}); @@ -19,10 +22,16 @@ class WebViewApp extends ConsumerStatefulWidget { } class WebViewAppState extends ConsumerState { - final scaffoldKey = GlobalKey(); late InAppWebViewController inAppWebViewController; final WebViewCookieManager cookieManager = WebViewCookieManager(); - var customHeader = {'my-header-value-01': '111111', 'my-header-value-02': '222222'}; + final options = InAppWebViewGroupOptions( + crossPlatform: InAppWebViewOptions( + useShouldOverrideUrlLoading: true, + useShouldInterceptAjaxRequest: true, + useShouldInterceptFetchRequest: true, + javaScriptEnabled: true, + ), + ); @override void initState() { @@ -32,74 +41,79 @@ class WebViewAppState extends ConsumerState { @override Widget build(BuildContext context) { + final initialRequest = URLRequest( + url: Uri.parse(widget.manifest.baseUrl), headers: customHeaders); + //Append random hash to customHeaders in this state the header should always exist. + customHeaders.addAll({'x-humhub-app-token': ref.read(humHubProvider).randomHash!}); return WillPopScope( onWillPop: () => inAppWebViewController.exitApp(context, ref), child: Scaffold( backgroundColor: HexColor(widget.manifest.themeColor), body: SafeArea( child: InAppWebView( - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions( - useShouldOverrideUrlLoading: true, - useShouldInterceptAjaxRequest: true, - useShouldInterceptFetchRequest: true, - javaScriptEnabled: true, - ), - ), - shouldOverrideUrlLoading: (controller, action) async { - // 1st check if url is not def. app url and open it in a browser or inApp. - final url = action.request.url!.origin; - if (!url.startsWith(widget.manifest.baseUrl)) { - launchUrl(action.request.url!, - mode: LaunchMode.externalApplication); - return NavigationActionPolicy.CANCEL; - } - // 2nd Append customHeader if url is in app redirect and CANCEL the requests without custom headers - if (Platform.isAndroid || - action.iosWKNavigationType == - IOSWKNavigationType.LINK_ACTIVATED) { - controller.loadUrl( - urlRequest: URLRequest( - url: action.request.url, headers: customHeader)); - return NavigationActionPolicy.CANCEL; - } - return NavigationActionPolicy.ALLOW; - }, - shouldInterceptAjaxRequest: (controller, ajaxReq) async { - // Append headers on every AJAX request - ajaxReq.headers = AjaxRequestHeaders(customHeader); - return ajaxReq; - }, - shouldInterceptFetchRequest: (controller, fetchReq) async { - fetchReq.headers?.addAll(customHeader); - return fetchReq; - }, - initialUrlRequest: URLRequest( - url: Uri.parse(widget.manifest.baseUrl), headers: customHeader), - onWebViewCreated: (controller) async { - await controller.addWebMessageListener( - WebMessageListener( - jsObjectName: "flutterChannel", - onPostMessage: - (message, sourceOrigin, isMainFrame, replyProxy) { - ref - .read(humHubProvider) - .setIsHideDialog(message == "humhub.mobile.hideOpener"); - if (!ref.read(humHubProvider).isHideDialog) { - ref.read(humHubProvider).clearSafeStorage(); - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (context) => const Opener()), - (Route route) => false); - } - }, - ), - ); - inAppWebViewController = controller; - }, - ), + initialUrlRequest: initialRequest, + initialOptions: options, + shouldOverrideUrlLoading: shouldOverrideUrlLoading, + onWebViewCreated: onWebViewCreated, + shouldInterceptAjaxRequest: shouldInterceptAjaxRequest, + shouldInterceptFetchRequest: shouldInterceptFetchRequest), ), ), ); } + + Future shouldOverrideUrlLoading( + InAppWebViewController controller, NavigationAction action) async { + // 1st check if url is not def. app url and open it in a browser or inApp. + final url = action.request.url!.origin; + if (!url.startsWith(widget.manifest.baseUrl)) { + launchUrl(action.request.url!, mode: LaunchMode.externalApplication); + return NavigationActionPolicy.CANCEL; + } + // 2nd Append customHeader if url is in app redirect and CANCEL the requests without custom headers + if (Platform.isAndroid || + action.iosWKNavigationType == IOSWKNavigationType.LINK_ACTIVATED) { + controller.loadUrl( + urlRequest: + URLRequest(url: action.request.url, headers: customHeaders)); + return NavigationActionPolicy.CANCEL; + } + return NavigationActionPolicy.ALLOW; + } + + onWebViewCreated(InAppWebViewController controller) async { + await controller.addWebMessageListener( + WebMessageListener( + jsObjectName: "flutterChannel", + onPostMessage: (message, sourceOrigin, isMainFrame, replyProxy) { + ref + .read(humHubProvider) + .setIsHideDialog(message == "humhub.mobile.hideOpener"); + if (!ref.read(humHubProvider).isHideDialog) { + ref.read(humHubProvider).clearSafeStorage(); + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute(builder: (context) => const Opener()), + (Route route) => false); + } + else{ + ref.read(humHubProvider).setHash(HumHub.generateHash(32)); + } + }, + ), + ); + inAppWebViewController = controller; + } + + Future shouldInterceptAjaxRequest( + InAppWebViewController controller, AjaxRequest ajaxReq) async { + // Append headers on every AJAX request + ajaxReq.headers = AjaxRequestHeaders(customHeaders); + return ajaxReq; + } + + Future shouldInterceptFetchRequest( + InAppWebViewController controller, FetchRequest fetchReq) async { + fetchReq.headers?.addAll(customHeaders); + return fetchReq; + } } diff --git a/lib/util/const.dart b/lib/util/const.dart index 60d0e8b..b56f931 100644 --- a/lib/util/const.dart +++ b/lib/util/const.dart @@ -5,4 +5,6 @@ const progress = Center(child: CircularProgressIndicator()); class StorageKeys{ static String humhubInstance = "humHubInstance"; static String lastInstanceUrl = "humHubLastUrl"; -} \ No newline at end of file +} + +var customHeaders = {'my-header-value-01': '111111', 'my-header-value-02': '222222'}; \ No newline at end of file diff --git a/lib/util/extensions.dart b/lib/util/extensions.dart index 8b43725..b63028e 100644 --- a/lib/util/extensions.dart +++ b/lib/util/extensions.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; @@ -5,8 +7,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:humhub/models/manifest.dart'; import 'package:humhub/pages/opener.dart'; import 'package:humhub/util/providers.dart'; +import 'package:url_launcher/url_launcher.dart'; import 'package:webview_flutter/webview_flutter.dart'; +import 'const.dart'; + extension MyCookies on WebViewCookieManager { Future setMyCookies(Manifest manifest) async { await setCookie( @@ -20,6 +25,7 @@ extension MyCookies on WebViewCookieManager { } extension MyWebViewController on InAppWebViewController { + Future exitApp(BuildContext context, ref) async { bool canGoBack = await this.canGoBack(); if (canGoBack) { diff --git a/lib/util/providers.dart b/lib/util/providers.dart index fb5a492..55e1779 100644 --- a/lib/util/providers.dart +++ b/lib/util/providers.dart @@ -16,6 +16,7 @@ class HumHubNotifier extends ChangeNotifier { bool get isHideDialog => _humHubInstance.isHideDialog; Manifest? get manifest => _humHubInstance.manifest; + String? get randomHash => _humHubInstance.randomHash; void setIsHideDialog(bool isHide) { _humHubInstance.isHideDialog = isHide; @@ -32,6 +33,13 @@ class HumHubNotifier extends ChangeNotifier { void setInstance(HumHub instance) { _humHubInstance.manifest = instance.manifest; _humHubInstance.isHideDialog = instance.isHideDialog; + _humHubInstance.randomHash = instance.randomHash; + _updateSafeStorage(); + notifyListeners(); + } + + void setHash(String hash) { + _humHubInstance.randomHash = hash; _updateSafeStorage(); notifyListeners(); }