mirror of
https://github.com/expo/config-plugins.git
synced 2026-11-13 20:28:07 -10:00
chore(networkConfig): Fix android crash on empty subdomains config (#46)
## Background
The app crashes when `detox` plugin is configured with an empty subdomain in cases where it's desired to allow all traffic.
## Cause
```
[
"@config-plugins/detox",
{
"subdomains": []
}
]
```
The configuration above generates an invalid xml resulting in a `Caused by: [android.security.net](http://android.security.net/).config.XmlConfigSource$ParserException: No domain elements in domain-config at: Binary XML file line #3` as it generates
```
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
</domain-config>
</network-security-config>
```
## Fix
This PR inserts a `base-config` instead of a `domain-config` when subdomains is "*" to allow all traffic.
```
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
```
It also omits making the network security config XML file when subdomains is an empty array.
This commit is contained in:
1 parent
ec2bbedf8b
commit
88e0cd0ccf
8 files changed
+173
-29
No files matched your search
@@ -53,6 +53,29 @@ Detox is an end-to-end (e2e) testing library for iOS and Android. You can use it
|
||||
- Generate the native code `expo prebuild`
|
||||
- Run `yarn detox init -r jest`
|
||||
|
||||
## API
|
||||
|
||||
The plugin provides props for extra customization. Every time you change the props or plugins, you'll need to rebuild (and `prebuild`) the native app. If no extra properties are added, defaults will be used.
|
||||
|
||||
- `skipProguard` (_boolean_): Disable adding proguard minification to the `app/build.gradle`. Defaults to `false`.
|
||||
- `subdomains` (_string[] | '*'_): Hostnames to add to the network security config. Pass `'*'` to allow all domains. Defaults to `['10.0.2.2', 'localhost']`.
|
||||
|
||||
`app.config.js`
|
||||
|
||||
```ts
|
||||
export default {
|
||||
plugins: [
|
||||
[
|
||||
"@config-plugins/detox",
|
||||
{
|
||||
skipProguard: false,
|
||||
subdomains: ["10.0.2.2", "localhost"],
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
If the following commands fail, you can get better debug info by running a subset command:
|
||||
|
||||
+3
-1
@@ -1,4 +1,5 @@
|
||||
import { ConfigPlugin } from "@expo/config-plugins";
|
||||
import { SubdomainsType } from "./withNetworkSecurityConfig";
|
||||
declare const _default: ConfigPlugin<void | {
|
||||
/**
|
||||
* Disable adding proguard minification to the `app/build.gradle`.
|
||||
@@ -9,9 +10,10 @@ declare const _default: ConfigPlugin<void | {
|
||||
/**
|
||||
* Subdomains to add to the network security config.
|
||||
* Pass `['10.0.3.2', 'localhost']` to use Genymotion emulators instead of Google emulators.
|
||||
* Pass `*` to allow all domains.
|
||||
*
|
||||
* @default ['10.0.2.2', 'localhost'] // (Google emulators)
|
||||
*/
|
||||
subdomains?: string[] | undefined;
|
||||
subdomains?: SubdomainsType | undefined;
|
||||
}>;
|
||||
export default _default;
|
||||
+3
-2
@@ -1,8 +1,9 @@
|
||||
import { ConfigPlugin } from "@expo/config-plugins";
|
||||
export declare function getTemplateFile(subdomains: string[]): string;
|
||||
export declare type SubdomainsType = string[] | "*";
|
||||
export declare function getTemplateFile(subdomains: SubdomainsType): string;
|
||||
/**
|
||||
* [Step 6](https://github.com/wix/Detox/blob/master/docs/Introduction.Android.md#6-enable-clear-text-unencrypted-traffic-for-detox). Link the `network_security_config.xml` file to the `AndroidManifest.xml`.
|
||||
*/
|
||||
export declare const withNetworkSecurityConfigManifest: ConfigPlugin<{
|
||||
subdomains: string[];
|
||||
subdomains: SubdomainsType;
|
||||
} | void>;
|
||||
+21
-6
@@ -27,16 +27,27 @@ const config_plugins_1 = require("@expo/config-plugins");
|
||||
const path = __importStar(require("path"));
|
||||
const fs = __importStar(require("fs"));
|
||||
const assert_1 = __importDefault(require("assert"));
|
||||
function getTemplateFile(subdomains) {
|
||||
return `<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
function getTemplateConfigContent(subdomains) {
|
||||
if (subdomains === "*") {
|
||||
// allow all domains
|
||||
return '<base-config cleartextTrafficPermitted="true" />';
|
||||
}
|
||||
return `
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
${subdomains
|
||||
${subdomains
|
||||
.map((subdomain) => `<domain includeSubdomains="true">${subdomain}</domain>`)
|
||||
.join("")}
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
function getTemplateFile(subdomains) {
|
||||
const content = getTemplateConfigContent(subdomains);
|
||||
return `
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
${content}
|
||||
</network-security-config>
|
||||
`;
|
||||
}
|
||||
exports.getTemplateFile = getTemplateFile;
|
||||
/**
|
||||
@@ -65,6 +76,10 @@ const withNetworkSecurityConfigManifest = (config, props) => {
|
||||
// https://developer.android.com/training/articles/security-config
|
||||
props = { subdomains: ["10.0.2.2", "localhost"] };
|
||||
}
|
||||
if (typeof props.subdomains === "object" && !props.subdomains.length) {
|
||||
// if subdomains is an empty array, skip network config mod
|
||||
return config;
|
||||
}
|
||||
config = withNetworkSecurityConfigFile(config, props);
|
||||
return config_plugins_1.withAndroidManifest(config, (config) => {
|
||||
const application = config_plugins_1.AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`getTemplateFile returns domain-config when subdomains is an array 1`] = `
|
||||
"
|
||||
<?xml version=\\"1.0\\" encoding=\\"utf-8\\"?>
|
||||
<network-security-config>
|
||||
|
||||
<domain-config cleartextTrafficPermitted=\\"true\\">
|
||||
<domain includeSubdomains=\\"true\\">localhost</domain>
|
||||
</domain-config>
|
||||
|
||||
</network-security-config>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`getTemplateFile returns only base-config when subdomains is '*' 1`] = `
|
||||
"
|
||||
<?xml version=\\"1.0\\" encoding=\\"utf-8\\"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted=\\"true\\" />
|
||||
</network-security-config>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`withNetworkSecurityConfigManifest doesn't modify config if subdomains is an empty array 1`] = `
|
||||
Object {
|
||||
"name": "foo",
|
||||
"slug": "bar",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`withNetworkSecurityConfigManifest modifies config if subdomains is not empty 1`] = `
|
||||
Object {
|
||||
"mods": Object {
|
||||
"android": Object {
|
||||
"dangerous": [Function],
|
||||
"manifest": [Function],
|
||||
},
|
||||
},
|
||||
"name": "foo",
|
||||
"slug": "bar",
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
getTemplateFile,
|
||||
withNetworkSecurityConfigManifest,
|
||||
} from "../withNetworkSecurityConfig";
|
||||
|
||||
describe(getTemplateFile, () => {
|
||||
it(`returns only base-config when subdomains is '*'`, () => {
|
||||
const content = getTemplateFile("*");
|
||||
expect(content).toMatchSnapshot();
|
||||
expect(content).toMatch(/base\-config/);
|
||||
expect(content).not.toMatch(/domain\-config/);
|
||||
});
|
||||
it(`returns domain-config when subdomains is an array`, () => {
|
||||
const content = getTemplateFile(["localhost"]);
|
||||
expect(content).toMatchSnapshot();
|
||||
expect(content).toMatch(/domain\-config/);
|
||||
expect(content).toMatch(/localhost/);
|
||||
});
|
||||
});
|
||||
|
||||
const exp = { name: "foo", slug: "bar" };
|
||||
|
||||
describe(withNetworkSecurityConfigManifest, () => {
|
||||
it(`doesn't modify config if subdomains is an empty array`, () => {
|
||||
const applied = withNetworkSecurityConfigManifest(exp, {
|
||||
subdomains: [],
|
||||
});
|
||||
expect(applied).toMatchSnapshot();
|
||||
expect(applied).toStrictEqual(exp);
|
||||
});
|
||||
it(`modifies config if subdomains is not empty`, () => {
|
||||
const applied = withNetworkSecurityConfigManifest(exp, {
|
||||
subdomains: ["10.0.2.2"],
|
||||
});
|
||||
expect(applied).toMatchSnapshot();
|
||||
expect(applied).toHaveProperty("mods.android");
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ import withDetoxProjectGradle from "./withDetoxProjectGradle";
|
||||
import withDetoxTestAppGradle from "./withDetoxTestAppGradle";
|
||||
import { withDetoxTestClass } from "./withDetoxTestClass";
|
||||
import withKotlinGradle from "./withKotlinGradle";
|
||||
import { withNetworkSecurityConfigManifest } from "./withNetworkSecurityConfig";
|
||||
import { withNetworkSecurityConfigManifest, SubdomainsType } from "./withNetworkSecurityConfig";
|
||||
import withProguardGradle from "./withProguardGradle";
|
||||
|
||||
const withDetox: ConfigPlugin<{
|
||||
@@ -21,10 +21,11 @@ const withDetox: ConfigPlugin<{
|
||||
/**
|
||||
* Subdomains to add to the network security config.
|
||||
* Pass `['10.0.3.2', 'localhost']` to use Genymotion emulators instead of Google emulators.
|
||||
* Pass `*` to allow all domains.
|
||||
*
|
||||
* @default ['10.0.2.2', 'localhost'] // (Google emulators)
|
||||
*/
|
||||
subdomains?: string[];
|
||||
subdomains?: SubdomainsType;
|
||||
} | void> = (config, { skipProguard, subdomains } = {}) => {
|
||||
return withPlugins(
|
||||
config,
|
||||
|
||||
@@ -8,28 +8,41 @@ import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import assert from "assert";
|
||||
|
||||
export function getTemplateFile(subdomains: string[]): string {
|
||||
return `<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
export type SubdomainsType = string[] | "*";
|
||||
|
||||
function getTemplateConfigContent(subdomains: SubdomainsType) {
|
||||
if (subdomains === "*") {
|
||||
// allow all domains
|
||||
return '<base-config cleartextTrafficPermitted="true" />';
|
||||
}
|
||||
return `
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
${subdomains
|
||||
.map(
|
||||
(subdomain) =>
|
||||
`<domain includeSubdomains="true">${subdomain}</domain>`
|
||||
)
|
||||
.join("")}
|
||||
${subdomains
|
||||
.map(
|
||||
(subdomain) =>
|
||||
`<domain includeSubdomains="true">${subdomain}</domain>`
|
||||
)
|
||||
.join("")}
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
export function getTemplateFile(subdomains: SubdomainsType): string {
|
||||
const content = getTemplateConfigContent(subdomains);
|
||||
return `
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
${content}
|
||||
</network-security-config>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create `network_security_config.xml` resource file.
|
||||
*/
|
||||
const withNetworkSecurityConfigFile: ConfigPlugin<{ subdomains: string[] }> = (
|
||||
config,
|
||||
{ subdomains }
|
||||
) => {
|
||||
const withNetworkSecurityConfigFile: ConfigPlugin<{
|
||||
subdomains: SubdomainsType;
|
||||
}> = (config, { subdomains }) => {
|
||||
return withDangerousMod(config, [
|
||||
"android",
|
||||
async (config) => {
|
||||
@@ -53,14 +66,22 @@ const withNetworkSecurityConfigFile: ConfigPlugin<{ subdomains: string[] }> = (
|
||||
/**
|
||||
* [Step 6](https://github.com/wix/Detox/blob/master/docs/Introduction.Android.md#6-enable-clear-text-unencrypted-traffic-for-detox). Link the `network_security_config.xml` file to the `AndroidManifest.xml`.
|
||||
*/
|
||||
export const withNetworkSecurityConfigManifest: ConfigPlugin<{
|
||||
subdomains: string[];
|
||||
} | void> = (config, props) => {
|
||||
export const withNetworkSecurityConfigManifest: ConfigPlugin<
|
||||
{
|
||||
subdomains: SubdomainsType;
|
||||
} | void
|
||||
> = (config, props) => {
|
||||
if (!props || !props.subdomains) {
|
||||
// (*) 10.0.2.2 for Google emulators, 10.0.3.2 for Genymotion emulators.
|
||||
// https://developer.android.com/training/articles/security-config
|
||||
props = { subdomains: ["10.0.2.2", "localhost"] };
|
||||
}
|
||||
|
||||
if (typeof props.subdomains === "object" && !props.subdomains.length) {
|
||||
// if subdomains is an empty array, skip network config mod
|
||||
return config;
|
||||
}
|
||||
|
||||
config = withNetworkSecurityConfigFile(config, props);
|
||||
return withAndroidManifest(config, (config) => {
|
||||
const application = AndroidConfig.Manifest.getMainApplicationOrThrow(
|
||||
|
||||
Reference in new issue
Block a user