1 | 'use strict'
|
2 |
|
3 | var cst = require('../../../constants.js');
|
4 | const chalk = require('chalk');
|
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 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 | this.io = new IOAPI().use(this._strategy)
|
69 | }
|
70 |
|
71 | static launch (command, opts) {
|
72 |
|
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 |
|
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 |
|
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 |
|
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 |
|
277 |
|
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 |
|
319 |
|
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 | }
|