@cloud-cli/px 3.5.2 → 3.5.3

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.
package/dist/index.d.ts CHANGED
@@ -1,13 +1,15 @@
1
1
  import { init } from '@cloud-cli/cli';
2
2
  import { DomainAndTarget, Domain } from './proxy-manager.js';
3
- import { ProxyEntry } from './proxy.js';
4
- declare function add(options: ProxyEntry): Promise<unknown>;
3
+ import { Proxy } from './proxy.js';
4
+ import { ProxyServer } from '@cloud-cli/proxy';
5
+ declare function reload(): Promise<void>;
6
+ declare function add(options: Proxy): Promise<unknown>;
5
7
  declare function remove(options: DomainAndTarget): Promise<unknown>;
6
- declare function update(options: ProxyEntry): Promise<boolean>;
7
- declare function list(): Promise<ProxyEntry[]>;
8
- declare function get(options: Domain): Promise<ProxyEntry[]>;
8
+ declare function update(options: Proxy): Promise<boolean>;
9
+ declare function list(): Promise<Proxy[]>;
10
+ declare function get(options: Domain): Promise<Proxy[]>;
9
11
  declare function domains(): Promise<string[]>;
10
- declare function reload(): Promise<void>;
12
+ declare function initServer(): Promise<ProxyServer>;
11
13
  declare const _default: {
12
14
  add: typeof add;
13
15
  remove: typeof remove;
@@ -16,6 +18,6 @@ declare const _default: {
16
18
  update: typeof update;
17
19
  domains: typeof domains;
18
20
  reload: typeof reload;
19
- [init]: typeof reload;
21
+ [init]: typeof initServer;
20
22
  };
21
23
  export default _default;
package/dist/index.js CHANGED
@@ -1,22 +1,43 @@
1
1
  import { init } from '@cloud-cli/cli';
2
2
  import { Resource, SQLiteDriver } from '@cloud-cli/store';
3
3
  import { ProxyManager } from './proxy-manager.js';
4
- import { ProxyEntry, ProxyServer } from './proxy.js';
5
- const px = new ProxyServer();
4
+ import { loadTargets, Proxy } from './proxy.js';
5
+ import { ProxyServer, ProxySettings, ProxyEntry } from '@cloud-cli/proxy';
6
+ const settings = new ProxySettings({
7
+ certificatesFolder: process.env.PX_CERTS_FOLDER || '/etc/letsencrypt/live',
8
+ certificateFile: 'fullchain.pem',
9
+ keyFile: 'privkey.pem',
10
+ });
11
+ const px = new ProxyServer(settings);
6
12
  const manager = new ProxyManager();
13
+ async function reload() {
14
+ const targets = await loadTargets();
15
+ px.reset();
16
+ targets.forEach(t => {
17
+ px.add(new ProxyEntry({
18
+ domain: t.domain,
19
+ target: t.target,
20
+ path: t.path,
21
+ redirectToHttps: t.redirect,
22
+ redirectToUrl: t.redirectUrl,
23
+ cors: t.cors,
24
+ }));
25
+ });
26
+ px.start();
27
+ }
7
28
  async function add(options) {
8
29
  const proxy = await manager.addProxy(options);
9
- px.reload();
30
+ await reload();
10
31
  return proxy;
11
32
  }
12
33
  async function remove(options) {
13
34
  const removed = await manager.removeProxy(options);
14
- px.reload();
35
+ await reload();
15
36
  return removed;
16
37
  }
17
38
  async function update(options) {
18
39
  const output = await manager.updateProxy(options);
19
- px.reload();
40
+ await reload();
20
41
  return output;
21
42
  }
22
43
  function list() {
@@ -28,9 +49,10 @@ function get(options) {
28
49
  function domains() {
29
50
  return manager.getDomainList();
30
51
  }
31
- async function reload() {
52
+ async function initServer() {
32
53
  Resource.use(new SQLiteDriver());
33
- await Resource.create(ProxyEntry);
34
- return px.start();
54
+ await Resource.create(Proxy);
55
+ await reload();
56
+ return px;
35
57
  }
36
- export default { add, remove, list, get, update, domains, reload, [init]: reload };
58
+ export default { add, remove, list, get, update, domains, reload, [init]: initServer };
@@ -1,4 +1,4 @@
1
- import { ProxyEntry } from './proxy.js';
1
+ import { Proxy } from './proxy.js';
2
2
  export interface Domain {
3
3
  domain: string;
4
4
  host?: string;
@@ -13,15 +13,15 @@ type ClassProperties<T> = {
13
13
  [K in KeysOf<T>]: T[K] extends never ? never : T[K];
14
14
  };
15
15
  export declare class ProxyManager {
16
- addProxy(proxy: ClassProperties<ProxyEntry> & {
16
+ addProxy(proxy: ClassProperties<Proxy> & {
17
17
  host?: string;
18
18
  }): Promise<unknown>;
19
- updateProxy(options: ClassProperties<ProxyEntry> & {
19
+ updateProxy(options: ClassProperties<Proxy> & {
20
20
  host?: string;
21
21
  }): Promise<boolean>;
22
22
  removeProxy(options: DomainAndTarget): Promise<unknown>;
23
23
  getDomainList(): Promise<string[]>;
24
- getProxyList(): Promise<ProxyEntry[]>;
25
- getProxyListForDomain(options: Domain): Promise<ProxyEntry[]>;
24
+ getProxyList(): Promise<Proxy[]>;
25
+ getProxyListForDomain(options: Domain): Promise<Proxy[]>;
26
26
  }
27
27
  export {};
@@ -1,5 +1,5 @@
1
1
  import { Query, Resource } from '@cloud-cli/store';
2
- import { ProxyEntry } from './proxy.js';
2
+ import { Proxy } from './proxy.js';
3
3
  const domainNotSpecifiedError = new Error('Domain not specified');
4
4
  const targetNotSpecifiedError = new Error('Target not specified');
5
5
  export class ProxyManager {
@@ -11,7 +11,7 @@ export class ProxyManager {
11
11
  if (!proxy.target && !proxy.redirectUrl) {
12
12
  return reject(targetNotSpecifiedError);
13
13
  }
14
- const entry = new ProxyEntry({
14
+ const entry = new Proxy({
15
15
  domain: proxy.domain || proxy.host,
16
16
  target: proxy.target,
17
17
  cors: !!proxy.cors,
@@ -31,9 +31,9 @@ export class ProxyManager {
31
31
  async updateProxy(options) {
32
32
  const domain = options.domain || options.host;
33
33
  const query = new Query().where('domain').is(domain);
34
- const proxies = await Resource.find(ProxyEntry, query);
34
+ const proxies = await Resource.find(Proxy, query);
35
35
  if (!proxies.length) {
36
- proxies.push(new ProxyEntry({ domain }));
36
+ proxies.push(new Proxy({ domain }));
37
37
  }
38
38
  for (const proxy of proxies) {
39
39
  ['target', 'cors', 'redirect', 'redirectUrl'].forEach(p => p in options && (proxy[p] = options[p]));
@@ -50,7 +50,7 @@ export class ProxyManager {
50
50
  if (options.target) {
51
51
  query.where('target').is(options.target);
52
52
  }
53
- const proxy = await Resource.find(ProxyEntry, query);
53
+ const proxy = await Resource.find(Proxy, query);
54
54
  try {
55
55
  for (const p of proxy) {
56
56
  await p.remove();
@@ -63,13 +63,13 @@ export class ProxyManager {
63
63
  });
64
64
  }
65
65
  async getDomainList() {
66
- const proxies = await Resource.find(ProxyEntry, new Query());
66
+ const proxies = await Resource.find(Proxy, new Query());
67
67
  return proxies.map((proxy) => proxy.domain);
68
68
  }
69
69
  async getProxyList() {
70
- return Resource.find(ProxyEntry, new Query());
70
+ return Resource.find(Proxy, new Query());
71
71
  }
72
72
  getProxyListForDomain(options) {
73
- return Resource.find(ProxyEntry, new Query().where('domain').is(options.domain || options.host));
73
+ return Resource.find(Proxy, new Query().where('domain').is(options.domain || options.host));
74
74
  }
75
75
  }
package/dist/proxy.d.ts CHANGED
@@ -1,34 +1,12 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- /// <reference types="node" />
4
- /// <reference types="node" />
5
- import { IncomingMessage, ClientRequest, ServerResponse } from 'http';
6
- import { ServerOptions as HttpsServerOptions } from 'https';
7
- import { SecureContext } from 'tls';
8
1
  import { Resource } from '@cloud-cli/store';
9
- export declare class ProxyEntry extends Resource {
2
+ export declare class Proxy extends Resource {
10
3
  id: number;
11
4
  domain: string;
5
+ path: string;
12
6
  target: string;
13
7
  redirect: boolean;
14
8
  redirectUrl: string;
15
9
  cors: boolean;
16
- constructor(p: Partial<ProxyEntry>);
17
- }
18
- export declare class ProxyServer {
19
- protected certs: Record<string, SecureContext>;
20
- protected targets: Record<string, ProxyEntry>;
21
- protected servers: any;
22
- start(): void;
23
- reload(): void;
24
- protected loadCertificates(): Promise<void>;
25
- protected loadTargets(): Promise<void>;
26
- protected serveRequest(req: IncomingMessage, res: ServerResponse, insecure?: boolean): void;
27
- protected getSslOptions(): HttpsServerOptions;
28
- protected findRootDomain(domain: string): SecureContext;
29
- protected setHeaders(from: IncomingMessage, to: ServerResponse | ClientRequest): void;
30
- protected getOrigin(req: IncomingMessage): URL;
31
- protected getHost(string: string): string;
32
- protected setCorsHeaders(req: IncomingMessage, res: ServerResponse): void;
33
- protected handleError(error: any, res: ServerResponse): void;
10
+ constructor(p: Partial<Proxy>);
34
11
  }
12
+ export declare function loadTargets(): Promise<Proxy[]>;
package/dist/proxy.js CHANGED
@@ -4,16 +4,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { createServer as createHttpServer, request as httpRequest, } from 'http';
8
- import { createServer as createHttpsServer, request as httpsRequest } from 'https';
9
- import { createSecureContext } from 'tls';
10
7
  import { Model, Primary, Property, Query, Resource } from '@cloud-cli/store';
11
- import { readdir, readFile } from 'fs/promises';
12
- import { join } from 'path';
13
- const certificatesFolder = process.env.PX_CERTS_FOLDER || '/etc/letsencrypt/live';
14
- const certificateFile = 'fullchain.pem';
15
- const keyFile = 'privkey.pem';
16
- let ProxyEntry = class ProxyEntry extends Resource {
8
+ let Proxy = class Proxy extends Resource {
17
9
  constructor(p) {
18
10
  super(p);
19
11
  }
@@ -21,172 +13,29 @@ let ProxyEntry = class ProxyEntry extends Resource {
21
13
  __decorate([
22
14
  Primary(),
23
15
  Property(Number)
24
- ], ProxyEntry.prototype, "id", void 0);
16
+ ], Proxy.prototype, "id", void 0);
25
17
  __decorate([
26
18
  Property(String)
27
- ], ProxyEntry.prototype, "domain", void 0);
19
+ ], Proxy.prototype, "domain", void 0);
28
20
  __decorate([
29
21
  Property(String)
30
- ], ProxyEntry.prototype, "target", void 0);
22
+ ], Proxy.prototype, "path", void 0);
23
+ __decorate([
24
+ Property(String)
25
+ ], Proxy.prototype, "target", void 0);
31
26
  __decorate([
32
27
  Property(Number)
33
- ], ProxyEntry.prototype, "redirect", void 0);
28
+ ], Proxy.prototype, "redirect", void 0);
34
29
  __decorate([
35
30
  Property(String)
36
- ], ProxyEntry.prototype, "redirectUrl", void 0);
31
+ ], Proxy.prototype, "redirectUrl", void 0);
37
32
  __decorate([
38
33
  Property(Number)
39
- ], ProxyEntry.prototype, "cors", void 0);
40
- ProxyEntry = __decorate([
34
+ ], Proxy.prototype, "cors", void 0);
35
+ Proxy = __decorate([
41
36
  Model('proxyentry')
42
- ], ProxyEntry);
43
- export { ProxyEntry };
44
- export class ProxyServer {
45
- constructor() {
46
- this.certs = {};
47
- this.targets = {};
48
- this.servers = [];
49
- }
50
- start() {
51
- this.reload();
52
- this.servers.forEach((server) => server.close());
53
- this.servers = [
54
- createHttpServer((req, res) => this.serveRequest(req, res, true)).listen(80),
55
- createHttpsServer(this.getSslOptions(), (req, res) => this.serveRequest(req, res)).listen(443),
56
- ];
57
- }
58
- reload() {
59
- this.loadCertificates();
60
- this.loadTargets();
61
- }
62
- async loadCertificates() {
63
- const certs = (this.certs = {});
64
- const localCerts = await readdir(certificatesFolder, { withFileTypes: true });
65
- const folders = localCerts.filter((entry) => entry.isDirectory()).map((dir) => dir.name);
66
- for (const rootDomain of folders) {
67
- if (process.env.DEBUG) {
68
- console.log(`Loading certificate for ${rootDomain}`);
69
- }
70
- certs[rootDomain] = createSecureContext({
71
- cert: await readFile(join(certificatesFolder, rootDomain, certificateFile), 'utf8'),
72
- key: await readFile(join(certificatesFolder, rootDomain, keyFile), 'utf8'),
73
- });
74
- }
75
- }
76
- async loadTargets() {
77
- const targets = await Resource.find(ProxyEntry, new Query());
78
- this.targets = {};
79
- targets.forEach((entry) => (this.targets[entry.domain] = entry));
80
- }
81
- serveRequest(req, res, insecure = false) {
82
- const origin = this.getOrigin(req);
83
- const proxyEntry = this.targets[origin?.hostname];
84
- if (!origin.hostname || !proxyEntry) {
85
- res.writeHead(404, 'Not found');
86
- res.end();
87
- return;
88
- }
89
- if (proxyEntry.redirectUrl) {
90
- res.setHeader('Location', String(proxyEntry.redirectUrl));
91
- res.writeHead(302, 'Moved somewhere else');
92
- res.end();
93
- return;
94
- }
95
- if (proxyEntry.redirect && insecure) {
96
- const newURL = new URL(req.url, `https://${req.headers.host}`);
97
- res.setHeader('Location', String(newURL));
98
- res.writeHead(301, 'HTTPS is better');
99
- res.end();
100
- return;
101
- }
102
- const target = proxyEntry.target;
103
- const url = new URL(req.url, target);
104
- const isCorsPreflight = req.method === 'OPTIONS' && proxyEntry.cors;
105
- if (isCorsPreflight) {
106
- this.setCorsHeaders(req, res);
107
- res.writeHead(204, { 'Content-Length': '0' });
108
- res.end();
109
- return;
110
- }
111
- const proxyRequest = (url.protocol === 'https:' ? httpsRequest : httpRequest)(url, { method: req.method });
112
- this.setHeaders(req, proxyRequest);
113
- proxyRequest.setHeader('host', this.getHost(target));
114
- proxyRequest.setHeader('x-forwarded-for', req.headers.host);
115
- proxyRequest.setHeader('x-forwarded-proto', insecure ? 'http' : 'https');
116
- proxyRequest.setHeader('forwarded', 'host=' + req.headers.host + ';proto=' + (insecure ? 'http' : 'https'));
117
- req.on('data', (chunk) => proxyRequest.write(chunk));
118
- req.on('end', () => proxyRequest.end());
119
- proxyRequest.on('error', (error) => this.handleError(error, res));
120
- proxyRequest.on('response', (proxyRes) => {
121
- this.setHeaders(proxyRes, res);
122
- const isCorsSimple = req.method !== 'OPTIONS' && proxyEntry.cors && req.headers.origin;
123
- if (isCorsSimple) {
124
- this.setCorsHeaders(req, res);
125
- }
126
- res.writeHead(proxyRes.statusCode, proxyRes.statusMessage);
127
- proxyRes.on('data', (chunk) => res.write(chunk));
128
- proxyRes.on('end', () => res.end());
129
- });
130
- }
131
- getSslOptions() {
132
- const server = this;
133
- return {
134
- SNICallback(domain, cb) {
135
- const rootDomain = server.findRootDomain(domain);
136
- if (rootDomain) {
137
- return cb(null, rootDomain);
138
- }
139
- cb(new Error('Not found', { cause: 404 }), null);
140
- },
141
- };
142
- }
143
- findRootDomain(domain) {
144
- const parts = domain.split('/')[0].split('.');
145
- const certs = this.certs;
146
- while (parts.length) {
147
- const rootDomain = parts.join('.');
148
- if (certs[rootDomain]) {
149
- return certs[rootDomain];
150
- }
151
- parts.shift();
152
- }
153
- return null;
154
- }
155
- setHeaders(from, to) {
156
- const headers = Object.entries(from.headers);
157
- for (const header of headers) {
158
- to.setHeader(header[0], header[1]);
159
- }
160
- }
161
- getOrigin(req) {
162
- const host = req.headers['x-forwarded-for'] || req.headers.host || '';
163
- return (host && new URL('http://' + host)) || null;
164
- }
165
- getHost(string) {
166
- const url = new URL(string);
167
- return url.hostname + (url.port ? ':' + url.port : '');
168
- }
169
- setCorsHeaders(req, res) {
170
- const headers = req.headers;
171
- const corsOrigin = new URL(req.headers.origin).origin;
172
- const allowedMethod = headers['access-control-request-method'] || 'GET,HEAD,PUT,PATCH,POST,DELETE';
173
- const allowedHeaders = headers['access-control-request-headers'] || '*';
174
- res.setHeader('Vary', 'Origin');
175
- res.setHeader('Access-Control-Allow-Origin', corsOrigin);
176
- res.setHeader('Access-Control-Allow-Headers', allowedHeaders);
177
- res.setHeader('Access-Control-Allow-Methods', allowedMethod);
178
- res.setHeader('Access-Control-Allow-Credentials', 'true');
179
- }
180
- handleError(error, res) {
181
- console.error(error);
182
- if (error.code === 'ECONNREFUSED' || error.code === 'ECONNRESET') {
183
- res.writeHead(502);
184
- res.end();
185
- return;
186
- }
187
- if (!res.headersSent) {
188
- res.writeHead(500);
189
- res.end();
190
- }
191
- }
37
+ ], Proxy);
38
+ export { Proxy };
39
+ export async function loadTargets() {
40
+ return await Resource.find(Proxy, new Query());
192
41
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@cloud-cli/px",
3
- "version": "3.5.2",
3
+ "version": "3.5.3",
4
4
  "description": "Reverse Proxy",
5
- "main": "dist/index.js",
5
+ "main": "./dist/index.js",
6
6
  "type": "module",
7
7
  "types": "./dist/index.d.ts",
8
8
  "prettier": "@cloud-cli/prettier-config",
@@ -29,6 +29,7 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@cloud-cli/cli": "^1.2.2",
32
- "@cloud-cli/store": "^1.1.0"
32
+ "@cloud-cli/store": "^1.1.0",
33
+ "@cloud-cli/proxy": "^1.0.0"
33
34
  }
34
35
  }