@corellium/corellium-cli 1.0.9 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.gitlab-ci.yml CHANGED
@@ -19,3 +19,11 @@ test:
19
19
  # - mysqladmin -u root -ppassword -P 3306 -h mariadb status
20
20
 
21
21
  - npm ci
22
+ - npm run test:ci
23
+ artifacts:
24
+ paths:
25
+ - coverage/
26
+ reports:
27
+ coverage_report:
28
+ coverage_format: cobertura
29
+ path: coverage/cobertura-coverage.xml
package/.nycrc ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "check-coverage": false,
3
+ "report-dir": "./coverage",
4
+ "reporter": ["cobertura", "lcov", "text"],
5
+ "branches": 60,
6
+ "lines": 60,
7
+ "functions": 60,
8
+ "statements": 60,
9
+ "temp-dir": "/tmp",
10
+ "watermarks": {
11
+ "lines": [60, 80],
12
+ "functions": [60, 80],
13
+ "branches": [60, 80],
14
+ "statements": [60, 80]
15
+ }
16
+ }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@corellium/corellium-cli",
3
- "version": "1.0.9",
3
+ "version": "1.1.1",
4
4
  "description": "Corellium CLI Tool",
5
5
  "scripts": {
6
6
  "corellium": "node index.js",
7
7
  "lint": "eslint .",
8
8
  "lint-staged": "lint-staged",
9
- "lint:fix": "eslint --fix ."
9
+ "lint:fix": "eslint --fix .",
10
+ "test:ci": "npx nyc report"
10
11
  },
11
12
  "bin": {
12
13
  "corellium": "index.sh"
@@ -32,6 +33,7 @@
32
33
  "progress": "^2.0.3",
33
34
  "prompts": "^2.4.2",
34
35
  "uuid": "^8.3.2",
36
+ "websocket-stream": "^5.5.2",
35
37
  "xhr2": "^0.2.0",
36
38
  "yargs": "^17.5.1"
37
39
  },
@@ -0,0 +1,2 @@
1
+ sonar.projectKey=middleware_corellium-cli_AYbC7e_Y2VKo8EJiY-py
2
+ sonar.qualitygate.wait=true
@@ -1,4 +1,4 @@
1
- const Client = require('../Client')
1
+ const Client = require('./Client')
2
2
 
