-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Bug]: Capacitor on Android detected as web platform after redirecting with allowNavigation
#7454
Comments
So it looks like there are two parts to this issue: redirecting to a local address, and capacitor not injecting the JS bridge into external pages. Again, both of these are only relevant to Android, the iOS side works perfectly For the first issue, if we try to navigate to a computer on the local network (http, through ip address or If we add our remote server to the Investigating this issue, we found the root cause to be the JavaScript injection in Bridge.java. We notice that if Here's the diff: Original: capacitor/android/capacitor/src/main/java/com/getcapacitor/Bridge.java Lines 254 to 269 in f8264cc
New: // Start the local web server
JSInjector injector = getJSInjector();
if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
WebViewCompat.addDocumentStartJavaScript(webView, injector.getScriptString(), allowedOriginRules);
injector = null;
}
localServer = new WebViewLocalServer(context, this, injector, authorities, html5mode); I am new to capacitor and have no idea if this would have any side effects, but this seems to fix our issue completely and bring parity with the iOS version. So to summarize: The original issue still exists while redirecting to a computer on a local network (or just over http, I don't have the capability to test that right now) There also appears to be a bug when redirecting to a remote server (again, maybe just over https) caused by the native bridge not injecting capacitor's Javascript on remote pages. A fix we found was to make the change above, but I'd rather ask if it is the correct approach before creating a pull request. |
I'm experiencing this as well. I use the allowNavigation to switch between different stages of the app, and this prevents that workflow. Thank you for all of the digging you did, the detailed explanation, and workaround! |
We are experiencing this issue as well, and I cannot thank you enough for having found this workaround 🙇 I can confirm that changing the I have hacked together a quick "patch" that we call in the MainActivity, to avoid editing the Bridge.Java file directly (as it gets messy with version control). Here it is in case anyone wants to use it (call it after class MainActivity : BridgeActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/**
* ...
*/
patchJSInjection();
}
/**
* ...
*/
private fun patchJSInjection() {
try {
if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
val getJsInjector = bridge::class.java.declaredMethods.single { it.name == "getJSInjector" };
getJsInjector.isAccessible = true;
val injector = getJsInjector.invoke(bridge);
val getScriptString = injector::class.java.declaredMethods.single { it.name === "getScriptString" };
val scriptString = getScriptString.invoke(injector) as String;
val allowedOrigins: MutableSet<String> = mutableSetOf();
// Add origins that the Capacitor JS Bridge should be injected into
allowedOrigins.add("https://www.foo.bar");
WebViewCompat.addDocumentStartJavaScript(bridge.webView, scriptString, allowedOrigins)
}
}catch (e: Exception) {
Log.e("Error", e.message ?: "");
}
}
} |
@Vadinci huge thanks for that snippet. I hacked together similar in java: private void patchJSInjection() {
try {
if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
var getJsInjector = Arrays.stream(bridge.getClass().getDeclaredMethods())
.filter(method -> method.getName().equals("getJSInjector"))
.findFirst()
.get();
getJsInjector.setAccessible(true);
var injector = getJsInjector.invoke(bridge);
var getScriptString = Arrays.stream(injector.getClass().getDeclaredMethods())
.filter(method -> method.getName().equals("getScriptString"))
.findFirst()
.get();
var scriptString = (String) getScriptString.invoke(injector);
var allowedOrigins = Arrays.stream(bridge.getConfig().getAllowNavigation())
.filter(str -> str.contains("yourdomain.com") || str.contains("otherdomain.com"))
.filter(str -> str.contains("https://"))
// WebViewCompat likes things formatted particularly, trim trailing /*
.map(str -> str.replaceAll("/\\*$", ""))
.collect(Collectors.toSet());
Logger.info("patchJSInjection", "Injecting custom rules " + allowedOrigins);
WebViewCompat.addDocumentStartJavaScript(bridge.getWebView(), scriptString, allowedOrigins);
}
} catch (Exception e) {
Logger.error( e.getMessage(), e);
}
} |
I got same issue too. To resolve this issue, I apply a patch to const xfs = require('fs/promises')
const { glob } = require('glob')
module.exports = async () => {
const originPatten = `\
// Start the local web server
JSInjector injector = getJSInjector();
if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
String allowedOrigin = appUrl;
Uri appUri = Uri.parse(appUrl);
if (appUri.getPath() != null) {
if (appUri.getPath().equals("/")) {
allowedOrigin = appUrl.substring(0, appUrl.length() - 1);
} else {
allowedOrigin = appUri.toString().replace(appUri.getPath(), "");
}
}
WebViewCompat.addDocumentStartJavaScript(webView, injector.getScriptString(), Collections.singleton(allowedOrigin));
injector = null;
}
localServer = new WebViewLocalServer(context, this, injector, authorities, html5mode);`.split('\n')
const replacePatten = `\
// Patched local web server
JSInjector injector = getJSInjector();
if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
WebViewCompat.addDocumentStartJavaScript(webView, injector.getScriptString(), allowedOriginRules);
injector = null;
}
localServer = new WebViewLocalServer(context, this, injector, authorities, html5mode);`.split('\n')
let modified = false
for (const path of await glob('node_modules/.pnpm/**/Bridge.java')) {
const source = await xfs.readFile(path, 'utf8')
const lines = source.split('\n')
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (line.includes('// Start the local web server')) {
if (lines.slice(i, i + originPatten.length).join('\n') !== originPatten.join('\n')) {
console.log('Skipped: Pattern not matched')
continue
}
console.log('Modified: Pattern matched')
lines.splice(i, originPatten.length, ...replacePatten)
modified = true
break
}
}
if (modified) {
await xfs.writeFile(path, lines.join('\n'), 'utf8')
break
}
}
} |
In our case the issue was, that the script did not get injected on allowNavigation pages because it is not provided to the addDocumentStartJavaScript function: i also recommend using https://www.npmjs.com/package/patch-package for all these patches until it is being resolved. |
This issue has been labeled as |
Thank you so much for this. I was thinking it had something to do with the JS code. My allowedOrigins collection looks like this, as I have simple domain names in allowNavigation:
|
Capacitor Version
💊 Capacitor Doctor 💊
Latest Dependencies:
@capacitor/cli: 6.0.0
@capacitor/core: 6.0.0
@capacitor/android: 6.0.0
@capacitor/ios: 6.0.0
Installed Dependencies:
@capacitor/cli: 6.0.0
@capacitor/core: 6.0.0
@capacitor/ios: 6.0.0
@capacitor/android: 6.0.0
[success] iOS looking great! 👌
[success] Android looking great! 👌
Other API Details
Platforms Affected
Current Behavior
After redirecting to an external URL that is allowed in the
server.allowNavigation
config setting, an android app reports being on a web platform instead of android.This can be seen by calling
Capacitor.getPlatform()
(which returnsandroid
before redirecting andweb
after redirecting) or by trying to use a native API, which will either fallback to the web version of the API, or fail to run at allAll this works on iOS, where after redirecting, the app can still use native iOS APIs
Expected Behavior
After redirecting, the android app should still have access to all the native Android APIs (and report itself as being an Android platform)
Project Reproduction
https://github.com/Dylancyclone/capacitor-android-redirect/tree/main
Additional Information
Reproduction details are in the repo, but the basic reproduction steps are as follows:
npx serve .
)server.allowNavigation
capacitor config settingCapacitor.getPlatform()
returnsandroid
on android andios
on iOSwindow.location.assign()
,window.location.href = ""
,window.location.replace()
, or redirecting through a formCapacitor.getPlatform()
now returnsweb
on android but stillios
on iOSPossibly related to #5455
Here are videos showing the issue. Even though these were recorded on a simulator, the results are identical to a physical device
android.mov
ios.mov
Our use case is to have one app that connects to different backends on different subdomains, very similar to Slack. So far everything is working except the android app loses access to its native APIs
The text was updated successfully, but these errors were encountered: