@speedkit/cli 4.23.2 → 4.24.0
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/.env.sample +7 -0
- package/CHANGELOG.md +21 -0
- package/README.md +1 -1
- package/dist/helpers/origin-proxy.d.ts +89 -0
- package/dist/helpers/origin-proxy.js +207 -0
- package/dist/helpers/origin-proxy.spec.d.ts +1 -0
- package/dist/helpers/origin-proxy.spec.js +167 -0
- package/dist/helpers/scrape.js +2 -2
- package/dist/helpers/site-analyzer.js +2 -2
- package/dist/services/bundler/bundle-service-factory.d.ts +2 -1
- package/dist/services/bundler/bundle-service-factory.js +4 -2
- package/dist/services/bundler/bundle-service.d.ts +30 -2
- package/dist/services/bundler/bundle-service.js +81 -6
- package/dist/services/bundler/bundle-service.spec.js +124 -0
- package/dist/services/deploy/checks/post-deploy/refresh-deployment-job-check.js +4 -3
- package/dist/services/deploy/checks/post-deploy/refresh-deployment-job-check.spec.d.ts +1 -0
- package/dist/services/deploy/checks/post-deploy/refresh-deployment-job-check.spec.js +33 -0
- package/dist/services/document-handler-runtime/document-handler-server.d.ts +23 -3
- package/dist/services/document-handler-runtime/document-handler-server.js +66 -43
- package/dist/services/document-handler-runtime/factory/document-handler-runtime-service-factory.js +7 -2
- package/dist/services/onboarding/dashboard/request-diff-service.js +5 -11
- package/dist/services/onboarding/onboarding-service-factory.d.ts +0 -2
- package/dist/services/onboarding/onboarding-service-factory.js +5 -43
- package/dist/services/onboarding/virtual-orestes-app/crawler.d.ts +1 -3
- package/dist/services/onboarding/virtual-orestes-app/crawler.js +6 -10
- package/dist/services/onboarding/virtual-orestes-app/crawler.spec.d.ts +1 -0
- package/dist/services/onboarding/virtual-orestes-app/crawler.spec.js +49 -0
- package/dist/services/origin-request/origin-request-service-factory.js +5 -1
- package/dist/services/origin-request/origin-request-service.js +2 -1
- package/oclif.manifest.json +1 -1
- package/package.json +2 -1
package/.env.sample
CHANGED
|
@@ -20,6 +20,13 @@
|
|
|
20
20
|
# Chrome Test Browser will use an existing sub-profile named 'Default'
|
|
21
21
|
# within CHROME_USER_PROFILE_PATH or creates one automatically on start up.
|
|
22
22
|
|
|
23
|
+
# Enforce a SOCKS proxy for every request the CLI sends to a customer origin.
|
|
24
|
+
# The value is a Chrome `--proxy-server` URL, so Chrome and the CLI share one setting.
|
|
25
|
+
# It overrides a `--proxy-server` entry in the customer config's chromeFlags. The CLI then names
|
|
26
|
+
# both proxies, because the customer config pins the exit region its origin expects.
|
|
27
|
+
# Requests to Baqend stay on a direct connection.
|
|
28
|
+
# SK_PROXY_SERVER='socks5://ap-northeast-1.proxy.baqend.com:1080'
|
|
29
|
+
|
|
23
30
|
# Slack Deploy Token
|
|
24
31
|
# SLACK_TOKEN='<SLACK_TOKEN_FROM_1PASSWORD>'
|
|
25
32
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
# [4.24.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.23.3...v4.24.0) (2026-08-28)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **onboarding:** apply the chromeFlags socks proxy to origin requests ([db0ce41](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/db0ce412b3d0a9056d9eb1778da11d6d84649695))
|
|
7
|
+
* **onboarding:** fetch external modules from app ([59646e7](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/59646e744fdeaf1f4b9e0091cc1c0c625478f2ab))
|
|
8
|
+
* **onboarding:** guard the crawler against a config without authentications ([10c75b8](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/10c75b86fd2e7191057d04f2d1b257f7b2c4b577))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **cli:** enforce a socks proxy for every origin request via SK_PROXY_SERVER ([66fab11](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/66fab119e3a5cb2f040c6d91c5f1f10b19e4e2a8))
|
|
14
|
+
|
|
15
|
+
## [4.23.3](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.23.2...v4.23.3) (2026-08-25)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **deploy:** match refresh job names by prefix ([c593d9d](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/c593d9d69db8e05ccb71b320cb4f9c7cbda5cdac))
|
|
21
|
+
|
|
1
22
|
## [4.23.2](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.23.1...v4.23.2) (2026-08-21)
|
|
2
23
|
|
|
3
24
|
|
package/README.md
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment variable that enforces a SOCKS proxy for every origin-directed request the CLI
|
|
3
|
+
* makes. It carries a Chrome `--proxy-server` value, so one string serves Chrome and the CLI, e.g.
|
|
4
|
+
* `SK_PROXY_SERVER=socks5://ap-northeast-1.proxy.baqend.com:1080`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const PROXY_ENVIRONMENT_VARIABLE = "SK_PROXY_SERVER";
|
|
7
|
+
/** SOCKS proxy in the shape `socksDispatcher` expects. */
|
|
8
|
+
export interface OriginProxy {
|
|
9
|
+
type: 4 | 5;
|
|
10
|
+
host: string;
|
|
11
|
+
port: number;
|
|
12
|
+
}
|
|
13
|
+
/** Only `writeWarning` is used, so this helper does not depend on the CLI service. */
|
|
14
|
+
export interface ProxyLogger {
|
|
15
|
+
writeWarning(message: string): void;
|
|
16
|
+
}
|
|
17
|
+
/** Per-request transport choice. */
|
|
18
|
+
export interface OriginFetchOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Accepts an untrusted certificate and allows legacy renegotiation. Set it only where the call
|
|
21
|
+
* site already tolerated a broken origin handshake, because it hides a real certificate fault.
|
|
22
|
+
*/
|
|
23
|
+
tolerateLegacyTls?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Parses a Chrome `--proxy-server` value into a SOCKS proxy.
|
|
27
|
+
*
|
|
28
|
+
* A value without a port takes 1080, the port Chrome assumes for a SOCKS proxy, so one string keeps
|
|
29
|
+
* working for both.
|
|
30
|
+
*
|
|
31
|
+
* @param value - Proxy URL, e.g. `socks5://proxy.example.com:1080`.
|
|
32
|
+
* @returns The parsed proxy, or undefined when the value is empty or not a SOCKS URL.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseSocksProxy(value?: string): OriginProxy | undefined;
|
|
35
|
+
/** Renders a proxy as the URL that Chrome and the log lines use. */
|
|
36
|
+
export declare function formatProxy(proxy: OriginProxy): string;
|
|
37
|
+
/** Renders a proxy as the Chrome flag that routes the browser through it. */
|
|
38
|
+
export declare function chromeProxyFlag(proxy: OriginProxy): string;
|
|
39
|
+
/**
|
|
40
|
+
* Finds the SOCKS proxy that a customer config pins through its Chrome flags.
|
|
41
|
+
*
|
|
42
|
+
* @param chromeFlags - `chromeFlags` of the customer config.
|
|
43
|
+
* @returns The `--proxy-server` value of the first SOCKS entry, or undefined.
|
|
44
|
+
*/
|
|
45
|
+
export declare function findProxyInChromeFlags(chromeFlags?: string[]): string;
|
|
46
|
+
/**
|
|
47
|
+
* Selects the SOCKS proxy for all origin-directed requests and installs it for `originFetch`.
|
|
48
|
+
*
|
|
49
|
+
* `SK_PROXY_SERVER` wins over the customer config, so an operator can route a session through
|
|
50
|
+
* another region without editing a customer directory. The customer's `--proxy-server` chrome flag
|
|
51
|
+
* is the fallback.
|
|
52
|
+
*
|
|
53
|
+
* A customer pins the proxy for a reason: its origin expects the exit region the config names. The
|
|
54
|
+
* logger therefore reports both values whenever the environment displaces one, so a forgotten
|
|
55
|
+
* export cannot silently move a session out of the customer's region.
|
|
56
|
+
*
|
|
57
|
+
* @param chromeFlags - `chromeFlags` of the customer config, when a command has one.
|
|
58
|
+
* @param logger - Receives one line naming the proxy in use.
|
|
59
|
+
* @returns The proxy in use, or undefined for a direct connection.
|
|
60
|
+
* @throws When `SK_PROXY_SERVER` holds something that is not a SOCKS URL. Failing is safer than a
|
|
61
|
+
* silent direct connection, because the operator asked for a proxy.
|
|
62
|
+
*/
|
|
63
|
+
export declare function applyOriginProxy(chromeFlags?: string[], logger?: ProxyLogger): OriginProxy | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Installs the proxy that `originFetch` uses. Prefer `applyOriginProxy`. This entry point exists
|
|
66
|
+
* for tests and for callers that resolved the proxy themselves.
|
|
67
|
+
*/
|
|
68
|
+
export declare function setOriginProxy(proxy?: OriginProxy, fromEnvironment?: boolean): void;
|
|
69
|
+
/** Drops the installed proxy, so the next call resolves it again. */
|
|
70
|
+
export declare function resetOriginProxy(): void;
|
|
71
|
+
/**
|
|
72
|
+
* Appends the enforced proxy to the Chrome flags of a launch.
|
|
73
|
+
*
|
|
74
|
+
* Chrome takes the last `--proxy-server`, so the environment beats a customer config here just as
|
|
75
|
+
* it does for the CLI. A proxy that came from the flags already is left untouched.
|
|
76
|
+
*
|
|
77
|
+
* @param chromeFlags - `chromeFlags` of the customer config.
|
|
78
|
+
*/
|
|
79
|
+
export declare function withOriginProxyFlag(chromeFlags?: string[]): string[];
|
|
80
|
+
/**
|
|
81
|
+
* Fetches a customer origin through the enforced SOCKS proxy.
|
|
82
|
+
*
|
|
83
|
+
* Every request that leaves the CLI for a customer origin has to use this instead of `fetch`, which
|
|
84
|
+
* carries no proxy support. Requests to Baqend keep their own client and connect directly.
|
|
85
|
+
*
|
|
86
|
+
* Node's built-in `fetch` rejects a dispatcher built by the `undici` package, so a request that
|
|
87
|
+
* needs one runs through undici's own `fetch`. A plain direct request keeps the global `fetch`.
|
|
88
|
+
*/
|
|
89
|
+
export declare function originFetch(url: string, init?: RequestInit, options?: OriginFetchOptions): Promise<Response>;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { socksDispatcher } from "fetch-socks";
|
|
3
|
+
import { fetch as undiciFetch } from "undici";
|
|
4
|
+
/**
|
|
5
|
+
* Environment variable that enforces a SOCKS proxy for every origin-directed request the CLI
|
|
6
|
+
* makes. It carries a Chrome `--proxy-server` value, so one string serves Chrome and the CLI, e.g.
|
|
7
|
+
* `SK_PROXY_SERVER=socks5://ap-northeast-1.proxy.baqend.com:1080`.
|
|
8
|
+
*/
|
|
9
|
+
export const PROXY_ENVIRONMENT_VARIABLE = "SK_PROXY_SERVER";
|
|
10
|
+
const PROXY_SCHEMES = {
|
|
11
|
+
"socks4:": 4,
|
|
12
|
+
"socks4a:": 4,
|
|
13
|
+
"socks5:": 5,
|
|
14
|
+
"socks5h:": 5,
|
|
15
|
+
};
|
|
16
|
+
const CHROME_PROXY_FLAG = "--proxy-server=";
|
|
17
|
+
/** Port Chrome assumes for a SOCKS proxy that names none. */
|
|
18
|
+
const DEFAULT_SOCKS_PORT = 1080;
|
|
19
|
+
let state;
|
|
20
|
+
/**
|
|
21
|
+
* Parses a Chrome `--proxy-server` value into a SOCKS proxy.
|
|
22
|
+
*
|
|
23
|
+
* A value without a port takes 1080, the port Chrome assumes for a SOCKS proxy, so one string keeps
|
|
24
|
+
* working for both.
|
|
25
|
+
*
|
|
26
|
+
* @param value - Proxy URL, e.g. `socks5://proxy.example.com:1080`.
|
|
27
|
+
* @returns The parsed proxy, or undefined when the value is empty or not a SOCKS URL.
|
|
28
|
+
*/
|
|
29
|
+
export function parseSocksProxy(value) {
|
|
30
|
+
const trimmed = (value || "").trim();
|
|
31
|
+
if (!trimmed) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
let url;
|
|
35
|
+
try {
|
|
36
|
+
url = new URL(trimmed);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const type = PROXY_SCHEMES[url.protocol];
|
|
42
|
+
if (!type || !url.hostname) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
type,
|
|
47
|
+
host: url.hostname,
|
|
48
|
+
port: url.port ? Number(url.port) : DEFAULT_SOCKS_PORT,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Renders a proxy as the URL that Chrome and the log lines use. */
|
|
52
|
+
export function formatProxy(proxy) {
|
|
53
|
+
return `socks${proxy.type}://${proxy.host}:${proxy.port}`;
|
|
54
|
+
}
|
|
55
|
+
/** Renders a proxy as the Chrome flag that routes the browser through it. */
|
|
56
|
+
export function chromeProxyFlag(proxy) {
|
|
57
|
+
return `${CHROME_PROXY_FLAG}${formatProxy(proxy)}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Finds the SOCKS proxy that a customer config pins through its Chrome flags.
|
|
61
|
+
*
|
|
62
|
+
* @param chromeFlags - `chromeFlags` of the customer config.
|
|
63
|
+
* @returns The `--proxy-server` value of the first SOCKS entry, or undefined.
|
|
64
|
+
*/
|
|
65
|
+
export function findProxyInChromeFlags(chromeFlags = []) {
|
|
66
|
+
for (const flag of chromeFlags) {
|
|
67
|
+
if (!flag.startsWith(CHROME_PROXY_FLAG)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const value = flag.slice(CHROME_PROXY_FLAG.length);
|
|
71
|
+
if (parseSocksProxy(value)) {
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Selects the SOCKS proxy for all origin-directed requests and installs it for `originFetch`.
|
|
78
|
+
*
|
|
79
|
+
* `SK_PROXY_SERVER` wins over the customer config, so an operator can route a session through
|
|
80
|
+
* another region without editing a customer directory. The customer's `--proxy-server` chrome flag
|
|
81
|
+
* is the fallback.
|
|
82
|
+
*
|
|
83
|
+
* A customer pins the proxy for a reason: its origin expects the exit region the config names. The
|
|
84
|
+
* logger therefore reports both values whenever the environment displaces one, so a forgotten
|
|
85
|
+
* export cannot silently move a session out of the customer's region.
|
|
86
|
+
*
|
|
87
|
+
* @param chromeFlags - `chromeFlags` of the customer config, when a command has one.
|
|
88
|
+
* @param logger - Receives one line naming the proxy in use.
|
|
89
|
+
* @returns The proxy in use, or undefined for a direct connection.
|
|
90
|
+
* @throws When `SK_PROXY_SERVER` holds something that is not a SOCKS URL. Failing is safer than a
|
|
91
|
+
* silent direct connection, because the operator asked for a proxy.
|
|
92
|
+
*/
|
|
93
|
+
export function applyOriginProxy(chromeFlags = [], logger) {
|
|
94
|
+
const fromEnvironment = (process.env[PROXY_ENVIRONMENT_VARIABLE] || "").trim();
|
|
95
|
+
if (fromEnvironment) {
|
|
96
|
+
const proxy = parseSocksProxy(fromEnvironment);
|
|
97
|
+
if (!proxy) {
|
|
98
|
+
throw new Error(`${PROXY_ENVIRONMENT_VARIABLE}="${fromEnvironment}" is not a SOCKS proxy URL. ` +
|
|
99
|
+
`Use socks4://host:port or socks5://host:port.`);
|
|
100
|
+
}
|
|
101
|
+
setOriginProxy(proxy, true);
|
|
102
|
+
logger?.writeWarning(enforcedProxyMessage(proxy, chromeFlags));
|
|
103
|
+
return proxy;
|
|
104
|
+
}
|
|
105
|
+
const proxy = parseSocksProxy(findProxyInChromeFlags(chromeFlags));
|
|
106
|
+
setOriginProxy(proxy, false);
|
|
107
|
+
if (proxy) {
|
|
108
|
+
logger?.writeWarning(`use proxy from chromeFlags for every origin request: ${formatProxy(proxy)}`);
|
|
109
|
+
}
|
|
110
|
+
return proxy;
|
|
111
|
+
}
|
|
112
|
+
/** True when both proxies name the same SOCKS endpoint. */
|
|
113
|
+
function isSameProxy(one, other) {
|
|
114
|
+
return (one.type === other.type &&
|
|
115
|
+
one.host === other.host &&
|
|
116
|
+
one.port === other.port);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Builds the line that reports an enforced proxy. A displaced customer proxy is named, because the
|
|
120
|
+
* operator has to see which region the session left behind.
|
|
121
|
+
*/
|
|
122
|
+
function enforcedProxyMessage(proxy, chromeFlags) {
|
|
123
|
+
const pinned = parseSocksProxy(findProxyInChromeFlags(chromeFlags));
|
|
124
|
+
if (pinned && !isSameProxy(pinned, proxy)) {
|
|
125
|
+
return (`${PROXY_ENVIRONMENT_VARIABLE} replaces the proxy of the customer config. ` +
|
|
126
|
+
`Chrome and every origin request leave through ${formatProxy(proxy)}, ` +
|
|
127
|
+
`not through ${formatProxy(pinned)}. Unset the variable to use the customer's region.`);
|
|
128
|
+
}
|
|
129
|
+
return `${PROXY_ENVIRONMENT_VARIABLE} routes Chrome and every origin request through ${formatProxy(proxy)}`;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Installs the proxy that `originFetch` uses. Prefer `applyOriginProxy`. This entry point exists
|
|
133
|
+
* for tests and for callers that resolved the proxy themselves.
|
|
134
|
+
*/
|
|
135
|
+
export function setOriginProxy(proxy, fromEnvironment = false) {
|
|
136
|
+
state = { proxy, fromEnvironment };
|
|
137
|
+
}
|
|
138
|
+
/** Drops the installed proxy, so the next call resolves it again. */
|
|
139
|
+
export function resetOriginProxy() {
|
|
140
|
+
state = undefined;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Appends the enforced proxy to the Chrome flags of a launch.
|
|
144
|
+
*
|
|
145
|
+
* Chrome takes the last `--proxy-server`, so the environment beats a customer config here just as
|
|
146
|
+
* it does for the CLI. A proxy that came from the flags already is left untouched.
|
|
147
|
+
*
|
|
148
|
+
* @param chromeFlags - `chromeFlags` of the customer config.
|
|
149
|
+
*/
|
|
150
|
+
export function withOriginProxyFlag(chromeFlags = []) {
|
|
151
|
+
const current = currentState();
|
|
152
|
+
if (!current.proxy || !current.fromEnvironment) {
|
|
153
|
+
return chromeFlags;
|
|
154
|
+
}
|
|
155
|
+
return [...chromeFlags, chromeProxyFlag(current.proxy)];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Fetches a customer origin through the enforced SOCKS proxy.
|
|
159
|
+
*
|
|
160
|
+
* Every request that leaves the CLI for a customer origin has to use this instead of `fetch`, which
|
|
161
|
+
* carries no proxy support. Requests to Baqend keep their own client and connect directly.
|
|
162
|
+
*
|
|
163
|
+
* Node's built-in `fetch` rejects a dispatcher built by the `undici` package, so a request that
|
|
164
|
+
* needs one runs through undici's own `fetch`. A plain direct request keeps the global `fetch`.
|
|
165
|
+
*/
|
|
166
|
+
export function originFetch(url, init, options = {}) {
|
|
167
|
+
const dispatcher = originDispatcher(options.tolerateLegacyTls === true);
|
|
168
|
+
if (!dispatcher) {
|
|
169
|
+
return fetch(url, init);
|
|
170
|
+
}
|
|
171
|
+
const request = { ...init, dispatcher };
|
|
172
|
+
return undiciFetch(url, request);
|
|
173
|
+
}
|
|
174
|
+
function currentState() {
|
|
175
|
+
if (!state) {
|
|
176
|
+
applyOriginProxy();
|
|
177
|
+
}
|
|
178
|
+
return state;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Builds the dispatcher for one transport and caches it.
|
|
182
|
+
*
|
|
183
|
+
* `socksDispatcher` connects directly when it gets no proxy, so the same builder serves a tolerant
|
|
184
|
+
* direct request. A strict direct request needs no dispatcher at all.
|
|
185
|
+
*
|
|
186
|
+
* @param tolerateLegacyTls - Accept an untrusted certificate and allow legacy renegotiation.
|
|
187
|
+
*/
|
|
188
|
+
function originDispatcher(tolerateLegacyTls) {
|
|
189
|
+
const current = currentState();
|
|
190
|
+
if (!current.proxy && !tolerateLegacyTls) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const proxies = current.proxy ? [current.proxy] : [];
|
|
194
|
+
const connect = tolerateLegacyTls
|
|
195
|
+
? {
|
|
196
|
+
keepAlive: true,
|
|
197
|
+
rejectUnauthorized: false,
|
|
198
|
+
secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
|
|
199
|
+
}
|
|
200
|
+
: { keepAlive: true };
|
|
201
|
+
if (tolerateLegacyTls) {
|
|
202
|
+
current.tolerantDispatcher ??= socksDispatcher(proxies, { connect });
|
|
203
|
+
return current.tolerantDispatcher;
|
|
204
|
+
}
|
|
205
|
+
current.strictDispatcher ??= socksDispatcher(proxies, { connect });
|
|
206
|
+
return current.strictDispatcher;
|
|
207
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, beforeEach, afterEach, it } from "mocha";
|
|
3
|
+
import http from "node:http";
|
|
4
|
+
import { applyOriginProxy, chromeProxyFlag, findProxyInChromeFlags, originFetch, parseSocksProxy, PROXY_ENVIRONMENT_VARIABLE, resetOriginProxy, setOriginProxy, withOriginProxyFlag, } from "./origin-proxy.js";
|
|
5
|
+
/** Sets or clears the enforcing environment variable. */
|
|
6
|
+
function setEnvironmentProxy(value) {
|
|
7
|
+
if (value === undefined) {
|
|
8
|
+
delete process.env.SK_PROXY_SERVER;
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
process.env.SK_PROXY_SERVER = value;
|
|
12
|
+
}
|
|
13
|
+
/** Collects the warnings a caller would print. */
|
|
14
|
+
function recordingLogger() {
|
|
15
|
+
const warnings = [];
|
|
16
|
+
return {
|
|
17
|
+
warnings,
|
|
18
|
+
writeWarning: (message) => warnings.push(message),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
describe("origin proxy", () => {
|
|
22
|
+
const originalEnvironment = process.env.SK_PROXY_SERVER;
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
setEnvironmentProxy(undefined);
|
|
25
|
+
resetOriginProxy();
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
setEnvironmentProxy(originalEnvironment);
|
|
29
|
+
resetOriginProxy();
|
|
30
|
+
});
|
|
31
|
+
describe("parseSocksProxy", () => {
|
|
32
|
+
it("keeps the host without the scheme separator", () => {
|
|
33
|
+
expect(parseSocksProxy("socks5://ap-northeast-1.proxy.baqend.com:1080")).to.deep.equal({
|
|
34
|
+
type: 5,
|
|
35
|
+
host: "ap-northeast-1.proxy.baqend.com",
|
|
36
|
+
port: 1080,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
it("maps every socks scheme to its protocol version", () => {
|
|
40
|
+
expect(parseSocksProxy("socks4://proxy.example.com:1080").type).to.equal(4);
|
|
41
|
+
expect(parseSocksProxy("socks4a://proxy.example.com:1080").type).to.equal(4);
|
|
42
|
+
expect(parseSocksProxy("socks5h://proxy.example.com:1080").type).to.equal(5);
|
|
43
|
+
});
|
|
44
|
+
it("takes 1080 for a value without a port, like chrome does", () => {
|
|
45
|
+
expect(parseSocksProxy("socks5://eu-central-1.proxy.baqend.com")).to.deep.equal({
|
|
46
|
+
type: 5,
|
|
47
|
+
host: "eu-central-1.proxy.baqend.com",
|
|
48
|
+
port: 1080,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
it("rejects a value that is not a socks url", () => {
|
|
52
|
+
expect(parseSocksProxy("")).to.equal(undefined);
|
|
53
|
+
expect(parseSocksProxy("http://proxy.example.com:8080")).to.equal(undefined);
|
|
54
|
+
expect(parseSocksProxy("https://eu-central-1.proxy.baqend.com/")).to.equal(undefined);
|
|
55
|
+
expect(parseSocksProxy("proxy.example.com:1080")).to.equal(undefined);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe("findProxyInChromeFlags", () => {
|
|
59
|
+
it("finds the socks entry among unrelated flags", () => {
|
|
60
|
+
const flags = [
|
|
61
|
+
"--window-size=390,844",
|
|
62
|
+
"--proxy-server=socks5://proxy.example.com:1080",
|
|
63
|
+
];
|
|
64
|
+
expect(findProxyInChromeFlags(flags)).to.equal("socks5://proxy.example.com:1080");
|
|
65
|
+
});
|
|
66
|
+
it("ignores a non-socks proxy flag", () => {
|
|
67
|
+
expect(findProxyInChromeFlags([
|
|
68
|
+
"--proxy-server=http://proxy.example.com:8080",
|
|
69
|
+
])).to.equal(undefined);
|
|
70
|
+
});
|
|
71
|
+
it("survives a config without chrome flags", () => {
|
|
72
|
+
expect(findProxyInChromeFlags()).to.equal(undefined);
|
|
73
|
+
expect(findProxyInChromeFlags([])).to.equal(undefined);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe("applyOriginProxy", () => {
|
|
77
|
+
it("takes the proxy from the customer chrome flags", () => {
|
|
78
|
+
const logger = recordingLogger();
|
|
79
|
+
const proxy = applyOriginProxy(["--proxy-server=socks5://proxy.example.com:1080"], logger);
|
|
80
|
+
expect(proxy).to.deep.equal({
|
|
81
|
+
type: 5,
|
|
82
|
+
host: "proxy.example.com",
|
|
83
|
+
port: 1080,
|
|
84
|
+
});
|
|
85
|
+
expect(logger.warnings).to.have.length(1);
|
|
86
|
+
});
|
|
87
|
+
it("lets the environment override the customer chrome flags", () => {
|
|
88
|
+
setEnvironmentProxy("socks5://enforced.example.com:1080");
|
|
89
|
+
const proxy = applyOriginProxy([
|
|
90
|
+
"--proxy-server=socks5://customer.example.com:1080",
|
|
91
|
+
]);
|
|
92
|
+
expect(proxy.host).to.equal("enforced.example.com");
|
|
93
|
+
});
|
|
94
|
+
it("names both proxies when it displaces the customer's region", () => {
|
|
95
|
+
setEnvironmentProxy("socks5://enforced.example.com:1080");
|
|
96
|
+
const logger = recordingLogger();
|
|
97
|
+
applyOriginProxy(["--proxy-server=socks5://ap-northeast-1.proxy.baqend.com:1080"], logger);
|
|
98
|
+
expect(logger.warnings).to.have.length(1);
|
|
99
|
+
expect(logger.warnings[0]).to.contain("enforced.example.com");
|
|
100
|
+
expect(logger.warnings[0]).to.contain("ap-northeast-1.proxy.baqend.com:1080");
|
|
101
|
+
});
|
|
102
|
+
it("reports no displacement when both sources name one endpoint", () => {
|
|
103
|
+
setEnvironmentProxy("socks5h://proxy.example.com:1080");
|
|
104
|
+
const logger = recordingLogger();
|
|
105
|
+
applyOriginProxy(["--proxy-server=socks5://proxy.example.com:1080"], logger);
|
|
106
|
+
expect(logger.warnings[0]).to.not.contain("replaces");
|
|
107
|
+
});
|
|
108
|
+
it("fails loudly on an unusable environment value", () => {
|
|
109
|
+
setEnvironmentProxy("http://proxy.example.com:8080");
|
|
110
|
+
expect(() => applyOriginProxy()).to.throw(PROXY_ENVIRONMENT_VARIABLE);
|
|
111
|
+
});
|
|
112
|
+
it("reports no proxy when neither source sets one", () => {
|
|
113
|
+
const logger = recordingLogger();
|
|
114
|
+
expect(applyOriginProxy([], logger)).to.equal(undefined);
|
|
115
|
+
expect(logger.warnings).to.have.length(0);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
describe("withOriginProxyFlag", () => {
|
|
119
|
+
it("appends the enforced proxy last, so chrome takes it", () => {
|
|
120
|
+
setEnvironmentProxy("socks5://enforced.example.com:1080");
|
|
121
|
+
applyOriginProxy(["--proxy-server=socks5://customer.example.com:1080"]);
|
|
122
|
+
expect(withOriginProxyFlag([
|
|
123
|
+
"--proxy-server=socks5://customer.example.com:1080",
|
|
124
|
+
])).to.deep.equal([
|
|
125
|
+
"--proxy-server=socks5://customer.example.com:1080",
|
|
126
|
+
"--proxy-server=socks5://enforced.example.com:1080",
|
|
127
|
+
]);
|
|
128
|
+
});
|
|
129
|
+
it("leaves a proxy that already came from the flags untouched", () => {
|
|
130
|
+
const flags = ["--proxy-server=socks5://customer.example.com:1080"];
|
|
131
|
+
applyOriginProxy(flags);
|
|
132
|
+
expect(withOriginProxyFlag(flags)).to.deep.equal(flags);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe("chromeProxyFlag", () => {
|
|
136
|
+
it("round-trips a parsed proxy", () => {
|
|
137
|
+
const value = "socks5://proxy.example.com:1080";
|
|
138
|
+
expect(chromeProxyFlag(parseSocksProxy(value))).to.equal(`--proxy-server=${value}`);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
describe("originFetch", () => {
|
|
142
|
+
let server;
|
|
143
|
+
let url;
|
|
144
|
+
beforeEach(async () => {
|
|
145
|
+
server = http.createServer((_request, response) => response.end("ok"));
|
|
146
|
+
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
147
|
+
url = `http://127.0.0.1:${server.address().port}/`;
|
|
148
|
+
});
|
|
149
|
+
afterEach(() => server.close());
|
|
150
|
+
it("connects directly when no proxy is set", async () => {
|
|
151
|
+
const response = await originFetch(url);
|
|
152
|
+
expect(await response.text()).to.equal("ok");
|
|
153
|
+
});
|
|
154
|
+
it("connects directly when only legacy tls is tolerated", async () => {
|
|
155
|
+
const response = await originFetch(url, undefined, {
|
|
156
|
+
tolerateLegacyTls: true,
|
|
157
|
+
});
|
|
158
|
+
expect(await response.text()).to.equal("ok");
|
|
159
|
+
});
|
|
160
|
+
it("routes through the installed proxy", async () => {
|
|
161
|
+
// Nothing listens on port 1, so a proxied request must fail to connect.
|
|
162
|
+
setOriginProxy({ type: 5, host: "127.0.0.1", port: 1 });
|
|
163
|
+
const failure = await originFetch(url).then(() => null, (error) => error);
|
|
164
|
+
expect(failure).to.be.instanceOf(Error);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
package/dist/helpers/scrape.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import fetch from "node-fetch";
|
|
2
1
|
import { safe } from "./safe.js";
|
|
2
|
+
import { originFetch } from "./origin-proxy.js";
|
|
3
3
|
const EMPTY_FLAGS = {
|
|
4
4
|
useGATracking: false,
|
|
5
5
|
withGoogleOptimize: false,
|
|
@@ -78,7 +78,7 @@ function findUrl(html, host, pattern) {
|
|
|
78
78
|
return `https://${host}${path}`;
|
|
79
79
|
}
|
|
80
80
|
async function fetchPage(url, signal) {
|
|
81
|
-
const result = await safe(
|
|
81
|
+
const result = await safe(originFetch(url, { signal }));
|
|
82
82
|
if (!result.success)
|
|
83
83
|
return null;
|
|
84
84
|
const response = result.data;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BundleService } from "./bundle-service.js";
|
|
2
2
|
export declare class BundleServiceFactory {
|
|
3
3
|
private customerFolder?;
|
|
4
|
-
|
|
4
|
+
private additionalModulePaths;
|
|
5
|
+
constructor(customerFolder?: string, additionalModulePaths?: string[]);
|
|
5
6
|
buildService(): BundleService;
|
|
6
7
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { BundleService } from "./bundle-service.js";
|
|
2
2
|
export class BundleServiceFactory {
|
|
3
3
|
customerFolder;
|
|
4
|
-
|
|
4
|
+
additionalModulePaths;
|
|
5
|
+
constructor(customerFolder, additionalModulePaths = []) {
|
|
5
6
|
this.customerFolder = customerFolder;
|
|
7
|
+
this.additionalModulePaths = additionalModulePaths;
|
|
6
8
|
}
|
|
7
9
|
buildService() {
|
|
8
|
-
return new BundleService(this.customerFolder);
|
|
10
|
+
return new BundleService(this.customerFolder, this.additionalModulePaths);
|
|
9
11
|
}
|
|
10
12
|
}
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { PluginBuild, LogLevel } from "esbuild";
|
|
2
2
|
export type BundleEntryPoints = Record<string, string>;
|
|
3
3
|
export type BundleBasePaths = Record<string, string>;
|
|
4
|
+
/**
|
|
5
|
+
* Returns the source of a relative import that could not be resolved on disk, or `null` when the
|
|
6
|
+
* fallback has nothing to offer. Returning `null` lets esbuild report its original resolve error.
|
|
7
|
+
*/
|
|
8
|
+
export type BundleResolveFallback = (specifier: string) => Promise<string | null>;
|
|
4
9
|
export declare class BundleService {
|
|
5
10
|
private moduleDiscoveryDir;
|
|
6
|
-
|
|
7
|
-
|
|
11
|
+
private additionalModulePaths;
|
|
12
|
+
/**
|
|
13
|
+
* @param moduleDiscoveryDir the directory bundled code resolves its own imports from
|
|
14
|
+
* @param additionalModulePaths extra `node_modules` directories to search, like `NODE_PATH`.
|
|
15
|
+
* `sk` installs the packages a customer config declares under `dependencies` into its cache
|
|
16
|
+
* rather than into the customer folder, so esbuild would otherwise never find them.
|
|
17
|
+
*/
|
|
18
|
+
constructor(moduleDiscoveryDir: string, additionalModulePaths?: string[]);
|
|
19
|
+
bundle({ entryPoints, basePaths, define, target, minify, logLevel, alias, platform, resolveFallback, }: {
|
|
8
20
|
entryPoints: BundleEntryPoints;
|
|
9
21
|
basePaths?: BundleBasePaths;
|
|
10
22
|
target?: string | string[];
|
|
@@ -13,6 +25,7 @@ export declare class BundleService {
|
|
|
13
25
|
alias?: Record<string, string>;
|
|
14
26
|
logLevel?: LogLevel;
|
|
15
27
|
platform?: "browser" | "node" | "neutral";
|
|
28
|
+
resolveFallback?: BundleResolveFallback;
|
|
16
29
|
}): Promise<string>;
|
|
17
30
|
/**
|
|
18
31
|
* Turns each alias target into an absolute path, resolved from the same directory the bundled
|
|
@@ -26,9 +39,24 @@ export declare class BundleService {
|
|
|
26
39
|
* A target that is not installed stays a bare name, so esbuild reports it as before.
|
|
27
40
|
*/
|
|
28
41
|
private resolveAliases;
|
|
42
|
+
/**
|
|
43
|
+
* Looks the target up the way the bundled code would: the customer folder first, then the
|
|
44
|
+
* additional module paths. Returns `null` when it is installed nowhere, so the bare name reaches
|
|
45
|
+
* esbuild and is reported as before.
|
|
46
|
+
*/
|
|
47
|
+
private resolveAliasTarget;
|
|
29
48
|
private entryPointsPlugin;
|
|
30
49
|
baqendPlugin(basePaths: BundleBasePaths): {
|
|
31
50
|
name: string;
|
|
32
51
|
setup(build: PluginBuild): void;
|
|
33
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* Resolves relative imports that exist nowhere in the customer folder through `resolveFallback`.
|
|
55
|
+
*
|
|
56
|
+
* A document handler shares its source with the Baqend app it is deployed to, so it may
|
|
57
|
+
* `require("./someModule")` a module that only lives in the app. Disk always wins: the fallback
|
|
58
|
+
* is only consulted after esbuild's own resolution has failed, and only for relative specifiers —
|
|
59
|
+
* a bare name is an npm package, and a missing one stays esbuild's error to report.
|
|
60
|
+
*/
|
|
61
|
+
private resolveFallbackPlugin;
|
|
34
62
|
}
|