3
3
  /**
4
4
  * @typedef {object} AppsResult
@@ -0,0 +1,89 @@
1
+ const axios = require('axios').default
2
+ const { readProfile } = require('../profile')
3
+ const { createWriteStream } = require('fs')
4
+ const wsstream = require('websocket-stream')
5
+
6
+ /**
7
+ * Client Base class
8
+ */
9
+ class Client {
10
+ constructor (argv) {
11
+ this.argv = Object.assign({}, argv)
12
+ this.project = argv.project || argv.projectId
13
+ this.instance = argv.instance || argv.instanceId
14
+ this._profile = readProfile()
15
+ this._version = require('../../package.json').version
16
+ }
17
+
18
+ /**
19
+ * @protected
20
+ * @returns default headers
21
+ */
22
+ _headers = () => ({
23
+ Authorization: `Bearer ${this._profile.token}`,
24
+ 'User-Agent': `corellium-cli v${this._version}`
25
+ })
26
+
27
+ /**
28
+ * Retrieve file stream
29
+ * @protected
30
+ * @param {string} path
31
+ * @param {PathLike} outputFile
32
+ * @returns {Promise<any>}
33
+ */
34
+ _fetchStream = async (path, outputFile) => {
35
+ const response = await axios({
36
+ method: 'GET',
37
+ url: `${this._profile.endpoint}/api/${path}`,
38
+ headers: this._headers(),
39
+ responseType: 'stream'
40
+ })
41
+
42
+ const writeStream = createWriteStream(outputFile)
43
+ response.data.pipe(writeStream)
44
+
45
+ return new Promise((resolve, reject) => {
46
+ writeStream.on('finish', resolve)
47
+ writeStream.on('error', reject)
48
+ })
49
+ }
50
+
51
+ /**
52
+ * Get Websocket stream
53
+ * @protected
54
+ * @param {string} token
55
+ * @returns {wsstream.WebSocketDuplex}
56
+ */
57
+ _fetchWSS = (token) => {
58
+ const url = `wss://${this._profile.endpoint}/api/v1/agent/${token}`
59
+ return wsstream(url, ['binary'])
60
+ }
61
+
62
+ /**
63
+ * Perform HTTP transaction
64
+ * @protected
65
+ * @param {string} method
66
+ * @param {string} path
67
+ * @param {object} data
68
+ * @returns {Promise<any>}
69
+ */
70
+ _fetch = async (method, path, data) => {
71
+ const headers = this._headers()
72
+ let response
73
+ switch (method) {
74
+ case 'GET':
75
+ response = await axios.get(`${this._profile.endpoint}/api/${path}`, { headers })
76
+ break
77
+ case 'POST':
78
+ response = await axios.post(`${this._profile.endpoint}/api/${path}`, data, { headers })
79
+ break
80
+ case 'DELETE':
81
+ response = await axios.delete(`${this._profile.endpoint}/api/${path}`, { headers })
82
+ break
83
+ }
84
+ if (response.status >= 400) { throw new Error(response.statusText) }
85
+ return response.data || {}
86
+ }
87
+ }
88
+
89
+ module.exports = Client
@@ -0,0 +1,84 @@
1
+ const Client = require('./Client')
2
+
3
+ /**
4
+ * Instance class
5
+ */
6
+ class InstanceCLI extends Client {
7
+ instanceBasePath = () => `v1/instances/${this.instance}`
8
+ preauthedBasePath = () => 'v1/preauthed'
9
+
10
+ /**
11
+ * Upgrade iOS version on instance
12
+ * @returns {Promise<any>}
13
+ */
14
+ upgrade = async (options) => {
15
+ const { os, osbuild } = options
16
+ return await this._fetch('POST', `${this.instanceBasePath()}/upgrade`, { os, osbuild })
17
+ }
18
+
19
+ /**
20
+ * Get websocket stream from token
21
+ * @returns {wsstream.WebSocketDuplex}
22
+ */
23
+ wss = (token) => {
24
+ return this._fetchWSS(token)
25
+ }
26
+
27
+ /**
28
+ * Start instance
29
+ */
30
+ start = async (options) => {
31
+ const { sockcap, paused } = options
32
+ return await this._fetch('POST', `${this.instanceBasePath()}/start`, { sockcap, paused })
33
+ }
34
+
35
+ /**
36
+ * Restore backup to instance
37
+ * @returns {Promise<any>}
38
+ */
39
+ restoreBackup = async () => {
40
+ return await this._fetch('POST', `${this.instanceBasePath()}/restoreBackup`)
41
+ }
42
+
43
+ /**
44
+ * Enable monitoring
45
+ * @returns {Promise<any>}
46
+ */
47
+ enableMonitoring = async (options) => {
48
+ return await this._fetch('POST', `${this.instanceBasePath()}/netdump/enable`, options)
49
+ }
50
+
51
+ /**
52
+ * Disable monitoring
53
+ * @returns {Promise<any>}
54
+ */
55
+ disableMonitoring = async () => {
56
+ return await this._fetch('POST', `${this.instanceBasePath()}/netdump/disable`)
57
+ }
58
+
59
+ /**
60
+ * Authorize pcap download
61
+ * @returns {Promise<any>}
62
+ */
63
+ authorizePcap = async () => {
64
+ return await this._fetch('POST', `${this.instanceBasePath()}/netdump-authorize`)
65
+ }
66
+
67
+ /**
68
+ * Download file
69
+ * @returns {Promise<any>}
70
+ */
71
+ download = async (token, file, outputFile) => {
72
+ return await this._fetchStream(`${this.preauthedBasePath()}/${token}/${file}`, outputFile)
73
+ }
74
+
75
+ /**
76
+ * Fetch instance information
77
+ * @returns {Promise<any>}
78
+ */
79
+ info = async () => {
80
+ return await this._fetch('GET', `${this.instanceBasePath()}`, {})
81
+ }
82
+ }
83
+
84
+ module.exports = InstanceCLI
@@ -1,6 +1,6 @@
1
- const Client = require('./commands/Client')
2
- const util = require('./utils')
3
- const { handler: login } = require('./commands/login')
1
+ const Client = require('./Client')
2
+ const util = require('../utils')
3
+ const { handler: login } = require('../commands/login')
4
4
 
