diff --git a/Runtime/GuruWebview/UniWebView5/Plugins.meta b/Runtime/GuruWebview/UniWebView5/Plugins.meta index c7cbf6d..0675717 100644 --- a/Runtime/GuruWebview/UniWebView5/Plugins.meta +++ b/Runtime/GuruWebview/UniWebView5/Plugins.meta @@ -1,8 +1,8 @@ fileFormatVersion: 2 -guid: 4920deff2cffa43c98b6c11f1d4c90fd +guid: d3dad2b92748d439e851e10d4f4ecaf0 folderAsset: yes timeCreated: 1489325633 -licenseType: Free +licenseType: Store DefaultImporter: userData: assetBundleName: diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/Android.meta b/Runtime/GuruWebview/UniWebView5/Plugins/Android.meta index d16eb63..41ddb41 100644 --- a/Runtime/GuruWebview/UniWebView5/Plugins/Android.meta +++ b/Runtime/GuruWebview/UniWebView5/Plugins/Android.meta @@ -1,9 +1,8 @@ fileFormatVersion: 2 -guid: 5f6c0bbe8d0744418b9d67f7b9fd7fb4 +guid: 3916a9bfa4fd449e2827f2f20897a7d7 folderAsset: yes -timeCreated: 1489325654 -licenseType: Free DefaultImporter: + externalObjects: {} userData: assetBundleName: assetBundleVariant: diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/Android/UniWebView.aar b/Runtime/GuruWebview/UniWebView5/Plugins/Android/UniWebView.aar index 9f4490d..f8c4eb8 100644 Binary files a/Runtime/GuruWebview/UniWebView5/Plugins/Android/UniWebView.aar and b/Runtime/GuruWebview/UniWebView5/Plugins/Android/UniWebView.aar differ diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/Android/UniWebView.aar.meta b/Runtime/GuruWebview/UniWebView5/Plugins/Android/UniWebView.aar.meta index 8e31aa5..788c83f 100644 --- a/Runtime/GuruWebview/UniWebView5/Plugins/Android/UniWebView.aar.meta +++ b/Runtime/GuruWebview/UniWebView5/Plugins/Android/UniWebView.aar.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 70a24c5a1222c4ae4b48a1d4738ae6de +guid: a70633e155d144f5da10d40c35d9c832 PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/Android/omsdk-android-1.4.0-release.aar b/Runtime/GuruWebview/UniWebView5/Plugins/Android/omsdk-android-1.4.0-release.aar deleted file mode 100644 index 83bf102..0000000 Binary files a/Runtime/GuruWebview/UniWebView5/Plugins/Android/omsdk-android-1.4.0-release.aar and /dev/null differ diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/Android/omsdk-android-1.4.0-release.aar.meta b/Runtime/GuruWebview/UniWebView5/Plugins/Android/omsdk-android-1.4.0-release.aar.meta deleted file mode 100644 index 2534f8d..0000000 --- a/Runtime/GuruWebview/UniWebView5/Plugins/Android/omsdk-android-1.4.0-release.aar.meta +++ /dev/null @@ -1,32 +0,0 @@ -fileFormatVersion: 2 -guid: 421fd959e80dc41d1b4d5f714a7f0752 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Android: Android - second: - enabled: 1 - settings: {} - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle.meta b/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle.meta index 58c187f..f4cb94b 100644 --- a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle.meta +++ b/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 12a93e06f58f74620aa3663f7e55ecb3 +guid: db3f23043d1754d4b9c96ddb5ab457c9 folderAsset: yes PluginImporter: externalObjects: {} diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents.meta b/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents.meta index b0f0183..2960afa 100644 --- a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents.meta +++ b/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 4fd2db5d630fd43298e453e00f5bf4b6 +guid: 19150ffd969374afd9b181cd8c71f662 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/Info.plist b/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/Info.plist index 2178f33..cadf633 100644 --- a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/Info.plist +++ b/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/Info.plist @@ -3,7 +3,7 @@ BuildMachineOSBuild - 22D68 + 23G93 CFBundleDevelopmentRegion en CFBundleExecutable @@ -27,21 +27,21 @@ DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild - 14C18 + DTPlatformName macosx DTPlatformVersion - 13.1 + 14.5 DTSDKBuild - 22C55 + 23F73 DTSDKName - macosx13.1 + macosx14.5 DTXcode - 1420 + 1540 DTXcodeBuild - 14C18 + 15F31d LSMinimumSystemVersion - 10.10 + 10.11 NSHumanReadableCopyright Copyright © 2017年 OneV's Den. All rights reserved. diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/Info.plist.meta b/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/Info.plist.meta deleted file mode 100644 index badb65e..0000000 --- a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/Info.plist.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 1a152c5424cf14075ba6d3833802d47c -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/MacOS.meta b/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/MacOS.meta index 31ab75c..10b32ad 100644 --- a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/MacOS.meta +++ b/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/MacOS.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 9e7db360725374b64b0ec3f96cce3fd3 +guid: 495312126946747a3b197461eaff9687 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/MacOS/UniWebView b/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/MacOS/UniWebView old mode 100755 new mode 100644 index a6226f3..cbc62a9 Binary files a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/MacOS/UniWebView and b/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/MacOS/UniWebView differ diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/MacOS/UniWebView.meta b/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/MacOS/UniWebView.meta deleted file mode 100644 index b4124e3..0000000 --- a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/MacOS/UniWebView.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: b22bec296e7344274920d89d60046b86 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/_CodeSignature.meta b/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/_CodeSignature.meta index 19041c7..ee53f06 100644 --- a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/_CodeSignature.meta +++ b/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/_CodeSignature.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 80556f40493b8401b9ed41de8942b552 +guid: 80fcb824c01d44613803213f4b1ed096 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/iOS.meta b/Runtime/GuruWebview/UniWebView5/Plugins/iOS.meta index d3cd1d8..c20e133 100644 --- a/Runtime/GuruWebview/UniWebView5/Plugins/iOS.meta +++ b/Runtime/GuruWebview/UniWebView5/Plugins/iOS.meta @@ -1,9 +1,8 @@ fileFormatVersion: 2 -guid: 921d907cf956b4498afa0a728241d961 +guid: 10cf9bec133da4d3b836f29204deca1b folderAsset: yes -timeCreated: 1489325641 -licenseType: Free DefaultImporter: + externalObjects: {} userData: assetBundleName: assetBundleVariant: diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/iOS/libUniWebView.a b/Runtime/GuruWebview/UniWebView5/Plugins/iOS/libUniWebView.a index ec3199d..7875aed 100644 Binary files a/Runtime/GuruWebview/UniWebView5/Plugins/iOS/libUniWebView.a and b/Runtime/GuruWebview/UniWebView5/Plugins/iOS/libUniWebView.a differ diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/iOS/libUniWebView.a.meta b/Runtime/GuruWebview/UniWebView5/Plugins/iOS/libUniWebView.a.meta index 2158153..a2bfd70 100644 --- a/Runtime/GuruWebview/UniWebView5/Plugins/iOS/libUniWebView.a.meta +++ b/Runtime/GuruWebview/UniWebView5/Plugins/iOS/libUniWebView.a.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: c7db402cd440f49ce97da3366b3cb653 +guid: 060e1768692e941c2aa0f7c9f9f10e32 PluginImporter: externalObjects: {} serializedVersion: 2 @@ -11,24 +11,6 @@ PluginImporter: isExplicitlyReferenced: 0 validateReferences: 1 platformData: - - first: - : Any - second: - enabled: 0 - settings: - Exclude Android: 1 - Exclude Editor: 1 - Exclude Linux64: 1 - Exclude OSXUniversal: 1 - Exclude Win: 1 - Exclude Win64: 1 - Exclude iOS: 0 - - first: - Android: Android - second: - enabled: 0 - settings: - CPU: ARMv7 - first: Any: second: @@ -39,42 +21,13 @@ PluginImporter: second: enabled: 0 settings: - CPU: AnyCPU DefaultValueInitialized: true - OS: AnyOS - - first: - Standalone: Linux64 - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: OSXUniversal - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win64 - second: - enabled: 0 - settings: - CPU: None - first: iPhone: iOS second: enabled: 1 settings: AddToEmbeddedBinaries: false - CPU: AnyCPU - CompileFlags: - FrameworkDependencies: userData: assetBundleName: assetBundleVariant: diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView.meta b/Runtime/GuruWebview/UniWebView5/UniWebView.meta index 906f478..772685a 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView.meta @@ -1,8 +1,8 @@ fileFormatVersion: 2 -guid: a1424e00355294877b7da28b5968d7ec +guid: 92454175c62504ab5bf7128917b56362 folderAsset: yes timeCreated: 1490878496 -licenseType: Free +licenseType: Store DefaultImporter: userData: assetBundleName: diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/CHANGELOG.md b/Runtime/GuruWebview/UniWebView5/UniWebView/CHANGELOG.md index 5a1203f..5c3b5e9 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/CHANGELOG.md +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/CHANGELOG.md @@ -1,10 +1,246 @@ # Release Note -### 5.0.3 +### 5.11.1 (12 Sep, 2024) + +#### Fix + +* A build issue when running under Unity Editor on Windows when the target is not iOS. It was introduced by 5.11.0. + +### 5.11.0 (10 Sep, 2024) + +#### Add + +* Support setting an HTTPS link (universal link for iOS or app link for Android) as the callback URL of an OAuth 2.0 flow. (Before, only the custom scheme was supported) +* iOS 18 & Xcode 16 build support. + +### 5.10.2 (3 Aug, 2024) + +#### Fix + +* Fix a potential freeze of the game when calling cookie related methods on iOS in some Unity versions. + +### 5.10.1 (21 Jun, 2024) + +#### Fix + +* Fixed a bug where certain comment lines were unexpectedly removed while patching Gradle files. +* Resolved an issue where the `OnLoadingErrorReceived` event (along with the deprecated `OnPageErrorReceived` event) was triggered twice during a loading error on certain Android versions. + +### 5.10.0 (9 Jun, 2024) + +#### Add + +* A `RestoreViewHierarchyOnResume` property to make the web view restore its view hierarchy when the app resumes from background on Android. This is an issue in these Unity versions: from Unity 2021.3.31 to 2021.3.34, from Unity 2022.3.10 to 2022.3.15. If you are using UniWebView in these versions, you need to set this value to `true` after upgrading. + +#### Fix + +* Some internal improvements. + +### 5.9.2 (10 Apr, 2024) + +#### Fix + +* An issue that missing the x86_64 slice for UniWebView on macOS. Now the macOS version can run on Unity editor under x86_64 mode again. + +### 5.9.1 (20 Mar, 2024) + +#### Fix + +* A compiling error of Android build system under Unity 2023.2.13 or later. This was due to Unity removed the related APIs in the new version. Now UniWebView reverts to use the old way (the same before UniWebView 5.8.0) to patch the Android project files. + +### 5.9.0 (28 Feb, 2024) + +#### Add + +* Now the WebRTC support on Android does not require adding trust domain by code. A prompt window will be shown to the user if the web page tries to access the camera or microphone. +* Now it is possible to customize the permission request handler for WebRTC, such as grant the access request without prompt to the user. Check `RegisterOnRequestMediaCapturePermission` for more information. + +#### Deprecate + +* As `RegisterOnRequestMediaCapturePermission` is introduced, the current `AddPermissionTrustDomain` method is deprecated. Use the new method if you need to grant the permission by code. + +### 5.8.0 (28 Jan, 2024) + +#### Add + +* Supporting of specifying the cache mode used when loading a request. You can now use `SetCacheMode` and pick a `UniWebViewCacheMode` policy to determine whether and how to use the cache data when loading a request. +* Adopt to use `AndroidProjectFilesModifier` to modify the exported Android project from Unity 2023.2. This prevents some potential edge issues when exporting the project if another custom Gradle template is used. +* Supporting of the data URI on the page. Now the links starting with `data:` is treated as a valid URL and it trigger a standard downloading process. +* Add a method `SetAllowUserEditFileNameBeforeDownloading` to provide a more customizable way of downloading. By setting it with `false`, UniWebView will skip the file name editing step and use the default file name instead, and start the downloading immediately. + +### 5.7.3 (25 Dec, 2023) + +#### Fix + +* The context menu image downloading now should work correctly on Android 14. +* An issue that the downloading URL was converted to lower case when triggering a context menu download on Android. +* Refine the gradle properties file patcher to not add a trailing new line when patching the file. +* A potential vulnerability scanner warning that the file provider can read files from a wider scope than needed. + +#### Deprecate + +* Completely mark the legacy toolbar related methods as deprecated. Use the new embedded toolbar instead whenever possible. + +### 5.7.2 (4 Dec, 2023) + +#### Fix + +* A potential issue on some Android browser implementations, the OAuth support crashes due to a "null activity handler found!" error. Now UniWebView will use a workaround to prevent the crash. + +### 5.7.1 (24 Nov, 2023) + +#### Fix + +* The `SetDragInteractionEnabled` method also works on Android now. It allows you to disable the drag interaction on Android devices that support drag-and-drop gesture. +* Mark several methods in `UniWebViewAuthenticationFlowCustomize` as `virtual` to allow overriding them in subclasses. +* Now the OAuth 2.0 flow will ignore the letter case when receiving the response code URL from the server. It allows you to register the redirect URL with different letter cases in the OAuth provider and UniWebView. + +### 5.7.0 (25 Oct, 2023) + +#### Add + +* A new `SetForwardWebConsoleToNativeOutput` method which enables forwarding the web console log (such as `console.log` or `console.error`) to the native log output. On iOS, it prints the log to Xcode console, on Android to the Android logcat, and on macOS Editor to the Unity console. This is useful for debugging issues from the web page. +* Now the `OnWebContentProcessTerminated` event will also be invoked when the render process is gone on Android (when the `OnRenderProcessGone` event is raise). This is a new behavior on Android 11 and above and the default handling will only prevent the whole app crash. You need to implement this event and +try to release resources and/or perform a reload to recover. + +#### Fix + +* An issue that the keyboard avoidance behavior on Android is not working properly and contains an undesired offset when the web view is not placed at the top of the screen. + +### 5.6.3 (4 Oct, 2023) + +#### Fix + +* An issue that the web view disappears when switching back to foreground in some newer Unity versions (2021.3.31f1, 2022.3.10f1, 2023.3.0a1). This is a regression of the particular Unity versions when it tries to fix [UUM-30881](https://issuetracker.unity3d.com/issues/android-a-black-screen-appears-for-a-few-seconds-when-returning-to-the-game-from-the-lock-screen-after-idle-time). + +### 5.6.2 (29 Sep, 2023) + +#### Fix + +* Support for Android 14. Solve a crash when setting Android 14 (API Level 34) as the target SDK for the game, and opening a web view on Android 14 devices. + +### 5.6.1 (9 Sep, 2023) + +#### Fix + +* A compile error when using UniWebView on Windows Editor was introduced in 5.6.0. Now you should be able to compile the project on Windows Editor. Mac Editor is not affected. + +### 5.6.0 (8 Sep, 2023) + +#### Add + +* New feature of rendering the web view to a texture. Now you can use `CreateRenderedTexture` to render the web view to a texture and use it in the game world. Check [this guide](https://docs.uniwebview.com/guide/render-to-texture.html) for more information and the implementation steps. + +#### Fix + +* Replace using of `NameValueCollection` in OAuth flows to prevent potential issues with different .Net versions. +* An issue that a crash may happen when adding downloading URL or MIME type with `UniWebViewDownloadMatchingType.RegularExpression` option. + + +### 5.5.1 (29 Aug, 2023) + +#### Fix + +* An issue that causes OAuth 2.0 flow crash on some browser implementations (such as Firefox and Amazon Silk) which does not provide the correct support. Chrome and some other browsers are not affected. You need a clean build after upgrading to get this fix. + +### 5.5.0 (6 Aug, 2023) + +#### Add + +* A new `OnLoadingErrorReceived` event, which is triggered when the web view loading encounters an error. This replaces the original `OnPageErrorReceived` event. The new event contains an additional `payload` parameter, in which some error detail (such as the failing URL) is contained. +* Add a build option for adding `androidx.core` package in UniWebView's preference panel. This should fix the problem of crashing due to missing class `FileProvider` on Android 11 or above on Android when disabling the `androidx.browser` package. For more about building issues on Android, we also prepared a new [trouble shooting guide page](https://docs.uniwebview.com/guide/trouble-shooting.html). + +#### Fix + +* A potential issue that cookie values sometimes are not treated correctly when requesting following resource request inside an iframe target. + +#### Deprecate + +* `OnPageErrorReceived` is now deprecated. Use `OnLoadingErrorReceived` instead. + +### 5.4.3 (26 Jul, 2023) + +#### Fix + +* A potential issue that code obfuscation on Android might cause class conflicting with other plugins which also perform a full obfuscation. Now UniWebView uses a more stable way to obfuscate its native code. +* A bug that `OnOrientationChanged` event might be called too early in some cases, which causes the received value is not yet correct. + +### 5.4.2 (9 Jun, 2023) + +#### Fix + +* The method `AddDownloadMIMEType` did not work as expected even when the target MIME type is detected. Now it should trigger the download as expected. + +### 5.4.1 (17 May, 2023) + +#### Fix + +* An issue that the web view crashes on some Android devices when there is no loading callback is registered under Release mode. This was a regression introduced in 5.4.0. If you are using 5.4.0 and has this crash, please upgrade to 5.4.1 to get a fix. + +### 5.4.0 (13 May, 2023) + +#### Add + +* A method to control whether a loading should be started or not. Use `RegisterShouldHandleRequest` to register a callback and returns whether the loading should be started or not. Check [its reference](https://docs.uniwebview.com/api/#registershouldhandlerequest) for more. + +#### Fix + +* Now web view on iOS 16.4 can also be inspected. However, different from previous versions, you need to call `SetWebContentsDebuggingEnabled` to enable the debugging mode before inspecting the web view on all platforms. + +### 5.3.2 (5 Apr, 2023) + +#### Fix + +* Strip using of methods from `System.Web.HttpUtility` internally to resolve a runtime issue in older Unity versions. + +### 5.3.1 (18 Feb, 2023) + +#### Fix + +* An issue that `SetOpenLinksInExternalBrowser` setting is ignored for links with "target=_blank" attribute. Now the external browser will be used for such links instead of opening the page in place. If `SetOpenLinksInExternalBrowser` and `SetSupportMultipleWindows` is set to `true` at the same time, the external browser will be used. +* Now the activity for OAuth 2.0 flow on Android allows orientation changes. It won't throw an exception anymore when the device is rotated during the flow. + +### 5.3.0 (28 Jan, 2023) + +#### Add + +* Support for customization of Kotlin and Android Browser package versions. This can help to resolve the conflict with other plugins which use another version of these packages. + +#### Fix + +* Improve the visual effect of the Embedded Toolbar on iOS. Now it has a larger and bold font for the title. +* A better way to find the root view controller for adding UniWebView to on iOS. Now you can implement a `rootViewControllerForUniWebView` method in your app delegate and return the view controller you want UniWebView to be added to. By default, if the app delegate does not implement either `rootViewControllerForUniWebView` or `rootViewController`, UniWebView now also tries to find the top-most view controller instead of the window's direct root view controller. + +### 5.2.1 (4 Jan, 2023) + +#### Fix + +* A crash when an SSL error is encountered during loading a scheme other than "http" or "https" (for example, "wss://" links) on Android. + +### 5.2.0 (23 Dec, 2022) + +#### Add + +* Most of the OAuth flows now support refresh token. Use `StartRefreshTokenFlow` on the flow to start token refresh. For more about OAuth 2.0 support in UniWebView, check [this guide](https://docs.uniwebview.com/guide/oauth2.html#refresh-token). + +#### Fix + +* A workaround for Facebook login on Android. Facebook is preventing a web view to perform OAuth login on Android, now the `UniWebViewAuthenticationFlowFacebook` will use a desktop browser user-agent to open the login page. +* A crash that caused by orientation change when the camera is presented on Android while the game is landscape but the camera is portrait. +* A crash when taking screenshot when the web view has a zero or negative size. Now under this case, the `CaptureSnapshot` method will give an error `-1002` in its `OnCaptureSnapshotFinished` event. + +### 5.1.0 (21 Nov, 2022) + +#### Add + +* New methods to control the behavior of the loading spinner. Use `SetAllowUserDismissSpinner` to allow or forbid users dismissing the loading indicator; Use `ShowSpinner` and `HideSpinner` to control its visibility programmatically. +* A new API `SetLimitsNavigationsToAppBoundDomains`, it is a wrapper of `limitsNavigationsToAppBoundDomains` for iOS. It limits user's navigation to only pre-defined domains on iOS. #### Fix * Improve the delay that on Android the received cookies are not flushed fast enough in some cases. Now a forcibly cookie flush is always performed when closing the web view. +* The Unity InputSystem assembly reference is added explicitly to prevent compiling error on Unity's versions without InputSystem support. +* An issue prevents the package built on macOS 13 SDK. ### 5.0.2 (24 Oct, 2022) diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/CHANGELOG.md.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/CHANGELOG.md.meta index d4c364b..63a59ca 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/CHANGELOG.md.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/CHANGELOG.md.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: f7ee8c3eb642344b08f4ceeb53d70cd1 timeCreated: 1497057465 -licenseType: Free +licenseType: Store DefaultImporter: userData: assetBundleName: diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Demo.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Demo.meta index 7f3c2c8..d0394e6 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Demo.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Demo.meta @@ -2,7 +2,7 @@ fileFormatVersion: 2 guid: 565d674726c3d499cade2709dd955ad0 folderAsset: yes timeCreated: 1536753564 -licenseType: Free +licenseType: Store DefaultImporter: userData: assetBundleName: diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Demo/UniWebViewDemo.unity.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Demo/UniWebViewDemo.unity.meta index 2b90c5f..2683ee3 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Demo/UniWebViewDemo.unity.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Demo/UniWebViewDemo.unity.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: 938c077f40a814d4584ce2ad17947cf3 timeCreated: 1536753573 -licenseType: Free +licenseType: Store DefaultImporter: userData: assetBundleName: diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/AndroidManifest.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/AndroidManifest.cs index dcc396d..9340e6d 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/AndroidManifest.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/AndroidManifest.cs @@ -1,17 +1,14 @@ using System.Xml; -using System.Collections; using System.Text; -using System.IO; using System; -using UnityEditor; using UnityEngine; internal class UniWebViewAndroidXmlDocument : XmlDocument { - private string path; - protected XmlNamespaceManager nameSpaceManager; - public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android"; + private readonly string path; + protected readonly XmlNamespaceManager nameSpaceManager; + protected const string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android"; - public UniWebViewAndroidXmlDocument(string path) { + protected UniWebViewAndroidXmlDocument(string path) { this.path = path; using (var reader = new XmlTextReader(path)) { reader.Read(); @@ -21,26 +18,25 @@ internal class UniWebViewAndroidXmlDocument : XmlDocument { nameSpaceManager.AddNamespace("android", AndroidXmlNamespace); } - public string Save() { - return SaveAs(path); + public void Save() { + SaveAs(path); } - public string SaveAs(string path) { - using (var writer = new XmlTextWriter(path, new UTF8Encoding(false))) { - writer.Formatting = Formatting.Indented; - Save(writer); - } - return path; + private void SaveAs(string path) + { + using var writer = new XmlTextWriter(path, new UTF8Encoding(false)); + writer.Formatting = Formatting.Indented; + Save(writer); } } internal class UniWebViewAndroidManifest : UniWebViewAndroidXmlDocument { - private readonly XmlElement ManifestElement; - private readonly XmlElement ApplicationElement; + private readonly XmlElement manifestElement; + private readonly XmlElement applicationElement; public UniWebViewAndroidManifest(string path) : base(path) { - ManifestElement = SelectSingleNode("/manifest") as XmlElement; - ApplicationElement = SelectSingleNode("/manifest/application") as XmlElement; + manifestElement = SelectSingleNode("/manifest") as XmlElement; + applicationElement = SelectSingleNode("/manifest/application") as XmlElement; } private XmlAttribute CreateAndroidAttribute(string key, string value) { @@ -58,17 +54,24 @@ internal class UniWebViewAndroidManifest : UniWebViewAndroidXmlDocument { } internal bool SetUsesCleartextTraffic() { - bool changed = false; - if (ApplicationElement.GetAttribute("usesCleartextTraffic", AndroidXmlNamespace) != "true") { - ApplicationElement.SetAttribute("usesCleartextTraffic", AndroidXmlNamespace, "true"); + var changed = false; + if (applicationElement.GetAttribute("usesCleartextTraffic", AndroidXmlNamespace) != "true") { + applicationElement.SetAttribute("usesCleartextTraffic", AndroidXmlNamespace, "true"); changed = true; } return changed; } internal bool SetHardwareAccelerated() { - bool changed = false; + var changed = false; var activity = GetActivityWithLaunchIntent() as XmlElement; + if (activity == null) + { + Debug.LogError( + "There is no launch intent activity in the AndroidManifest.xml." + + " Please check your AndroidManifest.xml file and make sure it has a main activity with intent filter"); + return false; + } if (activity.GetAttribute("hardwareAccelerated", AndroidXmlNamespace) != "true") { activity.SetAttribute("hardwareAccelerated", AndroidXmlNamespace, "true"); changed = true; @@ -77,17 +80,22 @@ internal class UniWebViewAndroidManifest : UniWebViewAndroidXmlDocument { } internal bool AddCameraPermission() { - bool changed = false; - if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.CAMERA']", nameSpaceManager).Count == 0) { + var changed = false; + var cameraPermission = "/manifest/uses-permission[@android:name='android.permission.CAMERA']"; + var cameraPermissionNode = SelectNodes(cameraPermission, nameSpaceManager); + if (cameraPermissionNode == null || cameraPermissionNode.Count == 0) { var elem = CreateElement("uses-permission"); elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.CAMERA")); - ManifestElement.AppendChild(elem); + manifestElement.AppendChild(elem); changed = true; } - if (SelectNodes("/manifest/uses-feature[@android:name='android.hardware.camera']", nameSpaceManager).Count == 0) { + + var hardwareCamera = "/manifest/uses-feature[@android:name='android.hardware.camera']"; + var hardwareCameraNode = SelectNodes(hardwareCamera, nameSpaceManager); + if (hardwareCameraNode == null || hardwareCameraNode.Count == 0) { var elem = CreateElement("uses-feature"); elem.Attributes.Append(CreateAndroidAttribute("name", "android.hardware.camera")); - ManifestElement.AppendChild(elem); + manifestElement.AppendChild(elem); changed = true; } return changed; @@ -95,80 +103,96 @@ internal class UniWebViewAndroidManifest : UniWebViewAndroidXmlDocument { internal bool AddMicrophonePermission() { bool changed = false; - if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.MICROPHONE']", nameSpaceManager).Count == 0) { + var microphonePermission = "/manifest/uses-permission[@android:name='android.permission.MICROPHONE']"; + var microphonePermissionNode = SelectNodes(microphonePermission, nameSpaceManager); + if (microphonePermissionNode == null || microphonePermissionNode.Count == 0) { var elem = CreateElement("uses-permission"); elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.MICROPHONE")); - ManifestElement.AppendChild(elem); + manifestElement.AppendChild(elem); changed = true; } - if (SelectNodes("/manifest/uses-feature[@android:name='android.hardware.microphone']", nameSpaceManager).Count == 0) { + + var microphoneHardware = "/manifest/uses-feature[@android:name='android.hardware.microphone']"; + var microphoneHardwareNode = SelectNodes(microphoneHardware, nameSpaceManager); + if (microphoneHardwareNode == null || microphoneHardwareNode.Count == 0) { var elem = CreateElement("uses-feature"); elem.Attributes.Append(CreateAndroidAttribute("name", "android.hardware.microphone")); - ManifestElement.AppendChild(elem); + manifestElement.AppendChild(elem); changed = true; } return changed; } internal bool AddReadExternalStoragePermission() { - bool changed = false; - if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.READ_EXTERNAL_STORAGE']", nameSpaceManager).Count == 0) { + var changed = false; + var externalPermission = "/manifest/uses-permission[@android:name='android.permission.READ_EXTERNAL_STORAGE']"; + var externalNode = SelectNodes(externalPermission, nameSpaceManager); + if (externalNode == null || externalNode.Count == 0) { var elem = CreateElement("uses-permission"); elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.READ_EXTERNAL_STORAGE")); - ManifestElement.AppendChild(elem); + manifestElement.AppendChild(elem); changed = true; } return changed; } internal bool AddWriteExternalStoragePermission() { - bool changed = false; - if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.WRITE_EXTERNAL_STORAGE']", nameSpaceManager).Count == 0) { + var changed = false; + var externalPermission = "/manifest/uses-permission[@android:name='android.permission.WRITE_EXTERNAL_STORAGE']"; + var externalNode = SelectNodes(externalPermission, nameSpaceManager); + if (externalNode == null || externalNode.Count == 0) { var elem = CreateElement("uses-permission"); elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.WRITE_EXTERNAL_STORAGE")); - ManifestElement.AppendChild(elem); + manifestElement.AppendChild(elem); changed = true; } return changed; } internal bool AddAccessFineLocationPermission() { - bool changed = false; - if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.ACCESS_FINE_LOCATION']", nameSpaceManager).Count == 0) { + var changed = false; + var locationPermission = "/manifest/uses-permission[@android:name='android.permission.ACCESS_FINE_LOCATION']"; + var locationNode = SelectNodes(locationPermission, nameSpaceManager); + if (locationNode == null || locationNode.Count == 0) { var elem = CreateElement("uses-permission"); elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.ACCESS_FINE_LOCATION")); - ManifestElement.AppendChild(elem); + manifestElement.AppendChild(elem); changed = true; } return changed; } internal bool AddAuthCallbacksIntentFilter(string[] authCallbackUrls) { - bool changed = false; + var changed = false; XmlElement authActivityNode; if (authCallbackUrls.Length > 0) { - var list = SelectNodes("/manifest/application/activity[@android:name='com.onevcat.uniwebview.UniWebViewAuthenticationActivity']", nameSpaceManager); - if (list.Count == 0) { + var authActivity = "/manifest/application/activity[@android:name='com.onevcat.uniwebview.UniWebViewAuthenticationActivity']"; + var list = SelectNodes(authActivity, nameSpaceManager); + if (list == null || list.Count == 0) { var created = CreateElement("activity"); created.SetAttribute("name", AndroidXmlNamespace, "com.onevcat.uniwebview.UniWebViewAuthenticationActivity"); created.SetAttribute("exported", AndroidXmlNamespace, "true"); - created.SetAttribute("launchMode", AndroidXmlNamespace, "singleTop"); + created.SetAttribute("launchMode", AndroidXmlNamespace, "singleTask"); + created.SetAttribute("configChanges", AndroidXmlNamespace, "orientation|screenSize|keyboardHidden"); authActivityNode = created; } else { authActivityNode = list[0] as XmlElement; } } else { - return changed; + return false; } foreach (var url in authCallbackUrls) { var intentFilter = CreateIntentFilter(url); if (intentFilter != null) { - authActivityNode.AppendChild(intentFilter); + authActivityNode?.AppendChild(intentFilter); changed = true; } } - ApplicationElement.AppendChild(authActivityNode); + + if (authActivityNode != null) { + applicationElement.AppendChild(authActivityNode); + } return changed; } @@ -176,12 +200,22 @@ internal class UniWebViewAndroidManifest : UniWebViewAndroidXmlDocument { var uri = new Uri(url); var scheme = uri.Scheme; - if (String.IsNullOrEmpty(scheme)) { + if (string.IsNullOrEmpty(scheme)) { Debug.LogError(" Auth callback url contains an empty scheme. Please check the url: " + url); return null; } var filter = CreateElement("intent-filter"); + + // Check if the scheme is HTTPS + // For Android App Links, we need to set the autoVerify attribute to true + // This allows Android to automatically verify the association between the app and the website + // Note: Only applies to HTTPS URLs, although Android App Links also support HTTP, we do not want it in + // UniWebView. + // https://developer.android.com/training/app-links + if (scheme == "https") { + filter.SetAttribute("autoVerify", AndroidXmlNamespace, "true"); + } var action = CreateElement("action"); action.SetAttribute("name", AndroidXmlNamespace, "android.intent.action.VIEW"); @@ -201,9 +235,13 @@ internal class UniWebViewAndroidManifest : UniWebViewAndroidXmlDocument { data.SetAttribute("host", AndroidXmlNamespace, uri.Host); } if (uri.Port != -1) { - data.SetAttribute("port", AndroidXmlNamespace, uri.Port.ToString()); + // For HTTPS, the default port is 443. We only need to set the port explicitly + // if we're not using HTTPS or if we're using a non-standard HTTPS port. + if (scheme != "https" || uri.Port != 443) { + data.SetAttribute("port", AndroidXmlNamespace, uri.Port.ToString()); + } } - if (!String.IsNullOrEmpty(uri.PathAndQuery) && uri.PathAndQuery != "/") { + if (!string.IsNullOrEmpty(uri.PathAndQuery) && uri.PathAndQuery != "/") { data.SetAttribute("path", AndroidXmlNamespace, uri.PathAndQuery); } diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/BuildGradle.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/BuildGradle.cs index 1af5fcb..55515e8 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/BuildGradle.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/BuildGradle.cs @@ -6,28 +6,23 @@ using System; public class UniWebViewGradleConfig { - private UniWebViewGradleNode m_root; - private String m_filePath; - private UniWebViewGradleNode m_curNode; + private readonly string m_filePath; - public UniWebViewGradleNode ROOT - { - get { return m_root; } - } + public UniWebViewGradleNode Root { get; } public UniWebViewGradleConfig(string filePath) { - string file = File.ReadAllText(filePath); + var file = File.ReadAllText(filePath); TextReader reader = new StringReader(file); m_filePath = filePath; - m_root = new UniWebViewGradleNode("root"); - m_curNode = m_root; + Root = new UniWebViewGradleNode("root"); + var curNode = Root; - StringBuilder str = new StringBuilder(); - bool inDoubleQuote = false; - bool inSingleQuote = false; - bool inDollarVariable = false; + var str = new StringBuilder(); + var inDoubleQuote = false; + var inSingleQuote = false; + var inDollarVariable = false; while (reader.Peek() > 0) { @@ -40,7 +35,7 @@ public class UniWebViewGradleConfig // reader.Read(); // string comment = reader.ReadLine(); // Debug.Log("Comment line: " + comment); - // m_curNode.AppendChildNode(new UniWebViewGradleCommentNode(comment, m_curNode)); + // curNode.AppendChildNode(new UniWebViewGradleCommentNode(comment, curNode)); // } // else // { @@ -53,7 +48,7 @@ public class UniWebViewGradleConfig var strf = FormatStr(str); if (!string.IsNullOrEmpty(strf)) { - m_curNode.AppendChildNode(new UniWebViewGradleContentNode(strf, m_curNode)); + curNode.AppendChildNode(new UniWebViewGradleContentNode(strf, curNode)); } } inDollarVariable = false; @@ -77,9 +72,9 @@ public class UniWebViewGradleConfig var n = FormatStr(str); if (!string.IsNullOrEmpty(n)) { - UniWebViewGradleNode node = new UniWebViewGradleNode(n, m_curNode); - m_curNode.AppendChildNode(node); - m_curNode = node; + UniWebViewGradleNode node = new UniWebViewGradleNode(n, curNode); + curNode.AppendChildNode(node); + curNode = node; } } str = new StringBuilder(); @@ -93,9 +88,9 @@ public class UniWebViewGradleConfig var strf = FormatStr(str); if (!string.IsNullOrEmpty(strf)) { - m_curNode.AppendChildNode(new UniWebViewGradleContentNode(strf, m_curNode)); + curNode.AppendChildNode(new UniWebViewGradleContentNode(strf, curNode)); } - m_curNode = m_curNode.PARENT; + curNode = curNode.Parent; } str = new StringBuilder(); break; @@ -131,27 +126,36 @@ public class UniWebViewGradleConfig } } + // End of file. + var endline = FormatStr(str); + if (!string.IsNullOrEmpty(endline)) + { + curNode.AppendChildNode(new UniWebViewGradleContentNode(endline, curNode)); + } //Debug.Log("Gradle parse done!"); } public void Save(string path = null) { - if (path == null) + if (path == null) { path = m_filePath; + } + File.WriteAllText(path, Print()); - //Debug.Log("Gradle parse done! " + path); } - private string FormatStr(StringBuilder sb) + private static string FormatStr(StringBuilder sb) { - string str = sb.ToString(); + var str = sb.ToString(); str = str.Trim(); return str; } public string Print() { StringBuilder sb = new StringBuilder(); - printNode(sb, m_root, -1); + PrintNode(sb, Root, -1); + // Remove the first empty line. + sb.Remove(0, 1); return sb.ToString(); } private string GetLevelIndent(int level) @@ -164,69 +168,52 @@ public class UniWebViewGradleConfig } return sb.ToString(); } - private void printNode(StringBuilder stringBuilder, UniWebViewGradleNode node, int level) + private void PrintNode(StringBuilder stringBuilder, UniWebViewGradleNode node, int level) { - if (node.PARENT != null) - { + if (node.Parent != null) { if (node is UniWebViewGradleCommentNode) { - stringBuilder.Append("\n" + GetLevelIndent(level) + @"//" + node.NAME); + stringBuilder.Append("\n" + GetLevelIndent(level) + @"//" + node.Name); } else { - stringBuilder.Append("\n" + GetLevelIndent(level) + node.NAME); + stringBuilder.Append("\n" + GetLevelIndent(level) + node.Name); } } - if (!(node is UniWebViewGradleContentNode) && !(node is UniWebViewGradleCommentNode)) - { - if (node.PARENT != null) - { - stringBuilder.Append(" {"); - } - foreach (var c in node.CHILDREN) - { - printNode(stringBuilder, c, level + 1); - } - if (node.PARENT != null) - { - stringBuilder.Append("\n" + GetLevelIndent(level) + "}"); - } + if (node is UniWebViewGradleContentNode || node is UniWebViewGradleCommentNode) return; + if (node.Parent != null) { + stringBuilder.Append(" {"); + } + foreach (var c in node.Children) { + PrintNode(stringBuilder, c, level + 1); + } + if (node.Parent != null) { + stringBuilder.Append("\n" + GetLevelIndent(level) + "}"); } } } public class UniWebViewGradleNode { - protected List m_children = new List(); - protected UniWebViewGradleNode m_parent; - protected String m_name; - public UniWebViewGradleNode PARENT - { - get { return m_parent; } - } + protected string m_name; + public UniWebViewGradleNode Parent { get; private set; } - public string NAME - { - get { return m_name; } - } + public string Name => m_name; - public List CHILDREN - { - get { return m_children; } - } + public List Children { get; private set; } = new List(); public UniWebViewGradleNode(string name, UniWebViewGradleNode parent = null) { - m_parent = parent; + Parent = parent; m_name = name; } public void Each(Action f) { f(this); - foreach (var n in m_children) + foreach (var n in Children) { n.Each(f); } @@ -234,20 +221,19 @@ public class UniWebViewGradleNode public void AppendChildNode(UniWebViewGradleNode node) { - if (m_children == null) m_children = new List(); - m_children.Add(node); - node.m_parent = this; + if (Children == null) Children = new List(); + Children.Add(node); + node.Parent = this; } public UniWebViewGradleNode TryGetNode(string path) { - string[] subpath = path.Split('/'); - UniWebViewGradleNode cnode = this; - for (int i = 0; i < subpath.Length; i++) + var subpath = path.Split('/'); + var cnode = this; + foreach (var p in subpath) { - var p = subpath[i]; if (string.IsNullOrEmpty(p)) continue; - UniWebViewGradleNode tnode = cnode.FindChildNodeByName(p); + var tnode = cnode.FindChildNodeByName(p); if (tnode == null) { Debug.Log("Can't find Node:" + p); @@ -255,7 +241,6 @@ public class UniWebViewGradleNode } cnode = tnode; - tnode = null; } return cnode; @@ -263,22 +248,22 @@ public class UniWebViewGradleNode public UniWebViewGradleNode FindChildNodeByName(string name) { - foreach (var n in m_children) + foreach (var n in Children) { if (n is UniWebViewGradleCommentNode || n is UniWebViewGradleContentNode) continue; - if (n.NAME == name) + if (n.Name == name) return n; } return null; } - public bool ReplaceContenStartsWith(string patten, string value) + public bool ReplaceContentStartsWith(string pattern, string value) { - foreach (var n in m_children) + foreach (var n in Children) { if (!(n is UniWebViewGradleContentNode)) continue; - if (n.m_name.StartsWith(patten)) + if (n.m_name.StartsWith(pattern)) { n.m_name = value; return true; @@ -287,12 +272,12 @@ public class UniWebViewGradleNode return false; } - public UniWebViewGradleContentNode ReplaceContenOrAddStartsWith(string patten, string value) + public UniWebViewGradleContentNode ReplaceContentOrAddStartsWith(string pattern, string value) { - foreach (var n in m_children) + foreach (var n in Children) { if (!(n is UniWebViewGradleContentNode)) continue; - if (n.m_name.StartsWith(patten)) + if (n.m_name.StartsWith(pattern)) { n.m_name = value; return (UniWebViewGradleContentNode)n; @@ -300,15 +285,10 @@ public class UniWebViewGradleNode } return AppendContentNode(value); } - - /// - /// 添加子节点 - /// - /// - /// + public UniWebViewGradleContentNode AppendContentNode(string content) { - foreach (var n in m_children) + foreach (var n in Children) { if (!(n is UniWebViewGradleContentNode)) continue; if (n.m_name == content) @@ -325,12 +305,12 @@ public class UniWebViewGradleNode public bool RemoveContentNode(string contentPattern) { - for(int i=0;i text.Contains("android.useAndroidX")); - bool hasJetifierProperty = lines.Any(text => text.Contains("android.enableJetifier")); + // Construct a patcher with file path. + public UniWebViewGradlePropertyPatcher(string filePath, UniWebViewEditorSettings settings) + { + this.filePath = filePath; + this.settings = settings; + } + + public void Patch() + { + var result = UpdatedString(); + File.WriteAllText(filePath, result); + } - StringBuilder builder = new StringBuilder(); + public string UpdatedString() + { + var lines = File.ReadAllLines(filePath); - foreach(string each in lines) { + var hasAndroidXProperty = lines.Any(text => text.Contains("android.useAndroidX")); + var hasJetifierProperty = lines.Any(text => text.Contains("android.enableJetifier")); + + var builder = new StringBuilder(); + + foreach(var each in lines) { builder.AppendLine(each); } - + if (!hasAndroidXProperty) { builder.AppendLine("android.useAndroidX=true"); } - if (!hasJetifierProperty && UniWebViewEditorSettings.GetOrCreateSettings().enableJetifier) { + if (!hasJetifierProperty && settings.enableJetifier) { builder.AppendLine("android.enableJetifier=true"); } - File.WriteAllText(filePath, builder.ToString()); + // AppendLine will add a new line at the end of the string, so we need to trim it to keep the file clean. + return builder.ToString().Trim(); } } \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/UniWebViewEditorSettings.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/UniWebViewEditorSettings.cs index f5bc626..cc9c016 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/UniWebViewEditorSettings.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/UniWebViewEditorSettings.cs @@ -4,9 +4,9 @@ using System.Collections.Generic; using System; using System.IO; -class UniWebViewEditorSettings: ScriptableObject +public class UniWebViewEditorSettings: ScriptableObject { - const string assetPath = "Assets/Guru/Editor/UniWebView/settings.asset"; + private const string AssetPath = "Assets/Editor/UniWebView/settings.asset"; [SerializeField] internal bool usesCleartextTraffic = false; @@ -20,9 +20,21 @@ class UniWebViewEditorSettings: ScriptableObject [SerializeField] internal bool addsKotlin = true; + [SerializeField] + internal string kotlinVersion = null; + [SerializeField] internal bool addsAndroidBrowser = true; + [SerializeField] + internal string androidBrowserVersion = null; + + [SerializeField] + internal bool addsAndroidXCore = false; + + [SerializeField] + internal string androidXCoreVersion = null; + [SerializeField] internal bool enableJetifier = true; @@ -32,14 +44,18 @@ class UniWebViewEditorSettings: ScriptableObject [SerializeField] internal bool supportLINELogin = false; + internal static string defaultKotlinVersion = "1.6.21"; + internal static string defaultAndroidBrowserVersion = "1.2.0"; + internal static string defaultAndroidXCoreVersion = "1.5.0"; + internal static UniWebViewEditorSettings GetOrCreateSettings() { - var settings = AssetDatabase.LoadAssetAtPath(assetPath); + var settings = AssetDatabase.LoadAssetAtPath(AssetPath); if (settings == null) { settings = ScriptableObject.CreateInstance(); - Directory.CreateDirectory("Assets/Guru/Editor/UniWebView/"); - AssetDatabase.CreateAsset(settings, assetPath); + Directory.CreateDirectory("Assets/Editor/UniWebView/"); + AssetDatabase.CreateAsset(settings, AssetPath); AssetDatabase.SaveAssets(); } @@ -51,6 +67,24 @@ class UniWebViewEditorSettings: ScriptableObject } } +// UniWebViewEditorSettings is not working well with AndroidProjectFilesModifier. +// (reading it requires main thread, but the OnModifyAndroidProjectFiles is not in main thread) +[Serializable] +public class UniWebViewEditorSettingsReading { + public bool usesCleartextTraffic = false; + public bool writeExternalStorage = false; + public bool accessFineLocation = false; + public bool addsKotlin = true; + public string kotlinVersion = null; + public bool addsAndroidBrowser = true; + public string androidBrowserVersion = null; + public bool addsAndroidXCore = false; + public string androidXCoreVersion = null; + public bool enableJetifier = true; + public string[] authCallbackUrls = { }; + public bool supportLINELogin = false; +} + static class UniWebViewSettingsProvider { static SerializedObject settings; @@ -70,6 +104,7 @@ static class UniWebViewSettingsProvider { #endif static void DrawPref() { EditorGUIUtility.labelWidth = 320; + EditorGUIUtility.fieldWidth = 20; if (settings == null) { settings = UniWebViewEditorSettings.GetSerializedSettings(); } @@ -100,8 +135,40 @@ static class UniWebViewSettingsProvider { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(settings.FindProperty("addsKotlin")); DrawDetailLabel("Turn off this if another library is already adding Kotlin runtime."); + var addingKotlin = settings.FindProperty("addsKotlin").boolValue; + if (addingKotlin) { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(settings.FindProperty("kotlinVersion"), GUILayout.Width(400)); + DrawDetailLabel("If not specified, use the default version: " + UniWebViewEditorSettings.defaultKotlinVersion); + EditorGUI.indentLevel--; + } + EditorGUILayout.PropertyField(settings.FindProperty("addsAndroidBrowser")); DrawDetailLabel("Turn off this if another library is already adding 'androidx.browser:browser'."); + var addingBrowser = settings.FindProperty("addsAndroidBrowser").boolValue; + if (addingBrowser) { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(settings.FindProperty("androidBrowserVersion"), GUILayout.Width(400)); + DrawDetailLabel("If not specified, use the default version: " + UniWebViewEditorSettings.defaultAndroidBrowserVersion); + EditorGUI.indentLevel--; + } + + if (!addingBrowser) { + EditorGUILayout.BeginVertical("box"); + EditorGUILayout.HelpBox("UniWebView at least requires `androidx.core` to run. Without it, your game will crash when launching.\nIf you do not have another `androidx.core` package in the project, enable the option below.", MessageType.Warning); + EditorGUILayout.PropertyField(settings.FindProperty("addsAndroidXCore")); + DrawDetailLabel("Turn on this if you disabled `Adds Android Browser` and there is no other library adding 'androidx.core:core'."); + var addingCore = settings.FindProperty("addsAndroidXCore").boolValue; + if (addingCore) { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(settings.FindProperty("androidXCoreVersion"), GUILayout.Width(400)); + DrawDetailLabel("If not specified, use the default version: " + UniWebViewEditorSettings.defaultAndroidXCoreVersion); + EditorGUI.indentLevel--; + } + EditorGUILayout.EndVertical(); + } + + EditorGUILayout.PropertyField(settings.FindProperty("enableJetifier")); DrawDetailLabel("Turn off this if you do not need Jetifier (for converting other legacy support dependencies to Android X)."); EditorGUI.indentLevel--; diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/UniWebViewPostBuildProcessor.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/UniWebViewPostBuildProcessor.cs index 5c7fecb..0f26ed2 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/UniWebViewPostBuildProcessor.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Editor/UniWebViewPostBuildProcessor.cs @@ -1,15 +1,215 @@ + +// #if UNITY_2023_2_OR_NEWER +#if UNIWEBVIEW_NEW_ANDROID_BUILD_SYSTEM + +using System; +using System.Linq; +using Unity.Android.Gradle; +using Unity.Android.Gradle.Manifest; +using UnityEditor.Android; +using UnityEngine; +using Action = Unity.Android.Gradle.Manifest.Action; +using UnityEditor.iOS.Xcode; + +class UniWebViewPostBuildModifier : AndroidProjectFilesModifier { + + const string ContextSettingsKey = "uniwebview_settings"; + + public override AndroidProjectFilesModifierContext Setup() { + var context = new AndroidProjectFilesModifierContext { + Dependencies = { + DependencyFiles = new[] { + // Set UniWebView editor settings asset path to be included in the build. With that we do not need + // a clean build to update the settings anymore (maybe, not confirmed). + UniWebViewEditorSettings.AssetPath + } + } + }; + + var settings = UniWebViewEditorSettings.GetOrCreateSettings(); + context.SetData(ContextSettingsKey, settings); + return context; + } + + public override void OnModifyAndroidProjectFiles(AndroidProjectFiles projectFiles) { + var settings = projectFiles.GetData(ContextSettingsKey); + PatchUnityLibraryAndroidManifest(projectFiles.UnityLibraryManifest, settings); + PatchUnityLibraryBuildGradle(projectFiles.UnityLibraryBuildGradle, settings); + PatchGradleProperty(projectFiles.GradleProperties, settings); + } + + private void PatchUnityLibraryAndroidManifest( + AndroidManifestFile manifest, UniWebViewEditorSettingsReading settings + ) { + // Set hardwareAccelerated + var launcherActivities = manifest.Manifest.GetActivitiesWithLauncherIntent(); + foreach (var activity in launcherActivities) { + // Required for playing video in web view. + activity.Attributes.HardwareAccelerated.Set(true); + } + + // Set usesCleartextTraffic + if (settings.usesCleartextTraffic) { + manifest.Manifest.Application.Attributes.UsesCleartextTraffic.Set(true); + } + + // Set WRITE_EXTERNAL_STORAGE permission + if (settings.writeExternalStorage) { + AddUsesPermission(manifest, "android.permission.WRITE_EXTERNAL_STORAGE"); + } + + // Set ACCESS_FINE_LOCATION permission + if (settings.accessFineLocation) { + AddUsesPermission(manifest, "android.permission.ACCESS_FINE_LOCATION"); + } + + // Set auth callback intent filter + if (settings.authCallbackUrls.Length > 0 || settings.supportLINELogin) { + var authActivity = new Activity(); + authActivity.Attributes.Name.Set("com.onevcat.uniwebview.UniWebViewAuthenticationActivity"); + authActivity.Attributes.Exported.Set(true); + authActivity.Attributes.LaunchMode.Set(LaunchMode.SingleTask); + authActivity.Attributes.ConfigChanges.Set( + new[] { + ConfigChanges.Orientation, ConfigChanges.ScreenSize, ConfigChanges.KeyboardHidden + }); + + foreach (var url in settings.authCallbackUrls) { + AddAuthCallbacksIntentFilter(authActivity, url); + } + if (settings.supportLINELogin) { + AddAuthCallbacksIntentFilter(authActivity, "lineauth://auth"); + } + manifest.Manifest.Application.ActivityList.AddElement(authActivity); + } + } + + private void PatchUnityLibraryBuildGradle(ModuleBuildGradleFile gradleFile, UniWebViewEditorSettingsReading settings) { + if (settings.addsKotlin) { + var kotlinPrefix = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:"; + var kotlinVersion = String.IsNullOrWhiteSpace(settings.kotlinVersion) + ? UniWebViewEditorSettings.defaultKotlinVersion : settings.kotlinVersion; + ReplaceContentOrAddStartsWith(gradleFile.Dependencies, + kotlinPrefix, + kotlinVersion); + Debug.Log(" Updated Kotlin dependency in build.gradle."); + } + + if (settings.addsAndroidBrowser) { + var browserPrefix = "androidx.browser:browser:"; + var browserVersion = String.IsNullOrWhiteSpace(settings.androidBrowserVersion) + ? UniWebViewEditorSettings.defaultAndroidBrowserVersion : settings.androidBrowserVersion; + ReplaceContentOrAddStartsWith(gradleFile.Dependencies, + browserPrefix, + browserVersion); + Debug.Log(" Updated Browser dependency in build.gradle."); + } + + if (!settings.addsAndroidBrowser && settings.addsAndroidXCore) { + var androidXCorePrefix = "androidx.core:core:"; + var androidXCoreVersion = String.IsNullOrWhiteSpace(settings.androidXCoreVersion) + ? UniWebViewEditorSettings.defaultAndroidXCoreVersion : settings.androidXCoreVersion; + ReplaceContentOrAddStartsWith(gradleFile.Dependencies, + androidXCorePrefix, + androidXCoreVersion); + Debug.Log(" Updated Android X Core dependency in build.gradle."); + } + } + + private void PatchGradleProperty(GradlePropertiesFile file, UniWebViewEditorSettingsReading settings) { + var values = file.GetElements(); + foreach (var ele in values) { + if (ele.GetRaw().Contains("android.enableJetifier")) { + ele.SetRaw("android.enableJetifier=" + (settings.enableJetifier ? "true": "false")); + } + } + } + + private void AddUsesPermission(AndroidManifestFile manifest, string name) { + + var list = manifest.Manifest.UsesPermissionList; + foreach (var item in list) { + Debug.LogError(item.GetName()); + } + + var existing = manifest.Manifest.UsesPermissionList + .FirstOrDefault(ele => ele.Attributes.Name.Get() == name); + if (existing != null) { + return; + } + var permission = new UsesPermission(); + permission.Attributes.Name.Set(name); + manifest.Manifest.UsesPermissionList.AddElement(permission); + } + + private void AddAuthCallbacksIntentFilter(Activity activity, string callbackUrl) { + var uri = new Uri(callbackUrl); + var scheme = uri.Scheme; + + if (String.IsNullOrEmpty(scheme)) { + Debug.LogError(" Auth callback url contains an empty scheme. Please check the url: " + callbackUrl); + return; + } + + var intentFilter = new IntentFilter(); + + var action = new Action(); + action.Attributes.Name.Set("android.intent.action.VIEW"); + intentFilter.ActionList.AddElement(action); + + var defaultCategory = new Category(); + intentFilter.CategoryList.AddElement(defaultCategory); + defaultCategory.Attributes.Name.Set("android.intent.category.DEFAULT"); + + var browsableCategory = new Category(); + browsableCategory.Attributes.Name.Set("android.intent.category.BROWSABLE"); + intentFilter.CategoryList.AddElement(browsableCategory); + + var data = new Data(); + data.Attributes.Scheme.Set(scheme); + if (!String.IsNullOrEmpty(uri.Host)) { + data.Attributes.Host.Set(uri.Host); + } + if (uri.Port != -1) { + data.Attributes.Port.Set(uri.Port.ToString()); + } + if (!String.IsNullOrEmpty(uri.PathAndQuery) && uri.PathAndQuery != "/") { + data.Attributes.Path.Set(uri.PathAndQuery); + } + intentFilter.DataList.AddElement(data); + activity.IntentFilterList.AddElement(intentFilter); + } + + private void ReplaceContentOrAddStartsWith(Dependencies dependencies, string prefix, string version) { + var all = dependencies.GetElements(); + var matching = "implementation '" + prefix; + var found = all.FirstOrDefault(ele => ele.GetRaw().StartsWith(matching)); + if (found != null) { + found.SetRaw($"implementation '{prefix}{version}'"); + } else { + dependencies.AddDependencyImplementationRaw($"'{prefix}{version}'"); + } + } +} + +#else +using System; using UnityEditor; using UnityEditor.Android; using UnityEngine; using System.IO; using System.Text; - +using UnityEditor.Callbacks; +#if UNITY_IOS +using UnityEditor.iOS.Xcode; +#endif +using System.Linq; class UniWebViewPostBuildProcessor : IPostGenerateGradleAndroidProject { public int callbackOrder { get { return 1; } } public void OnPostGenerateGradleAndroidProject(string path) { - Debug.Log(" UniWebView Post Build Scirpt is patching manifest file and gradle file..."); + Debug.Log(" UniWebView Post Build Script is patching manifest file and gradle file..."); PatchAndroidManifest(path); PatchBuildGradle(path); PatchGradleProperty(path); @@ -51,47 +251,66 @@ class UniWebViewPostBuildProcessor : IPostGenerateGradleAndroidProject var gradleFilePath = GetGradleFilePath(root); var config = new UniWebViewGradleConfig(gradleFilePath); + var settings = UniWebViewEditorSettings.GetOrCreateSettings(); + var kotlinPrefix = "implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:"; - var kotlinVersion = "1.6.20'"; + var kotlinVersion = String.IsNullOrWhiteSpace(settings.kotlinVersion) + ? UniWebViewEditorSettings.defaultKotlinVersion : settings.kotlinVersion; var browserPrefix = "implementation 'androidx.browser:browser:"; - var browserVersion = "1.2.0'"; + var browserVersion = String.IsNullOrWhiteSpace(settings.androidBrowserVersion) + ? UniWebViewEditorSettings.defaultAndroidBrowserVersion : settings.androidBrowserVersion; - var settings = UniWebViewEditorSettings.GetOrCreateSettings(); - - var dependenciesNode = config.ROOT.FindChildNodeByName("dependencies"); + var androidXCorePrefix = "implementation 'androidx.core:core:"; + var androidXCoreVersion = String.IsNullOrWhiteSpace(settings.androidXCoreVersion) + ? UniWebViewEditorSettings.defaultAndroidXCoreVersion : settings.androidXCoreVersion; + + var dependenciesNode = config.Root.FindChildNodeByName("dependencies"); if (dependenciesNode != null) { // Add kotlin if (settings.addsKotlin) { - dependenciesNode.ReplaceContenOrAddStartsWith(kotlinPrefix, kotlinPrefix + kotlinVersion); + dependenciesNode.ReplaceContentOrAddStartsWith(kotlinPrefix, kotlinPrefix + kotlinVersion + "'"); Debug.Log(" Updated Kotlin dependency in build.gradle."); } // Add browser package if (settings.addsAndroidBrowser) { - dependenciesNode.ReplaceContenOrAddStartsWith(browserPrefix, browserPrefix + browserVersion); + dependenciesNode.ReplaceContentOrAddStartsWith(browserPrefix, browserPrefix + browserVersion + "'"); Debug.Log(" Updated Browser dependency in build.gradle."); } + + // Add Android X Core package + if (!settings.addsAndroidBrowser && settings.addsAndroidXCore) { + // When adding android browser to the project, we don't need to add Android X Core package, since gradle resolves for it. + dependenciesNode.ReplaceContentOrAddStartsWith(androidXCorePrefix, androidXCorePrefix + androidXCoreVersion + "'"); + Debug.Log(" Updated Android X Core dependency in build.gradle."); + } } else { Debug.LogError("UniWebViewPostBuildProcessor didn't find the `dependencies` field in build.gradle."); Debug.LogError("Although we can continue to add a `dependencies`, make sure you have setup Gradle and the template correctly."); - var newNode = new UniWebViewGradleNode("dependencies", config.ROOT); + var newNode = new UniWebViewGradleNode("dependencies", config.Root); if (settings.addsKotlin) { - newNode.AppendContentNode(kotlinPrefix + kotlinVersion); + newNode.AppendContentNode(kotlinPrefix + kotlinVersion + "'"); } if (settings.addsAndroidBrowser) { - newNode.AppendContentNode(browserPrefix + browserVersion); + newNode.AppendContentNode(browserPrefix + browserVersion + "'"); + } + + if (settings.addsAndroidXCore) { + newNode.AppendContentNode(androidXCorePrefix + androidXCoreVersion + "'"); } newNode.AppendContentNode("implementation(name: 'UniWebView', ext:'aar')"); - config.ROOT.AppendChildNode(newNode); + config.Root.AppendChildNode(newNode); } config.Save(); } private void PatchGradleProperty(string root) { var gradlePropertyFilePath = GetGradlePropertyFilePath(root); - UniWebViewGradlePropertyPatcher.Patch(gradlePropertyFilePath); + var patcher = + new UniWebViewGradlePropertyPatcher(gradlePropertyFilePath, UniWebViewEditorSettings.GetOrCreateSettings()); + patcher.Patch(); } private string CombinePaths(string[] paths) { @@ -120,4 +339,50 @@ class UniWebViewPostBuildProcessor : IPostGenerateGradleAndroidProject #endif return CombinePaths(compos); } -} \ No newline at end of file +} +#endif + +#if UNITY_IOS +public class UniWebViewPostBuildProcessorIOS +{ + [PostProcessBuild(1)] + public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject) + { + if (target == BuildTarget.iOS) { + var projectPath = PBXProject.GetPBXProjectPath(pathToBuiltProject); + + var settings = UniWebViewEditorSettings.GetOrCreateSettings(); + if (settings.authCallbackUrls.Length > 0) { + var domains = GetHttpsAssociatedDomains(settings.authCallbackUrls); + if (domains.Length > 0) { + Debug.Log(" UniWebView Post Build Script is patching associated domains for auth callbacks..."); + AddAssociatedDomain(projectPath, domains); + } + } + } + } + + public static string[] GetHttpsAssociatedDomains(string[] urls) { + return urls + .Where(url => Uri.TryCreate(url, UriKind.Absolute, out Uri uri) && uri.Scheme == "https") + .Select(url => new Uri(url).Host) + .Distinct() + .Select(domain => "applinks:" + domain) + .ToArray(); + } + + public static void AddAssociatedDomain(string projectPath, string[] domains) { + + PBXProject project = new PBXProject(); + project.ReadFromString(File.ReadAllText(projectPath)); + + var entitlementsFileName = "Unity-iPhone.entitlements"; + var targetGUID = project.GetUnityMainTargetGuid(); + var capabilityManager = new ProjectCapabilityManager(projectPath, entitlementsFileName, null, targetGUID); + + capabilityManager.AddAssociatedDomains(domains); + + capabilityManager.WriteToFile(); + } +} +#endif \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface.meta index 807e044..f50095c 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface.meta @@ -2,7 +2,7 @@ fileFormatVersion: 2 guid: dc0f52a2d219347b1a6390b753d6ac97 folderAsset: yes timeCreated: 1491898971 -licenseType: Free +licenseType: Store DefaultImporter: userData: assetBundleName: diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroid.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroid.cs index 7e4515b..2c458a2 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroid.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroid.cs @@ -2,6 +2,17 @@ using UnityEngine; +class UniWebViewMethodChannel: AndroidJavaProxy + { + public UniWebViewMethodChannel() : base("com.onevcat.uniwebview.UniWebViewNativeChannel") { } + + string invokeChannelMethod(string name, string method, string parameters) { + UniWebViewLogger.Instance.Verbose("invokeChannelMethod invoked by native side. Name: " + name + " Method: " + + method + " Params: " + parameters); + return UniWebViewChannelMethodManager.Instance.InvokeMethod(name, method, parameters); + } + } + public class UniWebViewInterface { private static readonly AndroidJavaClass plugin; private static bool correctPlatform = Application.platform == RuntimePlatform.Android; @@ -10,39 +21,15 @@ public class UniWebViewInterface { var go = new GameObject("UniWebViewAndroidStaticListener"); go.AddComponent(); plugin = new AndroidJavaClass("com.onevcat.uniwebview.UniWebViewInterface"); + CheckPlatform(); + plugin.CallStatic("prepare"); + + UniWebViewLogger.Instance.Info("Connecting to native side method channel."); + plugin.CallStatic("registerChannel", new UniWebViewMethodChannel()); } - //OMSDK part------- - - public static void InitOMSDK(string jsonSettings, string omidJSServiceContent) { - CheckPlatform(); - plugin.CallStatic("initOMSDK",jsonSettings,omidJSServiceContent); - } - - public static void InitOMSDKSession(string resourceUrl) { - CheckPlatform(); - plugin.CallStatic("initOMSDKSession",resourceUrl); - } - - public static void StartImpression(string resourceUrl) { - CheckPlatform(); - plugin.CallStatic("startImpression",resourceUrl); - } - - public static void StopImpression(string resourceUrl) { - CheckPlatform(); - plugin.CallStatic("stopImpression",resourceUrl); - } - - public static void StopOMIDAdSession(string name) { - CheckPlatform(); - plugin.CallStatic("stopOMIDAdSession",name); - } - - //-------- - public static void SetLogLevel(int level) { CheckPlatform(); plugin.CallStatic("setLogLevel", level); @@ -212,6 +199,15 @@ public class UniWebViewInterface { CheckPlatform(); plugin.CallStatic("setAllowUniversalAccessFromFileURLs", flag); } + public static void BringContentToFront(string name) { + CheckPlatform(); + plugin.CallStatic("bringContentToFront", name); + } + + public static void SetForwardWebConsoleToNativeOutput(bool flag) { + CheckPlatform(); + plugin.CallStatic("setForwardWebConsoleToNativeOutput", flag); + } public static void SetEnableKeyboardAvoidance(bool flag) { CheckPlatform(); @@ -228,6 +224,11 @@ public class UniWebViewInterface { plugin.CallStatic("cleanCache", name); } + public static void SetCacheMode(string name, int mode) { + CheckPlatform(); + plugin.CallStatic("setCacheMode", name, mode); + } + public static void ClearCookies() { CheckPlatform(); plugin.CallStatic("clearCookies"); @@ -283,6 +284,21 @@ public class UniWebViewInterface { plugin.CallStatic("setSpinnerText", name, text); } + public static void SetAllowUserDismissSpinnerByGesture(string name, bool flag) { + CheckPlatform(); + plugin.CallStatic("setAllowUserDismissSpinnerByGesture", name, flag); + } + + public static void ShowSpinner(string name) { + CheckPlatform(); + plugin.CallStatic("showSpinner", name); + } + + public static void HideSpinner(string name) { + CheckPlatform(); + plugin.CallStatic("hideSpinner", name); + } + public static bool CanGoBack(string name) { CheckPlatform(); return plugin.CallStatic("canGoBack", name); @@ -387,6 +403,11 @@ public class UniWebViewInterface { plugin.CallStatic("setSupportMultipleWindows", name, enabled, allowJavaScriptOpening); } + public static void SetDragInteractionEnabled(string name, bool flag) { + CheckPlatform(); + plugin.CallStatic("setDragInteractionEnabled", name, flag); + } + public static void SetDefaultFontSize(string name, int size) { CheckPlatform(); plugin.CallStatic("setDefaultFontSize", name, size); @@ -412,6 +433,11 @@ public class UniWebViewInterface { plugin.CallStatic("setDownloadEventForContextMenuEnabled", name, enabled); } + public static void SetAllowUserEditFileNameBeforeDownloading(string name, bool allowed) { + CheckPlatform(); + plugin.CallStatic("setAllowUserEditFileNameBeforeDownloading", name, allowed); + } + // Safe Browsing public static bool IsSafeBrowsingSupported() { @@ -506,6 +532,31 @@ public class UniWebViewInterface { plugin.CallStatic("setEmbeddedToolbarNavigationButtonsShow", name, show); } + public static void StartSnapshotForRendering(string name, string identifier) { + CheckPlatform(); + plugin.CallStatic("startSnapshotForRendering", name, identifier); + } + + public static void StopSnapshotForRendering(string name) { + CheckPlatform(); + plugin.CallStatic("stopSnapshotForRendering", name); + } + + public static byte[] GetRenderedData(string name, int x, int y, int width, int height) { + CheckPlatform(); + var sbyteArray = plugin.CallStatic("getRenderedData", name, x, y, width, height); + if (sbyteArray == null) { + return null; + } + int length = sbyteArray.Length; + byte[] byteArray = new byte[length]; + + for (int i = 0; i < length; i++) { + byteArray[i] = (byte)sbyteArray[i]; + } + return byteArray; + } + // Platform public static void CheckPlatform() { diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroid.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroid.cs.meta index 4a8f17e..0066794 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroid.cs.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroid.cs.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: 5a1d3cecc27d64565835e14b493c935b timeCreated: 1490880130 -licenseType: Free +licenseType: Store MonoImporter: serializedVersion: 2 defaultReferences: [] diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroidStaticListener.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroidStaticListener.cs index 39c7751..eb693f5 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroidStaticListener.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroidStaticListener.cs @@ -11,7 +11,7 @@ public class UniWebViewAndroidStaticListener: MonoBehaviour { void OnJavaMessage(string message) { // {listener_name}@{method_name}@parameters - string[] parts = message.Split("@"[0]); + var parts = message.Split("@"[0]); if (parts.Length < 3) { Debug.Log("Not enough parts for receiving a message."); return; @@ -25,6 +25,7 @@ public class UniWebViewAndroidStaticListener: MonoBehaviour { MethodInfo methodInfo = typeof(UniWebViewNativeListener).GetMethod(parts[1]); if (methodInfo == null) { Debug.Log("Cannot find correct method to invoke: " + parts[1]); + return; } var leftLength = parts.Length - 2; @@ -32,7 +33,7 @@ public class UniWebViewAndroidStaticListener: MonoBehaviour { for (int i = 0; i < leftLength; i++) { left[i] = parts[i + 2]; } - methodInfo.Invoke(listener, new object[] { String.Join("@", left) }); + methodInfo.Invoke(listener, new object[] { string.Join("@", left) }); } } diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroidStaticListener.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroidStaticListener.cs.meta index 2d10c95..407b0f9 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroidStaticListener.cs.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewAndroidStaticListener.cs.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: 2704cf8e127d541f1888d96429308645 timeCreated: 1514387178 -licenseType: Free +licenseType: Store MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewCocoa.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewCocoa.cs index 9d12ffa..2fe8cd3 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewCocoa.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewCocoa.cs @@ -23,8 +23,12 @@ using AOT; using System.Reflection; public class UniWebViewInterface { + + private const string StaticListenerName = "UniWebView-static"; + static UniWebViewInterface() { ConnectMessageSender(); + RegisterChannel(); } delegate void UnitySendMessageDelegate(IntPtr objectName, IntPtr methodName, IntPtr parameter); @@ -67,6 +71,13 @@ public class UniWebViewInterface { "Received message sent from native. Name: " + name + " Method: " + method + " Params: " + parameters ); + if (name == StaticListenerName) { + MethodInfo methodInfo = typeof(UniWebViewStaticListener) + .GetMethod(method, BindingFlags.Static | BindingFlags.Public); + methodInfo.Invoke(null, new object[] { parameters }); + return; + } + var listener = UniWebViewNativeListener.GetListener(name); if (listener) { MethodInfo methodInfo = typeof(UniWebViewNativeListener).GetMethod(method); @@ -75,63 +86,28 @@ public class UniWebViewInterface { } } } - - //------- - + + delegate string ChannelMethodDelegate(IntPtr namePtr, IntPtr methodPtr, IntPtr parameterPtr); + [DllImport(DllLib)] - private static extern void uv_stopOMIDAdSession(string name); - public static void StopOMIDAdSession(string name) - { + private static extern void uv_registerChannel([MarshalAs(UnmanagedType.FunctionPtr)] ChannelMethodDelegate channel); + public static void RegisterChannel() { + UniWebViewLogger.Instance.Info("Connecting to native side method channel."); CheckPlatform(); - - if (String.IsNullOrEmpty(name)) - { - return; - } - - uv_stopOMIDAdSession(name); + uv_registerChannel(ChannelFunc); } - [DllImport(DllLib)] - private static extern void uv_initOMSDK(string jsonSettings, string omidJSServiceContent); - public static void InitOMSDK(string jsonSettings, string omidJSServiceContent) - { - CheckPlatform(); + [MonoPInvokeCallback(typeof(ChannelMethodDelegate))] + private static string ChannelFunc(IntPtr namePtr, IntPtr methodPtr, IntPtr parameterPtr) { + string name = Marshal.PtrToStringAuto(namePtr); + string method = Marshal.PtrToStringAuto(methodPtr); + string parameters = Marshal.PtrToStringAuto(parameterPtr); - uv_initOMSDK(jsonSettings, omidJSServiceContent); + UniWebViewLogger.Instance.Verbose("ChannelFunc invoked by native side. Name: " + name + " Method: " + + method + " Params: " + parameters); + return UniWebViewChannelMethodManager.Instance.InvokeMethod(name, method, parameters); } - - [DllImport(DllLib)] - private static extern void uv_initOMSDKSession(string resourceUrl); - public static void InitOMSDKSession(string resourceUrl) - { - CheckPlatform(); - - uv_initOMSDKSession(resourceUrl); - } - - [DllImport(DllLib)] - private static extern void uv_startImpression(string resourceUrl); - public static void StartImpression(string resourceUrl) - { - CheckPlatform(); - - uv_startImpression(resourceUrl); - } - - [DllImport(DllLib)] - private static extern void uv_stopImpression(string resourceUrl); - public static void StopImpression(string resourceUrl) - { - CheckPlatform(); - - uv_stopImpression(resourceUrl); - } - - - //-------- - - + [DllImport(DllLib)] private static extern void uv_setLogLevel(int level); public static void SetLogLevel(int level) { @@ -344,6 +320,13 @@ public class UniWebViewInterface { CheckPlatform(); uv_setAllowUniversalAccessFromFileURLs(flag); } + + [DllImport(DllLib)] + private static extern void uv_setForwardWebConsoleToNativeOutput(bool flag); + public static void SetForwardWebConsoleToNativeOutput(bool flag) { + CheckPlatform(); + uv_setForwardWebConsoleToNativeOutput(flag); + } [DllImport(DllLib)] private static extern void uv_setAllowJavaScriptOpenWindow(bool flag); @@ -358,6 +341,13 @@ public class UniWebViewInterface { CheckPlatform(); uv_setJavaScriptEnabled(flag); } + + [DllImport(DllLib)] + private static extern void uv_setLimitsNavigationsToAppBoundDomains(bool flag); + public static void SetLimitsNavigationsToAppBoundDomains(bool flag) { + CheckPlatform(); + uv_setLimitsNavigationsToAppBoundDomains(flag); + } [DllImport(DllLib)] private static extern void uv_cleanCache(string name); @@ -366,6 +356,13 @@ public class UniWebViewInterface { uv_cleanCache(name); } + [DllImport(DllLib)] + private static extern void uv_setCacheMode(string name, int mode); + public static void SetCacheMode(string name, int mode) { + CheckPlatform(); + uv_setCacheMode(name, mode); + } + [DllImport(DllLib)] private static extern void uv_clearCookies(); public static void ClearCookies() { @@ -443,6 +440,27 @@ public class UniWebViewInterface { uv_setSpinnerText(name, text); } + [DllImport(DllLib)] + private static extern void uv_setAllowUserDismissSpinnerByGesture(string name, bool flag); + public static void SetAllowUserDismissSpinnerByGesture(string name, bool flag) { + CheckPlatform(); + uv_setAllowUserDismissSpinnerByGesture(name, flag); + } + + [DllImport(DllLib)] + private static extern void uv_showSpinner(string name); + public static void ShowSpinner(string name) { + CheckPlatform(); + uv_showSpinner(name); + } + + [DllImport(DllLib)] + private static extern void uv_hideSpinner(string name); + public static void HideSpinner(string name) { + CheckPlatform(); + uv_hideSpinner(name); + } + [DllImport(DllLib)] private static extern bool uv_canGoBack(string name); public static bool CanGoBack(string name) { @@ -637,7 +655,14 @@ public class UniWebViewInterface { CheckPlatform(); uv_removeDownloadMIMETypes(name, MIMEType, type); } - + + [DllImport(DllLib)] + private static extern void uv_setAllowUserEditFileNameBeforeDownloading(string name, bool allowed); + public static void SetAllowUserEditFileNameBeforeDownloading(string name, bool allowed) { + CheckPlatform(); + uv_setAllowUserEditFileNameBeforeDownloading(name, allowed); + } + [DllImport(DllLib)] private static extern void uv_setAllowUserChooseActionAfterDownloading(string name, bool allowed); public static void SetAllowUserChooseActionAfterDownloading(string name, bool allowed) { @@ -703,6 +728,13 @@ public class UniWebViewInterface { CheckPlatform(); uv_authenticationStart(name); } + + [DllImport(DllLib)] + private static extern void uv_authenticationCancel(string name); + public static void AuthenticationCancel(string name) { + CheckPlatform(); + uv_authenticationCancel(name); + } [DllImport(DllLib)] private static extern void uv_authenticationSetPrivateMode(string name, bool flag); @@ -781,6 +813,33 @@ public class UniWebViewInterface { uv_setEmbeddedToolbarNavigationButtonsShow(name, show); } + [DllImport(DllLib)] + private static extern void uv_startSnapshotForRendering(string name, string identifier); + public static void StartSnapshotForRendering(string name, string identifier) { + CheckPlatform(); + uv_startSnapshotForRendering(name, identifier); + } + + [DllImport(DllLib)] + private static extern void uv_stopSnapshotForRendering(string name); + public static void StopSnapshotForRendering(string name) { + CheckPlatform(); + uv_stopSnapshotForRendering(name); + } + + [DllImport(DllLib)] + private static extern IntPtr uv_getRenderedData(string name, int x, int y, int width, int height, out int length); + public static byte[] GetRenderedData(string name, int x, int y, int width, int height) { + CheckPlatform(); + + IntPtr dataPtr = uv_getRenderedData(name, x, y, width, height, out var length); + + byte[] managedData = new byte[length]; + Marshal.Copy(dataPtr, managedData, 0, length); + + return managedData; + } + #region Deprecated [DllImport(DllLib)] diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewCocoa.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewCocoa.cs.meta index d0bce83..af02b38 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewCocoa.cs.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewCocoa.cs.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: 2e905ba0b47304f4cbf9e3e3345f84eb timeCreated: 1492400358 -licenseType: Free +licenseType: Store MonoImporter: serializedVersion: 2 defaultReferences: [] diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewPlaceholder.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewPlaceholder.cs index 95e165a..940b684 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewPlaceholder.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewPlaceholder.cs @@ -6,12 +6,6 @@ public class UniWebViewInterface { private static bool alreadyLoggedWarning = false; - public static void StopOMIDAdSession(string name) { CheckPlatform(); } - public static void InitOMSDK(string jsonSettings, string omidJSServiceContent) { CheckPlatform(); } - public static void InitOMSDKSession(string resourceUrl) { CheckPlatform(); } - public static void StartImpression(string resourceUrl) { CheckPlatform(); } - public static void StopImpression(string resourceUrl) { CheckPlatform(); } - public static void SetLogLevel(int level) { CheckPlatform(); } public static void Init(string name, int x, int y, int width, int height) { CheckPlatform(); } public static void Destroy(string name) { CheckPlatform(); } @@ -38,11 +32,14 @@ public class UniWebViewInterface { public static void SetAllowAutoPlay(bool flag) { CheckPlatform(); } public static void SetAllowInlinePlay(bool flag) { CheckPlatform(); } public static void SetAllowJavaScriptOpenWindow(bool flag) { CheckPlatform(); } + public static void SetForwardWebConsoleToNativeOutput(bool flag) { CheckPlatform(); } public static void SetAllowFileAccess(string name, bool flag) { CheckPlatform(); } public static void SetAllowFileAccessFromFileURLs(string name, bool flag) { CheckPlatform(); } public static void SetAllowUniversalAccessFromFileURLs(bool flag) { CheckPlatform(); } public static void SetJavaScriptEnabled(bool flag) { CheckPlatform(); } + public static void SetLimitsNavigationsToAppBoundDomains(bool enabled) { CheckPlatform(); } public static void CleanCache(string name) { CheckPlatform(); } + public static void SetCacheMode(string name, int mode) { CheckPlatform(); } public static void ClearCookies() { CheckPlatform(); } public static void SetCookie(string url, string cookie, bool skipEncoding) { CheckPlatform(); } public static void RemoveCookies(string url, bool skipEncoding) { CheckPlatform(); } @@ -54,6 +51,9 @@ public class UniWebViewInterface { public static float GetWebViewAlpha(string name) { CheckPlatform(); return 0.0f; } public static void SetShowSpinnerWhileLoading(string name, bool show) { CheckPlatform(); } public static void SetSpinnerText(string name, string text) { CheckPlatform(); } + public static void SetAllowUserDismissSpinnerByGesture(string name, bool flag) { CheckPlatform(); } + public static void ShowSpinner(string name) { CheckPlatform(); } + public static void HideSpinner(string name) { CheckPlatform(); } public static bool CanGoBack(string name) { CheckPlatform(); return false; } public static bool CanGoForward(string name) { CheckPlatform(); return false; } public static void GoBack(string name) { CheckPlatform(); } @@ -73,6 +73,7 @@ public class UniWebViewInterface { public static void SetTransparencyClickingThroughEnabled(string name, bool enabled) { CheckPlatform(); } public static void SetWebContentsDebuggingEnabled(bool enabled) { CheckPlatform(); } public static void SetAllowHTTPAuthPopUpWindow(string name, bool flag) { CheckPlatform(); } + public static void SetAllowUserEditFileNameBeforeDownloading(string name, bool allowed) { CheckPlatform(); } public static void Print(string name) { CheckPlatform(); } public static void CaptureSnapshot(string name, string filename) { CheckPlatform(); } public static void SetCalloutEnabled(string name, bool flag) { CheckPlatform(); } @@ -98,6 +99,9 @@ public class UniWebViewInterface { public static void SetEmbeddedToolbarButtonTextColor(string name, Color color) { CheckPlatform(); } public static void SetEmbeddedToolbarTitleTextColor(string name, Color color) { CheckPlatform(); } public static void SetEmeddedToolbarNavigationButtonsShow(string name, bool show) { CheckPlatform(); } + public static void StartSnapshotForRendering(string name, string identifier) { CheckPlatform(); } + public static void StopSnapshotForRendering(string name) { CheckPlatform(); } + public static byte[] GetRenderedData(string name, int x, int y, int width, int height) { CheckPlatform(); return null; } public static void CheckPlatform() { if (!alreadyLoggedWarning) { diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewPlaceholder.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewPlaceholder.cs.meta index c65d504..a99c046 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewPlaceholder.cs.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Interface/UniWebViewPlaceholder.cs.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: 0b3bad20d12b1433ab8927c3effc605b timeCreated: 1497403102 -licenseType: Free +licenseType: Store MonoImporter: serializedVersion: 2 defaultReferences: [] diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Prefab.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Prefab.meta index b8b3163..c6b458a 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Prefab.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Prefab.meta @@ -2,7 +2,7 @@ fileFormatVersion: 2 guid: d5d657f2ee1114e20bccaace74235a99 folderAsset: yes timeCreated: 1491898971 -licenseType: Free +licenseType: Store DefaultImporter: userData: assetBundleName: diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Prefab/UniWebView.prefab.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Prefab/UniWebView.prefab.meta index 28ac7bd..063115a 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Prefab/UniWebView.prefab.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Prefab/UniWebView.prefab.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: 7e3f16a6f6303419cbd9837f6c746de4 timeCreated: 1496204510 -licenseType: Free +licenseType: Store NativeFormatImporter: mainObjectFileID: 100100000 userData: diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/README.txt.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/README.txt.meta index 94a494f..b30fbfc 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/README.txt.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/README.txt.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: 95440ce70c5e14d6e96e166c45f7cf6d timeCreated: 1499302025 -licenseType: Free +licenseType: Store TextScriptImporter: userData: assetBundleName: diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script.meta index 80a72b1..c78c9fe 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script.meta @@ -2,7 +2,7 @@ fileFormatVersion: 2 guid: b34f36f9836464e04893f632434d0862 folderAsset: yes timeCreated: 1491898971 -licenseType: Free +licenseType: Store DefaultImporter: userData: assetBundleName: diff --git a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/_CodeSignature/CodeResources.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/External.meta similarity index 67% rename from Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/_CodeSignature/CodeResources.meta rename to Runtime/GuruWebview/UniWebView5/UniWebView/Script/External.meta index 3ad521f..2f17096 100644 --- a/Runtime/GuruWebview/UniWebView5/Plugins/UniWebView.bundle/Contents/_CodeSignature/CodeResources.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/External.meta @@ -1,5 +1,6 @@ fileFormatVersion: 2 -guid: e0dc774ebc4264ab3982157e12fd428b +guid: 43c2ab2efc7244e0293d0a1cb0d869e8 +folderAsset: yes DefaultImporter: externalObjects: {} userData: diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/External/MiniJSON.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/External/MiniJSON.cs new file mode 100644 index 0000000..030fcbb --- /dev/null +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/External/MiniJSON.cs @@ -0,0 +1,551 @@ +/* + * Copyright (c) 2013 Calvin Rien + * + * Based on the JSON parser by Patrick van Bergen + * http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html + * + * Simplified it so that it doesn't throw exceptions + * and can be used in Unity iPhone with maximum code stripping. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace UniWebViewExternal { + // Example usage: + // + // using UnityEngine; + // using System.Collections; + // using System.Collection + // + // + // + // + // + // + // + // + // + // + // + // s.Generic; + // using MiniJSON; + // + // public class MiniJSONTest : MonoBehaviour { + // void Start () { + // var jsonString = "{ \"array\": [1.44,2,3], " + + // "\"object\": {\"key1\":\"value1\", \"key2\":256}, " + + // "\"string\": \"The quick brown fox \\\"jumps\\\" over the lazy dog \", " + + // "\"unicode\": \"\\u3041 Men\u00fa sesi\u00f3n\", " + + // "\"int\": 65536, " + + // "\"float\": 3.1415926, " + + // "\"bool\": true, " + + // "\"null\": null }"; + // + // var dict = Json.Deserialize(jsonString) as Dictionary; + // + // Debug.Log("deserialized: " + dict.GetType()); + // Debug.Log("dict['array'][0]: " + ((List) dict["array"])[0]); + // Debug.Log("dict['string']: " + (string) dict["string"]); + // Debug.Log("dict['float']: " + (double) dict["float"]); // floats come out as doubles + // Debug.Log("dict['int']: " + (long) dict["int"]); // ints come out as longs + // Debug.Log("dict['unicode']: " + (string) dict["unicode"]); + // + // var str = Json.Serialize(dict); + // + // Debug.Log("serialized: " + str); + // } + // } + + /// + /// This class encodes and decodes JSON strings. + /// Spec. details, see http://www.json.org/ + /// + /// JSON uses Arrays and Objects. These correspond here to the datatypes IList and IDictionary. + /// All numbers are parsed to doubles. + /// + public static class Json { + /// + /// Parses the string json into a value + /// + /// A JSON string. + /// An List<object>, a Dictionary<string, object>, a double, an integer,a string, null, true, or false + public static object Deserialize(string json) { + // save the string for debug information + if (json == null) { + return null; + } + + return Parser.Parse(json); + } + + sealed class Parser : IDisposable { + const string WORD_BREAK = "{}[],:\""; + + public static bool IsWordBreak(char c) { + return Char.IsWhiteSpace(c) || WORD_BREAK.IndexOf(c) != -1; + } + + enum TOKEN { + NONE, + CURLY_OPEN, + CURLY_CLOSE, + SQUARED_OPEN, + SQUARED_CLOSE, + COLON, + COMMA, + STRING, + NUMBER, + TRUE, + FALSE, + NULL + }; + + StringReader json; + + Parser(string jsonString) { + json = new StringReader(jsonString); + } + + public static object Parse(string jsonString) { + using (var instance = new Parser(jsonString)) { + return instance.ParseValue(); + } + } + + public void Dispose() { + json.Dispose(); + json = null; + } + + Dictionary ParseObject() { + Dictionary table = new Dictionary(); + + // ditch opening brace + json.Read(); + + // { + while (true) { + switch (NextToken) { + case TOKEN.NONE: + return null; + case TOKEN.COMMA: + continue; + case TOKEN.CURLY_CLOSE: + return table; + default: + // name + string name = ParseString(); + if (name == null) { + return null; + } + + // : + if (NextToken != TOKEN.COLON) { + return null; + } + // ditch the colon + json.Read(); + + // value + table[name] = ParseValue(); + break; + } + } + } + + List ParseArray() { + List array = new List(); + + // ditch opening bracket + json.Read(); + + // [ + var parsing = true; + while (parsing) { + TOKEN nextToken = NextToken; + + switch (nextToken) { + case TOKEN.NONE: + return null; + case TOKEN.COMMA: + continue; + case TOKEN.SQUARED_CLOSE: + parsing = false; + break; + default: + object value = ParseByToken(nextToken); + + array.Add(value); + break; + } + } + + return array; + } + + object ParseValue() { + TOKEN nextToken = NextToken; + return ParseByToken(nextToken); + } + + object ParseByToken(TOKEN token) { + switch (token) { + case TOKEN.STRING: + return ParseString(); + case TOKEN.NUMBER: + return ParseNumber(); + case TOKEN.CURLY_OPEN: + return ParseObject(); + case TOKEN.SQUARED_OPEN: + return ParseArray(); + case TOKEN.TRUE: + return true; + case TOKEN.FALSE: + return false; + case TOKEN.NULL: + return null; + default: + return null; + } + } + + string ParseString() { + StringBuilder s = new StringBuilder(); + char c; + + // ditch opening quote + json.Read(); + + bool parsing = true; + while (parsing) { + + if (json.Peek() == -1) { + parsing = false; + break; + } + + c = NextChar; + switch (c) { + case '"': + parsing = false; + break; + case '\\': + if (json.Peek() == -1) { + parsing = false; + break; + } + + c = NextChar; + switch (c) { + case '"': + case '\\': + case '/': + s.Append(c); + break; + case 'b': + s.Append('\b'); + break; + case 'f': + s.Append('\f'); + break; + case 'n': + s.Append('\n'); + break; + case 'r': + s.Append('\r'); + break; + case 't': + s.Append('\t'); + break; + case 'u': + var hex = new char[4]; + + for (int i=0; i< 4; i++) { + hex[i] = NextChar; + } + + s.Append((char) Convert.ToInt32(new string(hex), 16)); + break; + } + break; + default: + s.Append(c); + break; + } + } + + return s.ToString(); + } + + object ParseNumber() { + string number = NextWord; + + if (number.IndexOf('.') == -1) { + long parsedInt; + Int64.TryParse(number, out parsedInt); + return parsedInt; + } + + double parsedDouble; + Double.TryParse(number, out parsedDouble); + return parsedDouble; + } + + void EatWhitespace() { + while (Char.IsWhiteSpace(PeekChar)) { + json.Read(); + + if (json.Peek() == -1) { + break; + } + } + } + + char PeekChar => Convert.ToChar(json.Peek()); + + char NextChar => Convert.ToChar(json.Read()); + + string NextWord { + get { + StringBuilder word = new StringBuilder(); + + while (!IsWordBreak(PeekChar)) { + word.Append(NextChar); + + if (json.Peek() == -1) { + break; + } + } + + return word.ToString(); + } + } + + TOKEN NextToken { + get { + EatWhitespace(); + + if (json.Peek() == -1) { + return TOKEN.NONE; + } + + switch (PeekChar) { + case '{': + return TOKEN.CURLY_OPEN; + case '}': + json.Read(); + return TOKEN.CURLY_CLOSE; + case '[': + return TOKEN.SQUARED_OPEN; + case ']': + json.Read(); + return TOKEN.SQUARED_CLOSE; + case ',': + json.Read(); + return TOKEN.COMMA; + case '"': + return TOKEN.STRING; + case ':': + return TOKEN.COLON; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return TOKEN.NUMBER; + } + + switch (NextWord) { + case "false": + return TOKEN.FALSE; + case "true": + return TOKEN.TRUE; + case "null": + return TOKEN.NULL; + } + + return TOKEN.NONE; + } + } + } + + /// + /// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string + /// + /// A Dictionary<string, object> / List<object> + /// A JSON encoded string, or null if object 'json' is not serializable + public static string Serialize(object obj) { + return Serializer.Serialize(obj); + } + + sealed class Serializer { + StringBuilder builder; + + Serializer() { + builder = new StringBuilder(); + } + + public static string Serialize(object obj) { + var instance = new Serializer(); + + instance.SerializeValue(obj); + + return instance.builder.ToString(); + } + + void SerializeValue(object value) { + IList asList; + IDictionary asDict; + string asStr; + + if (value == null) { + builder.Append("null"); + } else if ((asStr = value as string) != null) { + SerializeString(asStr); + } else if (value is bool) { + builder.Append((bool) value ? "true" : "false"); + } else if ((asList = value as IList) != null) { + SerializeArray(asList); + } else if ((asDict = value as IDictionary) != null) { + SerializeObject(asDict); + } else if (value is char) { + SerializeString(new string((char) value, 1)); + } else { + SerializeOther(value); + } + } + + void SerializeObject(IDictionary obj) { + bool first = true; + + builder.Append('{'); + + foreach (object e in obj.Keys) { + if (!first) { + builder.Append(','); + } + + SerializeString(e.ToString()); + builder.Append(':'); + + SerializeValue(obj[e]); + + first = false; + } + + builder.Append('}'); + } + + void SerializeArray(IList anArray) { + builder.Append('['); + + bool first = true; + + foreach (object obj in anArray) { + if (!first) { + builder.Append(','); + } + + SerializeValue(obj); + + first = false; + } + + builder.Append(']'); + } + + void SerializeString(string str) { + builder.Append('\"'); + + char[] charArray = str.ToCharArray(); + foreach (var c in charArray) { + switch (c) { + case '"': + builder.Append("\\\""); + break; + case '\\': + builder.Append("\\\\"); + break; + case '\b': + builder.Append("\\b"); + break; + case '\f': + builder.Append("\\f"); + break; + case '\n': + builder.Append("\\n"); + break; + case '\r': + builder.Append("\\r"); + break; + case '\t': + builder.Append("\\t"); + break; + default: + int codepoint = Convert.ToInt32(c); + if ((codepoint >= 32) && (codepoint <= 126)) { + builder.Append(c); + } else { + builder.Append("\\u"); + builder.Append(codepoint.ToString("x4")); + } + break; + } + } + + builder.Append('\"'); + } + + void SerializeOther(object value) { + // NOTE: decimals lose precision during serialization. + // They always have, I'm just letting you know. + // Previously floats and doubles lost precision too. + if (value is float) { + builder.Append(((float) value).ToString("R")); + } else if (value is int + || value is uint + || value is long + || value is sbyte + || value is byte + || value is short + || value is ushort + || value is ulong) { + builder.Append(value); + } else if (value is double + || value is decimal) { + builder.Append(Convert.ToDouble(value).ToString("R")); + } else { + SerializeString(value.ToString()); + } + } + } + } +} diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/External/MiniJSON.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/External/MiniJSON.cs.meta new file mode 100644 index 0000000..f043832 --- /dev/null +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/External/MiniJSON.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f11796a30129b4c71aa6a52d59c6be19 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebView.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebView.cs index cf6f398..0e975ed 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebView.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebView.cs @@ -15,7 +15,6 @@ // arising from, out of or in connection with the software or the use of other dealing in the software. // using UnityEngine; -using System.Collections; using System.Collections.Generic; using System; @@ -49,13 +48,15 @@ public class UniWebView: MonoBehaviour { /// Raised when the web view finished to load a url successfully. /// /// This method will be invoked when a valid response received from the url, regardless of the response status. - /// If a url loading fails before reaching to the server and getting a response, `OnPageErrorReceived` will be + /// If a url loading fails before reaching to the server and getting a response, `OnLoadingErrorReceived` will be /// raised instead. /// public event PageFinishedDelegate OnPageFinished; /// /// Delegate for page error received event. + /// + /// Deprecated. Use `LoadingErrorReceivedDelegate` instead. /// /// The web view component which raises this event. /// @@ -63,12 +64,41 @@ public class UniWebView: MonoBehaviour { /// It can be different from systems and platforms. /// /// The error message which indicates the error. + [Obsolete("PageErrorReceivedDelegate is deprecated. Use `LoadingErrorReceivedDelegate` instead.", false)] public delegate void PageErrorReceivedDelegate(UniWebView webView, int errorCode, string errorMessage); + /// /// Raised when an error encountered during the loading process. /// Such as the "host not found" error or "no Internet connection" error will raise this event. + /// + /// Deprecated. Use `OnLoadingErrorReceived` instead. If both `OnPageErrorReceived` and `OnLoadingErrorReceived` are + /// listened, only the new `OnLoadingErrorReceived` will be raised, `OnPageErrorReceived` will not be called. /// + [Obsolete("OnPageErrorReceived is deprecated. Use `OnLoadingErrorReceived` instead.", false)] public event PageErrorReceivedDelegate OnPageErrorReceived; + + /// + /// Delegate for page loading error received event. + /// + /// The web view component which raises this event. + /// + /// The error code which indicates the error type. + /// It can be different from systems and platforms. + /// + /// The error message which indicates the error. + /// The payload received from native side, which contains the error information, such as the failing URL, in its `Extra`. + public delegate void LoadingErrorReceivedDelegate( + UniWebView webView, + int errorCode, + string errorMessage, + UniWebViewNativeResultPayload payload); + + /// + /// Raised when an error encountered during the loading process. + /// Such as the "host not found" error or "no Internet connection" error will raise this event. + /// + /// + public event LoadingErrorReceivedDelegate OnLoadingErrorReceived; /// /// Delegate for page progress changed event. @@ -133,9 +163,14 @@ public class UniWebView: MonoBehaviour { /// The web view component which raises this event. public delegate void OnWebContentProcessTerminatedDelegate(UniWebView webView); /// - /// Raised when on iOS, when system calls `webViewWebContentProcessDidTerminate` method. - /// It is usually due to a low memory when loading the web content and leave you a blank white screen. - /// You need to free as much as memory you could and then do a page reload. + /// On iOS, raise when the system calls `webViewWebContentProcessDidTerminate` method. On Android, raise when the + /// system calls `onRenderProcessGone` method. + /// + /// It is usually due to a low memory or the render process crashes when loading the web content. When this happens, + /// the web view will leave you a blank white screen. + /// + /// Usually you should close the web view and clean all the resource since there is no good way to restore. In some + /// cases, you can also try to free as much as memory and do a page `Reload`. /// public event OnWebContentProcessTerminatedDelegate OnWebContentProcessTerminated; @@ -214,6 +249,8 @@ public class UniWebView: MonoBehaviour { /// public event MultipleWindowClosedDelegate OnMultipleWindowClosed; + private static readonly Rect snapshotFullViewRect = new Rect(-1, -1, -1, -1); + private string id = Guid.NewGuid().ToString(); private UniWebViewNativeListener listener; @@ -221,8 +258,10 @@ public class UniWebView: MonoBehaviour { /// Represents the embedded toolbar in the current web view. /// public UniWebViewEmbeddedToolbar EmbeddedToolbar { get; private set; } - - private bool isPortrait; + + private ScreenOrientation currentOrientation; + + [SerializeField] #pragma warning disable 0649 @@ -260,7 +299,7 @@ public class UniWebView: MonoBehaviour { /// The first two values of `Rect` is `x` and `y` position and the followed two `width` and `height`. /// public Rect Frame { - get { return frame; } + get => frame; set { frame = value; UpdateFrame(); @@ -278,9 +317,7 @@ public class UniWebView: MonoBehaviour { /// /// public RectTransform ReferenceRectTransform { - get { - return referenceRectTransform; - } + get => referenceRectTransform; set { referenceRectTransform = value; UpdateFrame(); @@ -294,9 +331,7 @@ public class UniWebView: MonoBehaviour { /// /// The url of current loaded web page. /// - public string Url { - get { return UniWebViewInterface.GetUrl(listener.Name); } - } + public string Url => UniWebViewInterface.GetUrl(listener.Name); /// /// Updates and sets current frame of web view to match the setting. @@ -306,58 +341,63 @@ public class UniWebView: MonoBehaviour { /// public void UpdateFrame() { Rect rect = NextFrameRect(); + // Sync web view frame property. + frame = rect; UniWebViewInterface.SetFrame(listener.Name, (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height); } - Rect NextFrameRect() { + Rect NextFrameRect() + { if (referenceRectTransform == null) { UniWebViewLogger.Instance.Info("Using Frame setting to determine web view frame."); return frame; - } else { - UniWebViewLogger.Instance.Info("Using reference RectTransform to determine web view frame."); - var worldCorners = new Vector3[4]; - - referenceRectTransform.GetWorldCorners(worldCorners); - - var bottomLeft = worldCorners[0]; - var topLeft = worldCorners[1]; - var topRight = worldCorners[2]; - var bottomRight = worldCorners[3]; + } - var canvas = referenceRectTransform.GetComponentInParent(); - if (canvas == null) { - return frame; - } + UniWebViewLogger.Instance.Info("Using reference RectTransform to determine web view frame."); + var worldCorners = new Vector3[4]; + + referenceRectTransform.GetWorldCorners(worldCorners); + + // var bottomLeft = worldCorners[0]; + var topLeft = worldCorners[1]; + // var topRight = worldCorners[2]; + var bottomRight = worldCorners[3]; - switch (canvas.renderMode) { - case RenderMode.ScreenSpaceOverlay: - break; - case RenderMode.ScreenSpaceCamera: - case RenderMode.WorldSpace: - var camera = canvas.worldCamera; - if (camera == null) { - UniWebViewLogger.Instance.Critical(@"You need a render camera + var canvas = referenceRectTransform.GetComponentInParent(); + if (canvas == null) { + return frame; + } + + switch (canvas.renderMode) { + case RenderMode.ScreenSpaceOverlay: + break; + case RenderMode.ScreenSpaceCamera: + case RenderMode.WorldSpace: + var camera = canvas.worldCamera; + if (camera == null) { + UniWebViewLogger.Instance.Critical(@"You need a render camera or event camera to use RectTransform to determine correct frame for UniWebView."); - UniWebViewLogger.Instance.Info("No camera found. Fall back to ScreenSpaceOverlay mode."); - } else { - bottomLeft = camera.WorldToScreenPoint(bottomLeft); - topLeft = camera.WorldToScreenPoint(topLeft); - topRight = camera.WorldToScreenPoint(topRight); - bottomRight = camera.WorldToScreenPoint(bottomRight); - } - break; - } - - float widthFactor = (float)UniWebViewInterface.NativeScreenWidth() / (float)Screen.width; - float heightFactor = (float)UniWebViewInterface.NativeScreenHeight() / (float)Screen.height; - - float x = topLeft.x * widthFactor; - float y = (Screen.height - topLeft.y) * heightFactor; - float width = (bottomRight.x - topLeft.x) * widthFactor; - float height = (topLeft.y - bottomRight.y) * heightFactor; - return new Rect(x, y, width, height); + UniWebViewLogger.Instance.Info("No camera found. Fall back to ScreenSpaceOverlay mode."); + } else { + // bottomLeft = camera.WorldToScreenPoint(bottomLeft); + topLeft = camera.WorldToScreenPoint(topLeft); + // topRight = camera.WorldToScreenPoint(topRight); + bottomRight = camera.WorldToScreenPoint(bottomRight); + } + break; + default: + throw new ArgumentOutOfRangeException(); } + + var widthFactor = UniWebViewInterface.NativeScreenWidth() / Screen.width; + var heightFactor = UniWebViewInterface.NativeScreenHeight() / Screen.height; + + var x = topLeft.x * widthFactor; + var y = (Screen.height - topLeft.y) * heightFactor; + var width = (bottomRight.x - topLeft.x) * widthFactor; + var height = (topLeft.y - bottomRight.y) * heightFactor; + return new Rect(x, y, width, height); } void Awake() { @@ -369,15 +409,10 @@ public class UniWebView: MonoBehaviour { EmbeddedToolbar = new UniWebViewEmbeddedToolbar(listener); - Rect rect; - if (fullScreen) { - rect = new Rect(0, 0, Screen.width, Screen.height); - } else { - rect = NextFrameRect(); - } + var rect = fullScreen ? new Rect(0, 0, Screen.width, Screen.height) : NextFrameRect(); UniWebViewInterface.Init(listener.Name, (int)rect.x, (int)rect. y, (int)rect.width, (int)rect.height); - isPortrait = Screen.height >= Screen.width; + currentOrientation = Screen.orientation; } void Start() { @@ -400,11 +435,11 @@ public class UniWebView: MonoBehaviour { } void Update() { - var newIsPortrait = Screen.height >= Screen.width; - if (isPortrait != newIsPortrait) { - isPortrait = newIsPortrait; + var newOrientation = Screen.orientation; + if (currentOrientation != newOrientation) { + currentOrientation = newOrientation; if (OnOrientationChanged != null) { - OnOrientationChanged(this, Screen.orientation); + OnOrientationChanged(this, newOrientation); } if (referenceRectTransform != null) { UpdateFrame(); @@ -476,6 +511,24 @@ public class UniWebView: MonoBehaviour { } } + /// + /// Sets whether this web view instance should try to restore its view hierarchy when resumed. + /// + /// In some versions of Unity when running on Android, the player view is brought to front when switching back + /// from a pause state, which causes the web view is invisible when the app is resumed. It requires an additional + /// step to bring the web view to front to make the web view visible. Set this to true to apply this workaround. + /// + /// Issue caused by: + /// https://issuetracker.unity3d.com/issues/android-a-black-screen-appears-for-a-few-seconds-when-returning-to-the-game-from-the-lock-screen-after-idle-time + /// + /// This issue is known in these released versions: + /// - Unity 2021.3.31, 2021.3.32, 2021.3.31, 2021.3.34 + /// - Unity 2022.3.10, 2022.3.11, 2022.3.12, 2022.3.13, 2022.3.14, 2022.3.15 + /// + /// If you are using UniWebView in these versions, you may want to set this value to `true`. + /// + public bool RestoreViewHierarchyOnResume { get; set; } + /// /// Loads a url in current web view. /// @@ -505,14 +558,6 @@ public class UniWebView: MonoBehaviour { UniWebViewInterface.LoadHTMLString(listener.Name, htmlString, baseUrl, skipEncoding); } - /// - /// Stop current OMID ad session - /// - public void StopOMIDAdSession() - { - UniWebViewInterface.StopOMIDAdSession(listener.Name); - } - /// /// Reloads the current page. /// @@ -530,20 +575,12 @@ public class UniWebView: MonoBehaviour { /// /// Gets whether there is a back page in the back-forward list that can be navigated to. /// - public bool CanGoBack { - get { - return UniWebViewInterface.CanGoBack(listener.Name); - } - } + public bool CanGoBack => UniWebViewInterface.CanGoBack(listener.Name); /// /// Gets whether there is a forward page in the back-forward list that can be navigated to. /// - public bool CanGoForward { - get { - return UniWebViewInterface.CanGoForward(listener.Name); - } - } + public bool CanGoForward => UniWebViewInterface.CanGoForward(listener.Name); /// /// Navigates to the back item in the back-forward list. @@ -858,7 +895,7 @@ public class UniWebView: MonoBehaviour { /// Sets whether loading a local file is allowed. /// /// If set to `false`, any load from a file URL `file://` for `Load` method will be rejected and trigger an - /// `OnPageErrorReceived` event. That means you cannot load a web page from any local file. If you are not going to + /// `OnLoadingErrorReceived` event. That means you cannot load a web page from any local file. If you do not going to /// load any local files, setting it to `false` helps to reduce the interface of web view and improve the security. /// /// By default, it is `true` and the local file URL loading is allowed. @@ -917,12 +954,14 @@ public class UniWebView: MonoBehaviour { } /// - /// Sets whether the web view area should avoid soft keyboard. It `true`, when the keyboard shows up, the web views + /// Sets whether the web view area should avoid soft keyboard. If `true`, when the keyboard shows up, the web views /// content view will resize itself to avoid keyboard overlap the web content. Otherwise, the web view will not resize /// and just leave the content below under the soft keyboard. /// /// This method is only for Android. On iOS, the keyboard avoidance is built into the system directly and there is /// no way to change its behavior. + /// + /// Default is `true`. /// /// Whether the keyboard should avoid web view content. public static void SetEnableKeyboardAvoidance(bool flag) { @@ -939,6 +978,24 @@ public class UniWebView: MonoBehaviour { UniWebViewInterface.SetJavaScriptEnabled(enabled); } + /// + /// Sets whether the web view limits navigation to pages within the app’s domain. + /// + /// This only works on iOS 14.0+. For more information, refer to the Apple's documentation: + /// https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/3585117-limitsnavigationstoappbounddomai + /// and the App-Bound Domains page: https://webkit.org/blog/10882/app-bound-domains/ + /// + /// This requires additional setup in `WKAppBoundDomains` key in the Info.plist file. + /// + /// On Android, this method does nothing. + /// + /// + public static void SetLimitsNavigationsToAppBoundDomains(bool enabled) { + #if (UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS) && !UNITY_EDITOR_WIN && !UNITY_EDITOR_LINUX + UniWebViewInterface.SetLimitsNavigationsToAppBoundDomains(enabled); + #endif + } + /// /// Sets whether JavaScript can open windows without user interaction. /// @@ -949,6 +1006,30 @@ public class UniWebView: MonoBehaviour { UniWebViewInterface.SetAllowJavaScriptOpenWindow(flag); } + /// + /// Sets whether the web page console output should be forwarded to native console. + /// + /// By setting this to `true`, UniWebView will try to intercept the web page console output methods and forward + /// them to the native console, for example, Xcode console on iOS, Android logcat on Android and Unity Console when + /// using Unity Editor on macOS. It provides a way to debug the web page by using the native console without opening + /// the web inspector. The forwarded logs in native side contains a "<UniWebView-Web>" tag. + /// + /// Default is `false`. You need to set it before you create a web view instance to apply this setting. Any existing + /// web views are not affected. + /// + /// Logs from the methods below will be forwarded: + /// + /// - console.log + /// - console.warn + /// - console.error + /// - console.debug + /// + /// + /// Whether the web page console output should be forwarded to native output. + public static void SetForwardWebConsoleToNativeOutput(bool flag) { + UniWebViewInterface.SetForwardWebConsoleToNativeOutput(flag); + } + /// /// Cleans web view cache. This removes cached local data of web view. /// @@ -958,6 +1039,16 @@ public class UniWebView: MonoBehaviour { UniWebViewInterface.CleanCache(listener.Name); } + /// + /// Sets the way of how the cache is used when loading a request. + /// + /// The default value is `UniWebViewCacheMode.Default`. + /// + /// The desired cache mode which the following request loading should be used. + public void SetCacheMode(UniWebViewCacheMode cacheMode) { + UniWebViewInterface.SetCacheMode(listener.Name, (int)cacheMode); + } + /// /// Clears all cookies from web view. /// @@ -1046,9 +1137,7 @@ public class UniWebView: MonoBehaviour { /// Gets or sets the background color of web view. The default value is `Color.white`. /// public Color BackgroundColor { - get { - return backgroundColor; - } + get => backgroundColor; set { backgroundColor = value; UniWebViewInterface.SetBackgroundColor(listener.Name, value.r, value.g, value.b, value.a); @@ -1063,12 +1152,8 @@ public class UniWebView: MonoBehaviour { /// Default is `1.0f`, which means totally opaque. Set it to `0.0f` will make the web view totally transparent. /// public float Alpha { - get { - return UniWebViewInterface.GetWebViewAlpha(listener.Name); - } - set { - UniWebViewInterface.SetWebViewAlpha(listener.Name, value); - } + get => UniWebViewInterface.GetWebViewAlpha(listener.Name); + set => UniWebViewInterface.SetWebViewAlpha(listener.Name, value); } /// @@ -1087,6 +1172,39 @@ public class UniWebView: MonoBehaviour { UniWebViewInterface.SetSpinnerText(listener.Name, text); } + /// + /// Sets whether the user can dismiss the loading indicator by tapping on it or the greyed-out background around. + /// + /// By default, when the loading spinner is shown, the user can dismiss it by a single tapping. Call this method + /// with `false` to disable this behavior. + /// + /// Whether the user can dismiss the loading indicator. + public void SetAllowUserDismissSpinner(bool flag) { + UniWebViewInterface.SetAllowUserDismissSpinnerByGesture(listener.Name, flag); + } + + /// + /// Shows the loading spinner. + /// + /// Calling this method will show the loading spinner, regardless if the `SetShowSpinnerWhileLoading` is set to + /// `true` or `false`. However, if `SetShowSpinnerWhileLoading` was called with `true`, UniWebView will still hide + /// the spinner when the loading finishes. + /// + public void ShowSpinner() { + UniWebViewInterface.ShowSpinner(listener.Name); + } + + /// + /// Hides the loading spinner. + /// + /// Calling this method will hide the loading spinner, regardless if the `SetShowSpinnerWhileLoading` is set to + /// `true` or `false`. However, if `SetShowSpinnerWhileLoading` was called with `false`, UniWebView will still show + /// the spinner when the loading starts. + /// + public void HideSpinner() { + UniWebViewInterface.HideSpinner(listener.Name); + } + /// /// Sets whether the horizontal scroll bar should show when the web content beyonds web view bounds. /// @@ -1129,11 +1247,18 @@ public class UniWebView: MonoBehaviour { /// /// Adds a trusted domain to white list and allow permission requests from the domain. /// - /// You only need this on Android devices with system before 6.0 when a site needs the location or camera - /// permission. It will allow the permission gets approved so you could access the corresponding devices. - /// From Android 6.0, the permission requests method is changed and this is not needed anymore. + /// You need this on Android devices when a site needs the location or camera permission. It will allow the + /// permission gets approved so you could access the corresponding devices. + /// + /// Deprecated. Use `RegisterOnRequestMediaCapturePermission` instead, which works for both Android and iOS and + /// provides a more flexible way to handle the permission requests. By default, if neither this method and + /// `RegisterOnRequestMediaCapturePermission` is called, the permission request will trigger a grant alert to ask + /// the user to decide whether to allow or deny the permission. + /// /// /// The domain to add to the white list. + [Obsolete("Deprecated. Use `RegisterOnRequestMediaCapturePermission` instead. Check " + + "https://docs.uniwebview.com/api/#registeronrequestmediacapturepermission", false)] public void AddPermissionTrustDomain(string domain) { #if UNITY_ANDROID && !UNITY_EDITOR UniWebViewInterface.AddPermissionTrustDomain(listener.Name, domain); @@ -1144,6 +1269,7 @@ public class UniWebView: MonoBehaviour { /// Removes a trusted domain from white list. /// /// The domain to remove from white list. + [Obsolete("Deprecated. Use `UnregisterOnRequestMediaCapturePermission` instead.", false)] public void RemovePermissionTrustDomain(string domain) { #if UNITY_ANDROID && !UNITY_EDITOR UniWebViewInterface.RemovePermissionTrustDomain(listener.Name, domain); @@ -1206,6 +1332,8 @@ public class UniWebView: MonoBehaviour { /// Default is `true` /// Whether the toolbar transition should also adjust web view position and size /// if overlapped. Default is `false` + [Obsolete("`SetShowToolbar` is deprecated. Use `EmbeddedToolbar.Show()` or `EmbeddedToolbar.Hide()`" + + "instead.", false)] public void SetShowToolbar(bool show, bool animated = false, bool onTop = true, bool adjustInset = false) { #if (UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS) && !UNITY_EDITOR_WIN && !UNITY_EDITOR_LINUX UniWebViewInterface.SetShowToolbar(listener.Name, show, animated, onTop, adjustInset); @@ -1221,6 +1349,8 @@ public class UniWebView: MonoBehaviour { /// This method is only for iOS, since there is no toolbar on Android. /// /// The text needed to be set as done button title. + [Obsolete("`SetToolbarDoneButtonText` is deprecated. Use `EmbeddedToolbar.SetDoneButtonText` " + + "instead.", false)] public void SetToolbarDoneButtonText(string text) { #if UNITY_IOS && !UNITY_EDITOR UniWebViewInterface.SetToolbarDoneButtonText(listener.Name, text); @@ -1236,6 +1366,8 @@ public class UniWebView: MonoBehaviour { /// This method is only for iOS and macOS Editor, since there is no toolbar on Android. /// /// The text needed to be set as go back button. + [Obsolete("`SetToolbarGoBackButtonText` is deprecated. Use `EmbeddedToolbar.SetGoBackButtonText` " + + "instead.", false)] public void SetToolbarGoBackButtonText(string text) { #if (UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS) && !UNITY_EDITOR_WIN && !UNITY_EDITOR_LINUX UniWebViewInterface.SetToolbarGoBackButtonText(listener.Name, text); @@ -1251,6 +1383,8 @@ public class UniWebView: MonoBehaviour { /// This method is only for iOS and macOS Editor, since there is no toolbar on Android. /// /// The text needed to be set as go forward button. + [Obsolete("`SetToolbarGoForwardButtonText` is deprecated. Use `EmbeddedToolbar.SetGoForwardButtonText` " + + "instead.", false)] public void SetToolbarGoForwardButtonText(string text) { #if (UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS) && !UNITY_EDITOR_WIN && !UNITY_EDITOR_LINUX UniWebViewInterface.SetToolbarGoForwardButtonText(listener.Name, text); @@ -1266,6 +1400,8 @@ public class UniWebView: MonoBehaviour { /// This method is only for iOS, since there is no toolbar on Android. /// /// The color should be used for the background tint of the toolbar. + [Obsolete("`SetToolbarTintColor` is deprecated. Use `EmbeddedToolbar.SetBackgroundColor` " + + "instead.", false)] public void SetToolbarTintColor(Color color) { #if UNITY_IOS && !UNITY_EDITOR UniWebViewInterface.SetToolbarTintColor(listener.Name, color.r, color.g, color.b); @@ -1281,6 +1417,8 @@ public class UniWebView: MonoBehaviour { /// This method is only for iOS, since there is no toolbar on Android. /// /// The color should be used for the button text of the toolbar. + [Obsolete("`SetToolbarTextColor` is deprecated. Use `EmbeddedToolbar.SetButtonTextColor` or " + + "`EmbeddedToolbar.SetTitleTextColor` instead.", false)] public void SetToolbarTextColor(Color color) { #if UNITY_IOS && !UNITY_EDITOR UniWebViewInterface.SetToolbarTextColor(listener.Name, color.r, color.g, color.b); @@ -1297,6 +1435,8 @@ public class UniWebView: MonoBehaviour { /// This method is only for iOS, since there is no toolbar on Android. /// /// Whether the navigation buttons on the toolbar should show or hide. + [Obsolete("`SetShowToolbarNavigationButtons` is deprecated. Use `EmbeddedToolbar.ShowNavigationButtons` or " + + "`EmbeddedToolbar.HideNavigationButtons` instead.", false)] public void SetShowToolbarNavigationButtons(bool show) { #if (UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS) && !UNITY_EDITOR_WIN && !UNITY_EDITOR_LINUX UniWebViewInterface.SetShowToolbarNavigationButtons(listener.Name, show); @@ -1500,19 +1640,21 @@ public class UniWebView: MonoBehaviour { /// /// Sets whether the drag interaction should be enabled on iOS. /// - /// From iOS 11, the iPad web view supports the drag interaction when user long presses an image, link or text. + /// From iOS 11, the web view on iOS supports the drag interaction when user long presses an image, link or text. /// Setting this to `false` would disable the drag feather on the web view. + /// + /// On Android, there is no direct native way to disable drag only. This method instead disables the long touch + /// event, which is used as a trigger for drag interaction. So, setting this to `false` would disable the long + /// touch gesture as a side effect. /// - /// This method only works on iOS. It does nothing on Android or macOS editor. Default is `true`, which means - /// drag interaction on iPad is enabled. + /// It does nothing on macOS editor. Default is `true`, which means drag interaction is enabled if the device and + /// system version supports it. /// /// /// Whether the drag interaction should be enabled. /// public void SetDragInteractionEnabled(bool enabled) { - #if (UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS) && !UNITY_EDITOR_WIN && !UNITY_EDITOR_LINUX UniWebViewInterface.SetDragInteractionEnabled(listener.Name, enabled); - #endif } /// @@ -1627,6 +1769,23 @@ public class UniWebView: MonoBehaviour { #endif } + /// + /// Sets whether allowing users to edit the file name before downloading. Default is `true`. + /// + /// If `true`, when a download task is triggered, a dialog will be shown to ask user to edit the file name and the + /// user has a chance to choose if the they actually want to download the file. If `false`, the file download will + /// start immediately without asking user to edit the file name. The file name is generated by guessing from the + /// content disposition header and the MIME type. If the guessing of the file name fails, a random string will be + /// used. + /// + /// + /// + /// Whether the user can edit the file name and determine whether actually starting the downloading. + /// + public void SetAllowUserEditFileNameBeforeDownloading(bool allowed) { + UniWebViewInterface.SetAllowUserEditFileNameBeforeDownloading(listener.Name, allowed); + } + /// /// Sets whether allowing users to choose the way to handle the downloaded file. Default is `true`. /// @@ -1639,7 +1798,7 @@ public class UniWebView: MonoBehaviour { /// This method does not have any effect on Android. On Android, the file is downloaded to the Download folder. /// /// - /// + /// Whether the user can choose the way to handle the downloaded file. public void SetAllowUserChooseActionAfterDownloading(bool allowed) { #if (UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS) && !UNITY_EDITOR_WIN && !UNITY_EDITOR_LINUX UniWebViewInterface.SetAllowUserChooseActionAfterDownloading(listener.Name, allowed); @@ -1666,12 +1825,239 @@ public class UniWebView: MonoBehaviour { #endif } + /// + /// Starts the process of continually rendering the snapshot. + /// + /// + /// + /// You take the responsibility of calling this method before you use either or + /// to get the rendered data or texture. It prepares a render buffer for the image + /// data and performs the initial rendering for later use. + /// + /// + /// If this method is not called, the related data or texture methods will not work and will only return null. Once you + /// no longer need the web view to be rendered as a texture, you should call to clean up + /// the associated resources. + /// + /// + /// The optional rectangle to specify the area for rendering. If null, the entire view is rendered. + /// + /// An optional callback to execute when rendering has started. The callback receives a parameter + /// representing the rendered texture. + /// + public void StartSnapshotForRendering(Rect? rect = null, Action onStarted = null) { + string identifier = null; + if (onStarted != null) { + identifier = Guid.NewGuid().ToString(); + actions.Add(identifier, () => { + var texture = CreateRenderedTexture(rect); + onStarted(texture); + }); + } + UniWebViewInterface.StartSnapshotForRendering(listener.Name, identifier); + } + + /// + /// Stops the process of continually rendering the snapshot. + /// + /// + /// + /// You should call this method when you no longer need any further data or texture from the + /// or methods. This helps in releasing + /// resources and terminating the rendering process. + /// + /// + public void StopSnapshotForRendering() { + UniWebViewInterface.StopSnapshotForRendering(listener.Name); + } + + /// + /// Gets the data of the rendered image for the current web view. + /// + /// + /// + /// This method provides you with the raw bytes of the rendered image data in PNG format. To successfully retrieve the + /// current rendered data, you should first call to initiate the rendering process. + /// If has not been called, this method will return null. + /// + /// + /// The rendering area specified by the parameter is based on the local coordinates of the web view. + /// For example, new Rect(webView.frame.width / 2, webView.frame.height / 2, 100, 100) means setting the origin to the + /// center of the web view and taking a 100x100 square as the snapshot area. + /// + /// + /// Please note that this method supports only software-rendered content. Content rendered by hardware, such as videos + /// and WebGL content, will appear as a black rectangle in the rendered image. + /// + /// + /// + /// The desired rectangle within which the snapshot rendering should occur in the web view. If default value `null` is used, + /// the whole web view frame will be used as the snapshot area. + /// + /// + /// An array of raw bytes representing the rendered image data in PNG format, or null if the rendering process fails + /// or if the data is not prepared. + /// + /// + public byte[] GetRenderedData(Rect? rect = null) { + var r = rect ?? snapshotFullViewRect; + return UniWebViewInterface.GetRenderedData( + listener.Name, (int)r.x, (int)r.y, (int)r.width, (int)r.height + ); + } + + /// + /// Creates a rendered texture for the current web view. + /// + /// + /// + /// You should destroy the returned texture using the `Destroy` method when you no longer need it to free up resources. + /// + /// + /// This method provides you with a texture of the rendered image for the web view, which you can use in your 3D game world. + /// To obtain the current rendered data, you should call before using this method. + /// If has not been called, this method will return null. + /// + /// + /// Please note that this method supports only software-rendered content. Content rendered by hardware, such as videos + /// and WebGL content, will appear as a black rectangle in the rendered image. + /// + /// + /// This method returns a plain object. The texture is not user interactive and can only be used for + /// display purposes. It is your responsibility to call the `Destroy` method on this texture when you no longer need it. + /// + /// + /// + /// The desired rectangle within which the snapshot rendering should occur in the web view. If default value `null` is used, + /// the whole web view frame will be used as the snapshot area. + /// + /// + /// A rendered texture of the current web view, or null if the rendering process fails or if the data is not prepared. + /// + /// + public Texture2D CreateRenderedTexture(Rect? rect = null) { + var bytes = GetRenderedData(rect); + if (bytes == null) { + return null; + } + Texture2D texture = new Texture2D(2, 2, TextureFormat.RGB24, false); + texture.LoadImage(bytes); + return texture; + } + + /// + /// Registers a method handler for deciding whether UniWebView should handle the request received by the web view. + /// + /// The handler is called before the web view actually starts to load the new request. You can check the request + /// properties, such as the URL, to decide whether UniWebView should continue to handle the request or not. If you + /// return `true` from the handler function, UniWebView will continue to load the request. Otherwise, UniWebView + /// will stop the loading. + /// + /// + /// A handler you can implement your own logic against the input request value. You need to return a boolean value + /// to indicate whether UniWebView should continue to load the request or not as soon as possible. + /// + public void RegisterShouldHandleRequest(Func handler) { + object Func(object obj) => handler((UniWebViewChannelMethodHandleRequest)obj); + UniWebViewChannelMethodManager.Instance.RegisterChannelMethod( + listener.Name, + UniWebViewChannelMethod.ShouldUniWebViewHandleRequest, + Func + ); + } + + /// + /// Unregisters the method handler for handling request received by the web view. + /// + /// This clears the handler registered by `RegisterHandlingRequest` method. + /// + public void UnregisterShouldHandleRequest() { + UniWebViewChannelMethodManager.Instance.UnregisterChannelMethod( + listener.Name, + UniWebViewChannelMethod.ShouldUniWebViewHandleRequest + ); + } + + /// + /// Registers a method handler for deciding whether UniWebView should allow a media request from the web page or + /// not. + /// + /// The handler is called when the web view receives a request to capture media, such as camera or microphone. It + /// usually happens when the web view is trying to access the camera or microphone by using the "getUserMedia" APIs + /// in WebRTC. You can check the request properties in the input `UniWebViewChannelMethodMediaCapturePermission` + /// instance, which contains information like the media type, the request origin (protocol and host), then decide + /// whether this media request should be allowed or not. + /// + /// According to the `UniWebViewMediaCapturePermissionDecision` value you return from the handler function, + /// UniWebView behaves differently: + /// + /// - `Grant`: UniWebView allows the access without asking the user. + /// - `Deny`: UniWebView forbids the access and the web page will receive an error. + /// - `Prompt`: UniWebView asks the user for permission. The web page will receive a prompt to ask the user if they + /// allow the access to the requested media resources (camera or/and microphone). + /// + /// If this method is never called or the handler is unregistered, UniWebView will prompt the user for the + /// permission. + /// + /// On iOS, this method is available from iOS 15.0 or later. On earlier version of iOS, the handler will be ignored + /// and the web view will always prompt the user for the permission. + /// + /// + /// + /// A handler you can implement your own logic to decide whether UniWebView should allow, deny or prompt the media + /// resource access request. + /// + /// You need to return a `UniWebViewMediaCapturePermissionDecision` value to indicate the decision as soon as + /// possible. + /// + public void RegisterOnRequestMediaCapturePermission( + Func< + UniWebViewChannelMethodMediaCapturePermission, + UniWebViewMediaCapturePermissionDecision + > handler + ) + { + object Func(object obj) => handler((UniWebViewChannelMethodMediaCapturePermission)obj); + UniWebViewChannelMethodManager.Instance.RegisterChannelMethod( + listener.Name, + UniWebViewChannelMethod.RequestMediaCapturePermission, + Func + ); + } + + /// + /// Unregisters the method handler for handling media capture permission request. + /// + /// This clears the handler registered by `RegisterOnRequestMediaCapturePermission` method. + /// + public void UnregisterOnRequestMediaCapturePermission() { + UniWebViewChannelMethodManager.Instance.UnregisterChannelMethod( + listener.Name, + UniWebViewChannelMethod.RequestMediaCapturePermission + ); + } + void OnDestroy() { UniWebViewNativeListener.RemoveListener(listener.Name); + UniWebViewChannelMethodManager.Instance.UnregisterChannel(listener.Name); UniWebViewInterface.Destroy(listener.Name); Destroy(listener.gameObject); } +#if UNITY_ANDROID && !UNITY_EDITOR + // From Unity 2022.3.10, the player view is brought to front when switching back from a pause + // state. Requiring to bring the web view to front to make the web view visible. + // Issue caused by: + // https://issuetracker.unity3d.com/issues/android-a-black-screen-appears-for-a-few-seconds-when-returning-to-the-game-from-the-lock-screen-after-idle-time + // + // Ref: UWV-1061 + void OnApplicationPause(bool pauseStatus) { + if (RestoreViewHierarchyOnResume && !pauseStatus) { + UniWebViewInterface.BringContentToFront(listener.Name); + } + } +#endif + /* ////////////////////////////////////////////////////// // Internal Listener Interface ////////////////////////////////////////////////////// */ @@ -1735,9 +2121,14 @@ public class UniWebView: MonoBehaviour { } internal void InternalOnPageErrorReceived(UniWebViewNativeResultPayload payload) { - if (OnPageErrorReceived != null) { - int code = -1; - if (int.TryParse(payload.resultCode, out code)) { + if (OnLoadingErrorReceived != null) { + if (int.TryParse(payload.resultCode, out var code)) { + OnLoadingErrorReceived(this, code, payload.data, payload); + } else { + UniWebViewLogger.Instance.Critical("Invalid error code received: " + payload.resultCode); + } + } else if (OnPageErrorReceived != null) { + if (int.TryParse(payload.resultCode, out var code)) { OnPageErrorReceived(this, code, payload.data); } else { UniWebViewLogger.Instance.Critical("Invalid error code received: " + payload.resultCode); @@ -1806,6 +2197,14 @@ public class UniWebView: MonoBehaviour { OnCaptureSnapshotFinished(this, errorCode, payload.data); } } + + internal void InternalOnSnapshotRenderingStarted(string identifier) { + Action action; + if (actions.TryGetValue(identifier, out action)) { + action(); + actions.Remove(identifier); + } + } /// /// Sets whether the web view should behave in immersive mode, that is, diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebView.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebView.cs.meta index c09896b..891198a 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebView.cs.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebView.cs.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: 598e18fb001004a81960f552978ecf4e timeCreated: 1491898971 -licenseType: Free +licenseType: Store MonoImporter: serializedVersion: 2 defaultReferences: [] diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationCommonFlow.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationCommonFlow.cs index db12498..ae30336 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationCommonFlow.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationCommonFlow.cs @@ -31,6 +31,7 @@ public abstract class UniWebViewAuthenticationCommonFlow: MonoBehaviour { /// Whether to start authentication as soon as the script `Start`s. /// public bool authorizeOnStart; + /// /// Whether to use private mode to authenticate the user. If `true` and the device supports, the authentication /// will begin under the incognito mode. @@ -45,21 +46,29 @@ public abstract class UniWebViewAuthenticationCommonFlow: MonoBehaviour { // Security. Store the state. private string state; + // Security. Store the code challenge verifier. - private string codeVerify; - - protected string CodeVerify => codeVerify; - + protected string CodeVerify { get; private set; } + public void Start() { if (authorizeOnStart) { StartAuthenticationFlow(); } } - - // Subclass should override this method to start the authentication flow. Usually it starts - // a `UniWebViewAuthenticationFlow`. But you can also choose whatever you need to do. + + /// + /// Subclass should override this method to start the authentication flow. Usually it starts + /// a `UniWebViewAuthenticationFlow`. But you can also choose whatever you need to do. + /// public abstract void StartAuthenticationFlow(); + /// + /// Subclass should override this method to start the authentication flow. Usually it starts + /// a Unity Web Request against the authentication flow's token entry point to refresh the token. + /// + /// The refresh token. + public abstract void StartRefreshTokenFlow(string refreshToken); + // Child classes are expected to call this method to request a `state` (and store it for later check) if the // `state` verification is enabled. protected string GenerateAndStoreState() { @@ -70,8 +79,8 @@ public abstract class UniWebViewAuthenticationCommonFlow: MonoBehaviour { // Child classes are expected to call this method to request a `code_challenge`. Later when exchanging the access // token, the `code_verifier` will be used to verify the `code_challenge`. Subclass can read it from `CodeVerify`. protected string GenerateCodeChallengeAndStoreCodeVerify(UniWebViewAuthenticationPKCE method) { - codeVerify = UniWebViewAuthenticationUtils.GenerateCodeVerifier(); - return UniWebViewAuthenticationUtils.CalculateCodeChallenge(codeVerify, method); + CodeVerify = UniWebViewAuthenticationUtils.GenerateCodeVerifier(); + return UniWebViewAuthenticationUtils.CalculateCodeChallenge(CodeVerify, method); } // Perform verifying for `state`. diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlow.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlow.cs index 2769759..695552d 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlow.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlow.cs @@ -19,6 +19,7 @@ using System; using System.Collections; using UnityEngine.Networking; using System.Collections.Generic; +using System.Collections.Specialized; using UnityEngine; using UnityEngine.Events; @@ -64,6 +65,19 @@ public interface IUniWebViewAuthenticationFlow /// The dictionary indicates parameters that are used to perform the authentication request. Dictionary GetAuthenticationUriArguments(); + /// + /// Returns additional query arguments that are used to construct the query string of the authentication request. + /// + /// If you want to add some extra query arguments to the authentication request, you can override this method and + /// return a string that contains the additional query arguments. The returned string will be appended to the query + /// string that constructed from `GetAuthenticationUriArguments`. + /// + /// + /// + /// The additional query arguments that are used to construct the query string of the authentication request. + /// + string GetAdditionalAuthenticationUriQuery(); + /// /// Returns a dictionary contains the parameters that are used to perform the access token exchange request. /// The key value pairs in the dictionary are used to construct the HTTP form body of the access token exchange request. @@ -77,7 +91,17 @@ public interface IUniWebViewAuthenticationFlow /// The dictionary indicates parameters that are used to perform the access token exchange request. /// Dictionary GetAccessTokenRequestParameters(string authResponse); - + + /// + /// Returns a dictionary contains the parameters that are used to perform the access token refresh request. + /// The key value pairs in the dictionary are used to construct the HTTP form body of the access token refresh request. + /// + /// The refresh token should be used to perform the refresh request. + /// + /// The dictionary indicates parameters that are used to perform the access token refresh request. + /// + Dictionary GetRefreshTokenRequestParameters(string refreshToken); + /// /// Returns the strong-typed token for the authentication process. /// @@ -97,11 +121,21 @@ public interface IUniWebViewAuthenticationFlow /// Called when the authentication flow succeeds and a valid token is generated. /// UnityEvent OnAuthenticationFinished { get; } - + /// /// Called when any error (including user cancellation) happens during the authentication flow. /// UnityEvent OnAuthenticationErrored { get; } + + /// + /// Called when the access token refresh request finishes and a valid refreshed token is generated. + /// + UnityEvent OnRefreshTokenFinished { get; } + + /// + /// Called when any error happens during the access token refresh flow. + /// + UnityEvent OnRefreshTokenErrored { get; } } /// @@ -112,6 +146,8 @@ public interface IUniWebViewAuthenticationFlow public class UniWebViewAuthenticationFlow { private IUniWebViewAuthenticationFlow service; + private UniWebViewAuthenticationSession session; + private Uri callbackUri; public UniWebViewAuthenticationFlow( IUniWebViewAuthenticationFlow service @@ -127,48 +163,96 @@ public class UniWebViewAuthenticationFlow { { var callbackUri = new Uri(service.GetCallbackUrl()); var authUrl = GetAuthUrl(); - var session = UniWebViewAuthenticationSession.Create(authUrl, callbackUri.Scheme); + session = UniWebViewAuthenticationSession.Create(authUrl, callbackUri.Scheme); var flow = service as UniWebViewAuthenticationCommonFlow; if (flow != null && flow.privateMode) { session.SetPrivateMode(true); } + + #if UNITY_IOS + #if UNITY_EDITOR + // Editor does not support deep link. + UniWebViewLogger.Instance.Info("Seems you are trying to perform OAuth with a universal link. It is not supported in editor. Try on a real device."); + #else + // The callback URL seems to be a universal link. We need to handle it in a different way. + if (callbackUri.Scheme == "https") { + Application.deepLinkActivated += HandleUniversalLink; + } + + #endif // UNITY_EDITOR + #endif // UNITY_IOS + session.OnAuthenticationFinished += (_, resultUrl) => { UniWebViewLogger.Instance.Verbose("Auth flow received callback url: " + resultUrl); ExchangeToken(resultUrl); + Application.deepLinkActivated -= HandleUniversalLink; }; session.OnAuthenticationErrorReceived += (_, errorCode, message) => { - FlowErrored(errorCode, message); + UniWebViewLogger.Instance.Verbose("Auth flow received error: " + errorCode + ". Detail: " + message); + ExchangeTokenErrored(errorCode, message); + Application.deepLinkActivated -= HandleUniversalLink; }; UniWebViewLogger.Instance.Verbose("Starting auth flow with url: " + authUrl + "; Callback scheme: " + callbackUri.Scheme); session.Start(); } + + private void HandleUniversalLink(string url) { + if (url.StartsWith(url, StringComparison.InvariantCultureIgnoreCase)) { + UniWebViewLogger.Instance.Verbose("HandleUniversalLink: " + url); + Application.deepLinkActivated -= HandleUniversalLink; + session.Cancel(); + ExchangeToken(url); + } + } private void ExchangeToken(string response) { try { var args = service.GetAccessTokenRequestParameters(response); var request = GetTokenRequest(args); MonoBehaviour context = (MonoBehaviour)service; - context.StartCoroutine(SendTokenRequest(request)); + context.StartCoroutine(SendExchangeTokenRequest(request)); } catch (Exception e) { var message = e.Message; var code = -1; if (e is AuthenticationResponseException ex) { code = ex.Code; } - UniWebViewLogger.Instance.Critical("Exception on parsing response: " + e + ". Code: " + code + ". Message: " + message); - FlowErrored(code, message); + UniWebViewLogger.Instance.Critical("Exception on exchange token response: " + e + ". Code: " + code + ". Message: " + message); + ExchangeTokenErrored(code, message); + } + } + + /// + /// Refresh the access token with the given refresh token. + /// + /// + public void RefreshToken(string refreshToken) { + try { + var args = service.GetRefreshTokenRequestParameters(refreshToken); + var request = GetTokenRequest(args); + MonoBehaviour context = (MonoBehaviour)service; + context.StartCoroutine(SendRefreshTokenRequest(request)); + } catch (Exception e) { + var message = e.Message; + var code = -1; + if (e is AuthenticationResponseException ex) { + code = ex.Code; + } + UniWebViewLogger.Instance.Critical("Exception on refresh token response: " + e + ". Code: " + code + ". Message: " + message); + RefreshTokenErrored(code, message); } } private string GetAuthUrl() { var builder = new UriBuilder(service.GetAuthenticationConfiguration().authorizationEndpoint); - var query = System.Web.HttpUtility.ParseQueryString(""); + var query = new Dictionary(); foreach (var kv in service.GetAuthenticationUriArguments()) { query.Add(kv.Key, kv.Value); } - builder.Query = query.ToString(); + var additionalQuery = service.GetAdditionalAuthenticationUriQuery(); + builder.Query = UniWebViewAuthenticationUtils.CreateQueryString(query, additionalQuery); return builder.ToString(); } @@ -181,55 +265,78 @@ public class UniWebViewAuthenticationFlow { return UnityWebRequest.Post(builder.ToString(), form); } - private IEnumerator SendTokenRequest(UnityWebRequest request) { - using (var www = request) { - yield return www.SendWebRequest(); - if (www.result != UnityWebRequest.Result.Success) { - string errorMessage = null; - string errorBody = null; - if (www.error != null) { - errorMessage = www.error; - } - if (www.downloadHandler != null && www.downloadHandler.text != null) { - errorBody = www.downloadHandler.text; - } - UniWebViewLogger.Instance.Critical("Failed to get access token. Error: " + errorMessage + ". " + errorBody); - FlowErrored(www.responseCode, errorBody ?? errorMessage); - } else { - var responseText = www.downloadHandler.text; - UniWebViewLogger.Instance.Info("Token exchange request succeeded. Response: " + responseText); - try { - var token = service.GenerateTokenFromExchangeResponse(www.downloadHandler.text); - FlowFinished(token); - } catch (Exception e) { - var message = e.Message; - var code = -1; - if (e is AuthenticationResponseException ex) { - code = ex.Code; - } - UniWebViewLogger.Instance.Critical( - "Exception on parsing token response: " + e + ". Code: " + code + ". Message: " + - message + ". Response: " + responseText); - FlowErrored(code, message); + private IEnumerator SendExchangeTokenRequest(UnityWebRequest request) { + return SendTokenRequest(request, ExchangeTokenFinished, ExchangeTokenErrored); + } + + private IEnumerator SendRefreshTokenRequest(UnityWebRequest request) { + return SendTokenRequest(request, RefreshTokenFinished, RefreshTokenErrored); + } + + private IEnumerator SendTokenRequest(UnityWebRequest request, Action finishAction, ActionerrorAction) + { + using var www = request; + yield return www.SendWebRequest(); + if (www.result != UnityWebRequest.Result.Success) { + string errorMessage = null; + string errorBody = null; + if (www.error != null) { + errorMessage = www.error; + } + if (www.downloadHandler != null && www.downloadHandler.text != null) { + errorBody = www.downloadHandler.text; + } + UniWebViewLogger.Instance.Critical("Failed to get access token. Error: " + errorMessage + ". " + errorBody); + errorAction(www.responseCode, errorBody ?? errorMessage); + } else { + var responseText = www.downloadHandler.text; + UniWebViewLogger.Instance.Info("Token exchange request succeeded. Response: " + responseText); + try { + var token = service.GenerateTokenFromExchangeResponse(www.downloadHandler.text); + finishAction(token); + } catch (Exception e) { + var message = e.Message; + var code = -1; + if (e is AuthenticationResponseException ex) { + code = ex.Code; } + UniWebViewLogger.Instance.Critical( + "Exception on parsing token response: " + e + ". Code: " + code + ". Message: " + + message + ". Response: " + responseText); + errorAction(code, message); } } } - - private void FlowFinished(TTokenType token) { + + private void ExchangeTokenFinished(TTokenType token) { if (service.OnAuthenticationFinished != null) { service.OnAuthenticationFinished.Invoke(token); } - service = null;; + service = null; } - private void FlowErrored(long code, string message) { + private void ExchangeTokenErrored(long code, string message) { UniWebViewLogger.Instance.Info("Auth flow errored: " + code + ". Detail: " + message); if (service.OnAuthenticationErrored != null) { service.OnAuthenticationErrored.Invoke(code, message); } service = null; } + + private void RefreshTokenFinished(TTokenType token) { + if (service.OnRefreshTokenFinished != null) { + service.OnRefreshTokenFinished.Invoke(token); + } + service = null; + } + + private void RefreshTokenErrored(long code, string message) { + UniWebViewLogger.Instance.Info("Refresh flow errored: " + code + ". Detail: " + message); + if (service.OnRefreshTokenErrored != null) { + service.OnRefreshTokenErrored.Invoke(code, message); + } + service = null; + } } /// diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowCustomize.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowCustomize.cs index fb03b07..39b2173 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowCustomize.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowCustomize.cs @@ -49,7 +49,7 @@ public class UniWebViewAuthenticationFlowCustomize : UniWebViewAuthenticationCom /// The client Id of your OAuth application. /// public string clientId = ""; - + /// /// The redirect URI of your OAuth application. The service provider is expected to call this URI to pass back the /// authorization code. It should be something also set to your OAuth application. @@ -77,11 +77,21 @@ public class UniWebViewAuthenticationFlowCustomize : UniWebViewAuthenticationCom var flow = new UniWebViewAuthenticationFlow(this); flow.StartAuth(); } - + + /// + /// Starts the refresh flow with the standard OAuth 2.0. + /// This implements the abstract method in `UniWebViewAuthenticationCommonFlow`. + /// + /// The refresh token received with a previous access token response. + public override void StartRefreshTokenFlow(string refreshToken) { + var flow = new UniWebViewAuthenticationFlow(this); + flow.RefreshToken(refreshToken); + } + /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// - public UniWebViewAuthenticationConfiguration GetAuthenticationConfiguration() { + public virtual UniWebViewAuthenticationConfiguration GetAuthenticationConfiguration() { return new UniWebViewAuthenticationConfiguration( config.authorizationEndpoint, config.tokenEndpoint @@ -91,14 +101,14 @@ public class UniWebViewAuthenticationFlowCustomize : UniWebViewAuthenticationCom /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// - public string GetCallbackUrl() { + public virtual string GetCallbackUrl() { return redirectUri; } /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// - public Dictionary GetAuthenticationUriArguments() { + public virtual Dictionary GetAuthenticationUriArguments() { var authorizeArgs = new Dictionary { { "client_id", clientId }, { "redirect_uri", redirectUri }, @@ -122,12 +132,16 @@ public class UniWebViewAuthenticationFlowCustomize : UniWebViewAuthenticationCom return authorizeArgs; } + + public string GetAdditionalAuthenticationUriQuery() { + return optional.additionalAuthenticationUriQuery; + } /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// - public Dictionary GetAccessTokenRequestParameters(string authResponse) { - if (!authResponse.StartsWith(redirectUri)) { + public virtual Dictionary GetAccessTokenRequestParameters(string authResponse) { + if (!authResponse.StartsWith(redirectUri, StringComparison.InvariantCultureIgnoreCase)) { throw AuthenticationResponseException.UnexpectedAuthCallbackUrl; } @@ -136,7 +150,7 @@ public class UniWebViewAuthenticationFlowCustomize : UniWebViewAuthenticationCom if (!response.TryGetValue("code", out var code)) { throw AuthenticationResponseException.InvalidResponse(authResponse); } - if (optional.enableState) { + if (optional != null && optional.enableState) { VerifyState(response); } var parameters = new Dictionary { @@ -148,6 +162,9 @@ public class UniWebViewAuthenticationFlowCustomize : UniWebViewAuthenticationCom if (CodeVerify != null) { parameters.Add("code_verifier", CodeVerify); } + if (optional != null && !String.IsNullOrEmpty(optional.clientSecret)) { + parameters.Add("client_secret", optional.clientSecret); + } return parameters; } @@ -155,7 +172,22 @@ public class UniWebViewAuthenticationFlowCustomize : UniWebViewAuthenticationCom /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// - public UniWebViewAuthenticationStandardToken GenerateTokenFromExchangeResponse(string exchangeResponse) { + public virtual Dictionary GetRefreshTokenRequestParameters(string refreshToken) { + var parameters = new Dictionary { + { "client_id", clientId }, + { "refresh_token", refreshToken }, + { "grant_type", config.refreshTokenGrantType } + }; + if (optional != null && !String.IsNullOrEmpty(optional.clientSecret)) { + parameters.Add("client_secret", optional.clientSecret); + } + return parameters; + } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + public virtual UniWebViewAuthenticationStandardToken GenerateTokenFromExchangeResponse(string exchangeResponse) { return UniWebViewAuthenticationTokenFactory.Parse(exchangeResponse); } @@ -170,6 +202,18 @@ public class UniWebViewAuthenticationFlowCustomize : UniWebViewAuthenticationCom /// [field: SerializeField] public UnityEvent OnAuthenticationErrored { get; set; } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + [field: SerializeField] + public UnityEvent OnRefreshTokenFinished { get; set; } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + [field: SerializeField] + public UnityEvent OnRefreshTokenErrored { get; set; } } [Serializable] @@ -178,10 +222,14 @@ public class UniWebViewAuthenticationFlowCustomizeConfig { public string tokenEndpoint = ""; public string responseType = "code"; public string grantType = "authorization_code"; + + public string refreshTokenGrantType = "refresh_token"; } [Serializable] public class UniWebViewAuthenticationFlowCustomizeOptional { public UniWebViewAuthenticationPKCE PKCESupport = UniWebViewAuthenticationPKCE.None; public bool enableState = false; + public string clientSecret = ""; + public string additionalAuthenticationUriQuery = ""; } \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowDiscord.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowDiscord.cs index d7a43b8..962680b 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowDiscord.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowDiscord.cs @@ -53,9 +53,9 @@ public class UniWebViewAuthenticationFlowDiscord : UniWebViewAuthenticationCommo /// Optional to control this flow's behaviour. /// public UniWebViewAuthenticationFlowDiscordOptional optional; - - private string responseType = "code"; - private string grantType = "authorization_code"; + + private const string responseType = "code"; + private const string grantType = "authorization_code"; private readonly UniWebViewAuthenticationConfiguration config = new UniWebViewAuthenticationConfiguration( @@ -71,7 +71,17 @@ public class UniWebViewAuthenticationFlowDiscord : UniWebViewAuthenticationCommo var flow = new UniWebViewAuthenticationFlow(this); flow.StartAuth(); } - + + /// + /// Starts the refresh flow with the standard OAuth 2.0. + /// This implements the abstract method in `UniWebViewAuthenticationCommonFlow`. + /// + /// The refresh token received with a previous access token response. + public override void StartRefreshTokenFlow(string refreshToken) { + var flow = new UniWebViewAuthenticationFlow(this); + flow.RefreshToken(refreshToken); + } + /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// @@ -113,12 +123,16 @@ public class UniWebViewAuthenticationFlowDiscord : UniWebViewAuthenticationCommo return authorizeArgs; } + + public string GetAdditionalAuthenticationUriQuery() { + return optional.additionalAuthenticationUriQuery; + } /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// public Dictionary GetAccessTokenRequestParameters(string authResponse) { - if (!authResponse.StartsWith(redirectUri)) { + if (!authResponse.StartsWith(redirectUri, StringComparison.InvariantCultureIgnoreCase)) { throw AuthenticationResponseException.UnexpectedAuthCallbackUrl; } @@ -144,6 +158,18 @@ public class UniWebViewAuthenticationFlowDiscord : UniWebViewAuthenticationCommo return parameters; } + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + public Dictionary GetRefreshTokenRequestParameters(string refreshToken) { + return new Dictionary { + { "client_id", clientId }, + { "client_secret", clientSecret }, + { "refresh_token", refreshToken }, + { "grant_type", "refresh_token" } + }; + } + /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// @@ -151,10 +177,29 @@ public class UniWebViewAuthenticationFlowDiscord : UniWebViewAuthenticationCommo return UniWebViewAuthenticationTokenFactory.Parse(exchangeResponse); } + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// [field: SerializeField] public UnityEvent OnAuthenticationFinished { get; set; } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// [field: SerializeField] public UnityEvent OnAuthenticationErrored { get; set; } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + [field: SerializeField] + public UnityEvent OnRefreshTokenFinished { get; set; } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + [field: SerializeField] + public UnityEvent OnRefreshTokenErrored { get; set; } } /// @@ -172,6 +217,16 @@ public class UniWebViewAuthenticationFlowDiscordOptional { /// authentication callback. Default is `true`. /// public bool enableState = true; + + /// + /// The additional query arguments that are used to construct the query string of the authentication request. + /// + /// This is useful when you want to add some custom parameters to the authentication request. This string will be + /// appended to the query string that constructed from `GetAuthenticationUriArguments`. + /// + /// For example, if you set `prompt=consent&ui_locales=en`, it will be contained in the final authentication query. + /// + public string additionalAuthenticationUriQuery = ""; } /// diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowFacebook.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowFacebook.cs index 710cd99..a507e54 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowFacebook.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowFacebook.cs @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using UnityEngine; using UnityEngine.Events; @@ -49,9 +50,10 @@ public class UniWebViewAuthenticationFlowFacebook: UniWebViewAuthenticationCommo public UniWebViewAuthenticationFlowFacebookOptional optional; // The redirect URL should be exactly this one. Web view should inspect the loading of this URL to handle the result. - private string redirectUri = "https://www.facebook.com/connect/login_success.html"; + private const string redirectUri = "https://www.facebook.com/connect/login_success.html"; + // Only `token` response type is supported to use Facebook Login as the manual flow. - private string responseType = "token"; + private const string responseType = "token"; [field: SerializeField] public UnityEvent OnAuthenticationFinished { get; set; } @@ -72,6 +74,12 @@ public class UniWebViewAuthenticationFlowFacebook: UniWebViewAuthenticationCommo /// public override void StartAuthenticationFlow() { var webView = gameObject.AddComponent(); + + // Facebook login deprecates the Web View login on Android. As a workaround, prevents to be a desktop browser to continue the manual flow. + #if UNITY_ANDROID && !UNITY_EDITOR + webView.SetUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 13_0_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15"); + #endif + webView.OnPageFinished += (view, status, url) => { if (status != 200) { if (OnAuthenticationErrored != null) { @@ -114,24 +122,35 @@ public class UniWebViewAuthenticationFlowFacebook: UniWebViewAuthenticationCommo } } }; - webView.OnPageErrorReceived += (view, code, message) => { + webView.OnLoadingErrorReceived += (view, code, message, payload) => { if (OnAuthenticationErrored != null) { OnAuthenticationErrored.Invoke(code, message); } }; webView.Frame = new Rect(0, 0, Screen.width, Screen.height); webView.Load(GetAuthUrl()); - webView.SetShowToolbar(true, adjustInset: true); + webView.EmbeddedToolbar.Show(); webView.Show(false, UniWebViewTransitionEdge.Bottom, 0.25f); } - + + /// + /// Starts the refresh flow with the standard OAuth 2.0. + /// This implements the abstract method in `UniWebViewAuthenticationCommonFlow`. + /// + /// The refresh token received with a previous access token response. + public override void StartRefreshTokenFlow(string refreshToken) { + Debug.LogError("Facebook does not provide a refresh token flow when building with the manual flow."); + throw new NotImplementedException(); + } + private string GetAuthUrl() { var builder = new UriBuilder(config.authorizationEndpoint); - var query = System.Web.HttpUtility.ParseQueryString(""); + var query = new Dictionary(); foreach (var kv in GetAuthenticationUriArguments()) { query.Add(kv.Key, kv.Value); } - builder.Query = query.ToString(); + + builder.Query = UniWebViewAuthenticationUtils.CreateQueryString(query, optional.additionalAuthenticationUriQuery); return builder.ToString(); } @@ -163,6 +182,16 @@ public class UniWebViewAuthenticationFlowFacebookOptional { /// The scope string of all your required scopes. /// public string scope = ""; + + /// + /// The additional query arguments that are used to construct the query string of the authentication request. + /// + /// This is useful when you want to add some custom parameters to the authentication request. This string will be + /// appended to the query string that constructed from `GetAuthenticationUriArguments`. + /// + /// For example, if you set `prompt=consent&ui_locales=en`, it will be contained in the final authentication query. + /// + public string additionalAuthenticationUriQuery = ""; } /// The token object from Facebook. @@ -187,7 +216,7 @@ public class UniWebViewAuthenticationFacebookToken { public UniWebViewAuthenticationFacebookToken(string response, Dictionary values) { RawValue = response; - AccessToken = values.ContainsKey("access_token") ? values["access_token"] as string : null ; + AccessToken = values.ContainsKey("access_token") ? values["access_token"] : null ; if (AccessToken == null) { throw AuthenticationResponseException.InvalidResponse(response); } diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowGitHub.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowGitHub.cs index 8338bee..06b1d4a 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowGitHub.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowGitHub.cs @@ -56,10 +56,29 @@ public class UniWebViewAuthenticationFlowGitHub: UniWebViewAuthenticationCommonF "https://github.com/login/oauth/access_token" ); + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// [field: SerializeField] public UnityEvent OnAuthenticationFinished { get; set; } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// [field: SerializeField] public UnityEvent OnAuthenticationErrored { get; set; } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + [field: SerializeField] + public UnityEvent OnRefreshTokenFinished { get; set; } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + [field: SerializeField] + public UnityEvent OnRefreshTokenErrored { get; set; } /// /// Starts the authentication flow with the standard OAuth 2.0. @@ -70,6 +89,16 @@ public class UniWebViewAuthenticationFlowGitHub: UniWebViewAuthenticationCommonF flow.StartAuth(); } + /// + /// Starts the refresh flow with the standard OAuth 2.0. + /// This implements the abstract method in `UniWebViewAuthenticationCommonFlow`. + /// + /// The refresh token received with a previous access token response. + public override void StartRefreshTokenFlow(string refreshToken) { + var flow = new UniWebViewAuthenticationFlow(this); + flow.RefreshToken(refreshToken); + } + /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// @@ -92,11 +121,18 @@ public class UniWebViewAuthenticationFlowGitHub: UniWebViewAuthenticationCommonF if (!optional.allowSignup) { // The default value is true. authorizeArgs.Add("allow_signup", "false"); } + if (!String.IsNullOrEmpty(optional.prompt)) { + authorizeArgs.Add("prompt", optional.prompt); + } } return authorizeArgs; } + public string GetAdditionalAuthenticationUriQuery() { + return optional.additionalAuthenticationUriQuery; + } + /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// @@ -115,7 +151,7 @@ public class UniWebViewAuthenticationFlowGitHub: UniWebViewAuthenticationCommonF /// Implements required method in `IUniWebViewAuthenticationFlow`. /// public Dictionary GetAccessTokenRequestParameters(string authResponse) { - if (!authResponse.StartsWith(callbackUrl)) { + if (!authResponse.StartsWith(callbackUrl, StringComparison.InvariantCultureIgnoreCase)) { throw AuthenticationResponseException.UnexpectedAuthCallbackUrl; } var uri = new Uri(authResponse); @@ -138,6 +174,18 @@ public class UniWebViewAuthenticationFlowGitHub: UniWebViewAuthenticationCommonF return result; } + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + public Dictionary GetRefreshTokenRequestParameters(string refreshToken) { + return new Dictionary { + { "client_id", clientId }, + { "client_secret", clientSecret }, + { "refresh_token", refreshToken }, + { "grant_type", "refresh_token" } + }; + } + /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// @@ -169,9 +217,27 @@ public class UniWebViewAuthenticationFlowGitHubOptional { /// public bool enableState = false; /// - /// Whether or not unauthenticated users will be offered an option to sign up for GitHub during the OAuth flow. + /// Whether unauthenticated users will be offered an option to sign up for GitHub during the OAuth flow. /// public bool allowSignup = true; + + /// + /// The prompt that will be set to the authentication request query. For example, the possible values can be + /// `login`, `consent`, `select_account` and so on. + /// + /// See https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + /// + public string prompt = ""; + + /// + /// The additional query arguments that are used to construct the query string of the authentication request. + /// + /// This is useful when you want to add some custom parameters to the authentication request. This string will be + /// appended to the query string that constructed from `GetAuthenticationUriArguments`. + /// + /// For example, if you set `prompt=consent&ui_locales=en`, it will be contained in the final authentication query. + /// + public string additionalAuthenticationUriQuery = ""; } /// diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowGoogle.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowGoogle.cs index 6dcb426..85d2da8 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowGoogle.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowGoogle.cs @@ -57,8 +57,8 @@ public class UniWebViewAuthenticationFlowGoogle : UniWebViewAuthenticationCommon /// public UniWebViewAuthenticationFlowGoogleOptional optional; - private string responseType = "code"; - private string grantType = "authorization_code"; + private const string responseType = "code"; + private const string grantType = "authorization_code"; private readonly UniWebViewAuthenticationConfiguration config = new UniWebViewAuthenticationConfiguration( @@ -74,7 +74,17 @@ public class UniWebViewAuthenticationFlowGoogle : UniWebViewAuthenticationCommon var flow = new UniWebViewAuthenticationFlow(this); flow.StartAuth(); } - + + /// + /// Starts the refresh flow with the standard OAuth 2.0. + /// This implements the abstract method in `UniWebViewAuthenticationCommonFlow`. + /// + /// The refresh token received with a previous access token response. + public override void StartRefreshTokenFlow(string refreshToken) { + var flow = new UniWebViewAuthenticationFlow(this); + flow.RefreshToken(refreshToken); + } + /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// @@ -116,16 +126,23 @@ public class UniWebViewAuthenticationFlowGoogle : UniWebViewAuthenticationCommon if (!String.IsNullOrEmpty(optional.loginHint)) { authorizeArgs.Add("login_hint", optional.loginHint); } + if (!String.IsNullOrEmpty(optional.prompt)) { + authorizeArgs.Add("prompt", optional.prompt); + } } return authorizeArgs; } + + public string GetAdditionalAuthenticationUriQuery() { + return optional.additionalAuthenticationUriQuery; + } /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// public Dictionary GetAccessTokenRequestParameters(string authResponse) { - if (!authResponse.StartsWith(redirectUri)) { + if (!authResponse.StartsWith(redirectUri, StringComparison.InvariantCultureIgnoreCase)) { throw AuthenticationResponseException.UnexpectedAuthCallbackUrl; } @@ -149,7 +166,18 @@ public class UniWebViewAuthenticationFlowGoogle : UniWebViewAuthenticationCommon return parameters; } - + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + public Dictionary GetRefreshTokenRequestParameters(string refreshToken) { + return new Dictionary { + { "client_id", clientId }, + { "refresh_token", refreshToken }, + { "grant_type", "refresh_token" } + }; + } + /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// @@ -157,10 +185,29 @@ public class UniWebViewAuthenticationFlowGoogle : UniWebViewAuthenticationCommon return UniWebViewAuthenticationTokenFactory.Parse(exchangeResponse); } + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// [field: SerializeField] public UnityEvent OnAuthenticationFinished { get; set; } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// [field: SerializeField] public UnityEvent OnAuthenticationErrored { get; set; } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + [field: SerializeField] + public UnityEvent OnRefreshTokenFinished { get; set; } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + [field: SerializeField] + public UnityEvent OnRefreshTokenErrored { get; set; } } /// @@ -182,6 +229,24 @@ public class UniWebViewAuthenticationFlowGoogleOptional { /// the Google Authentication Server. /// public string loginHint = ""; + + /// + /// The prompt that will be set to the authentication request query. For example, the possible values can be + /// `login`, `consent`, `select_account` and so on. + /// + /// See https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + /// + public string prompt = ""; + + /// + /// The additional query arguments that are used to construct the query string of the authentication request. + /// + /// This is useful when you want to add some custom parameters to the authentication request. This string will be + /// appended to the query string that constructed from `GetAuthenticationUriArguments`. + /// + /// For example, if you set `prompt=consent&ui_locales=en`, it will be contained in the final authentication query. + /// + public string additionalAuthenticationUriQuery = ""; } /// diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowLine.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowLine.cs index 4e83078..9b3d035 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowLine.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowLine.cs @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using UnityEngine; using UnityEngine.Events; @@ -57,8 +58,8 @@ public class UniWebViewAuthenticationFlowLine : UniWebViewAuthenticationCommonFl /// public UniWebViewAuthenticationFlowLineOptional optional; - private string responseType = "code"; - private string grantType = "authorization_code"; + private const string responseType = "code"; + private const string grantType = "authorization_code"; private string RedirectUri { get { @@ -88,7 +89,17 @@ public class UniWebViewAuthenticationFlowLine : UniWebViewAuthenticationCommonFl var flow = new UniWebViewAuthenticationFlow(this); flow.StartAuth(); } - + + /// + /// Starts the refresh flow with the standard OAuth 2.0. + /// This implements the abstract method in `UniWebViewAuthenticationCommonFlow`. + /// + /// The refresh token received with a previous access token response. + public override void StartRefreshTokenFlow(string refreshToken) { + var flow = new UniWebViewAuthenticationFlow(this); + flow.RefreshToken(refreshToken); + } + /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// @@ -114,13 +125,18 @@ public class UniWebViewAuthenticationFlowLine : UniWebViewAuthenticationCommonFl return authorizeArgs; } + + public string GetAdditionalAuthenticationUriQuery() { + return optional.additionalAuthenticationUriQuery; + } private string GenerateReturnUri() { - var query = System.Web.HttpUtility.ParseQueryString(""); - query.Add("response_type", responseType); - query.Add("client_id", clientId); - query.Add("redirect_uri", RedirectUri); - + var query = new Dictionary { + { "response_type", responseType }, + { "client_id", clientId }, + { "redirect_uri", RedirectUri } + }; + // State is a must in LINE Login. var state = GenerateAndStoreState(); query.Add("state", state); @@ -139,7 +155,8 @@ public class UniWebViewAuthenticationFlowLine : UniWebViewAuthenticationCommonFl query.Add("code_challenge_method", method); } } - return "/oauth2/v2.1/authorize/consent?" + query; + var additionalQuery = GetAdditionalAuthenticationUriQuery(); + return "/oauth2/v2.1/authorize/consent?" + UniWebViewAuthenticationUtils.CreateQueryString(query, additionalQuery); } /// @@ -147,7 +164,7 @@ public class UniWebViewAuthenticationFlowLine : UniWebViewAuthenticationCommonFl /// public Dictionary GetAccessTokenRequestParameters(string authResponse) { var normalizedRedirectUri = UniWebViewAuthenticationUtils.ConvertIntentUri(RedirectUri); - if (!authResponse.StartsWith(normalizedRedirectUri)) { + if (!authResponse.StartsWith(normalizedRedirectUri, StringComparison.InvariantCultureIgnoreCase)) { throw AuthenticationResponseException.UnexpectedAuthCallbackUrl; } @@ -170,6 +187,17 @@ public class UniWebViewAuthenticationFlowLine : UniWebViewAuthenticationCommonFl return parameters; } + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + public Dictionary GetRefreshTokenRequestParameters(string refreshToken) { + return new Dictionary { + { "client_id", clientId }, + { "refresh_token", refreshToken }, + { "grant_type", "refresh_token" } + }; + } + /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// @@ -181,6 +209,10 @@ public class UniWebViewAuthenticationFlowLine : UniWebViewAuthenticationCommonFl public UnityEvent OnAuthenticationFinished { get; set; } [field: SerializeField] public UnityEvent OnAuthenticationErrored { get; set; } + [field: SerializeField] + public UnityEvent OnRefreshTokenFinished { get; set; } + [field: SerializeField] + public UnityEvent OnRefreshTokenErrored { get; set; } } /// @@ -192,6 +224,16 @@ public class UniWebViewAuthenticationFlowLineOptional { /// Whether to enable PKCE when performing authentication. Default is `S256`. /// public UniWebViewAuthenticationPKCE PKCESupport = UniWebViewAuthenticationPKCE.S256; + + /// + /// The additional query arguments that are used to construct the query string of the authentication request. + /// + /// This is useful when you want to add some custom parameters to the authentication request. This string will be + /// appended to the query string that constructed from `GetAuthenticationUriArguments`. + /// + /// For example, if you set `prompt=consent&ui_locales=en`, it will be contained in the final authentication query. + /// + public string additionalAuthenticationUriQuery = ""; } /// diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowTwitter.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowTwitter.cs index 167430f..83b4790 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowTwitter.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationFlowTwitter.cs @@ -46,8 +46,8 @@ public class UniWebViewAuthenticationFlowTwitter : UniWebViewAuthenticationCommo /// public UniWebViewAuthenticationFlowTwitterOptional optional; - private string responseType = "code"; - private string grantType = "authorization_code"; + private const string responseType = "code"; + private const string grantType = "authorization_code"; private readonly UniWebViewAuthenticationConfiguration config = new UniWebViewAuthenticationConfiguration( @@ -63,7 +63,17 @@ public class UniWebViewAuthenticationFlowTwitter : UniWebViewAuthenticationCommo var flow = new UniWebViewAuthenticationFlow(this); flow.StartAuth(); } - + + /// + /// Starts the refresh flow with the standard OAuth 2.0. + /// This implements the abstract method in `UniWebViewAuthenticationCommonFlow`. + /// + /// The refresh token received with a previous access token response. + public override void StartRefreshTokenFlow(string refreshToken) { + var flow = new UniWebViewAuthenticationFlow(this); + flow.RefreshToken(refreshToken); + } + /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// @@ -105,12 +115,16 @@ public class UniWebViewAuthenticationFlowTwitter : UniWebViewAuthenticationCommo return authorizeArgs; } + + public string GetAdditionalAuthenticationUriQuery() { + return optional.additionalAuthenticationUriQuery; + } /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// public Dictionary GetAccessTokenRequestParameters(string authResponse) { - if (!authResponse.StartsWith(redirectUri)) { + if (!authResponse.StartsWith(redirectUri, StringComparison.InvariantCultureIgnoreCase)) { throw AuthenticationResponseException.UnexpectedAuthCallbackUrl; } @@ -134,6 +148,17 @@ public class UniWebViewAuthenticationFlowTwitter : UniWebViewAuthenticationCommo return parameters; } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + public Dictionary GetRefreshTokenRequestParameters(string refreshToken) { + return new Dictionary { + { "client_id", clientId }, + { "refresh_token", refreshToken }, + { "grant_type", "refresh_token" } + }; + } /// /// Implements required method in `IUniWebViewAuthenticationFlow`. @@ -142,10 +167,29 @@ public class UniWebViewAuthenticationFlowTwitter : UniWebViewAuthenticationCommo return UniWebViewAuthenticationTokenFactory.Parse(exchangeResponse); } + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// [field: SerializeField] public UnityEvent OnAuthenticationFinished { get; set; } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// [field: SerializeField] public UnityEvent OnAuthenticationErrored { get; set; } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + [field: SerializeField] + public UnityEvent OnRefreshTokenFinished { get; set; } + + /// + /// Implements required method in `IUniWebViewAuthenticationFlow`. + /// + [field: SerializeField] + public UnityEvent OnRefreshTokenErrored { get; set; } } /// @@ -163,6 +207,16 @@ public class UniWebViewAuthenticationFlowTwitterOptional { /// authentication callback. This has to be `true`, otherwise, Twitter will reject the authentication request. /// public bool enableState = true; + + /// + /// The additional query arguments that are used to construct the query string of the authentication request. + /// + /// This is useful when you want to add some custom parameters to the authentication request. This string will be + /// appended to the query string that constructed from `GetAuthenticationUriArguments`. + /// + /// For example, if you set `prompt=consent&ui_locales=en`, it will be contained in the final authentication query. + /// + public string additionalAuthenticationUriQuery = ""; } /// diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationSession.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationSession.cs index 43dbb36..865b8c2 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationSession.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationSession.cs @@ -71,7 +71,7 @@ public class UniWebViewAuthenticationSession: UnityEngine.Object { /// public event AuthErrorReceivedDelegate OnAuthenticationErrorReceived; - private string id = Guid.NewGuid().ToString(); + private readonly string id = Guid.NewGuid().ToString(); private UniWebViewNativeListener listener; /// @@ -134,11 +134,7 @@ public class UniWebViewAuthenticationSession: UnityEngine.Object { /// Returns `true` if the safe browsing mode is supported and the page will be opened in safe browsing /// mode. Otherwise, `false`. /// - public static bool IsAuthenticationSupported { - get { - return UniWebViewInterface.IsAuthenticationIsSupported(); - } - } + public static bool IsAuthenticationSupported => UniWebViewInterface.IsAuthenticationIsSupported(); /// /// Creates a new authentication session with a given authentication page URL and a callback scheme. @@ -167,6 +163,17 @@ public class UniWebViewAuthenticationSession: UnityEngine.Object { UniWebViewInterface.AuthenticationStart(listener.Name); } + /// + /// Cancels the authentication session. + /// + /// This method is only available on iOS. + /// + internal void Cancel() { +#if UNITY_IOS && !UNITY_EDITOR + UniWebViewInterface.AuthenticationCancel(listener.Name); +#endif + } + /// /// Sets to use the private mode for the authentication. If running under private mode, the previous stored /// authentication information will not be used. diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationUtils.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationUtils.cs index 4153bca..07cbf80 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationUtils.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewAuthentication/UniWebViewAuthenticationUtils.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; +using System.Text; using UnityEngine.Networking; /// @@ -83,7 +84,7 @@ public class UniWebViewAuthenticationUtils { /// The Base64URL encoded string. /// A string with Base64 encoded for the input. public static string ConvertToBase64String(string input) { - string result = input.Replace('_', '/').Replace('-', '+'); + var result = input.Replace('_', '/').Replace('-', '+'); switch (input.Length % 4) { case 2: result += "=="; @@ -103,14 +104,13 @@ public class UniWebViewAuthenticationUtils { /// A generated code verifier for PKCE usage. public static string GenerateCodeVerifier(int length = 64) { var randomNumber = new byte[32]; - string value = ""; + string value; - using (var rng = RandomNumberGenerator.Create()) { - rng.GetBytes(randomNumber); - String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; - Random random = new Random(System.BitConverter.ToInt32(randomNumber, 0)); - value = new String(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray()); - } + using var rng = RandomNumberGenerator.Create(); + rng.GetBytes(randomNumber); + const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; + var random = new Random(System.BitConverter.ToInt32(randomNumber, 0)); + value = new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray()); return value; } @@ -157,7 +157,6 @@ public class UniWebViewAuthenticationUtils { var host = uri.Host; string scheme = null; - var fragmentPairs = new Dictionary(); var fragments = uri.Fragment; fragments.Split(';').ToList().ForEach(fragment => { var kv = fragment.Split('='); @@ -172,6 +171,28 @@ public class UniWebViewAuthenticationUtils { return input; } + + public static string CreateQueryString(Dictionary collection, string additionalQuery = "") { + int count = collection.Count; + if (count == 0) { + return additionalQuery ?? ""; + } + StringBuilder sb = new StringBuilder(); + string [] keys = collection.Keys.ToArray(); + for (int i = 0; i < count; i++) { + sb.AppendFormat ("{0}={1}&", keys[i], UnityWebRequest.EscapeURL(collection[keys[i]])); + } + + if (sb.Length > 0) { + if (string.IsNullOrEmpty(additionalQuery)) { + sb.Length--; + } else { + sb.AppendFormat("{0}", additionalQuery); + } + } + + return sb.ToString(); + } } public enum UniWebViewAuthenticationPKCE diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewCacheMode.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewCacheMode.cs new file mode 100644 index 0000000..b6faae9 --- /dev/null +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewCacheMode.cs @@ -0,0 +1,45 @@ +// +// UniWebViewCacheMode.cs +// Created by Wang Wei(@onevcat) on 2024-01-17. +// +// This file is a part of UniWebView Project (https://uniwebview.com) +// By purchasing the asset, you are allowed to use this code in as many as projects +// you want, only if you publish the final products under the name of the same account +// used for the purchase. +// +// This asset and all corresponding files (such as source code) are provided on an +// “as is” basis, without warranty of any kind, express of implied, including but not +// limited to the warranties of merchantability, fitness for a particular purpose, and +// noninfringement. In no event shall the authors or copyright holders be liable for any +// claim, damages or other liability, whether in action of contract, tort or otherwise, +// arising from, out of or in connection with the software or the use of other dealing in the software. + +/// +/// Defines the cache mode for UniWebView. +/// +public enum UniWebViewCacheMode +{ + /// + /// Default mode. The web view will check the validity of the cache copy when there is one. If the copy is invalid, + /// the web view will load from the network. This is the default setting. + /// + Default = 0, + + /// + /// No cache is used. All pages are loaded directly from the network. This is useful for applications that do not + /// want to have a cache. + /// + NoCache = 1, + + /// + /// Prioritize the cache. If there is a copy of the page in the cache, the web view will use it even if the copy + /// has expired. The web view will only load from the network when the page does not exist in the cache. + /// + CacheElseLoad = 2, + + /// + /// Only use the cache. In this mode, the web view will not load pages from the network, only use the content in + /// the cache. If the requested URL is not in the cache, an error is returned. + /// + CacheOnly = 3, +} \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewCacheMode.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewCacheMode.cs.meta new file mode 100644 index 0000000..0e0599f --- /dev/null +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewCacheMode.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: af9091341ad1451a88060e20c2dc953f +timeCreated: 1705494877 \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod.meta new file mode 100644 index 0000000..56b2fb8 --- /dev/null +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 983375010eb44a88b27df35ee1eedc4f +timeCreated: 1682064507 \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodHandleRequest.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodHandleRequest.cs new file mode 100644 index 0000000..73a5f83 --- /dev/null +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodHandleRequest.cs @@ -0,0 +1,33 @@ +// +// UniWebViewChannelMethodHandleRequest.cs +// Created by Wang Wei(@onevcat) on 2023-04-21. +// +// This file is a part of UniWebView Project (https://uniwebview.com) +// By purchasing the asset, you are allowed to use this code in as many as projects +// you want, only if you publish the final products under the name of the same account +// used for the purchase. +// +// This asset and all corresponding files (such as source code) are provided on an +// “as is” basis, without warranty of any kind, express of implied, including but not +// limited to the warranties of merchantability, fitness for a particular purpose, and +// noninfringement. In no event shall the authors or copyright holders be liable for any +// claim, damages or other liability, whether in action of contract, tort or otherwise, +// arising from, out of or in connection with the software or the use of other dealing in the software. +// + +using System; +using UnityEngine; + +/// +/// Represents the request of a loading used in request handler. +/// +[Serializable] +public class UniWebViewChannelMethodHandleRequest { + [SerializeField] + private string url; + + /// + /// The URL of the request. + /// + public string Url => url; +} \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodHandleRequest.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodHandleRequest.cs.meta new file mode 100644 index 0000000..8fd89c7 --- /dev/null +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodHandleRequest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e1fb4e291b944fa8b8a114f076f06da9 +timeCreated: 1682064771 \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodManager.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodManager.cs new file mode 100644 index 0000000..5a73c9a --- /dev/null +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodManager.cs @@ -0,0 +1,144 @@ +// +// UniWebViewChannelMethodManager.cs +// Created by Wang Wei(@onevcat) on 2023-04-21. +// +// This file is a part of UniWebView Project (https://uniwebview.com) +// By purchasing the asset, you are allowed to use this code in as many as projects +// you want, only if you publish the final products under the name of the same account +// used for the purchase. +// +// This asset and all corresponding files (such as source code) are provided on an +// “as is” basis, without warranty of any kind, express of implied, including but not +// limited to the warranties of merchantability, fitness for a particular purpose, and +// noninfringement. In no event shall the authors or copyright holders be liable for any +// claim, damages or other liability, whether in action of contract, tort or otherwise, +// arising from, out of or in connection with the software or the use of other dealing in the software. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +enum UniWebViewChannelMethod { + ShouldUniWebViewHandleRequest, + RequestMediaCapturePermission +} + +class UniWebViewChannelMethodManager { + + private static UniWebViewChannelMethodManager instance; + private Dictionary>> channels = + new Dictionary>>(); + + internal static UniWebViewChannelMethodManager Instance { + get { + if (instance == null) { + instance = new UniWebViewChannelMethodManager(); + } + return instance; + } + } + + internal void RegisterChannelMethod( + string webViewName, + UniWebViewChannelMethod method, + Func handler) + { + if (!HasRegisteredChannel(webViewName)) { + channels[webViewName] = new Dictionary>(); + } + var methodName = method.ToString(); + channels[webViewName][methodName] = handler; + UniWebViewLogger.Instance.Info("Channel method is registered for web view: " + webViewName + ", method: " + + methodName); + } + + internal void UnregisterChannel(string webViewName) { + if (!HasRegisteredChannel(webViewName)) { + return; + } + channels.Remove(webViewName); + UniWebViewLogger.Instance.Debug("All channel methods are unregistered for web view: " + webViewName); + } + + internal void UnregisterChannelMethod(string webViewName, UniWebViewChannelMethod method) { + var methodName = method.ToString(); + if (!HasRegisteredChannel(webViewName)) { + return; + } + channels[webViewName].Remove(methodName); + UniWebViewLogger.Instance.Debug("Channel method is unregistered for web view: " + webViewName + ", method: " + + methodName); + } + + bool HasRegisteredChannel(string webViewName) { + return channels.Keys.Contains(webViewName); + } + + bool HasRegisteredMethod(string webViewName, string methodName) { + if (!HasRegisteredChannel(webViewName)) { + return false; + } + + return channels[webViewName].Keys.Contains(methodName); + } + + internal string InvokeMethod(string webViewName, string methodName, string parameters) { + if (!HasRegisteredMethod(webViewName, methodName)) { + UniWebViewLogger.Instance.Info("There is no handler for the channel method. Ignoring."); + return null; + } + var func = channels[webViewName][methodName]; + + if (!Enum.TryParse(methodName, out var method)) { + UniWebViewLogger.Instance.Info("Unknown method name: " + methodName + ". Please check, ignoring."); + return null; + } + + UniWebViewLogger.Instance.Verbose("Channel method invoking received for web view: " + webViewName + ", method: " + + methodName + ", parameters: " + parameters); + string result; + switch (method) { + case UniWebViewChannelMethod.ShouldUniWebViewHandleRequest: { + // (UniWebViewChannelMethodHandleRequest) -> bool + var input = JsonUtility.FromJson(parameters); + bool Func(UniWebViewChannelMethodHandleRequest i) => (bool)func(i); + result = ResultJsonWith(Func(input)); + break; + } + case UniWebViewChannelMethod.RequestMediaCapturePermission: { + // (UniWebViewChannelMethodMediaCapturePermission) -> UniWebViewMediaCapturePermissionDecision + var input = JsonUtility.FromJson(parameters); + UniWebViewMediaCapturePermissionDecision Func(UniWebViewChannelMethodMediaCapturePermission i) => (UniWebViewMediaCapturePermissionDecision)func(i); + result = ResultJsonWith(Func(input)); + break; + } + default: + result = null; + break; + } + UniWebViewLogger.Instance.Debug("Channel method handler responded. Result: " + result); + return result; + } + + string ResultJsonWith(bool value) { + return value ? "{\"result\":true}" : "{\"result\":false}"; + } + + string ResultJsonWith(UniWebViewMediaCapturePermissionDecision decision) { + switch (decision) { + case UniWebViewMediaCapturePermissionDecision.Prompt: + return "{\"result\":\"prompt\"}"; + case UniWebViewMediaCapturePermissionDecision.Grant: + return "{\"result\":\"grant\"}"; + case UniWebViewMediaCapturePermissionDecision.Deny: + return "{\"result\":\"deny\"}"; + default: + UniWebViewLogger.Instance.Critical("Unknown decision: " + decision); + break; + } + Debug.LogAssertion("Unrecognized UniWebViewMediaCapturePermissionDecision. Fallback to prompt."); + return "{\"result\":\"prompt\"}"; + } +} \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodManager.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodManager.cs.meta new file mode 100644 index 0000000..aa8c340 --- /dev/null +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodManager.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f9415d08641d44ec97fdccd2799861e1 +timeCreated: 1682063884 \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodMediaCapturePermission.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodMediaCapturePermission.cs new file mode 100644 index 0000000..2433dd5 --- /dev/null +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodMediaCapturePermission.cs @@ -0,0 +1,69 @@ +// +// UniWebViewChannelMethodMediaCapturePermission.cs +// Created by Wang Wei(@onevcat) on 2024-02-20. +// +// This file is a part of UniWebView Project (https://uniwebview.com) +// By purchasing the asset, you are allowed to use this code in as many as projects +// you want, only if you publish the final products under the name of the same account +// used for the purchase. +// +// This asset and all corresponding files (such as source code) are provided on an +// “as is” basis, without warranty of any kind, express of implied, including but not +// limited to the warranties of merchantability, fitness for a particular purpose, and +// noninfringement. In no event shall the authors or copyright holders be liable for any +// claim, damages or other liability, whether in action of contract, tort or otherwise, +// arising from, out of or in connection with the software or the use of other dealing in the software. +// + +using System; +using UnityEngine; + +/// +/// Represents the request of media capture permission. +/// +/// This class represents the request of media capture permission. When the web page requests the permission to access +/// the device's hardware, such as the camera or microphone, UniWebView will ask you to handle the request with a value +/// of this class. You can use the values in this class to determine the decision for the permission request. +/// +[Serializable] +public class UniWebViewChannelMethodMediaCapturePermission { + + [SerializeField] + private string protocol; + + [SerializeField] + private string host; + + [SerializeField] + private int port; + + [SerializeField] + private string[] resources; + + /// + /// The protocol used by the permission request. Such as "https" or "http". + /// + public string Protocol => protocol; + + /// + /// The host of the origin of the permission request. + /// + /// It is usually the domain of the web page. + /// + public string Host => host; + + /// + /// The port of the origin of the permission request. + /// + /// If not existing in the request URL, it is -1. + /// + public int Port => port; + + /// + /// The string representation of the resources of the origin of the permission request. + /// + /// An array contains requested resources, the most common values are "VIDEO", "AUDIO". On Android, some pages can + /// also requests for "PROTECTED_MEDIA_ID", "MIDI_SYSEX", etc. + /// + public string[] Resources => resources; +} \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodMediaCapturePermission.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodMediaCapturePermission.cs.meta new file mode 100644 index 0000000..c2c0d72 --- /dev/null +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewChannelMethodMediaCapturePermission.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 31347bc762ad46fda0634683da0201ac +timeCreated: 1708439899 \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewMediaCapturePermissionDecision.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewMediaCapturePermissionDecision.cs new file mode 100644 index 0000000..109d16b --- /dev/null +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewMediaCapturePermissionDecision.cs @@ -0,0 +1,42 @@ +// +// UniWebViewMediaCapturePermissionDecision.cs +// Created by Wang Wei(@onevcat) on 2024-02-20. +// +// This file is a part of UniWebView Project (https://uniwebview.com) +// By purchasing the asset, you are allowed to use this code in as many as projects +// you want, only if you publish the final products under the name of the same account +// used for the purchase. +// +// This asset and all corresponding files (such as source code) are provided on an +// “as is” basis, without warranty of any kind, express of implied, including but not +// limited to the warranties of merchantability, fitness for a particular purpose, and +// noninfringement. In no event shall the authors or copyright holders be liable for any +// claim, damages or other liability, whether in action of contract, tort or otherwise, +// arising from, out of or in connection with the software or the use of other dealing in the software. +// + +/// +/// Represents the decision of media capture permission request. +/// +/// Use value in this enum to guide how UniWebView should handle the media capture permission request. +/// +public enum UniWebViewMediaCapturePermissionDecision { + /// + /// Display a prompt to ask user for the permission. + /// + /// The prompt alert shows the origin of the request and the resources requested. It asks user to grant or deny the + /// permission. + /// + Prompt, + + /// + /// Grant the permission request without asking user. + /// + Grant, + + /// + /// Deny the permission request. The web page will receive an error and it knows the request resources are not + /// allowed to use. + /// + Deny +} \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewMediaCapturePermissionDecision.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewMediaCapturePermissionDecision.cs.meta new file mode 100644 index 0000000..70043cf --- /dev/null +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewChannelMethod/UniWebViewMediaCapturePermissionDecision.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 325ac2081331446986f826bce9a5d62e +timeCreated: 1708440272 \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewEmbeddedToolbar.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewEmbeddedToolbar.cs index d0b65f2..cf7e3e9 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewEmbeddedToolbar.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewEmbeddedToolbar.cs @@ -100,8 +100,9 @@ public class UniWebViewEmbeddedToolbar { } /// - /// Hides the navigation buttons on the toolbar. When called, the back button and forward button will not be shown. - /// + /// Hides the navigation buttons on the toolbar. + /// + /// When called, the back button and forward button will not be shown. /// By default, the navigation buttons are shown. /// public void HideNavigationButtons() { @@ -109,8 +110,9 @@ public class UniWebViewEmbeddedToolbar { } /// - /// Shows the navigation buttons on the toolbar. When called, the back button and forward button will be shown. + /// Shows the navigation buttons on the toolbar. /// + /// When called, the back button and forward button will be shown. /// By default, the navigation buttons are shown. /// public void ShowNavigationButtons() { diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewHelper.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewHelper.cs.meta index 927842b..4eeae45 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewHelper.cs.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewHelper.cs.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: 824ddd9d1592945268d857265662a174 timeCreated: 1495373327 -licenseType: Free +licenseType: Store MonoImporter: serializedVersion: 2 defaultReferences: [] diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewLogger.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewLogger.cs index e2be3e1..bec78bf 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewLogger.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewLogger.cs @@ -58,7 +58,7 @@ public class UniWebViewLogger { /// Default is `Critical`, which means the logger only prints errors and exceptions. /// public Level LogLevel { - get { return level; } + get => level; set { Log(Level.Off, "Setting UniWebView logger level to: " + value); level = value; @@ -107,10 +107,11 @@ public class UniWebViewLogger { /// The message to log. public void Critical(string message) { Log(Level.Critical, message); } - private void Log(Level level, string message) { - if (level >= this.LogLevel) { + // ReSharper disable Unity.PerformanceAnalysis + private void Log(Level targetLevel, string message) { + if (targetLevel >= this.LogLevel) { var logMessage = " " + message; - if (level == Level.Critical) { + if (targetLevel == Level.Critical) { UnityEngine.Debug.LogError(logMessage); } else { UnityEngine.Debug.Log(logMessage); diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewLogger.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewLogger.cs.meta index 6528b5f..b12e2f1 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewLogger.cs.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewLogger.cs.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: 06ca7a8564d9842ba99c77d43e9ce4f5 timeCreated: 1491898971 -licenseType: Free +licenseType: Store MonoImporter: serializedVersion: 2 defaultReferences: [] diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewMessage.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewMessage.cs index b8a26a2..2be6dcd 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewMessage.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewMessage.cs @@ -14,15 +14,9 @@ // claim, damages or other liability, whether in action of contract, tort or otherwise, // arising from, out of or in connection with the software or the use of other dealing in the software. // -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.Networking; -#if UNITY_2017_3_OR_NEWER +using System.Collections.Generic; using Net = UnityEngine.Networking.UnityWebRequest; -#else -using Net = UnityEngine.WWW; -#endif /// /// A structure represents a message from webview. diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewMessage.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewMessage.cs.meta index d93de6f..7432d6c 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewMessage.cs.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewMessage.cs.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: 0dbdb1ed01b9747a1ad6eb7bcc7b4014 timeCreated: 1491898971 -licenseType: Free +licenseType: Store MonoImporter: serializedVersion: 2 defaultReferences: [] diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewNativeListener.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewNativeListener.cs index d8c4f12..7570162 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewNativeListener.cs +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewNativeListener.cs @@ -15,9 +15,9 @@ // arising from, out of or in connection with the software or the use of other dealing in the software. // using UnityEngine; -using System.Collections; using System.Collections.Generic; using System; +using UniWebViewExternal; /// /// A listener script for message sent from native side of UniWebView. @@ -66,11 +66,7 @@ public class UniWebViewNativeListener: MonoBehaviour { /// Name of current listener. This is a UUID string by which native side could use to find /// the message destination. /// - public string Name { - get { - return gameObject.name; - } - } + public string Name => gameObject.name; public void PageStarted(string url) { UniWebViewLogger.Instance.Info("Page Started Event. Url: " + url); @@ -185,6 +181,11 @@ public class UniWebViewNativeListener: MonoBehaviour { var payload = JsonUtility.FromJson(result); session.InternalAuthenticationErrorReceived(payload); } + + public void SnapshotRenderingStarted(string identifier) { + UniWebViewLogger.Instance.Info("Snapshot Rendering Started Event. Identifier: " + identifier); + webView.InternalOnSnapshotRenderingStarted(identifier); + } } /// @@ -193,6 +194,12 @@ public class UniWebViewNativeListener: MonoBehaviour { /// [System.Serializable] public class UniWebViewNativeResultPayload { + + /// + /// The key in `Extra` dictionary which contains the failing URL, if available. + /// + public const string ExtraFailingURLKey = "failingURL"; + /// /// The identifier bound to this payload. It would be used internally to identify the callback. /// @@ -205,6 +212,28 @@ public class UniWebViewNativeResultPayload { /// /// Return value or data from native. You should look at /// corresponding APIs to know what exactly contained in this. + /// + /// Usually it is a string value represents the reason or a small piece of data related to the result. /// public string data; + + /// + /// The extra data from native side. It is a JSON string and could be parsed to a dictionary. + /// + /// Usually you do not access to this value directly. Instead, use `Extra` property to get the parsed dictionary. + /// + public string extra; + + /// + /// The extra data from native side, in dictionary format. If there is no extra data provided, this will be null. + /// Otherwise, it contains values passed from native side. + /// + public Dictionary Extra { + get { + if (String.IsNullOrEmpty(extra)) { + return null; + } + return Json.Deserialize(extra) as Dictionary; + } + } } \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewNativeListener.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewNativeListener.cs.meta index e39b7f3..077e468 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewNativeListener.cs.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewNativeListener.cs.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: 5538b25d1713f4de994dacdc6eaacb95 timeCreated: 1491961733 -licenseType: Free +licenseType: Store MonoImporter: serializedVersion: 2 defaultReferences: [] diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewStaticListener.cs b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewStaticListener.cs new file mode 100644 index 0000000..ab659e7 --- /dev/null +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewStaticListener.cs @@ -0,0 +1,27 @@ +using UnityEngine; + +public class UniWebViewStaticListener { + public static void DebugLog(string value) { + var payload = JsonUtility.FromJson(value); + switch (payload.resultCode) { + case "0": + Debug.Log(payload.data); + break; + case "1": + Debug.Log(payload.data); + break; + case "2": + Debug.LogWarning(payload.data); + break; + case "3": + Debug.LogError(payload.data); + break; + case "4": + Debug.LogError(payload.data); + break; + default: + Debug.Log(payload.data); + break; + } + } +} \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewStaticListener.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewStaticListener.cs.meta new file mode 100644 index 0000000..b1bced1 --- /dev/null +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewStaticListener.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 17a82aa0398d47948eaf456b1c3f4716 +timeCreated: 1696325753 \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewToolbarPosition.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewToolbarPosition.cs.meta index a3f1f7c..3f8363d 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewToolbarPosition.cs.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewToolbarPosition.cs.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: 9e4c706c92e5e4dc381385bf4edbd3dd timeCreated: 1496199547 -licenseType: Free +licenseType: Store MonoImporter: serializedVersion: 2 defaultReferences: [] diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewTransitionEdge.cs.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewTransitionEdge.cs.meta index 29ce0d8..704be0a 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewTransitionEdge.cs.meta +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/Script/UniWebViewTransitionEdge.cs.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: 1256e7eea8f184fae9b700f8f78e014c timeCreated: 1493888384 -licenseType: Free +licenseType: Store MonoImporter: serializedVersion: 2 defaultReferences: [] diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/UniWebView-CSharp.asmdef b/Runtime/GuruWebview/UniWebView5/UniWebView/UniWebView-CSharp.asmdef index cd058cc..df8c7ad 100644 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/UniWebView-CSharp.asmdef +++ b/Runtime/GuruWebview/UniWebView5/UniWebView/UniWebView-CSharp.asmdef @@ -1,7 +1,11 @@ { "name": "UniWebView-CSharp", "rootNamespace": "", - "references": ["GUID:343deaaf83e0cee4ca978e7df0b80d21","GUID:2bafac87e7f4b9b418d9448d219b01ab"], + "references": [ + "GUID:343deaaf83e0cee4ca978e7df0b80d21", + "GUID:2bafac87e7f4b9b418d9448d219b01ab", + "GUID:75469ad4d38634e559750d17036d5f7c" + ], "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode": false, diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/csc.rsp b/Runtime/GuruWebview/UniWebView5/UniWebView/csc.rsp deleted file mode 100644 index 349a1e2..0000000 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/csc.rsp +++ /dev/null @@ -1 +0,0 @@ --r:System.Web.dll \ No newline at end of file diff --git a/Runtime/GuruWebview/UniWebView5/UniWebView/csc.rsp.meta b/Runtime/GuruWebview/UniWebView5/UniWebView/csc.rsp.meta deleted file mode 100644 index a1db554..0000000 --- a/Runtime/GuruWebview/UniWebView5/UniWebView/csc.rsp.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 81b32579db9e845999209664ba8e2ec3 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: