@revenexx/integrations-node-sdk 1.8.1 → 1.9.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.
- package/dist/conditions.d.ts +16 -0
- package/dist/credentialType.d.ts +17 -0
- package/dist/credentials.d.ts +162 -0
- package/dist/errors.d.ts +5 -0
- package/dist/extract.d.ts +5 -0
- package/dist/fetch.d.ts +111 -0
- package/dist/images.d.ts +28 -0
- package/dist/index.cjs +17 -1
- package/dist/index.d.ts +14 -1254
- package/dist/index.js +17 -1
- package/dist/localized.d.ts +15 -0
- package/dist/manifest.d.ts +83 -0
- package/dist/nodeErrors.d.ts +61 -0
- package/dist/operators.d.ts +27 -0
- package/dist/retry.d.ts +75 -0
- package/dist/ssrf.d.ts +88 -0
- package/dist/types.d.ts +620 -0
- package/package.json +4 -4
- package/dist/index.d.cts +0 -1254
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IShowIfCondition } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether a setting applies, given what the author has filled in so far.
|
|
4
|
+
*
|
|
5
|
+
* A setting with no condition always applies — which is what makes `showIf`
|
|
6
|
+
* additive: a node that says nothing gets the behaviour it has today, and an
|
|
7
|
+
* editor that does not know the key draws every field as before.
|
|
8
|
+
*
|
|
9
|
+
* The value the condition reads is taken from the config as it stands mid-edit,
|
|
10
|
+
* so it is routinely `undefined`: the moment a node is dropped on the canvas
|
|
11
|
+
* nothing is filled in. That is why `equals` against a missing value is `false`
|
|
12
|
+
* rather than an error — the setting does not apply *yet*.
|
|
13
|
+
*/
|
|
14
|
+
export declare function settingApplies(field: {
|
|
15
|
+
showIf?: IShowIfCondition;
|
|
16
|
+
}, config: Record<string, unknown>): boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reduce an `IConfigField.credentialType` (a single slug or an array of slugs)
|
|
3
|
+
* to a normalized, deduplicated `string[]`.
|
|
4
|
+
*
|
|
5
|
+
* The union `string | string[]` exists so node authors can write the common
|
|
6
|
+
* single-type case as a bare string, but every consumer (the editor that
|
|
7
|
+
* lists tenant credential instances, the Laravel side) needs a list to
|
|
8
|
+
* iterate. This centralizes the
|
|
9
|
+
* "is it a string or an array?" branch — analogous to {@link normalizeLocalized}
|
|
10
|
+
* for `LocalizedString` — so the rules are defined once:
|
|
11
|
+
*
|
|
12
|
+
* - a single non-empty slug becomes a one-element array;
|
|
13
|
+
* - an array is trimmed of blank entries and deduplicated, preserving order;
|
|
14
|
+
* - `undefined`, an empty string, or an empty/all-blank array yields `[]`, so
|
|
15
|
+
* the caller can treat "accepts nothing" uniformly.
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizeCredentialType(value: string | string[] | undefined): string[];
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { ICredential, ICredentialContext, ICredentialDescription, ICredentialOAuthAuthorize, ICredentialResolveResult, ICredentialTestResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reusable credential base classes. Concrete credential types (in node
|
|
4
|
+
* packages) extend one of these and only supply field mappings / endpoints —
|
|
5
|
+
* the auth strategy itself lives here so it is implemented once.
|
|
6
|
+
*
|
|
7
|
+
* All of this runs in the credentials broker (a Node side-container), never in
|
|
8
|
+
* workflow code, so wall-clock time and network I/O are fine.
|
|
9
|
+
*/
|
|
10
|
+
type Config = Record<string, unknown>;
|
|
11
|
+
type DurableCreds = Record<string, unknown> | null;
|
|
12
|
+
export interface OAuthTokenResponse {
|
|
13
|
+
access_token?: string;
|
|
14
|
+
refresh_token?: string;
|
|
15
|
+
expires_in?: number;
|
|
16
|
+
token_type?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Provider-specific base URL the access token must target (e.g. Pipedrive
|
|
19
|
+
* returns the company-scoped `api_domain` on both the initial exchange and
|
|
20
|
+
* every refresh). Absent for providers that use a single global API host.
|
|
21
|
+
*/
|
|
22
|
+
api_domain?: string;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The base for every credential type. Stores the published description and
|
|
27
|
+
* provides a small HTTP helper. `resolve` is abstract; `test` defaults to a
|
|
28
|
+
* presence check and should be overridden where a real connection test exists.
|
|
29
|
+
*/
|
|
30
|
+
export declare abstract class BaseCredential implements ICredential {
|
|
31
|
+
abstract readonly description: ICredentialDescription;
|
|
32
|
+
abstract resolve(ctx: ICredentialContext, config: Config, durableCreds: DurableCreds): Promise<ICredentialResolveResult>;
|
|
33
|
+
test(_ctx: ICredentialContext, config: Config): Promise<ICredentialTestResult>;
|
|
34
|
+
/**
|
|
35
|
+
* POST `application/x-www-form-urlencoded` and parse a JSON token response.
|
|
36
|
+
*
|
|
37
|
+
* Goes through {@link safeFetch}, not a raw `fetch` (PO-185). The token
|
|
38
|
+
* endpoint is not a constant: `tokenUrl(config)` derives it from credential
|
|
39
|
+
* config, i.e. from a value someone types into a credential form. A raw fetch
|
|
40
|
+
* would happily POST the client secret at `http://169.254.169.254/…` or any
|
|
41
|
+
* other internal address, which is precisely what `assertPublicUrl` exists to
|
|
42
|
+
* prevent. Every OAuth token exchange in this file — client-credentials,
|
|
43
|
+
* auth-code, refresh — funnels through here, so this is the one call site that
|
|
44
|
+
* has to hold.
|
|
45
|
+
*
|
|
46
|
+
* Two behaviours safeFetch adds, both wanted here: a request budget
|
|
47
|
+
* (`DEFAULT_TIMEOUT_MS`) on top of `ctx.signal`, and manual redirect handling
|
|
48
|
+
* that re-checks each hop and drops `Authorization` across an origin boundary.
|
|
49
|
+
* Note that a 301/302 answer to this POST is downgraded to a bodiless GET —
|
|
50
|
+
* per the redirect rules, not a quirk of ours. Real token endpoints do not
|
|
51
|
+
* redirect; one that does was never going to complete the exchange anyway.
|
|
52
|
+
*
|
|
53
|
+
* The third protection is the caller's own: safeFetch hands back a `Response`
|
|
54
|
+
* and reading it is not its job, so the answer goes through {@link readText}
|
|
55
|
+
* under {@link MAX_TOKEN_RESPONSE_BYTES} rather than a bare `res.text()`.
|
|
56
|
+
* Both branches below read that same string, so the cap covers the refusal as
|
|
57
|
+
* well as the token — an error body is the less predictable of the two.
|
|
58
|
+
*/
|
|
59
|
+
protected postForm(ctx: ICredentialContext, url: string, form: Record<string, string | undefined>, headers?: Record<string, string>): Promise<OAuthTokenResponse>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* "Simple value retrieval": the validated config fields ARE the access data
|
|
63
|
+
* handed to the node (e.g. SMTP host/port/user/password, SFTP host/key). No
|
|
64
|
+
* token, no expiry. Override `test` to add a real connection check.
|
|
65
|
+
*/
|
|
66
|
+
export declare abstract class SimpleValueCredential extends BaseCredential {
|
|
67
|
+
resolve(_ctx: ICredentialContext, config: Config, _durableCreds: DurableCreds): Promise<ICredentialResolveResult>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* API-key credential. Reads the key from the configured field and hands it to
|
|
71
|
+
* the node under that same name; override {@link credentialShape} where the
|
|
72
|
+
* node needs something else, or more than the key alone.
|
|
73
|
+
*/
|
|
74
|
+
export declare abstract class ApiKeyCredential extends BaseCredential {
|
|
75
|
+
/**
|
|
76
|
+
* Which field of the credential form the key is typed into. Override it to
|
|
77
|
+
* name the form field this provider calls its key — `botToken`, `authKey`,
|
|
78
|
+
* `webhookUrl`.
|
|
79
|
+
*
|
|
80
|
+
* **What this does not do** (PO-497): it does not decide what a node looks
|
|
81
|
+
* the key up under at run time. It used to do only half of that — it changed
|
|
82
|
+
* which field was *read* while {@link credentialShape} went on emitting
|
|
83
|
+
* `apiKey` — so a credential that renamed its form field alone handed the
|
|
84
|
+
* node a key under a name it was never told to expect, and the node refused
|
|
85
|
+
* a perfectly good key as a missing one. The default shape now follows this
|
|
86
|
+
* name, so overriding this alone is correct on both sides. Overriding
|
|
87
|
+
* {@link credentialShape} as well is still how a credential emits a
|
|
88
|
+
* different name, or more than the key.
|
|
89
|
+
*
|
|
90
|
+
* Note that no node test can see a mismatch between the two: a mocked node
|
|
91
|
+
* context is seeded with the resolved blob directly and never calls
|
|
92
|
+
* `resolve`. Only a credential test asserting the whole resolved shape
|
|
93
|
+
* catches it, and asserting it loosely still misses — a partial match is
|
|
94
|
+
* happy with the wrong key present beside the right one.
|
|
95
|
+
*/
|
|
96
|
+
protected apiKeyField(): string;
|
|
97
|
+
/**
|
|
98
|
+
* What the node receives. Defaults to the key under the name
|
|
99
|
+
* {@link apiKeyField} returns, so the two cannot drift apart by default.
|
|
100
|
+
*
|
|
101
|
+
* Override it where the node needs a different name than the form uses, or
|
|
102
|
+
* needs more than the key — a base URL derived from it, say.
|
|
103
|
+
*/
|
|
104
|
+
protected credentialShape(apiKey: string): Record<string, unknown>;
|
|
105
|
+
resolve(_ctx: ICredentialContext, config: Config, _durableCreds: DurableCreds): Promise<ICredentialResolveResult>;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* HTTP Basic auth credential. Exposes `{ username, password }` by default.
|
|
109
|
+
*/
|
|
110
|
+
export declare abstract class BasicAuthCredential extends BaseCredential {
|
|
111
|
+
protected usernameField(): string;
|
|
112
|
+
protected passwordField(): string;
|
|
113
|
+
resolve(_ctx: ICredentialContext, config: Config, _durableCreds: DurableCreds): Promise<ICredentialResolveResult>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* OAuth2 client-credentials grant (service-to-service, no user, no
|
|
117
|
+
* refresh_token). Concrete types supply the token endpoint + client creds via
|
|
118
|
+
* the config field map. Robust to downtime: a fresh access token is minted on
|
|
119
|
+
* every resolve when the broker cache misses.
|
|
120
|
+
*/
|
|
121
|
+
export declare abstract class OAuth2ClientCredentialsCredential extends BaseCredential {
|
|
122
|
+
protected abstract tokenUrl(config: Config): string;
|
|
123
|
+
protected abstract clientId(config: Config): string;
|
|
124
|
+
protected abstract clientSecret(config: Config): string;
|
|
125
|
+
protected scope(_config: Config): string | undefined;
|
|
126
|
+
/** Map the raw token response to the node-facing credentials. */
|
|
127
|
+
protected credentialShape(token: OAuthTokenResponse): Record<string, unknown>;
|
|
128
|
+
resolve(ctx: ICredentialContext, config: Config, _durableCreds: DurableCreds): Promise<ICredentialResolveResult>;
|
|
129
|
+
test(ctx: ICredentialContext, config: Config): Promise<ICredentialTestResult>;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* OAuth2 authorization-code grant (3-legged, interactive consent). Implements
|
|
133
|
+
* the consent dance (with PKCE by default), code exchange, and refresh-token
|
|
134
|
+
* based resolution. The long-lived `refresh_token` is stored in durableCreds;
|
|
135
|
+
* if the provider rotates it, the new one is persisted via the context.
|
|
136
|
+
*/
|
|
137
|
+
export declare abstract class OAuth2AuthCodeCredential extends BaseCredential implements ICredentialOAuthAuthorize {
|
|
138
|
+
protected abstract authorizeUrl(config: Config): string;
|
|
139
|
+
protected abstract tokenUrl(config: Config): string;
|
|
140
|
+
protected abstract clientId(config: Config): string;
|
|
141
|
+
protected abstract clientSecret(config: Config): string;
|
|
142
|
+
protected scope(_config: Config): string | undefined;
|
|
143
|
+
protected usePkce(): boolean;
|
|
144
|
+
protected credentialShape(token: OAuthTokenResponse): Record<string, unknown>;
|
|
145
|
+
buildAuthorizeUrl(_ctx: ICredentialContext, config: Config, params: {
|
|
146
|
+
redirectUri: string;
|
|
147
|
+
state: string;
|
|
148
|
+
}): Promise<{
|
|
149
|
+
authorizeUrl: string;
|
|
150
|
+
codeVerifier?: string;
|
|
151
|
+
}>;
|
|
152
|
+
exchangeCode(ctx: ICredentialContext, config: Config, params: {
|
|
153
|
+
code: string;
|
|
154
|
+
redirectUri: string;
|
|
155
|
+
codeVerifier?: string;
|
|
156
|
+
}): Promise<{
|
|
157
|
+
durableCreds: Record<string, unknown>;
|
|
158
|
+
}>;
|
|
159
|
+
resolve(ctx: ICredentialContext, config: Config, durableCreds: DurableCreds): Promise<ICredentialResolveResult>;
|
|
160
|
+
test(_ctx: ICredentialContext, config: Config): Promise<ICredentialTestResult>;
|
|
161
|
+
}
|
|
162
|
+
export {};
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ICredential, ICredentialDescription, INode, INodeDescription } from './types.js';
|
|
2
|
+
export declare function extractManifest(node: INode): INodeDescription;
|
|
3
|
+
export declare function extractManifests(nodes: INode[]): INodeDescription[];
|
|
4
|
+
export declare function extractCredentialManifest(credential: ICredential): ICredentialDescription;
|
|
5
|
+
export declare function extractCredentialManifests(credentials: ICredential[]): ICredentialDescription[];
|
package/dist/fetch.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { IConfigField } from './types.js';
|
|
2
|
+
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
3
|
+
export declare const MAX_TIMEOUT_MS = 120000;
|
|
4
|
+
/** Maximum number of redirect hops `safeFetch` follows before giving up. */
|
|
5
|
+
export declare const MAX_REDIRECTS = 5;
|
|
6
|
+
/**
|
|
7
|
+
* Whether a request header with this name goes on to the next hop when
|
|
8
|
+
* {@link safeFetch} follows a redirect to another origin — `true` for `Accept`,
|
|
9
|
+
* `Accept-Encoding`, `Accept-Language`, `Content-Encoding`, `Content-Language`,
|
|
10
|
+
* `Content-Type`, `Idempotency-Key`, `If-Match`, `If-Modified-Since`,
|
|
11
|
+
* `If-None-Match`, `If-Range`, `If-Unmodified-Since`, `Range`, `SOAPAction` and
|
|
12
|
+
* `User-Agent`, in any case, and `false` for every other name. The keep-list is by
|
|
13
|
+
* name, so it can only keep a secret out of the headers it drops: a credential
|
|
14
|
+
* whose header name the author chooses must refuse a name this answers `true`
|
|
15
|
+
* for, or its key would cross with the harmless headers.
|
|
16
|
+
*/
|
|
17
|
+
export declare function headerCrossesOrigin(name: string): boolean;
|
|
18
|
+
export declare const DEFAULT_RETRY_ATTEMPTS = 0;
|
|
19
|
+
export declare const MAX_RETRY_ATTEMPTS = 5;
|
|
20
|
+
export declare const DEFAULT_RETRY_DELAY_MS = 1000;
|
|
21
|
+
/**
|
|
22
|
+
* Default cap for response bodies read via {@link readArrayBuffer} /
|
|
23
|
+
* {@link readText} / {@link readJsonOrText}. Guards the (shared) worker process
|
|
24
|
+
* against a single oversized response exhausting its memory.
|
|
25
|
+
*/
|
|
26
|
+
export declare const DEFAULT_MAX_RESPONSE_BYTES: number;
|
|
27
|
+
/**
|
|
28
|
+
* Hard upper ceiling for any per-node `maxBytes`. Even a permissive node author
|
|
29
|
+
* (who calls {@link maxBytesConfigField} without an explicit `max`, or passes a
|
|
30
|
+
* large `maxBytes` to the `read*` helpers) can never lift the cap above this, so
|
|
31
|
+
* the shared worker's memory stays bounded. Analogous to {@link MAX_TIMEOUT_MS}.
|
|
32
|
+
*/
|
|
33
|
+
export declare const MAX_RESPONSE_BYTES: number;
|
|
34
|
+
/** Clamp a requested `maxBytes` into `[1, MAX_RESPONSE_BYTES]`. */
|
|
35
|
+
export declare function clampResponseBytes(maxBytes: number): number;
|
|
36
|
+
export interface SafeFetchRetry {
|
|
37
|
+
attempts: number;
|
|
38
|
+
delayMs?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface SafeFetchOptions extends RequestInit {
|
|
41
|
+
timeoutMs?: number;
|
|
42
|
+
/** Pass `ctx.signal` so the workflow engine can cancel the request. */
|
|
43
|
+
signal?: AbortSignal;
|
|
44
|
+
retry?: SafeFetchRetry;
|
|
45
|
+
}
|
|
46
|
+
export declare function safeFetch(url: string | URL, options?: SafeFetchOptions): Promise<Response>;
|
|
47
|
+
/**
|
|
48
|
+
* Read a response body into an ArrayBuffer while enforcing a hard byte cap.
|
|
49
|
+
*
|
|
50
|
+
* The `Content-Length` header is used as a fast-reject (bail before downloading
|
|
51
|
+
* anything), but the limit is *also* enforced while streaming, since the header
|
|
52
|
+
* can be absent or lie. On overrun the stream is cancelled and a
|
|
53
|
+
* `NodeError('RESPONSE_TOO_LARGE', …, { status })` is thrown. Both refusal paths
|
|
54
|
+
* cancel the body first, so neither leaves the connection held.
|
|
55
|
+
*/
|
|
56
|
+
export declare function readArrayBuffer(res: Response, maxBytes?: number): Promise<ArrayBuffer>;
|
|
57
|
+
/** Read a response body as UTF-8 text, capped at `maxBytes` (see {@link readArrayBuffer}). */
|
|
58
|
+
export declare function readText(res: Response, maxBytes?: number): Promise<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Read a response body as JSON when the `Content-Type` denotes JSON (see
|
|
61
|
+
* {@link isJsonContentType}), otherwise as text — the content-type sniff
|
|
62
|
+
* previously duplicated across the HTTP/Upload/DeepL node sinks. Capped at
|
|
63
|
+
* `maxBytes` (see {@link readArrayBuffer}).
|
|
64
|
+
*
|
|
65
|
+
* A malformed JSON body surfaces as `NodeError('RESPONSE_PARSE_ERROR', …, { status })`
|
|
66
|
+
* rather than a raw `SyntaxError`, keeping to the SDK error contract.
|
|
67
|
+
*/
|
|
68
|
+
export declare function readJsonOrText(res: Response, maxBytes?: number): Promise<unknown>;
|
|
69
|
+
/**
|
|
70
|
+
* The setting a node offers its workflow author for the response size cap,
|
|
71
|
+
* ready to spread into `description.config`.
|
|
72
|
+
*
|
|
73
|
+
* **What the node still has to do** (PO-497): declaring the setting does not
|
|
74
|
+
* wire it up. Read the configured value in `execute` and pass it on — the
|
|
75
|
+
* helpers here only produce the declaration, and a node that spreads this and
|
|
76
|
+
* never reads `config.maxBytes` offers a setting that does nothing.
|
|
77
|
+
*
|
|
78
|
+
* Everything an author reads is supplied in both languages the node packages
|
|
79
|
+
* write, so spreading this unchanged already satisfies the rule they hold.
|
|
80
|
+
* Nothing is left to the caller on that side: a node that wants different
|
|
81
|
+
* words replaces the field rather than completing it.
|
|
82
|
+
*/
|
|
83
|
+
export declare function maxBytesConfigField(opts?: {
|
|
84
|
+
default?: number;
|
|
85
|
+
max?: number;
|
|
86
|
+
}): IConfigField;
|
|
87
|
+
/**
|
|
88
|
+
* The setting a node offers its workflow author for the request budget, ready
|
|
89
|
+
* to spread into `description.config`.
|
|
90
|
+
*
|
|
91
|
+
* **What the node still has to do** (PO-497): pass the configured value to
|
|
92
|
+
* `safeFetch` as `timeoutMs`. See {@link maxBytesConfigField} for the same note
|
|
93
|
+
* and for why the label and description are not the caller's to complete.
|
|
94
|
+
*/
|
|
95
|
+
export declare function timeoutConfigField(opts?: {
|
|
96
|
+
default?: number;
|
|
97
|
+
max?: number;
|
|
98
|
+
}): IConfigField;
|
|
99
|
+
/**
|
|
100
|
+
* The two settings a node offers its workflow author for the retry budget,
|
|
101
|
+
* ready to spread into `description.config`.
|
|
102
|
+
*
|
|
103
|
+
* **What the node still has to do** (PO-497): pass both configured values to
|
|
104
|
+
* `safeFetch` as `retry.attempts` and `retry.delayMs`. See
|
|
105
|
+
* {@link maxBytesConfigField} for the same note and for why the labels and
|
|
106
|
+
* descriptions are not the caller's to complete.
|
|
107
|
+
*/
|
|
108
|
+
export declare function retryConfigFields(opts?: {
|
|
109
|
+
defaultAttempts?: number;
|
|
110
|
+
defaultDelayMs?: number;
|
|
111
|
+
}): IConfigField[];
|
package/dist/images.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { IImage } from './types.js';
|
|
2
|
+
/** Something that may carry an `images` array (a node/credential/template manifest). */
|
|
3
|
+
interface WithImages {
|
|
4
|
+
images?: IImage[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Collect the unique `src` of every image declared across all nodes,
|
|
8
|
+
* credential types, and templates in a manifest.
|
|
9
|
+
*/
|
|
10
|
+
export declare function collectImageSources(manifest: {
|
|
11
|
+
nodes?: WithImages[];
|
|
12
|
+
credentials?: WithImages[];
|
|
13
|
+
templates?: WithImages[];
|
|
14
|
+
}): string[];
|
|
15
|
+
/**
|
|
16
|
+
* Copy declared image files into `dist/`, preserving their sub-path
|
|
17
|
+
* (`images/x.png` → `dist/images/x.png`) so `npm pack` picks them up via the
|
|
18
|
+
* package's `files: ["dist"]`. Sources are resolved relative to `rootDir`
|
|
19
|
+
* (the package root, `process.cwd()`).
|
|
20
|
+
*
|
|
21
|
+
* Every problematic declaration warns and is skipped rather than throwing, so a
|
|
22
|
+
* stale manifest never breaks the build: this covers a missing file, a `src`
|
|
23
|
+
* that is a directory rather than a file, and — defensively — an unsafe `src`
|
|
24
|
+
* that is absolute or escapes the package root via `..` traversal (which would
|
|
25
|
+
* otherwise read/write outside `dist/`).
|
|
26
|
+
*/
|
|
27
|
+
export declare function copyImages(sources: string[], outDir: string, rootDir: string): void;
|
|
28
|
+
export {};
|
package/dist/index.cjs
CHANGED
|
@@ -572,6 +572,15 @@ async function guardUrl(url, ctxSignal, effectiveMs) {
|
|
|
572
572
|
clearTimeout(timer);
|
|
573
573
|
}
|
|
574
574
|
}
|
|
575
|
+
function hasContent(body) {
|
|
576
|
+
if (body == null) return false;
|
|
577
|
+
if (typeof body === "string") return body.length > 0;
|
|
578
|
+
if (body instanceof ArrayBuffer) return body.byteLength > 0;
|
|
579
|
+
if (ArrayBuffer.isView(body)) return body.byteLength > 0;
|
|
580
|
+
if (body instanceof Blob) return body.size > 0;
|
|
581
|
+
if (body instanceof URLSearchParams) return body.toString().length > 0;
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
575
584
|
async function guardedFetch(url, fetchOptions, ctxSignal, effectiveMs) {
|
|
576
585
|
let currentUrl = url;
|
|
577
586
|
let currentInit = { ...fetchOptions, redirect: "manual" };
|
|
@@ -604,7 +613,6 @@ async function guardedFetch(url, fetchOptions, ctxSignal, effectiveMs) {
|
|
|
604
613
|
if (base.protocol === "https:" && nextUrl.protocol !== "https:") {
|
|
605
614
|
throw new NodeError("BLOCKED_ADDRESS", "Blocked https\u2192http downgrade on redirect", { status: 0 });
|
|
606
615
|
}
|
|
607
|
-
await guardUrl(nextUrl, ctxSignal, effectiveMs);
|
|
608
616
|
const headers = new Headers(currentInit.headers);
|
|
609
617
|
let method = (currentInit.method ?? "GET").toUpperCase();
|
|
610
618
|
let body = currentInit.body;
|
|
@@ -614,6 +622,14 @@ async function guardedFetch(url, fetchOptions, ctxSignal, effectiveMs) {
|
|
|
614
622
|
headers.delete("content-type");
|
|
615
623
|
headers.delete("content-length");
|
|
616
624
|
}
|
|
625
|
+
if (hasContent(body) && nextUrl.origin !== base.origin) {
|
|
626
|
+
throw new NodeError(
|
|
627
|
+
"BLOCKED_ADDRESS",
|
|
628
|
+
`Blocked ${redirectStatus} redirect to another origin: it would resend the request body to ${nextUrl.origin}`,
|
|
629
|
+
{ status: 0 }
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
await guardUrl(nextUrl, ctxSignal, effectiveMs);
|
|
617
633
|
if (nextUrl.origin !== base.origin) {
|
|
618
634
|
for (const name of [...headers.keys()]) {
|
|
619
635
|
if (!headerCrossesOrigin(name)) headers.delete(name);
|