From ac9d15f971e13d828644d52ab0e2d0092cbf9f43 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:43:23 -0600 Subject: [PATCH 1/2] Add Android Request Interception --- Demo-Android/Gutenberg/build.gradle.kts | 3 ++ .../gutenberg/GutenbergRequestInterceptor.kt | 13 ++++++ .../org/wordpress/gutenberg/GutenbergView.kt | 40 +++++++++++++++++-- Demo-Android/gradle/libs.versions.toml | 3 ++ 4 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 Demo-Android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergRequestInterceptor.kt diff --git a/Demo-Android/Gutenberg/build.gradle.kts b/Demo-Android/Gutenberg/build.gradle.kts index 4093204e..dd2e8524 100644 --- a/Demo-Android/Gutenberg/build.gradle.kts +++ b/Demo-Android/Gutenberg/build.gradle.kts @@ -39,6 +39,9 @@ dependencies { implementation(libs.androidx.appcompat) implementation(libs.material) implementation(libs.androidx.webkit) + implementation(platform(libs.okhttp.bom)) + implementation(libs.okhttp) + testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/Demo-Android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergRequestInterceptor.kt b/Demo-Android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergRequestInterceptor.kt new file mode 100644 index 00000000..cf06bb04 --- /dev/null +++ b/Demo-Android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergRequestInterceptor.kt @@ -0,0 +1,13 @@ +package org.wordpress.gutenberg + +import android.webkit.WebResourceRequest + +public interface GutenbergRequestInterceptor { + fun interceptRequest(request: WebResourceRequest): WebResourceRequest +} + +class DefaultGutenbergRequestInterceptor: GutenbergRequestInterceptor { + override fun interceptRequest(request: WebResourceRequest): WebResourceRequest { + return request + } +} \ No newline at end of file diff --git a/Demo-Android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt b/Demo-Android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt index 0d9421df..6a5eb4e0 100644 --- a/Demo-Android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt +++ b/Demo-Android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt @@ -20,6 +20,11 @@ import android.webkit.WebView import android.webkit.WebViewClient import androidx.webkit.WebViewAssetLoader import androidx.webkit.WebViewAssetLoader.AssetsPathHandler +import okhttp3.Headers.Companion.toHeaders +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okio.IOException import org.json.JSONObject import java.lang.ref.WeakReference @@ -48,10 +53,15 @@ class GutenbergView : WebView { var editorDidBecomeAvailable: ((GutenbergView) -> Unit)? = null var filePathCallback: ValueCallback?>? = null val pickImageRequestCode = 1 + + var requestInterceptor: GutenbergRequestInterceptor = DefaultGutenbergRequestInterceptor() + private var onFileChooserRequested: WeakReference<((Intent, Int) -> Unit)?>? = null private var contentChangeListener: WeakReference? = null private var editorDidBecomeAvailableListener: EditorAvailableListener? = null + private val httpClient = OkHttpClient() + fun setContentChangeListener(listener: ContentChangeListener) { contentChangeListener = WeakReference(listener) } @@ -103,10 +113,34 @@ class GutenbergView : WebView { hasSetEditorConfig = true } - return if (request?.url != null) { - assetLoader.shouldInterceptRequest(request.url) + if (request?.url == null) { + return super.shouldInterceptRequest(view, request) + } else if(request.url.host?.contains("appassets.androidplatform.net") == true) { + return assetLoader.shouldInterceptRequest(request.url) } else { - super.shouldInterceptRequest(view, request) + val modifiedRequest = requestInterceptor.interceptRequest(request) + + try { + val okHttpRequest = Request.Builder() + .url(modifiedRequest.url!!.toString()) + .headers(modifiedRequest.requestHeaders.toHeaders()) + .build() + + val response: Response = httpClient.newCall(okHttpRequest).execute() + + val body = if(response.body != null) { response.body!! } else { return null } + val contentType = if(body.contentType() != null) { body.contentType() } else { return null } + + return WebResourceResponse( + contentType.toString(), + response.header("content-encoding", null), + body.byteStream() + ) + } catch (e: IOException) { + // We don't need to handle this ourselves, just tell the WebView that + // we weren't able to fetch the resource + return null; + } } } } diff --git a/Demo-Android/gradle/libs.versions.toml b/Demo-Android/gradle/libs.versions.toml index 68abc3ab..6b10dbda 100644 --- a/Demo-Android/gradle/libs.versions.toml +++ b/Demo-Android/gradle/libs.versions.toml @@ -9,6 +9,7 @@ appcompat = "1.7.0" material = "1.12.0" activity = "1.9.0" constraintlayout = "2.1.4" +okhttp = "4.12.0" webkit = "1.11.0" [libraries] @@ -21,6 +22,8 @@ material = { group = "com.google.android.material", name = "material", version.r androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" } androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } androidx-webkit = { group = "androidx.webkit", name = "webkit", version.ref = "webkit" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } +okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttp" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } From 43505ff03ba06c25d529e31c323bb320a5fc76b3 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Mon, 21 Oct 2024 17:11:57 -0600 Subject: [PATCH 2/2] Add iOS Request Interceptor --- .../Sources/EditorViewController.swift | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/Sources/GutenbergKit/Sources/EditorViewController.swift b/Sources/GutenbergKit/Sources/EditorViewController.swift index 0d0a77a3..632c9006 100644 --- a/Sources/GutenbergKit/Sources/EditorViewController.swift +++ b/Sources/GutenbergKit/Sources/EditorViewController.swift @@ -132,6 +132,8 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro } private func setUpEditor() { + registerRequestInterceptor() + let webViewConfiguration = webView.configuration let userContentController = webViewConfiguration.userContentController let editorInitialConfig = getEditorConfiguration() @@ -368,6 +370,64 @@ private final class GutenbergEditorController: NSObject, WKNavigationDelegate, W } } -private extension WKWebView { +class InterceptionProtocol: URLProtocol { + override class func canInit(with request: URLRequest) -> Bool { + + // We don't want to interfere with loading the editor JS + guard request.url?.host != "localhost" else { + debugPrint("Not intercepting \(request)") + return false + } + + // We care about WordPress.com resources – let's modify those if needed + if request.url?.host?.contains("wordpress.com") == true { + return request.value(forHTTPHeaderField: "Authorization") == nil // If there's no auth header, we need to intercept + } + + return false + } + + override class func canonicalRequest(for request: URLRequest) -> URLRequest { + var mutableRequest = request + mutableRequest.allHTTPHeaderFields?["Authorization"] = "Bearer [REDACTED]" + return mutableRequest + } + private var taskHandle: Task? + private let session: URLSession = URLSession(configuration: .ephemeral) + + override func startLoading() { + self.taskHandle = Task { + do { + let (data, response) = try await session.data(for: self.request) + self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .allowedInMemoryOnly) + self.client?.urlProtocol(self, didLoad: data) + self.client?.urlProtocolDidFinishLoading(self) + + } catch { + self.client?.urlProtocol(self, didFailWithError: error) + } + } + } + + override func stopLoading() { + self.taskHandle?.cancel() + } +} + +private extension EditorViewController { + + // Inject the interceptor + func registerRequestInterceptor() { + let browsingContextClass: AnyClass = NSClassFromString("WKBrowsingContextController")! + let registerSchemeSelector: Selector = NSSelectorFromString("registerSchemeForCustomProtocol:") +// let unregisterSchemeSelector: Selector = NSSelectorFromString("unregisterSchemeForCustomProtocol:") + + if browsingContextClass.responds(to: registerSchemeSelector) == true { + browsingContextClass.performSelector(inBackground: registerSchemeSelector, with: "http") + browsingContextClass.performSelector(inBackground: registerSchemeSelector, with: "https") + } + + URLProtocol.registerClass(InterceptionProtocol.self) + } }