diff --git a/README.md b/README.md index d080e02824..8161a6e774 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ Made possible by the Capacitor community. 💖

+ diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java index 9b6b019240..e9f20f1b5d 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java @@ -131,6 +131,9 @@ public class Bridge { // A list of listeners that trigger when webView events occur private List webViewListeners = new ArrayList<>(); + // An interface to manipulate route resolving + private RouteProcessor routeProcessor; + /** * Create the Bridge with a reference to the main {@link Activity} for the * app, and a reference to the {@link WebView} our app will use. @@ -1201,6 +1204,14 @@ void setWebViewListeners(List webViewListeners) { this.webViewListeners = webViewListeners; } + RouteProcessor getRouteProcessor() { + return routeProcessor; + } + + void setRouteProcessor(RouteProcessor routeProcessor) { + this.routeProcessor = routeProcessor; + } + /** * Add a listener that the WebViewClient can trigger on certain events. * @param webViewListener A {@link WebViewListener} to add. @@ -1224,6 +1235,7 @@ public static class Builder { private List> plugins = new ArrayList<>(); private AppCompatActivity activity; private Fragment fragment; + private RouteProcessor routeProcessor; private final List webViewListeners = new ArrayList<>(); public Builder(AppCompatActivity activity) { @@ -1276,6 +1288,11 @@ public Builder addWebViewListeners(List webViewListeners) { return this; } + public Builder setRouteProcessor(RouteProcessor routeProcessor) { + this.routeProcessor = routeProcessor; + return this; + } + public Bridge create() { // Cordova initialization ConfigXmlParser parser = new ConfigXmlParser(); @@ -1299,6 +1316,7 @@ public Bridge create() { Bridge bridge = new Bridge(activity, fragment, webView, plugins, cordovaInterface, pluginManager, preferences, config); bridge.setCordovaWebView(mockWebView); bridge.setWebViewListeners(webViewListeners); + bridge.setRouteProcessor(routeProcessor); if (instanceState != null) { bridge.restoreInstanceState(instanceState); diff --git a/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java b/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java index 18a7ea07e5..b260414d21 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java +++ b/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java @@ -12,11 +12,11 @@ public class BridgeActivity extends AppCompatActivity { protected Bridge bridge; protected boolean keepRunning = true; - private CapConfig config; + protected CapConfig config; - private int activityDepth = 0; - private List> initialPlugins = new ArrayList<>(); - private final Bridge.Builder bridgeBuilder = new Bridge.Builder(this); + protected int activityDepth = 0; + protected List> initialPlugins = new ArrayList<>(); + protected final Bridge.Builder bridgeBuilder = new Bridge.Builder(this); @Override protected void onCreate(Bundle savedInstanceState) { @@ -66,7 +66,7 @@ protected void load(Bundle savedInstanceState) { this.load(); } - private void load() { + protected void load() { Logger.debug("Starting BridgeActivity"); bridge = bridgeBuilder.addPlugins(initialPlugins).setConfig(config).create(); diff --git a/android/capacitor/src/main/java/com/getcapacitor/RouteProcessor.java b/android/capacitor/src/main/java/com/getcapacitor/RouteProcessor.java new file mode 100644 index 0000000000..077c20c6a7 --- /dev/null +++ b/android/capacitor/src/main/java/com/getcapacitor/RouteProcessor.java @@ -0,0 +1,8 @@ +package com.getcapacitor; + +/** + * An interface used in the processing of routes + */ +public interface RouteProcessor { + String process(String path); +} diff --git a/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java b/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java index e38cb05b8b..36ddb6bb37 100755 --- a/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java +++ b/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java @@ -24,7 +24,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; -import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; @@ -256,6 +255,10 @@ private WebResourceResponse handleLocalRequest(WebResourceRequest request, PathH InputStream responseStream; try { String startPath = this.basePath + "/index.html"; + if (bridge.getRouteProcessor() != null) { + startPath = this.basePath + bridge.getRouteProcessor().process("/index.html"); + } + if (isAsset) { responseStream = protocolHandler.openAsset(startPath); } else { @@ -467,6 +470,13 @@ private void createHostingDetails() { public InputStream handle(Uri url) { InputStream stream = null; String path = url.getPath(); + + // Pass path to routeProcessor if present + RouteProcessor routeProcessor = bridge.getRouteProcessor(); + if (routeProcessor != null) { + path = bridge.getRouteProcessor().process(path); + } + try { if (path.startsWith(capacitorContentStart)) { stream = protocolHandler.openContentUrl(url); diff --git a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj index a0d4ea61d7..979e61bad8 100644 --- a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj +++ b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj @@ -80,6 +80,8 @@ 62FABD1A25AE5C01007B3814 /* Array+Capacitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FABD1925AE5C01007B3814 /* Array+Capacitor.swift */; }; 62FABD2325AE60BA007B3814 /* BridgedTypesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 62FABD2225AE60BA007B3814 /* BridgedTypesTests.m */; }; 62FABD2B25AE6182007B3814 /* BridgedTypesHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FABD2A25AE6182007B3814 /* BridgedTypesHelper.swift */; }; + A71289E627F380A500DADDF3 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71289E527F380A500DADDF3 /* Router.swift */; }; + A71289EB27F380FD00DADDF3 /* RouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71289EA27F380FD00DADDF3 /* RouterTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -217,6 +219,8 @@ 62FABD1925AE5C01007B3814 /* Array+Capacitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Capacitor.swift"; sourceTree = ""; }; 62FABD2225AE60BA007B3814 /* BridgedTypesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BridgedTypesTests.m; sourceTree = ""; }; 62FABD2A25AE6182007B3814 /* BridgedTypesHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgedTypesHelper.swift; sourceTree = ""; }; + A71289E527F380A500DADDF3 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; + A71289EA27F380FD00DADDF3 /* RouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouterTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -292,6 +296,7 @@ 621ECCCD254204C400D3D615 /* CapacitorTests-Bridging-Header.h */, 62E79C712638B23300414164 /* JSExportTests.swift */, 62959BBD2526510200A3D7F1 /* Info.plist */, + A71289EA27F380FD00DADDF3 /* RouterTests.swift */, ); path = CapacitorTests; sourceTree = ""; @@ -349,6 +354,7 @@ 373A69C0255C9360000A6F44 /* NotificationHandlerProtocol.swift */, 373A69F1255C95D0000A6F44 /* NotificationRouter.swift */, 62E79C562638AF7500414164 /* assets */, + A71289E527F380A500DADDF3 /* Router.swift */, ); path = Capacitor; sourceTree = ""; @@ -580,6 +586,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A71289E627F380A500DADDF3 /* Router.swift in Sources */, 62959B362524DA7800A3D7F1 /* CAPBridgeViewController.swift in Sources */, 621ECCB72542045900D3D615 /* CAPBridgedJSTypes.m in Sources */, 62959B402524DA7800A3D7F1 /* TmpViewController.swift in Sources */, @@ -634,6 +641,7 @@ 62A91C3425535F5700861508 /* ConfigurationTests.swift in Sources */, 62FABD2325AE60BA007B3814 /* BridgedTypesTests.m in Sources */, 621ECCC3254204B700D3D615 /* BridgedTypesTests.swift in Sources */, + A71289EB27F380FD00DADDF3 /* RouterTests.swift in Sources */, 6263686025F6EC0100576C1C /* PluginCallAccessorTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift index 12c3abaaab..865909b520 100644 --- a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift +++ b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift @@ -42,7 +42,7 @@ import Cordova setScreenOrientationDefaults() // get the web view - let assetHandler = WebViewAssetHandler() + let assetHandler = WebViewAssetHandler(router: router()) assetHandler.setAssetPath(configuration.appLocation.path) let delegationHandler = WebViewDelegationHandler() prepareWebView(with: configuration, assetHandler: assetHandler, delegationHandler: delegationHandler) @@ -89,7 +89,11 @@ import Cordova } return descriptor } - + + open func router() -> Router { + return _Router() + } + /** The WKWebViewConfiguration to use for the webview. diff --git a/ios/Capacitor/Capacitor/Router.swift b/ios/Capacitor/Capacitor/Router.swift new file mode 100644 index 0000000000..6ac417d31f --- /dev/null +++ b/ios/Capacitor/Capacitor/Router.swift @@ -0,0 +1,27 @@ +// +// Router.swift +// Capacitor +// +// Created by Steven Sherry on 3/29/22. +// Copyright © 2022 Drifty Co. All rights reserved. +// + +import Foundation + +public protocol Router { + func route(for path: String) -> String +} + +// swiftlint:disable:next type_name +internal struct _Router: Router { + func route(for path: String) -> String { + let pathUrl = URL(string: path) + + // if the pathUrl is null, then it is an invalid url (meaning it is empty or just plain invalid) then we want to route to /index.html + if pathUrl?.pathExtension.isEmpty ?? true { + return "/index.html" + } + + return path + } +} diff --git a/ios/Capacitor/Capacitor/WebViewAssetHandler.swift b/ios/Capacitor/Capacitor/WebViewAssetHandler.swift index 9dbff7a0cc..da63326d3b 100644 --- a/ios/Capacitor/Capacitor/WebViewAssetHandler.swift +++ b/ios/Capacitor/Capacitor/WebViewAssetHandler.swift @@ -3,8 +3,13 @@ import MobileCoreServices @objc(CAPWebViewAssetHandler) internal class WebViewAssetHandler: NSObject, WKURLSchemeHandler { - + private let router: Router private var basePath: String = "" + + init(router: Router) { + self.router = router + super.init() + } func setAssetPath(_ assetPath: String) { self.basePath = assetPath @@ -14,14 +19,14 @@ internal class WebViewAssetHandler: NSObject, WKURLSchemeHandler { var startPath = self.basePath let url = urlSchemeTask.request.url! let stringToLoad = url.path - + + let resolvedRoute = router.route(for: stringToLoad) if stringToLoad.starts(with: CapacitorBridge.fileStartIdentifier) { startPath = stringToLoad.replacingOccurrences(of: CapacitorBridge.fileStartIdentifier, with: "") - } else if stringToLoad.isEmpty || url.pathExtension.isEmpty { - startPath.append("/index.html") } else { - startPath.append(stringToLoad) + startPath.append(resolvedRoute) } + let localUrl = URL.init(string: url.absoluteString)! let fileUrl = URL.init(fileURLWithPath: startPath) diff --git a/ios/Capacitor/CapacitorTests/CapacitorTests.swift b/ios/Capacitor/CapacitorTests/CapacitorTests.swift index 00131f4390..beb4b34458 100644 --- a/ios/Capacitor/CapacitorTests/CapacitorTests.swift +++ b/ios/Capacitor/CapacitorTests/CapacitorTests.swift @@ -23,7 +23,7 @@ class CapacitorTests: XCTestCase { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. let descriptor = InstanceDescriptor.init() - bridge = MockBridge(with: InstanceConfiguration(with: descriptor, isDebug: true), delegate: MockBridgeViewController(), cordovaConfiguration: descriptor.cordovaConfiguration, assetHandler: MockAssetHandler(), delegationHandler: MockDelegationHandler()) + bridge = MockBridge(with: InstanceConfiguration(with: descriptor, isDebug: true), delegate: MockBridgeViewController(), cordovaConfiguration: descriptor.cordovaConfiguration, assetHandler: MockAssetHandler(router: _Router()), delegationHandler: MockDelegationHandler()) } override func tearDown() { diff --git a/ios/Capacitor/CapacitorTests/RouterTests.swift b/ios/Capacitor/CapacitorTests/RouterTests.swift new file mode 100644 index 0000000000..f328c9c7ca --- /dev/null +++ b/ios/Capacitor/CapacitorTests/RouterTests.swift @@ -0,0 +1,30 @@ +// +// RouterTests.swift +// CapacitorTests +// +// Created by Steven Sherry on 3/29/22. +// Copyright © 2022 Drifty Co. All rights reserved. +// + +import XCTest +@testable import Capacitor + +class RouterTests: XCTestCase { + let router = _Router() + + func testRouterReturnsIndexWhenProvidedInvalidPath() { + XCTAssertEqual(router.route(for: "/skull.💀"), "/index.html") + } + + func testRouterReturnsIndexWhenProvidedEmptyPath() { + XCTAssertEqual(router.route(for: ""), "/index.html") + } + + func testRouterReturnsIndexWhenProviedPathWithoutExtension() { + XCTAssertEqual(router.route(for: "/a/valid/path/no/ext"), "/index.html") + } + + func testRouterReturnsPathWhenProvidedValidPath() { + XCTAssertEqual(router.route(for: "/a/valid/path.ext"), "/a/valid/path.ext") + } +}