unplugin-https-reverse-proxy 1.0.0 → 2.0.1

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.
@@ -1,18 +1,19 @@
1
- import { platform } from 'node:os'
2
- import process from 'node:process'
3
- import { chmodSync, createWriteStream, existsSync, mkdirSync, unlinkSync, writeFileSync } from 'node:fs'
4
1
  import { spawn } from 'node:child_process'
2
+ import { chmodSync, createWriteStream, existsSync, mkdirSync, unlinkSync, writeFileSync } from 'node:fs'
5
3
  import { readFile, unlink, writeFile } from 'node:fs/promises'
4
+ import { platform } from 'node:os'
5
+ import { join } from 'node:path'
6
+ import process from 'node:process'
6
7
  import { got } from 'got-cjs'
7
8
  import { HttpProxyAgent } from 'http-proxy-agent'
8
9
  import { HttpsProxyAgent } from 'https-proxy-agent'
9
10
  import * as lockfile from 'proper-lockfile'
10
- import { chmodRecursive, consola, once } from '../utils'
11
11
  import { addHost, removeHost } from '../host'
12
- import { TEMP_DIR, caddyFilePath, caddyLockFilePath, caddyPath, supportList } from './constants'
12
+ import { chmodRecursive, consola, once } from '../utils'
13
+ import { caddyFilePath, caddyLockFilePath, caddyPath, supportList, TEMP_DIR } from './constants'
13
14
  import { kill, logProgress, logProgressOver, tryPort } from './utils'
14
15
 
