@zappinginc/zm2 6.0.14

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.
Files changed (133) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/.gitattributes +4 -0
  3. package/.mocharc.js +14 -0
  4. package/CHANGELOG.md +2416 -0
  5. package/CLAUDE.md +84 -0
  6. package/CONTRIBUTING.md +124 -0
  7. package/GNU-AGPL-3.0.txt +665 -0
  8. package/LICENSE +1 -0
  9. package/README.md +248 -0
  10. package/bin/zm2 +3 -0
  11. package/bin/zm2-dev +3 -0
  12. package/bin/zm2-docker +3 -0
  13. package/bin/zm2-runtime +3 -0
  14. package/bin/zm2-windows +3 -0
  15. package/bin/zm2.ps1 +3 -0
  16. package/bun.lock +421 -0
  17. package/constants.js +114 -0
  18. package/index.js +13 -0
  19. package/lib/API/Configuration.js +212 -0
  20. package/lib/API/Containerizer.js +335 -0
  21. package/lib/API/Dashboard.js +459 -0
  22. package/lib/API/Deploy.js +117 -0
  23. package/lib/API/Extra.js +775 -0
  24. package/lib/API/ExtraMgmt/Docker.js +30 -0
  25. package/lib/API/Log.js +315 -0
  26. package/lib/API/LogManagement.js +371 -0
  27. package/lib/API/Modules/LOCAL.js +122 -0
  28. package/lib/API/Modules/Modularizer.js +148 -0
  29. package/lib/API/Modules/NPM.js +445 -0
  30. package/lib/API/Modules/TAR.js +362 -0
  31. package/lib/API/Modules/flagExt.js +46 -0
  32. package/lib/API/Modules/index.js +120 -0
  33. package/lib/API/Monit.js +247 -0
  34. package/lib/API/Serve.js +343 -0
  35. package/lib/API/Startup.js +629 -0
  36. package/lib/API/UX/helpers.js +213 -0
  37. package/lib/API/UX/index.js +9 -0
  38. package/lib/API/UX/pm2-describe.js +193 -0
  39. package/lib/API/UX/pm2-ls-minimal.js +31 -0
  40. package/lib/API/UX/pm2-ls.js +483 -0
  41. package/lib/API/Version.js +382 -0
  42. package/lib/API/interpreter.json +12 -0
  43. package/lib/API/pm2-plus/PM2IO.js +372 -0
  44. package/lib/API/pm2-plus/auth-strategies/CliAuth.js +288 -0
  45. package/lib/API/pm2-plus/auth-strategies/WebAuth.js +187 -0
  46. package/lib/API/pm2-plus/helpers.js +97 -0
  47. package/lib/API/pm2-plus/link.js +126 -0
  48. package/lib/API/pm2-plus/pres/motd +16 -0
  49. package/lib/API/pm2-plus/pres/motd.update +26 -0
  50. package/lib/API/pm2-plus/pres/welcome +28 -0
  51. package/lib/API/pm2-plus/process-selector.js +52 -0
  52. package/lib/API/schema.json +379 -0
  53. package/lib/API.js +1931 -0
  54. package/lib/Client.js +776 -0
  55. package/lib/Common.js +911 -0
  56. package/lib/Configuration.js +304 -0
  57. package/lib/Daemon.js +456 -0
  58. package/lib/Event.js +37 -0
  59. package/lib/God/ActionMethods.js +909 -0
  60. package/lib/God/ClusterMode.js +97 -0
  61. package/lib/God/ForkMode.js +297 -0
  62. package/lib/God/Methods.js +265 -0
  63. package/lib/God/Reload.js +240 -0
  64. package/lib/God.js +632 -0
  65. package/lib/HttpInterface.js +76 -0
  66. package/lib/ProcessContainer.js +305 -0
  67. package/lib/ProcessContainerBun.js +360 -0
  68. package/lib/ProcessContainerFork.js +42 -0
  69. package/lib/ProcessContainerForkBun.js +33 -0
  70. package/lib/ProcessUtils.js +55 -0
  71. package/lib/TreeKill.js +118 -0
  72. package/lib/Utility.js +430 -0
  73. package/lib/VersionCheck.js +46 -0
  74. package/lib/Watcher.js +117 -0
  75. package/lib/Worker.js +169 -0
  76. package/lib/binaries/CLI.js +1041 -0
  77. package/lib/binaries/DevCLI.js +183 -0
  78. package/lib/binaries/Runtime.js +101 -0
  79. package/lib/binaries/Runtime4Docker.js +192 -0
  80. package/lib/completion.js +229 -0
  81. package/lib/completion.sh +40 -0
  82. package/lib/motd +36 -0
  83. package/lib/templates/Dockerfiles/Dockerfile-java.tpl +7 -0
  84. package/lib/templates/Dockerfiles/Dockerfile-nodejs.tpl +8 -0
  85. package/lib/templates/Dockerfiles/Dockerfile-ruby.tpl +7 -0
  86. package/lib/templates/ecosystem-es.tpl +24 -0
  87. package/lib/templates/ecosystem-simple-es.tpl +8 -0
  88. package/lib/templates/ecosystem-simple.tpl +6 -0
  89. package/lib/templates/ecosystem.tpl +22 -0
  90. package/lib/templates/init-scripts/launchd.tpl +35 -0
  91. package/lib/templates/init-scripts/openrc.tpl +52 -0
  92. package/lib/templates/init-scripts/pm2-init-amazon.sh +86 -0
  93. package/lib/templates/init-scripts/rcd-openbsd.tpl +41 -0
  94. package/lib/templates/init-scripts/rcd.tpl +44 -0
  95. package/lib/templates/init-scripts/smf.tpl +43 -0
  96. package/lib/templates/init-scripts/systemd-online.tpl +22 -0
  97. package/lib/templates/init-scripts/systemd.tpl +22 -0
  98. package/lib/templates/init-scripts/upstart.tpl +103 -0
  99. package/lib/templates/logrotate.d/pm2 +10 -0
  100. package/lib/templates/sample-apps/http-server/README.md +14 -0
  101. package/lib/templates/sample-apps/http-server/api.js +9 -0
  102. package/lib/templates/sample-apps/http-server/ecosystem.config.js +14 -0
  103. package/lib/templates/sample-apps/http-server/package.json +11 -0
  104. package/lib/templates/sample-apps/pm2-plus-metrics-actions/README.md +45 -0
  105. package/lib/templates/sample-apps/pm2-plus-metrics-actions/custom-metrics.js +66 -0
  106. package/lib/templates/sample-apps/pm2-plus-metrics-actions/ecosystem.config.js +12 -0
  107. package/lib/templates/sample-apps/pm2-plus-metrics-actions/package.json +11 -0
  108. package/lib/templates/sample-apps/python-app/README.md +4 -0
  109. package/lib/templates/sample-apps/python-app/echo.py +7 -0
  110. package/lib/templates/sample-apps/python-app/ecosystem.config.js +12 -0
  111. package/lib/templates/sample-apps/python-app/package.json +11 -0
  112. package/lib/tools/Config.js +248 -0
  113. package/lib/tools/IsAbsolute.js +20 -0
  114. package/lib/tools/copydirSync.js +101 -0
  115. package/lib/tools/deleteFolderRecursive.js +19 -0
  116. package/lib/tools/find-package-json.js +74 -0
  117. package/lib/tools/fmt.js +72 -0
  118. package/lib/tools/isbinaryfile.js +94 -0
  119. package/lib/tools/json5.js +752 -0
  120. package/lib/tools/open.js +63 -0
  121. package/lib/tools/passwd.js +58 -0
  122. package/lib/tools/promise.min.js +1 -0
  123. package/lib/tools/sexec.js +55 -0
  124. package/lib/tools/treeify.js +113 -0
  125. package/lib/tools/which.js +120 -0
  126. package/lib/tools/xdg-open +861 -0
  127. package/package.json +219 -0
  128. package/paths.js +93 -0
  129. package/pm2 +11 -0
  130. package/preinstall.js +24 -0
  131. package/run.sh +9 -0
  132. package/types/index.d.ts +722 -0
  133. package/types/tsconfig.json +14 -0
