diff --git a/android/capacitor/src/main/java/com/getcapacitor/AndroidProtocolHandler.java b/android/capacitor/src/main/java/com/getcapacitor/AndroidProtocolHandler.java index 430207c145..4319fc2a2b 100755 --- a/android/capacitor/src/main/java/com/getcapacitor/AndroidProtocolHandler.java +++ b/android/capacitor/src/main/java/com/getcapacitor/AndroidProtocolHandler.java @@ -69,14 +69,17 @@ private static int getFieldId(Context context, String assetType, String assetNam } public InputStream openFile(String filePath) throws IOException { - File localFile = new File(filePath); + String realPath = filePath.replace(Bridge.CAPACITOR_FILE_START, ""); + File localFile = new File(realPath); return new FileInputStream(localFile); } public InputStream openContentUrl(Uri uri) throws IOException { + String realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + Bridge.CAPACITOR_CONTENT_START, "content:/"); + InputStream stream = null; try { - stream = context.getContentResolver().openInputStream(Uri.parse(uri.toString().replace(Bridge.CAPACITOR_CONTENT_SCHEME_NAME + ":///", "content://"))); + stream = context.getContentResolver().openInputStream(Uri.parse(realPath)); } catch (SecurityException e) { Log.e(LogUtils.getCoreTag(), "Unable to open content URL: " + uri, e); } diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java index 0ef3272709..fb49d8a928 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java @@ -84,8 +84,8 @@ public class Bridge { // The name of the directory we use to look for index.html and the rest of our web assets public static final String DEFAULT_WEB_ASSET_DIR = "public"; public static final String CAPACITOR_SCHEME_NAME = "http"; - public static final String CAPACITOR_FILE_SCHEME_NAME = "capacitor-file"; - public static final String CAPACITOR_CONTENT_SCHEME_NAME = "capacitor-content"; + public static final String CAPACITOR_FILE_START = "/_capacitor_file_"; + public static final String CAPACITOR_CONTENT_START = "/_capacitor_content_"; // Loaded Capacitor config private JSONObject config = new JSONObject(); @@ -163,16 +163,18 @@ private void loadWebView() { appUrlConfig = Config.getString("server.url"); appAllowNavigationConfig = Config.getArray("server.allowNavigation"); + ArrayList authorities = new ArrayList(); + if (appAllowNavigationConfig != null) { + authorities.addAll(Arrays.asList(appAllowNavigationConfig)); + } String authority = Config.getString("server.hostname", "localhost"); - localUrl = CAPACITOR_SCHEME_NAME + "://" + authority + "/"; - - boolean isLocal = true; + authorities.add(authority); + localUrl = CAPACITOR_SCHEME_NAME + "://" + authority; if (appUrlConfig != null) { try { URL appUrlObject = new URL(appUrlConfig); - authority = appUrlObject.getAuthority(); - isLocal = false; + authorities.add(appUrlObject.getAuthority()); } catch (Exception ex) { } @@ -182,7 +184,7 @@ private void loadWebView() { final boolean html5mode = Config.getBoolean("server.html5mode", true); // Start the local web server - localServer = new WebViewLocalServer(context, this, getJSInjector(), authority, html5mode, isLocal); + localServer = new WebViewLocalServer(context, this, getJSInjector(), authorities, html5mode); localServer.hostAssets(DEFAULT_WEB_ASSET_DIR); @@ -197,13 +199,6 @@ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceReque return localServer.shouldInterceptRequest(request); } - @Override - public void onPageFinished(WebView view, final String location) { - if (appUrlConfig != null || appAllowNavigationConfig != null) { - injectScriptFile(view, getJSInjector().getScriptString()); - } - } - @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { Uri url = request.getUrl(); @@ -241,8 +236,8 @@ private boolean launchIntent(Uri url) { public void handleAppUrlLoadError(Exception ex) { if (ex instanceof SocketTimeoutException) { - Toast.show(getContext(), "Unable to load app. Are you sure the server is running at " + localServer.getAuthority() + "?"); - Log.e(LOG_TAG, "Unable to load app. Ensure the server is running at " + localServer.getAuthority() + ", or modify the " + + Toast.show(getContext(), "Unable to load app. Are you sure the server is running at " + appUrl + "?"); + Log.e(LOG_TAG, "Unable to load app. Ensure the server is running at " + appUrl + ", or modify the " + "appUrl setting in capacitor.config.json (make sure to npx cap copy after to commit changes).", ex); } } @@ -794,20 +789,6 @@ public void run() { }); } - private void injectScriptFile(WebView view, String script) { - - // Base64 encode string before injecting as innerHTML - String encoded = Base64.encodeToString(script.getBytes(), Base64.NO_WRAP); - view.loadUrl("javascript:(function() {" + - "var parent = document.getElementsByTagName('head').item(0);" + - "var script = document.createElement('script');" + - "script.type = 'text/javascript';" + - // Base64 decode injected javascript - "script.innerHTML = window.atob('" + encoded + "');" + - "parent.appendChild(script)" + - "})()"); - } - private boolean matchHost(String host, String pattern) { int offset; @@ -826,11 +807,17 @@ private boolean matchHost(String host, String pattern) { } private boolean matchHosts(String host, String[] patterns) { - for (String pattern: patterns) { - if (matchHost(host, pattern)) { - return true; + if (patterns != null) { + for (String pattern: patterns) { + if (matchHost(host, pattern)) { + return true; + } } } return false; } + + public String getLocalUrl() { + return localUrl; + } } diff --git a/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java b/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java index 012b24cbb5..fe0aee679b 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java +++ b/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java @@ -42,7 +42,7 @@ */ public class FileUtils { - private static String CapacitorFileScheme = Bridge.CAPACITOR_FILE_SCHEME_NAME + "://"; + private static String CapacitorFileScheme = Bridge.CAPACITOR_FILE_START; public enum Type { IMAGE("image"); @@ -52,14 +52,14 @@ public enum Type { } } - public static String getPortablePath(Context c, Uri u) { + public static String getPortablePath(Context c, String host, Uri u) { String path = getFileUrlForUri(c, u); if (path.startsWith("file://")) { - path = path.replace("file://", CapacitorFileScheme); + path = path.replace("file://", ""); } else if (path.startsWith("/")) { - path = CapacitorFileScheme + path; + path = path; } - return path; + return host + Bridge.CAPACITOR_FILE_START + path; } public static String getFileUrlForUri(final Context context, final Uri uri) { diff --git a/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java b/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java index 26027afded..1c8f3cb3b6 100755 --- a/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java +++ b/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java @@ -27,6 +27,7 @@ import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLConnection; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -45,24 +46,19 @@ public class WebViewLocalServer { private final static String capacitorScheme = Bridge.CAPACITOR_SCHEME_NAME; - private final static String capacitorFileScheme = Bridge.CAPACITOR_FILE_SCHEME_NAME; - private final static String capacitorContentScheme = Bridge.CAPACITOR_CONTENT_SCHEME_NAME; + private final static String capacitorFileStart = Bridge.CAPACITOR_FILE_START; + private final static String capacitorContentStart = Bridge.CAPACITOR_CONTENT_START; private String basePath; private final UriMatcher uriMatcher; private final AndroidProtocolHandler protocolHandler; - private final String authority; - // Whether we're serving local files or proxying (for example, when doing livereload on a - // non-local endpoint (will be false in that case) - private final boolean isLocal; + private final ArrayList authorities; private boolean isAsset; // Whether to route all requests to paths without extensions back to `index.html` private final boolean html5mode; private final JSInjector jsInjector; private final Bridge bridge; - public String getAuthority() { return authority; } - /** * A handler that produces responses for paths on the virtual asset server. *

@@ -132,12 +128,11 @@ public Map getResponseHeaders() { } } - WebViewLocalServer(Context context, Bridge bridge, JSInjector jsInjector, String authority, boolean html5mode, boolean isLocal) { + WebViewLocalServer(Context context, Bridge bridge, JSInjector jsInjector, ArrayList authorities, boolean html5mode) { uriMatcher = new UriMatcher(null); this.html5mode = html5mode; this.protocolHandler = new AndroidProtocolHandler(context.getApplicationContext()); - this.authority = authority; - this.isLocal = isLocal; + this.authorities = authorities; this.bridge = bridge; this.jsInjector = jsInjector; } @@ -169,6 +164,7 @@ private static Uri parseAndVerifyUrl(String url) { * @return a response if the request URL had a matching handler, null if no handler was found. */ public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) { + Uri loadingUrl = request.getUrl(); PathHandler handler; synchronized (uriMatcher) { handler = (PathHandler) uriMatcher.match(request.getUrl()); @@ -177,7 +173,7 @@ public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) { return null; } - if (request.getUrl().getScheme().equals(capacitorContentScheme) || request.getUrl().getScheme().equals(capacitorFileScheme)) { + if (isLocalFile(loadingUrl)) { InputStream responseStream = new LollipopLazyInputStream(handler, request); String mimeType = getMimeType(request.getUrl().getPath(), responseStream); int statusCode = getStatusCode(responseStream, handler.getStatusCode()); @@ -185,7 +181,7 @@ public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) { statusCode, handler.getReasonPhrase(), handler.getResponseHeaders(), responseStream); } - if (this.isLocal) { + if (loadingUrl.toString().startsWith(bridge.getLocalUrl())) { Log.d(LogUtils.getCoreTag(), "Handling local request: " + request.getUrl().toString()); return handleLocalRequest(request, handler); } else { @@ -193,6 +189,14 @@ public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) { } } + private boolean isLocalFile(Uri uri) { + String path = uri.getPath(); + if (path.startsWith(capacitorContentStart) || path.startsWith(capacitorFileStart)) { + return true; + } + return false; + } + private WebResourceResponse handleLocalRequest(WebResourceRequest request, PathHandler handler) { String path = request.getUrl().getPath(); @@ -379,58 +383,6 @@ public void hostAssets(String assetPath) { createHostingDetails(); } - - /** - * Hosts the application's resources on an https:// URL. Resources - * https://{uuid}.androidplatform.net/res/{resource_type}/{resource_name}. - * - * @return prefixes under which the resources are hosted. - */ - public void hostResources() { - hostResources("/res"); - } - - /** - * Hosts the application's resources on an https:// URL. Resources - * https://{domain}/{virtualResourcesPath}/{resource_type}/{resource_name}. - * - * @param virtualResourcesPath the path on the local server under which the resources - * should be hosted. - * @return prefixes under which the resources are hosted. - */ - public void hostResources(final String virtualResourcesPath) { - if (virtualResourcesPath.indexOf('*') != -1) { - throw new IllegalArgumentException( - "virtualResourcesPath cannot contain the '*' character."); - } - - Uri.Builder uriBuilder = new Uri.Builder(); - uriBuilder.scheme(capacitorScheme); - uriBuilder.authority(authority); - uriBuilder.path(virtualResourcesPath); - Uri capacitorPrefix = null; - - PathHandler handler = new PathHandler() { - @Override - public InputStream handle(Uri url) { - InputStream stream = protocolHandler.openResource(url); - String mimeType = null; - try { - mimeType = URLConnection.guessContentTypeFromStream(stream); - } catch (Exception ex) { - Log.e(LogUtils.getPluginTag("LN"), "Unable to get mime type" + url); - } - - return stream; - } - }; - - uriBuilder.scheme(capacitorScheme); - capacitorPrefix = uriBuilder.build(); - register(Uri.withAppendedPath(capacitorPrefix, "**"), handler); - - } - /** * Hosts the application's files on an https:// URL. Files from the basePath * basePath/... will be available under @@ -453,23 +405,22 @@ private void createHostingDetails() { throw new IllegalArgumentException("assetPath cannot contain the '*' character."); } - Uri capacitorPrefix = null; - PathHandler handler = new PathHandler() { @Override public InputStream handle(Uri url) { InputStream stream = null; String path = url.getPath(); - if (!isAsset) { - path = basePath + url.getPath(); - } try { - if (url.getScheme().equals(capacitorScheme) && isAsset) { - stream = protocolHandler.openAsset(assetPath + path); - } else if (url.getScheme().equals(capacitorFileScheme) || !isAsset) { - stream = protocolHandler.openFile(path); - } else if (url.getScheme().equals(capacitorContentScheme)) { + if (path.startsWith(capacitorContentStart)) { stream = protocolHandler.openContentUrl(url); + } else if (path.startsWith(capacitorFileStart) || !isAsset) { + if (!path.startsWith(capacitorFileStart)) { + path = basePath + url.getPath(); + } + System.out.println("path "+path); + stream = protocolHandler.openFile(path); + } else { + stream = protocolHandler.openAsset(assetPath + path); } } catch (IOException e) { Log.e(LogUtils.getCoreTag(), "Unable to open asset URL: " + url); @@ -480,9 +431,9 @@ public InputStream handle(Uri url) { } }; - registerUriForScheme(capacitorScheme, handler, authority); - registerUriForScheme(capacitorFileScheme, handler, ""); - registerUriForScheme(capacitorContentScheme, handler, ""); + for (String authority: authorities) { + registerUriForScheme(capacitorScheme, handler, authority); + } } diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/Camera.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/Camera.java index 0074a1e9e8..4742712d77 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/Camera.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/Camera.java @@ -353,7 +353,7 @@ private void returnFileURI(PluginCall call, ExifWrapper exif, Bitmap bitmap, Uri JSObject ret = new JSObject(); ret.put("exif", exif.toJson()); ret.put("path", newUri.toString()); - ret.put("webPath", FileUtils.getPortablePath(getContext(), newUri)); + ret.put("webPath", FileUtils.getPortablePath(getContext(), bridge.getLocalUrl(), newUri)); call.resolve(ret); } catch (IOException ex) { call.reject(UNABLE_TO_PROCESS_IMAGE, ex); diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/Filesystem.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/Filesystem.java index 2cec9d97c5..6757a0ca9c 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/Filesystem.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/Filesystem.java @@ -77,7 +77,7 @@ private File getDirectory(String directory) { private File getFileObject(String path, String directory) { if (directory == null) { Uri u = Uri.parse(path); - if (u.getScheme().equals("file")) { + if (u.getScheme() == null || u.getScheme().equals("file")) { return new File(u.getPath()); } } diff --git a/core/native-bridge.js b/core/native-bridge.js index 3eaccdf17a..4b3f4631cc 100644 --- a/core/native-bridge.js +++ b/core/native-bridge.js @@ -508,13 +508,13 @@ return url; } if (url.startsWith('/')) { - return 'capacitor-file://' + url; + return window.WEBVIEW_SERVER_URL + '/_capacitor_file_' + url; } if (url.startsWith('file://')) { - return url.replace('file', 'capacitor-file'); + return window.WEBVIEW_SERVER_URL + url.replace('file://', '/_capacitor_file_'); } - if (url.startsWith('content:')) { - return url.replace('content:', 'capacitor-content:/'); + if (url.startsWith('content://')) { + return window.WEBVIEW_SERVER_URL + url.replace('content:/', '/_capacitor_content_'); } return url; } diff --git a/ios-template/App/Podfile b/ios-template/App/Podfile index e92381d1e5..f8b17c1f18 100644 --- a/ios-template/App/Podfile +++ b/ios-template/App/Podfile @@ -1,11 +1,14 @@ platform :ios, '11.0' use_frameworks! -target 'App' do - # Add your Pods here - +def capacitor_pods # Automatic Capacitor Pod dependencies, do not delete pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' # Do not delete end + +target 'App' do + capacitor_pods + # Add your Pods here +end diff --git a/ios/Capacitor/Capacitor/CAPAssetHandler.swift b/ios/Capacitor/Capacitor/CAPAssetHandler.swift index e484134ad4..c48c8a8425 100644 --- a/ios/Capacitor/Capacitor/CAPAssetHandler.swift +++ b/ios/Capacitor/Capacitor/CAPAssetHandler.swift @@ -13,13 +13,11 @@ class CAPAssetHandler: NSObject, WKURLSchemeHandler { startPath = Bundle.main.path(forResource: "public", ofType: nil)! if stringToLoad.isEmpty || url.pathExtension.isEmpty { startPath.append("/index.html") + } else if stringToLoad.starts(with: CAPBridge.CAP_FILE_START) { + startPath = stringToLoad.replacingOccurrences(of: CAPBridge.CAP_FILE_START, with: "") } else { startPath.append(stringToLoad) } - } else if scheme == CAPBridge.CAP_FILE_SCHEME { - if !stringToLoad.isEmpty { - startPath = stringToLoad - } } let localUrl = URL.init(string: url.absoluteString)! diff --git a/ios/Capacitor/Capacitor/CAPBridge.swift b/ios/Capacitor/Capacitor/CAPBridge.swift index ae0aafc4ed..ebbfed8c94 100644 --- a/ios/Capacitor/Capacitor/CAPBridge.swift +++ b/ios/Capacitor/Capacitor/CAPBridge.swift @@ -12,7 +12,7 @@ enum BridgeError: Error { public static let statusBarTappedNotification = Notification(name: Notification.Name(rawValue: "statusBarTappedNotification")) public static var CAP_SITE = "https://getcapacitor.com/" public static var CAP_SCHEME = "capacitor" - public static var CAP_FILE_SCHEME = "capacitor-file" + public static var CAP_FILE_START = "/_capacitor_file_" // The last URL that caused the app to open private static var lastUrl: URL? diff --git a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift index 7b42d1ad7b..6b828f9391 100644 --- a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift +++ b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift @@ -40,7 +40,6 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScr let webViewConfiguration = WKWebViewConfiguration() webViewConfiguration.setURLSchemeHandler(CAPAssetHandler(), forURLScheme: CAPBridge.CAP_SCHEME) - webViewConfiguration.setURLSchemeHandler(CAPAssetHandler(), forURLScheme: CAPBridge.CAP_FILE_SCHEME) let o = WKUserContentController() o.add(self, name: "bridge") diff --git a/ios/Capacitor/Capacitor/CAPFile.swift b/ios/Capacitor/Capacitor/CAPFile.swift index d72f7bcb14..a59e04c956 100644 --- a/ios/Capacitor/Capacitor/CAPFile.swift +++ b/ios/Capacitor/Capacitor/CAPFile.swift @@ -27,10 +27,10 @@ public class CAPFile { return nil } - public static func getPortablePath(uri: URL?) -> String? { + public static func getPortablePath(host: String, uri: URL?) -> String? { if uri != nil { - let assetUrl = uri!.absoluteString.replacingOccurrences(of: "file://", with: "\(CAPBridge.CAP_FILE_SCHEME)://") - return assetUrl + let uriWithoutFile = uri!.absoluteString.replacingOccurrences(of: "file://", with: "") + return host + CAPBridge.CAP_FILE_START + uriWithoutFile } return nil } diff --git a/ios/Capacitor/Capacitor/Plugins/Camera.swift b/ios/Capacitor/Capacitor/Plugins/Camera.swift index b66e9bf1ff..ac231d6f9d 100644 --- a/ios/Capacitor/Capacitor/Plugins/Camera.swift +++ b/ios/Capacitor/Capacitor/Plugins/Camera.swift @@ -223,7 +223,7 @@ public class CAPCameraPlugin : CAPPlugin, UIImagePickerControllerDelegate, UINav ]) } else if settings.resultType == "uri" { let path = try! saveTemporaryImage(jpeg) - guard let webPath = CAPFileManager.getPortablePath(uri: URL(string: path)) else { + guard let webPath = CAPFileManager.getPortablePath(host: bridge.getLocalUrl(), uri: URL(string: path)) else { call?.reject("Unable to get portable path to file") return }