Add capacitorjs runtime
21
node_modules/native-run/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Drifty Co
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
39
node_modules/native-run/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
[](https://github.com/ionic-team/native-run/actions?query=workflow%3ACI)
|
||||
[](https://github.com/semantic-release/semantic-release)
|
||||
[](https://www.npmjs.com/package/native-run)
|
||||
|
||||
# native-run
|
||||
|
||||
`native-run` is a cross-platform command-line utility for running native app binaries (`.ipa` and `.apk` files) on iOS and Android devices. It can be used for both hardware and virtual devices.
|
||||
|
||||
This tool is used by the Ionic CLI, but it can be used standalone as part of a development or testing pipeline for launching apps. It doesn't matter whether the `.apk` or `.ipa` is created with Cordova or native IDEs, `native-run` will be able to deploy it.
|
||||
|
||||
## Install
|
||||
|
||||
`native-run` is written entirely in TypeScript/NodeJS, so there are no native dependencies.
|
||||
|
||||
To install, run:
|
||||
|
||||
```
|
||||
npm install -g native-run
|
||||
```
|
||||
|
||||
:memo: Requires NodeJS 16+
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
native-run <platform> [options]
|
||||
```
|
||||
|
||||
See the help documentation with the `--help` flag.
|
||||
|
||||
```
|
||||
native-run --help
|
||||
native-run ios --help
|
||||
native-run android --help
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
Much more information can be printed to the screen with the `--verbose` flag.
|
||||
10
node_modules/native-run/assets/android/skins/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
Copyright (C) 2013 The Android Open Source Project Licensed under the Apache
|
||||
License, Version 2.0 (the "License"); you may not use this file except in
|
||||
compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
4
node_modules/native-run/assets/android/skins/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Android Skins
|
||||
|
||||
These skins are copied from the Android Plugin for IntelliJ IDEA:
|
||||
https://github.com/JetBrains/android/tree/master/artwork/resources/device-art-resources
|
||||
BIN
node_modules/native-run/assets/android/skins/nexus_5x/land_back.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
node_modules/native-run/assets/android/skins/nexus_5x/land_fore.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
node_modules/native-run/assets/android/skins/nexus_5x/land_shadow.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
59
node_modules/native-run/assets/android/skins/nexus_5x/layout
generated
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
parts {
|
||||
device {
|
||||
display {
|
||||
width 1080
|
||||
height 1920
|
||||
x 0
|
||||
y 0
|
||||
}
|
||||
}
|
||||
portrait {
|
||||
background {
|
||||
image port_back.webp
|
||||
}
|
||||
onion {
|
||||
image port_fore.webp
|
||||
}
|
||||
}
|
||||
landscape {
|
||||
background {
|
||||
image land_back.webp
|
||||
}
|
||||
onion {
|
||||
image land_fore.webp
|
||||
}
|
||||
}
|
||||
}
|
||||
layouts {
|
||||
portrait {
|
||||
width 1370
|
||||
height 2446
|
||||
event EV_SW:0:1
|
||||
part1 {
|
||||
name portrait
|
||||
x 0
|
||||
y 0
|
||||
}
|
||||
part2 {
|
||||
name device
|
||||
x 147
|
||||
y 233
|
||||
}
|
||||
}
|
||||
landscape {
|
||||
width 2497
|
||||
height 1234
|
||||
event EV_SW:0:0
|
||||
part1 {
|
||||
name landscape
|
||||
x 0
|
||||
y 0
|
||||
}
|
||||
part2 {
|
||||
name device
|
||||
x 278
|
||||
y 1143
|
||||
rotation 3
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
node_modules/native-run/assets/android/skins/nexus_5x/port_back.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
node_modules/native-run/assets/android/skins/nexus_5x/port_fore.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
node_modules/native-run/assets/android/skins/nexus_5x/port_shadow.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
node_modules/native-run/assets/android/skins/pixel/land_back.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
node_modules/native-run/assets/android/skins/pixel/land_fore.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
node_modules/native-run/assets/android/skins/pixel/land_shadow.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 24 KiB |
59
node_modules/native-run/assets/android/skins/pixel/layout
generated
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
parts {
|
||||
device {
|
||||
display {
|
||||
width 1080
|
||||
height 1920
|
||||
x 0
|
||||
y 0
|
||||
}
|
||||
}
|
||||
portrait {
|
||||
background {
|
||||
image port_back.webp
|
||||
}
|
||||
onion {
|
||||
image port_fore.webp
|
||||
}
|
||||
}
|
||||
landscape {
|
||||
background {
|
||||
image land_back.webp
|
||||
}
|
||||
onion {
|
||||
image land_fore.webp
|
||||
}
|
||||
}
|
||||
}
|
||||
layouts {
|
||||
portrait {
|
||||
width 1370
|
||||
height 2534
|
||||
event EV_SW:0:1
|
||||
part1 {
|
||||
name portrait
|
||||
x 0
|
||||
y 0
|
||||
}
|
||||
part2 {
|
||||
name device
|
||||
x 139
|
||||
y 285
|
||||
}
|
||||
}
|
||||
landscape {
|
||||
width 2596
|
||||
height 1258
|
||||
event EV_SW:0:0
|
||||
part1 {
|
||||
name landscape
|
||||
x 0
|
||||
y 0
|
||||
}
|
||||
part2 {
|
||||
name device
|
||||
x 338
|
||||
y 1158
|
||||
rotation 3
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
node_modules/native-run/assets/android/skins/pixel/port_back.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
node_modules/native-run/assets/android/skins/pixel/port_fore.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
node_modules/native-run/assets/android/skins/pixel/port_shadow.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
node_modules/native-run/assets/android/skins/pixel_2/land_back.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
node_modules/native-run/assets/android/skins/pixel_2/land_fore.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 176 B |
BIN
node_modules/native-run/assets/android/skins/pixel_2/land_shadow.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 17 KiB |
59
node_modules/native-run/assets/android/skins/pixel_2/layout
generated
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
parts {
|
||||
device {
|
||||
display {
|
||||
width 1080
|
||||
height 1920
|
||||
x 0
|
||||
y 0
|
||||
}
|
||||
}
|
||||
portrait {
|
||||
background {
|
||||
image port_back.webp
|
||||
}
|
||||
onion {
|
||||
image port_fore.webp
|
||||
}
|
||||
}
|
||||
landscape {
|
||||
background {
|
||||
image land_back.webp
|
||||
}
|
||||
onion {
|
||||
image land_fore.webp
|
||||
}
|
||||
}
|
||||
}
|
||||
layouts {
|
||||
portrait {
|
||||
width 1370
|
||||
height 2534
|
||||
event EV_SW:0:1
|
||||
part1 {
|
||||
name portrait
|
||||
x 0
|
||||
y 0
|
||||
}
|
||||
part2 {
|
||||
name device
|
||||
x 140
|
||||
y 280
|
||||
}
|
||||
}
|
||||
landscape {
|
||||
width 2596
|
||||
height 1258
|
||||
event EV_SW:0:0
|
||||
part1 {
|
||||
name landscape
|
||||
x 0
|
||||
y 0
|
||||
}
|
||||
part2 {
|
||||
name device
|
||||
x 338
|
||||
y 68
|
||||
rotation 3
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
node_modules/native-run/assets/android/skins/pixel_2/port_back.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 202 KiB |
BIN
node_modules/native-run/assets/android/skins/pixel_2/port_fore.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 188 B |
BIN
node_modules/native-run/assets/android/skins/pixel_2/port_shadow.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
38
node_modules/native-run/assets/android/skins/pixel_3/layout
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
parts {
|
||||
device {
|
||||
display {
|
||||
width 1080
|
||||
height 2160
|
||||
x 0
|
||||
y 0
|
||||
}
|
||||
}
|
||||
portrait {
|
||||
background {
|
||||
image port_back.webp
|
||||
}
|
||||
foreground {
|
||||
mask round_corners.webp
|
||||
}
|
||||
onion {
|
||||
image port_fore.webp
|
||||
}
|
||||
}
|
||||
}
|
||||
layouts {
|
||||
portrait {
|
||||
width 1194
|
||||
height 2532
|
||||
event EV_SW:0:1
|
||||
part1 {
|
||||
name portrait
|
||||
x 0
|
||||
y 0
|
||||
}
|
||||
part2 {
|
||||
name device
|
||||
x 54
|
||||
y 196
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
node_modules/native-run/assets/android/skins/pixel_3/port_back.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
node_modules/native-run/assets/android/skins/pixel_3/round_corners.webp
generated
vendored
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
11
node_modules/native-run/bin/native-run
generated
vendored
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
process.title = 'native-run';
|
||||
|
||||
if (process.argv.includes('--verbose')) {
|
||||
process.env.DEBUG = '*';
|
||||
}
|
||||
|
||||
require('../').run();
|
||||
38
node_modules/native-run/dist/android/help.js
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.run = void 0;
|
||||
const help = `
|
||||
Usage: native-run android [options]
|
||||
|
||||
Run an .apk on a device or emulator target
|
||||
|
||||
Targets are selected as follows:
|
||||
1) --target using device/emulator serial number or AVD ID
|
||||
2) A connected device, unless --virtual is used
|
||||
3) A running emulator
|
||||
|
||||
If the above criteria are not met, an emulator is started from a default
|
||||
AVD, which is created if it does not exist.
|
||||
|
||||
Use --list to list available targets.
|
||||
|
||||
Options:
|
||||
|
||||
--list .................. Print available targets, then quit
|
||||
--sdk-info .............. Print SDK information, then quit
|
||||
--json .................. Output JSON
|
||||
|
||||
|
||||
--app <path> ............ Deploy specified .apk file
|
||||
--device ................ Use a device if available
|
||||
With --list prints connected devices
|
||||
--virtual ............... Prefer an emulator
|
||||
With --list prints available emulators
|
||||
--target <id> ........... Use a specific target
|
||||
--connect ............... Tie process to app process
|
||||
--forward <port:port> ... Forward a port from device to host
|
||||
`;
|
||||
async function run(args) {
|
||||
process.stdout.write(`${help}\n`);
|
||||
}
|
||||
exports.run = run;
|
||||
21
node_modules/native-run/dist/android/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.run = void 0;
|
||||
async function run(args) {
|
||||
let cmd;
|
||||
if (args.includes('--help') || args.includes('-h')) {
|
||||
cmd = await Promise.resolve().then(() => require('./help'));
|
||||
return cmd.run(args);
|
||||
}
|
||||
if (args.includes('--list')) {
|
||||
cmd = await Promise.resolve().then(() => require('./list'));
|
||||
return cmd.run(args);
|
||||
}
|
||||
if (args.includes('--sdk-info')) {
|
||||
cmd = await Promise.resolve().then(() => require('./sdk-info'));
|
||||
return cmd.run(args);
|
||||
}
|
||||
cmd = await Promise.resolve().then(() => require('./run'));
|
||||
await cmd.run(args);
|
||||
}
|
||||
exports.run = run;
|
||||
37
node_modules/native-run/dist/android/list.js
generated
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.list = exports.run = void 0;
|
||||
const list_1 = require("../utils/list");
|
||||
const list_2 = require("./utils/list");
|
||||
const sdk_1 = require("./utils/sdk");
|
||||
async function run(args) {
|
||||
const targets = await list(args);
|
||||
process.stdout.write(`\n${(0, list_1.formatTargets)(args, targets)}\n`);
|
||||
}
|
||||
exports.run = run;
|
||||
async function list(args) {
|
||||
const sdk = await (0, sdk_1.getSDK)();
|
||||
const errors = [];
|
||||
const [devices, virtualDevices] = await Promise.all([
|
||||
(async () => {
|
||||
try {
|
||||
return await (0, list_2.getDeviceTargets)(sdk);
|
||||
}
|
||||
catch (e) {
|
||||
errors.push(e);
|
||||
return [];
|
||||
}
|
||||
})(),
|
||||
(async () => {
|
||||
try {
|
||||
return await (0, list_2.getVirtualTargets)(sdk);
|
||||
}
|
||||
catch (e) {
|
||||
errors.push(e);
|
||||
return [];
|
||||
}
|
||||
})(),
|
||||
]);
|
||||
return { devices, virtualDevices, errors };
|
||||
}
|
||||
exports.list = list;
|
||||
103
node_modules/native-run/dist/android/run.js
generated
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.selectDevice = exports.run = void 0;
|
||||
const Debug = require("debug");
|
||||
const errors_1 = require("../errors");
|
||||
const cli_1 = require("../utils/cli");
|
||||
const log_1 = require("../utils/log");
|
||||
const process_1 = require("../utils/process");
|
||||
const adb_1 = require("./utils/adb");
|
||||
const apk_1 = require("./utils/apk");
|
||||
const avd_1 = require("./utils/avd");
|
||||
const run_1 = require("./utils/run");
|
||||
const sdk_1 = require("./utils/sdk");
|
||||
const modulePrefix = 'native-run:android:run';
|
||||
async function run(args) {
|
||||
const sdk = await (0, sdk_1.getSDK)();
|
||||
const apkPath = (0, cli_1.getOptionValue)(args, '--app');
|
||||
const forwardedPorts = (0, cli_1.getOptionValues)(args, '--forward');
|
||||
const ports = [];
|
||||
if (forwardedPorts && forwardedPorts.length > 0) {
|
||||
forwardedPorts.forEach((port) => {
|
||||
const [device, host] = port.split(':');
|
||||
if (!device || !host) {
|
||||
throw new errors_1.CLIException(`Invalid --forward value "${port}": expecting <device port:host port>, e.g. 8080:8080`);
|
||||
}
|
||||
ports.push({ device, host });
|
||||
});
|
||||
}
|
||||
if (!apkPath) {
|
||||
throw new errors_1.CLIException('--app is required', errors_1.ERR_BAD_INPUT);
|
||||
}
|
||||
const device = await selectDevice(sdk, args);
|
||||
(0, log_1.log)(`Selected ${device.type === 'hardware' ? 'hardware device' : 'emulator'} ${device.serial}\n`);
|
||||
const { appId, activityName } = await (0, apk_1.getApkInfo)(apkPath);
|
||||
await (0, adb_1.waitForBoot)(sdk, device);
|
||||
if (ports) {
|
||||
await Promise.all(ports.map(async (port) => {
|
||||
await (0, adb_1.forwardPorts)(sdk, device, port);
|
||||
(0, log_1.log)(`Forwarded device port ${port.device} to host port ${port.host}\n`);
|
||||
}));
|
||||
}
|
||||
await (0, run_1.installApkToDevice)(sdk, device, apkPath, appId);
|
||||
(0, log_1.log)(`Starting application activity ${appId}/${activityName}...\n`);
|
||||
await (0, adb_1.startActivity)(sdk, device, appId, activityName);
|
||||
(0, log_1.log)(`Run Successful\n`);
|
||||
(0, process_1.onBeforeExit)(async () => {
|
||||
if (ports) {
|
||||
await Promise.all(ports.map(async (port) => {
|
||||
await (0, adb_1.unforwardPorts)(sdk, device, port);
|
||||
}));
|
||||
}
|
||||
});
|
||||
if (args.includes('--connect')) {
|
||||
(0, process_1.onBeforeExit)(async () => {
|
||||
await (0, adb_1.closeApp)(sdk, device, appId);
|
||||
});
|
||||
(0, log_1.log)(`Waiting for app to close...\n`);
|
||||
await (0, adb_1.waitForClose)(sdk, device, appId);
|
||||
}
|
||||
}
|
||||
exports.run = run;
|
||||
async function selectDevice(sdk, args) {
|
||||
const debug = Debug(`${modulePrefix}:${selectDevice.name}`);
|
||||
const devices = await (0, adb_1.getDevices)(sdk);
|
||||
const avds = await (0, avd_1.getInstalledAVDs)(sdk);
|
||||
const target = (0, cli_1.getOptionValue)(args, '--target');
|
||||
const preferEmulator = args.includes('--virtual');
|
||||
if (target) {
|
||||
const targetDevice = await (0, run_1.selectDeviceByTarget)(sdk, devices, avds, target);
|
||||
if (targetDevice) {
|
||||
return targetDevice;
|
||||
}
|
||||
else {
|
||||
throw new errors_1.AndroidRunException(`Target not found: ${target}`, errors_1.ERR_TARGET_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
if (!preferEmulator) {
|
||||
const selectedDevice = await (0, run_1.selectHardwareDevice)(devices);
|
||||
if (selectedDevice) {
|
||||
return selectedDevice;
|
||||
}
|
||||
else if (args.includes('--device')) {
|
||||
throw new errors_1.AndroidRunException(`No hardware devices found. Not attempting emulator because --device was specified.`, errors_1.ERR_NO_DEVICE);
|
||||
}
|
||||
else {
|
||||
(0, log_1.log)('No hardware devices found, attempting emulator...\n');
|
||||
}
|
||||
}
|
||||
try {
|
||||
return await (0, run_1.selectVirtualDevice)(sdk, devices, avds);
|
||||
}
|
||||
catch (e) {
|
||||
if (!(e instanceof errors_1.AVDException)) {
|
||||
throw e;
|
||||
}
|
||||
debug('Issue with AVDs: %s', e.message);
|
||||
if (e.code === errors_1.ERR_UNSUITABLE_API_INSTALLATION) {
|
||||
throw new errors_1.AndroidRunException('No targets devices/emulators available. Cannot create AVD because there is no suitable API installation. Use --sdk-info to reveal missing packages and other issues.', errors_1.ERR_NO_TARGET);
|
||||
}
|
||||
}
|
||||
throw new errors_1.AndroidRunException('No target devices/emulators available.', errors_1.ERR_NO_TARGET);
|
||||
}
|
||||
exports.selectDevice = selectDevice;
|
||||
46
node_modules/native-run/dist/android/sdk-info.js
generated
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.run = void 0;
|
||||
const json_1 = require("../utils/json");
|
||||
const sdk_1 = require("./utils/sdk");
|
||||
const api_1 = require("./utils/sdk/api");
|
||||
async function run(args) {
|
||||
const sdk = await (0, sdk_1.getSDK)();
|
||||
const packages = await (0, sdk_1.findAllSDKPackages)(sdk);
|
||||
const apis = await (0, api_1.getAPILevels)(packages);
|
||||
const platforms = apis.map((api) => {
|
||||
return { ...api };
|
||||
});
|
||||
const sdkinfo = {
|
||||
root: sdk.root,
|
||||
avdHome: sdk.avdHome,
|
||||
platforms,
|
||||
tools: packages.filter((pkg) => typeof pkg.apiLevel === 'undefined'),
|
||||
};
|
||||
if (args.includes('--json')) {
|
||||
process.stdout.write((0, json_1.stringify)(sdkinfo));
|
||||
return;
|
||||
}
|
||||
process.stdout.write(`${formatSDKInfo(sdkinfo)}\n\n`);
|
||||
}
|
||||
exports.run = run;
|
||||
function formatSDKInfo(sdk) {
|
||||
return `
|
||||
SDK Location: ${sdk.root}
|
||||
AVD Home${sdk.avdHome ? `: ${sdk.avdHome}` : ` (!): not found`}
|
||||
|
||||
${sdk.platforms.map((platform) => `${formatPlatform(platform)}\n\n`).join('\n')}
|
||||
Tools:
|
||||
|
||||
${sdk.tools.map((tool) => formatPackage(tool)).join('\n')}
|
||||
`.trim();
|
||||
}
|
||||
function formatPlatform(platform) {
|
||||
return `
|
||||
API Level: ${platform.apiLevel}
|
||||
Packages: ${platform.packages.map((p) => formatPackage(p)).join('\n' + ' '.repeat(22))}
|
||||
`.trim();
|
||||
}
|
||||
function formatPackage(p) {
|
||||
return `${p.name} ${p.path} ${typeof p.version === 'string' ? p.version : ''}`;
|
||||
}
|
||||
318
node_modules/native-run/dist/android/utils/adb.js
generated
vendored
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.execAdb = exports.unforwardPorts = exports.forwardPorts = exports.parseAdbDevices = exports.startActivity = exports.parseAdbInstallOutput = exports.ADBEvent = exports.uninstallApp = exports.closeApp = exports.installApk = exports.waitForClose = exports.waitForBoot = exports.waitForDevice = exports.getDeviceProperties = exports.getDeviceProperty = exports.getDevices = void 0;
|
||||
const child_process_1 = require("child_process");
|
||||
const Debug = require("debug");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
const split2 = require("split2");
|
||||
const through2 = require("through2");
|
||||
const errors_1 = require("../../errors");
|
||||
const process_1 = require("../../utils/process");
|
||||
const sdk_1 = require("./sdk");
|
||||
const modulePrefix = 'native-run:android:utils:adb';
|
||||
const ADB_GETPROP_MAP = new Map([
|
||||
['ro.product.manufacturer', 'manufacturer'],
|
||||
['ro.product.model', 'model'],
|
||||
['ro.product.name', 'product'],
|
||||
['ro.build.version.sdk', 'sdkVersion'],
|
||||
]);
|
||||
async function getDevices(sdk) {
|
||||
const debug = Debug(`${modulePrefix}:${getDevices.name}`);
|
||||
const args = ['devices', '-l'];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
const stdout = await execAdb(sdk, args, { timeout: 5000 });
|
||||
const devices = parseAdbDevices(stdout);
|
||||
await Promise.all(devices.map(async (device) => {
|
||||
const properties = await getDeviceProperties(sdk, device);
|
||||
for (const [prop, deviceProp] of ADB_GETPROP_MAP.entries()) {
|
||||
const value = properties[prop];
|
||||
if (value) {
|
||||
device[deviceProp] = value;
|
||||
}
|
||||
}
|
||||
}));
|
||||
debug('Found adb devices: %O', devices);
|
||||
return devices;
|
||||
}
|
||||
exports.getDevices = getDevices;
|
||||
async function getDeviceProperty(sdk, device, property) {
|
||||
const debug = Debug(`${modulePrefix}:${getDeviceProperty.name}`);
|
||||
const args = ['-s', device.serial, 'shell', 'getprop', property];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
const stdout = await execAdb(sdk, args, { timeout: 5000 });
|
||||
return stdout.trim();
|
||||
}
|
||||
exports.getDeviceProperty = getDeviceProperty;
|
||||
async function getDeviceProperties(sdk, device) {
|
||||
const debug = Debug(`${modulePrefix}:${getDeviceProperties.name}`);
|
||||
const args = ['-s', device.serial, 'shell', 'getprop'];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
const stdout = await execAdb(sdk, args, { timeout: 5000 });
|
||||
const re = /^\[([a-z0-9.]+)\]: \[(.*)\]$/;
|
||||
const propAllowList = [...ADB_GETPROP_MAP.keys()];
|
||||
const properties = {};
|
||||
for (const line of stdout.split(os.EOL)) {
|
||||
const m = line.match(re);
|
||||
if (m) {
|
||||
const [, key, value] = m;
|
||||
if (propAllowList.includes(key)) {
|
||||
properties[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
exports.getDeviceProperties = getDeviceProperties;
|
||||
async function waitForDevice(sdk, serial) {
|
||||
const debug = Debug(`${modulePrefix}:${waitForDevice.name}`);
|
||||
const args = ['-s', serial, 'wait-for-any-device'];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
await execAdb(sdk, args);
|
||||
debug('Device %s is connected to ADB!', serial);
|
||||
}
|
||||
exports.waitForDevice = waitForDevice;
|
||||
async function waitForBoot(sdk, device) {
|
||||
const debug = Debug(`${modulePrefix}:${waitForBoot.name}`);
|
||||
return new Promise((resolve) => {
|
||||
const interval = setInterval(async () => {
|
||||
const booted = await getDeviceProperty(sdk, device, 'dev.bootcomplete');
|
||||
if (booted) {
|
||||
debug('Device %s is booted!', device.serial);
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
exports.waitForBoot = waitForBoot;
|
||||
async function waitForClose(sdk, device, app) {
|
||||
const debug = Debug(`${modulePrefix}:${waitForClose.name}`);
|
||||
const args = ['-s', device.serial, 'shell', `ps | grep ${app}`];
|
||||
return new Promise((resolve) => {
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
debug('Invoking adb with args: %O', args);
|
||||
await execAdb(sdk, args);
|
||||
}
|
||||
catch (e) {
|
||||
debug('Error received from adb: %O', e);
|
||||
debug('App %s no longer found in process list for %s', app, device.serial);
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
exports.waitForClose = waitForClose;
|
||||
async function installApk(sdk, device, apk) {
|
||||
const debug = Debug(`${modulePrefix}:${installApk.name}`);
|
||||
const platformTools = await (0, sdk_1.getSDKPackage)(path.join(sdk.root, 'platform-tools'));
|
||||
const adbBin = path.join(platformTools.location, 'adb');
|
||||
const args = ['-s', device.serial, 'install', '-r', '-t', apk];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
const p = (0, child_process_1.spawn)(adbBin, args, {
|
||||
stdio: 'pipe',
|
||||
env: (0, sdk_1.supplementProcessEnv)(sdk),
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
p.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
}
|
||||
else {
|
||||
reject(new errors_1.ADBException(`Non-zero exit code from adb: ${code}`));
|
||||
}
|
||||
});
|
||||
p.on('error', (err) => {
|
||||
debug('adb install error: %O', err);
|
||||
reject(err);
|
||||
});
|
||||
p.stderr.pipe(split2()).pipe(through2((chunk, enc, cb) => {
|
||||
const line = chunk.toString();
|
||||
debug('adb install: %O', line);
|
||||
const event = parseAdbInstallOutput(line);
|
||||
if (event === ADBEvent.IncompatibleUpdateFailure) {
|
||||
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_INCOMPATIBLE_UPDATE));
|
||||
}
|
||||
else if (event === ADBEvent.NewerVersionOnDeviceFailure) {
|
||||
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_VERSION_DOWNGRADE));
|
||||
}
|
||||
else if (event === ADBEvent.NewerSdkRequiredOnDeviceFailure) {
|
||||
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_MIN_SDK_VERSION));
|
||||
}
|
||||
else if (event === ADBEvent.NoCertificates) {
|
||||
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_NO_CERTIFICATES));
|
||||
}
|
||||
else if (event === ADBEvent.NotEnoughSpace) {
|
||||
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_NOT_ENOUGH_SPACE));
|
||||
}
|
||||
else if (event === ADBEvent.DeviceOffline) {
|
||||
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_DEVICE_OFFLINE));
|
||||
}
|
||||
cb();
|
||||
}));
|
||||
});
|
||||
}
|
||||
exports.installApk = installApk;
|
||||
async function closeApp(sdk, device, app) {
|
||||
const debug = Debug(`${modulePrefix}:${closeApp.name}`);
|
||||
const args = ['-s', device.serial, 'shell', 'am', 'force-stop', app];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
await execAdb(sdk, args);
|
||||
}
|
||||
exports.closeApp = closeApp;
|
||||
async function uninstallApp(sdk, device, app) {
|
||||
const debug = Debug(`${modulePrefix}:${uninstallApp.name}`);
|
||||
const args = ['-s', device.serial, 'uninstall', app];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
await execAdb(sdk, args);
|
||||
}
|
||||
exports.uninstallApp = uninstallApp;
|
||||
var ADBEvent;
|
||||
(function (ADBEvent) {
|
||||
ADBEvent[ADBEvent["IncompatibleUpdateFailure"] = 0] = "IncompatibleUpdateFailure";
|
||||
ADBEvent[ADBEvent["NewerVersionOnDeviceFailure"] = 1] = "NewerVersionOnDeviceFailure";
|
||||
ADBEvent[ADBEvent["NewerSdkRequiredOnDeviceFailure"] = 2] = "NewerSdkRequiredOnDeviceFailure";
|
||||
ADBEvent[ADBEvent["NoCertificates"] = 3] = "NoCertificates";
|
||||
ADBEvent[ADBEvent["NotEnoughSpace"] = 4] = "NotEnoughSpace";
|
||||
ADBEvent[ADBEvent["DeviceOffline"] = 5] = "DeviceOffline";
|
||||
})(ADBEvent = exports.ADBEvent || (exports.ADBEvent = {}));
|
||||
function parseAdbInstallOutput(line) {
|
||||
const debug = Debug(`${modulePrefix}:${parseAdbInstallOutput.name}`);
|
||||
let event;
|
||||
if (line.includes('INSTALL_FAILED_UPDATE_INCOMPATIBLE')) {
|
||||
event = ADBEvent.IncompatibleUpdateFailure;
|
||||
}
|
||||
else if (line.includes('INSTALL_FAILED_VERSION_DOWNGRADE')) {
|
||||
event = ADBEvent.NewerVersionOnDeviceFailure;
|
||||
}
|
||||
else if (line.includes('INSTALL_FAILED_OLDER_SDK')) {
|
||||
event = ADBEvent.NewerSdkRequiredOnDeviceFailure;
|
||||
}
|
||||
else if (line.includes('INSTALL_PARSE_FAILED_NO_CERTIFICATES')) {
|
||||
event = ADBEvent.NoCertificates;
|
||||
}
|
||||
else if (line.includes('INSTALL_FAILED_INSUFFICIENT_STORAGE') || line.includes('not enough space')) {
|
||||
event = ADBEvent.NotEnoughSpace;
|
||||
}
|
||||
else if (line.includes('device offline')) {
|
||||
event = ADBEvent.DeviceOffline;
|
||||
}
|
||||
if (typeof event !== 'undefined') {
|
||||
debug('Parsed event from adb install output: %s', ADBEvent[event]);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
exports.parseAdbInstallOutput = parseAdbInstallOutput;
|
||||
async function startActivity(sdk, device, packageName, activityName) {
|
||||
const debug = Debug(`${modulePrefix}:${startActivity.name}`);
|
||||
const args = ['-s', device.serial, 'shell', 'am', 'start', '-W', '-n', `${packageName}/${activityName}`];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
await execAdb(sdk, args, { timeout: 5000 });
|
||||
}
|
||||
exports.startActivity = startActivity;
|
||||
function parseAdbDevices(output) {
|
||||
const debug = Debug(`${modulePrefix}:${parseAdbDevices.name}`);
|
||||
const re = /^([\S]+)\s+([a-z\s]+)\s+(.*)$/;
|
||||
const ipRe = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/;
|
||||
const lines = output.split(os.EOL);
|
||||
debug('Parsing adb devices from output lines: %O', lines);
|
||||
const devices = [];
|
||||
for (const line of lines) {
|
||||
if (line && !line.startsWith('List')) {
|
||||
const m = line.match(re);
|
||||
if (m) {
|
||||
const [, serial, state, description] = m;
|
||||
const properties = description
|
||||
.split(/\s+/)
|
||||
.map((prop) => (prop.includes(':') ? prop.split(':') : undefined))
|
||||
.filter((kv) => typeof kv !== 'undefined' && kv.length >= 2)
|
||||
.reduce((acc, [k, v]) => {
|
||||
if (k && v) {
|
||||
acc[k.trim()] = v.trim();
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
const isIP = !!serial.match(ipRe);
|
||||
const isGenericDevice = (properties['device'] || '').startsWith('generic');
|
||||
const type = 'usb' in properties || isIP || !serial.startsWith('emulator') || !isGenericDevice ? 'hardware' : 'emulator';
|
||||
const connection = 'usb' in properties ? 'usb' : isIP ? 'tcpip' : null;
|
||||
devices.push({
|
||||
serial,
|
||||
state,
|
||||
type,
|
||||
connection,
|
||||
properties,
|
||||
// We might not know these yet
|
||||
manufacturer: '',
|
||||
model: properties['model'] || '',
|
||||
product: properties['product'] || '',
|
||||
sdkVersion: '',
|
||||
});
|
||||
}
|
||||
else {
|
||||
debug('adb devices output line does not match expected regex: %O', line);
|
||||
}
|
||||
}
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
exports.parseAdbDevices = parseAdbDevices;
|
||||
async function forwardPorts(sdk, device, ports) {
|
||||
const debug = Debug(`${modulePrefix}:${forwardPorts.name}`);
|
||||
const args = ['-s', device.serial, 'reverse', `tcp:${ports.device}`, `tcp:${ports.host}`];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
await execAdb(sdk, args, { timeout: 5000 });
|
||||
}
|
||||
exports.forwardPorts = forwardPorts;
|
||||
async function unforwardPorts(sdk, device, ports) {
|
||||
const debug = Debug(`${modulePrefix}:${unforwardPorts.name}`);
|
||||
const args = ['-s', device.serial, 'reverse', '--remove', `tcp:${ports.device}`];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
await execAdb(sdk, args, { timeout: 5000 });
|
||||
}
|
||||
exports.unforwardPorts = unforwardPorts;
|
||||
async function execAdb(sdk, args, options = {}) {
|
||||
const debug = Debug(`${modulePrefix}:${execAdb.name}`);
|
||||
let timer;
|
||||
const retry = async () => {
|
||||
const msg = `ADBs is unresponsive after ${options.timeout}ms, killing server and retrying...\n`;
|
||||
if (process.argv.includes('--json')) {
|
||||
debug(msg);
|
||||
}
|
||||
else {
|
||||
process.stderr.write(msg);
|
||||
}
|
||||
debug('ADB timeout of %O reached, killing server and retrying...', options.timeout);
|
||||
debug('Invoking adb with args: %O', ['kill-server']);
|
||||
await execAdb(sdk, ['kill-server']);
|
||||
debug('Invoking adb with args: %O', ['start-server']);
|
||||
await execAdb(sdk, ['start-server']);
|
||||
debug('Retrying...');
|
||||
return run();
|
||||
};
|
||||
const run = async () => {
|
||||
const platformTools = await (0, sdk_1.getSDKPackage)(path.join(sdk.root, 'platform-tools'));
|
||||
const adbBin = path.join(platformTools.location, 'adb');
|
||||
const { stdout } = await (0, process_1.execFile)(adbBin, args, {
|
||||
env: (0, sdk_1.supplementProcessEnv)(sdk),
|
||||
});
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = undefined;
|
||||
}
|
||||
return stdout;
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
if (options.timeout) {
|
||||
timer = setTimeout(() => retry().then(resolve, reject), options.timeout);
|
||||
}
|
||||
run().then(resolve, (err) => {
|
||||
if (!timer) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
exports.execAdb = execAdb;
|
||||
36
node_modules/native-run/dist/android/utils/apk.js
generated
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getApkInfo = exports.readAndroidManifest = void 0;
|
||||
const unzip_1 = require("../../utils/unzip");
|
||||
const binary_xml_parser_1 = require("./binary-xml-parser");
|
||||
async function readAndroidManifest(apkPath) {
|
||||
let error;
|
||||
const chunks = [];
|
||||
await (0, unzip_1.unzip)(apkPath, async (entry, zipfile, openReadStream) => {
|
||||
if (entry.fileName === 'AndroidManifest.xml') {
|
||||
const readStream = await openReadStream(entry);
|
||||
readStream.on('error', (err) => (error = err));
|
||||
readStream.on('data', (chunk) => chunks.push(chunk));
|
||||
readStream.on('end', () => zipfile.close());
|
||||
}
|
||||
else {
|
||||
zipfile.readEntry();
|
||||
}
|
||||
});
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
const buf = Buffer.concat(chunks);
|
||||
const manifestBuffer = Buffer.from(buf);
|
||||
return new binary_xml_parser_1.BinaryXmlParser(manifestBuffer).parse();
|
||||
}
|
||||
exports.readAndroidManifest = readAndroidManifest;
|
||||
async function getApkInfo(apkPath) {
|
||||
const doc = await readAndroidManifest(apkPath);
|
||||
const appId = doc.attributes.find((a) => a.name === 'package').value;
|
||||
const application = doc.childNodes.find((n) => n.nodeName === 'application');
|
||||
const activity = application.childNodes.find((n) => n.nodeName === 'activity');
|
||||
const activityName = activity.attributes.find((a) => a.name === 'name').value;
|
||||
return { appId, activityName };
|
||||
}
|
||||
exports.getApkInfo = getApkInfo;
|
||||
69
node_modules/native-run/dist/android/utils/avd.js
generated
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getInstalledAVDs = exports.getAVDFromINI = exports.getSDKVersionFromTarget = exports.getAVDFromConfigINI = exports.getAVDINIs = exports.isAVDConfigINI = exports.isAVDINI = void 0;
|
||||
const utils_fs_1 = require("@ionic/utils-fs");
|
||||
const Debug = require("debug");
|
||||
const pathlib = require("path");
|
||||
const ini_1 = require("../../utils/ini");
|
||||
const modulePrefix = 'native-run:android:utils:avd';
|
||||
const isAVDINI = (o) => o &&
|
||||
typeof o['avd.ini.encoding'] === 'string' &&
|
||||
typeof o['path'] === 'string' &&
|
||||
typeof o['path.rel'] === 'string' &&
|
||||
typeof o['target'] === 'string';
|
||||
exports.isAVDINI = isAVDINI;
|
||||
const isAVDConfigINI = (o) => o &&
|
||||
(typeof o['avd.ini.displayname'] === 'undefined' || typeof o['avd.ini.displayname'] === 'string') &&
|
||||
(typeof o['hw.lcd.density'] === 'undefined' || typeof o['hw.lcd.density'] === 'string') &&
|
||||
(typeof o['hw.lcd.height'] === 'undefined' || typeof o['hw.lcd.height'] === 'string') &&
|
||||
(typeof o['hw.lcd.width'] === 'undefined' || typeof o['hw.lcd.width'] === 'string') &&
|
||||
(typeof o['image.sysdir.1'] === 'undefined' || typeof o['image.sysdir.1'] === 'string');
|
||||
exports.isAVDConfigINI = isAVDConfigINI;
|
||||
async function getAVDINIs(sdk) {
|
||||
const debug = Debug(`${modulePrefix}:${getAVDINIs.name}`);
|
||||
const contents = await (0, utils_fs_1.readdir)(sdk.avdHome);
|
||||
const iniFilePaths = contents
|
||||
.filter((f) => pathlib.extname(f) === '.ini')
|
||||
.map((f) => pathlib.resolve(sdk.avdHome, f));
|
||||
debug('Discovered AVD ini files: %O', iniFilePaths);
|
||||
const iniFiles = await Promise.all(iniFilePaths.map(async (f) => [f, await (0, ini_1.readINI)(f, exports.isAVDINI)]));
|
||||
const avdInis = iniFiles.filter((c) => typeof c[1] !== 'undefined');
|
||||
return avdInis;
|
||||
}
|
||||
exports.getAVDINIs = getAVDINIs;
|
||||
function getAVDFromConfigINI(inipath, ini, configini) {
|
||||
const inibasename = pathlib.basename(inipath);
|
||||
const id = inibasename.substring(0, inibasename.length - pathlib.extname(inibasename).length);
|
||||
const name = configini['avd.ini.displayname'] ? String(configini['avd.ini.displayname']) : id.replace(/_/g, ' ');
|
||||
const screenDPI = configini['hw.lcd.density'] ? Number(configini['hw.lcd.density']) : null;
|
||||
const screenWidth = configini['hw.lcd.width'] ? Number(configini['hw.lcd.width']) : null;
|
||||
const screenHeight = configini['hw.lcd.height'] ? Number(configini['hw.lcd.height']) : null;
|
||||
return {
|
||||
id,
|
||||
path: ini.path,
|
||||
name,
|
||||
sdkVersion: getSDKVersionFromTarget(ini.target),
|
||||
screenDPI,
|
||||
screenWidth,
|
||||
screenHeight,
|
||||
};
|
||||
}
|
||||
exports.getAVDFromConfigINI = getAVDFromConfigINI;
|
||||
function getSDKVersionFromTarget(target) {
|
||||
return target.replace(/^android-(\d+)/, '$1');
|
||||
}
|
||||
exports.getSDKVersionFromTarget = getSDKVersionFromTarget;
|
||||
async function getAVDFromINI(inipath, ini) {
|
||||
const configini = await (0, ini_1.readINI)(pathlib.resolve(ini.path, 'config.ini'), exports.isAVDConfigINI);
|
||||
if (configini) {
|
||||
return getAVDFromConfigINI(inipath, ini, configini);
|
||||
}
|
||||
}
|
||||
exports.getAVDFromINI = getAVDFromINI;
|
||||
async function getInstalledAVDs(sdk) {
|
||||
const avdInis = await getAVDINIs(sdk);
|
||||
const possibleAvds = await Promise.all(avdInis.map(([inipath, ini]) => getAVDFromINI(inipath, ini)));
|
||||
const avds = possibleAvds.filter((avd) => typeof avd !== 'undefined');
|
||||
return avds;
|
||||
}
|
||||
exports.getInstalledAVDs = getInstalledAVDs;
|
||||
593
node_modules/native-run/dist/android/utils/binary-xml-parser.js
generated
vendored
Normal file
|
|
@ -0,0 +1,593 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BinaryXmlParser = void 0;
|
||||
/*
|
||||
Copyright © 2013 CyberAgent, Inc.
|
||||
Copyright © 2016 The OpenSTF Project
|
||||
Modifications Copyright © 2018 Drifty Co
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
https://github.com/openstf/adbkit-apkreader/blob/368f6b207c57e82fa7373c1608920ca7f4a8904c/lib/apkreader/parser/binaryxml.js
|
||||
*/
|
||||
const assert = require("assert");
|
||||
// import * as Debug from 'debug';
|
||||
const errors_1 = require("../../errors");
|
||||
// const debug = Debug('native-run:android:util:binary-xml-parser');
|
||||
const NodeType = {
|
||||
ELEMENT_NODE: 1,
|
||||
ATTRIBUTE_NODE: 2,
|
||||
CDATA_SECTION_NODE: 4,
|
||||
};
|
||||
const ChunkType = {
|
||||
NULL: 0x0000,
|
||||
STRING_POOL: 0x0001,
|
||||
TABLE: 0x0002,
|
||||
XML: 0x0003,
|
||||
XML_FIRST_CHUNK: 0x0100,
|
||||
XML_START_NAMESPACE: 0x0100,
|
||||
XML_END_NAMESPACE: 0x0101,
|
||||
XML_START_ELEMENT: 0x0102,
|
||||
XML_END_ELEMENT: 0x0103,
|
||||
XML_CDATA: 0x0104,
|
||||
XML_LAST_CHUNK: 0x017f,
|
||||
XML_RESOURCE_MAP: 0x0180,
|
||||
TABLE_PACKAGE: 0x0200,
|
||||
TABLE_TYPE: 0x0201,
|
||||
TABLE_TYPE_SPEC: 0x0202,
|
||||
};
|
||||
const StringFlags = {
|
||||
SORTED: 1 << 0,
|
||||
UTF8: 1 << 8,
|
||||
};
|
||||
// Taken from android.util.TypedValue
|
||||
const TypedValue = {
|
||||
COMPLEX_MANTISSA_MASK: 0x00ffffff,
|
||||
COMPLEX_MANTISSA_SHIFT: 0x00000008,
|
||||
COMPLEX_RADIX_0p23: 0x00000003,
|
||||
COMPLEX_RADIX_16p7: 0x00000001,
|
||||
COMPLEX_RADIX_23p0: 0x00000000,
|
||||
COMPLEX_RADIX_8p15: 0x00000002,
|
||||
COMPLEX_RADIX_MASK: 0x00000003,
|
||||
COMPLEX_RADIX_SHIFT: 0x00000004,
|
||||
COMPLEX_UNIT_DIP: 0x00000001,
|
||||
COMPLEX_UNIT_FRACTION: 0x00000000,
|
||||
COMPLEX_UNIT_FRACTION_PARENT: 0x00000001,
|
||||
COMPLEX_UNIT_IN: 0x00000004,
|
||||
COMPLEX_UNIT_MASK: 0x0000000f,
|
||||
COMPLEX_UNIT_MM: 0x00000005,
|
||||
COMPLEX_UNIT_PT: 0x00000003,
|
||||
COMPLEX_UNIT_PX: 0x00000000,
|
||||
COMPLEX_UNIT_SHIFT: 0x00000000,
|
||||
COMPLEX_UNIT_SP: 0x00000002,
|
||||
DENSITY_DEFAULT: 0x00000000,
|
||||
DENSITY_NONE: 0x0000ffff,
|
||||
TYPE_ATTRIBUTE: 0x00000002,
|
||||
TYPE_DIMENSION: 0x00000005,
|
||||
TYPE_FIRST_COLOR_INT: 0x0000001c,
|
||||
TYPE_FIRST_INT: 0x00000010,
|
||||
TYPE_FLOAT: 0x00000004,
|
||||
TYPE_FRACTION: 0x00000006,
|
||||
TYPE_INT_BOOLEAN: 0x00000012,
|
||||
TYPE_INT_COLOR_ARGB4: 0x0000001e,
|
||||
TYPE_INT_COLOR_ARGB8: 0x0000001c,
|
||||
TYPE_INT_COLOR_RGB4: 0x0000001f,
|
||||
TYPE_INT_COLOR_RGB8: 0x0000001d,
|
||||
TYPE_INT_DEC: 0x00000010,
|
||||
TYPE_INT_HEX: 0x00000011,
|
||||
TYPE_LAST_COLOR_INT: 0x0000001f,
|
||||
TYPE_LAST_INT: 0x0000001f,
|
||||
TYPE_NULL: 0x00000000,
|
||||
TYPE_REFERENCE: 0x00000001,
|
||||
TYPE_STRING: 0x00000003,
|
||||
};
|
||||
class BinaryXmlParser {
|
||||
constructor(buffer, options = {}) {
|
||||
this.buffer = buffer;
|
||||
this.cursor = 0;
|
||||
this.strings = [];
|
||||
this.resources = [];
|
||||
this.stack = [];
|
||||
this.debug = false;
|
||||
this.debug = options.debug || false;
|
||||
}
|
||||
readU8() {
|
||||
// debug('readU8');
|
||||
// debug('cursor:', this.cursor);
|
||||
const val = this.buffer[this.cursor];
|
||||
// debug('value:', val);
|
||||
this.cursor += 1;
|
||||
return val;
|
||||
}
|
||||
readU16() {
|
||||
// debug('readU16');
|
||||
// debug('cursor:', this.cursor);
|
||||
const val = this.buffer.readUInt16LE(this.cursor);
|
||||
// debug('value:', val);
|
||||
this.cursor += 2;
|
||||
return val;
|
||||
}
|
||||
readS32() {
|
||||
// debug('readS32');
|
||||
// debug('cursor:', this.cursor);
|
||||
const val = this.buffer.readInt32LE(this.cursor);
|
||||
// debug('value:', val);
|
||||
this.cursor += 4;
|
||||
return val;
|
||||
}
|
||||
readU32() {
|
||||
// debug('readU32');
|
||||
// debug('cursor:', this.cursor);
|
||||
const val = this.buffer.readUInt32LE(this.cursor);
|
||||
// debug('value:', val);
|
||||
this.cursor += 4;
|
||||
return val;
|
||||
}
|
||||
readLength8() {
|
||||
// debug('readLength8');
|
||||
let len = this.readU8();
|
||||
if (len & 0x80) {
|
||||
len = (len & 0x7f) << 8;
|
||||
len += this.readU8();
|
||||
}
|
||||
// debug('length:', len);
|
||||
return len;
|
||||
}
|
||||
readLength16() {
|
||||
// debug('readLength16');
|
||||
let len = this.readU16();
|
||||
if (len & 0x8000) {
|
||||
len = (len & 0x7fff) << 16;
|
||||
len += this.readU16();
|
||||
}
|
||||
// debug('length:', len);
|
||||
return len;
|
||||
}
|
||||
readDimension() {
|
||||
// debug('readDimension');
|
||||
const dimension = {
|
||||
value: null,
|
||||
unit: null,
|
||||
rawUnit: null,
|
||||
};
|
||||
const value = this.readU32();
|
||||
const unit = dimension.value & 0xff;
|
||||
dimension.value = value >> 8;
|
||||
dimension.rawUnit = unit;
|
||||
switch (unit) {
|
||||
case TypedValue.COMPLEX_UNIT_MM:
|
||||
dimension.unit = 'mm';
|
||||
break;
|
||||
case TypedValue.COMPLEX_UNIT_PX:
|
||||
dimension.unit = 'px';
|
||||
break;
|
||||
case TypedValue.COMPLEX_UNIT_DIP:
|
||||
dimension.unit = 'dp';
|
||||
break;
|
||||
case TypedValue.COMPLEX_UNIT_SP:
|
||||
dimension.unit = 'sp';
|
||||
break;
|
||||
case TypedValue.COMPLEX_UNIT_PT:
|
||||
dimension.unit = 'pt';
|
||||
break;
|
||||
case TypedValue.COMPLEX_UNIT_IN:
|
||||
dimension.unit = 'in';
|
||||
break;
|
||||
}
|
||||
return dimension;
|
||||
}
|
||||
readFraction() {
|
||||
// debug('readFraction');
|
||||
const fraction = {
|
||||
value: null,
|
||||
type: null,
|
||||
rawType: null,
|
||||
};
|
||||
const value = this.readU32();
|
||||
const type = value & 0xf;
|
||||
fraction.value = this.convertIntToFloat(value >> 4);
|
||||
fraction.rawType = type;
|
||||
switch (type) {
|
||||
case TypedValue.COMPLEX_UNIT_FRACTION:
|
||||
fraction.type = '%';
|
||||
break;
|
||||
case TypedValue.COMPLEX_UNIT_FRACTION_PARENT:
|
||||
fraction.type = '%p';
|
||||
break;
|
||||
}
|
||||
return fraction;
|
||||
}
|
||||
readHex24() {
|
||||
// debug('readHex24');
|
||||
const val = (this.readU32() & 0xffffff).toString(16);
|
||||
return val;
|
||||
}
|
||||
readHex32() {
|
||||
// debug('readHex32');
|
||||
const val = this.readU32().toString(16);
|
||||
return val;
|
||||
}
|
||||
readTypedValue() {
|
||||
// debug('readTypedValue');
|
||||
const typedValue = {
|
||||
value: null,
|
||||
type: null,
|
||||
rawType: null,
|
||||
};
|
||||
const start = this.cursor;
|
||||
let size = this.readU16();
|
||||
/* const zero = */ this.readU8();
|
||||
const dataType = this.readU8();
|
||||
// Yes, there has been a real world APK where the size is malformed.
|
||||
if (size === 0) {
|
||||
size = 8;
|
||||
}
|
||||
typedValue.rawType = dataType;
|
||||
switch (dataType) {
|
||||
case TypedValue.TYPE_INT_DEC:
|
||||
typedValue.value = this.readS32();
|
||||
typedValue.type = 'int_dec';
|
||||
break;
|
||||
case TypedValue.TYPE_INT_HEX:
|
||||
typedValue.value = this.readS32();
|
||||
typedValue.type = 'int_hex';
|
||||
break;
|
||||
case TypedValue.TYPE_STRING: {
|
||||
const ref = this.readS32();
|
||||
typedValue.value = ref > 0 ? this.strings[ref] : '';
|
||||
typedValue.type = 'string';
|
||||
break;
|
||||
}
|
||||
case TypedValue.TYPE_REFERENCE: {
|
||||
const id = this.readU32();
|
||||
typedValue.value = `resourceId:0x${id.toString(16)}`;
|
||||
typedValue.type = 'reference';
|
||||
break;
|
||||
}
|
||||
case TypedValue.TYPE_INT_BOOLEAN:
|
||||
typedValue.value = this.readS32() !== 0;
|
||||
typedValue.type = 'boolean';
|
||||
break;
|
||||
case TypedValue.TYPE_NULL:
|
||||
this.readU32();
|
||||
typedValue.value = null;
|
||||
typedValue.type = 'null';
|
||||
break;
|
||||
case TypedValue.TYPE_INT_COLOR_RGB8:
|
||||
typedValue.value = this.readHex24();
|
||||
typedValue.type = 'rgb8';
|
||||
break;
|
||||
case TypedValue.TYPE_INT_COLOR_RGB4:
|
||||
typedValue.value = this.readHex24();
|
||||
typedValue.type = 'rgb4';
|
||||
break;
|
||||
case TypedValue.TYPE_INT_COLOR_ARGB8:
|
||||
typedValue.value = this.readHex32();
|
||||
typedValue.type = 'argb8';
|
||||
break;
|
||||
case TypedValue.TYPE_INT_COLOR_ARGB4:
|
||||
typedValue.value = this.readHex32();
|
||||
typedValue.type = 'argb4';
|
||||
break;
|
||||
case TypedValue.TYPE_DIMENSION:
|
||||
typedValue.value = this.readDimension();
|
||||
typedValue.type = 'dimension';
|
||||
break;
|
||||
case TypedValue.TYPE_FRACTION:
|
||||
typedValue.value = this.readFraction();
|
||||
typedValue.type = 'fraction';
|
||||
break;
|
||||
default: {
|
||||
// const type = dataType.toString(16);
|
||||
// debug(`Not sure what to do with typed value of type 0x${type}, falling back to reading an uint32.`);
|
||||
typedValue.value = this.readU32();
|
||||
typedValue.type = 'unknown';
|
||||
}
|
||||
}
|
||||
// Ensure we consume the whole value
|
||||
const end = start + size;
|
||||
if (this.cursor !== end) {
|
||||
// const type = dataType.toString(16);
|
||||
// const diff = end - this.cursor;
|
||||
// debug(`Cursor is off by ${diff} bytes at ${this.cursor} at supposed end \
|
||||
// of typed value of type 0x${type}. The typed value started at offset ${start} \
|
||||
// and is supposed to end at offset ${end}. Ignoring the rest of the value.`);
|
||||
this.cursor = end;
|
||||
}
|
||||
return typedValue;
|
||||
}
|
||||
// https://twitter.com/kawasima/status/427730289201139712
|
||||
convertIntToFloat(int) {
|
||||
const buf = new ArrayBuffer(4);
|
||||
new Int32Array(buf)[0] = int;
|
||||
return new Float32Array(buf)[0];
|
||||
}
|
||||
readString(encoding) {
|
||||
// debug('readString', encoding);
|
||||
let stringLength;
|
||||
let byteLength;
|
||||
let value;
|
||||
switch (encoding) {
|
||||
case 'utf-8':
|
||||
stringLength = this.readLength8();
|
||||
// debug('stringLength:', stringLength);
|
||||
byteLength = this.readLength8();
|
||||
// debug('byteLength:', byteLength);
|
||||
value = this.buffer.toString(encoding, this.cursor, (this.cursor += byteLength));
|
||||
// debug('value:', value);
|
||||
assert.equal(this.readU8(), 0, 'String must end with trailing zero');
|
||||
return value;
|
||||
case 'ucs2':
|
||||
stringLength = this.readLength16();
|
||||
// debug('stringLength:', stringLength);
|
||||
byteLength = stringLength * 2;
|
||||
// debug('byteLength:', byteLength);
|
||||
value = this.buffer.toString(encoding, this.cursor, (this.cursor += byteLength));
|
||||
// debug('value:', value);
|
||||
assert.equal(this.readU16(), 0, 'String must end with trailing zero');
|
||||
return value;
|
||||
default:
|
||||
throw new errors_1.Exception(`Unsupported encoding '${encoding}'`);
|
||||
}
|
||||
}
|
||||
readChunkHeader() {
|
||||
// debug('readChunkHeader');
|
||||
const header = {
|
||||
startOffset: this.cursor,
|
||||
chunkType: this.readU16(),
|
||||
headerSize: this.readU16(),
|
||||
chunkSize: this.readU32(),
|
||||
};
|
||||
// debug('startOffset:', header.startOffset);
|
||||
// debug('chunkType:', header.chunkType);
|
||||
// debug('headerSize:', header.headerSize);
|
||||
// debug('chunkSize:', header.chunkSize);
|
||||
return header;
|
||||
}
|
||||
readStringPool(header) {
|
||||
// debug('readStringPool');
|
||||
header.stringCount = this.readU32();
|
||||
// debug('stringCount:', header.stringCount);
|
||||
header.styleCount = this.readU32();
|
||||
// debug('styleCount:', header.styleCount);
|
||||
header.flags = this.readU32();
|
||||
// debug('flags:', header.flags);
|
||||
header.stringsStart = this.readU32();
|
||||
// debug('stringsStart:', header.stringsStart);
|
||||
header.stylesStart = this.readU32();
|
||||
// debug('stylesStart:', header.stylesStart);
|
||||
if (header.chunkType !== ChunkType.STRING_POOL) {
|
||||
throw new errors_1.Exception('Invalid string pool header');
|
||||
}
|
||||
const offsets = [];
|
||||
for (let i = 0, l = header.stringCount; i < l; ++i) {
|
||||
// debug('offset:', i);
|
||||
offsets.push(this.readU32());
|
||||
}
|
||||
// const sorted = (header.flags & StringFlags.SORTED) === StringFlags.SORTED;
|
||||
// debug('sorted:', sorted);
|
||||
const encoding = (header.flags & StringFlags.UTF8) === StringFlags.UTF8 ? 'utf-8' : 'ucs2';
|
||||
// debug('encoding:', encoding);
|
||||
const stringsStart = header.startOffset + header.stringsStart;
|
||||
this.cursor = stringsStart;
|
||||
for (let i = 0, l = header.stringCount; i < l; ++i) {
|
||||
// debug('string:', i);
|
||||
// debug('offset:', offsets[i]);
|
||||
this.cursor = stringsStart + offsets[i];
|
||||
this.strings.push(this.readString(encoding));
|
||||
}
|
||||
// Skip styles
|
||||
this.cursor = header.startOffset + header.chunkSize;
|
||||
return null;
|
||||
}
|
||||
readResourceMap(header) {
|
||||
// debug('readResourceMap');
|
||||
const count = Math.floor((header.chunkSize - header.headerSize) / 4);
|
||||
for (let i = 0; i < count; ++i) {
|
||||
this.resources.push(this.readU32());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
readXmlNamespaceStart( /* header */) {
|
||||
// debug('readXmlNamespaceStart');
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
// const line = this.readU32();
|
||||
// const commentRef = this.readU32();
|
||||
// const prefixRef = this.readS32();
|
||||
// const uriRef = this.readS32();
|
||||
// We don't currently care about the values, but they could
|
||||
// be accessed like so:
|
||||
//
|
||||
// namespaceURI.prefix = this.strings[prefixRef] // if prefixRef > 0
|
||||
// namespaceURI.uri = this.strings[uriRef] // if uriRef > 0
|
||||
return null;
|
||||
}
|
||||
readXmlNamespaceEnd( /* header */) {
|
||||
// debug('readXmlNamespaceEnd');
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
// const line = this.readU32();
|
||||
// const commentRef = this.readU32();
|
||||
// const prefixRef = this.readS32();
|
||||
// const uriRef = this.readS32();
|
||||
// We don't currently care about the values, but they could
|
||||
// be accessed like so:
|
||||
//
|
||||
// namespaceURI.prefix = this.strings[prefixRef] // if prefixRef > 0
|
||||
// namespaceURI.uri = this.strings[uriRef] // if uriRef > 0
|
||||
return null;
|
||||
}
|
||||
readXmlElementStart( /* header */) {
|
||||
// debug('readXmlElementStart');
|
||||
const node = {
|
||||
namespaceURI: null,
|
||||
nodeType: NodeType.ELEMENT_NODE,
|
||||
nodeName: null,
|
||||
attributes: [],
|
||||
childNodes: [],
|
||||
};
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
// const line = this.readU32();
|
||||
// const commentRef = this.readU32();
|
||||
const nsRef = this.readS32();
|
||||
const nameRef = this.readS32();
|
||||
if (nsRef > 0) {
|
||||
node.namespaceURI = this.strings[nsRef];
|
||||
}
|
||||
node.nodeName = this.strings[nameRef];
|
||||
this.readU16();
|
||||
this.readU16();
|
||||
// const attrStart = this.readU16();
|
||||
// const attrSize = this.readU16();
|
||||
const attrCount = this.readU16();
|
||||
// const idIndex = this.readU16();
|
||||
// const classIndex = this.readU16();
|
||||
// const styleIndex = this.readU16();
|
||||
this.readU16();
|
||||
this.readU16();
|
||||
this.readU16();
|
||||
for (let i = 0; i < attrCount; ++i) {
|
||||
node.attributes.push(this.readXmlAttribute());
|
||||
}
|
||||
if (this.document) {
|
||||
this.parent.childNodes.push(node);
|
||||
this.parent = node;
|
||||
}
|
||||
else {
|
||||
this.document = this.parent = node;
|
||||
}
|
||||
this.stack.push(node);
|
||||
return node;
|
||||
}
|
||||
readXmlAttribute() {
|
||||
// debug('readXmlAttribute');
|
||||
const attr = {
|
||||
namespaceURI: null,
|
||||
nodeType: NodeType.ATTRIBUTE_NODE,
|
||||
nodeName: null,
|
||||
name: null,
|
||||
value: null,
|
||||
typedValue: null,
|
||||
};
|
||||
const nsRef = this.readS32();
|
||||
const nameRef = this.readS32();
|
||||
const valueRef = this.readS32();
|
||||
if (nsRef > 0) {
|
||||
attr.namespaceURI = this.strings[nsRef];
|
||||
}
|
||||
attr.nodeName = attr.name = this.strings[nameRef];
|
||||
if (valueRef > 0) {
|
||||
attr.value = this.strings[valueRef];
|
||||
}
|
||||
attr.typedValue = this.readTypedValue();
|
||||
return attr;
|
||||
}
|
||||
readXmlElementEnd( /* header */) {
|
||||
// debug('readXmlCData');
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
// const line = this.readU32();
|
||||
// const commentRef = this.readU32();
|
||||
// const nsRef = this.readS32();
|
||||
// const nameRef = this.readS32();
|
||||
this.stack.pop();
|
||||
this.parent = this.stack[this.stack.length - 1];
|
||||
return null;
|
||||
}
|
||||
readXmlCData( /* header */) {
|
||||
// debug('readXmlCData');
|
||||
const cdata = {
|
||||
namespaceURI: null,
|
||||
nodeType: NodeType.CDATA_SECTION_NODE,
|
||||
nodeName: '#cdata',
|
||||
data: null,
|
||||
typedValue: null,
|
||||
};
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
// const line = this.readU32();
|
||||
// const commentRef = this.readU32();
|
||||
const dataRef = this.readS32();
|
||||
if (dataRef > 0) {
|
||||
cdata.data = this.strings[dataRef];
|
||||
}
|
||||
cdata.typedValue = this.readTypedValue();
|
||||
this.parent.childNodes.push(cdata);
|
||||
return cdata;
|
||||
}
|
||||
readNull(header) {
|
||||
// debug('readNull');
|
||||
this.cursor += header.chunkSize - header.headerSize;
|
||||
return null;
|
||||
}
|
||||
parse() {
|
||||
// debug('parse');
|
||||
const xmlHeader = this.readChunkHeader();
|
||||
if (xmlHeader.chunkType !== ChunkType.XML) {
|
||||
throw new errors_1.Exception('Invalid XML header');
|
||||
}
|
||||
while (this.cursor < this.buffer.length) {
|
||||
// debug('chunk');
|
||||
const start = this.cursor;
|
||||
const header = this.readChunkHeader();
|
||||
switch (header.chunkType) {
|
||||
case ChunkType.STRING_POOL:
|
||||
this.readStringPool(header);
|
||||
break;
|
||||
case ChunkType.XML_RESOURCE_MAP:
|
||||
this.readResourceMap(header);
|
||||
break;
|
||||
case ChunkType.XML_START_NAMESPACE:
|
||||
this.readXmlNamespaceStart();
|
||||
break;
|
||||
case ChunkType.XML_END_NAMESPACE:
|
||||
this.readXmlNamespaceEnd();
|
||||
break;
|
||||
case ChunkType.XML_START_ELEMENT:
|
||||
this.readXmlElementStart();
|
||||
break;
|
||||
case ChunkType.XML_END_ELEMENT:
|
||||
this.readXmlElementEnd();
|
||||
break;
|
||||
case ChunkType.XML_CDATA:
|
||||
this.readXmlCData();
|
||||
break;
|
||||
case ChunkType.NULL:
|
||||
this.readNull(header);
|
||||
break;
|
||||
default:
|
||||
throw new errors_1.Exception(`Unsupported chunk type '${header.chunkType}'`);
|
||||
}
|
||||
// Ensure we consume the whole chunk
|
||||
const end = start + header.chunkSize;
|
||||
if (this.cursor !== end) {
|
||||
// const diff = end - this.cursor;
|
||||
// const type = header.chunkType.toString(16);
|
||||
// debug(`Cursor is off by ${diff} bytes at ${this.cursor} at supposed \
|
||||
// end of chunk of type 0x${type}. The chunk started at offset ${start} and is \
|
||||
// supposed to end at offset ${end}. Ignoring the rest of the chunk.`);
|
||||
// this.cursor = end;
|
||||
}
|
||||
}
|
||||
return this.document;
|
||||
}
|
||||
}
|
||||
exports.BinaryXmlParser = BinaryXmlParser;
|
||||
204
node_modules/native-run/dist/android/utils/emulator.js
generated
vendored
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.parseAndroidConsoleResponse = exports.getAVDFromEmulator = exports.parseEmulatorOutput = exports.EmulatorEvent = exports.spawnEmulator = exports.runEmulator = void 0;
|
||||
const utils_fs_1 = require("@ionic/utils-fs");
|
||||
const child_process_1 = require("child_process");
|
||||
const Debug = require("debug");
|
||||
const net = require("net");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
const split2 = require("split2");
|
||||
const through2 = require("through2");
|
||||
const errors_1 = require("../../errors");
|
||||
const fn_1 = require("../../utils/fn");
|
||||
const adb_1 = require("./adb");
|
||||
const sdk_1 = require("./sdk");
|
||||
const modulePrefix = 'native-run:android:utils:emulator';
|
||||
/**
|
||||
* Resolves when emulator is ready and running with the specified AVD.
|
||||
*/
|
||||
async function runEmulator(sdk, avd, port) {
|
||||
try {
|
||||
await spawnEmulator(sdk, avd, port);
|
||||
}
|
||||
catch (e) {
|
||||
if (!(e instanceof errors_1.EmulatorException) || e.code !== errors_1.ERR_ALREADY_RUNNING) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
const serial = `emulator-${port}`;
|
||||
const devices = await (0, adb_1.getDevices)(sdk);
|
||||
const emulator = devices.find((device) => device.serial === serial);
|
||||
if (!emulator) {
|
||||
throw new errors_1.EmulatorException(`Emulator not found: ${serial}`);
|
||||
}
|
||||
return emulator;
|
||||
}
|
||||
exports.runEmulator = runEmulator;
|
||||
async function spawnEmulator(sdk, avd, port) {
|
||||
const debug = Debug(`${modulePrefix}:${spawnEmulator.name}`);
|
||||
const emulator = await (0, sdk_1.getSDKPackage)(path.join(sdk.root, 'emulator'));
|
||||
const emulatorBin = path.join(emulator.location, 'emulator');
|
||||
const args = ['-avd', avd.id, '-port', port.toString(), '-verbose'];
|
||||
debug('Invoking emulator: %O %O', emulatorBin, args);
|
||||
const p = (0, child_process_1.spawn)(emulatorBin, args, {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: (0, sdk_1.supplementProcessEnv)(sdk),
|
||||
});
|
||||
p.unref();
|
||||
return new Promise((_resolve, _reject) => {
|
||||
const resolve = (0, fn_1.once)(() => {
|
||||
_resolve();
|
||||
cleanup();
|
||||
});
|
||||
const reject = (0, fn_1.once)((err) => {
|
||||
_reject(err);
|
||||
cleanup();
|
||||
});
|
||||
(0, adb_1.waitForDevice)(sdk, `emulator-${port}`).then(() => resolve(), (err) => reject(err));
|
||||
const eventParser = through2((chunk, enc, cb) => {
|
||||
const line = chunk.toString();
|
||||
debug('Android Emulator: %O', line);
|
||||
const event = parseEmulatorOutput(line);
|
||||
if (event === EmulatorEvent.AlreadyRunning) {
|
||||
reject(new errors_1.EmulatorException(`Emulator already running with AVD [${avd.id}]`, errors_1.ERR_ALREADY_RUNNING));
|
||||
}
|
||||
else if (event === EmulatorEvent.UnknownAVD) {
|
||||
reject(new errors_1.EmulatorException(`Unknown AVD name [${avd.id}]`, errors_1.ERR_UNKNOWN_AVD));
|
||||
}
|
||||
else if (event === EmulatorEvent.AVDHomeNotFound) {
|
||||
reject(new errors_1.EmulatorException(`Emulator cannot find AVD home`, errors_1.ERR_AVD_HOME_NOT_FOUND));
|
||||
}
|
||||
cb();
|
||||
});
|
||||
const stdoutStream = p.stdout.pipe(split2());
|
||||
const stderrStream = p.stderr.pipe(split2());
|
||||
stdoutStream.pipe(eventParser);
|
||||
stderrStream.pipe(eventParser);
|
||||
const cleanup = () => {
|
||||
debug('Unhooking stdout/stderr streams from emulator process');
|
||||
p.stdout.push(null);
|
||||
p.stderr.push(null);
|
||||
};
|
||||
p.on('close', (code) => {
|
||||
debug('Emulator closed, exit code %d', code);
|
||||
if (code) {
|
||||
reject(new errors_1.EmulatorException(`Non-zero exit code from Emulator: ${code}`, errors_1.ERR_NON_ZERO_EXIT));
|
||||
}
|
||||
});
|
||||
p.on('error', (err) => {
|
||||
debug('Emulator error: %O', err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
exports.spawnEmulator = spawnEmulator;
|
||||
var EmulatorEvent;
|
||||
(function (EmulatorEvent) {
|
||||
EmulatorEvent[EmulatorEvent["UnknownAVD"] = 0] = "UnknownAVD";
|
||||
EmulatorEvent[EmulatorEvent["AlreadyRunning"] = 1] = "AlreadyRunning";
|
||||
EmulatorEvent[EmulatorEvent["AVDHomeNotFound"] = 2] = "AVDHomeNotFound";
|
||||
})(EmulatorEvent = exports.EmulatorEvent || (exports.EmulatorEvent = {}));
|
||||
function parseEmulatorOutput(line) {
|
||||
const debug = Debug(`${modulePrefix}:${parseEmulatorOutput.name}`);
|
||||
let event;
|
||||
if (line.includes('Unknown AVD name')) {
|
||||
event = EmulatorEvent.UnknownAVD;
|
||||
}
|
||||
else if (line.includes('another emulator instance running with the current AVD')) {
|
||||
event = EmulatorEvent.AlreadyRunning;
|
||||
}
|
||||
else if (line.includes('Cannot find AVD system path')) {
|
||||
event = EmulatorEvent.AVDHomeNotFound;
|
||||
}
|
||||
if (typeof event !== 'undefined') {
|
||||
debug('Parsed event from emulator output: %s', EmulatorEvent[event]);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
exports.parseEmulatorOutput = parseEmulatorOutput;
|
||||
async function getAVDFromEmulator(emulator, avds) {
|
||||
const debug = Debug(`${modulePrefix}:${getAVDFromEmulator.name}`);
|
||||
const emulatorPortRegex = /^emulator-(\d+)$/;
|
||||
const m = emulator.serial.match(emulatorPortRegex);
|
||||
if (!m) {
|
||||
throw new errors_1.EmulatorException(`Emulator ${emulator.serial} does not match expected emulator serial format`);
|
||||
}
|
||||
const port = Number.parseInt(m[1], 10);
|
||||
const host = 'localhost';
|
||||
const sock = net.createConnection({ host, port });
|
||||
sock.setEncoding('utf8');
|
||||
sock.setTimeout(5000);
|
||||
const readAuthFile = new Promise((resolve, reject) => {
|
||||
sock.on('connect', () => {
|
||||
debug('Connected to %s:%d', host, port);
|
||||
(0, utils_fs_1.readFile)(path.resolve(os.homedir(), '.emulator_console_auth_token'), {
|
||||
encoding: 'utf8',
|
||||
}).then((contents) => resolve(contents.trim()), (err) => reject(err));
|
||||
});
|
||||
});
|
||||
let Stage;
|
||||
(function (Stage) {
|
||||
Stage[Stage["Initial"] = 0] = "Initial";
|
||||
Stage[Stage["Auth"] = 1] = "Auth";
|
||||
Stage[Stage["AuthSuccess"] = 2] = "AuthSuccess";
|
||||
Stage[Stage["Response"] = 3] = "Response";
|
||||
Stage[Stage["Complete"] = 4] = "Complete";
|
||||
})(Stage || (Stage = {}));
|
||||
return new Promise((resolve, reject) => {
|
||||
let stage = Stage.Initial;
|
||||
const timer = setTimeout(() => {
|
||||
if (stage !== Stage.Complete) {
|
||||
reject(new errors_1.EmulatorException(`Took too long to get AVD name from Android Emulator Console, something went wrong.`));
|
||||
}
|
||||
}, 3000);
|
||||
const cleanup = (0, fn_1.once)(() => {
|
||||
clearTimeout(timer);
|
||||
sock.end();
|
||||
});
|
||||
sock.on('timeout', () => {
|
||||
reject(new errors_1.EmulatorException(`Socket timeout on ${host}:${port}`));
|
||||
cleanup();
|
||||
});
|
||||
sock.pipe(split2()).pipe(through2((chunk, enc, cb) => {
|
||||
const line = chunk.toString();
|
||||
debug('Android Console: %O', line);
|
||||
if (stage === Stage.Initial && line.includes('Authentication required')) {
|
||||
stage = Stage.Auth;
|
||||
}
|
||||
else if (stage === Stage.Auth && line.trim() === 'OK') {
|
||||
readAuthFile.then((token) => sock.write(`auth ${token}\n`, 'utf8'), (err) => reject(err));
|
||||
stage = Stage.AuthSuccess;
|
||||
}
|
||||
else if (stage === Stage.AuthSuccess && line.trim() === 'OK') {
|
||||
sock.write('avd name\n', 'utf8');
|
||||
stage = Stage.Response;
|
||||
}
|
||||
else if (stage === Stage.Response) {
|
||||
const avdId = line.trim();
|
||||
const avd = avds.find((avd) => avd.id === avdId);
|
||||
if (avd) {
|
||||
resolve(avd);
|
||||
}
|
||||
else {
|
||||
reject(new errors_1.EmulatorException(`Unknown AVD name [${avdId}]`, errors_1.ERR_UNKNOWN_AVD));
|
||||
}
|
||||
stage = Stage.Complete;
|
||||
cleanup();
|
||||
}
|
||||
cb();
|
||||
}));
|
||||
});
|
||||
}
|
||||
exports.getAVDFromEmulator = getAVDFromEmulator;
|
||||
function parseAndroidConsoleResponse(output) {
|
||||
const debug = Debug(`${modulePrefix}:${parseAndroidConsoleResponse.name}`);
|
||||
const m = /([\s\S]+)OK\r?\n/g.exec(output);
|
||||
if (m) {
|
||||
const [, response] = m;
|
||||
debug('Parsed response data from Android Console output: %O', response);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
exports.parseAndroidConsoleResponse = parseAndroidConsoleResponse;
|
||||
32
node_modules/native-run/dist/android/utils/list.js
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.avdToTarget = exports.deviceToTarget = exports.getVirtualTargets = exports.getDeviceTargets = void 0;
|
||||
const adb_1 = require("./adb");
|
||||
const avd_1 = require("./avd");
|
||||
async function getDeviceTargets(sdk) {
|
||||
return (await (0, adb_1.getDevices)(sdk)).filter((device) => device.type === 'hardware').map(deviceToTarget);
|
||||
}
|
||||
exports.getDeviceTargets = getDeviceTargets;
|
||||
async function getVirtualTargets(sdk) {
|
||||
const avds = await (0, avd_1.getInstalledAVDs)(sdk);
|
||||
return avds.map(avdToTarget);
|
||||
}
|
||||
exports.getVirtualTargets = getVirtualTargets;
|
||||
function deviceToTarget(device) {
|
||||
return {
|
||||
platform: 'android',
|
||||
model: `${device.manufacturer} ${device.model}`,
|
||||
sdkVersion: device.sdkVersion,
|
||||
id: device.serial,
|
||||
};
|
||||
}
|
||||
exports.deviceToTarget = deviceToTarget;
|
||||
function avdToTarget(avd) {
|
||||
return {
|
||||
platform: 'android',
|
||||
name: avd.name,
|
||||
sdkVersion: avd.sdkVersion,
|
||||
id: avd.id,
|
||||
};
|
||||
}
|
||||
exports.avdToTarget = avdToTarget;
|
||||
132
node_modules/native-run/dist/android/utils/run.js
generated
vendored
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.installApkToDevice = exports.selectVirtualDevice = exports.selectHardwareDevice = exports.selectDeviceByTarget = exports.isLikelyEmulator = exports.findAvailableEmulatorPort = void 0;
|
||||
const Debug = require("debug");
|
||||
const errors_1 = require("../../errors");
|
||||
const log_1 = require("../../utils/log");
|
||||
const adb_1 = require("./adb");
|
||||
const emulator_1 = require("./emulator");
|
||||
const modulePrefix = 'native-run:android:utils:run';
|
||||
async function findAvailableEmulatorPort(devices, start = 5554, end = 5584) {
|
||||
const debug = Debug(`${modulePrefix}:${findAvailableEmulatorPort.name}`);
|
||||
const usedPorts = new Set();
|
||||
for (const d of devices) {
|
||||
const m = d.serial.match(/^emulator-(\d+)$/);
|
||||
if (m) {
|
||||
usedPorts.add(Number(m[1]));
|
||||
}
|
||||
}
|
||||
for (let port = start; port <= end; port += 2) {
|
||||
if (!usedPorts.has(port)) {
|
||||
debug('Available emulator port found: %d', port);
|
||||
return port;
|
||||
}
|
||||
}
|
||||
debug('No available emulator ports found in range %d-%d; defaulting to 5554', start, end);
|
||||
return 5554;
|
||||
}
|
||||
exports.findAvailableEmulatorPort = findAvailableEmulatorPort;
|
||||
function isLikelyEmulator(device) {
|
||||
const serialEmu = /^emulator-(\d+)$/;
|
||||
if (serialEmu.test(device.serial)) {
|
||||
return true;
|
||||
}
|
||||
if (device.type === 'emulator') {
|
||||
return true;
|
||||
}
|
||||
const props = device.properties || {};
|
||||
const deviceProp = (props['device'] || '').toLowerCase();
|
||||
const productProp = (props['product'] || '').toLowerCase();
|
||||
const model = (device.model || '').toLowerCase();
|
||||
if (deviceProp.startsWith('emu') || deviceProp.includes('generic')) {
|
||||
return true;
|
||||
}
|
||||
if (productProp.includes('sdk_gphone') || productProp.includes('google_sdk')) {
|
||||
return true;
|
||||
}
|
||||
if (model.includes('android_sdk') || model.includes('sdk_gphone')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
exports.isLikelyEmulator = isLikelyEmulator;
|
||||
async function selectDeviceByTarget(sdk, devices, avds, target) {
|
||||
const debug = Debug(`${modulePrefix}:${selectDeviceByTarget.name}`);
|
||||
debug('--target %s detected', target);
|
||||
debug('Checking if device can be found by serial: %s', target);
|
||||
const device = devices.find((d) => d.serial === target);
|
||||
if (device) {
|
||||
debug('Device found by serial: %s', device.serial);
|
||||
return device;
|
||||
}
|
||||
const emulatorDevices = devices.filter(isLikelyEmulator);
|
||||
const pairAVD = async (emulator) => {
|
||||
let avd;
|
||||
try {
|
||||
avd = await (0, emulator_1.getAVDFromEmulator)(emulator, avds);
|
||||
debug('Emulator %s is using AVD: %s', emulator.serial, avd.id);
|
||||
}
|
||||
catch (e) {
|
||||
debug('Error with emulator %s: %O', emulator.serial, e);
|
||||
}
|
||||
return [emulator, avd];
|
||||
};
|
||||
debug('Checking if any of %d running emulators are using AVD by ID: %s', emulatorDevices.length, target);
|
||||
const emulatorsAndAVDs = await Promise.all(emulatorDevices.map((emulator) => pairAVD(emulator)));
|
||||
const emulators = emulatorsAndAVDs.filter((t) => typeof t[1] !== 'undefined');
|
||||
const emulator = emulators.find(([, avd]) => avd.id === target);
|
||||
if (emulator) {
|
||||
const [device, avd] = emulator;
|
||||
debug('Emulator %s found by AVD: %s', device.serial, avd.id);
|
||||
return device;
|
||||
}
|
||||
debug('Checking if AVD can be found by ID: %s', target);
|
||||
const avd = avds.find((avd) => avd.id === target);
|
||||
if (avd) {
|
||||
debug('AVD found by ID: %s', avd.id);
|
||||
const port = await findAvailableEmulatorPort(devices);
|
||||
debug('Using emulator port: %d', port);
|
||||
const device = await (0, emulator_1.runEmulator)(sdk, avd, port);
|
||||
debug('Emulator ready, running avd: %s on %s', avd.id, device.serial);
|
||||
return device;
|
||||
}
|
||||
}
|
||||
exports.selectDeviceByTarget = selectDeviceByTarget;
|
||||
async function selectHardwareDevice(devices) {
|
||||
const hardwareDevices = devices.filter((d) => d.type === 'hardware');
|
||||
// If a hardware device is found, we prefer launching to it instead of in an emulator.
|
||||
if (hardwareDevices.length > 0) {
|
||||
return hardwareDevices[0]; // TODO: can probably do better analysis on which to use?
|
||||
}
|
||||
}
|
||||
exports.selectHardwareDevice = selectHardwareDevice;
|
||||
async function selectVirtualDevice(sdk, devices, avds) {
|
||||
const debug = Debug(`${modulePrefix}:${selectVirtualDevice.name}`);
|
||||
const emulators = devices.filter((d) => d.type === 'emulator');
|
||||
// If an emulator is running, use it.
|
||||
if (emulators.length > 0) {
|
||||
const [emulator] = emulators;
|
||||
debug('Found running emulator: %s', emulator.serial);
|
||||
return emulator;
|
||||
}
|
||||
throw new errors_1.AndroidRunException('No target devices/emulators available.', errors_1.ERR_NO_TARGET);
|
||||
}
|
||||
exports.selectVirtualDevice = selectVirtualDevice;
|
||||
async function installApkToDevice(sdk, device, apk, appId) {
|
||||
(0, log_1.log)(`Installing ${apk}...\n`);
|
||||
try {
|
||||
await (0, adb_1.installApk)(sdk, device, apk);
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof errors_1.ADBException) {
|
||||
if (e.code === errors_1.ERR_INCOMPATIBLE_UPDATE || e.code === errors_1.ERR_VERSION_DOWNGRADE) {
|
||||
(0, log_1.log)(`${e.message} Uninstalling and trying again...\n`);
|
||||
await (0, adb_1.uninstallApp)(sdk, device, appId);
|
||||
await (0, adb_1.installApk)(sdk, device, apk);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
exports.installApkToDevice = installApkToDevice;
|
||||
47
node_modules/native-run/dist/android/utils/sdk/api.js
generated
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.findPackageBySchemaPath = exports.findPackageBySchema = exports.findUnsatisfiedPackages = exports.getAPILevels = void 0;
|
||||
const Debug = require("debug");
|
||||
const modulePrefix = 'native-run:android:utils:sdk:api';
|
||||
async function getAPILevels(packages) {
|
||||
const debug = Debug(`${modulePrefix}:${getAPILevels.name}`);
|
||||
const levels = [
|
||||
...new Set(packages.map((pkg) => pkg.apiLevel).filter((apiLevel) => typeof apiLevel !== 'undefined')),
|
||||
].sort((a, b) => (a <= b ? 1 : -1));
|
||||
const apis = levels.map((apiLevel) => ({
|
||||
apiLevel,
|
||||
packages: packages.filter((pkg) => pkg.apiLevel === apiLevel),
|
||||
}));
|
||||
debug('Discovered installed API Levels: %O', apis.map((api) => ({ ...api, packages: api.packages.map((pkg) => pkg.path) })));
|
||||
return apis;
|
||||
}
|
||||
exports.getAPILevels = getAPILevels;
|
||||
function findUnsatisfiedPackages(packages, schemas) {
|
||||
return schemas.filter((pkg) => !findPackageBySchema(packages, pkg));
|
||||
}
|
||||
exports.findUnsatisfiedPackages = findUnsatisfiedPackages;
|
||||
function findPackageBySchema(packages, pkg) {
|
||||
const apiPkg = findPackageBySchemaPath(packages, pkg.path);
|
||||
if (apiPkg) {
|
||||
if (typeof pkg.version === 'string') {
|
||||
if (pkg.version === apiPkg.version) {
|
||||
return apiPkg;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (apiPkg.version.match(pkg.version)) {
|
||||
return apiPkg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.findPackageBySchema = findPackageBySchema;
|
||||
function findPackageBySchemaPath(packages, path) {
|
||||
return packages.find((pkg) => {
|
||||
if (typeof path !== 'string') {
|
||||
return !!pkg.path.match(path);
|
||||
}
|
||||
return path === pkg.path;
|
||||
});
|
||||
}
|
||||
exports.findPackageBySchemaPath = findPackageBySchemaPath;
|
||||
169
node_modules/native-run/dist/android/utils/sdk/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.supplementProcessEnv = exports.resolveAVDHome = exports.resolveEmulatorHome = exports.resolveSDKRoot = exports.getSDKPackage = exports.findAllSDKPackages = exports.getSDK = exports.SDK_DIRECTORIES = void 0;
|
||||
const utils_fs_1 = require("@ionic/utils-fs");
|
||||
const Debug = require("debug");
|
||||
const os = require("os");
|
||||
const pathlib = require("path");
|
||||
const errors_1 = require("../../../errors");
|
||||
const fs_1 = require("../../../utils/fs");
|
||||
const xml_1 = require("./xml");
|
||||
const modulePrefix = 'native-run:android:utils:sdk';
|
||||
const homedir = os.homedir();
|
||||
exports.SDK_DIRECTORIES = new Map([
|
||||
['darwin', [pathlib.join(homedir, 'Library', 'Android', 'sdk')]],
|
||||
['linux', [pathlib.join(homedir, 'Android', 'sdk')]],
|
||||
['win32', [pathlib.join(process.env.LOCALAPPDATA || pathlib.join(homedir, 'AppData', 'Local'), 'Android', 'Sdk')]],
|
||||
]);
|
||||
async function getSDK() {
|
||||
const root = await resolveSDKRoot();
|
||||
const emulatorHome = await resolveEmulatorHome();
|
||||
const avdHome = await resolveAVDHome();
|
||||
return { root, emulatorHome, avdHome };
|
||||
}
|
||||
exports.getSDK = getSDK;
|
||||
const pkgcache = new Map();
|
||||
async function findAllSDKPackages(sdk) {
|
||||
const debug = Debug(`${modulePrefix}:${findAllSDKPackages.name}`);
|
||||
if (sdk.packages) {
|
||||
return sdk.packages;
|
||||
}
|
||||
const sourcesRe = /^sources\/android-\d+\/.+\/.+/;
|
||||
debug('Walking %s to discover SDK packages', sdk.root);
|
||||
const contents = await (0, utils_fs_1.readdirp)(sdk.root, {
|
||||
filter: (item) => pathlib.basename(item.path) === 'package.xml',
|
||||
onError: (err) => debug('Error while walking SDK: %O', err),
|
||||
walkerOptions: {
|
||||
pathFilter: (p) => {
|
||||
if ([
|
||||
'bin',
|
||||
'bin64',
|
||||
'lib',
|
||||
'lib64',
|
||||
'include',
|
||||
'clang-include',
|
||||
'skins',
|
||||
'data',
|
||||
'examples',
|
||||
'resources',
|
||||
'systrace',
|
||||
'extras',
|
||||
// 'm2repository',
|
||||
].includes(pathlib.basename(p))) {
|
||||
return false;
|
||||
}
|
||||
if (p.match(sourcesRe)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
sdk.packages = await Promise.all(contents.map((p) => pathlib.dirname(p)).map((p) => getSDKPackage(p)));
|
||||
sdk.packages.sort((a, b) => (a.name >= b.name ? 1 : -1));
|
||||
return sdk.packages;
|
||||
}
|
||||
exports.findAllSDKPackages = findAllSDKPackages;
|
||||
async function getSDKPackage(location) {
|
||||
const debug = Debug(`${modulePrefix}:${getSDKPackage.name}`);
|
||||
let pkg = pkgcache.get(location);
|
||||
if (!pkg) {
|
||||
const packageXmlPath = pathlib.join(location, 'package.xml');
|
||||
debug('Parsing %s', packageXmlPath);
|
||||
try {
|
||||
const packageXml = await (0, xml_1.readPackageXml)(packageXmlPath);
|
||||
const name = (0, xml_1.getNameFromPackageXml)(packageXml);
|
||||
const version = (0, xml_1.getVersionFromPackageXml)(packageXml);
|
||||
const path = (0, xml_1.getPathFromPackageXml)(packageXml);
|
||||
const apiLevel = (0, xml_1.getAPILevelFromPackageXml)(packageXml);
|
||||
pkg = {
|
||||
path,
|
||||
location,
|
||||
version,
|
||||
name,
|
||||
apiLevel,
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
debug('Encountered error with %s: %O', packageXmlPath, e);
|
||||
if (e.code === 'ENOENT') {
|
||||
throw new errors_1.SDKException(`SDK package not found by location: ${location}.`, errors_1.ERR_SDK_PACKAGE_NOT_FOUND);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
pkgcache.set(location, pkg);
|
||||
}
|
||||
return pkg;
|
||||
}
|
||||
exports.getSDKPackage = getSDKPackage;
|
||||
async function resolveSDKRoot() {
|
||||
const debug = Debug(`${modulePrefix}:${resolveSDKRoot.name}`);
|
||||
debug('Looking for $ANDROID_HOME');
|
||||
// $ANDROID_HOME is deprecated, but still overrides $ANDROID_SDK_ROOT if
|
||||
// defined and valid.
|
||||
if (process.env.ANDROID_HOME && (await (0, fs_1.isDir)(process.env.ANDROID_HOME))) {
|
||||
debug('Using $ANDROID_HOME at %s', process.env.ANDROID_HOME);
|
||||
return process.env.ANDROID_HOME;
|
||||
}
|
||||
debug('Looking for $ANDROID_SDK_ROOT');
|
||||
// No valid $ANDROID_HOME, try $ANDROID_SDK_ROOT.
|
||||
if (process.env.ANDROID_SDK_ROOT && (await (0, fs_1.isDir)(process.env.ANDROID_SDK_ROOT))) {
|
||||
debug('Using $ANDROID_SDK_ROOT at %s', process.env.ANDROID_SDK_ROOT);
|
||||
return process.env.ANDROID_SDK_ROOT;
|
||||
}
|
||||
const sdkDirs = exports.SDK_DIRECTORIES.get(process.platform);
|
||||
if (!sdkDirs) {
|
||||
throw new errors_1.SDKException(`Unsupported platform: ${process.platform}`);
|
||||
}
|
||||
debug('Looking at following directories: %O', sdkDirs);
|
||||
for (const sdkDir of sdkDirs) {
|
||||
if (await (0, fs_1.isDir)(sdkDir)) {
|
||||
debug('Using %s', sdkDir);
|
||||
return sdkDir;
|
||||
}
|
||||
}
|
||||
throw new errors_1.SDKException(`No valid Android SDK root found.`, errors_1.ERR_SDK_NOT_FOUND);
|
||||
}
|
||||
exports.resolveSDKRoot = resolveSDKRoot;
|
||||
async function resolveEmulatorHome() {
|
||||
const debug = Debug(`${modulePrefix}:${resolveEmulatorHome.name}`);
|
||||
debug('Looking for $ANDROID_EMULATOR_HOME');
|
||||
if (process.env.ANDROID_EMULATOR_HOME && (await (0, fs_1.isDir)(process.env.ANDROID_EMULATOR_HOME))) {
|
||||
debug('Using $ANDROID_EMULATOR_HOME at %s', process.env.ANDROID_EMULATOR_HOME);
|
||||
return process.env.ANDROID_EMULATOR_HOME;
|
||||
}
|
||||
debug('Looking at $HOME/.android');
|
||||
const homeEmulatorHome = pathlib.join(homedir, '.android');
|
||||
if (await (0, fs_1.isDir)(homeEmulatorHome)) {
|
||||
debug('Using $HOME/.android/ at %s', homeEmulatorHome);
|
||||
return homeEmulatorHome;
|
||||
}
|
||||
throw new errors_1.SDKException(`No valid Android Emulator home found.`, errors_1.ERR_EMULATOR_HOME_NOT_FOUND);
|
||||
}
|
||||
exports.resolveEmulatorHome = resolveEmulatorHome;
|
||||
async function resolveAVDHome() {
|
||||
const debug = Debug(`${modulePrefix}:${resolveAVDHome.name}`);
|
||||
debug('Looking for $ANDROID_AVD_HOME');
|
||||
if (process.env.ANDROID_AVD_HOME && (await (0, fs_1.isDir)(process.env.ANDROID_AVD_HOME))) {
|
||||
debug('Using $ANDROID_AVD_HOME at %s', process.env.ANDROID_AVD_HOME);
|
||||
return process.env.ANDROID_AVD_HOME;
|
||||
}
|
||||
debug('Looking at $HOME/.android/avd');
|
||||
const homeAvdHome = pathlib.join(homedir, '.android', 'avd');
|
||||
if (!(await (0, fs_1.isDir)(homeAvdHome))) {
|
||||
debug('Creating directory: %s', homeAvdHome);
|
||||
await (0, utils_fs_1.mkdirp)(homeAvdHome);
|
||||
}
|
||||
debug('Using $HOME/.android/avd/ at %s', homeAvdHome);
|
||||
return homeAvdHome;
|
||||
}
|
||||
exports.resolveAVDHome = resolveAVDHome;
|
||||
function supplementProcessEnv(sdk) {
|
||||
return {
|
||||
...process.env,
|
||||
ANDROID_SDK_ROOT: sdk.root,
|
||||
ANDROID_EMULATOR_HOME: sdk.emulatorHome,
|
||||
ANDROID_AVD_HOME: sdk.avdHome,
|
||||
};
|
||||
}
|
||||
exports.supplementProcessEnv = supplementProcessEnv;
|
||||
58
node_modules/native-run/dist/android/utils/sdk/xml.js
generated
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getVersionFromPackageXml = exports.getNameFromPackageXml = exports.getPathFromPackageXml = exports.readPackageXml = exports.getAPILevelFromPackageXml = void 0;
|
||||
const utils_fs_1 = require("@ionic/utils-fs");
|
||||
const errors_1 = require("../../../errors");
|
||||
function getAPILevelFromPackageXml(packageXml) {
|
||||
var _a;
|
||||
const apiLevel = packageXml.find('./localPackage/type-details/api-level');
|
||||
return (_a = apiLevel === null || apiLevel === void 0 ? void 0 : apiLevel.text) === null || _a === void 0 ? void 0 : _a.toString();
|
||||
}
|
||||
exports.getAPILevelFromPackageXml = getAPILevelFromPackageXml;
|
||||
async function readPackageXml(path) {
|
||||
const et = await Promise.resolve().then(() => require('elementtree'));
|
||||
const contents = await (0, utils_fs_1.readFile)(path, { encoding: 'utf8' });
|
||||
const etree = et.parse(contents);
|
||||
return etree;
|
||||
}
|
||||
exports.readPackageXml = readPackageXml;
|
||||
function getPathFromPackageXml(packageXml) {
|
||||
const localPackage = packageXml.find('./localPackage');
|
||||
if (!localPackage) {
|
||||
throw new errors_1.SDKException(`Invalid SDK package.`, errors_1.ERR_INVALID_SDK_PACKAGE);
|
||||
}
|
||||
const path = localPackage.get('path');
|
||||
if (!path) {
|
||||
throw new errors_1.SDKException(`Invalid SDK package path.`, errors_1.ERR_INVALID_SDK_PACKAGE);
|
||||
}
|
||||
return path.toString();
|
||||
}
|
||||
exports.getPathFromPackageXml = getPathFromPackageXml;
|
||||
function getNameFromPackageXml(packageXml) {
|
||||
const name = packageXml.find('./localPackage/display-name');
|
||||
if (!(name === null || name === void 0 ? void 0 : name.text)) {
|
||||
throw new errors_1.SDKException(`Invalid SDK package name.`, errors_1.ERR_INVALID_SDK_PACKAGE);
|
||||
}
|
||||
return name.text.toString();
|
||||
}
|
||||
exports.getNameFromPackageXml = getNameFromPackageXml;
|
||||
function getVersionFromPackageXml(packageXml) {
|
||||
const versionElements = [
|
||||
packageXml.find('./localPackage/revision/major'),
|
||||
packageXml.find('./localPackage/revision/minor'),
|
||||
packageXml.find('./localPackage/revision/micro'),
|
||||
];
|
||||
const textFromElement = (e) => ((e === null || e === void 0 ? void 0 : e.text) ? e.text.toString() : '');
|
||||
const versions = [];
|
||||
for (const version of versionElements.map(textFromElement)) {
|
||||
if (!version) {
|
||||
break;
|
||||
}
|
||||
versions.push(version);
|
||||
}
|
||||
if (versions.length === 0) {
|
||||
throw new errors_1.SDKException(`Invalid SDK package version.`, errors_1.ERR_INVALID_SDK_PACKAGE);
|
||||
}
|
||||
return versions.join('.');
|
||||
}
|
||||
exports.getVersionFromPackageXml = getVersionFromPackageXml;
|
||||
5
node_modules/native-run/dist/constants.js
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ASSETS_PATH = void 0;
|
||||
const pathlib = require("path");
|
||||
exports.ASSETS_PATH = pathlib.resolve(__dirname, '..', 'assets');
|
||||
81
node_modules/native-run/dist/errors.js
generated
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.serializeError = exports.IOSRunException = exports.SDKException = exports.AndroidRunException = exports.EmulatorException = exports.AVDException = exports.ADBException = exports.CLIException = exports.ERR_UNKNOWN_AVD = exports.ERR_DEVICE_LOCKED = exports.ERR_NO_TARGET = exports.ERR_NO_DEVICE = exports.ERR_TARGET_NOT_FOUND = exports.ERR_SDK_PACKAGE_NOT_FOUND = exports.ERR_SDK_NOT_FOUND = exports.ERR_UNSUITABLE_API_INSTALLATION = exports.ERR_NON_ZERO_EXIT = exports.ERR_INVALID_SDK_PACKAGE = exports.ERR_DEVICE_OFFLINE = exports.ERR_NOT_ENOUGH_SPACE = exports.ERR_NO_CERTIFICATES = exports.ERR_MIN_SDK_VERSION = exports.ERR_VERSION_DOWNGRADE = exports.ERR_INCOMPATIBLE_UPDATE = exports.ERR_EMULATOR_HOME_NOT_FOUND = exports.ERR_AVD_HOME_NOT_FOUND = exports.ERR_ALREADY_RUNNING = exports.ERR_BAD_INPUT = exports.AndroidException = exports.Exception = void 0;
|
||||
const json_1 = require("./utils/json");
|
||||
class Exception extends Error {
|
||||
constructor(message, code, exitCode = 1 /* ExitCode.GENERAL */, data) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
this.code = code;
|
||||
this.exitCode = exitCode;
|
||||
this.data = data;
|
||||
}
|
||||
serialize() {
|
||||
return `${this.code ? this.code : 'ERR_UNKNOWN'}: ${this.message}`;
|
||||
}
|
||||
toJSON() {
|
||||
return {
|
||||
error: this.message,
|
||||
code: this.code,
|
||||
...this.data,
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.Exception = Exception;
|
||||
class AndroidException extends Exception {
|
||||
serialize() {
|
||||
return (`${super.serialize()}\n\n` +
|
||||
`\tMore details for this error may be available online:\n\n` +
|
||||
`\thttps://github.com/ionic-team/native-run/wiki/Android-Errors`);
|
||||
}
|
||||
}
|
||||
exports.AndroidException = AndroidException;
|
||||
exports.ERR_BAD_INPUT = 'ERR_BAD_INPUT';
|
||||
exports.ERR_ALREADY_RUNNING = 'ERR_ALREADY_RUNNING ';
|
||||
exports.ERR_AVD_HOME_NOT_FOUND = 'ERR_AVD_HOME_NOT_FOUND';
|
||||
exports.ERR_EMULATOR_HOME_NOT_FOUND = 'ERR_EMULATOR_HOME_NOT_FOUND';
|
||||
exports.ERR_INCOMPATIBLE_UPDATE = 'ERR_INCOMPATIBLE_UPDATE';
|
||||
exports.ERR_VERSION_DOWNGRADE = 'ERR_VERSION_DOWNGRADE';
|
||||
exports.ERR_MIN_SDK_VERSION = 'ERR_MIN_SDK_VERSION';
|
||||
exports.ERR_NO_CERTIFICATES = 'ERR_NO_CERTIFICATES';
|
||||
exports.ERR_NOT_ENOUGH_SPACE = 'ERR_NOT_ENOUGH_SPACE';
|
||||
exports.ERR_DEVICE_OFFLINE = 'ERR_DEVICE_OFFLINE';
|
||||
exports.ERR_INVALID_SDK_PACKAGE = 'ERR_INVALID_SDK_PACKAGE';
|
||||
exports.ERR_NON_ZERO_EXIT = 'ERR_NON_ZERO_EXIT';
|
||||
exports.ERR_UNSUITABLE_API_INSTALLATION = 'ERR_UNSUITABLE_API_INSTALLATION';
|
||||
exports.ERR_SDK_NOT_FOUND = 'ERR_SDK_NOT_FOUND';
|
||||
exports.ERR_SDK_PACKAGE_NOT_FOUND = 'ERR_SDK_PACKAGE_NOT_FOUND';
|
||||
exports.ERR_TARGET_NOT_FOUND = 'ERR_TARGET_NOT_FOUND';
|
||||
exports.ERR_NO_DEVICE = 'ERR_NO_DEVICE';
|
||||
exports.ERR_NO_TARGET = 'ERR_NO_TARGET';
|
||||
exports.ERR_DEVICE_LOCKED = 'ERR_DEVICE_LOCKED';
|
||||
exports.ERR_UNKNOWN_AVD = 'ERR_UNKNOWN_AVD';
|
||||
class CLIException extends Exception {
|
||||
}
|
||||
exports.CLIException = CLIException;
|
||||
class ADBException extends AndroidException {
|
||||
}
|
||||
exports.ADBException = ADBException;
|
||||
class AVDException extends AndroidException {
|
||||
}
|
||||
exports.AVDException = AVDException;
|
||||
class EmulatorException extends AndroidException {
|
||||
}
|
||||
exports.EmulatorException = EmulatorException;
|
||||
class AndroidRunException extends AndroidException {
|
||||
}
|
||||
exports.AndroidRunException = AndroidRunException;
|
||||
class SDKException extends AndroidException {
|
||||
}
|
||||
exports.SDKException = SDKException;
|
||||
class IOSRunException extends Exception {
|
||||
}
|
||||
exports.IOSRunException = IOSRunException;
|
||||
function serializeError(e = new Error()) {
|
||||
const stack = String(e.stack ? e.stack : e);
|
||||
if (process.argv.includes('--json')) {
|
||||
return (0, json_1.stringify)(e instanceof Exception ? e : { error: stack });
|
||||
}
|
||||
return (e instanceof Exception ? e.serialize() : stack) + '\n';
|
||||
}
|
||||
exports.serializeError = serializeError;
|
||||
18
node_modules/native-run/dist/help.js
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.run = void 0;
|
||||
const help = `
|
||||
Usage: native-run [ios|android] [options]
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help ........... Print help for the platform, then quit
|
||||
--version ............ Print version, then quit
|
||||
--verbose ............ Print verbose output to stderr
|
||||
--list ............... Print connected devices and virtual devices
|
||||
|
||||
`;
|
||||
async function run(args) {
|
||||
process.stdout.write(help);
|
||||
}
|
||||
exports.run = run;
|
||||
49
node_modules/native-run/dist/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
"use strict";
|
||||
var _a;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.run = void 0;
|
||||
const Debug = require("debug");
|
||||
const path = require("path");
|
||||
const errors_1 = require("./errors");
|
||||
const debug = Debug('native-run');
|
||||
async function run() {
|
||||
const args = process.argv.slice(2);
|
||||
if (args.includes('--version')) {
|
||||
const pkg = await (_a = path.resolve(__dirname, '../package.json'), Promise.resolve().then(() => require(_a)));
|
||||
process.stdout.write(pkg.version + '\n');
|
||||
return;
|
||||
}
|
||||
let cmd;
|
||||
const [platform, ...platformArgs] = args;
|
||||
try {
|
||||
if (platform === 'android') {
|
||||
cmd = await Promise.resolve().then(() => require('./android'));
|
||||
await cmd.run(platformArgs);
|
||||
}
|
||||
else if (platform === 'ios') {
|
||||
cmd = await Promise.resolve().then(() => require('./ios'));
|
||||
await cmd.run(platformArgs);
|
||||
}
|
||||
else if (platform === '--list') {
|
||||
cmd = await Promise.resolve().then(() => require('./list'));
|
||||
await cmd.run(args);
|
||||
}
|
||||
else {
|
||||
if (!platform ||
|
||||
platform === 'help' ||
|
||||
args.includes('--help') ||
|
||||
args.includes('-h') ||
|
||||
platform.startsWith('-')) {
|
||||
cmd = await Promise.resolve().then(() => require('./help'));
|
||||
return cmd.run(args);
|
||||
}
|
||||
throw new errors_1.CLIException(`Unsupported platform: "${platform}"`, errors_1.ERR_BAD_INPUT);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
debug('Caught fatal error: %O', e);
|
||||
process.exitCode = e instanceof errors_1.Exception ? e.exitCode : 1 /* ExitCode.GENERAL */;
|
||||
process.stdout.write((0, errors_1.serializeError)(e));
|
||||
}
|
||||
}
|
||||
exports.run = run;
|
||||
35
node_modules/native-run/dist/ios/help.js
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.run = void 0;
|
||||
const help = `
|
||||
Usage: native-run ios [options]
|
||||
|
||||
Run an .app or .ipa on a device or simulator target
|
||||
|
||||
Targets are selected as follows:
|
||||
1) --target using device/simulator UUID
|
||||
2) A connected device, unless --virtual is used
|
||||
3) A running simulator
|
||||
|
||||
If the above criteria are not met, the app is run on the default simulator
|
||||
(the last simulator in the list).
|
||||
|
||||
Use --list to list available targets.
|
||||
|
||||
Options:
|
||||
|
||||
--list ............... Print available targets, then quit
|
||||
--json ............... Output JSON
|
||||
|
||||
--app <path> ......... Deploy specified .app or .ipa file
|
||||
--device ............. Use a device if available
|
||||
With --list prints connected devices
|
||||
--virtual ............ Prefer a simulator
|
||||
With --list prints available simulators
|
||||
--target <id> ........ Use a specific target
|
||||
--connect ............ Tie process to app process
|
||||
`;
|
||||
async function run() {
|
||||
process.stdout.write(`${help}\n`);
|
||||
}
|
||||
exports.run = run;
|
||||
17
node_modules/native-run/dist/ios/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.run = void 0;
|
||||
async function run(args) {
|
||||
let cmd;
|
||||
if (args.includes('--help') || args.includes('-h')) {
|
||||
cmd = await Promise.resolve().then(() => require('./help'));
|
||||
return cmd.run(args);
|
||||
}
|
||||
if (args.includes('--list') || args.includes('-l')) {
|
||||
cmd = await Promise.resolve().then(() => require('./list'));
|
||||
return cmd.run(args);
|
||||
}
|
||||
cmd = await Promise.resolve().then(() => require('./run'));
|
||||
await cmd.run(args);
|
||||
}
|
||||
exports.run = run;
|
||||
164
node_modules/native-run/dist/ios/lib/client/afc.js
generated
vendored
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AFCClient = void 0;
|
||||
const Debug = require("debug");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const util_1 = require("util");
|
||||
const afc_1 = require("../protocol/afc");
|
||||
const client_1 = require("./client");
|
||||
const debug = Debug('native-run:ios:lib:client:afc');
|
||||
const MAX_OPEN_FILES = 240;
|
||||
class AFCClient extends client_1.ServiceClient {
|
||||
constructor(socket) {
|
||||
super(socket, new afc_1.AFCProtocolClient(socket));
|
||||
this.socket = socket;
|
||||
}
|
||||
async getFileInfo(path) {
|
||||
debug(`getFileInfo: ${path}`);
|
||||
const resp = await this.protocolClient.sendMessage({
|
||||
operation: afc_1.AFC_OPS.GET_FILE_INFO,
|
||||
data: toCString(path),
|
||||
});
|
||||
const strings = [];
|
||||
let currentString = '';
|
||||
const tokens = resp.data;
|
||||
tokens.forEach((token) => {
|
||||
if (token === 0) {
|
||||
strings.push(currentString);
|
||||
currentString = '';
|
||||
}
|
||||
else {
|
||||
currentString += String.fromCharCode(token);
|
||||
}
|
||||
});
|
||||
return strings;
|
||||
}
|
||||
async writeFile(fd, data) {
|
||||
debug(`writeFile: ${Array.prototype.toString.call(fd)}`);
|
||||
return this.protocolClient.sendMessage({
|
||||
operation: afc_1.AFC_OPS.FILE_WRITE,
|
||||
data: fd,
|
||||
payload: data,
|
||||
});
|
||||
}
|
||||
async openFile(path) {
|
||||
debug(`openFile: ${path}`);
|
||||
// mode + path + null terminator
|
||||
const data = Buffer.alloc(8 + Buffer.byteLength(path) + 1);
|
||||
// write mode
|
||||
data.writeUInt32LE(afc_1.AFC_FILE_OPEN_FLAGS.WRONLY, 0);
|
||||
// then path to file
|
||||
toCString(path).copy(data, 8);
|
||||
const resp = await this.protocolClient.sendMessage({
|
||||
operation: afc_1.AFC_OPS.FILE_OPEN,
|
||||
data,
|
||||
});
|
||||
if (resp.operation === afc_1.AFC_OPS.FILE_OPEN_RES) {
|
||||
return resp.data;
|
||||
}
|
||||
throw new Error(`There was an unknown error opening file ${path}, response: ${Array.prototype.toString.call(resp.data)}`);
|
||||
}
|
||||
async closeFile(fd) {
|
||||
debug(`closeFile fd: ${Array.prototype.toString.call(fd)}`);
|
||||
return this.protocolClient.sendMessage({
|
||||
operation: afc_1.AFC_OPS.FILE_CLOSE,
|
||||
data: fd,
|
||||
});
|
||||
}
|
||||
async uploadFile(srcPath, destPath) {
|
||||
debug(`uploadFile: ${srcPath}`);
|
||||
// read local file and get fd of destination
|
||||
const [srcFile, destFile] = await Promise.all([
|
||||
await (0, util_1.promisify)(fs.readFile)(srcPath),
|
||||
await this.openFile(destPath),
|
||||
]);
|
||||
try {
|
||||
await this.writeFile(destFile, srcFile);
|
||||
await this.closeFile(destFile);
|
||||
}
|
||||
catch (err) {
|
||||
await this.closeFile(destFile);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
async makeDirectory(path) {
|
||||
debug(`makeDirectory: ${path}`);
|
||||
return this.protocolClient.sendMessage({
|
||||
operation: afc_1.AFC_OPS.MAKE_DIR,
|
||||
data: toCString(path),
|
||||
});
|
||||
}
|
||||
async uploadDirectory(srcPath, destPath) {
|
||||
debug(`uploadDirectory: ${srcPath}`);
|
||||
await this.makeDirectory(destPath);
|
||||
// AFC doesn't seem to give out more than 240 file handles,
|
||||
// so we delay any requests that would push us over until more open up
|
||||
let numOpenFiles = 0;
|
||||
const pendingFileUploads = [];
|
||||
const _this = this;
|
||||
return uploadDir(srcPath);
|
||||
async function uploadDir(dirPath) {
|
||||
const promises = [];
|
||||
for (const file of fs.readdirSync(dirPath)) {
|
||||
const filePath = path.join(dirPath, file);
|
||||
const remotePath = path.join(destPath, path.relative(srcPath, filePath));
|
||||
if (fs.lstatSync(filePath).isDirectory()) {
|
||||
promises.push(_this.makeDirectory(remotePath).then(() => uploadDir(filePath)));
|
||||
}
|
||||
else {
|
||||
// Create promise to add to promises array
|
||||
// this way it can be resolved once a pending upload has finished
|
||||
let resolve;
|
||||
let reject;
|
||||
const promise = new Promise((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
promises.push(promise);
|
||||
// wrap upload in a function in case we need to save it for later
|
||||
const uploadFile = (tries = 0) => {
|
||||
numOpenFiles++;
|
||||
_this
|
||||
.uploadFile(filePath, remotePath)
|
||||
.then(() => {
|
||||
resolve();
|
||||
numOpenFiles--;
|
||||
const fn = pendingFileUploads.pop();
|
||||
if (fn) {
|
||||
fn();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
// Couldn't get fd for whatever reason, try again
|
||||
// # of retries is arbitrary and can be adjusted
|
||||
if (err.status === afc_1.AFC_STATUS.NO_RESOURCES && tries < 10) {
|
||||
debug(`Received NO_RESOURCES from AFC, retrying ${filePath} upload. ${tries}`);
|
||||
uploadFile(tries++);
|
||||
}
|
||||
else {
|
||||
numOpenFiles--;
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
if (numOpenFiles < MAX_OPEN_FILES) {
|
||||
uploadFile();
|
||||
}
|
||||
else {
|
||||
debug(`numOpenFiles >= ${MAX_OPEN_FILES}, adding to pending queue. Length: ${pendingFileUploads.length}`);
|
||||
pendingFileUploads.push(uploadFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.AFCClient = AFCClient;
|
||||
function toCString(s) {
|
||||
const buf = Buffer.alloc(Buffer.byteLength(s) + 1);
|
||||
const len = buf.write(s);
|
||||
buf.writeUInt8(0, len);
|
||||
return buf;
|
||||
}
|
||||
17
node_modules/native-run/dist/ios/lib/client/client.js
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ResponseError = exports.ServiceClient = void 0;
|
||||
class ServiceClient {
|
||||
constructor(socket, protocolClient) {
|
||||
this.socket = socket;
|
||||
this.protocolClient = protocolClient;
|
||||
}
|
||||
}
|
||||
exports.ServiceClient = ServiceClient;
|
||||
class ResponseError extends Error {
|
||||
constructor(msg, response) {
|
||||
super(msg);
|
||||
this.response = response;
|
||||
}
|
||||
}
|
||||
exports.ResponseError = ResponseError;
|
||||
66
node_modules/native-run/dist/ios/lib/client/debugserver.js
generated
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DebugserverClient = void 0;
|
||||
const Debug = require("debug");
|
||||
const path = require("path");
|
||||
const gdb_1 = require("../protocol/gdb");
|
||||
const client_1 = require("./client");
|
||||
const debug = Debug('native-run:ios:lib:client:debugserver');
|
||||
class DebugserverClient extends client_1.ServiceClient {
|
||||
constructor(socket) {
|
||||
super(socket, new gdb_1.GDBProtocolClient(socket));
|
||||
this.socket = socket;
|
||||
}
|
||||
async setMaxPacketSize(size) {
|
||||
return this.sendCommand('QSetMaxPacketSize:', [size.toString()]);
|
||||
}
|
||||
async setWorkingDir(workingDir) {
|
||||
return this.sendCommand('QSetWorkingDir:', [workingDir]);
|
||||
}
|
||||
async checkLaunchSuccess() {
|
||||
return this.sendCommand('qLaunchSuccess', []);
|
||||
}
|
||||
async attachByName(name) {
|
||||
const hexName = Buffer.from(name).toString('hex');
|
||||
return this.sendCommand(`vAttachName;${hexName}`, []);
|
||||
}
|
||||
async continue() {
|
||||
return this.sendCommand('c', []);
|
||||
}
|
||||
halt() {
|
||||
// ^C
|
||||
debug('Sending ^C to debugserver');
|
||||
return this.protocolClient.socket.write('\u0003');
|
||||
}
|
||||
async kill() {
|
||||
const msg = { cmd: 'k', args: [] };
|
||||
return this.protocolClient.sendMessage(msg, (resp, resolve, reject) => {
|
||||
this.protocolClient.socket.write('+');
|
||||
const parts = resp.split(';');
|
||||
for (const part of parts) {
|
||||
if (part.includes('description')) {
|
||||
// description:{hex encoded message like: "Terminated with signal 9"}
|
||||
resolve(Buffer.from(part.split(':')[1], 'hex').toString('ascii'));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// TODO support app args
|
||||
// https://sourceware.org/gdb/onlinedocs/gdb/Packets.html#Packets
|
||||
// A arglen,argnum,arg,
|
||||
async launchApp(appPath, executableName) {
|
||||
const fullPath = path.join(appPath, executableName);
|
||||
const hexAppPath = Buffer.from(fullPath).toString('hex');
|
||||
const appCommand = `A${hexAppPath.length},0,${hexAppPath}`;
|
||||
return this.sendCommand(appCommand, []);
|
||||
}
|
||||
async sendCommand(cmd, args) {
|
||||
const msg = { cmd, args };
|
||||
debug(`Sending command: ${cmd}, args: ${args}`);
|
||||
const resp = await this.protocolClient.sendMessage(msg);
|
||||
// we need to ACK as well
|
||||
this.protocolClient.socket.write('+');
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
exports.DebugserverClient = DebugserverClient;
|
||||
10
node_modules/native-run/dist/ios/lib/client/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const tslib_1 = require("tslib");
|
||||
tslib_1.__exportStar(require("./client"), exports);
|
||||
tslib_1.__exportStar(require("./afc"), exports);
|
||||
tslib_1.__exportStar(require("./debugserver"), exports);
|
||||
tslib_1.__exportStar(require("./installation_proxy"), exports);
|
||||
tslib_1.__exportStar(require("./lockdownd"), exports);
|
||||
tslib_1.__exportStar(require("./mobile_image_mounter"), exports);
|
||||
tslib_1.__exportStar(require("./usbmuxd"), exports);
|
||||
72
node_modules/native-run/dist/ios/lib/client/installation_proxy.js
generated
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.InstallationProxyClient = void 0;
|
||||
const Debug = require("debug");
|
||||
const lockdown_1 = require("../protocol/lockdown");
|
||||
const client_1 = require("./client");
|
||||
const debug = Debug('native-run:ios:lib:client:installation_proxy');
|
||||
function isIPLookupResponse(resp) {
|
||||
return resp.length && resp[0].LookupResult !== undefined;
|
||||
}
|
||||
function isIPInstallPercentCompleteResponse(resp) {
|
||||
return resp.length && resp[0].PercentComplete !== undefined;
|
||||
}
|
||||
function isIPInstallCFBundleIdentifierResponse(resp) {
|
||||
return resp.length && resp[0].CFBundleIdentifier !== undefined;
|
||||
}
|
||||
function isIPInstallCompleteResponse(resp) {
|
||||
return resp.length && resp[0].Status === 'Complete';
|
||||
}
|
||||
class InstallationProxyClient extends client_1.ServiceClient {
|
||||
constructor(socket) {
|
||||
super(socket, new lockdown_1.LockdownProtocolClient(socket));
|
||||
this.socket = socket;
|
||||
}
|
||||
async lookupApp(bundleIds, options = {
|
||||
ReturnAttributes: ['Path', 'Container', 'CFBundleExecutable', 'CFBundleIdentifier'],
|
||||
ApplicationsType: 'Any',
|
||||
}) {
|
||||
debug(`lookupApp, options: ${JSON.stringify(options)}`);
|
||||
const resp = await this.protocolClient.sendMessage({
|
||||
Command: 'Lookup',
|
||||
ClientOptions: {
|
||||
BundleIDs: bundleIds,
|
||||
...options,
|
||||
},
|
||||
});
|
||||
if (isIPLookupResponse(resp)) {
|
||||
return resp[0].LookupResult;
|
||||
}
|
||||
else {
|
||||
throw new client_1.ResponseError(`There was an error looking up app`, resp);
|
||||
}
|
||||
}
|
||||
async installApp(packagePath, bundleId, options = {
|
||||
ApplicationsType: 'Any',
|
||||
PackageType: 'Developer',
|
||||
}) {
|
||||
debug(`installApp, packagePath: ${packagePath}, bundleId: ${bundleId}`);
|
||||
return this.protocolClient.sendMessage({
|
||||
Command: 'Install',
|
||||
PackagePath: packagePath,
|
||||
ClientOptions: {
|
||||
CFBundleIdentifier: bundleId,
|
||||
...options,
|
||||
},
|
||||
}, (resp, resolve, reject) => {
|
||||
if (isIPInstallCompleteResponse(resp)) {
|
||||
resolve();
|
||||
}
|
||||
else if (isIPInstallPercentCompleteResponse(resp)) {
|
||||
debug(`Installation status: ${resp[0].Status}, %${resp[0].PercentComplete}`);
|
||||
}
|
||||
else if (isIPInstallCFBundleIdentifierResponse(resp)) {
|
||||
debug(`Installed app: ${resp[0].CFBundleIdentifier}`);
|
||||
}
|
||||
else {
|
||||
reject(new client_1.ResponseError('There was an error installing app', resp));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.InstallationProxyClient = InstallationProxyClient;
|
||||
111
node_modules/native-run/dist/ios/lib/client/lockdownd.js
generated
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.LockdowndClient = void 0;
|
||||
const Debug = require("debug");
|
||||
const tls = require("tls");
|
||||
const lockdown_1 = require("../protocol/lockdown");
|
||||
const client_1 = require("./client");
|
||||
const debug = Debug('native-run:ios:lib:client:lockdownd');
|
||||
function isLockdowndServiceResponse(resp) {
|
||||
return resp.Request === 'StartService' && resp.Service !== undefined && resp.Port !== undefined;
|
||||
}
|
||||
function isLockdowndSessionResponse(resp) {
|
||||
return resp.Request === 'StartSession';
|
||||
}
|
||||
function isLockdowndAllValuesResponse(resp) {
|
||||
return resp.Request === 'GetValue' && resp.Value !== undefined;
|
||||
}
|
||||
function isLockdowndValueResponse(resp) {
|
||||
return resp.Request === 'GetValue' && resp.Key !== undefined && typeof resp.Value === 'string';
|
||||
}
|
||||
function isLockdowndQueryTypeResponse(resp) {
|
||||
return resp.Request === 'QueryType' && resp.Type !== undefined;
|
||||
}
|
||||
class LockdowndClient extends client_1.ServiceClient {
|
||||
constructor(socket) {
|
||||
super(socket, new lockdown_1.LockdownProtocolClient(socket));
|
||||
this.socket = socket;
|
||||
}
|
||||
async startService(name) {
|
||||
debug(`startService: ${name}`);
|
||||
const resp = await this.protocolClient.sendMessage({
|
||||
Request: 'StartService',
|
||||
Service: name,
|
||||
});
|
||||
if (isLockdowndServiceResponse(resp)) {
|
||||
return { port: resp.Port, enableServiceSSL: !!resp.EnableServiceSSL };
|
||||
}
|
||||
else {
|
||||
throw new client_1.ResponseError(`Error starting service ${name}`, resp);
|
||||
}
|
||||
}
|
||||
async startSession(pairRecord) {
|
||||
debug(`startSession: ${pairRecord}`);
|
||||
const resp = await this.protocolClient.sendMessage({
|
||||
Request: 'StartSession',
|
||||
HostID: pairRecord.HostID,
|
||||
SystemBUID: pairRecord.SystemBUID,
|
||||
});
|
||||
if (isLockdowndSessionResponse(resp)) {
|
||||
if (resp.EnableSessionSSL) {
|
||||
this.protocolClient.socket = new tls.TLSSocket(this.protocolClient.socket, {
|
||||
secureContext: tls.createSecureContext({
|
||||
secureProtocol: 'TLSv1_2_method',
|
||||
cert: pairRecord.RootCertificate,
|
||||
key: pairRecord.RootPrivateKey,
|
||||
}),
|
||||
});
|
||||
debug(`Socket upgraded to TLS connection`);
|
||||
}
|
||||
// TODO: save sessionID for StopSession?
|
||||
}
|
||||
else {
|
||||
throw new client_1.ResponseError('Error starting session', resp);
|
||||
}
|
||||
}
|
||||
async getAllValues() {
|
||||
debug(`getAllValues`);
|
||||
const resp = await this.protocolClient.sendMessage({ Request: 'GetValue' });
|
||||
if (isLockdowndAllValuesResponse(resp)) {
|
||||
return resp.Value;
|
||||
}
|
||||
else {
|
||||
throw new client_1.ResponseError('Error getting lockdown value', resp);
|
||||
}
|
||||
}
|
||||
async getValue(val) {
|
||||
debug(`getValue: ${val}`);
|
||||
const resp = await this.protocolClient.sendMessage({
|
||||
Request: 'GetValue',
|
||||
Key: val,
|
||||
});
|
||||
if (isLockdowndValueResponse(resp)) {
|
||||
return resp.Value;
|
||||
}
|
||||
else {
|
||||
throw new client_1.ResponseError('Error getting lockdown value', resp);
|
||||
}
|
||||
}
|
||||
async queryType() {
|
||||
debug('queryType');
|
||||
const resp = await this.protocolClient.sendMessage({
|
||||
Request: 'QueryType',
|
||||
});
|
||||
if (isLockdowndQueryTypeResponse(resp)) {
|
||||
return resp.Type;
|
||||
}
|
||||
else {
|
||||
throw new client_1.ResponseError('Error getting lockdown query type', resp);
|
||||
}
|
||||
}
|
||||
async doHandshake(pairRecord) {
|
||||
debug('doHandshake');
|
||||
// if (await this.lockdownQueryType() !== 'com.apple.mobile.lockdown') {
|
||||
// throw new Error('Invalid type received from lockdown handshake');
|
||||
// }
|
||||
// await this.getLockdownValue('ProductVersion');
|
||||
// TODO: validate pair and pair
|
||||
await this.startSession(pairRecord);
|
||||
}
|
||||
}
|
||||
exports.LockdowndClient = LockdowndClient;
|
||||
61
node_modules/native-run/dist/ios/lib/client/mobile_image_mounter.js
generated
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.MobileImageMounterClient = void 0;
|
||||
const Debug = require("debug");
|
||||
const fs = require("fs");
|
||||
const lockdown_1 = require("../protocol/lockdown");
|
||||
const client_1 = require("./client");
|
||||
const debug = Debug('native-run:ios:lib:client:mobile_image_mounter');
|
||||
function isMIMUploadCompleteResponse(resp) {
|
||||
return resp.Status === 'Complete';
|
||||
}
|
||||
function isMIMUploadReceiveBytesResponse(resp) {
|
||||
return resp.Status === 'ReceiveBytesAck';
|
||||
}
|
||||
class MobileImageMounterClient extends client_1.ServiceClient {
|
||||
constructor(socket) {
|
||||
super(socket, new lockdown_1.LockdownProtocolClient(socket));
|
||||
}
|
||||
async mountImage(imagePath, imageSig) {
|
||||
debug(`mountImage: ${imagePath}`);
|
||||
const resp = await this.protocolClient.sendMessage({
|
||||
Command: 'MountImage',
|
||||
ImagePath: imagePath,
|
||||
ImageSignature: imageSig,
|
||||
ImageType: 'Developer',
|
||||
});
|
||||
if (!(0, lockdown_1.isLockdownResponse)(resp) || resp.Status !== 'Complete') {
|
||||
throw new client_1.ResponseError(`There was an error mounting ${imagePath} on device`, resp);
|
||||
}
|
||||
}
|
||||
async uploadImage(imagePath, imageSig) {
|
||||
debug(`uploadImage: ${imagePath}`);
|
||||
const imageSize = fs.statSync(imagePath).size;
|
||||
return this.protocolClient.sendMessage({
|
||||
Command: 'ReceiveBytes',
|
||||
ImageSize: imageSize,
|
||||
ImageSignature: imageSig,
|
||||
ImageType: 'Developer',
|
||||
}, (resp, resolve, reject) => {
|
||||
if (isMIMUploadReceiveBytesResponse(resp)) {
|
||||
const imageStream = fs.createReadStream(imagePath);
|
||||
imageStream.pipe(this.protocolClient.socket, { end: false });
|
||||
imageStream.on('error', (err) => reject(err));
|
||||
}
|
||||
else if (isMIMUploadCompleteResponse(resp)) {
|
||||
resolve();
|
||||
}
|
||||
else {
|
||||
reject(new client_1.ResponseError(`There was an error uploading image ${imagePath} to the device`, resp));
|
||||
}
|
||||
});
|
||||
}
|
||||
async lookupImage() {
|
||||
debug('lookupImage');
|
||||
return this.protocolClient.sendMessage({
|
||||
Command: 'LookupImage',
|
||||
ImageType: 'Developer',
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.MobileImageMounterClient = MobileImageMounterClient;
|
||||
103
node_modules/native-run/dist/ios/lib/client/usbmuxd.js
generated
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.UsbmuxdClient = void 0;
|
||||
const Debug = require("debug");
|
||||
const net = require("net");
|
||||
const plist = require("plist");
|
||||
const usbmux_1 = require("../protocol/usbmux");
|
||||
const client_1 = require("./client");
|
||||
const debug = Debug('native-run:ios:lib:client:usbmuxd');
|
||||
function isUsbmuxdConnectResponse(resp) {
|
||||
return resp.MessageType === 'Result' && resp.Number !== undefined;
|
||||
}
|
||||
function isUsbmuxdDeviceResponse(resp) {
|
||||
return resp.DeviceList !== undefined;
|
||||
}
|
||||
function isUsbmuxdPairRecordResponse(resp) {
|
||||
return resp.PairRecordData !== undefined;
|
||||
}
|
||||
class UsbmuxdClient extends client_1.ServiceClient {
|
||||
constructor(socket) {
|
||||
super(socket, new usbmux_1.UsbmuxProtocolClient(socket));
|
||||
this.socket = socket;
|
||||
}
|
||||
static connectUsbmuxdSocket() {
|
||||
debug('connectUsbmuxdSocket');
|
||||
if ('win32' === process.platform) {
|
||||
return net.connect({ port: 27015, host: 'localhost' });
|
||||
}
|
||||
else {
|
||||
return net.connect({ path: '/var/run/usbmuxd' });
|
||||
}
|
||||
}
|
||||
async connect(device, port) {
|
||||
debug(`connect: ${device.DeviceID} on port ${port}`);
|
||||
const resp = await this.protocolClient.sendMessage({
|
||||
messageType: 'Connect',
|
||||
extraFields: {
|
||||
DeviceID: device.DeviceID,
|
||||
PortNumber: htons(port),
|
||||
},
|
||||
});
|
||||
if (isUsbmuxdConnectResponse(resp) && resp.Number === 0) {
|
||||
return this.protocolClient.socket;
|
||||
}
|
||||
else {
|
||||
throw new client_1.ResponseError(`There was an error connecting to ${device.DeviceID} on port ${port}`, resp);
|
||||
}
|
||||
}
|
||||
async getDevices() {
|
||||
debug('getDevices');
|
||||
const resp = await this.protocolClient.sendMessage({
|
||||
messageType: 'ListDevices',
|
||||
});
|
||||
if (isUsbmuxdDeviceResponse(resp)) {
|
||||
return resp.DeviceList;
|
||||
}
|
||||
else {
|
||||
throw new client_1.ResponseError('Invalid response from getDevices', resp);
|
||||
}
|
||||
}
|
||||
async getDevice(udid) {
|
||||
debug(`getDevice ${udid ? 'udid: ' + udid : ''}`);
|
||||
const devices = await this.getDevices();
|
||||
if (!devices.length) {
|
||||
throw new Error('No devices found');
|
||||
}
|
||||
if (!udid) {
|
||||
return devices[0];
|
||||
}
|
||||
for (const device of devices) {
|
||||
if (device.Properties && device.Properties.SerialNumber === udid) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
throw new Error(`No device with udid ${udid} found`);
|
||||
}
|
||||
async readPairRecord(udid) {
|
||||
debug(`readPairRecord: ${udid}`);
|
||||
const resp = await this.protocolClient.sendMessage({
|
||||
messageType: 'ReadPairRecord',
|
||||
extraFields: { PairRecordID: udid },
|
||||
});
|
||||
if (isUsbmuxdPairRecordResponse(resp)) {
|
||||
// the pair record can be created as a binary plist
|
||||
const BPLIST_MAGIC = Buffer.from('bplist00');
|
||||
if (BPLIST_MAGIC.compare(resp.PairRecordData, 0, 8) === 0) {
|
||||
debug('Binary plist pair record detected.');
|
||||
const bplistParser = await Promise.resolve().then(() => require('bplist-parser'));
|
||||
return bplistParser.parseBuffer(resp.PairRecordData)[0];
|
||||
}
|
||||
else {
|
||||
return plist.parse(resp.PairRecordData.toString()); // TODO: type guard
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new client_1.ResponseError(`There was an error reading pair record for udid: ${udid}`, resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.UsbmuxdClient = UsbmuxdClient;
|
||||
function htons(n) {
|
||||
return ((n & 0xff) << 8) | ((n >> 8) & 0xff);
|
||||
}
|
||||
6
node_modules/native-run/dist/ios/lib/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const tslib_1 = require("tslib");
|
||||
tslib_1.__exportStar(require("./client"), exports);
|
||||
tslib_1.__exportStar(require("./protocol"), exports);
|
||||
tslib_1.__exportStar(require("./manager"), exports);
|
||||
10
node_modules/native-run/dist/ios/lib/lib-errors.js
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.IOSLibError = void 0;
|
||||
class IOSLibError extends Error {
|
||||
constructor(message, code) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
exports.IOSLibError = IOSLibError;
|
||||
139
node_modules/native-run/dist/ios/lib/manager.js
generated
vendored
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ClientManager = void 0;
|
||||
const stream_1 = require("stream");
|
||||
const tls = require("tls");
|
||||
const afc_1 = require("./client/afc");
|
||||
const debugserver_1 = require("./client/debugserver");
|
||||
const installation_proxy_1 = require("./client/installation_proxy");
|
||||
const lockdownd_1 = require("./client/lockdownd");
|
||||
const mobile_image_mounter_1 = require("./client/mobile_image_mounter");
|
||||
const usbmuxd_1 = require("./client/usbmuxd");
|
||||
class ClientManager {
|
||||
constructor(pairRecord, device, lockdowndClient) {
|
||||
this.pairRecord = pairRecord;
|
||||
this.device = device;
|
||||
this.lockdowndClient = lockdowndClient;
|
||||
this.connections = [lockdowndClient.socket];
|
||||
}
|
||||
static async create(udid) {
|
||||
const usbmuxClient = new usbmuxd_1.UsbmuxdClient(usbmuxd_1.UsbmuxdClient.connectUsbmuxdSocket());
|
||||
const device = await usbmuxClient.getDevice(udid);
|
||||
const pairRecord = await usbmuxClient.readPairRecord(device.Properties.SerialNumber);
|
||||
const lockdownSocket = await usbmuxClient.connect(device, 62078);
|
||||
const lockdownClient = new lockdownd_1.LockdowndClient(lockdownSocket);
|
||||
await lockdownClient.doHandshake(pairRecord);
|
||||
return new ClientManager(pairRecord, device, lockdownClient);
|
||||
}
|
||||
async getUsbmuxdClient() {
|
||||
const usbmuxClient = new usbmuxd_1.UsbmuxdClient(usbmuxd_1.UsbmuxdClient.connectUsbmuxdSocket());
|
||||
this.connections.push(usbmuxClient.socket);
|
||||
return usbmuxClient;
|
||||
}
|
||||
async getLockdowndClient() {
|
||||
const usbmuxClient = new usbmuxd_1.UsbmuxdClient(usbmuxd_1.UsbmuxdClient.connectUsbmuxdSocket());
|
||||
const lockdownSocket = await usbmuxClient.connect(this.device, 62078);
|
||||
const lockdownClient = new lockdownd_1.LockdowndClient(lockdownSocket);
|
||||
this.connections.push(lockdownClient.socket);
|
||||
return lockdownClient;
|
||||
}
|
||||
async getLockdowndClientWithHandshake() {
|
||||
const lockdownClient = await this.getLockdowndClient();
|
||||
await lockdownClient.doHandshake(this.pairRecord);
|
||||
return lockdownClient;
|
||||
}
|
||||
async getAFCClient() {
|
||||
return this.getServiceClient('com.apple.afc', afc_1.AFCClient);
|
||||
}
|
||||
async getInstallationProxyClient() {
|
||||
return this.getServiceClient('com.apple.mobile.installation_proxy', installation_proxy_1.InstallationProxyClient);
|
||||
}
|
||||
async getMobileImageMounterClient() {
|
||||
return this.getServiceClient('com.apple.mobile.mobile_image_mounter', mobile_image_mounter_1.MobileImageMounterClient);
|
||||
}
|
||||
async getDebugserverClient() {
|
||||
try {
|
||||
// iOS 14 added support for a secure debug service so try to connect to that first
|
||||
return await this.getServiceClient('com.apple.debugserver.DVTSecureSocketProxy', debugserver_1.DebugserverClient);
|
||||
}
|
||||
catch {
|
||||
// otherwise, fall back to the previous implementation
|
||||
return this.getServiceClient('com.apple.debugserver', debugserver_1.DebugserverClient, true);
|
||||
}
|
||||
}
|
||||
async getServiceClient(name, ServiceType, disableSSL = false) {
|
||||
const { port: servicePort, enableServiceSSL } = await this.lockdowndClient.startService(name);
|
||||
const usbmuxClient = new usbmuxd_1.UsbmuxdClient(usbmuxd_1.UsbmuxdClient.connectUsbmuxdSocket());
|
||||
let usbmuxdSocket = await usbmuxClient.connect(this.device, servicePort);
|
||||
if (enableServiceSSL) {
|
||||
const tlsOptions = {
|
||||
rejectUnauthorized: false,
|
||||
secureContext: tls.createSecureContext({
|
||||
secureProtocol: 'TLSv1_2_method',
|
||||
cert: this.pairRecord.RootCertificate,
|
||||
key: this.pairRecord.RootPrivateKey,
|
||||
}),
|
||||
};
|
||||
// Some services seem to not support TLS/SSL after the initial handshake
|
||||
// More info: https://github.com/libimobiledevice/libimobiledevice/issues/793
|
||||
if (disableSSL) {
|
||||
// According to https://nodejs.org/api/tls.html#tls_tls_connect_options_callback we can
|
||||
// pass any Duplex in to tls.connect instead of a Socket. So we'll use our proxy to keep
|
||||
// the TLS wrapper and underlying usbmuxd socket separate.
|
||||
const proxy = new UsbmuxdProxy(usbmuxdSocket);
|
||||
tlsOptions.socket = proxy;
|
||||
await new Promise((res, rej) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
rej('The TLS handshake failed to complete after 5s.');
|
||||
}, 5000);
|
||||
tls.connect(tlsOptions, function () {
|
||||
clearTimeout(timeoutId);
|
||||
// After the handshake, we don't need TLS or the proxy anymore,
|
||||
// since we'll just pass in the naked usbmuxd socket to the service client
|
||||
this.destroy();
|
||||
res();
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
tlsOptions.socket = usbmuxdSocket;
|
||||
usbmuxdSocket = tls.connect(tlsOptions);
|
||||
}
|
||||
}
|
||||
const client = new ServiceType(usbmuxdSocket);
|
||||
this.connections.push(client.socket);
|
||||
return client;
|
||||
}
|
||||
end() {
|
||||
for (const socket of this.connections) {
|
||||
// may already be closed
|
||||
try {
|
||||
socket.end();
|
||||
}
|
||||
catch (err) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.ClientManager = ClientManager;
|
||||
class UsbmuxdProxy extends stream_1.Duplex {
|
||||
constructor(usbmuxdSock) {
|
||||
super();
|
||||
this.usbmuxdSock = usbmuxdSock;
|
||||
this.usbmuxdSock.on('data', (data) => {
|
||||
this.push(data);
|
||||
});
|
||||
}
|
||||
_write(chunk, encoding, callback) {
|
||||
this.usbmuxdSock.write(chunk);
|
||||
callback();
|
||||
}
|
||||
_read(size) {
|
||||
// Stub so we don't error, since we push everything we get from usbmuxd as it comes in.
|
||||
// TODO: better way to do this?
|
||||
}
|
||||
_destroy() {
|
||||
this.usbmuxdSock.removeAllListeners();
|
||||
}
|
||||
}
|
||||
373
node_modules/native-run/dist/ios/lib/protocol/afc.js
generated
vendored
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AFCProtocolWriter = exports.AFCProtocolReader = exports.AFCProtocolClient = exports.AFCError = exports.AFC_FILE_OPEN_FLAGS = exports.AFC_STATUS = exports.AFC_OPS = exports.AFC_HEADER_SIZE = exports.AFC_MAGIC = void 0;
|
||||
const Debug = require("debug");
|
||||
const protocol_1 = require("./protocol");
|
||||
const debug = Debug('native-run:ios:lib:protocol:afc');
|
||||
exports.AFC_MAGIC = 'CFA6LPAA';
|
||||
exports.AFC_HEADER_SIZE = 40;
|
||||
/**
|
||||
* AFC Operations
|
||||
*/
|
||||
var AFC_OPS;
|
||||
(function (AFC_OPS) {
|
||||
/**
|
||||
* Invalid
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["INVALID"] = 0] = "INVALID";
|
||||
/**
|
||||
* Status
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["STATUS"] = 1] = "STATUS";
|
||||
/**
|
||||
* Data
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["DATA"] = 2] = "DATA";
|
||||
/**
|
||||
* ReadDir
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["READ_DIR"] = 3] = "READ_DIR";
|
||||
/**
|
||||
* ReadFile
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["READ_FILE"] = 4] = "READ_FILE";
|
||||
/**
|
||||
* WriteFile
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["WRITE_FILE"] = 5] = "WRITE_FILE";
|
||||
/**
|
||||
* WritePart
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["WRITE_PART"] = 6] = "WRITE_PART";
|
||||
/**
|
||||
* TruncateFile
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["TRUNCATE"] = 7] = "TRUNCATE";
|
||||
/**
|
||||
* RemovePath
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["REMOVE_PATH"] = 8] = "REMOVE_PATH";
|
||||
/**
|
||||
* MakeDir
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["MAKE_DIR"] = 9] = "MAKE_DIR";
|
||||
/**
|
||||
* GetFileInfo
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["GET_FILE_INFO"] = 10] = "GET_FILE_INFO";
|
||||
/**
|
||||
* GetDeviceInfo
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["GET_DEVINFO"] = 11] = "GET_DEVINFO";
|
||||
/**
|
||||
* WriteFileAtomic (tmp file+rename)
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["WRITE_FILE_ATOM"] = 12] = "WRITE_FILE_ATOM";
|
||||
/**
|
||||
* FileRefOpen
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["FILE_OPEN"] = 13] = "FILE_OPEN";
|
||||
/**
|
||||
* FileRefOpenResult
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["FILE_OPEN_RES"] = 14] = "FILE_OPEN_RES";
|
||||
/**
|
||||
* FileRefRead
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["FILE_READ"] = 15] = "FILE_READ";
|
||||
/**
|
||||
* FileRefWrite
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["FILE_WRITE"] = 16] = "FILE_WRITE";
|
||||
/**
|
||||
* FileRefSeek
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["FILE_SEEK"] = 17] = "FILE_SEEK";
|
||||
/**
|
||||
* FileRefTell
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["FILE_TELL"] = 18] = "FILE_TELL";
|
||||
/**
|
||||
* FileRefTellResult
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["FILE_TELL_RES"] = 19] = "FILE_TELL_RES";
|
||||
/**
|
||||
* FileRefClose
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["FILE_CLOSE"] = 20] = "FILE_CLOSE";
|
||||
/**
|
||||
* FileRefSetFileSize (ftruncate)
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["FILE_SET_SIZE"] = 21] = "FILE_SET_SIZE";
|
||||
/**
|
||||
* GetConnectionInfo
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["GET_CON_INFO"] = 22] = "GET_CON_INFO";
|
||||
/**
|
||||
* SetConnectionOptions
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["SET_CON_OPTIONS"] = 23] = "SET_CON_OPTIONS";
|
||||
/**
|
||||
* RenamePath
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["RENAME_PATH"] = 24] = "RENAME_PATH";
|
||||
/**
|
||||
* SetFSBlockSize (0x800000)
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["SET_FS_BS"] = 25] = "SET_FS_BS";
|
||||
/**
|
||||
* SetSocketBlockSize (0x800000)
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["SET_SOCKET_BS"] = 26] = "SET_SOCKET_BS";
|
||||
/**
|
||||
* FileRefLock
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["FILE_LOCK"] = 27] = "FILE_LOCK";
|
||||
/**
|
||||
* MakeLink
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["MAKE_LINK"] = 28] = "MAKE_LINK";
|
||||
/**
|
||||
* GetFileHash
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["GET_FILE_HASH"] = 29] = "GET_FILE_HASH";
|
||||
/**
|
||||
* SetModTime
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["SET_FILE_MOD_TIME"] = 30] = "SET_FILE_MOD_TIME";
|
||||
/**
|
||||
* GetFileHashWithRange
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["GET_FILE_HASH_RANGE"] = 31] = "GET_FILE_HASH_RANGE";
|
||||
// iOS 6+
|
||||
/**
|
||||
* FileRefSetImmutableHint
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["FILE_SET_IMMUTABLE_HINT"] = 32] = "FILE_SET_IMMUTABLE_HINT";
|
||||
/**
|
||||
* GetSizeOfPathContents
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["GET_SIZE_OF_PATH_CONTENTS"] = 33] = "GET_SIZE_OF_PATH_CONTENTS";
|
||||
/**
|
||||
* RemovePathAndContents
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["REMOVE_PATH_AND_CONTENTS"] = 34] = "REMOVE_PATH_AND_CONTENTS";
|
||||
/**
|
||||
* DirectoryEnumeratorRefOpen
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["DIR_OPEN"] = 35] = "DIR_OPEN";
|
||||
/**
|
||||
* DirectoryEnumeratorRefOpenResult
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["DIR_OPEN_RESULT"] = 36] = "DIR_OPEN_RESULT";
|
||||
/**
|
||||
* DirectoryEnumeratorRefRead
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["DIR_READ"] = 37] = "DIR_READ";
|
||||
/**
|
||||
* DirectoryEnumeratorRefClose
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["DIR_CLOSE"] = 38] = "DIR_CLOSE";
|
||||
// iOS 7+
|
||||
/**
|
||||
* FileRefReadWithOffset
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["FILE_READ_OFFSET"] = 39] = "FILE_READ_OFFSET";
|
||||
/**
|
||||
* FileRefWriteWithOffset
|
||||
*/
|
||||
AFC_OPS[AFC_OPS["FILE_WRITE_OFFSET"] = 40] = "FILE_WRITE_OFFSET";
|
||||
})(AFC_OPS = exports.AFC_OPS || (exports.AFC_OPS = {}));
|
||||
/**
|
||||
* Error Codes
|
||||
*/
|
||||
var AFC_STATUS;
|
||||
(function (AFC_STATUS) {
|
||||
AFC_STATUS[AFC_STATUS["SUCCESS"] = 0] = "SUCCESS";
|
||||
AFC_STATUS[AFC_STATUS["UNKNOWN_ERROR"] = 1] = "UNKNOWN_ERROR";
|
||||
AFC_STATUS[AFC_STATUS["OP_HEADER_INVALID"] = 2] = "OP_HEADER_INVALID";
|
||||
AFC_STATUS[AFC_STATUS["NO_RESOURCES"] = 3] = "NO_RESOURCES";
|
||||
AFC_STATUS[AFC_STATUS["READ_ERROR"] = 4] = "READ_ERROR";
|
||||
AFC_STATUS[AFC_STATUS["WRITE_ERROR"] = 5] = "WRITE_ERROR";
|
||||
AFC_STATUS[AFC_STATUS["UNKNOWN_PACKET_TYPE"] = 6] = "UNKNOWN_PACKET_TYPE";
|
||||
AFC_STATUS[AFC_STATUS["INVALID_ARG"] = 7] = "INVALID_ARG";
|
||||
AFC_STATUS[AFC_STATUS["OBJECT_NOT_FOUND"] = 8] = "OBJECT_NOT_FOUND";
|
||||
AFC_STATUS[AFC_STATUS["OBJECT_IS_DIR"] = 9] = "OBJECT_IS_DIR";
|
||||
AFC_STATUS[AFC_STATUS["PERM_DENIED"] = 10] = "PERM_DENIED";
|
||||
AFC_STATUS[AFC_STATUS["SERVICE_NOT_CONNECTED"] = 11] = "SERVICE_NOT_CONNECTED";
|
||||
AFC_STATUS[AFC_STATUS["OP_TIMEOUT"] = 12] = "OP_TIMEOUT";
|
||||
AFC_STATUS[AFC_STATUS["TOO_MUCH_DATA"] = 13] = "TOO_MUCH_DATA";
|
||||
AFC_STATUS[AFC_STATUS["END_OF_DATA"] = 14] = "END_OF_DATA";
|
||||
AFC_STATUS[AFC_STATUS["OP_NOT_SUPPORTED"] = 15] = "OP_NOT_SUPPORTED";
|
||||
AFC_STATUS[AFC_STATUS["OBJECT_EXISTS"] = 16] = "OBJECT_EXISTS";
|
||||
AFC_STATUS[AFC_STATUS["OBJECT_BUSY"] = 17] = "OBJECT_BUSY";
|
||||
AFC_STATUS[AFC_STATUS["NO_SPACE_LEFT"] = 18] = "NO_SPACE_LEFT";
|
||||
AFC_STATUS[AFC_STATUS["OP_WOULD_BLOCK"] = 19] = "OP_WOULD_BLOCK";
|
||||
AFC_STATUS[AFC_STATUS["IO_ERROR"] = 20] = "IO_ERROR";
|
||||
AFC_STATUS[AFC_STATUS["OP_INTERRUPTED"] = 21] = "OP_INTERRUPTED";
|
||||
AFC_STATUS[AFC_STATUS["OP_IN_PROGRESS"] = 22] = "OP_IN_PROGRESS";
|
||||
AFC_STATUS[AFC_STATUS["INTERNAL_ERROR"] = 23] = "INTERNAL_ERROR";
|
||||
AFC_STATUS[AFC_STATUS["MUX_ERROR"] = 30] = "MUX_ERROR";
|
||||
AFC_STATUS[AFC_STATUS["NO_MEM"] = 31] = "NO_MEM";
|
||||
AFC_STATUS[AFC_STATUS["NOT_ENOUGH_DATA"] = 32] = "NOT_ENOUGH_DATA";
|
||||
AFC_STATUS[AFC_STATUS["DIR_NOT_EMPTY"] = 33] = "DIR_NOT_EMPTY";
|
||||
AFC_STATUS[AFC_STATUS["FORCE_SIGNED_TYPE"] = -1] = "FORCE_SIGNED_TYPE";
|
||||
})(AFC_STATUS = exports.AFC_STATUS || (exports.AFC_STATUS = {}));
|
||||
var AFC_FILE_OPEN_FLAGS;
|
||||
(function (AFC_FILE_OPEN_FLAGS) {
|
||||
/**
|
||||
* r (O_RDONLY)
|
||||
*/
|
||||
AFC_FILE_OPEN_FLAGS[AFC_FILE_OPEN_FLAGS["RDONLY"] = 1] = "RDONLY";
|
||||
/**
|
||||
* r+ (O_RDWR | O_CREAT)
|
||||
*/
|
||||
AFC_FILE_OPEN_FLAGS[AFC_FILE_OPEN_FLAGS["RW"] = 2] = "RW";
|
||||
/**
|
||||
* w (O_WRONLY | O_CREAT | O_TRUNC)
|
||||
*/
|
||||
AFC_FILE_OPEN_FLAGS[AFC_FILE_OPEN_FLAGS["WRONLY"] = 3] = "WRONLY";
|
||||
/**
|
||||
* w+ (O_RDWR | O_CREAT | O_TRUNC)
|
||||
*/
|
||||
AFC_FILE_OPEN_FLAGS[AFC_FILE_OPEN_FLAGS["WR"] = 4] = "WR";
|
||||
/**
|
||||
* a (O_WRONLY | O_APPEND | O_CREAT)
|
||||
*/
|
||||
AFC_FILE_OPEN_FLAGS[AFC_FILE_OPEN_FLAGS["APPEND"] = 5] = "APPEND";
|
||||
/**
|
||||
* a+ (O_RDWR | O_APPEND | O_CREAT)
|
||||
*/
|
||||
AFC_FILE_OPEN_FLAGS[AFC_FILE_OPEN_FLAGS["RDAPPEND"] = 6] = "RDAPPEND";
|
||||
})(AFC_FILE_OPEN_FLAGS = exports.AFC_FILE_OPEN_FLAGS || (exports.AFC_FILE_OPEN_FLAGS = {}));
|
||||
function isAFCResponse(resp) {
|
||||
return AFC_OPS[resp.operation] !== undefined && resp.id !== undefined && resp.data !== undefined;
|
||||
}
|
||||
function isStatusResponse(resp) {
|
||||
return isAFCResponse(resp) && resp.operation === AFC_OPS.STATUS;
|
||||
}
|
||||
function isErrorStatusResponse(resp) {
|
||||
return isStatusResponse(resp) && resp.data !== AFC_STATUS.SUCCESS;
|
||||
}
|
||||
class AFCInternalError extends Error {
|
||||
constructor(msg, requestId) {
|
||||
super(msg);
|
||||
this.requestId = requestId;
|
||||
}
|
||||
}
|
||||
class AFCError extends Error {
|
||||
constructor(msg, status) {
|
||||
super(msg);
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
exports.AFCError = AFCError;
|
||||
class AFCProtocolClient extends protocol_1.ProtocolClient {
|
||||
constructor(socket) {
|
||||
super(socket, new protocol_1.ProtocolReaderFactory(AFCProtocolReader), new AFCProtocolWriter());
|
||||
this.requestId = 0;
|
||||
this.requestCallbacks = {};
|
||||
const reader = this.readerFactory.create((resp, err) => {
|
||||
if (err && err instanceof AFCInternalError) {
|
||||
this.requestCallbacks[err.requestId](resp, err);
|
||||
}
|
||||
else if (isErrorStatusResponse(resp)) {
|
||||
this.requestCallbacks[resp.id](resp, new AFCError(AFC_STATUS[resp.data], resp.data));
|
||||
}
|
||||
else {
|
||||
this.requestCallbacks[resp.id](resp);
|
||||
}
|
||||
});
|
||||
socket.on('data', reader.onData);
|
||||
}
|
||||
sendMessage(msg) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestId = this.requestId++;
|
||||
this.requestCallbacks[requestId] = async (resp, err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (isAFCResponse(resp)) {
|
||||
resolve(resp);
|
||||
}
|
||||
else {
|
||||
reject(new Error('Malformed AFC response'));
|
||||
}
|
||||
};
|
||||
this.writer.write(this.socket, { ...msg, requestId });
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.AFCProtocolClient = AFCProtocolClient;
|
||||
class AFCProtocolReader extends protocol_1.ProtocolReader {
|
||||
constructor(callback) {
|
||||
super(exports.AFC_HEADER_SIZE, callback);
|
||||
}
|
||||
parseHeader(data) {
|
||||
const magic = data.slice(0, 8).toString('ascii');
|
||||
if (magic !== exports.AFC_MAGIC) {
|
||||
throw new AFCInternalError(`Invalid AFC packet received (magic != ${exports.AFC_MAGIC})`, data.readUInt32LE(24));
|
||||
}
|
||||
// technically these are uint64
|
||||
this.header = {
|
||||
magic,
|
||||
totalLength: data.readUInt32LE(8),
|
||||
headerLength: data.readUInt32LE(16),
|
||||
requestId: data.readUInt32LE(24),
|
||||
operation: data.readUInt32LE(32),
|
||||
};
|
||||
debug(`parse header: ${JSON.stringify(this.header)}`);
|
||||
if (this.header.headerLength < exports.AFC_HEADER_SIZE) {
|
||||
throw new AFCInternalError('Invalid AFC header', this.header.requestId);
|
||||
}
|
||||
return this.header.totalLength - exports.AFC_HEADER_SIZE;
|
||||
}
|
||||
parseBody(data) {
|
||||
const body = {
|
||||
operation: this.header.operation,
|
||||
id: this.header.requestId,
|
||||
data,
|
||||
};
|
||||
if (isStatusResponse(body)) {
|
||||
const status = data.readUInt32LE(0);
|
||||
debug(`${AFC_OPS[this.header.operation]} response: ${AFC_STATUS[status]}`);
|
||||
body.data = status;
|
||||
}
|
||||
else if (data.length <= 8) {
|
||||
debug(`${AFC_OPS[this.header.operation]} response: ${Array.prototype.toString.call(body)}`);
|
||||
}
|
||||
else {
|
||||
debug(`${AFC_OPS[this.header.operation]} response length: ${data.length} bytes`);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
}
|
||||
exports.AFCProtocolReader = AFCProtocolReader;
|
||||
class AFCProtocolWriter {
|
||||
write(socket, msg) {
|
||||
const { data, payload, operation, requestId } = msg;
|
||||
const dataLength = data ? data.length : 0;
|
||||
const payloadLength = payload ? payload.length : 0;
|
||||
const header = Buffer.alloc(exports.AFC_HEADER_SIZE);
|
||||
const magic = Buffer.from(exports.AFC_MAGIC);
|
||||
magic.copy(header);
|
||||
header.writeUInt32LE(exports.AFC_HEADER_SIZE + dataLength + payloadLength, 8);
|
||||
header.writeUInt32LE(exports.AFC_HEADER_SIZE + dataLength, 16);
|
||||
header.writeUInt32LE(requestId, 24);
|
||||
header.writeUInt32LE(operation, 32);
|
||||
socket.write(header);
|
||||
socket.write(data);
|
||||
if (data.length <= 8) {
|
||||
debug(`socket write, header: { requestId: ${requestId}, operation: ${AFC_OPS[operation]}}, body: ${Array.prototype.toString.call(data)}`);
|
||||
}
|
||||
else {
|
||||
debug(`socket write, header: { requestId: ${requestId}, operation: ${AFC_OPS[operation]}}, body: ${data.length} bytes`);
|
||||
}
|
||||
debug(`socket write, bytes written ${header.length} (header), ${data.length} (body)`);
|
||||
if (payload) {
|
||||
socket.write(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.AFCProtocolWriter = AFCProtocolWriter;
|
||||
112
node_modules/native-run/dist/ios/lib/protocol/gdb.js
generated
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.GDBProtocolWriter = exports.GDBProtocolReader = exports.GDBProtocolClient = void 0;
|
||||
const Debug = require("debug");
|
||||
const protocol_1 = require("./protocol");
|
||||
const debug = Debug('native-run:ios:lib:protocol:gdb');
|
||||
const ACK_SUCCESS = '+'.charCodeAt(0);
|
||||
class GDBProtocolClient extends protocol_1.ProtocolClient {
|
||||
constructor(socket) {
|
||||
super(socket, new protocol_1.ProtocolReaderFactory(GDBProtocolReader), new GDBProtocolWriter());
|
||||
}
|
||||
}
|
||||
exports.GDBProtocolClient = GDBProtocolClient;
|
||||
class GDBProtocolReader extends protocol_1.ProtocolReader {
|
||||
constructor(callback) {
|
||||
super(1 /* "Header" is '+' or '-' */, callback);
|
||||
}
|
||||
onData(data) {
|
||||
// the GDB protocol does not support body length in its header so we cannot rely on
|
||||
// the parent implementation to determine when a payload is complete
|
||||
try {
|
||||
// if there's data, add it to the existing buffer
|
||||
this.buffer = data ? Buffer.concat([this.buffer, data]) : this.buffer;
|
||||
// do we have enough bytes to proceed
|
||||
if (this.buffer.length < this.headerSize) {
|
||||
return; // incomplete header, wait for more
|
||||
}
|
||||
// first, check the header
|
||||
if (this.parseHeader(this.buffer) === -1) {
|
||||
// we have a valid header so check the body. GDB packets will always be a leading '$', data bytes,
|
||||
// a trailing '#', and a two digit checksum. minimum valid body is the empty response '$#00'
|
||||
// https://developer.apple.com/library/archive/documentation/DeveloperTools/gdb/gdb/gdb_33.html
|
||||
const packetData = this.buffer.toString().match('\\$.*#[0-9a-f]{2}');
|
||||
if (packetData == null) {
|
||||
return; // incomplete body, wait for more
|
||||
}
|
||||
// extract the body and update the buffer
|
||||
const body = Buffer.from(packetData[0]);
|
||||
this.buffer = this.buffer.slice(this.headerSize + body.length);
|
||||
// parse the payload and recurse if there is more data to process
|
||||
this.callback(this.parseBody(body));
|
||||
if (this.buffer.length) {
|
||||
this.onData();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
this.callback(null, err);
|
||||
}
|
||||
}
|
||||
parseHeader(data) {
|
||||
if (data[0] !== ACK_SUCCESS) {
|
||||
throw new Error('Unsuccessful debugserver response');
|
||||
} // TODO: retry?
|
||||
return -1;
|
||||
}
|
||||
parseBody(buffer) {
|
||||
debug(`Response body: ${buffer.toString()}`);
|
||||
// check for checksum
|
||||
const checksum = buffer.slice(-3).toString();
|
||||
if (checksum.match(/#[0-9a-f]{2}/)) {
|
||||
// remove '$' prefix and checksum
|
||||
const msg = buffer.slice(1, -3).toString();
|
||||
if (validateChecksum(checksum, msg)) {
|
||||
return msg;
|
||||
}
|
||||
else {
|
||||
throw new Error('Invalid checksum received from debugserver');
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error("Didn't receive checksum");
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.GDBProtocolReader = GDBProtocolReader;
|
||||
class GDBProtocolWriter {
|
||||
write(socket, msg) {
|
||||
const { cmd, args } = msg;
|
||||
debug(`Socket write: ${cmd}, args: ${args}`);
|
||||
// hex encode and concat all args
|
||||
const encodedArgs = args
|
||||
.map((arg) => Buffer.from(arg).toString('hex'))
|
||||
.join()
|
||||
.toUpperCase();
|
||||
const checksumStr = calculateChecksum(cmd + encodedArgs);
|
||||
const formattedCmd = `$${cmd}${encodedArgs}#${checksumStr}`;
|
||||
socket.write(formattedCmd);
|
||||
}
|
||||
}
|
||||
exports.GDBProtocolWriter = GDBProtocolWriter;
|
||||
// hex value of (sum of cmd chars mod 256)
|
||||
function calculateChecksum(cmdStr) {
|
||||
let checksum = 0;
|
||||
for (let i = 0; i < cmdStr.length; i++) {
|
||||
checksum += cmdStr.charCodeAt(i);
|
||||
}
|
||||
let result = (checksum % 256).toString(16);
|
||||
// pad if necessary
|
||||
if (result.length === 1) {
|
||||
result = `0${result}`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function validateChecksum(checksum, msg) {
|
||||
// remove '#' from checksum
|
||||
const checksumVal = checksum.slice(1);
|
||||
// remove '$' from msg and calculate its checksum
|
||||
const computedChecksum = calculateChecksum(msg);
|
||||
debug(`Checksum: ${checksumVal}, computed checksum: ${computedChecksum}`);
|
||||
return checksumVal === computedChecksum;
|
||||
}
|
||||
8
node_modules/native-run/dist/ios/lib/protocol/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const tslib_1 = require("tslib");
|
||||
tslib_1.__exportStar(require("./protocol"), exports);
|
||||
tslib_1.__exportStar(require("./afc"), exports);
|
||||
tslib_1.__exportStar(require("./gdb"), exports);
|
||||
tslib_1.__exportStar(require("./lockdown"), exports);
|
||||
tslib_1.__exportStar(require("./usbmux"), exports);
|
||||
57
node_modules/native-run/dist/ios/lib/protocol/lockdown.js
generated
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.LockdownProtocolWriter = exports.LockdownProtocolReader = exports.LockdownProtocolClient = exports.isLockdownErrorResponse = exports.isLockdownResponse = exports.LOCKDOWN_HEADER_SIZE = void 0;
|
||||
const Debug = require("debug");
|
||||
const plist = require("plist");
|
||||
const lib_errors_1 = require("../lib-errors");
|
||||
const protocol_1 = require("./protocol");
|
||||
const debug = Debug('native-run:ios:lib:protocol:lockdown');
|
||||
exports.LOCKDOWN_HEADER_SIZE = 4;
|
||||
function isDefined(val) {
|
||||
return typeof val !== 'undefined';
|
||||
}
|
||||
function isLockdownResponse(resp) {
|
||||
return isDefined(resp.Status);
|
||||
}
|
||||
exports.isLockdownResponse = isLockdownResponse;
|
||||
function isLockdownErrorResponse(resp) {
|
||||
return isDefined(resp.Error);
|
||||
}
|
||||
exports.isLockdownErrorResponse = isLockdownErrorResponse;
|
||||
class LockdownProtocolClient extends protocol_1.ProtocolClient {
|
||||
constructor(socket) {
|
||||
super(socket, new protocol_1.ProtocolReaderFactory(LockdownProtocolReader), new LockdownProtocolWriter());
|
||||
}
|
||||
}
|
||||
exports.LockdownProtocolClient = LockdownProtocolClient;
|
||||
class LockdownProtocolReader extends protocol_1.PlistProtocolReader {
|
||||
constructor(callback) {
|
||||
super(exports.LOCKDOWN_HEADER_SIZE, callback);
|
||||
}
|
||||
parseHeader(data) {
|
||||
return data.readUInt32BE(0);
|
||||
}
|
||||
parseBody(data) {
|
||||
const resp = super.parseBody(data);
|
||||
debug(`Response: ${JSON.stringify(resp)}`);
|
||||
if (isLockdownErrorResponse(resp)) {
|
||||
if (resp.Error === 'DeviceLocked') {
|
||||
throw new lib_errors_1.IOSLibError('Device is currently locked.', 'DeviceLocked');
|
||||
}
|
||||
throw new Error(resp.Error);
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
exports.LockdownProtocolReader = LockdownProtocolReader;
|
||||
class LockdownProtocolWriter {
|
||||
write(socket, plistData) {
|
||||
debug(`socket write: ${JSON.stringify(plistData)}`);
|
||||
const plistMessage = plist.build(plistData);
|
||||
const header = Buffer.alloc(exports.LOCKDOWN_HEADER_SIZE);
|
||||
header.writeUInt32BE(plistMessage.length, 0);
|
||||
socket.write(header);
|
||||
socket.write(plistMessage);
|
||||
}
|
||||
}
|
||||
exports.LockdownProtocolWriter = LockdownProtocolWriter;
|
||||
111
node_modules/native-run/dist/ios/lib/protocol/protocol.js
generated
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ProtocolClient = exports.PlistProtocolReader = exports.ProtocolReader = exports.ProtocolReaderFactory = void 0;
|
||||
const bplistParser = require("bplist-parser");
|
||||
const plist = require("plist");
|
||||
const BPLIST_MAGIC = Buffer.from('bplist00');
|
||||
class ProtocolReaderFactory {
|
||||
constructor(ProtocolReader) {
|
||||
this.ProtocolReader = ProtocolReader;
|
||||
}
|
||||
create(callback) {
|
||||
return new this.ProtocolReader(callback);
|
||||
}
|
||||
}
|
||||
exports.ProtocolReaderFactory = ProtocolReaderFactory;
|
||||
class ProtocolReader {
|
||||
constructor(headerSize, callback) {
|
||||
this.headerSize = headerSize;
|
||||
this.callback = callback;
|
||||
this.buffer = Buffer.alloc(0);
|
||||
this.onData = this.onData.bind(this);
|
||||
}
|
||||
onData(data) {
|
||||
try {
|
||||
// if there's data, add it on to existing buffer
|
||||
this.buffer = data ? Buffer.concat([this.buffer, data]) : this.buffer;
|
||||
// we haven't gotten the body length from the header yet
|
||||
if (!this.bodyLength) {
|
||||
if (this.buffer.length < this.headerSize) {
|
||||
// partial header, wait for rest
|
||||
return;
|
||||
}
|
||||
this.bodyLength = this.parseHeader(this.buffer);
|
||||
// move on to body
|
||||
this.buffer = this.buffer.slice(this.headerSize);
|
||||
if (!this.buffer.length) {
|
||||
// only got header, wait for body
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.buffer.length < this.bodyLength) {
|
||||
// wait for rest of body
|
||||
return;
|
||||
}
|
||||
if (this.bodyLength === -1) {
|
||||
this.callback(this.parseBody(this.buffer));
|
||||
this.buffer = Buffer.alloc(0);
|
||||
}
|
||||
else {
|
||||
this.body = this.buffer.slice(0, this.bodyLength);
|
||||
this.bodyLength -= this.body.length;
|
||||
if (!this.bodyLength) {
|
||||
this.callback(this.parseBody(this.body));
|
||||
}
|
||||
this.buffer = this.buffer.slice(this.body.length);
|
||||
// There are multiple messages here, call parse again
|
||||
if (this.buffer.length) {
|
||||
this.onData();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
this.callback(null, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.ProtocolReader = ProtocolReader;
|
||||
class PlistProtocolReader extends ProtocolReader {
|
||||
parseBody(body) {
|
||||
if (BPLIST_MAGIC.compare(body, 0, 8) === 0) {
|
||||
return bplistParser.parseBuffer(body);
|
||||
}
|
||||
else {
|
||||
return plist.parse(body.toString('utf8'));
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.PlistProtocolReader = PlistProtocolReader;
|
||||
class ProtocolClient {
|
||||
constructor(socket, readerFactory, writer) {
|
||||
this.socket = socket;
|
||||
this.readerFactory = readerFactory;
|
||||
this.writer = writer;
|
||||
}
|
||||
sendMessage(msg, callback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = this.readerFactory.create(async (resp, err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (callback) {
|
||||
callback(resp, (value) => {
|
||||
this.socket.removeListener('data', reader.onData);
|
||||
resolve(value);
|
||||
}, reject);
|
||||
}
|
||||
else {
|
||||
this.socket.removeListener('data', reader.onData);
|
||||
resolve(resp);
|
||||
}
|
||||
});
|
||||
this.socket.on('error', (err) => {
|
||||
throw err;
|
||||
});
|
||||
this.socket.on('data', reader.onData);
|
||||
this.writer.write(this.socket, msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.ProtocolClient = ProtocolClient;
|
||||
57
node_modules/native-run/dist/ios/lib/protocol/usbmux.js
generated
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.UsbmuxProtocolWriter = exports.UsbmuxProtocolReader = exports.UsbmuxProtocolClient = exports.USBMUXD_HEADER_SIZE = void 0;
|
||||
const Debug = require("debug");
|
||||
const plist = require("plist");
|
||||
const protocol_1 = require("./protocol");
|
||||
const debug = Debug('native-run:ios:lib:protocol:usbmux');
|
||||
exports.USBMUXD_HEADER_SIZE = 16;
|
||||
class UsbmuxProtocolClient extends protocol_1.ProtocolClient {
|
||||
constructor(socket) {
|
||||
super(socket, new protocol_1.ProtocolReaderFactory(UsbmuxProtocolReader), new UsbmuxProtocolWriter());
|
||||
}
|
||||
}
|
||||
exports.UsbmuxProtocolClient = UsbmuxProtocolClient;
|
||||
class UsbmuxProtocolReader extends protocol_1.PlistProtocolReader {
|
||||
constructor(callback) {
|
||||
super(exports.USBMUXD_HEADER_SIZE, callback);
|
||||
}
|
||||
parseHeader(data) {
|
||||
return data.readUInt32LE(0) - exports.USBMUXD_HEADER_SIZE;
|
||||
}
|
||||
parseBody(data) {
|
||||
const resp = super.parseBody(data);
|
||||
debug(`Response: ${JSON.stringify(resp)}`);
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
exports.UsbmuxProtocolReader = UsbmuxProtocolReader;
|
||||
class UsbmuxProtocolWriter {
|
||||
constructor() {
|
||||
this.useTag = 0;
|
||||
}
|
||||
write(socket, msg) {
|
||||
// TODO Usbmux message type
|
||||
debug(`socket write: ${JSON.stringify(msg)}`);
|
||||
const { messageType, extraFields } = msg;
|
||||
const plistMessage = plist.build({
|
||||
BundleID: 'io.ionic.native-run',
|
||||
ClientVersionString: 'usbmux.js',
|
||||
MessageType: messageType,
|
||||
ProgName: 'native-run',
|
||||
kLibUSBMuxVersion: 3,
|
||||
...extraFields,
|
||||
});
|
||||
const dataSize = plistMessage ? plistMessage.length : 0;
|
||||
const protocolVersion = 1;
|
||||
const messageCode = 8;
|
||||
const header = Buffer.alloc(exports.USBMUXD_HEADER_SIZE);
|
||||
header.writeUInt32LE(exports.USBMUXD_HEADER_SIZE + dataSize, 0);
|
||||
header.writeUInt32LE(protocolVersion, 4);
|
||||
header.writeUInt32LE(messageCode, 8);
|
||||
header.writeUInt32LE(this.useTag++, 12); // TODO
|
||||
socket.write(header);
|
||||
socket.write(plistMessage);
|
||||
}
|
||||
}
|
||||
exports.UsbmuxProtocolWriter = UsbmuxProtocolWriter;
|
||||
55
node_modules/native-run/dist/ios/list.js
generated
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.list = exports.run = void 0;
|
||||
const list_1 = require("../utils/list");
|
||||
const device_1 = require("./utils/device");
|
||||
const simulator_1 = require("./utils/simulator");
|
||||
async function run(args) {
|
||||
const targets = await list(args);
|
||||
process.stdout.write(`\n${(0, list_1.formatTargets)(args, targets)}\n`);
|
||||
}
|
||||
exports.run = run;
|
||||
async function list(args) {
|
||||
const errors = [];
|
||||
const [devices, virtualDevices] = await Promise.all([
|
||||
(async () => {
|
||||
try {
|
||||
const devices = await (0, device_1.getConnectedDevices)();
|
||||
return devices.map(deviceToTarget);
|
||||
}
|
||||
catch (e) {
|
||||
errors.push(e);
|
||||
return [];
|
||||
}
|
||||
})(),
|
||||
(async () => {
|
||||
try {
|
||||
const simulators = await (0, simulator_1.getSimulators)();
|
||||
return simulators.map(simulatorToTarget);
|
||||
}
|
||||
catch (e) {
|
||||
errors.push(e);
|
||||
return [];
|
||||
}
|
||||
})(),
|
||||
]);
|
||||
return { devices, virtualDevices, errors };
|
||||
}
|
||||
exports.list = list;
|
||||
function deviceToTarget(device) {
|
||||
return {
|
||||
platform: 'ios',
|
||||
name: device.DeviceName,
|
||||
model: device.ProductType,
|
||||
sdkVersion: device.ProductVersion,
|
||||
id: device.UniqueDeviceID,
|
||||
};
|
||||
}
|
||||
function simulatorToTarget(simulator) {
|
||||
return {
|
||||
platform: 'ios',
|
||||
name: simulator.name,
|
||||
sdkVersion: simulator.runtime.version,
|
||||
id: simulator.udid,
|
||||
};
|
||||
}
|
||||
116
node_modules/native-run/dist/ios/run.js
generated
vendored
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.run = void 0;
|
||||
const utils_fs_1 = require("@ionic/utils-fs");
|
||||
const Debug = require("debug");
|
||||
const fs_1 = require("fs");
|
||||
const path = require("path");
|
||||
const errors_1 = require("../errors");
|
||||
const cli_1 = require("../utils/cli");
|
||||
const process_1 = require("../utils/process");
|
||||
const lib_errors_1 = require("./lib/lib-errors");
|
||||
const app_1 = require("./utils/app");
|
||||
const device_1 = require("./utils/device");
|
||||
const simulator_1 = require("./utils/simulator");
|
||||
const debug = Debug('native-run:ios:run');
|
||||
async function runIpaOrAppFile({ udid, devices, simulators, appPath, bundleId, waitForApp, preferSimulator, }) {
|
||||
if (udid) {
|
||||
if (devices.find((d) => d.UniqueDeviceID === udid)) {
|
||||
await (0, device_1.runOnDevice)(udid, appPath, bundleId, waitForApp);
|
||||
}
|
||||
else if (simulators.find((s) => s.udid === udid)) {
|
||||
await (0, simulator_1.runOnSimulator)(udid, appPath, bundleId, waitForApp);
|
||||
}
|
||||
else {
|
||||
throw new errors_1.IOSRunException(`No device or simulator with UDID "${udid}" found`, errors_1.ERR_TARGET_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
else if (devices.length && !preferSimulator) {
|
||||
// no udid, use first connected device
|
||||
await (0, device_1.runOnDevice)(devices[0].UniqueDeviceID, appPath, bundleId, waitForApp);
|
||||
}
|
||||
else {
|
||||
// use default sim
|
||||
await (0, simulator_1.runOnSimulator)(simulators[simulators.length - 1].udid, appPath, bundleId, waitForApp);
|
||||
}
|
||||
}
|
||||
async function runIpaOrAppFileOnInterval(config) {
|
||||
const maxRetryCount = 12; // 1 minute
|
||||
const retryInterval = 5000; // 5 seconds
|
||||
let error;
|
||||
let retryCount = 0;
|
||||
const retry = async () => {
|
||||
process.stderr.write('Please unlock your device. Waiting 5 seconds...\n');
|
||||
retryCount++;
|
||||
await (0, process_1.wait)(retryInterval);
|
||||
await run();
|
||||
};
|
||||
const run = async () => {
|
||||
try {
|
||||
await runIpaOrAppFile(config);
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof lib_errors_1.IOSLibError && err.code == 'DeviceLocked' && retryCount < maxRetryCount) {
|
||||
await retry();
|
||||
}
|
||||
else {
|
||||
if (retryCount >= maxRetryCount) {
|
||||
error = new errors_1.IOSRunException(`Device still locked after 1 minute. Aborting.`, errors_1.ERR_DEVICE_LOCKED);
|
||||
}
|
||||
else {
|
||||
error = err;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
await run();
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async function run(args) {
|
||||
let appPath = (0, cli_1.getOptionValue)(args, '--app');
|
||||
if (!appPath) {
|
||||
throw new errors_1.CLIException('--app is required', errors_1.ERR_BAD_INPUT);
|
||||
}
|
||||
const udid = (0, cli_1.getOptionValue)(args, '--target');
|
||||
const preferSimulator = args.includes('--virtual');
|
||||
const waitForApp = args.includes('--connect');
|
||||
const isIPA = appPath.endsWith('.ipa');
|
||||
if (!(0, fs_1.existsSync)(appPath)) {
|
||||
throw new errors_1.IOSRunException(`Path '${appPath}' not found`);
|
||||
}
|
||||
try {
|
||||
if (isIPA) {
|
||||
const { tmpdir } = await Promise.resolve().then(() => require('os'));
|
||||
const tempDir = (0, fs_1.mkdtempSync)(`${tmpdir()}${path.sep}`);
|
||||
debug(`Unzipping .ipa to ${tempDir}`);
|
||||
const appDir = await (0, app_1.unzipIPA)(appPath, tempDir);
|
||||
appPath = path.join(tempDir, appDir);
|
||||
}
|
||||
const bundleId = await (0, app_1.getBundleId)(appPath);
|
||||
const [devices, simulators] = await Promise.all([(0, device_1.getConnectedDevices)(), (0, simulator_1.getSimulators)()]);
|
||||
// try to run on device or simulator with udid
|
||||
const config = {
|
||||
udid,
|
||||
devices,
|
||||
simulators,
|
||||
appPath,
|
||||
bundleId,
|
||||
waitForApp,
|
||||
preferSimulator,
|
||||
};
|
||||
await runIpaOrAppFileOnInterval(config);
|
||||
}
|
||||
finally {
|
||||
if (isIPA) {
|
||||
try {
|
||||
await (0, utils_fs_1.remove)(appPath);
|
||||
}
|
||||
catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.run = run;
|
||||
60
node_modules/native-run/dist/ios/utils/app.js
generated
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.unzipIPA = exports.getBundleId = void 0;
|
||||
const utils_fs_1 = require("@ionic/utils-fs");
|
||||
const Debug = require("debug");
|
||||
const fs_1 = require("fs");
|
||||
const path = require("path");
|
||||
const errors_1 = require("../../errors");
|
||||
const process_1 = require("../../utils/process");
|
||||
const unzip_1 = require("../../utils/unzip");
|
||||
const debug = Debug('native-run:ios:utils:app');
|
||||
// TODO: cross platform? Use plist/bplist
|
||||
async function getBundleId(appPath) {
|
||||
const plistPath = path.resolve(appPath, 'Info.plist');
|
||||
try {
|
||||
const { stdout } = await (0, process_1.execFile)('/usr/libexec/PlistBuddy', ['-c', 'Print :CFBundleIdentifier', plistPath], {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
if (stdout) {
|
||||
return stdout.trim();
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// ignore
|
||||
}
|
||||
throw new errors_1.Exception('Unable to get app bundle identifier');
|
||||
}
|
||||
exports.getBundleId = getBundleId;
|
||||
async function unzipIPA(ipaPath, destPath) {
|
||||
let error;
|
||||
let appPath = '';
|
||||
await (0, unzip_1.unzip)(ipaPath, async (entry, zipfile, openReadStream) => {
|
||||
debug(`Unzip: ${entry.fileName}`);
|
||||
const dest = path.join(destPath, entry.fileName);
|
||||
if (entry.fileName.endsWith('/')) {
|
||||
await (0, utils_fs_1.mkdirp)(dest);
|
||||
if (entry.fileName.endsWith('.app/')) {
|
||||
appPath = entry.fileName;
|
||||
}
|
||||
zipfile.readEntry();
|
||||
}
|
||||
else {
|
||||
await (0, utils_fs_1.mkdirp)(path.dirname(dest));
|
||||
const readStream = await openReadStream(entry);
|
||||
readStream.on('error', (err) => (error = err));
|
||||
readStream.on('end', () => {
|
||||
zipfile.readEntry();
|
||||
});
|
||||
readStream.pipe((0, fs_1.createWriteStream)(dest));
|
||||
}
|
||||
});
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
if (!appPath) {
|
||||
throw new errors_1.Exception('Unable to determine .app directory from .ipa');
|
||||
}
|
||||
return appPath;
|
||||
}
|
||||
exports.unzipIPA = unzipIPA;
|
||||
141
node_modules/native-run/dist/ios/utils/device.js
generated
vendored
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.runOnDevice = exports.getConnectedDevices = void 0;
|
||||
const child_process_1 = require("child_process");
|
||||
const Debug = require("debug");
|
||||
const fs_1 = require("fs");
|
||||
const path = require("path");
|
||||
const errors_1 = require("../../errors");
|
||||
const process_1 = require("../../utils/process");
|
||||
const lib_1 = require("../lib");
|
||||
const xcode_1 = require("./xcode");
|
||||
const debug = Debug('native-run:ios:utils:device');
|
||||
async function getConnectedDevices() {
|
||||
const usbmuxClient = new lib_1.UsbmuxdClient(lib_1.UsbmuxdClient.connectUsbmuxdSocket());
|
||||
const usbmuxDevices = await usbmuxClient.getDevices();
|
||||
usbmuxClient.socket.end();
|
||||
return Promise.all(usbmuxDevices.map(async (d) => {
|
||||
const socket = await new lib_1.UsbmuxdClient(lib_1.UsbmuxdClient.connectUsbmuxdSocket()).connect(d, 62078);
|
||||
const device = await new lib_1.LockdowndClient(socket).getAllValues();
|
||||
socket.end();
|
||||
// For network-connected devices, UniqueDeviceID may not be present in lockdownd response
|
||||
// Use SerialNumber from usbmuxd device info as fallback (they are the same value)
|
||||
if (!device.UniqueDeviceID && d.Properties && d.Properties.SerialNumber) {
|
||||
device.UniqueDeviceID = d.Properties.SerialNumber;
|
||||
debug(`Using SerialNumber as UniqueDeviceID for network device: ${device.UniqueDeviceID}`);
|
||||
}
|
||||
return device;
|
||||
}));
|
||||
}
|
||||
exports.getConnectedDevices = getConnectedDevices;
|
||||
async function runOnDevice(udid, appPath, bundleId, waitForApp) {
|
||||
const clientManager = await lib_1.ClientManager.create(udid);
|
||||
try {
|
||||
await mountDeveloperDiskImage(clientManager);
|
||||
const packageName = path.basename(appPath);
|
||||
const destPackagePath = path.join('PublicStaging', packageName);
|
||||
await uploadApp(clientManager, appPath, destPackagePath);
|
||||
const installer = await clientManager.getInstallationProxyClient();
|
||||
await installer.installApp(destPackagePath, bundleId);
|
||||
const { [bundleId]: appInfo } = await installer.lookupApp([bundleId]);
|
||||
// launch fails with EBusy or ENotFound if you try to launch immediately after install
|
||||
await (0, process_1.wait)(200);
|
||||
try {
|
||||
const debugServerClient = await launchApp(clientManager, appInfo);
|
||||
if (waitForApp) {
|
||||
(0, process_1.onBeforeExit)(async () => {
|
||||
// causes continue() to return
|
||||
debugServerClient.halt();
|
||||
// give continue() time to return response
|
||||
await (0, process_1.wait)(64);
|
||||
});
|
||||
debug(`Waiting for app to close...\n`);
|
||||
const result = await debugServerClient.continue();
|
||||
// TODO: I have no idea what this packet means yet (successful close?)
|
||||
// if not a close (ie, most likely due to halt from onBeforeExit), then kill the app
|
||||
if (result !== 'W00') {
|
||||
await debugServerClient.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// if launching app throws, try with devicectl, but requires Xcode 15
|
||||
const [xcodeVersion] = (0, xcode_1.getXcodeVersionInfo)();
|
||||
const xcodeMajorVersion = Number(xcodeVersion.split('.')[0]);
|
||||
if (xcodeMajorVersion >= 15) {
|
||||
const launchResult = (0, child_process_1.spawn)('xcrun', ['devicectl', 'device', 'process', 'launch', '--device', udid, bundleId]);
|
||||
return new Promise((resolve, reject) => {
|
||||
launchResult.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
}
|
||||
else {
|
||||
reject(new errors_1.Exception(`There was an error launching app on device`));
|
||||
}
|
||||
});
|
||||
launchResult.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw new errors_1.Exception(`running on iOS 17 devices requires Xcode 15 and later`);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
clientManager.end();
|
||||
}
|
||||
}
|
||||
exports.runOnDevice = runOnDevice;
|
||||
async function mountDeveloperDiskImage(clientManager) {
|
||||
const imageMounter = await clientManager.getMobileImageMounterClient();
|
||||
// Check if already mounted. If not, mount.
|
||||
if (!(await imageMounter.lookupImage()).ImageSignature) {
|
||||
// verify DeveloperDiskImage exists (TODO: how does this work on Windows/Linux?)
|
||||
// TODO: if windows/linux, download?
|
||||
const version = await (await clientManager.getLockdowndClient()).getValue('ProductVersion');
|
||||
const developerDiskImagePath = await (0, xcode_1.getDeveloperDiskImagePath)(version);
|
||||
const developerDiskImageSig = (0, fs_1.readFileSync)(`${developerDiskImagePath}.signature`);
|
||||
await imageMounter.uploadImage(developerDiskImagePath, developerDiskImageSig);
|
||||
await imageMounter.mountImage(developerDiskImagePath, developerDiskImageSig);
|
||||
}
|
||||
}
|
||||
async function uploadApp(clientManager, srcPath, destinationPath) {
|
||||
const afcClient = await clientManager.getAFCClient();
|
||||
try {
|
||||
await afcClient.getFileInfo('PublicStaging');
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof lib_1.AFCError && err.status === lib_1.AFC_STATUS.OBJECT_NOT_FOUND) {
|
||||
await afcClient.makeDirectory('PublicStaging');
|
||||
}
|
||||
else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
await afcClient.uploadDirectory(srcPath, destinationPath);
|
||||
}
|
||||
async function launchApp(clientManager, appInfo) {
|
||||
let tries = 0;
|
||||
while (tries < 3) {
|
||||
const debugServerClient = await clientManager.getDebugserverClient();
|
||||
await debugServerClient.setMaxPacketSize(1024);
|
||||
await debugServerClient.setWorkingDir(appInfo.Container);
|
||||
await debugServerClient.launchApp(appInfo.Path, appInfo.CFBundleExecutable);
|
||||
const result = await debugServerClient.checkLaunchSuccess();
|
||||
if (result === 'OK') {
|
||||
return debugServerClient;
|
||||
}
|
||||
else if (result === 'EBusy' || result === 'ENotFound') {
|
||||
debug('Device busy or app not found, trying to launch again in .5s...');
|
||||
tries++;
|
||||
debugServerClient.socket.end();
|
||||
await (0, process_1.wait)(500);
|
||||
}
|
||||
else {
|
||||
throw new errors_1.Exception(`There was an error launching app: ${result}`);
|
||||
}
|
||||
}
|
||||
throw new errors_1.Exception('Unable to launch app, number of tries exceeded');
|
||||
}
|
||||
90
node_modules/native-run/dist/ios/utils/simulator.js
generated
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.runOnSimulator = exports.getSimulators = void 0;
|
||||
const child_process_1 = require("child_process"); // TODO: need cross-spawn for windows?
|
||||
const Debug = require("debug");
|
||||
const errors_1 = require("../../errors");
|
||||
const log_1 = require("../../utils/log");
|
||||
const process_1 = require("../../utils/process");
|
||||
const xcode_1 = require("./xcode");
|
||||
const debug = Debug('native-run:ios:utils:simulator');
|
||||
async function getSimulators() {
|
||||
const simctl = (0, child_process_1.spawnSync)('xcrun', ['simctl', 'list', '--json'], {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
if (simctl.status) {
|
||||
throw new errors_1.Exception(`Unable to retrieve simulator list: ${simctl.stderr}`);
|
||||
}
|
||||
try {
|
||||
const output = JSON.parse(simctl.stdout);
|
||||
return output.runtimes
|
||||
.filter((runtime) => runtime.name.indexOf('watch') === -1 && runtime.name.indexOf('tv') === -1)
|
||||
.map((runtime) => (output.devices[runtime.identifier] || output.devices[runtime.name])
|
||||
.filter((device) => device.isAvailable)
|
||||
.map((device) => ({ ...device, runtime })))
|
||||
.reduce((prev, next) => prev.concat(next)) // flatten
|
||||
.sort((a, b) => (a.name < b.name ? -1 : 1));
|
||||
}
|
||||
catch (err) {
|
||||
throw new errors_1.Exception(`Unable to retrieve simulator list: ${err.message}`);
|
||||
}
|
||||
}
|
||||
exports.getSimulators = getSimulators;
|
||||
async function runOnSimulator(udid, appPath, bundleId, waitForApp) {
|
||||
debug(`Booting simulator ${udid}`);
|
||||
const bootResult = (0, child_process_1.spawnSync)('xcrun', ['simctl', 'boot', udid], {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
// TODO: is there a better way to check this?
|
||||
if (bootResult.status && !bootResult.stderr.includes('Unable to boot device in current state: Booted')) {
|
||||
throw new errors_1.Exception(`There was an error booting simulator: ${bootResult.stderr}`);
|
||||
}
|
||||
debug(`Installing ${appPath} on ${udid}`);
|
||||
const installResult = (0, child_process_1.spawnSync)('xcrun', ['simctl', 'install', udid, appPath], { encoding: 'utf8' });
|
||||
if (installResult.status) {
|
||||
throw new errors_1.Exception(`There was an error installing app on simulator: ${installResult.stderr}`);
|
||||
}
|
||||
const xCodePath = await (0, xcode_1.getXCodePath)();
|
||||
debug(`Running simulator ${udid}`);
|
||||
const openResult = (0, child_process_1.spawnSync)('open', [`${xCodePath}/Applications/Simulator.app`, '--args', '-CurrentDeviceUDID', udid], { encoding: 'utf8' });
|
||||
if (openResult.status) {
|
||||
throw new errors_1.Exception(`There was an error opening simulator: ${openResult.stderr}`);
|
||||
}
|
||||
debug(`Launching ${appPath} on ${udid}`);
|
||||
const launchResult = (0, child_process_1.spawnSync)('xcrun', ['simctl', 'launch', udid, bundleId], { encoding: 'utf8' });
|
||||
if (launchResult.status) {
|
||||
throw new errors_1.Exception(`There was an error launching app on simulator: ${launchResult.stderr}`);
|
||||
}
|
||||
if (waitForApp) {
|
||||
(0, process_1.onBeforeExit)(async () => {
|
||||
const terminateResult = (0, child_process_1.spawnSync)('xcrun', ['simctl', 'terminate', udid, bundleId], { encoding: 'utf8' });
|
||||
if (terminateResult.status) {
|
||||
debug('Unable to terminate app on simulator');
|
||||
}
|
||||
});
|
||||
(0, log_1.log)(`Waiting for app to close...\n`);
|
||||
await waitForSimulatorClose(udid, bundleId);
|
||||
}
|
||||
}
|
||||
exports.runOnSimulator = runOnSimulator;
|
||||
async function waitForSimulatorClose(udid, bundleId) {
|
||||
return new Promise((resolve) => {
|
||||
// poll service list for bundle id
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
const data = (0, child_process_1.spawnSync)('xcrun', ['simctl', 'spawn', udid, 'launchctl', 'list'], { encoding: 'utf8' });
|
||||
// if bundle id isn't in list, app isn't running
|
||||
if (data.stdout.indexOf(bundleId) === -1) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
debug('Error received from launchctl: %O', e);
|
||||
debug('App %s no longer found in process list for %s', bundleId, udid);
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
54
node_modules/native-run/dist/ios/utils/xcode.js
generated
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getDeveloperDiskImagePath = exports.getXCodePath = exports.getXcodeVersionInfo = void 0;
|
||||
const utils_fs_1 = require("@ionic/utils-fs");
|
||||
const child_process_1 = require("child_process");
|
||||
const errors_1 = require("../../errors");
|
||||
const process_1 = require("../../utils/process");
|
||||
function getXcodeVersionInfo() {
|
||||
const xcodeVersionInfo = (0, child_process_1.spawnSync)('xcodebuild', ['-version'], {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
if (xcodeVersionInfo.error) {
|
||||
throw xcodeVersionInfo.error;
|
||||
}
|
||||
try {
|
||||
const trimmed = xcodeVersionInfo.stdout.trim().split('\n');
|
||||
return ['Xcode ', 'Build version'].map((s, i) => trimmed[i].replace(s, ''));
|
||||
}
|
||||
catch (error) {
|
||||
throw new errors_1.Exception(`There was an error trying to retrieve the Xcode version: ${xcodeVersionInfo.stderr}`);
|
||||
}
|
||||
}
|
||||
exports.getXcodeVersionInfo = getXcodeVersionInfo;
|
||||
async function getXCodePath() {
|
||||
try {
|
||||
const { stdout } = await (0, process_1.execFile)('xcode-select', ['-p'], {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
if (stdout) {
|
||||
return stdout.trim();
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// ignore
|
||||
}
|
||||
throw new errors_1.Exception('Unable to get Xcode location. Is Xcode installed?');
|
||||
}
|
||||
exports.getXCodePath = getXCodePath;
|
||||
async function getDeveloperDiskImagePath(version) {
|
||||
const xCodePath = await getXCodePath();
|
||||
const versionDirs = await (0, utils_fs_1.readdir)(`${xCodePath}/Platforms/iPhoneOS.platform/DeviceSupport/`);
|
||||
const versionPrefix = version.match(/\d+\.\d+/);
|
||||
if (versionPrefix === null) {
|
||||
throw new errors_1.Exception(`Invalid iOS version: ${version}`);
|
||||
}
|
||||
// Can look like "11.2 (15C107)"
|
||||
for (const dir of versionDirs) {
|
||||
if (dir.includes(versionPrefix[0])) {
|
||||
return `${xCodePath}/Platforms/iPhoneOS.platform/DeviceSupport/${dir}/DeveloperDiskImage.dmg`;
|
||||
}
|
||||
}
|
||||
throw new errors_1.Exception(`Unable to find Developer Disk Image path for SDK ${version}. Do you have the right version of Xcode?`);
|
||||
}
|
||||
exports.getDeveloperDiskImagePath = getDeveloperDiskImagePath;
|
||||
35
node_modules/native-run/dist/list.js
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.run = void 0;
|
||||
const json_1 = require("./utils/json");
|
||||
const list_1 = require("./utils/list");
|
||||
async function run(args) {
|
||||
const [ios, android] = await Promise.all([
|
||||
(async () => {
|
||||
const cmd = await Promise.resolve().then(() => require('./ios/list'));
|
||||
return cmd.list(args);
|
||||
})(),
|
||||
(async () => {
|
||||
const cmd = await Promise.resolve().then(() => require('./android/list'));
|
||||
return cmd.list(args);
|
||||
})(),
|
||||
]);
|
||||
if (args.includes('--json')) {
|
||||
process.stdout.write((0, json_1.stringify)({ ios, android }));
|
||||
}
|
||||
else {
|
||||
process.stdout.write(`
|
||||
iOS
|
||||
---
|
||||
|
||||
${(0, list_1.formatTargets)(args, ios)}
|
||||
|
||||
Android
|
||||
-------
|
||||
|
||||
${(0, list_1.formatTargets)(args, android)}
|
||||
|
||||
`);
|
||||
}
|
||||
}
|
||||
exports.run = run;
|
||||
21
node_modules/native-run/dist/utils/cli.js
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getOptionValues = exports.getOptionValue = void 0;
|
||||
function getOptionValue(args, arg, defaultValue) {
|
||||
const i = args.indexOf(arg);
|
||||
if (i >= 0) {
|
||||
return args[i + 1];
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
exports.getOptionValue = getOptionValue;
|
||||
function getOptionValues(args, arg) {
|
||||
const returnVal = [];
|
||||
args.map((entry, idx) => {
|
||||
if (entry === arg) {
|
||||
returnVal.push(args[idx + 1]);
|
||||
}
|
||||
});
|
||||
return returnVal;
|
||||
}
|
||||
exports.getOptionValues = getOptionValues;
|
||||
16
node_modules/native-run/dist/utils/fn.js
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.once = void 0;
|
||||
function once(fn) {
|
||||
let called = false;
|
||||
let r;
|
||||
const wrapper = (...args) => {
|
||||
if (!called) {
|
||||
called = true;
|
||||
r = fn(...args);
|
||||
}
|
||||
return r;
|
||||
};
|
||||
return wrapper;
|
||||
}
|
||||
exports.once = once;
|
||||
12
node_modules/native-run/dist/utils/fs.js
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isDir = void 0;
|
||||
const utils_fs_1 = require("@ionic/utils-fs");
|
||||
async function isDir(p) {
|
||||
const stats = await (0, utils_fs_1.statSafe)(p);
|
||||
if (stats === null || stats === void 0 ? void 0 : stats.isDirectory()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
exports.isDir = isDir;
|
||||
30
node_modules/native-run/dist/utils/ini.js
generated
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.writeINI = exports.readINI = void 0;
|
||||
const utils_fs_1 = require("@ionic/utils-fs");
|
||||
const Debug = require("debug");
|
||||
const util = require("util");
|
||||
const debug = Debug('native-run:android:utils:ini');
|
||||
async function readINI(p, guard = (o) => true) {
|
||||
const ini = await Promise.resolve().then(() => require('ini'));
|
||||
try {
|
||||
const contents = await (0, utils_fs_1.readFile)(p, { encoding: 'utf8' });
|
||||
const config = ini.decode(contents);
|
||||
if (!guard(config)) {
|
||||
throw new Error(`Invalid ini configuration file: ${p}\n` +
|
||||
`The following guard was used: ${guard.toString()}\n` +
|
||||
`INI config parsed as: ${util.inspect(config)}`);
|
||||
}
|
||||
return { __filename: p, ...config };
|
||||
}
|
||||
catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
exports.readINI = readINI;
|
||||
async function writeINI(p, o) {
|
||||
const ini = await Promise.resolve().then(() => require('ini'));
|
||||
const contents = ini.encode(o);
|
||||
await (0, utils_fs_1.writeFile)(p, contents, { encoding: 'utf8' });
|
||||
}
|
||||
exports.writeINI = writeINI;
|
||||
7
node_modules/native-run/dist/utils/json.js
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.stringify = void 0;
|
||||
function stringify(obj) {
|
||||
return JSON.stringify(obj, (k, v) => (v instanceof RegExp ? v.toString() : v), '\t');
|
||||
}
|
||||
exports.stringify = stringify;
|
||||
69
node_modules/native-run/dist/utils/list.js
generated
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.formatTargets = void 0;
|
||||
const utils_terminal_1 = require("@ionic/utils-terminal");
|
||||
const errors_1 = require("../errors");
|
||||
const json_1 = require("./json");
|
||||
function formatTargets(args, targets) {
|
||||
const { devices, virtualDevices, errors } = targets;
|
||||
const virtualOnly = args.includes('--virtual');
|
||||
const devicesOnly = args.includes('--device');
|
||||
if (virtualOnly && devicesOnly) {
|
||||
throw new errors_1.CLIException('Only one of --device or --virtual may be specified', errors_1.ERR_BAD_INPUT);
|
||||
}
|
||||
if (args.includes('--json')) {
|
||||
let result;
|
||||
if (virtualOnly) {
|
||||
result = { virtualDevices, errors };
|
||||
}
|
||||
else if (devicesOnly) {
|
||||
result = { devices, errors };
|
||||
}
|
||||
else {
|
||||
result = { devices, virtualDevices, errors };
|
||||
}
|
||||
return (0, json_1.stringify)(result);
|
||||
}
|
||||
let output = '';
|
||||
if (errors.length > 0) {
|
||||
output += `Errors (!):\n\n${errors.map((e) => ` ${(0, errors_1.serializeError)(e)}`)}\n`;
|
||||
}
|
||||
if (!virtualOnly) {
|
||||
output += printTargets('Connected Device', devices);
|
||||
if (devicesOnly) {
|
||||
return output;
|
||||
}
|
||||
output += '\n';
|
||||
}
|
||||
output += printTargets('Virtual Device', virtualDevices);
|
||||
return output;
|
||||
}
|
||||
exports.formatTargets = formatTargets;
|
||||
function printTargets(name, targets) {
|
||||
let output = `${name}s:\n\n`;
|
||||
if (targets.length === 0) {
|
||||
output += ` No ${name.toLowerCase()}s found\n`;
|
||||
}
|
||||
else {
|
||||
output += formatTargetTable(targets) + '\n';
|
||||
}
|
||||
return output;
|
||||
}
|
||||
function formatTargetTable(targets) {
|
||||
const spacer = (0, utils_terminal_1.indent)(2);
|
||||
return (spacer +
|
||||
(0, utils_terminal_1.columnar)(targets.map(targetToRow), {
|
||||
headers: ['Name', 'API', 'Target ID'],
|
||||
vsep: ' ',
|
||||
})
|
||||
.split('\n')
|
||||
.join(`\n${spacer}`));
|
||||
}
|
||||
function targetToRow(target) {
|
||||
var _a, _b, _c, _d;
|
||||
return [
|
||||
(_c = (_b = (_a = target.name) !== null && _a !== void 0 ? _a : target.model) !== null && _b !== void 0 ? _b : target.id) !== null && _c !== void 0 ? _c : '?',
|
||||
`${target.platform === 'ios' ? 'iOS' : 'API'} ${target.sdkVersion}`,
|
||||
(_d = target.id) !== null && _d !== void 0 ? _d : '?',
|
||||
];
|
||||
}
|
||||
11
node_modules/native-run/dist/utils/log.js
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.log = void 0;
|
||||
const json_1 = require("./json");
|
||||
function log(message) {
|
||||
if (process.argv.includes('--json')) {
|
||||
message = (0, json_1.stringify)({ message });
|
||||
}
|
||||
process.stdout.write(message);
|
||||
}
|
||||
exports.log = log;
|
||||
15
node_modules/native-run/dist/utils/object.js
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.sort = void 0;
|
||||
function sort(obj) {
|
||||
const entries = [...Object.entries(obj)];
|
||||
entries.sort(([k1], [k2]) => k1.localeCompare(k2));
|
||||
for (const [key] of entries) {
|
||||
delete obj[key];
|
||||
}
|
||||
for (const [key, value] of entries) {
|
||||
obj[key] = value;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
exports.sort = sort;
|
||||
34
node_modules/native-run/dist/utils/process.js
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.onBeforeExit = exports.wait = exports.execFile = exports.exec = void 0;
|
||||
const cp = require("child_process");
|
||||
const Debug = require("debug");
|
||||
const util = require("util");
|
||||
const fn_1 = require("./fn");
|
||||
const debug = Debug('native-run:utils:process');
|
||||
exports.exec = util.promisify(cp.exec);
|
||||
exports.execFile = util.promisify(cp.execFile);
|
||||
exports.wait = util.promisify(setTimeout);
|
||||
const exitQueue = [];
|
||||
function onBeforeExit(fn) {
|
||||
exitQueue.push(fn);
|
||||
}
|
||||
exports.onBeforeExit = onBeforeExit;
|
||||
const BEFORE_EXIT_SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGBREAK'];
|
||||
const beforeExitHandlerWrapper = (signal) => (0, fn_1.once)(async () => {
|
||||
debug('onBeforeExit handler: %s received', signal);
|
||||
debug('onBeforeExit handler: running %s queued functions', exitQueue.length);
|
||||
for (const [i, fn] of exitQueue.entries()) {
|
||||
try {
|
||||
await fn();
|
||||
}
|
||||
catch (e) {
|
||||
debug('Error from function %d in exit queue: %O', i, e);
|
||||
}
|
||||
}
|
||||
debug('onBeforeExit handler: exiting (exit code %s)', process.exitCode ? process.exitCode : 0);
|
||||
process.exit();
|
||||
});
|
||||
for (const signal of BEFORE_EXIT_SIGNALS) {
|
||||
process.on(signal, beforeExitHandlerWrapper(signal));
|
||||
}
|
||||
22
node_modules/native-run/dist/utils/unzip.js
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.unzip = void 0;
|
||||
const util_1 = require("util");
|
||||
async function unzip(srcPath, onEntry) {
|
||||
const yauzl = await Promise.resolve().then(() => require('yauzl'));
|
||||
return new Promise((resolve, reject) => {
|
||||
yauzl.open(srcPath, { lazyEntries: true }, (err, zipfile) => {
|
||||
if (!zipfile || err) {
|
||||
return reject(err);
|
||||
}
|
||||
const openReadStream = (0, util_1.promisify)(zipfile.openReadStream.bind(zipfile));
|
||||
zipfile.once('error', reject);
|
||||
// resolve when either one happens
|
||||
zipfile.once('close', resolve); // fd of zip closed
|
||||
zipfile.once('end', resolve); // last entry read
|
||||
zipfile.on('entry', (entry) => onEntry(entry, zipfile, openReadStream));
|
||||
zipfile.readEntry();
|
||||
});
|
||||
});
|
||||
}
|
||||
exports.unzip = unzip;
|
||||
106
node_modules/native-run/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
{
|
||||
"name": "native-run",
|
||||
"version": "2.0.3",
|
||||
"description": "A CLI for running apps on iOS/Android devices and simulators/emulators",
|
||||
"bin": {
|
||||
"native-run": "bin/native-run"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf dist",
|
||||
"build": "npm run clean && tsc",
|
||||
"watch": "tsc -w",
|
||||
"test": "jest --maxWorkers=4",
|
||||
"lint": "npm run eslint && npm run prettier -- --check",
|
||||
"fmt": "npm run eslint -- --fix && npm run prettier -- --write",
|
||||
"prettier": "prettier \"**/*.ts\"",
|
||||
"eslint": "eslint . --ext .ts",
|
||||
"publish:ci": "semantic-release",
|
||||
"publish:testing": "npm version prerelease --preid=testing --no-git-tag-version && npm publish --tag=testing && git stash",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"assets",
|
||||
"bin",
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ionic-team/native-run.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ionic/utils-fs": "^3.1.7",
|
||||
"@ionic/utils-terminal": "^2.3.4",
|
||||
"bplist-parser": "^0.3.2",
|
||||
"debug": "^4.3.4",
|
||||
"elementtree": "^0.1.7",
|
||||
"ini": "^4.1.1",
|
||||
"plist": "^3.1.0",
|
||||
"split2": "^4.2.0",
|
||||
"through2": "^4.0.2",
|
||||
"tslib": "^2.6.2",
|
||||
"yauzl": "^2.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ionic/eslint-config": "^0.4.0",
|
||||
"@ionic/prettier-config": "^4.0.0",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@types/debug": "^4.1.10",
|
||||
"@types/elementtree": "^0.1.3",
|
||||
"@types/ini": "^1.3.32",
|
||||
"@types/jest": "^26.0.13",
|
||||
"@types/node": "^16.0.0",
|
||||
"@types/plist": "^3.0.4",
|
||||
"@types/split2": "^4.2.2",
|
||||
"@types/through2": "^2.0.40",
|
||||
"@types/yauzl": "^2.10.2",
|
||||
"eslint": "^8.57.0",
|
||||
"jest": "^26.4.2",
|
||||
"prettier": "^3.0.3",
|
||||
"semantic-release": "^19.0.5",
|
||||
"ts-jest": "^26.3.0",
|
||||
"typescript": "~4.9.5"
|
||||
},
|
||||
"prettier": "@ionic/prettier-config",
|
||||
"eslintConfig": {
|
||||
"extends": "@ionic/eslint-config/recommended",
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-module-boundary-types": [
|
||||
"warn",
|
||||
{
|
||||
"allowArgumentsExplicitlyTypedAsAny": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"release": {
|
||||
"branches": "stable",
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/changelog",
|
||||
"@semantic-release/npm",
|
||||
"@semantic-release/github",
|
||||
"@semantic-release/git"
|
||||
]
|
||||
},
|
||||
"keywords": [
|
||||
"android",
|
||||
"ios",
|
||||
"cli",
|
||||
"mobile",
|
||||
"app",
|
||||
"hybrid",
|
||||
"native"
|
||||
],
|
||||
"author": "Ionic Team <hi@ionicframework.com> (https://ionicframework.com)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ionic-team/native-run/issues"
|
||||
},
|
||||
"homepage": "https://github.com/ionic-team/native-run#readme"
|
||||
}
|
||||