@aluvia/sdk 1.0.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/LICENSE +21 -0
- package/README.md +423 -0
- package/dist/cjs/api/AluviaApi.js +51 -0
- package/dist/cjs/api/account.js +155 -0
- package/dist/cjs/api/geos.js +76 -0
- package/dist/cjs/api/request.js +84 -0
- package/dist/cjs/api/types.js +2 -0
- package/dist/cjs/client/AluviaClient.js +325 -0
- package/dist/cjs/client/ConfigManager.js +303 -0
- package/dist/cjs/client/ProxyServer.js +182 -0
- package/dist/cjs/client/adapters.js +49 -0
- package/dist/cjs/client/logger.js +52 -0
- package/dist/cjs/client/rules.js +128 -0
- package/dist/cjs/client/types.js +3 -0
- package/dist/cjs/errors.js +49 -0
- package/dist/cjs/index.js +16 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/api/AluviaApi.js +47 -0
- package/dist/esm/api/account.js +152 -0
- package/dist/esm/api/geos.js +73 -0
- package/dist/esm/api/request.js +81 -0
- package/dist/esm/api/types.js +1 -0
- package/dist/esm/client/AluviaClient.js +321 -0
- package/dist/esm/client/ConfigManager.js +299 -0
- package/dist/esm/client/ProxyServer.js +178 -0
- package/dist/esm/client/adapters.js +39 -0
- package/dist/esm/client/logger.js +48 -0
- package/dist/esm/client/rules.js +124 -0
- package/dist/esm/client/types.js +2 -0
- package/dist/esm/errors.js +42 -0
- package/dist/esm/index.js +7 -0
- package/dist/types/api/AluviaApi.d.ts +29 -0
- package/dist/types/api/account.d.ts +41 -0
- package/dist/types/api/geos.d.ts +5 -0
- package/dist/types/api/request.d.ts +20 -0
- package/dist/types/api/types.d.ts +30 -0
- package/dist/types/client/AluviaClient.d.ts +50 -0
- package/dist/types/client/ConfigManager.d.ts +100 -0
- package/dist/types/client/ProxyServer.d.ts +47 -0
- package/dist/types/client/adapters.d.ts +26 -0
- package/dist/types/client/logger.d.ts +33 -0
- package/dist/types/client/rules.d.ts +34 -0
- package/dist/types/client/types.d.ts +194 -0
- package/dist/types/errors.d.ts +25 -0
- package/dist/types/index.d.ts +5 -0
- package/package.json +65 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// ProxyServer - Local HTTP proxy using proxy-chain
|
|
2
|
+
import { Server as ProxyChainServer } from 'proxy-chain';
|
|
3
|
+
import { Logger } from './logger.js';
|
|
4
|
+
import { ProxyStartError } from '../errors.js';
|
|
5
|
+
import { shouldProxy } from './rules.js';
|
|
6
|
+
/**
|
|
7
|
+
* ProxyServer manages the local HTTP(S) proxy that routes traffic
|
|
8
|
+
* through Aluvia or directly based on rules.
|
|
9
|
+
*/
|
|
10
|
+
export class ProxyServer {
|
|
11
|
+
constructor(configManager, options) {
|
|
12
|
+
this.server = null;
|
|
13
|
+
this.bindHost = '127.0.0.1';
|
|
14
|
+
this.lastNoConfigWarnAt = 0;
|
|
15
|
+
this.suppressedNoConfigWarnCount = 0;
|
|
16
|
+
this.configManager = configManager;
|
|
17
|
+
this.logger = new Logger(options?.logLevel ?? 'info');
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Start the local proxy server.
|
|
21
|
+
*
|
|
22
|
+
* @param port - Optional port to listen on. If not provided, OS assigns a free port.
|
|
23
|
+
* @returns ProxyServerInfo with host, port, and url
|
|
24
|
+
* @throws ProxyStartError if server fails to start
|
|
25
|
+
*/
|
|
26
|
+
async start(port) {
|
|
27
|
+
const listenPort = port ?? 0;
|
|
28
|
+
try {
|
|
29
|
+
this.server = new ProxyChainServer({
|
|
30
|
+
// Security: bind to loopback only (proxy-chain defaults to 0.0.0.0 if host is omitted)
|
|
31
|
+
host: this.bindHost,
|
|
32
|
+
port: listenPort,
|
|
33
|
+
prepareRequestFunction: this.handleRequest.bind(this),
|
|
34
|
+
});
|
|
35
|
+
await this.server.listen();
|
|
36
|
+
// Get the actual port (especially important when port was 0)
|
|
37
|
+
const address = this.server.server.address();
|
|
38
|
+
const actualPort = address.port;
|
|
39
|
+
const info = {
|
|
40
|
+
host: this.bindHost,
|
|
41
|
+
port: actualPort,
|
|
42
|
+
url: `http://${this.bindHost}:${actualPort}`,
|
|
43
|
+
};
|
|
44
|
+
this.logger.info(`Proxy server listening on ${info.url}`);
|
|
45
|
+
return info;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
49
|
+
throw new ProxyStartError(`Failed to start proxy server: ${message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Stop the local proxy server.
|
|
54
|
+
*/
|
|
55
|
+
async stop() {
|
|
56
|
+
if (!this.server) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
await this.server.close(true);
|
|
61
|
+
this.logger.info('Proxy server stopped');
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
this.server = null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Handle incoming proxy requests.
|
|
69
|
+
* Decides whether to route through Aluvia or direct.
|
|
70
|
+
*/
|
|
71
|
+
handleRequest(params) {
|
|
72
|
+
// Get current config
|
|
73
|
+
const config = this.configManager.getConfig();
|
|
74
|
+
if (!config) {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
const shouldWarn = this.lastNoConfigWarnAt === 0 ||
|
|
77
|
+
now - this.lastNoConfigWarnAt >= ProxyServer.NO_CONFIG_WARN_INTERVAL_MS;
|
|
78
|
+
if (shouldWarn) {
|
|
79
|
+
const suppressed = this.suppressedNoConfigWarnCount;
|
|
80
|
+
this.suppressedNoConfigWarnCount = 0;
|
|
81
|
+
this.lastNoConfigWarnAt = now;
|
|
82
|
+
const suffix = suppressed > 0 ? ` (suppressed ${suppressed} similar warnings)` : '';
|
|
83
|
+
this.logger.warn(`No config available, bypassing proxy (direct)${suffix}`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
this.suppressedNoConfigWarnCount += 1;
|
|
87
|
+
this.logger.debug('No config available, bypassing proxy (direct)');
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
// Extract hostname
|
|
92
|
+
const hostname = this.extractHostname(params);
|
|
93
|
+
if (!hostname) {
|
|
94
|
+
this.logger.debug('Could not extract hostname, going direct');
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
// Check if we should proxy this hostname
|
|
98
|
+
const useProxy = shouldProxy(hostname, config.rules);
|
|
99
|
+
if (!useProxy) {
|
|
100
|
+
this.logger.debug(`Hostname ${hostname} bypassing proxy (direct)`);
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
// Build upstream proxy URL
|
|
104
|
+
const { protocol, host, port, username, password } = config.rawProxy;
|
|
105
|
+
const upstreamProxyUrl = `${protocol}://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${host}:${port}`;
|
|
106
|
+
this.logger.debug(`Hostname ${hostname} routing through Aluvia`);
|
|
107
|
+
return { upstreamProxyUrl };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Extract hostname from request parameters.
|
|
111
|
+
*/
|
|
112
|
+
extractHostname(params) {
|
|
113
|
+
// For CONNECT requests (HTTPS), hostname is provided directly
|
|
114
|
+
if (typeof params.hostname === 'string') {
|
|
115
|
+
const trimmed = params.hostname.trim();
|
|
116
|
+
if (trimmed.length > 0)
|
|
117
|
+
return trimmed;
|
|
118
|
+
}
|
|
119
|
+
const urlLikeRaw = params.request?.url;
|
|
120
|
+
if (typeof urlLikeRaw === 'string') {
|
|
121
|
+
const urlLike = urlLikeRaw.trim();
|
|
122
|
+
if (urlLike.length > 0) {
|
|
123
|
+
const fromUrlLike = (() => {
|
|
124
|
+
try {
|
|
125
|
+
return new URL(urlLike).hostname;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// continue
|
|
129
|
+
}
|
|
130
|
+
if (urlLike.startsWith('//')) {
|
|
131
|
+
try {
|
|
132
|
+
return new URL(`http:${urlLike}`).hostname;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// continue
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (urlLike.startsWith('/')) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
return new URL(`http://${urlLike}`).hostname;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
148
|
+
if (fromUrlLike)
|
|
149
|
+
return fromUrlLike;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// For origin-form URLs, fall back to Host header if available.
|
|
153
|
+
const hostHeader = (() => {
|
|
154
|
+
const headers = params.request?.headers;
|
|
155
|
+
if (!headers)
|
|
156
|
+
return null;
|
|
157
|
+
const host = headers['host'];
|
|
158
|
+
if (Array.isArray(host))
|
|
159
|
+
return typeof host[0] === 'string' ? host[0] : null;
|
|
160
|
+
return typeof host === 'string' ? host : null;
|
|
161
|
+
})();
|
|
162
|
+
if (hostHeader) {
|
|
163
|
+
const value = hostHeader.trim();
|
|
164
|
+
if (!value)
|
|
165
|
+
return null;
|
|
166
|
+
if (value.startsWith('[')) {
|
|
167
|
+
const end = value.indexOf(']');
|
|
168
|
+
if (end > 1)
|
|
169
|
+
return value.slice(1, end);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const hostOnly = value.split(':')[0]?.trim();
|
|
173
|
+
return hostOnly && hostOnly.length > 0 ? hostOnly : null;
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
ProxyServer.NO_CONFIG_WARN_INTERVAL_MS = 30000;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { HttpProxyAgent } from 'http-proxy-agent';
|
|
2
|
+
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
3
|
+
import { ProxyAgent as UndiciProxyAgent, fetch as undiciFetch } from 'undici';
|
|
4
|
+
export function toPlaywrightProxySettings(serverUrl) {
|
|
5
|
+
return { server: serverUrl };
|
|
6
|
+
}
|
|
7
|
+
export function toPuppeteerArgs(serverUrl) {
|
|
8
|
+
// Puppeteer wants --proxy-server=<server> without embedding creds.
|
|
9
|
+
return [`--proxy-server=${serverUrl}`];
|
|
10
|
+
}
|
|
11
|
+
export function toSeleniumArgs(serverUrl) {
|
|
12
|
+
// Selenium/Chromium wants --proxy-server=<server> without embedding creds.
|
|
13
|
+
return `--proxy-server=${serverUrl}`;
|
|
14
|
+
}
|
|
15
|
+
export function createNodeProxyAgents(serverUrl) {
|
|
16
|
+
return {
|
|
17
|
+
http: new HttpProxyAgent(serverUrl),
|
|
18
|
+
https: new HttpsProxyAgent(serverUrl),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function toAxiosConfig(agents) {
|
|
22
|
+
return {
|
|
23
|
+
proxy: false,
|
|
24
|
+
httpAgent: agents.http,
|
|
25
|
+
httpsAgent: agents.https,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function toGotOptions(agents) {
|
|
29
|
+
return { agent: { http: agents.http, https: agents.https } };
|
|
30
|
+
}
|
|
31
|
+
export function createUndiciDispatcher(serverUrl) {
|
|
32
|
+
return new UndiciProxyAgent(serverUrl);
|
|
33
|
+
}
|
|
34
|
+
export function createUndiciFetch(dispatcher) {
|
|
35
|
+
return ((input, init) => {
|
|
36
|
+
const nextInit = init ? { ...init, dispatcher } : { dispatcher };
|
|
37
|
+
return undiciFetch(input, nextInit);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Logger utility for Aluvia Client
|
|
2
|
+
/**
|
|
3
|
+
* Simple logger that respects log levels.
|
|
4
|
+
*
|
|
5
|
+
* Log level hierarchy:
|
|
6
|
+
* - 'silent': only error() logs
|
|
7
|
+
* - 'info': info(), warn(), error() log
|
|
8
|
+
* - 'debug': all methods log
|
|
9
|
+
*/
|
|
10
|
+
export class Logger {
|
|
11
|
+
constructor(level) {
|
|
12
|
+
this.level = level;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Log informational messages.
|
|
16
|
+
* Logs when level is 'info' or 'debug'.
|
|
17
|
+
*/
|
|
18
|
+
info(...args) {
|
|
19
|
+
if (this.level === 'info' || this.level === 'debug') {
|
|
20
|
+
console.log('[aluvia][info]', ...args);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Log debug messages.
|
|
25
|
+
* Logs only when level is 'debug'.
|
|
26
|
+
*/
|
|
27
|
+
debug(...args) {
|
|
28
|
+
if (this.level === 'debug') {
|
|
29
|
+
console.debug('[aluvia][debug]', ...args);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Log warning messages.
|
|
34
|
+
* Logs when level is not 'silent'.
|
|
35
|
+
*/
|
|
36
|
+
warn(...args) {
|
|
37
|
+
if (this.level !== 'silent') {
|
|
38
|
+
console.warn('[aluvia][warn]', ...args);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Log error messages.
|
|
43
|
+
* Always logs regardless of level.
|
|
44
|
+
*/
|
|
45
|
+
error(...args) {
|
|
46
|
+
console.error('[aluvia][error]', ...args);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Rule engine for hostname matching and proxy decision
|
|
2
|
+
/**
|
|
3
|
+
* Match a hostname against a pattern.
|
|
4
|
+
*
|
|
5
|
+
* Supported patterns:
|
|
6
|
+
* - '*' matches any hostname
|
|
7
|
+
* - '*.example.com' matches subdomains of example.com (but not example.com itself)
|
|
8
|
+
* - 'example.com' exact match
|
|
9
|
+
* - 'google.*' matches google.com, google.co.uk, etc.
|
|
10
|
+
*
|
|
11
|
+
* @param hostname - The hostname to match
|
|
12
|
+
* @param pattern - The pattern to match against
|
|
13
|
+
* @returns true if hostname matches pattern
|
|
14
|
+
*/
|
|
15
|
+
export function matchPattern(hostname, pattern) {
|
|
16
|
+
// Normalize inputs to lowercase
|
|
17
|
+
const normalizedHostname = hostname.trim().toLowerCase();
|
|
18
|
+
const normalizedPattern = pattern.trim().toLowerCase();
|
|
19
|
+
if (!normalizedHostname)
|
|
20
|
+
return false;
|
|
21
|
+
if (!normalizedPattern)
|
|
22
|
+
return false;
|
|
23
|
+
// Universal wildcard matches everything
|
|
24
|
+
if (normalizedPattern === '*') {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
// Exact match
|
|
28
|
+
if (normalizedHostname === normalizedPattern) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
// Prefix wildcard: *.example.com matches subdomains
|
|
32
|
+
if (normalizedPattern.startsWith('*.')) {
|
|
33
|
+
const suffix = normalizedPattern.slice(1); // '.example.com'
|
|
34
|
+
// Must end with the suffix and have something before it
|
|
35
|
+
if (normalizedHostname.endsWith(suffix)) {
|
|
36
|
+
const prefix = normalizedHostname.slice(0, -suffix.length);
|
|
37
|
+
// Prefix must not be empty. This matches any subdomain depth (for example, foo.example.com and foo.bar.example.com).
|
|
38
|
+
return prefix.length > 0;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// Suffix wildcard: google.* matches google.com, google.co.uk, etc.
|
|
43
|
+
if (normalizedPattern.endsWith('.*')) {
|
|
44
|
+
const prefix = normalizedPattern.slice(0, -2); // 'google'
|
|
45
|
+
// Must start with prefix followed by a dot
|
|
46
|
+
if (normalizedHostname.startsWith(prefix + '.')) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Determine if a hostname should be proxied based on rules.
|
|
55
|
+
*
|
|
56
|
+
* Rules semantics:
|
|
57
|
+
* - [] (empty) → no proxy (return false)
|
|
58
|
+
* - ['*'] → proxy everything
|
|
59
|
+
* - ['example.com'] → proxy only example.com
|
|
60
|
+
* - ['*.google.com'] → proxy subdomains of google.com
|
|
61
|
+
* - ['*', '-example.com'] → proxy everything except example.com
|
|
62
|
+
* - ['AUTO', 'example.com'] → AUTO is placeholder (ignored), proxy example.com
|
|
63
|
+
*
|
|
64
|
+
* Negative patterns (prefixed with '-') exclude hosts from proxying.
|
|
65
|
+
* If '*' is in rules, default is to proxy unless excluded.
|
|
66
|
+
* Without '*', only explicitly matched patterns are proxied.
|
|
67
|
+
*
|
|
68
|
+
* @param hostname - The hostname to check
|
|
69
|
+
* @param rules - Array of rule patterns
|
|
70
|
+
* @returns true if the hostname should be proxied
|
|
71
|
+
*/
|
|
72
|
+
export function shouldProxy(hostname, rules) {
|
|
73
|
+
const normalizedHostname = hostname.trim();
|
|
74
|
+
if (!normalizedHostname)
|
|
75
|
+
return false;
|
|
76
|
+
// Empty rules means no proxy
|
|
77
|
+
if (!rules || rules.length === 0) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const normalizedRules = rules
|
|
81
|
+
.filter((r) => typeof r === 'string')
|
|
82
|
+
.map((r) => r.trim())
|
|
83
|
+
.filter((r) => r.length > 0);
|
|
84
|
+
// Filter out AUTO placeholder
|
|
85
|
+
const effectiveRules = normalizedRules.filter((r) => r.toUpperCase() !== 'AUTO');
|
|
86
|
+
// If no effective rules after filtering, no proxy
|
|
87
|
+
if (effectiveRules.length === 0) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
// Separate positive and negative rules
|
|
91
|
+
const negativeRules = [];
|
|
92
|
+
const positiveRules = [];
|
|
93
|
+
for (const rule of effectiveRules) {
|
|
94
|
+
if (rule.startsWith('-')) {
|
|
95
|
+
const neg = rule.slice(1).trim(); // Remove the '-' prefix
|
|
96
|
+
if (neg.length > 0)
|
|
97
|
+
negativeRules.push(neg);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
positiveRules.push(rule);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Check if hostname matches any negative rule
|
|
104
|
+
for (const negRule of negativeRules) {
|
|
105
|
+
if (matchPattern(normalizedHostname, negRule)) {
|
|
106
|
+
// Excluded by negative rule
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Check if we have a catch-all '*'
|
|
111
|
+
const hasCatchAll = positiveRules.includes('*');
|
|
112
|
+
if (hasCatchAll) {
|
|
113
|
+
// With catch-all, proxy everything not excluded by negative rules
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
// Without catch-all, check if hostname matches any positive rule
|
|
117
|
+
for (const posRule of positiveRules) {
|
|
118
|
+
if (matchPattern(normalizedHostname, posRule)) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// No match found
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Error classes for Aluvia Client
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when the apiKey is not provided to AluviaClient.
|
|
4
|
+
*/
|
|
5
|
+
export class MissingApiKeyError extends Error {
|
|
6
|
+
constructor(message = 'Aluvia connection apiKey is required') {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'MissingApiKeyError';
|
|
9
|
+
Object.setPrototypeOf(this, MissingApiKeyError.prototype);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Thrown when the API returns 401 or 403, indicating the apiKey is invalid.
|
|
14
|
+
*/
|
|
15
|
+
export class InvalidApiKeyError extends Error {
|
|
16
|
+
constructor(message = 'Invalid or expired Aluvia connection apiKey') {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'InvalidApiKeyError';
|
|
19
|
+
Object.setPrototypeOf(this, InvalidApiKeyError.prototype);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Thrown for general API errors (non-2xx responses other than auth errors).
|
|
24
|
+
*/
|
|
25
|
+
export class ApiError extends Error {
|
|
26
|
+
constructor(message, statusCode) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = 'ApiError';
|
|
29
|
+
this.statusCode = statusCode;
|
|
30
|
+
Object.setPrototypeOf(this, ApiError.prototype);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Thrown when the local proxy server fails to start.
|
|
35
|
+
*/
|
|
36
|
+
export class ProxyStartError extends Error {
|
|
37
|
+
constructor(message = 'Failed to start local proxy server') {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = 'ProxyStartError';
|
|
40
|
+
Object.setPrototypeOf(this, ProxyStartError.prototype);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Aluvia Client Node
|
|
2
|
+
// Main entry point
|
|
3
|
+
// Public class
|
|
4
|
+
export { AluviaClient } from './client/AluviaClient.js';
|
|
5
|
+
export { AluviaApi } from './api/AluviaApi.js';
|
|
6
|
+
// Public error classes
|
|
7
|
+
export { MissingApiKeyError, InvalidApiKeyError, ApiError, ProxyStartError, } from './errors.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createAccountApi } from './account.js';
|
|
2
|
+
import { createGeosApi } from './geos.js';
|
|
3
|
+
export type AluviaApiOptions = {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
apiBaseUrl?: string;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
};
|
|
9
|
+
export type AluviaApiRequestArgs = {
|
|
10
|
+
method: 'GET' | 'POST' | 'PATCH' | 'DELETE';
|
|
11
|
+
path: string;
|
|
12
|
+
query?: Record<string, string | number | boolean | null | undefined>;
|
|
13
|
+
body?: unknown;
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
};
|
|
16
|
+
export declare class AluviaApi {
|
|
17
|
+
private readonly apiKey;
|
|
18
|
+
private readonly apiBaseUrl;
|
|
19
|
+
private readonly timeoutMs?;
|
|
20
|
+
private readonly fetchImpl?;
|
|
21
|
+
readonly account: ReturnType<typeof createAccountApi>;
|
|
22
|
+
readonly geos: ReturnType<typeof createGeosApi>;
|
|
23
|
+
constructor(options: AluviaApiOptions);
|
|
24
|
+
request(args: AluviaApiRequestArgs): Promise<{
|
|
25
|
+
status: number;
|
|
26
|
+
etag: string | null;
|
|
27
|
+
body: unknown | null;
|
|
28
|
+
}>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Account, AccountConnection, AccountConnectionDeleteResult, AccountPayment, AccountUsage } from './types.js';
|
|
2
|
+
export type ApiRequestArgs = {
|
|
3
|
+
method: 'GET' | 'POST' | 'PATCH' | 'DELETE';
|
|
4
|
+
path: string;
|
|
5
|
+
query?: Record<string, string | number | boolean | null | undefined>;
|
|
6
|
+
body?: unknown;
|
|
7
|
+
headers?: Record<string, string>;
|
|
8
|
+
etag?: string | null;
|
|
9
|
+
};
|
|
10
|
+
export type ApiRequestResult = {
|
|
11
|
+
status: number;
|
|
12
|
+
etag: string | null;
|
|
13
|
+
body: unknown | null;
|
|
14
|
+
};
|
|
15
|
+
export type ApiContext = {
|
|
16
|
+
request: (args: ApiRequestArgs) => Promise<ApiRequestResult>;
|
|
17
|
+
};
|
|
18
|
+
export declare function createAccountApi(ctx: ApiContext): {
|
|
19
|
+
get: () => Promise<Account>;
|
|
20
|
+
usage: {
|
|
21
|
+
get: (params?: {
|
|
22
|
+
start?: string;
|
|
23
|
+
end?: string;
|
|
24
|
+
}) => Promise<AccountUsage>;
|
|
25
|
+
};
|
|
26
|
+
payments: {
|
|
27
|
+
list: (params?: {
|
|
28
|
+
start?: string;
|
|
29
|
+
end?: string;
|
|
30
|
+
}) => Promise<Array<AccountPayment>>;
|
|
31
|
+
};
|
|
32
|
+
connections: {
|
|
33
|
+
list: () => Promise<Array<AccountConnection>>;
|
|
34
|
+
create: (body: Record<string, unknown>) => Promise<AccountConnection>;
|
|
35
|
+
get: (connectionId: string | number, options?: {
|
|
36
|
+
etag?: string | null;
|
|
37
|
+
}) => Promise<AccountConnection | null>;
|
|
38
|
+
patch: (connectionId: string | number, body: Record<string, unknown>) => Promise<AccountConnection>;
|
|
39
|
+
delete: (connectionId: string | number) => Promise<AccountConnectionDeleteResult>;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';
|
|
2
|
+
export type RequestQuery = Record<string, string | number | boolean | null | undefined | Array<string | number | boolean>>;
|
|
3
|
+
export type RequestCoreOptions = {
|
|
4
|
+
apiBaseUrl: string;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
method: HttpMethod;
|
|
7
|
+
path: string;
|
|
8
|
+
query?: RequestQuery;
|
|
9
|
+
body?: unknown;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
ifNoneMatch?: string | null;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
fetch?: typeof fetch;
|
|
14
|
+
};
|
|
15
|
+
export type RequestCoreResult = {
|
|
16
|
+
status: number;
|
|
17
|
+
etag: string | null;
|
|
18
|
+
body: unknown | null;
|
|
19
|
+
};
|
|
20
|
+
export declare function requestCore(options: RequestCoreOptions): Promise<RequestCoreResult>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type SuccessEnvelope<T> = {
|
|
2
|
+
success: true;
|
|
3
|
+
data: T;
|
|
4
|
+
};
|
|
5
|
+
export type ErrorEnvelope = {
|
|
6
|
+
success: false;
|
|
7
|
+
error: {
|
|
8
|
+
code: string;
|
|
9
|
+
message: string;
|
|
10
|
+
details?: unknown;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export type Envelope<T> = SuccessEnvelope<T> | ErrorEnvelope;
|
|
14
|
+
export type Account = Record<string, unknown>;
|
|
15
|
+
export type AccountUsage = Record<string, unknown>;
|
|
16
|
+
export type AccountPayment = Record<string, unknown>;
|
|
17
|
+
export type AccountConnection = {
|
|
18
|
+
id?: string | number;
|
|
19
|
+
connection_id?: string;
|
|
20
|
+
proxy_username?: string;
|
|
21
|
+
proxy_password?: string;
|
|
22
|
+
rules?: string[];
|
|
23
|
+
session_id?: string | null;
|
|
24
|
+
target_geo?: string | null;
|
|
25
|
+
} & Record<string, unknown>;
|
|
26
|
+
export type AccountConnectionDeleteResult = {
|
|
27
|
+
connection_id: string;
|
|
28
|
+
deleted: boolean;
|
|
29
|
+
};
|
|
30
|
+
export type Geo = Record<string, unknown>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { AluviaClientConnection, AluviaClientOptions } from './types.js';
|
|
2
|
+
import { AluviaApi } from '../api/AluviaApi.js';
|
|
3
|
+
/**
|
|
4
|
+
* AluviaClient is the main entry point for the Aluvia Client.
|
|
5
|
+
*
|
|
6
|
+
* It manages the local proxy server and configuration polling.
|
|
7
|
+
*/
|
|
8
|
+
export declare class AluviaClient {
|
|
9
|
+
private readonly options;
|
|
10
|
+
private readonly configManager;
|
|
11
|
+
private readonly proxyServer;
|
|
12
|
+
private connection;
|
|
13
|
+
private started;
|
|
14
|
+
private startPromise;
|
|
15
|
+
private readonly logger;
|
|
16
|
+
readonly api: AluviaApi;
|
|
17
|
+
constructor(options: AluviaClientOptions);
|
|
18
|
+
/**
|
|
19
|
+
* Start the Aluvia Client connection:
|
|
20
|
+
* - Fetch initial account connection config from Aluvia.
|
|
21
|
+
* - Start polling for config updates.
|
|
22
|
+
* - If localProxy is enabled (default): start a local HTTP proxy on 127.0.0.1:<localPort or free port>.
|
|
23
|
+
* - If localProxy is disabled: do NOT start a local proxy; adapters use gateway proxy settings.
|
|
24
|
+
*
|
|
25
|
+
* Returns the active connection with host/port/url and a stop() method.
|
|
26
|
+
*/
|
|
27
|
+
start(): Promise<AluviaClientConnection>;
|
|
28
|
+
/**
|
|
29
|
+
* Global cleanup:
|
|
30
|
+
* - Stop the local proxy server (if running).
|
|
31
|
+
* - Stop config polling.
|
|
32
|
+
*/
|
|
33
|
+
stop(): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Update the filtering rules used by the proxy.
|
|
36
|
+
* @param rules
|
|
37
|
+
*/
|
|
38
|
+
updateRules(rules: Array<string>): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Update the upstream session_id.
|
|
41
|
+
* @param sessionId
|
|
42
|
+
*/
|
|
43
|
+
updateSessionId(sessionId: string): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Update the upstream target_geo (geo targeting).
|
|
46
|
+
*
|
|
47
|
+
* Pass null to clear geo targeting.
|
|
48
|
+
*/
|
|
49
|
+
updateTargetGeo(targetGeo: string | null): Promise<void>;
|
|
50
|
+
}
|