15
- export async function download() {
16
+ export async function download(): Promise<string> {
16
17
  if (await testCaddy())
17
18
  return caddyPath
18
19
 
@@ -38,7 +39,7 @@ export async function download() {
38
39
  if (!support)
39
40
  return reject(new Error('not support'))
40
41
 
41
- const dowmloadLink = `https://caddyserver.com/api/download?os=${support.platform}&arch=${support.arch}`
42
+ const dowmloadLink = `https://caddyserver.com/api/download?os=${support.platform}&arch=${support.arch}&p=github.com%2Fcaddyserver%2Fforwardproxy`
42
43
 
43
44
  existsSync(caddyPath) && unlinkSync(caddyPath)
44
45
 
@@ -75,7 +76,7 @@ export async function download() {
75
76
  })
76
77
  }
77
78
 
78
- function testCaddy() {
79
+ function testCaddy(): Promise<boolean> {
79
80
  return new Promise((resolve, reject) => {
80
81
  if (!existsSync(caddyPath))
81
82
  return resolve(false)
@@ -123,13 +124,13 @@ export class CaddyInstant {
123
124
  this.locked = this.checkLock()
124
125
  }
125
126
 
126
- checkLock() {
127
+ checkLock(): boolean {
127
128
  if (!existsSync(caddyLockFilePath))
128
129
  writeFileSync(caddyLockFilePath, '')
129
130
  return lockfile.checkSync(caddyLockFilePath)
130
131
  }
131
132
 
132
- async getCaddyfile() {
133
+ async getCaddyfile(): Promise<string> {
133
134
  if (!existsSync(caddyFilePath))
134
135
  await this.initCaddyfile()
135
136
 
@@ -137,27 +138,49 @@ export class CaddyInstant {
137
138
  return this.caddyfile
138
139
  }
139
140
 
140
- async initCaddyfile() {
141
+ async initCaddyfile(): Promise<void> {
141
142
  const contet = `
142
143
  {
143
144
  debug
144
145
  auto_https disable_redirects
145
146
  }
146
-
147
+
148
+ :7601 {
149
+ root * "${join(
150
+ process.platform === 'win32'
151
+ ? `${process.env.AppData!}/Caddy`
152
+ : `${process.env.HOME!}/Library/Application Support/Caddy`,
153
+ './pki/authorities/local',
154
+ )}"
155
+ file_server browse
156
+ }
157
+
158
+ # 正向代理配置
159
+ :7600 {
160
+ bind 0.0.0.0
161
+
162
+ forward_proxy {
163
+ hide_ip
164
+ hide_via
165
+ acl {
166
+ allow all
167
+ }
168
+ }
169
+ }
147
170
  `
148
171
  await writeFile(caddyFilePath, contet)
149
172
  this.caddyfile = contet
150
173
  }
151
174
 
152
- async writeCaddyfile() {
175
+ async writeCaddyfile(): Promise<void> {
153
176
  return await writeFile(caddyFilePath, this.caddyfile || '')
154
177
  }
155
178
 
156
- async deleteCaddyfile() {
179
+ async deleteCaddyfile(): Promise<void> {
157
180
  return await unlink(caddyFilePath)
158
181
  }
159
182
 
160
- async init() {
183
+ async init(): Promise<void> {
161
184
  await download()
162
185
  this.inited = true
163
186
  }
@@ -165,9 +188,8 @@ export class CaddyInstant {
165
188
  /**
166
189
  * @param source like `127.0.0.1:8080`
167
190
  * @param target like `test.abc.com`. And if you provide a port ,it will be ignore.
168
- * @returns
169
191
  */
170
- async run(source: string, target: string, options?: Partial<RunOptions>) {
192
+ async run(source: string, target: string, options?: Partial<RunOptions>): Promise<void> {
171
193
  source = source.replace('0.0.0.0', '127.0.0.1')
172
194
  const {
173
195
  restore = true,
@@ -214,7 +236,6 @@ ${target.split(':')[0]}${https ? '' : ':80'} {
214
236
  }
215
237
  }
216
238
  }
217
-
218
239
  `
219
240
 
220
241
  await this.writeCaddyfile()
@@ -230,7 +251,7 @@ ${target.split(':')[0]}${https ? '' : ':80'} {
230
251
  }
231
252
 
232
253
  return new Promise<void>((resolve, reject) => {
233
- const setupCleanup = () => {
254
+ const setupCleanup = (): void => {
234
255
  process.on('SIGINT', async () => {
235
256
  if (this.stoped)
236
257
  return
@@ -340,7 +361,7 @@ ${target.split(':')[0]}${https ? '' : ':80'} {
340
361
  })
341
362
  }
342
363
 
343
- async baseCleanup(restore: boolean = true) {
364
+ async baseCleanup(restore: boolean = true): Promise<void> {
344
365
  try {
345
366
  if (restore && this._source && this._target) {
346
367
  if (await removeHost(this._source.split(':')[0], this._target.split(':')[0]))
@@ -1,5 +1,5 @@
1
- import { createServer } from 'node:net'
2
1
  import { exec } from 'node:child_process'
2
+ import { createServer } from 'node:net'
3
3
  import ora from 'ora'
4
4
  import { consola } from '../utils'
5
5
 
@@ -8,8 +8,8 @@ let spinner: ora.Ora
8
8
  /**
9
9
  * @returns whether the port is in use
10
10
  */
11
- export function tryPort(port: number) {
12
- return new Promise<boolean>((resolve, reject) => {
11
+ export function tryPort(port: number): Promise<boolean> {
12
+ return new Promise<boolean>((resolve, _reject) => {
13
13
  try {
14
14
  const server = createServer().listen(port)
15
15
  server.on('listening', () => {
@@ -23,7 +23,7 @@ export function tryPort(port: number) {
23
23
  server.close()
24
24
  })
25
25
  }
26
- catch (e) {
26
+ catch {
27
27
  resolve(true)
28
28
  }
29
29
  })
@@ -32,9 +32,9 @@ export function tryPort(port: number) {
32
32
  /**
33
33
  * npx free-port :port
34
34
  */
35
- export async function kill(port: number) {
35
+ export async function kill(port: number): Promise<void> {
36
36
  return new Promise<void>((resolve, reject) => {
37
- exec(`npx @maxbo/free-port ${port} -s`, (err, stdout, stderr) => {
37
+ exec(`npx @maxbo/free-port ${port} -s`, (err, stdout, _stderr) => {
38
38
  if (err) {
39
39
  reject(err)
40
40
  }
@@ -46,7 +46,7 @@ export async function kill(port: number) {
46
46
  })
47
47
  }
48
48
 
49
- function getNumber(number: number) {
49
+ function getNumber(number: number): string {
50
50
  if (number <= 0)
51
51
  return '0%'
52
52
  else if (number < 100)
@@ -55,7 +55,7 @@ function getNumber(number: number) {
55
55
  return '100%'
56
56
  }
57
57
 
58
- export function logProgress(percent: number | string) {
58
+ export function logProgress(percent: number | string): void {
59
59
  if (!spinner) {
60
60
  // empty a line before start
61
61
  consola.log('')
@@ -68,7 +68,7 @@ export function logProgress(percent: number | string) {
68
68
  else { spinner.text = `Download Caddy: ${getNumber(Number(percent) * 100)}` }
69
69
  }
70
70
 
71
- export function logProgressOver() {
71
+ export function logProgressOver(): void {
72
72
  if (!spinner) {
73
73
  spinner = ora({
74
74
  text: 'Start Download Caddy: 100%',
package/src/host/index.ts CHANGED
@@ -1,6 +1,6 @@
1
- import process from 'node:process'
2
1
  import { exec } from 'node:child_process'
3
2
  import { chmodSync } from 'node:fs'
3
+ import process from 'node:process'
4
4
  import { promisify } from 'node:util'
5
5
 
6
6
  import hostile from 'hostile'
@@ -9,29 +9,30 @@ import { consola } from '../utils'
9
9
  const _setHost = promisify(hostile.set)
10
10
  const _removeHost = promisify(hostile.remove)
11
11
 
12
- function getPathOfSystemHostsPath() {
12
+ function getPathOfSystemHostsPath(): string {
13
13
  return process.platform === 'win32'
14
14
  ? `${process.env.windir || 'C:\\WINDOWS'}\\system32\\drivers\\etc\\hosts`
15
15
  : '/etc/hosts'
16
16
  }
17
17
 
18
- async function syncHost() {
19
- return new Promise<boolean>((resolve, reject) => {
18
+ async function syncHost(): Promise<boolean> {
19
+ return new Promise<boolean>((resolve, _reject) => {
20
20
  exec(
21
21
  process.platform === 'win32'
22
22
  ? 'ipconfig /flushdns'
23
23
  : 'sudo -E killall -HUP mDNSResponder',
24
- (error, stdout, stderr) => {
24
+ (error, _stdout, _stderr) => {
25
25
  if (error) {
26
26
  consola.error(`exec error: ${error}`)
27
27
  return resolve(false)
28
28
  }
29
29
  return resolve(true)
30
- })
30
+ },
31
+ )
31
32
  })
32
33
  }
33
34
 
34
- export async function addHost(ip: string, host: string) {
35
+ export async function addHost(ip: string, host: string): Promise<boolean> {
35
36
  if (process.platform !== 'win32')
36
37
  chmodSync(getPathOfSystemHostsPath(), 0o777)
37
38
  await _setHost('127.0.0.1', 'localhost')
@@ -41,7 +42,7 @@ export async function addHost(ip: string, host: string) {
41
42
  return await syncHost()
42
43
  }
43
44
 
44
- export async function removeHost(ip: string, host: string) {
45
+ export async function removeHost(ip: string, host: string): Promise<boolean> {
45
46
  if (process.platform !== 'win32')
46
47
  chmodSync(getPathOfSystemHostsPath(), 0o777)
47
48
  await _removeHost(ip, host)
package/src/index.ts CHANGED
@@ -1,11 +1,12 @@
1
- import process from 'node:process'
2
1
  import type { UnpluginFactory } from 'unplugin'
3
- import { createUnplugin } from 'unplugin'
4
2
  import type { ResolvedConfig } from 'vite'
5
- import c from 'picocolors'
6
- import { consola, isAdmin, once } from './utils'
7
3
  import type { Options } from './types'
4
+ import process from 'node:process'
5
+ import { Table } from 'console-table-printer'
6
+ import c from 'picocolors'
7
+ import { createUnplugin } from 'unplugin'
8
8
  import { CaddyInstant } from './caddy'
9
+ import { consola, generateQrcode, getIpv4List, isAdmin, once } from './utils'
9
10
 
10
11
  let config: ResolvedConfig
11
12
 
@@ -13,6 +14,42 @@ let caddy: CaddyInstant
13
14
 
14
15
  const cwd = process.cwd()
15
16
 
17
+ function colorUrl(url: string): string {
18
+ return c.green(url.replace(/:(\d+)\//, (_, port) => `:${c.bold(port)}/`))
19
+ }
20
+
21
+ async function printForwardProxy(): Promise<void> {
22
+ const ipv4List = getIpv4List()
23
+ if (ipv4List.length) {
24
+ const ips = ipv4List.map(ip => c.cyan(ip)).join(', ')
25
+ consola.info(`${c.bold('Mobile device proxy access')}:\n`)
26
+ consola.info('1. Install certificate: scan the QR code and download the \`root.crt\` file,')
27
+ consola.info(' then install it on your mobile device')
28
+
29
+ const ipTable = await Promise.all(
30
+ ipv4List.map(async ip => ({
31
+ target: `http://${ip}:7601`,
32
+ certificate: await generateQrcode(`http://${ip}:7601`),
33
+ })),
34
+ )
35
+
36
+ const table = new Table({
37
+ columns: [
38
+ { name: 'target', alignment: 'left' },
39
+ { name: 'certificate', alignment: 'center', minLen: 28 },
40
+ ],
41
+ })
42
+
43
+ ipTable.forEach((ip) => {
44
+ table.addRow(ip)
45
+ })
46
+
47
+ table.printTable()
48
+
49
+ consola.info(`2. Set proxy on mobile device (Available IPs: ${ips}):${c.bold(7600)}\n\n`)
50
+ }
51
+ }
52
+
16
53
  export const vitePrintUrls = once((
17
54
  options: Options,
18
55
  source: string,
@@ -28,8 +65,9 @@ export const vitePrintUrls = once((
28
65
  caddy.run(source, target, {
29
66
  base,
30
67
  ...options,
31
- }).then(() => {
32
- const colorUrl = (url: string) => c.green(url.replace(/:(\d+)\//, (_, port) => `:${c.bold(port)}/`))
68
+ }).then(async () => {
69
+ await printForwardProxy()
70
+
33
71
  consola.success(` ${c.green('➜')} ${c.bold('run caddy reverse proxy success')}: ${colorUrl(`${options.https ? 'https' : 'http'}://${target}${base}`)}`)
34
72
  }).catch((e) => {
35
73
  throw e
@@ -64,6 +102,11 @@ export const unpluginFactory: UnpluginFactory<Options> = options => ({
64
102
  return
65
103
  }
66
104
 
105
+ if (target.match(/^(?:https?)?:\/\//)) {
106
+ consola.fail('target must be a domain')
107
+ return
108
+ }
109
+
67
110
  const _printUrls = server.printUrls.bind(server)
68
111
 
69
112
  let source = `localhost:${config.server.port || 5173}`
@@ -100,6 +143,11 @@ export const unpluginFactory: UnpluginFactory<Options> = options => ({
100
143
  return
101
144
  }
102
145
 
146
+ if (target.match(/^(?:https?)?:\/\//)) {
147
+ consola.fail('target must be a domain')
148
+ return
149
+ }
150
+
103
151
  const devServer = {
104
152
  host: 'localhost',
105
153
  port: 8080,
@@ -131,9 +179,9 @@ export const unpluginFactory: UnpluginFactory<Options> = options => ({
131
179
  caddy.run(source, target, {
132
180
  ...options,
133
181
  }).then(() => {
134
- const colorUrl = (url: string) => c.green(url.replace(/:(\d+)\//, (_, port) => `:${c.bold(port)}/`))
182
+ compiler.hooks.done.tap('unplugin-https-reverse-proxy', async () => {
183
+ await printForwardProxy()
135
184
 
136
- compiler.hooks.done.tap('unplugin-https-reverse-proxy', () => {
137
185
  consola.success(` ${c.green('➜')} ${c.bold('run caddy reverse proxy success')}: ${colorUrl(`${options.https ? 'https' : 'http'}://${target}`)}`)
138
186
  })
139
187
  }).catch((e) => {
@@ -162,6 +210,11 @@ export const unpluginFactory: UnpluginFactory<Options> = options => ({
162
210
  return
163
211
  }
164
212
 
213
+ if (target.match(/^(?:https?)?:\/\//)) {
214
+ consola.fail('target must be a domain')
215
+ return
216
+ }
217
+
165
218
  const devServer = {
166
219
  host: 'localhost',
167
220
  port: 8080,
@@ -180,8 +233,8 @@ export const unpluginFactory: UnpluginFactory<Options> = options => ({
180
233
 
181
234
  caddy.run(source, target, {
182
235
  ...options,
183
- }).then(() => {
184
- const colorUrl = (url: string) => c.green(url.replace(/:(\d+)\//, (_, port) => `:${c.bold(port)}/`))
236
+ }).then(async () => {
237
+ await printForwardProxy()
185
238
 
186
239
  consola.success(` ${c.green('➜')} ${c.bold('run caddy reverse proxy success')}: ${colorUrl(`${options.https ? 'https' : 'http'}://${target}`)}`)
187
240
  }).catch((e) => {
package/src/nuxt.ts CHANGED
@@ -1,9 +1,9 @@
1
+ import type { Options } from './types'
1
2
  import process from 'node:process'
2
3
  import { defineNuxtModule } from '@nuxt/kit'
3
- import type { Options } from './types'
4
- import '@nuxt/schema'
5
- import { consola, isAdmin } from './utils'
6
4
  import { vitePrintUrls } from '.'
5
+ import { consola, isAdmin } from './utils'
6
+ import '@nuxt/schema'
7
7
 
8
8
  export interface ModuleOptions extends Options {
9
9
 
@@ -36,6 +36,11 @@ export default defineNuxtModule<ModuleOptions>({
36
36
  return
37
37
  }
38
38
 
39
+ if (target.match(/^(?:https?)?:\/\//)) {
40
+ consola.fail('target must be a domain')
41
+ return
42
+ }
43
+
39
44
  // https://github.com/nuxt/cli/issues/220#issuecomment-1735394141
40
45
  _nuxt.hook('listen', (_, listener) => {
41
46
  const url = listener.url
package/src/utils.ts CHANGED
@@ -1,14 +1,17 @@
1
- import process from 'node:process'
2
- import path from 'node:path'
3
- import { chmod, readdir, stat } from 'node:fs/promises'
4
1
  import { execSync } from 'node:child_process'
2
+ import { chmod, readdir, stat } from 'node:fs/promises'
3
+ import { networkInterfaces } from 'node:os'
4
+ import path from 'node:path'
5
+ import process from 'node:process'
5
6
  import { createConsola } from 'consola'
7
+ import c from 'picocolors'
8
+ import qrcode from 'qrcode-terminal'
6
9
 
7
10
  export const consola = createConsola({
8
11
  level: 3,
9
12
  }).withTag('reverse-proxy')
10
13
 
11
- export async function chmodRecursive(_path: string, mode: number) {
14
+ export async function chmodRecursive(_path: string, mode: number): Promise<void> {
12
15
  await chmod(_path, mode)
13
16
  if ((await stat(_path)).isDirectory()) {
14
17
  const children = await readdir(_path)
@@ -27,7 +30,7 @@ export function once<T extends (...args: any[]) => any>(fn: T): (...args: Parame
27
30
  }
28
31
  }
29
32
 
30
- export function isAdmin() {
33
+ export function isAdmin(): boolean {
31
34
  if (process.platform === 'win32') {
32
35
  try {
33
36
  execSync('net session', { stdio: 'ignore' })
@@ -38,6 +41,30 @@ export function isAdmin() {
38
41
  }
39
42
  }
40
43
  else {
41
- return process.getuid && process.getuid() === 0
44
+ return !!(process.getuid && process.getuid() === 0)
42
45
  }
43
46
  }
47
+
48
+ export function getIpv4List(): string[] {
49
+ const interfaces = networkInterfaces()
50
+
51
+ return Object.values(interfaces)
52
+ .flatMap(nInterface => nInterface ?? [])
53
+ .filter(
54
+ detail =>
55
+ detail.address
56
+ && (detail.family === 'IPv4'
57
+ // @ts-expect-error Node 18.0 - 18.3 returns number
58
+ || detail.family === 4),
59
+ )
60
+ .filter(detail => detail.family === 'IPv4' && !detail.internal)
61
+ .map(detail => detail.address)
62
+ }
63
+
64
+ export async function generateQrcode(input: string): Promise<string> {
65
+ return new Promise((resolve) => {
66
+ qrcode.generate(input, { small: true }, (code) => {
67
+ resolve(code.split('\n').map(line => c.bgBlack(c.white(line))).join('\n'))
68
+ })
69
+ })
70
+ }