5
5
  class TrialAccount extends Client {
6
6
  constructor (argv) {
@@ -1,12 +1,12 @@
1
- const Client = require('../Client')
1
+ const Client = require('./Client')
2
2
  const {
3
3
  updateProfile
4
- } = require('../../profile')
4
+ } = require('../profile')
5
5
 
6
6
  /**
7
- * WebPlayer class
7
+ * Webplayer class
8
8
  */
9
- class WebPlayer extends Client {
9
+ class Webplayer extends Client {
10
10
  constructor (argv) {
11
11
  super(argv)
12
12
  const { features, permissions, expiresIn } = this.argv
@@ -16,7 +16,7 @@ class WebPlayer extends Client {
16
16
  }
17
17
 
18
18
  /**
19
- * Create a Web Player session
19
+ * Create a Webplayer session
20
20
  * @returns {Promise<Array<object>>}
21
21
  */
22
22
  createSession = async () => this._fetch('POST', 'v1/webplayer', {
@@ -28,14 +28,14 @@ class WebPlayer extends Client {
28
28
  })
29
29
 
30
30
  /**
31
- * Destroy a Web Player session
31
+ * Destroy a Webplayer session
32
32
  * @param {string} sessionId
33
33
  * @returns {Promise<{object}>}
34
34
  */
35
35
  destroySession = async (sessionId) => this._fetch('DELETE', `v1/webplayer/${sessionId}`, {})
36
36
 
37
37
  /**
38
- * List Web Player sessions
38
+ * List Webplayer sessions
39
39
  * @returns {Promise<Array<object>>}
40
40
  */
41
41
  listSessions = async (sessionId) => {
@@ -44,7 +44,7 @@ class WebPlayer extends Client {
44
44
  }
45
45
 
46
46
  /**
47
- * Log into WebPlayer session by activating WebPlayer credentials
47
+ * Log into Webplayer session by activating Webplayer credentials
48
48
  * @param {string} sessionId
49
49
  * @returns {Promise<void>}
50
50
  */
@@ -54,4 +54,4 @@ class WebPlayer extends Client {
54
54
  }
55
55
  }
56
56
 
57
- module.exports = WebPlayer
57
+ module.exports = Webplayer
@@ -1,4 +1,4 @@
1
- const AgentCLI = require('./Agent')
1
+ const AgentCLI = require('../../clients/Agent')
2
2
  const { displayTable } = require('../../table')
3
3
  const { validateNonEmpty } = require('../../utils')
4
4
  const { getErrorMessage } = require('../../error')
@@ -1,4 +1,4 @@
1
- const AgentCLI = require('./Agent')
1
+ const AgentCLI = require('../../clients/Agent')
2
2
  const { displayTable } = require('../../table')
3
3
  const { validateNonEmpty } = require('../../utils')
4
4
  const { getErrorMessage } = require('../../error')
@@ -1,4 +1,4 @@
1
- const AgentCLI = require('./Agent')
1
+ const AgentCLI = require('../../clients/Agent')
2
2
  const { validateNonEmpty } = require('../../utils')
3
3
  const { getErrorMessage } = require('../../error')
4
4
  const log = require('../../logging')
@@ -4,7 +4,9 @@ const subcommands = [
4
4
  require('./delete'),
5
5
  require('./stop'),
6
6
  require('./start'),
7
- require('./upgrade')
7
+ require('./upgrade'),
8
+ require('./netmon'),
9
+ require('./restoreBackup')
8
10
  ]
9
11
 
10
12
  let _yargs
@@ -0,0 +1,44 @@
1
+ const log = require('../../../logging')
2
+ const InstanceCLI = require('../../../clients/Instance')
3
+
4
+ async function builder (yargs) {
5
+ yargs
6
+ .positional('instanceId', {
7
+ type: 'string',
8
+ describe: 'Instance id',
9
+ demandOption: true
10
+ })
11
+ .option('verbose', {
12
+ alias: 'v',
13
+ type: 'boolean',
14
+ describe: 'Console will receive verbose error output',
15
+ demandOption: false
16
+ })
17
+ }
18
+
19
+ async function handler (argv) {
20
+ try {
21
+ const instance = new InstanceCLI(argv)
22
+ await instance.disableMonitoring()
23
+ log.info('Success')
24
+ } catch (e) {
25
+ if (e.response) {
26
+ log.error(`${e.response.status} ${e.response.data.error || e.response.statusText}`)
27
+ } else {
28
+ if (e.message) {
29
+ log.error(e.message)
30
+ } else {
31
+ log.error('Urecognized error, use --verbose for details')
32
+ }
33
+ }
34
+
35
+ if (argv.verbose) { log.error(JSON.stringify(e)) }
36
+ }
37
+ }
38
+
39
+ module.exports = {
40
+ builder,
41
+ handler,
42
+ command: 'disable <instanceId> [--verbose]',
43
+ describe: 'Disable network monitoring'
44
+ }
@@ -0,0 +1,50 @@
1
+ const log = require('../../../logging')
2
+ const InstanceCLI = require('../../../clients/Instance')
3
+
4
+ async function builder (yargs) {
5
+ yargs
6
+ .positional('instanceId', {
7
+ type: 'string',
8
+ describe: 'Instance id',
9
+ demandOption: true
10
+ })
11
+ .positional('outputFile', {
12
+ type: 'string',
13
+ describe: 'Path to output',
14
+ demandOption: true
15
+ })
16
+ .option('verbose', {
17
+ alias: 'v',
18
+ type: 'boolean',
19
+ describe: 'Console will receive verbose error output',
20
+ demandOption: false
21
+ })
22
+ }
23
+
24
+ async function handler (argv) {
25
+ try {
26
+ const instance = new InstanceCLI(argv)
27
+ const { token } = await instance.authorizePcap()
28
+ await instance.download(token, 'netdump.pcap', argv.outputFile)
29
+ log.info(`Success, pcap saved to ${argv.outputFile}`)
30
+ } catch (e) {
31
+ if (e.response) {
32
+ log.error(`${e.response.status} ${e.response.data.error || e.response.statusText}`)
33
+ } else {
34
+ if (e.message) {
35
+ log.error(e.message)
36
+ } else {
37
+ log.error('Urecognized error, use --verbose for details')
38
+ }
39
+ }
40
+
41
+ if (argv.verbose) { log.error(JSON.stringify(e)) }
42
+ }
43
+ }
44
+
45
+ module.exports = {
46
+ builder,
47
+ handler,
48
+ command: 'download <instanceId> <outputFile> [--verbose]',
49
+ describe: 'Download pcap file'
50
+ }
@@ -0,0 +1,72 @@
1
+ const log = require('../../../logging')
2
+ const InstanceCLI = require('../../../clients/Instance')
3
+
4
+ async function builder (yargs) {
5
+ yargs
6
+ .positional('instanceId', {
7
+ type: 'string',
8
+ describe: 'Instance id',
9
+ demandOption: true
10
+ })
11
+ .option('srcPorts', {
12
+ type: 'array',
13
+ describe: 'Source ports to filter'
14
+ })
15
+ .option('dstPorts', {
16
+ type: 'array',
17
+ describe: 'Destination ports to filter'
18
+ })
19
+ .option('ports', {
20
+ type: 'array',
21
+ describe: 'Source or destination ports to filter'
22
+ })
23
+ .option('processes', {
24
+ type: 'array',
25
+ describe: 'List of process names or pids to filter'
26
+ })
27
+ .option('portRanges', {
28
+ type: 'array',
29
+ describe: 'List of port ranges in that format \'[beginPort]-[endport]\''
30
+ })
31
+ .option('protocols', {
32
+ type: 'array',
33
+ describe: 'List of protocols'
34
+ })
35
+ .option('verbose', {
36
+ alias: 'v',
37
+ type: 'boolean',
38
+ describe: 'Console will receive verbose error output',
39
+ demandOption: false
40
+ })
41
+ }
42
+
43
+ async function handler (argv) {
44
+ try {
45
+ const { srcPorts, dstPorts, ports, processes, portRanges, protocols } = argv
46
+
47
+ const instance = new InstanceCLI(argv)
48
+ await instance.enableMonitoring({
49
+ srcPorts, dstPorts, ports, processes, portRanges, protocols
50
+ })
51
+ log.info('Success')
52
+ } catch (e) {
53
+ if (e.response) {
54
+ log.error(`${e.response.status} ${e.response.data.error || e.response.statusText}`)
55
+ } else {
56
+ if (e.message) {
57
+ log.error(e.message)
58
+ } else {
59
+ log.error('Urecognized error, use --verbose for details')
60
+ }
61
+ }
62
+
63
+ if (argv.verbose) { log.error(JSON.stringify(e)) }
64
+ }
65
+ }
66
+
67
+ module.exports = {
68
+ builder,
69
+ handler,
70
+ command: 'enable <instanceId> [--verbose]',
71
+ describe: 'Enable network monitoring'
72
+ }
@@ -0,0 +1,15 @@
1
+ const subcommands = [
2
+ require('./enable'),
3
+ require('./disable'),
4
+ require('./download'),
5
+ require('./stream')
6
+ ]
7
+
8
+ let _yargs
9
+
10
+ module.exports = {
11
+ builder: yargs => (_yargs = yargs) && yargs.command(subcommands).scriptName(''),
12
+ handler: argv => !argv[1] && _yargs.showHelp(),
13
+ command: 'netmon',
14
+ describe: 'Network Monitoring related commands'
15
+ }
@@ -0,0 +1,48 @@
1
+ const log = require('../../../logging')
2
+ const InstanceCLI = require('../../../clients/Instance')
3
+
4
+ async function builder (yargs) {
5
+ yargs
6
+ .positional('instanceId', {
7
+ type: 'string',
8
+ describe: 'Instance id',
9
+ demandOption: true
10
+ })
11
+ .option('verbose', {
12
+ alias: 'v',
13
+ type: 'boolean',
14
+ describe: 'Console will receive verbose error output',
15
+ demandOption: false
16
+ })
17
+ }
18
+
19
+ async function handler (argv) {
20
+ try {
21
+ const instance = new InstanceCLI(argv)
22
+ const { netdump } = await instance.info()
23
+ if (netdump && netdump.enabled) {
24
+ instance.wss(netdump.info).pipe(process.stdout)
25
+ } else {
26
+ log.error('Network monitoring is not enabled')
27
+ }
28
+ } catch (e) {
29
+ if (e.response) {
30
+ log.error(`${e.response.status} ${e.response.data.error || e.response.statusText}`)
31
+ } else {
32
+ if (e.message) {
33
+ log.error(e.message)
34
+ } else {
35
+ log.error('Urecognized error, use --verbose for details')
36
+ }
37
+ }
38
+
39
+ if (argv.verbose) { log.error(JSON.stringify(e)) }
40
+ }
41
+ }
42
+
43
+ module.exports = {
44
+ builder,
45
+ handler,
46
+ command: 'stream <instanceId> [--verbose]',
47
+ describe: 'Stream monitoring events'
48
+ }
@@ -0,0 +1,77 @@
1
+ const log = require('../../logging')
2
+ const InstanceCLI = require('../../clients/Instance')
3
+ const { readProfile } = require('../../profile')
4
+ const { v4: uuidv4 } = require('uuid')
5
+ const { basename } = require('path')
6
+ const MultiProgress = require('multi-progress')
7
+ const { uploadWithProgress } = require('../../upload')
8
+ const { waitForTaskState } = require('../../utils')
9
+
10
+ async function builder (yargs) {
11
+ yargs
12
+ .positional('instanceId', {
13
+ type: 'string',
14
+ describe: 'Instance id',
15
+ demandOption: true
16
+ })
17
+ .positional('zipFile', {
18
+ type: 'string',
19
+ describe: 'Path to backup zip file',
20
+ demandOption: true
21
+ })
22
+ .option('verbose', {
23
+ alias: 'v',
24
+ type: 'boolean',
25
+ describe: 'Console will receive verbose error output',
26
+ demandOption: false
27
+ })
28
+ }
29
+
30
+ async function handler (argv) {
31
+ const profile = readProfile()
32
+ const { instanceId, zipFile } = argv
33
+ const name = basename(zipFile)
34
+ const image = uuidv4()
35
+ const url = profile.endpoint +
36
+ '/api/v1/instances' +
37
+ '/' +
38
+ encodeURIComponent(instanceId) +
39
+ '/' +
40
+ 'image-upload/backup' +
41
+ '/' +
42
+ encodeURIComponent(image) +
43
+ '/' +
44
+ encodeURIComponent(name)
45
+
46
+ const multi = new MultiProgress(process.stderr)
47
+
48
+ try {
49
+ await uploadWithProgress(url, profile.token, zipFile, multi, {
50
+ 'x-corellium-image-encapsulated': 'false'
51
+ })
52
+ console.log(`Backup uploaded ${image}`)
53
+
54
+ const instance = new InstanceCLI(argv)
55
+ await instance.restoreBackup()
56
+
57
+ await waitForTaskState(instanceId, instance.info.bind(instance), 'restoring-backup')
58
+ console.log('Restoring in progress')
59
+ await waitForTaskState(instanceId, instance.info.bind(instance), 'none')
60
+
61
+ log.info(`Restored backup ${zipFile} to ${argv.instanceId}`)
62
+ } catch (err) {
63
+ const errorMessage = err.message
64
+ log.error(`Restore backup failed: ${errorMessage}`)
65
+ if (err.response && err.response.data && err.response.data.error) {
66
+ log.error(err.response.data.error)
67
+ }
68
+ if (argv.verbose) { log.error(err) }
69
+ }
70
+ }
71
+
72
+ module.exports = {
73
+ builder,
74
+ handler,
75
+ command: 'restore-backup <instanceId> <zipFile> [--verbose]',
76
+ describe: 'Restore iOS backup to instance'
77
+ }
@@ -0,0 +1,47 @@
1
+ const { getErrorMessage } = require('../../error')
2
+ const asciiArt = require('ascii-art')
3
+
4
+ const Client = require('../../clients/Client')
5
+ async function builder (yargs) {
6
+ yargs.positional('instanceId', {
7
+ type: 'string',
8
+ describe: 'Instance id',
9
+ demandOption: true
10
+ })
11
+ .option('verbose', {
12
+ alias: 'v',
13
+ type: 'boolean',
14
+ describe: 'Console will receive verbose error output'
15
+ })
16
+ .option('orientation', {
17
+ alias: 'o',
18
+ type: 'number',
19
+ describe: 'Orientation type'
20
+ })
21
+ }
22
+
23
+ async function handler (argv) {
24
+ const client = new Client(argv)
25
+ const instanceId = argv.instanceId
26
+ const orientation = argv.orientation || 4
27
+ try {
28
+ await client._fetch('POST', `v1/instances/${instanceId}/rotate`, { orientation })
29
+ console.log(
30
+ asciiArt.style(`Successfully rotated instance ${instanceId}`, 'green', true)
31
+ )
32
+ } catch (error) {
33
+ const errorMessage = getErrorMessage(error)
34
+ console.error(`Failed to rotate ${instanceId}: ${errorMessage}`)
35
+
36
+ if (argv.detailedErrors) {
37
+ console.log(error)
38
+ }
39
+ }
40
+ }
41
+
42
+ module.exports = {
43
+ builder,
44
+ handler,
45
+ command: 'rotate <instanceId> [orientation]',
46
+ describe: 'Rotate instance'
47
+ }
@@ -1,6 +1,7 @@
1
1
  const { getErrorMessage } = require('../../error')
2
2
  const asciiArt = require('ascii-art')
3
- const { getApi, waitForState } = require('../../utils')
3
+ const { waitForState } = require('../../utils')
4
+ const InstanceCLI = require('../../clients/Instance')
4
5
 
5
6
  async function builder (yargs) {
6
7
  yargs.positional('instanceId', {
@@ -17,25 +18,33 @@ async function builder (yargs) {
17
18
  type: 'boolean',
18
19
  describe: 'Wait for the instance to be fully started'
19
20
  })
21
+ .option('paused', {
22
+ type: 'boolean',
23
+ describe: 'Start instance in a paused state'
24
+ })
25
+ .option('sockcap', {
26
+ type: 'boolean',
27
+ describe: 'Start sockcap add-on if loaded'
28
+ })
20
29
  }
21
30
 
22
31
  async function handler (argv) {
23
- const api = await getApi()
24
- const instanceId = argv.instanceId
32
+ const { sockcap, paused } = argv
33
+ const instance = new InstanceCLI(argv)
25
34
  try {
26
- await api.v1StartInstance(instanceId)
35
+ await instance.start({ sockcap, paused })
27
36
  if (argv.wait) {
28
37
  // Wait 60 attempts for the instance to be created (5 minutes)
29
- await waitForState(instanceId, api.v1GetInstance.bind(api), 'on', 60)
38
+ await waitForState(argv.instanceId, instance.info.bind(instance), 'on', 60)
30
39
  }
31
40
  console.log(
32
- asciiArt.style(`Successfully started instance ${instanceId}`, 'green', true)
41
+ asciiArt.style(`Successfully started instance ${argv.instanceId}`, 'green', true)
33
42
  )
34
43
  } catch (error) {
35
44
  const errorMessage = getErrorMessage(error)
36
- console.error(`Failed to start ${instanceId}: ${errorMessage}`)
45
+ console.error(`Failed to start ${argv.instance}: ${errorMessage}`)
37
46
 
38
- if (argv.detailedErrors) {
47
+ if (argv.verbose) {
39
48
  console.log(error)
40
49
  }
41
50
  }
@@ -44,6 +53,6 @@ async function handler (argv) {
44
53
  module.exports = {
45
54
  builder,
46
55
  handler,
47
- command: 'start <instanceId> [wait]',
56
+ command: 'start <instanceId> [--wait] [--verbose] [--paused] [--sockcap]',
48
57
  describe: 'Start instance'
49
58
  }
@@ -1,5 +1,5 @@
1
1
  const log = require('../../logging')
2
- const InstanceCLI = require('./Instance')
2
+ const InstanceCLI = require('../../clients/Instance')
3
3
  const { getErrorMessage } = require('../../error')
4
4
  const { waitForState, waitForIntervalMilliSec } = require('../../utils')
5
5
 
@@ -1,4 +1,4 @@
1
- const TrialAccount = require('../TrialAccount')
1
+ const TrialAccount = require('../clients/TrialAccount')
2
2
  const { makePassword, validateNonEmpty } = require('../utils')
3
3
  const { getErrorMessage } = require('../error')
4
4
  const log = require('../logging')
@@ -7,7 +7,7 @@ async function builder (yargs) {
7
7
  yargs.options({
8
8
  endpoint: {
9
9
  type: 'input',
10
- describe: 'Server endpoint e.g. https://app.ci-4.corellium.co',
10
+ describe: 'Server endpoint e.g. https://app.corellium.co',
11
11
  demandOption: true,
12
12
  string: true,
13
13
  validateNonEmpty
@@ -1,4 +1,4 @@
1
- const WebPlayer = require('./WebPlayer')
1
+ const Webplayer = require('../../clients/Webplayer')
2
2
  const log = require('../../logging')
3
3
  const { displayTable } = require('../../table')
4
4
  const { validateNonEmpty } = require('../../utils')
@@ -49,13 +49,13 @@ async function builder (yargs) {
49
49
 
50
50
  async function handler (argv) {
51
51
  try {
52
- const webplayer = new WebPlayer(argv)
52
+ const webplayer = new Webplayer(argv)
53
53
  const session = await webplayer.createSession()
54
- // log.info('Created web player session')
54
+ // log.info('Created Webplayer session')
55
55
  await displayTable(argv.format || 'json', [session], ['identifier', 'token', 'expiration', 'features'])
56
56
  } catch (err) {
57
57
  const errorMessage = getErrorMessage(err)
58
- log.error(`Create web player session failed: ${errorMessage}`)
58
+ log.error(`Create Webplayer session failed: ${errorMessage}`)
59
59
  if (argv.detailedErrors) { log.error(err) }
60
60
  }
61
61
  }
@@ -64,5 +64,5 @@ module.exports = {
64
64
  builder,
65
65
  handler,
66
66
  command: 'create',
67
- describe: 'Create a web player session returning the URL and token'
67
+ describe: 'Create a Webplayer session returning the URL and token'
68
68
  }
@@ -1,4 +1,4 @@
1
- const WebPlayer = require('./WebPlayer')
1
+ const Webplayer = require('../../clients/Webplayer')
2
2
  const log = require('../../logging')
3
3
  const { validateNonEmpty } = require('../../utils')
4
4
  const { getErrorMessage } = require('../../error')
@@ -16,7 +16,7 @@ async function builder (yargs) {
16
16
  validateNonEmpty
17
17
  }).option('session', {
18
18
  type: 'input',
19
- describe: 'WebPlayer Session ID',
19
+ describe: 'Webplayer Session ID',
20
20
  demandOption: true,
21
21
  string: true,
22
22
  validateNonEmpty
@@ -26,12 +26,12 @@ async function builder (yargs) {
26
26
  async function handler (argv) {
27
27
  const { session: sessionId } = argv
28
28
  try {
29
- const webplayer = new WebPlayer(argv)
29
+ const webplayer = new Webplayer(argv)
30
30
  await webplayer.destroySession(sessionId)
31
- log.info(`Destroyed web player session ${sessionId}`)
31
+ log.info(`Destroyed Webplayer session ${sessionId}`)
32
32
  } catch (err) {
33
33
  const errorMessage = getErrorMessage(err)
34
- log.error(`Destroy web player session ${sessionId} failed: ${errorMessage}`)
34
+ log.error(`Destroy Webplayer session ${sessionId} failed: ${errorMessage}`)
35
35
  if (argv.detailedErrors) { log.error(err) }
36
36
  }
37
37
  }
@@ -40,5 +40,5 @@ module.exports = {
40
40
  builder,
41
41
  handler,
42
42
  command: 'destroy',
43
- describe: 'Destroy a web player session'
43
+ describe: 'Destroy a Webplayer session'
44
44
  }
@@ -11,5 +11,5 @@ module.exports = {
11
11
  builder: yargs => (_yargs = yargs) && yargs.command(subcommands).scriptName(''),
12
12
  handler: argv => !argv[1] && _yargs.showHelp(),
13
13
  command: 'webplayer',
14
- describe: 'Web Player related commands'
14
+ describe: 'Webplayer related commands'
15
15
  }
@@ -1,4 +1,4 @@
1
- const WebPlayer = require('./WebPlayer')
1
+ const Webplayer = require('../../clients/Webplayer')
2
2
  const log = require('../../logging')
3
3
  const { displayTable } = require('../../table')
4
4
  const { validateNonEmpty } = require('../../utils')
@@ -17,7 +17,7 @@ async function builder (yargs) {
17
17
  validateNonEmpty
18
18
  }).option('session', {
19
19
  type: 'input',
20
- describe: 'Web Player Session ID',
20
+ describe: 'Webplayer Session ID',
21
21
  demandOption: false,
22
22
  string: true
23
23
  }).option('format', {
@@ -30,7 +30,7 @@ async function builder (yargs) {
30
30
 
31
31
  async function handler (argv) {
32
32
  try {
33
- const webplayer = new WebPlayer(argv)
33
+ const webplayer = new Webplayer(argv)
34
34
  const result = await webplayer.listSessions(argv.session)
35
35
  if (!result.length && argv.format !== 'json') {
36
36
  console.error('Found no matching Webplayer sessions')
@@ -63,5 +63,5 @@ module.exports = {
63
63
  builder,
64
64
  handler,
65
65
  command: 'list',
66
- describe: 'List web player sessions for a project'
66
+ describe: 'List Webplayer sessions for a project'
67
67
  }
@@ -1,4 +1,4 @@
1
- const WebPlayer = require('./WebPlayer')
1
+ const Webplayer = require('../../clients/Webplayer')
2
2
  const log = require('../../logging')
3
3
  const { validateNonEmpty } = require('../../utils')
4
4
  const { getErrorMessage } = require('../../error')
@@ -16,7 +16,7 @@ async function builder (yargs) {
16
16
  validateNonEmpty
17
17
  }).option('session', {
18
18
  type: 'input',
19
- describe: 'WebPlayer Session ID',
19
+ describe: 'Webplayer Session ID',
20
20
  demandOption: true,
21
21
  string: true,
22
22
  validateNonEmpty
@@ -26,12 +26,12 @@ async function builder (yargs) {
26
26
  async function handler (argv) {
27
27
  const { session: sessionId } = argv
28
28
  try {
29
- const webplayer = new WebPlayer(argv)
29
+ const webplayer = new Webplayer(argv)
30
30
  await webplayer.login(sessionId)
31
- log.info(`Login to Web Player session ${sessionId} successful`)
31
+ log.info(`Login to Webplayer session ${sessionId} successful`)
32
32
  } catch (err) {
33
33
  const errorMessage = getErrorMessage(err)
34
- log.error(`Login to Web Player session ${sessionId} failed: ${errorMessage}`)
34
+ log.error(`Login to Webplayer session ${sessionId} failed: ${errorMessage}`)
35
35
  if (argv.detailedErrors) {
36
36
  log.error(err)
37
37
  }
@@ -42,5 +42,5 @@ module.exports = {
42
42
  builder,
43
43
  handler,
44
44
  command: 'login',
45
- describe: 'Log CLI into a web player session'
45
+ describe: 'Log CLI into a Webplayer session'
46
46
  }
package/src/upload.js CHANGED
@@ -19,13 +19,14 @@ class File {
19
19
  }
20
20
  }
21
21
 
22
- async function uploadFile (token, url, filePath, progress) {
22
+ async function uploadFile (token, url, filePath, progress, headers) {
23
23
  return new Promise((resolve, reject) => {
24
24
  const r = new Resumable({
25
25
  target: url,
26
26
  headers: {
27
27
  authorization: token,
28
- 'x-corellium-image-encoding': 'plain'
28
+ 'x-corellium-image-encoding': 'plain',
29
+ ...headers
29
30
  },
30
31
  uploadMethod: 'PUT',
31
32
  chunkSize: 5 * 1024 * 1024,
@@ -64,7 +65,7 @@ async function uploadFile (token, url, filePath, progress) {
64
65
  })
65
66
  }
66
67
 
67
- async function uploadWithProgress (url, token, file, multi) {
68
+ async function uploadWithProgress (url, token, file, multi, headers) {
68
69
  const fileName = basename(file)
69
70
 
70
71
  const bar = multi.newBar(` loading ${fileName} [:bar] :percent`, {
@@ -82,7 +83,8 @@ async function uploadWithProgress (url, token, file, multi) {
82
83
  (progress) => {
83
84
  const ticks = Math.ceil(100 * progress)
84
85
  bar.tick(ticks)
85
- }
86
+ },
87
+ headers
86
88
  )
87
89
  return res
88
90
  } catch (error) {
package/src/utils.js CHANGED
@@ -63,6 +63,23 @@ const waitForState = async (instanceId, fn, state, attempts = 20, reporterFn = n
63
63
  }
64
64
  }
65
65
 
66
+ const waitForTaskState = async (instanceId, fn, taskState, attempts = 20) => {
67
+ let response = await fn(instanceId)
68
+
69
+ while (response.taskState !== taskState) {
70
+ if (response.state === 'error') {
71
+ throw new Error(response.error)
72
+ }
73
+
74
+ if (attempts <= 0) {
75
+ throw new Error(`Timed out waiting for taskState ${taskState}`)
76
+ }
77
+ --attempts
78
+ await sleep(waitForIntervalMilliSec)
79
+ response = await fn(instanceId)
80
+ }
81
+ }
82
+
66
83
  async function getApi () {
67
84
  const profile = readProfile()
68
85
  const api = new CorelliumClient.CorelliumApi()
@@ -145,6 +162,7 @@ module.exports = {
145
162
  planOptions,
146
163
  makePassword,
147
164
  waitForState,
165
+ waitForTaskState,
148
166
  getApi,
149
167
  getAxios,
150
168
  validateNonEmpty,
@@ -1,45 +0,0 @@
1
- const axios = require('axios').default
2
- const { readProfile } = require('../profile')
3
-
4
- /**
5
- * Client Base class
6
- */
7
- class Client {
8
- constructor (argv) {
9
- this.argv = Object.assign({}, argv)
10
- this.project = argv.project || argv.projectId
11
- this.instance = argv.instance || argv.instanceId
12
- this._profile = readProfile()
13
- }
14
-
15
- /**
16
- * Perform HTTP transaction
17
- * @protected
18
- * @param {string} method
19
- * @param {string} path
20
- * @param {object} data
21
- * @returns {Promise<any>}
22
- */
23
- _fetch = async (method, path, data) => {
24
- const headers = {
25
- Authorization: `Bearer ${this._profile.token}`,
26
- 'User-Agent': `corellium-cli v${require('../../package.json').version}`
27
- }
28
- let response
29
- switch (method) {
30
- case 'GET':
31
- response = await axios.get(`${this._profile.endpoint}/api/${path}`, { headers })
32
- break
33
- case 'POST':
34
- response = await axios.post(`${this._profile.endpoint}/api/${path}`, data, { headers })
35
- break
36
- case 'DELETE':
37
- response = await axios.delete(`${this._profile.endpoint}/api/${path}`, { headers })
38
- break
39
- }
40
- if (response.status >= 400) { throw new Error(response.statusText) }
41
- return response.data || {}
42
- }
43
- }
44
-
45
- module.exports = Client
@@ -1,27 +0,0 @@
1
- const Client = require('../Client')
2
-
3
- /**
4
- * Instance class
5
- */
6
- class InstanceCLI extends Client {
7
- instanceBasePath = () => `v1/instances/${this.instance}`
8
-
9
- /**
10
- * Upgrade iOS version on instance
11
- * @returns {Promise<any>}
12
- */
13
- upgrade = async (options) => {
14
- const { os, osbuild } = options
15
- return await this._fetch('POST', `${this.instanceBasePath()}/upgrade`, { os, osbuild })
16
- }
17
-
18
- /**
19
- * Fetch instance information
20
- * @returns {Promise<any>}
21
- */
22
- info = async () => {
23
- return await this._fetch('GET', `${this.instanceBasePath()}`, {})
24
- }
25
- }
26
-
27
- module.exports = InstanceCLI