1 | const fetch = require('node-fetch')
|
2 | const fs = require('fs')
|
3 | const os = require('os')
|
4 | const path = require('path')
|
5 | const execa = require('execa')
|
6 | const chalk = require('chalk')
|
7 | const { fetchLatest, updateAvailable } = require('gh-release-fetch')
|
8 | const {
|
9 | NETLIFYDEVLOG,
|
10 |
|
11 | NETLIFYDEVERR
|
12 | } = require('./logo')
|
13 |
|
14 | async function createTunnel(siteId, netlifyApiToken, log) {
|
15 | await installTunnelClient(log)
|
16 |
|
17 | if (!siteId) {
|
18 |
|
19 | console.error(
|
20 | `${NETLIFYDEVERR} Error: no siteId defined, did you forget to run ${chalk.yellow(
|
21 | 'netlify init'
|
22 | )} or ${chalk.yellow('netlify link')}?`
|
23 | )
|
24 | process.exit(1)
|
25 | }
|
26 | log(`${NETLIFYDEVLOG} Creating Live Tunnel for ` + siteId)
|
27 | const url = `https://api.netlify.com/api/v1/live_sessions?site_id=${siteId}`
|
28 |
|
29 | const response = await fetch(url, {
|
30 | method: 'POST',
|
31 | headers: {
|
32 | 'Content-Type': 'application/json',
|
33 | Authorization: `Bearer ${netlifyApiToken}`
|
34 | },
|
35 | body: JSON.stringify({})
|
36 | })
|
37 |
|
38 | const data = await response.json()
|
39 |
|
40 | if (response.status !== 201) {
|
41 | throw new Error(data.message)
|
42 | }
|
43 |
|
44 | return data
|
45 | }
|
46 |
|
47 | async function connectTunnel(session, netlifyApiToken, localPort, log) {
|
48 | const execPath = path.join(os.homedir(), '.netlify', 'tunnel', 'bin', 'live-tunnel-client')
|
49 | const args = ['connect', '-s', session.id, '-t', netlifyApiToken, '-l', localPort]
|
50 | if (process.env.DEBUG) {
|
51 | args.push('-v')
|
52 | log(execPath, args)
|
53 | }
|
54 |
|
55 | const ps = execa(execPath, args, { stdio: 'inherit' })
|
56 | ps.on('close', code => process.exit(code))
|
57 | ps.on('SIGINT', process.exit)
|
58 | ps.on('SIGTERM', process.exit)
|
59 | }
|
60 |
|
61 | async function installTunnelClient(log) {
|
62 | const win = isWindows()
|
63 | const binPath = path.join(os.homedir(), '.netlify', 'tunnel', 'bin')
|
64 | const execName = win ? 'live-tunnel-client.exe' : 'live-tunnel-client'
|
65 | const execPath = path.join(binPath, execName)
|
66 | const newVersion = await fetchTunnelClient(execPath)
|
67 | if (!newVersion) {
|
68 | return
|
69 | }
|
70 |
|
71 | log(`${NETLIFYDEVLOG} Installing Live Tunnel Client`)
|
72 |
|
73 | const platform = win ? 'windows' : process.platform
|
74 | const extension = win ? 'zip' : 'tar.gz'
|
75 | const release = {
|
76 | repository: 'netlify/live-tunnel-client',
|
77 | package: `live-tunnel-client-${platform}-amd64.${extension}`,
|
78 | destination: binPath,
|
79 | extract: true
|
80 | }
|
81 | await fetchLatest(release)
|
82 | }
|
83 |
|
84 | async function fetchTunnelClient(execPath) {
|
85 | if (!execExist(execPath)) {
|
86 | return true
|
87 | }
|
88 |
|
89 | const { stdout } = await execa(execPath, ['version'])
|
90 | if (!stdout) {
|
91 | return false
|
92 | }
|
93 |
|
94 | const match = stdout.match(/^live-tunnel-client\/v?([^\s]+)/)
|
95 | if (!match) {
|
96 | return false
|
97 | }
|
98 |
|
99 | return updateAvailable('netlify/live-tunnel-client', match[1])
|
100 | }
|
101 |
|
102 | function execExist(binPath) {
|
103 | if (!fs.existsSync(binPath)) {
|
104 | return false
|
105 | }
|
106 | const stat = fs.statSync(binPath)
|
107 | return stat && stat.isFile() && isExe(stat.mode, stat.gid, stat.uid)
|
108 | }
|
109 |
|
110 | function isExe(mode, gid, uid) {
|
111 | if (isWindows()) {
|
112 | return true
|
113 | }
|
114 |
|
115 | const isGroup = gid ? process.getgid && gid === process.getgid() : true
|
116 | const isUser = uid ? process.getuid && uid === process.getuid() : true
|
117 |
|
118 | return Boolean(mode & 0o0001 || (mode & 0o0010 && isGroup) || (mode & 0o0100 && isUser))
|
119 | }
|
120 |
|
121 | function isWindows() {
|
122 | return process.platform === 'win32'
|
123 | }
|
124 |
|
125 | module.exports = {
|
126 | createTunnel: createTunnel,
|
127 | connectTunnel: connectTunnel
|
128 | }
|