@@ -0,0 +1,372 @@
1
+ 'use strict'
2
+
3
+ var cst = require('../../../constants.js');
4
+ const chalk = require('ansis');
5
+ const path = require('path');
6
+ const fs = require('fs');
7
+ const Table = require('cli-tableau');
8
+ const pkg = require('../../../package.json')
9
+ const IOAPI = require('@pm2/js-api')
10
+ const promptly = require('promptly')
11
+ var CLIStrategy = require('./auth-strategies/CliAuth')
12
+ var WebStrategy = require('./auth-strategies/WebAuth')
13
+ const exec = require('child_process').exec
14
+
15
+ const OAUTH_CLIENT_ID_WEB = '138558311'
16
+ const OAUTH_CLIENT_ID_CLI = '0943857435'
17
+
18
+ module.exports = class PM2ioHandler {
19
+
20
+ static usePM2Client (instance) {
21
+ this.pm2 = instance
22
+ }
23
+
24
+ static strategy () {
25
+ switch (process.platform) {
26
+ case 'darwin': {
27
+ return new WebStrategy({
28
+ client_id: OAUTH_CLIENT_ID_WEB
29
+ })
30
+ }
31
+ case 'win32': {
32
+ return new WebStrategy({
33
+ client_id: OAUTH_CLIENT_ID_WEB
34
+ })
35
+ }
36
+ case 'linux': {
37
+ const isDesktop = process.env.XDG_CURRENT_DESKTOP || process.env.XDG_SESSION_DESKTOP || process.env.DISPLAY
38
+ const isSSH = process.env.SSH_TTY || process.env.SSH_CONNECTION
39
+ if (isDesktop && !isSSH) {
40
+ return new WebStrategy({
41
+ client_id: OAUTH_CLIENT_ID_WEB
42
+ })
43
+ } else {
44
+ return new CLIStrategy({
45
+ client_id: OAUTH_CLIENT_ID_CLI
46
+ })
47
+ }
48
+ }
49
+ default: {
50
+ return new CLIStrategy({
51
+ client_id: OAUTH_CLIENT_ID_CLI
52
+ })
53
+ }
54
+ }
55
+ }
56
+
57
+ static init () {
58
+ this._strategy = this.strategy()
59
+ /**
60
+ * If you are using a local backend you should give those options :
61
+ * {
62
+ * services: {
63
+ * API: 'http://localhost:3000',
64
+ * OAUTH: 'http://localhost:3100'
65
+ * }
66
+ * }
67
+ */
68
+ this.io = new IOAPI().use(this._strategy)
69
+ }
70
+
71
+ static launch (command, opts) {
72
+ // first init the strategy and the io client
73
+ this.init()
74
+
75
+ switch (command) {
76
+ case 'connect' :
77
+ case 'login' :
78
+ case 'register' :
79
+ case undefined :
80
+ case 'authenticate' : {
81
+ this.authenticate()
82
+ break
83
+ }
84
+ case 'validate' : {
85
+ this.validateAccount(opts)
86
+ break
87
+ }
88
+ case 'help' :
89
+ case 'welcome': {
90
+ var dt = fs.readFileSync(path.join(__dirname, './pres/welcome'));
91
+ console.log(dt.toString());
92
+ return process.exit(0)
93
+ }
94
+ case 'logout': {
95
+ this._strategy.isAuthenticated().then(isConnected => {
96
+ // try to kill the agent anyway
97
+ this.pm2.killAgent(err => {})
98
+
99
+ if (isConnected === false) {
100
+ console.log(`${cst.PM2_IO_MSG} Already disconnected`)
101
+ return process.exit(0)
102
+ }
103
+
104
+ this._strategy._retrieveTokens((err, tokens) => {
105
+ if (err) {
106
+ console.log(`${cst.PM2_IO_MSG} Successfully disconnected`)
107
+ return process.exit(0)
108
+ }
109
+ this._strategy.deleteTokens(this.io).then(_ => {
110
+ console.log(`${cst.PM2_IO_MSG} Successfully disconnected`)
111
+ return process.exit(0)
112
+ }).catch(err => {
113
+ console.log(`${cst.PM2_IO_MSG_ERR} Unexpected error: ${err.message}`)
114
+ return process.exit(1)
115
+ })
116
+ })
117
+ }).catch(err => {
118
+ console.error(`${cst.PM2_IO_MSG_ERR} Failed to logout: ${err.message}`)
119
+ console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`)
120
+ })
121
+ break
122
+ }
123
+ case 'create': {
124
+ this._strategy.isAuthenticated().then(res => {
125
+ // if the user isn't authenticated, we make them do the whole flow
126
+ if (res !== true) {
127
+ this.authenticate()
128
+ } else {
129
+ this.createBucket(this.createBucketHandler.bind(this))
130
+ }
131
+ }).catch(err => {
132
+ console.error(`${cst.PM2_IO_MSG_ERR} Failed to create to the bucket: ${err.message}`)
133
+ console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`)
134
+ })
135
+ break
136
+ }
137
+ case 'web': {
138
+ this._strategy.isAuthenticated().then(res => {
139
+ // if the user isn't authenticated, we make them do the whole flow
140
+ if (res === false) {
141
+ console.error(`${cst.PM2_IO_MSG_ERR} You need to be authenticated to do that, please use: pm2 plus login`)
142
+ return process.exit(1)
143
+ }
144
+ this._strategy._retrieveTokens(() => {
145
+ return this.openUI()
146
+ })
147
+ }).catch(err => {
148
+ console.error(`${cst.PM2_IO_MSG_ERR} Failed to open the UI: ${err.message}`)
149
+ console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`)
150
+ })
151
+ break
152
+ }
153
+ default : {
154
+ console.log(`${cst.PM2_IO_MSG_ERR} Invalid command ${command}, available : login,register,validate,connect or web`)
155
+ process.exit(1)
156
+ }
157
+ }
158
+ }
159
+
160
+ static openUI () {
161
+ this.io.bucket.retrieveAll().then(res => {
162
+ const buckets = res.data
163
+
164
+ if (buckets.length === 0) {
165
+ return this.createBucket((err, bucket) => {
166
+ if (err) {
167
+ console.error(`${cst.PM2_IO_MSG_ERR} Failed to connect to the bucket: ${err.message}`)
168
+ if (bucket) {
169
+ console.error(`${cst.PM2_IO_MSG_ERR} You can retry using: pm2 plus link ${bucket.secret_id} ${bucket.public_id}`)
170
+ }
171
+ console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`)
172
+ return process.exit(0)
173
+ }
174
+ const targetURL = `https://app.pm2.io/#/bucket/${bucket._id}`
175
+ console.log(`${cst.PM2_IO_MSG} Please follow the popup or go to this URL :`, '\n', ' ', targetURL)
176
+ this.open(targetURL)
177
+ return process.exit(0)
178
+ })
179
+ }
180
+
181
+ var table = new Table({
182
+ style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true},
183
+ head : ['Bucket name', 'Plan type']
184
+ })
185
+
186
+ buckets.forEach(function(bucket) {
187
+ table.push([bucket.name, bucket.credits.offer_type])
188
+ })
189
+ console.log(table.toString())
190
+ console.log(`${cst.PM2_IO_MSG} If you don't want to open the UI to a bucket, type 'none'`)
191
+
192
+ const choices = buckets.map(bucket => bucket.name)
193
+ choices.push('none')
194
+
195
+ promptly.choose(`${cst.PM2_IO_MSG} Type the name of the bucket you want to connect to :`, choices, (err, value) => {
196
+ if (value === 'none') process.exit(0)
197
+
198
+ const bucket = buckets.find(bucket => bucket.name === value)
199
+ if (bucket === undefined) return process.exit(0)
200
+
201
+ const targetURL = `https://app.pm2.io/#/bucket/${bucket._id}`
202
+ console.log(`${cst.PM2_IO_MSG} Please follow the popup or go to this URL :`, '\n', ' ', targetURL)
203
+ this.open(targetURL)
204
+ return process.exit(0)
205
+ })
206
+ })
207
+ }
208
+
209
+ static validateAccount (token) {
210
+ this.io.auth.validEmail(token)
211
+ .then(res => {
212
+ console.log(`${cst.PM2_IO_MSG} Email succesfully validated.`)
213
+ console.log(`${cst.PM2_IO_MSG} You can now proceed and use: pm2 plus connect`)
214
+ return process.exit(0)
215
+ }).catch(err => {
216
+ if (err.status === 401) {
217
+ console.error(`${cst.PM2_IO_MSG_ERR} Invalid token`)
218
+ return process.exit(1)
219
+ } else if (err.status === 301) {
220
+ console.log(`${cst.PM2_IO_MSG} Email succesfully validated.`)
221
+ console.log(`${cst.PM2_IO_MSG} You can now proceed and use: pm2 plus connect`)
222
+ return process.exit(0)
223
+ }
224
+ const msg = err.data ? err.data.error_description || err.data.msg : err.message
225
+ console.error(`${cst.PM2_IO_MSG_ERR} Failed to validate your email: ${msg}`)
226
+ console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`)
227
+ return process.exit(1)
228
+ })
229
+ }
230
+
231
+ static createBucketHandler (err, bucket) {
232
+ if (err) {
233
+ console.trace(`${cst.PM2_IO_MSG_ERR} Failed to connect to the bucket: ${err.message}`)
234
+ if (bucket) {
235
+ console.error(`${cst.PM2_IO_MSG_ERR} You can retry using: pm2 plus link ${bucket.secret_id} ${bucket.public_id}`)
236
+ }
237
+ console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`)
238
+ return process.exit(0)
239
+ }
240
+ if (bucket === undefined) {
241
+ return process.exit(0)
242
+ }
243
+ console.log(`${cst.PM2_IO_MSG} Successfully connected to bucket ${bucket.name}`)
244
+ var targetURL = `https://app.pm2.io/#/bucket/${bucket._id}`
245
+ console.log(`${cst.PM2_IO_MSG} You can use the web interface over there: ${targetURL}`)
246
+ this.open(targetURL)
247
+ return process.exit(0)
248
+ }
249
+
250
+ static createBucket (cb) {
251
+ console.log(`${cst.PM2_IO_MSG} By default we allow you to trial PM2 Plus for 14 days without any credit card.`)
252
+
253
+ this.io.bucket.create({
254
+ name: 'PM2 Plus Monitoring'
255
+ }).then(res => {
256
+ const bucket = res.data.bucket
257
+
258
+ console.log(`${cst.PM2_IO_MSG} Successfully created the bucket`)
259
+ this.pm2.link({
260
+ public_key: bucket.public_id,
261
+ secret_key: bucket.secret_id,
262
+ pm2_version: pkg.version
263
+ }, (err) => {
264
+ if (err) {
265
+ return cb(new Error('Failed to connect your local PM2 to your bucket'), bucket)
266
+ } else {
267
+ return cb(null, bucket)
268
+ }
269
+ })
270
+ }).catch(err => {
271
+ return cb(new Error(`Failed to create a bucket: ${err.message}`))
272
+ })
273
+ }
274
+
275
+ /**
276
+ * Connect the local agent to a specific bucket
277
+ * @param {Function} cb
278
+ */
279
+ static connectToBucket (cb) {
280
+ this.io.bucket.retrieveAll().then(res => {
281
+ const buckets = res.data
282
+
283
+ if (buckets.length === 0) {
284
+ return this.createBucket(cb)
285
+ }
286
+
287
+ var table = new Table({
288
+ style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true},
289
+ head : ['Bucket name', 'Plan type']
290
+ })
291
+
292
+ buckets.forEach(function(bucket) {
293
+ table.push([bucket.name, bucket.payment.offer_type])
294
+ })
295
+ console.log(table.toString())
296
+ console.log(`${cst.PM2_IO_MSG} If you don't want to connect to a bucket, type 'none'`)
297
+
298
+ const choices = buckets.map(bucket => bucket.name)
299
+ choices.push('none')
300
+
301
+ promptly.choose(`${cst.PM2_IO_MSG} Type the name of the bucket you want to connect to :`, choices, (err, value) => {
302
+ if (value === 'none') return cb()
303
+
304
+ const bucket = buckets.find(bucket => bucket.name === value)
305
+ if (bucket === undefined) return cb()
306
+ this.pm2.link({
307
+ public_key: bucket.public_id,
308
+ secret_key: bucket.secret_id,
309
+ pm2_version: pkg.version
310
+ }, (err) => {
311
+ return err ? cb(err) : cb(null, bucket)
312
+ })
313
+ })
314
+ })
315
+ }
316
+
317
+ /**
318
+ * Authenticate the user with either of the strategy
319
+ * @param {Function} cb
320
+ */
321
+ static authenticate () {
322
+ this._strategy._retrieveTokens((err, tokens) => {
323
+ if (err) {
324
+ const msg = err.data ? err.data.error_description || err.data.msg : err.message
325
+ console.log(`${cst.PM2_IO_MSG_ERR} Unexpected error : ${msg}`)
326
+ return process.exit(1)
327
+ }
328
+ console.log(`${cst.PM2_IO_MSG} Successfully authenticated`)
329
+ this.io.user.retrieve().then(res => {
330
+ const user = res.data
331
+
332
+ this.io.user.retrieve().then(res => {
333
+ const tmpUser = res.data
334
+ console.log(`${cst.PM2_IO_MSG} Successfully validated`)
335
+ this.connectToBucket(this.createBucketHandler.bind(this))
336
+ })
337
+ })
338
+ })
339
+ }
340
+
341
+ static open (target, appName, callback) {
342
+ let opener
343
+ const escape = function (s) {
344
+ return s.replace(/"/g, '\\"')
345
+ }
346
+
347
+ if (typeof (appName) === 'function') {
348
+ callback = appName
349
+ appName = null
350
+ }
351
+
352
+ switch (process.platform) {
353
+ case 'darwin': {
354
+ opener = appName ? `open -a "${escape(appName)}"` : `open`
355
+ break
356
+ }
357
+ case 'win32': {
358
+ opener = appName ? `start "" ${escape(appName)}"` : `start ""`
359
+ break
360
+ }
361
+ default: {
362
+ opener = appName ? escape(appName) : `xdg-open`
363
+ break
364
+ }
365
+ }
366
+
367
+ if (process.env.SUDO_USER) {
368
+ opener = 'sudo -u ' + process.env.SUDO_USER + ' ' + opener
369
+ }
370
+ return exec(`${opener} "${escape(target)}"`, callback)
371
+ }
372
+ }
@@ -0,0 +1,288 @@
1
+ 'use strict'
2
+
3
+ const AuthStrategy = require('@pm2/js-api/src/auth_strategies/strategy')
4
+ const querystring = require('querystring');
5
+
6
+ const http = require('http')
7
+ const fs = require('fs')
8
+ const url = require('url')
9
+ const exec = require('child_process').exec
10
+ const tryEach = require('async/tryEach')
11
+ const path = require('path')
12
+ const os = require('os')
13
+ const needle = require('needle')
14
+ const chalk = require('ansis')
15
+ const cst = require('../../../../constants.js')
16
+ const promptly = require('promptly')
17
+
18
+ module.exports = class CliStrategy extends AuthStrategy {
19
+ // the client will try to call this but we handle this part ourselves
20
+ retrieveTokens (km, cb) {
21
+ this.authenticated = false
22
+ this.callback = cb
23
+ this.km = km
24
+ this.BASE_URI = 'https://id.keymetrics.io';
25
+ }
26
+
27
+ // so the cli know if we need to tell user to login/register
28
+ isAuthenticated () {
29
+ return new Promise((resolve, reject) => {
30
+ if (this.authenticated) return resolve(true)
31
+
32
+ let tokensPath = cst.PM2_IO_ACCESS_TOKEN
33
+ fs.readFile(tokensPath, (err, tokens) => {
34
+ if (err && err.code === 'ENOENT') return resolve(false)
35
+ if (err) return reject(err)
36
+
37
+ // verify that the token is valid
38
+ try {
39
+ tokens = JSON.parse(tokens || '{}')
40
+ } catch (err) {
41
+ fs.unlinkSync(tokensPath)
42
+ return resolve(false)
43
+ }
44
+
45
+ // if the refresh tokens is here, the user could be automatically authenticated
46
+ return resolve(typeof tokens.refresh_token === 'string')
47
+ })
48
+ })
49
+ }
50
+
51
+ verifyToken (refresh) {
52
+ return this.km.auth.retrieveToken({
53
+ client_id: this.client_id,
54
+ refresh_token: refresh
55
+ })
56
+ }
57
+
58
+ // called when we are sure the user asked to be logged in
59
+ _retrieveTokens (optionalCallback) {
60
+ const km = this.km
61
+ const cb = this.callback
62
+
63
+ tryEach([
64
+ // try to find the token via the environment
65
+ (next) => {
66
+ if (!process.env.PM2_IO_TOKEN) {
67
+ return next(new Error('No token in env'))
68
+ }
69
+ this.verifyToken(process.env.PM2_IO_TOKEN)
70
+ .then((res) => {
71
+ return next(null, res.data)
72
+ }).catch(next)
73
+ },
74
+ // try to find it in the file system
75
+ (next) => {
76
+ fs.readFile(cst.PM2_IO_ACCESS_TOKEN, (err, tokens) => {
77
+ if (err) return next(err)
78
+ // verify that the token is valid
79
+ tokens = JSON.parse(tokens || '{}')
80
+ if (new Date(tokens.expire_at) > new Date(new Date().toISOString())) {
81
+ return next(null, tokens)
82
+ }
83
+
84
+ this.verifyToken(tokens.refresh_token)
85
+ .then((res) => {
86
+ return next(null, res.data)
87
+ }).catch(next)
88
+ })
89
+ },
90
+ // otherwise make the whole flow
91
+ (next) => {
92
+ return this.authenticate((err, data) => {
93
+ if (err instanceof Error) return next(err)
94
+ // verify that the token is valid
95
+ this.verifyToken(data.refresh_token)
96
+ .then((res) => {
97
+ return next(null, res.data)
98
+ }).catch(next)
99
+ })
100
+ }
101
+ ], (err, result) => {
102
+ // if present run the optional callback
103
+ if (typeof optionalCallback === 'function') {
104
+ optionalCallback(err, result)
105
+ }
106
+
107
+ if (result.refresh_token) {
108
+ this.authenticated = true
109
+ let file = cst.PM2_IO_ACCESS_TOKEN
110
+ fs.writeFile(file, JSON.stringify(result), () => {
111
+ return cb(err, result)
112
+ })
113
+ } else {
114
+ return cb(err, result)
115
+ }
116
+ })
117
+ }
118
+
119
+ authenticate (cb) {
120
+ console.log(`${cst.PM2_IO_MSG} Using non-browser authentication.`)
121
+ promptly.confirm(`${cst.PM2_IO_MSG} Do you have a pm2.io account? (y/n)`, (err, answer) => {
122
+ // Either login or register
123
+ return answer === true ? this.login(cb) : this.register(cb)
124
+ })
125
+ }
126
+
127
+ login (cb) {
128
+ let retry = () => {
129
+ promptly.prompt(`${cst.PM2_IO_MSG} Your username or email: `, (err, username) => {
130
+ if (err) return retry();
131
+
132
+ promptly.password(`${cst.PM2_IO_MSG} Your password: `, { replace : '*' }, (err, password) => {
133
+ if (err) return retry();
134
+
135
+ console.log(`${cst.PM2_IO_MSG} Authenticating ...`)
136
+ this._loginUser({
137
+ username: username,
138
+ password: password
139
+ }, (err, data) => {
140
+ if (err) {
141
+ console.error(`${cst.PM2_IO_MSG_ERR} Failed to authenticate: ${err.message}`)
142
+ return retry()
143
+ }
144
+ return cb(null, data)
145
+ })
146
+ })
147
+ })
148
+ }
149
+
150
+ retry()
151
+ }
152
+
153
+ register (cb) {
154
+ console.log(`${cst.PM2_IO_MSG} No problem ! We just need few informations to create your account`)
155
+
156
+ var retry = () => {
157
+ promptly.prompt(`${cst.PM2_IO_MSG} Please choose an username :`, {
158
+ validator : this._validateUsername,
159
+ retry : true
160
+ }, (err, username) => {
161
+ promptly.prompt(`${cst.PM2_IO_MSG} Please choose an email :`, {
162
+ validator : this._validateEmail,
163
+ retry : true
164
+ },(err, email) => {
165
+ promptly.password(`${cst.PM2_IO_MSG} Please choose a password :`, { replace : '*' }, (err, password) => {
166
+ promptly.confirm(`${cst.PM2_IO_MSG} Do you accept the terms and privacy policy (https://pm2.io/legals/terms_conditions.pdf) ? (y/n)`, (err, answer) => {
167
+ if (err) {
168
+ console.error(chalk.bold.red(err));
169
+ return retry()
170
+ } else if (answer === false) {
171
+ console.error(`${cst.PM2_IO_MSG_ERR} You must accept the terms and privacy policy to contiue.`)
172
+ return retry()
173
+ }
174
+
175
+ this._registerUser({
176
+ email : email,
177
+ password : password,
178
+ username : username
179
+ }, (err, data) => {
180
+ console.log('\n')
181
+ if (err) {
182
+ console.error(`${cst.PM2_IO_MSG_ERR} Unexpect error: ${err.message}`)
183
+ console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`)
184
+ return process.exit(1)
185
+ }
186
+ return cb(undefined, data)
187
+ })
188
+ })
189
+ })
190
+ })
191
+ })
192
+ }
193
+ retry()
194
+ }
195
+
196
+ /**
197
+ * Register function
198
+ * @param opts.username
199
+ * @param opts.password
200
+ * @param opts.email
201
+ */
202
+ _registerUser (opts, cb) {
203
+ const data = Object.assign(opts, {
204
+ password_confirmation: opts.password,
205
+ accept_terms: true
206
+ })
207
+ needle.post(this.BASE_URI + '/api/oauth/register', data, {
208
+ json: true,
209
+ headers: {
210
+ 'X-Register-Provider': 'pm2-register',
211
+ 'x-client-id': this.client_id
212
+ }
213
+ }, function (err, res, body) {
214
+ if (err) return cb(err)
215
+ if (body.email && body.email.message) return cb(new Error(body.email.message))
216
+ if (body.username && body.username.message) return cb(new Error(body.username.message))
217
+ if (!body.access_token) return cb(new Error(body.msg))
218
+
219
+ return cb(null, {
220
+ refresh_token : body.refresh_token.token,
221
+ access_token : body.access_token.token
222
+ })
223
+ });
224
+ }
225
+
226
+ _loginUser (user_info, cb) {
227
+ const URL_AUTH = '/api/oauth/authorize?response_type=token&scope=all&client_id=' +
228
+ this.client_id + '&redirect_uri=http://localhost:43532';
229
+
230
+ needle.get(this.BASE_URI + URL_AUTH, (err, res) => {
231
+ if (err) return cb(err);
232
+
233
+ var cookie = res.cookies;
234
+
235
+ needle.post(this.BASE_URI + '/api/oauth/login', user_info, {
236
+ cookies : cookie
237
+ }, (err, resp, body) => {
238
+ if (err) return cb(err)
239
+ if (resp.statusCode != 200) return cb('Wrong credentials')
240
+
241
+ var location = resp.headers['x-redirect']
242
+
243
+ needle.get(this.BASE_URI + location, {
244
+ cookies : cookie
245
+ }, (err, res) => {
246
+ if (err) return cb(err);
247
+ var refresh_token = querystring.parse(url.parse(res.headers.location).query).access_token;
248
+ needle.post(this.BASE_URI + '/api/oauth/token', {
249
+ client_id : this.client_id,
250
+ grant_type : 'refresh_token',
251
+ refresh_token : refresh_token,
252
+ scope : 'all'
253
+ }, (err, res, body) => {
254
+ if (err) return cb(err)
255
+ return cb(null, body)
256
+ })
257
+ })
258
+ })
259
+ })
260
+ }
261
+
262
+ _validateEmail (email) {
263
+ var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
264
+ if (re.test(email) == false)
265
+ throw new Error('Not an email');
266
+ return email;
267
+ }
268
+
269
+ _validateUsername (value) {
270
+ if (value.length < 6) {
271
+ throw new Error('Min length of 6');
272
+ }
273
+ return value;
274
+ };
275
+
276
+ deleteTokens (km) {
277
+ return new Promise((resolve, reject) => {
278
+ // revoke the refreshToken
279
+ km.auth.revoke()
280
+ .then(res => {
281
+ // remove the token from the filesystem
282
+ let file = cst.PM2_IO_ACCESS_TOKEN
283
+ fs.unlinkSync(file)
284
+ return resolve(res)
285
+ }).catch(reject)
286
+ })
287
+ }
288
+ }