UNPKG

12.3 kBJavaScriptView Raw
1'use strict'
2
3var cst = require('../../../constants.js');
4const chalk = require('chalk');
5const path = require('path');
6const fs = require('fs');
7const Table = require('cli-tableau');
8const pkg = require('../../../package.json')
9const IOAPI = require('@pm2/js-api')
10const promptly = require('promptly')
11var CLIStrategy = require('./auth-strategies/CliAuth')
12var WebStrategy = require('./auth-strategies/WebAuth')
13const exec = require('child_process').exec
14
15const OAUTH_CLIENT_ID_WEB = '138558311'
16const OAUTH_CLIENT_ID_CLI = '0943857435'
17
18module.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}