@corellium/corellium-cli 1.0.9 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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