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")
+ }
+}