@cat-factory/integrations 0.25.2 → 0.26.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/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/documents/ClaudeDesignProvider.d.ts +32 -0
- package/dist/modules/documents/ClaudeDesignProvider.d.ts.map +1 -0
- package/dist/modules/documents/ClaudeDesignProvider.js +163 -0
- package/dist/modules/documents/ClaudeDesignProvider.js.map +1 -0
- package/dist/modules/documents/ConfluenceProvider.d.ts +2 -1
- package/dist/modules/documents/ConfluenceProvider.d.ts.map +1 -1
- package/dist/modules/documents/DocumentConnectionService.d.ts +28 -11
- package/dist/modules/documents/DocumentConnectionService.d.ts.map +1 -1
- package/dist/modules/documents/DocumentConnectionService.js +71 -12
- package/dist/modules/documents/DocumentConnectionService.js.map +1 -1
- package/dist/modules/documents/DocumentImportService.d.ts +9 -3
- package/dist/modules/documents/DocumentImportService.d.ts.map +1 -1
- package/dist/modules/documents/DocumentImportService.js +11 -5
- package/dist/modules/documents/DocumentImportService.js.map +1 -1
- package/dist/modules/documents/FigmaProvider.d.ts +42 -0
- package/dist/modules/documents/FigmaProvider.d.ts.map +1 -0
- package/dist/modules/documents/FigmaProvider.js +176 -0
- package/dist/modules/documents/FigmaProvider.js.map +1 -0
- package/dist/modules/documents/GitHubDocsProvider.d.ts +2 -1
- package/dist/modules/documents/GitHubDocsProvider.d.ts.map +1 -1
- package/dist/modules/documents/LinearDocumentProvider.d.ts +2 -1
- package/dist/modules/documents/LinearDocumentProvider.d.ts.map +1 -1
- package/dist/modules/documents/NotionProvider.d.ts +2 -1
- package/dist/modules/documents/NotionProvider.d.ts.map +1 -1
- package/dist/modules/documents/claudeDesign.logic.d.ts +88 -0
- package/dist/modules/documents/claudeDesign.logic.d.ts.map +1 -0
- package/dist/modules/documents/claudeDesign.logic.js +317 -0
- package/dist/modules/documents/claudeDesign.logic.js.map +1 -0
- package/dist/modules/documents/figma.logic.d.ts +98 -0
- package/dist/modules/documents/figma.logic.d.ts.map +1 -0
- package/dist/modules/documents/figma.logic.js +249 -0
- package/dist/modules/documents/figma.logic.js.map +1 -0
- package/dist/modules/documents/http.d.ts +35 -0
- package/dist/modules/documents/http.d.ts.map +1 -0
- package/dist/modules/documents/http.js +120 -0
- package/dist/modules/documents/http.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { assertHostPinned } from './http.js';
|
|
2
|
+
// Figma-specific pure logic, kept out of the provider shell so it is unit-testable
|
|
3
|
+
// without a live workspace: parsing/canonicalising a file+node ref out of user input,
|
|
4
|
+
// the fixed-host SSRF guard, and converting the Figma node/variables JSON into the
|
|
5
|
+
// lightweight Markdown the generic planner + `.cat-context/` materialisation consume.
|
|
6
|
+
// The `fetch` itself (the `X-Figma-Token` REST client) lives in `FigmaProvider`.
|
|
7
|
+
/** Figma's REST API host. The PAT is sent to this host only — see {@link assertSafeFigmaUrl}. */
|
|
8
|
+
export const FIGMA_API_HOST = 'api.figma.com';
|
|
9
|
+
/** What the connect UI renders, and which credentials the provider needs. */
|
|
10
|
+
export const FIGMA_DESCRIPTOR = {
|
|
11
|
+
source: 'figma',
|
|
12
|
+
label: 'Figma',
|
|
13
|
+
icon: 'i-lucide-figma',
|
|
14
|
+
credentialFields: [
|
|
15
|
+
{
|
|
16
|
+
key: 'apiToken',
|
|
17
|
+
label: 'Personal access token',
|
|
18
|
+
secret: true,
|
|
19
|
+
placeholder: 'figd_…',
|
|
20
|
+
help: 'Create a personal access token at figma.com → Settings → Security → Personal access tokens (file_content + file_variables read scopes). Design tokens require an Enterprise plan; without it the tokens section is simply omitted.',
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
refLabel: 'Figma file or frame URL',
|
|
24
|
+
refPlaceholder: 'https://www.figma.com/design/<key>/Title?node-id=1-2',
|
|
25
|
+
// No catalogue search API for a PAT — import a specific file/frame by URL.
|
|
26
|
+
searchable: false,
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The Figma REST host is fixed, so any request/redirect must stay on
|
|
30
|
+
* `api.figma.com` over https. A redirect off-host (e.g. to an internal address)
|
|
31
|
+
* is treated as an SSRF attempt and rejected. Mirrors the per-hop guard the other
|
|
32
|
+
* document providers run. Throws a plain `Error` (the provider maps it to a
|
|
33
|
+
* `FigmaApiError`); kept pure so it is unit-testable without a network.
|
|
34
|
+
*/
|
|
35
|
+
export function assertSafeFigmaUrl(url) {
|
|
36
|
+
assertHostPinned(url, FIGMA_API_HOST, 'Figma');
|
|
37
|
+
}
|
|
38
|
+
// ---- Ref parsing + canonical URL ------------------------------------------
|
|
39
|
+
/**
|
|
40
|
+
* Normalise a Figma node id to the API/colon form. Figma share URLs encode a
|
|
41
|
+
* node id as `1234-5678` (dash) where the REST API expects `1234:5678` (colon);
|
|
42
|
+
* a `?node-id=1234%3A5678` decodes straight to the colon form. We accept either
|
|
43
|
+
* and return the colon form, or null for anything that isn't a simple
|
|
44
|
+
* `n` / `n:n` node id (complex instance ids like `I12:3;45:6` are dropped → the
|
|
45
|
+
* import falls back to the whole file rather than guessing).
|
|
46
|
+
*/
|
|
47
|
+
export function normalizeFigmaNodeId(raw) {
|
|
48
|
+
const value = raw.trim();
|
|
49
|
+
if (!value)
|
|
50
|
+
return null;
|
|
51
|
+
const colon = value.includes(':') ? value : value.replace(/-/g, ':');
|
|
52
|
+
if (/^\d+:\d+$/.test(colon) || /^\d+$/.test(colon))
|
|
53
|
+
return colon;
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve a Figma reference from raw user input into the stable composite
|
|
58
|
+
* external id this provider stores: `"<fileKey>"` for a whole-file link, or
|
|
59
|
+
* `"<fileKey>:<nodeId>"` for a specific frame/node (nodeId in colon form).
|
|
60
|
+
* Accepts a `figma.com` file/design/proto/board URL, a bare file key, or a
|
|
61
|
+
* `fileKey:node:id` string. Returns null when no file key is found.
|
|
62
|
+
*
|
|
63
|
+
* `parseRef` is deterministic (same input → same external id), which is what the
|
|
64
|
+
* `(workspace, source, externalId)` document key relies on for de-duplication —
|
|
65
|
+
* URL auto-match via `getByUrl` is a separate, best-effort path keyed on the
|
|
66
|
+
* canonical {@link figmaUrlFor} output.
|
|
67
|
+
*/
|
|
68
|
+
export function parseFigmaRef(input) {
|
|
69
|
+
const trimmed = input.trim();
|
|
70
|
+
if (!trimmed)
|
|
71
|
+
return null;
|
|
72
|
+
// Bare ref: a file key, optionally `:`-suffixed with a node id. Only when it is
|
|
73
|
+
// clearly not a URL (no scheme, no slash, not a figma.com host).
|
|
74
|
+
if (!trimmed.includes('/') && !/figma\.com/i.test(trimmed)) {
|
|
75
|
+
const [key, ...rest] = trimmed.split(':');
|
|
76
|
+
if (!key || !/^[A-Za-z0-9]+$/.test(key))
|
|
77
|
+
return null;
|
|
78
|
+
if (rest.length === 0)
|
|
79
|
+
return key;
|
|
80
|
+
const node = normalizeFigmaNodeId(rest.join(':'));
|
|
81
|
+
return node ? `${key}:${node}` : key;
|
|
82
|
+
}
|
|
83
|
+
let url;
|
|
84
|
+
try {
|
|
85
|
+
url = new URL(trimmed);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
if (!/(^|\.)figma\.com$/i.test(url.hostname))
|
|
91
|
+
return null;
|
|
92
|
+
const match = url.pathname.match(/\/(?:file|design|proto|board)\/([A-Za-z0-9]+)/);
|
|
93
|
+
if (!match)
|
|
94
|
+
return null;
|
|
95
|
+
const fileKey = match[1];
|
|
96
|
+
const nodeRaw = url.searchParams.get('node-id');
|
|
97
|
+
if (!nodeRaw)
|
|
98
|
+
return fileKey;
|
|
99
|
+
const node = normalizeFigmaNodeId(nodeRaw);
|
|
100
|
+
return node ? `${fileKey}:${node}` : fileKey;
|
|
101
|
+
}
|
|
102
|
+
/** Split a composite external id back into its file key and optional node id (colon form). */
|
|
103
|
+
export function splitFigmaExternalId(externalId) {
|
|
104
|
+
const idx = externalId.indexOf(':');
|
|
105
|
+
if (idx === -1)
|
|
106
|
+
return { fileKey: externalId };
|
|
107
|
+
return { fileKey: externalId.slice(0, idx), nodeId: externalId.slice(idx + 1) };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Build the canonical web URL stored on the imported document (and matched by
|
|
111
|
+
* `getByUrl`). The node id is rendered back in the share-URL dash form so the
|
|
112
|
+
* stored URL matches the kind of link a teammate pastes into a task description.
|
|
113
|
+
*/
|
|
114
|
+
export function figmaUrlFor(externalId) {
|
|
115
|
+
const { fileKey, nodeId } = splitFigmaExternalId(externalId);
|
|
116
|
+
const base = `https://www.figma.com/design/${fileKey}`;
|
|
117
|
+
if (!nodeId)
|
|
118
|
+
return base;
|
|
119
|
+
return `${base}?node-id=${nodeId.replace(/:/g, '-')}`;
|
|
120
|
+
}
|
|
121
|
+
const MAX_TREE_DEPTH = 6;
|
|
122
|
+
const MAX_TREE_NODES = 400;
|
|
123
|
+
function dimensionLabel(node) {
|
|
124
|
+
const box = node.absoluteBoundingBox;
|
|
125
|
+
if (!box || box.width == null || box.height == null)
|
|
126
|
+
return '';
|
|
127
|
+
return ` (${Math.round(box.width)}×${Math.round(box.height)})`;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Render a node and its descendants as an indented bullet tree (name + type +
|
|
131
|
+
* size), bounded in depth and total nodes so a huge frame can't blow up the
|
|
132
|
+
* context file. Mutates `counter` to enforce the global node cap.
|
|
133
|
+
*/
|
|
134
|
+
function renderLayout(node, depth, counter, lines) {
|
|
135
|
+
if (depth > MAX_TREE_DEPTH || counter.n >= MAX_TREE_NODES)
|
|
136
|
+
return;
|
|
137
|
+
counter.n++;
|
|
138
|
+
const indent = ' '.repeat(depth);
|
|
139
|
+
const name = node.name?.trim() || '(unnamed)';
|
|
140
|
+
const type = node.type ? ` _${node.type}_` : '';
|
|
141
|
+
lines.push(`${indent}- ${name}${type}${dimensionLabel(node)}`);
|
|
142
|
+
for (const child of node.children ?? []) {
|
|
143
|
+
if (counter.n >= MAX_TREE_NODES) {
|
|
144
|
+
lines.push(`${indent} - … (truncated)`);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
renderLayout(child, depth + 1, counter, lines);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** Collect every TEXT node's `characters`, in document order, bounded. */
|
|
151
|
+
function collectText(node, out) {
|
|
152
|
+
if (out.length >= MAX_TREE_NODES)
|
|
153
|
+
return;
|
|
154
|
+
if (node.type === 'TEXT' && node.characters?.trim())
|
|
155
|
+
out.push(node.characters.trim());
|
|
156
|
+
for (const child of node.children ?? [])
|
|
157
|
+
collectText(child, out);
|
|
158
|
+
}
|
|
159
|
+
/** Collect the distinct design-system components a node tree instantiates. */
|
|
160
|
+
function collectComponents(node, components, out) {
|
|
161
|
+
if (node.type === 'INSTANCE') {
|
|
162
|
+
const name = (node.componentId && components[node.componentId]?.name) || node.name?.trim();
|
|
163
|
+
if (name)
|
|
164
|
+
out.add(name);
|
|
165
|
+
}
|
|
166
|
+
for (const child of node.children ?? [])
|
|
167
|
+
collectComponents(child, components, out);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Convert a fetched Figma node (a frame or a whole file's document) into the
|
|
171
|
+
* lightweight Markdown the planner/agent consume: a `## <name>` heading, a
|
|
172
|
+
* `### Layout` bullet tree, the `### Text content`, and the distinct
|
|
173
|
+
* `### Components used` (matched against the repo's components by the agent).
|
|
174
|
+
*/
|
|
175
|
+
export function figmaNodesToMarkdown(roots, components = {}) {
|
|
176
|
+
const sections = [];
|
|
177
|
+
for (const root of roots) {
|
|
178
|
+
const lines = [];
|
|
179
|
+
lines.push(`## ${root.name?.trim() || '(unnamed frame)'}${dimensionLabel(root)}`);
|
|
180
|
+
const layout = [];
|
|
181
|
+
// One counter shared across every top-level child so MAX_TREE_NODES bounds the whole
|
|
182
|
+
// frame, not each subtree — a wide frame can't blow past the cap one branch at a time.
|
|
183
|
+
const counter = { n: 0 };
|
|
184
|
+
for (const child of root.children ?? [])
|
|
185
|
+
renderLayout(child, 0, counter, layout);
|
|
186
|
+
if (layout.length) {
|
|
187
|
+
lines.push('', '### Layout', ...layout);
|
|
188
|
+
}
|
|
189
|
+
const text = [];
|
|
190
|
+
collectText(root, text);
|
|
191
|
+
if (text.length) {
|
|
192
|
+
lines.push('', '### Text content', ...text.map((t) => `- ${t.replace(/\s+/g, ' ')}`));
|
|
193
|
+
}
|
|
194
|
+
const comps = new Set();
|
|
195
|
+
collectComponents(root, components, comps);
|
|
196
|
+
if (comps.size) {
|
|
197
|
+
lines.push('', '### Components used', ...[...comps].sort().map((c) => `- ${c}`));
|
|
198
|
+
}
|
|
199
|
+
sections.push(lines.join('\n').trim());
|
|
200
|
+
}
|
|
201
|
+
return sections.join('\n\n').trim();
|
|
202
|
+
}
|
|
203
|
+
/** Render a single variable value (colour object → hex/rgba, else compact JSON). */
|
|
204
|
+
function renderVariableValue(value) {
|
|
205
|
+
if (value && typeof value === 'object' && 'r' in value) {
|
|
206
|
+
const c = value;
|
|
207
|
+
const to255 = (n) => Math.round((n ?? 0) * 255);
|
|
208
|
+
const hex = [c.r, c.g, c.b].map((n) => to255(n).toString(16).padStart(2, '0')).join('');
|
|
209
|
+
return c.a != null && c.a < 1 ? `#${hex} (a=${c.a.toFixed(2)})` : `#${hex}`;
|
|
210
|
+
}
|
|
211
|
+
if (value && typeof value === 'object' && 'type' in value) {
|
|
212
|
+
// An alias to another variable — surface its target id.
|
|
213
|
+
const alias = value;
|
|
214
|
+
if (alias.type === 'VARIABLE_ALIAS' && alias.id)
|
|
215
|
+
return `→ ${alias.id}`;
|
|
216
|
+
}
|
|
217
|
+
if (typeof value === 'number')
|
|
218
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(2);
|
|
219
|
+
if (typeof value === 'string' || typeof value === 'boolean')
|
|
220
|
+
return String(value);
|
|
221
|
+
return JSON.stringify(value);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Convert the Figma local-variables payload into a `### Design tokens` section,
|
|
225
|
+
* one line per `collection › mode › name = value`. Returns '' when there are no
|
|
226
|
+
* variables (so the caller drops the section entirely).
|
|
227
|
+
*/
|
|
228
|
+
export function figmaVariablesToMarkdown(meta) {
|
|
229
|
+
const variables = meta?.variables ?? {};
|
|
230
|
+
const collections = meta?.variableCollections ?? {};
|
|
231
|
+
const lines = [];
|
|
232
|
+
for (const variable of Object.values(variables)) {
|
|
233
|
+
if (!variable?.name)
|
|
234
|
+
continue;
|
|
235
|
+
const collection = variable.variableCollectionId
|
|
236
|
+
? collections[variable.variableCollectionId]
|
|
237
|
+
: undefined;
|
|
238
|
+
const collectionName = collection?.name ?? 'Tokens';
|
|
239
|
+
const modes = collection?.modes ?? [];
|
|
240
|
+
for (const [modeId, value] of Object.entries(variable.valuesByMode ?? {})) {
|
|
241
|
+
const modeName = modes.find((m) => m.modeId === modeId)?.name ?? 'default';
|
|
242
|
+
lines.push(`- ${collectionName} › ${modeName} › ${variable.name} = ${renderVariableValue(value)}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (!lines.length)
|
|
246
|
+
return '';
|
|
247
|
+
return ['### Design tokens', ...lines.sort()].join('\n');
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=figma.logic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figma.logic.js","sourceRoot":"","sources":["../../../src/modules/documents/figma.logic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Carries the HTTP status so a provider can surface a meaningful error to the caller. */
|
|
2
|
+
export declare class DocumentHttpError extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
constructor(status: number, message: string);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Assert a request/redirect URL stays on the provider's fixed `host` over https. A
|
|
8
|
+
* redirect off-host (e.g. to a link-local metadata address) or an http downgrade is
|
|
9
|
+
* treated as an SSRF attempt and rejected. Throws a plain `Error` so it stays trivially
|
|
10
|
+
* unit-testable without a network; the caller maps it to a {@link DocumentHttpError}.
|
|
11
|
+
* `label` names the provider in the message (e.g. `Figma`, `Claude Design`).
|
|
12
|
+
*/
|
|
13
|
+
export declare function assertHostPinned(url: string, host: string, label: string): void;
|
|
14
|
+
export interface HostPinnedFetchOptions {
|
|
15
|
+
/** The single host this provider's API lives on (e.g. `api.figma.com`). */
|
|
16
|
+
host: string;
|
|
17
|
+
/** Provider name used in error messages. */
|
|
18
|
+
label: string;
|
|
19
|
+
/** Max redirect hops before giving up (default 5). */
|
|
20
|
+
maxRedirects?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build a `fetch` that follows redirects **by hand** so {@link assertHostPinned} runs
|
|
24
|
+
* against EVERY hop. With the default `redirect: 'follow'` a 302 from the API could be
|
|
25
|
+
* chased to an internal target (or downgraded to http), leaking the credential. We force
|
|
26
|
+
* `redirect: 'manual'`, re-resolve the `Location`, and re-validate before each hop.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createHostPinnedFetch(opts: HostPinnedFetchOptions): (url: string, init: RequestInit) => Promise<Response>;
|
|
29
|
+
/**
|
|
30
|
+
* Read a response body with a running byte cap so a hostile/huge response can't OOM the
|
|
31
|
+
* isolate. Checks the declared Content-Length first, then enforces the cap while
|
|
32
|
+
* streaming. `label` names the provider in the error message.
|
|
33
|
+
*/
|
|
34
|
+
export declare function readCappedText(res: Response, maxBytes: number, label?: string): Promise<string>;
|
|
35
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/modules/documents/http.ts"],"names":[],"mappings":"AAaA,0FAA0F;AAC1F,qBAAa,iBAAkB,SAAQ,KAAK;IAExC,QAAQ,CAAC,MAAM,EAAE,MAAM;IADzB,YACW,MAAM,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM,EAIhB;CACF;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAa/E;AAKD,MAAM,WAAW,sBAAsB;IACrC,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAA;IACZ,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAA;IACb,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,sBAAsB,GAC3B,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CA0BvD;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,QAAQ,EACb,QAAQ,EAAE,MAAM,EAChB,KAAK,SAAa,GACjB,OAAO,CAAC,MAAM,CAAC,CAgCjB"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Shared HTTP helpers for the host-pinned document-source providers (Figma, Claude
|
|
2
|
+
// Design, …). Every provider that talks to a *fixed* third-party API host shares the
|
|
3
|
+
// same two concerns: (1) keep the credential on the intended host — follow redirects
|
|
4
|
+
// by hand and re-validate every hop so a 302 can't chase the token to an internal
|
|
5
|
+
// address (SSRF) or downgrade it to http; (2) bound the bytes read off any response so
|
|
6
|
+
// a hostile/huge body can't OOM the isolate. Notion/Confluence each grew their own copy
|
|
7
|
+
// of this; it is hoisted here so a new fixed-host provider reuses it instead of
|
|
8
|
+
// re-deriving the per-hop guard (and the latent bug of forgetting it).
|
|
9
|
+
//
|
|
10
|
+
// The helper is host-*pinned*, not host-*allow-listed*: it serves providers whose API
|
|
11
|
+
// lives at a single known host. A site-configurable provider (Confluence) keeps its own
|
|
12
|
+
// guard because its allowed host is per-connection, not a constant.
|
|
13
|
+
/** Carries the HTTP status so a provider can surface a meaningful error to the caller. */
|
|
14
|
+
export class DocumentHttpError extends Error {
|
|
15
|
+
status;
|
|
16
|
+
constructor(status, message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.status = status;
|
|
19
|
+
this.name = 'DocumentHttpError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Assert a request/redirect URL stays on the provider's fixed `host` over https. A
|
|
24
|
+
* redirect off-host (e.g. to a link-local metadata address) or an http downgrade is
|
|
25
|
+
* treated as an SSRF attempt and rejected. Throws a plain `Error` so it stays trivially
|
|
26
|
+
* unit-testable without a network; the caller maps it to a {@link DocumentHttpError}.
|
|
27
|
+
* `label` names the provider in the message (e.g. `Figma`, `Claude Design`).
|
|
28
|
+
*/
|
|
29
|
+
export function assertHostPinned(url, host, label) {
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
parsed = new URL(url);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw new Error(`${label} request URL is invalid: ${url}`);
|
|
36
|
+
}
|
|
37
|
+
if (parsed.protocol !== 'https:') {
|
|
38
|
+
throw new Error(`${label} request must use https`);
|
|
39
|
+
}
|
|
40
|
+
if (parsed.hostname.toLowerCase() !== host.toLowerCase()) {
|
|
41
|
+
throw new Error(`${label} redirect to a disallowed host: ${parsed.hostname}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Bound the redirect chain so the fixed API host can't 302 us elsewhere indefinitely. */
|
|
45
|
+
const DEFAULT_MAX_REDIRECTS = 5;
|
|
46
|
+
/**
|
|
47
|
+
* Build a `fetch` that follows redirects **by hand** so {@link assertHostPinned} runs
|
|
48
|
+
* against EVERY hop. With the default `redirect: 'follow'` a 302 from the API could be
|
|
49
|
+
* chased to an internal target (or downgraded to http), leaking the credential. We force
|
|
50
|
+
* `redirect: 'manual'`, re-resolve the `Location`, and re-validate before each hop.
|
|
51
|
+
*/
|
|
52
|
+
export function createHostPinnedFetch(opts) {
|
|
53
|
+
const maxRedirects = opts.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
|
|
54
|
+
return async function safeFetch(url, init) {
|
|
55
|
+
let current = url;
|
|
56
|
+
for (let hop = 0;; hop++) {
|
|
57
|
+
try {
|
|
58
|
+
assertHostPinned(current, opts.host, opts.label);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
throw new DocumentHttpError(502, err instanceof Error ? err.message : `${opts.label} request blocked`);
|
|
62
|
+
}
|
|
63
|
+
const res = await fetch(current, { ...init, redirect: 'manual' });
|
|
64
|
+
if (res.status >= 300 && res.status < 400) {
|
|
65
|
+
const location = res.headers.get('location');
|
|
66
|
+
if (!location)
|
|
67
|
+
return res;
|
|
68
|
+
if (hop >= maxRedirects) {
|
|
69
|
+
throw new DocumentHttpError(502, `${opts.label} returned too many redirects`);
|
|
70
|
+
}
|
|
71
|
+
current = new URL(location, current).toString();
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
return res;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Read a response body with a running byte cap so a hostile/huge response can't OOM the
|
|
80
|
+
* isolate. Checks the declared Content-Length first, then enforces the cap while
|
|
81
|
+
* streaming. `label` names the provider in the error message.
|
|
82
|
+
*/
|
|
83
|
+
export async function readCappedText(res, maxBytes, label = 'Response') {
|
|
84
|
+
const declared = res.headers.get('content-length');
|
|
85
|
+
if (declared && Number(declared) > maxBytes) {
|
|
86
|
+
throw new DocumentHttpError(502, `${label} response too large`);
|
|
87
|
+
}
|
|
88
|
+
const body = res.body;
|
|
89
|
+
if (!body)
|
|
90
|
+
return '';
|
|
91
|
+
const reader = body.getReader();
|
|
92
|
+
const chunks = [];
|
|
93
|
+
let total = 0;
|
|
94
|
+
try {
|
|
95
|
+
for (;;) {
|
|
96
|
+
const { done, value } = await reader.read();
|
|
97
|
+
if (done)
|
|
98
|
+
break;
|
|
99
|
+
if (!value)
|
|
100
|
+
continue;
|
|
101
|
+
total += value.byteLength;
|
|
102
|
+
if (total > maxBytes) {
|
|
103
|
+
await reader.cancel();
|
|
104
|
+
throw new DocumentHttpError(502, `${label} response too large`);
|
|
105
|
+
}
|
|
106
|
+
chunks.push(value);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
reader.releaseLock();
|
|
111
|
+
}
|
|
112
|
+
const merged = new Uint8Array(total);
|
|
113
|
+
let offset = 0;
|
|
114
|
+
for (const c of chunks) {
|
|
115
|
+
merged.set(c, offset);
|
|
116
|
+
offset += c.byteLength;
|
|
117
|
+
}
|
|
118
|
+
return new TextDecoder().decode(merged);
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/modules/documents/http.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/integrations",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "External-system integration domain logic for the Agent Architecture Board (GitHub, documents, tasks, environments, runners).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"ai": "^6.0.209",
|
|
28
|
-
"@cat-factory/contracts": "0.
|
|
29
|
-
"@cat-factory/kernel": "0.
|
|
28
|
+
"@cat-factory/contracts": "0.43.0",
|
|
29
|
+
"@cat-factory/kernel": "0.45.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"typescript": "7.0.1-rc",
|