notte-sdk 0.0.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/README.md +632 -0
- package/dist/index.d.mts +9036 -0
- package/dist/index.d.ts +9036 -0
- package/dist/index.js +2866 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2761 -0
- package/dist/index.mjs.map +1 -0
- package/dist/proxy/core.d.mts +17 -0
- package/dist/proxy/core.d.ts +17 -0
- package/dist/proxy/core.js +312 -0
- package/dist/proxy/core.js.map +1 -0
- package/dist/proxy/core.mjs +285 -0
- package/dist/proxy/core.mjs.map +1 -0
- package/dist/proxy/next.d.mts +82 -0
- package/dist/proxy/next.d.ts +82 -0
- package/dist/proxy/next.js +354 -0
- package/dist/proxy/next.js.map +1 -0
- package/dist/proxy/next.mjs +323 -0
- package/dist/proxy/next.mjs.map +1 -0
- package/dist/types-DmqqdEMP.d.mts +75 -0
- package/dist/types-DmqqdEMP.d.ts +75 -0
- package/package.json +66 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/proxy/core.ts
|
|
21
|
+
var core_exports = {};
|
|
22
|
+
__export(core_exports, {
|
|
23
|
+
handleProxyRequest: () => handleProxyRequest
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(core_exports);
|
|
26
|
+
|
|
27
|
+
// src/proxy/patterns.ts
|
|
28
|
+
var DEFAULT_ALLOWED_PATTERNS = [
|
|
29
|
+
// Agents endpoints
|
|
30
|
+
/^agents$/,
|
|
31
|
+
// /agents
|
|
32
|
+
/^agents\/[^\/]+$/,
|
|
33
|
+
// /agents/{agent_id}
|
|
34
|
+
/^agents\/[^\/]+\/stop$/,
|
|
35
|
+
// /agents/{agent_id}/stop
|
|
36
|
+
/^agents\/[^\/]+\/workflow\/code$/,
|
|
37
|
+
// /agents/{agent_id}/workflow/code
|
|
38
|
+
/^agents\/start$/,
|
|
39
|
+
// /agents/start
|
|
40
|
+
// Anything endpoints
|
|
41
|
+
/^anything\/start$/,
|
|
42
|
+
// /anything/start
|
|
43
|
+
// Functions endpoints
|
|
44
|
+
/^functions$/,
|
|
45
|
+
// /functions
|
|
46
|
+
/^functions\/[^\/]+$/,
|
|
47
|
+
// /functions/{function_id}
|
|
48
|
+
/^functions\/[^\/]+\/fork$/,
|
|
49
|
+
// /functions/{function_id}/fork
|
|
50
|
+
/^functions\/[^\/]+\/runs$/,
|
|
51
|
+
// /functions/{function_id}/runs
|
|
52
|
+
/^functions\/[^\/]+\/runs\/[^\/]+$/,
|
|
53
|
+
// /functions/{function_id}/runs/{run_id}
|
|
54
|
+
/^functions\/[^\/]+\/runs\/start$/,
|
|
55
|
+
// /functions/{function_id}/runs/start
|
|
56
|
+
/^functions\/[^\/]+\/schedule$/,
|
|
57
|
+
// /functions/{function_id}/schedule
|
|
58
|
+
// Personas endpoints
|
|
59
|
+
/^personas$/,
|
|
60
|
+
// /personas
|
|
61
|
+
/^personas\/[^\/]+$/,
|
|
62
|
+
// /personas/{persona_id}
|
|
63
|
+
/^personas\/[^\/]+\/emails$/,
|
|
64
|
+
// /personas/{persona_id}/emails
|
|
65
|
+
/^personas\/[^\/]+\/sms$/,
|
|
66
|
+
// /personas/{persona_id}/sms
|
|
67
|
+
/^personas\/create$/,
|
|
68
|
+
// /personas/create
|
|
69
|
+
// Profiles endpoints
|
|
70
|
+
/^profiles$/,
|
|
71
|
+
// /profiles
|
|
72
|
+
/^profiles\/[^\/]+$/,
|
|
73
|
+
// /profiles/{profile_id}
|
|
74
|
+
/^profiles\/create$/,
|
|
75
|
+
// /profiles/create
|
|
76
|
+
// Prompts endpoints
|
|
77
|
+
/^prompts\/improve$/,
|
|
78
|
+
// /prompts/improve
|
|
79
|
+
/^prompts\/nudge$/,
|
|
80
|
+
// /prompts/nudge
|
|
81
|
+
// Root endpoints
|
|
82
|
+
/^health$/,
|
|
83
|
+
// /health
|
|
84
|
+
/^scrape$/,
|
|
85
|
+
// /scrape
|
|
86
|
+
/^scrape_from_html$/,
|
|
87
|
+
// /scrape_from_html
|
|
88
|
+
// Sessions endpoints
|
|
89
|
+
/^sessions$/,
|
|
90
|
+
// /sessions
|
|
91
|
+
/^sessions\/[^\/]+$/,
|
|
92
|
+
// /sessions/{session_id}
|
|
93
|
+
/^sessions\/[^\/]+\/cookies$/,
|
|
94
|
+
// /sessions/{session_id}/cookies
|
|
95
|
+
/^sessions\/[^\/]+\/debug$/,
|
|
96
|
+
// /sessions/{session_id}/debug
|
|
97
|
+
/^sessions\/[^\/]+\/network\/logs$/,
|
|
98
|
+
// /sessions/{session_id}/network/logs
|
|
99
|
+
/^sessions\/[^\/]+\/offset$/,
|
|
100
|
+
// /sessions/{session_id}/offset
|
|
101
|
+
/^sessions\/[^\/]+\/page\/execute$/,
|
|
102
|
+
// /sessions/{session_id}/page/execute
|
|
103
|
+
/^sessions\/[^\/]+\/page\/observe$/,
|
|
104
|
+
// /sessions/{session_id}/page/observe
|
|
105
|
+
/^sessions\/[^\/]+\/page\/scrape$/,
|
|
106
|
+
// /sessions/{session_id}/page/scrape
|
|
107
|
+
/^sessions\/[^\/]+\/page\/screenshot$/,
|
|
108
|
+
// /sessions/{session_id}/page/screenshot
|
|
109
|
+
/^sessions\/[^\/]+\/replay$/,
|
|
110
|
+
// /sessions/{session_id}/replay
|
|
111
|
+
/^sessions\/[^\/]+\/stop$/,
|
|
112
|
+
// /sessions/{session_id}/stop
|
|
113
|
+
/^sessions\/[^\/]+\/workflow\/code$/,
|
|
114
|
+
// /sessions/{session_id}/workflow/code
|
|
115
|
+
/^sessions\/start$/,
|
|
116
|
+
// /sessions/start
|
|
117
|
+
// Storage endpoints
|
|
118
|
+
/^storage\/[^\/]+\/downloads$/,
|
|
119
|
+
// /storage/{session_id}/downloads
|
|
120
|
+
/^storage\/[^\/]+\/downloads\/[^\/]+$/,
|
|
121
|
+
// /storage/{session_id}/downloads/{filename}
|
|
122
|
+
/^storage\/uploads$/,
|
|
123
|
+
// /storage/uploads
|
|
124
|
+
/^storage\/uploads\/[^\/]+$/,
|
|
125
|
+
// /storage/uploads/{filename}
|
|
126
|
+
// Usage endpoints
|
|
127
|
+
/^usage$/,
|
|
128
|
+
// /usage
|
|
129
|
+
/^usage\/logs$/,
|
|
130
|
+
// /usage/logs
|
|
131
|
+
// Vaults endpoints
|
|
132
|
+
/^vaults$/,
|
|
133
|
+
// /vaults
|
|
134
|
+
/^vaults\/[^\/]+$/,
|
|
135
|
+
// /vaults/{vault_id}
|
|
136
|
+
/^vaults\/[^\/]+\/card$/,
|
|
137
|
+
// /vaults/{vault_id}/card
|
|
138
|
+
/^vaults\/[^\/]+\/credentials$/,
|
|
139
|
+
// /vaults/{vault_id}/credentials
|
|
140
|
+
/^vaults\/create$/
|
|
141
|
+
// /vaults/create
|
|
142
|
+
];
|
|
143
|
+
function validatePath(pathSegments, allowedPatterns) {
|
|
144
|
+
const path = pathSegments.join("/");
|
|
145
|
+
if (path.includes("..") || path.includes("//") || path.startsWith("/")) {
|
|
146
|
+
return {
|
|
147
|
+
isValid: false,
|
|
148
|
+
error: "Invalid path: directory traversal detected"
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const isValidPattern = allowedPatterns.some((pattern) => pattern.test(path));
|
|
152
|
+
if (!isValidPattern) {
|
|
153
|
+
return {
|
|
154
|
+
isValid: false,
|
|
155
|
+
error: `Invalid path: ${path} does not match any allowed endpoint pattern`
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return { isValid: true };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/proxy/types.ts
|
|
162
|
+
var NotteProxyAuthError = class extends Error {
|
|
163
|
+
constructor(message = "Unauthorized") {
|
|
164
|
+
super(message);
|
|
165
|
+
this.name = "NotteProxyAuthError";
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// src/proxy/core.ts
|
|
170
|
+
var DEFAULT_API_URL = "https://api.notte.cc";
|
|
171
|
+
var DEFAULT_FORWARD_HEADERS = ["content-type", "accept"];
|
|
172
|
+
var METHODS_WITH_BODY = ["POST", "PUT", "PATCH"];
|
|
173
|
+
async function handleProxyRequest(request, pathSegments, config) {
|
|
174
|
+
try {
|
|
175
|
+
if (config.allowedPatterns !== false) {
|
|
176
|
+
const patterns = config.allowedPatterns || DEFAULT_ALLOWED_PATTERNS;
|
|
177
|
+
const pathValidation = validatePath(pathSegments, patterns);
|
|
178
|
+
if (!pathValidation.isValid) {
|
|
179
|
+
return new Response(
|
|
180
|
+
JSON.stringify({
|
|
181
|
+
error: "Invalid API endpoint",
|
|
182
|
+
details: pathValidation.error
|
|
183
|
+
}),
|
|
184
|
+
{
|
|
185
|
+
status: 400,
|
|
186
|
+
headers: { "Content-Type": "application/json" }
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
let apiKey = config.apiKey || process.env.NOTTE_API_KEY;
|
|
192
|
+
if (config.authenticate) {
|
|
193
|
+
const result = await config.authenticate(request);
|
|
194
|
+
if (typeof result === "string") {
|
|
195
|
+
apiKey = result;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (!apiKey) {
|
|
199
|
+
return new Response(
|
|
200
|
+
JSON.stringify({ error: "No API key configured" }),
|
|
201
|
+
{
|
|
202
|
+
status: 401,
|
|
203
|
+
headers: { "Content-Type": "application/json" }
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
const apiUrl = (config.apiUrl || DEFAULT_API_URL).replace(/\/$/, "");
|
|
208
|
+
const path = pathSegments.join("/");
|
|
209
|
+
const incomingUrl = new URL(request.url);
|
|
210
|
+
const searchParams = incomingUrl.searchParams.toString();
|
|
211
|
+
const targetUrl = `${apiUrl}/${path}${searchParams ? `?${searchParams}` : ""}`;
|
|
212
|
+
const forwardHeaders = new Headers();
|
|
213
|
+
forwardHeaders.set("Authorization", `Bearer ${apiKey}`);
|
|
214
|
+
forwardHeaders.set(
|
|
215
|
+
"x-notte-request-origin",
|
|
216
|
+
config.requestOrigin || "sdk-proxy"
|
|
217
|
+
);
|
|
218
|
+
const headersToForward = config.forwardHeaders || DEFAULT_FORWARD_HEADERS;
|
|
219
|
+
for (const header of headersToForward) {
|
|
220
|
+
const value = request.headers.get(header);
|
|
221
|
+
if (value) {
|
|
222
|
+
forwardHeaders.set(header, value);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (config.onBeforeRequest) {
|
|
226
|
+
await config.onBeforeRequest({
|
|
227
|
+
path,
|
|
228
|
+
method: request.method,
|
|
229
|
+
headers: forwardHeaders,
|
|
230
|
+
url: targetUrl,
|
|
231
|
+
incomingRequest: request
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
const requestOptions = {
|
|
235
|
+
method: request.method,
|
|
236
|
+
headers: forwardHeaders
|
|
237
|
+
};
|
|
238
|
+
if (METHODS_WITH_BODY.includes(request.method) && request.body) {
|
|
239
|
+
requestOptions.body = request.body;
|
|
240
|
+
requestOptions.duplex = "half";
|
|
241
|
+
}
|
|
242
|
+
let response;
|
|
243
|
+
try {
|
|
244
|
+
response = await fetch(targetUrl, requestOptions);
|
|
245
|
+
} catch (fetchError) {
|
|
246
|
+
const errorMessage = fetchError instanceof Error ? fetchError.message : "Unknown fetch error";
|
|
247
|
+
return new Response(
|
|
248
|
+
JSON.stringify({
|
|
249
|
+
error: "Failed to forward request to Notte API",
|
|
250
|
+
details: errorMessage
|
|
251
|
+
}),
|
|
252
|
+
{
|
|
253
|
+
status: 502,
|
|
254
|
+
headers: { "Content-Type": "application/json" }
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
const contentType = response.headers.get("content-type") || "";
|
|
259
|
+
const isBinaryResponse = contentType.startsWith("image/") || contentType.startsWith("video/") || contentType.startsWith("audio/") || contentType.includes("octet-stream");
|
|
260
|
+
const isStreamingResponse = contentType.includes("text/event-stream");
|
|
261
|
+
if (isStreamingResponse && response.body) {
|
|
262
|
+
return new Response(response.body, {
|
|
263
|
+
status: response.status,
|
|
264
|
+
statusText: response.statusText,
|
|
265
|
+
headers: {
|
|
266
|
+
"Content-Type": contentType,
|
|
267
|
+
"Cache-Control": "no-cache",
|
|
268
|
+
Connection: "keep-alive"
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (response.body) {
|
|
273
|
+
return new Response(response.body, {
|
|
274
|
+
status: response.status,
|
|
275
|
+
statusText: response.statusText,
|
|
276
|
+
headers: {
|
|
277
|
+
"Content-Type": contentType
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
if (isBinaryResponse) {
|
|
282
|
+
const data2 = await response.arrayBuffer();
|
|
283
|
+
return new Response(data2, {
|
|
284
|
+
status: response.status,
|
|
285
|
+
statusText: response.statusText,
|
|
286
|
+
headers: { "Content-Type": contentType }
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const data = await response.text();
|
|
290
|
+
return new Response(data, {
|
|
291
|
+
status: response.status,
|
|
292
|
+
statusText: response.statusText,
|
|
293
|
+
headers: { "Content-Type": contentType }
|
|
294
|
+
});
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (error instanceof NotteProxyAuthError) {
|
|
297
|
+
return new Response(
|
|
298
|
+
JSON.stringify({ error: error.message }),
|
|
299
|
+
{
|
|
300
|
+
status: 401,
|
|
301
|
+
headers: { "Content-Type": "application/json" }
|
|
302
|
+
}
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
309
|
+
0 && (module.exports = {
|
|
310
|
+
handleProxyRequest
|
|
311
|
+
});
|
|
312
|
+
//# sourceMappingURL=core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/proxy/core.ts","../../src/proxy/patterns.ts","../../src/proxy/types.ts"],"sourcesContent":["import { validatePath, DEFAULT_ALLOWED_PATTERNS } from './patterns';\nimport type { NotteProxyConfig } from './types';\nimport { NotteProxyAuthError } from './types';\n\nconst DEFAULT_API_URL = 'https://api.notte.cc';\nconst DEFAULT_FORWARD_HEADERS = ['content-type', 'accept'];\nconst METHODS_WITH_BODY = ['POST', 'PUT', 'PATCH'];\n\n/**\n * Framework-agnostic proxy handler that forwards requests to the Notte API.\n *\n * Takes a standard Web API `Request`, injects authentication, and returns\n * a standard Web API `Response`. Works with any runtime that supports\n * the Web API standards (Node 18+, Deno, Bun, Cloudflare Workers).\n *\n * @param request - The incoming HTTP request (standard Web API Request)\n * @param pathSegments - The path segments after the proxy base path (e.g., ['sessions', 'start'])\n * @param config - Proxy configuration including API key, auth hooks, etc.\n * @returns A standard Web API Response\n */\nexport async function handleProxyRequest(\n request: Request,\n pathSegments: string[],\n config: NotteProxyConfig,\n): Promise<Response> {\n try {\n // Step 1: Path validation\n if (config.allowedPatterns !== false) {\n const patterns = config.allowedPatterns || DEFAULT_ALLOWED_PATTERNS;\n const pathValidation = validatePath(pathSegments, patterns);\n\n if (!pathValidation.isValid) {\n return new Response(\n JSON.stringify({\n error: 'Invalid API endpoint',\n details: pathValidation.error,\n }),\n {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n },\n );\n }\n }\n\n // Step 2: Authentication\n let apiKey = config.apiKey || process.env.NOTTE_API_KEY;\n\n if (config.authenticate) {\n const result = await config.authenticate(request);\n if (typeof result === 'string') {\n apiKey = result;\n }\n }\n\n if (!apiKey) {\n return new Response(\n JSON.stringify({ error: 'No API key configured' }),\n {\n status: 401,\n headers: { 'Content-Type': 'application/json' },\n },\n );\n }\n\n // Step 3: Build target URL\n const apiUrl = (config.apiUrl || DEFAULT_API_URL).replace(/\\/$/, '');\n const path = pathSegments.join('/');\n const incomingUrl = new URL(request.url);\n const searchParams = incomingUrl.searchParams.toString();\n const targetUrl = `${apiUrl}/${path}${searchParams ? `?${searchParams}` : ''}`;\n\n // Step 4: Build headers\n const forwardHeaders = new Headers();\n forwardHeaders.set('Authorization', `Bearer ${apiKey}`);\n forwardHeaders.set(\n 'x-notte-request-origin',\n config.requestOrigin || 'sdk-proxy',\n );\n\n const headersToForward =\n config.forwardHeaders || DEFAULT_FORWARD_HEADERS;\n for (const header of headersToForward) {\n const value = request.headers.get(header);\n if (value) {\n forwardHeaders.set(header, value);\n }\n }\n\n // Step 5: onBeforeRequest hook\n if (config.onBeforeRequest) {\n await config.onBeforeRequest({\n path,\n method: request.method,\n headers: forwardHeaders,\n url: targetUrl,\n incomingRequest: request,\n });\n }\n\n // Step 6: Forward the request\n const requestOptions: RequestInit = {\n method: request.method,\n headers: forwardHeaders,\n };\n\n if (METHODS_WITH_BODY.includes(request.method) && request.body) {\n requestOptions.body = request.body;\n (requestOptions as any).duplex = 'half';\n }\n\n let response: globalThis.Response;\n try {\n response = await fetch(targetUrl, requestOptions);\n } catch (fetchError) {\n const errorMessage =\n fetchError instanceof Error\n ? fetchError.message\n : 'Unknown fetch error';\n return new Response(\n JSON.stringify({\n error: 'Failed to forward request to Notte API',\n details: errorMessage,\n }),\n {\n status: 502,\n headers: { 'Content-Type': 'application/json' },\n },\n );\n }\n\n // Step 7: Handle response\n const contentType = response.headers.get('content-type') || '';\n\n const isBinaryResponse =\n contentType.startsWith('image/') ||\n contentType.startsWith('video/') ||\n contentType.startsWith('audio/') ||\n contentType.includes('octet-stream');\n\n const isStreamingResponse = contentType.includes('text/event-stream');\n\n // For streaming responses, pass through the body with appropriate headers\n if (isStreamingResponse && response.body) {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: {\n 'Content-Type': contentType,\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n }\n\n // For all other responses with a body stream, pass it through directly\n if (response.body) {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: {\n 'Content-Type': contentType,\n },\n });\n }\n\n // Fallback for responses without a body stream: buffer appropriately\n if (isBinaryResponse) {\n const data = await response.arrayBuffer();\n return new Response(data, {\n status: response.status,\n statusText: response.statusText,\n headers: { 'Content-Type': contentType },\n });\n }\n\n // Fallback: buffer as text\n const data = await response.text();\n return new Response(data, {\n status: response.status,\n statusText: response.statusText,\n headers: { 'Content-Type': contentType },\n });\n } catch (error) {\n // NotteProxyAuthError from authenticate() → 401\n if (error instanceof NotteProxyAuthError) {\n return new Response(\n JSON.stringify({ error: error.message }),\n {\n status: 401,\n headers: { 'Content-Type': 'application/json' },\n },\n );\n }\n\n // Re-throw unexpected errors so the adapter layer can handle them\n throw error;\n }\n}\n","/**\n * Auto-generated from sdk.gen.ts by scripts/generate-proxy-patterns.js\n * DO NOT EDIT MANUALLY — re-run `npm run generate` to update.\n *\n * Allowed API endpoint patterns to prevent SSRF attacks.\n * Every pattern corresponds to an endpoint in the Notte API OpenAPI spec.\n */\nexport const DEFAULT_ALLOWED_PATTERNS: RegExp[] = [\n // Agents endpoints\n /^agents$/, // /agents\n /^agents\\/[^\\/]+$/, // /agents/{agent_id}\n /^agents\\/[^\\/]+\\/stop$/, // /agents/{agent_id}/stop\n /^agents\\/[^\\/]+\\/workflow\\/code$/, // /agents/{agent_id}/workflow/code\n /^agents\\/start$/, // /agents/start\n\n // Anything endpoints\n /^anything\\/start$/, // /anything/start\n\n // Functions endpoints\n /^functions$/, // /functions\n /^functions\\/[^\\/]+$/, // /functions/{function_id}\n /^functions\\/[^\\/]+\\/fork$/, // /functions/{function_id}/fork\n /^functions\\/[^\\/]+\\/runs$/, // /functions/{function_id}/runs\n /^functions\\/[^\\/]+\\/runs\\/[^\\/]+$/, // /functions/{function_id}/runs/{run_id}\n /^functions\\/[^\\/]+\\/runs\\/start$/, // /functions/{function_id}/runs/start\n /^functions\\/[^\\/]+\\/schedule$/, // /functions/{function_id}/schedule\n\n // Personas endpoints\n /^personas$/, // /personas\n /^personas\\/[^\\/]+$/, // /personas/{persona_id}\n /^personas\\/[^\\/]+\\/emails$/, // /personas/{persona_id}/emails\n /^personas\\/[^\\/]+\\/sms$/, // /personas/{persona_id}/sms\n /^personas\\/create$/, // /personas/create\n\n // Profiles endpoints\n /^profiles$/, // /profiles\n /^profiles\\/[^\\/]+$/, // /profiles/{profile_id}\n /^profiles\\/create$/, // /profiles/create\n\n // Prompts endpoints\n /^prompts\\/improve$/, // /prompts/improve\n /^prompts\\/nudge$/, // /prompts/nudge\n\n // Root endpoints\n /^health$/, // /health\n /^scrape$/, // /scrape\n /^scrape_from_html$/, // /scrape_from_html\n\n // Sessions endpoints\n /^sessions$/, // /sessions\n /^sessions\\/[^\\/]+$/, // /sessions/{session_id}\n /^sessions\\/[^\\/]+\\/cookies$/, // /sessions/{session_id}/cookies\n /^sessions\\/[^\\/]+\\/debug$/, // /sessions/{session_id}/debug\n /^sessions\\/[^\\/]+\\/network\\/logs$/, // /sessions/{session_id}/network/logs\n /^sessions\\/[^\\/]+\\/offset$/, // /sessions/{session_id}/offset\n /^sessions\\/[^\\/]+\\/page\\/execute$/, // /sessions/{session_id}/page/execute\n /^sessions\\/[^\\/]+\\/page\\/observe$/, // /sessions/{session_id}/page/observe\n /^sessions\\/[^\\/]+\\/page\\/scrape$/, // /sessions/{session_id}/page/scrape\n /^sessions\\/[^\\/]+\\/page\\/screenshot$/, // /sessions/{session_id}/page/screenshot\n /^sessions\\/[^\\/]+\\/replay$/, // /sessions/{session_id}/replay\n /^sessions\\/[^\\/]+\\/stop$/, // /sessions/{session_id}/stop\n /^sessions\\/[^\\/]+\\/workflow\\/code$/, // /sessions/{session_id}/workflow/code\n /^sessions\\/start$/, // /sessions/start\n\n // Storage endpoints\n /^storage\\/[^\\/]+\\/downloads$/, // /storage/{session_id}/downloads\n /^storage\\/[^\\/]+\\/downloads\\/[^\\/]+$/, // /storage/{session_id}/downloads/{filename}\n /^storage\\/uploads$/, // /storage/uploads\n /^storage\\/uploads\\/[^\\/]+$/, // /storage/uploads/{filename}\n\n // Usage endpoints\n /^usage$/, // /usage\n /^usage\\/logs$/, // /usage/logs\n\n // Vaults endpoints\n /^vaults$/, // /vaults\n /^vaults\\/[^\\/]+$/, // /vaults/{vault_id}\n /^vaults\\/[^\\/]+\\/card$/, // /vaults/{vault_id}/card\n /^vaults\\/[^\\/]+\\/credentials$/, // /vaults/{vault_id}/credentials\n /^vaults\\/create$/, // /vaults/create\n];\n\nexport interface PathValidationResult {\n isValid: boolean;\n error?: string;\n}\n\n/**\n * Validates a request path against a list of allowed patterns.\n * Prevents directory traversal and SSRF attacks.\n */\nexport function validatePath(\n pathSegments: string[],\n allowedPatterns: RegExp[],\n): PathValidationResult {\n const path = pathSegments.join('/');\n\n // Check for directory traversal attempts\n if (path.includes('..') || path.includes('//') || path.startsWith('/')) {\n return {\n isValid: false,\n error: 'Invalid path: directory traversal detected',\n };\n }\n\n // Check against allowed patterns\n const isValidPattern = allowedPatterns.some((pattern) => pattern.test(path));\n if (!isValidPattern) {\n return {\n isValid: false,\n error: `Invalid path: ${path} does not match any allowed endpoint pattern`,\n };\n }\n\n return { isValid: true };\n}\n","/**\n * Error class for authentication/authorization failures in the proxy.\n * Throw this from `authenticate()` to return a 401 response.\n *\n * @example\n * ```typescript\n * authenticate: async (request) => {\n * const session = await getSession(request);\n * if (!session) throw new NotteProxyAuthError('Not logged in');\n * }\n * ```\n */\nexport class NotteProxyAuthError extends Error {\n constructor(message = 'Unauthorized') {\n super(message);\n this.name = 'NotteProxyAuthError';\n }\n}\n\n/**\n * Configuration for the Notte API proxy.\n *\n * The proxy forwards incoming HTTP requests to the Notte API,\n * injecting authentication headers server-side so the API key\n * is never exposed to the client.\n */\nexport interface NotteProxyConfig {\n /**\n * The Notte API key to use for all forwarded requests.\n * Falls back to the NOTTE_API_KEY environment variable when omitted.\n * Can be overridden per-request by returning a string from `authenticate`.\n */\n apiKey?: string;\n\n /**\n * Base URL of the Notte API.\n * @default 'https://api.notte.cc'\n */\n apiUrl?: string;\n\n /**\n * Optional async function to authenticate/authorize incoming requests.\n *\n * - Throw a `NotteProxyAuthError` to reject the request (returns 401).\n * - Return `void` to allow the request through using the default `apiKey`.\n * - Return a `string` to override the API key for this specific request\n * (useful for per-user API keys).\n */\n authenticate?: (request: Request) => Promise<void | string>;\n\n /**\n * Allowed API endpoint patterns for path validation (SSRF prevention).\n *\n * - `undefined` (default): uses the built-in allowlist derived from the OpenAPI spec.\n * - `RegExp[]`: custom list of allowed patterns.\n * - `false`: disables path validation entirely (not recommended for public-facing proxies).\n */\n allowedPatterns?: RegExp[] | false;\n\n /**\n * Value for the `x-notte-request-origin` header sent to the Notte API.\n * @default 'sdk-proxy'\n */\n requestOrigin?: string;\n\n /**\n * List of header names to forward from the incoming request to the Notte API.\n * @default ['content-type', 'accept']\n */\n forwardHeaders?: string[];\n\n /**\n * Optional hook called just before the request is forwarded to the Notte API.\n * Use this to modify outgoing headers, log requests, etc.\n */\n onBeforeRequest?: (ctx: {\n path: string;\n method: string;\n headers: Headers;\n url: string;\n incomingRequest: Request;\n }) => Promise<void> | void;\n}\n"],"mappings":"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","names":["data"]}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// src/proxy/patterns.ts
|
|
2
|
+
var DEFAULT_ALLOWED_PATTERNS = [
|
|
3
|
+
// Agents endpoints
|
|
4
|
+
/^agents$/,
|
|
5
|
+
// /agents
|
|
6
|
+
/^agents\/[^\/]+$/,
|
|
7
|
+
// /agents/{agent_id}
|
|
8
|
+
/^agents\/[^\/]+\/stop$/,
|
|
9
|
+
// /agents/{agent_id}/stop
|
|
10
|
+
/^agents\/[^\/]+\/workflow\/code$/,
|
|
11
|
+
// /agents/{agent_id}/workflow/code
|
|
12
|
+
/^agents\/start$/,
|
|
13
|
+
// /agents/start
|
|
14
|
+
// Anything endpoints
|
|
15
|
+
/^anything\/start$/,
|
|
16
|
+
// /anything/start
|
|
17
|
+
// Functions endpoints
|
|
18
|
+
/^functions$/,
|
|
19
|
+
// /functions
|
|
20
|
+
/^functions\/[^\/]+$/,
|
|
21
|
+
// /functions/{function_id}
|
|
22
|
+
/^functions\/[^\/]+\/fork$/,
|
|
23
|
+
// /functions/{function_id}/fork
|
|
24
|
+
/^functions\/[^\/]+\/runs$/,
|
|
25
|
+
// /functions/{function_id}/runs
|
|
26
|
+
/^functions\/[^\/]+\/runs\/[^\/]+$/,
|
|
27
|
+
// /functions/{function_id}/runs/{run_id}
|
|
28
|
+
/^functions\/[^\/]+\/runs\/start$/,
|
|
29
|
+
// /functions/{function_id}/runs/start
|
|
30
|
+
/^functions\/[^\/]+\/schedule$/,
|
|
31
|
+
// /functions/{function_id}/schedule
|
|
32
|
+
// Personas endpoints
|
|
33
|
+
/^personas$/,
|
|
34
|
+
// /personas
|
|
35
|
+
/^personas\/[^\/]+$/,
|
|
36
|
+
// /personas/{persona_id}
|
|
37
|
+
/^personas\/[^\/]+\/emails$/,
|
|
38
|
+
// /personas/{persona_id}/emails
|
|
39
|
+
/^personas\/[^\/]+\/sms$/,
|
|
40
|
+
// /personas/{persona_id}/sms
|
|
41
|
+
/^personas\/create$/,
|
|
42
|
+
// /personas/create
|
|
43
|
+
// Profiles endpoints
|
|
44
|
+
/^profiles$/,
|
|
45
|
+
// /profiles
|
|
46
|
+
/^profiles\/[^\/]+$/,
|
|
47
|
+
// /profiles/{profile_id}
|
|
48
|
+
/^profiles\/create$/,
|
|
49
|
+
// /profiles/create
|
|
50
|
+
// Prompts endpoints
|
|
51
|
+
/^prompts\/improve$/,
|
|
52
|
+
// /prompts/improve
|
|
53
|
+
/^prompts\/nudge$/,
|
|
54
|
+
// /prompts/nudge
|
|
55
|
+
// Root endpoints
|
|
56
|
+
/^health$/,
|
|
57
|
+
// /health
|
|
58
|
+
/^scrape$/,
|
|
59
|
+
// /scrape
|
|
60
|
+
/^scrape_from_html$/,
|
|
61
|
+
// /scrape_from_html
|
|
62
|
+
// Sessions endpoints
|
|
63
|
+
/^sessions$/,
|
|
64
|
+
// /sessions
|
|
65
|
+
/^sessions\/[^\/]+$/,
|
|
66
|
+
// /sessions/{session_id}
|
|
67
|
+
/^sessions\/[^\/]+\/cookies$/,
|
|
68
|
+
// /sessions/{session_id}/cookies
|
|
69
|
+
/^sessions\/[^\/]+\/debug$/,
|
|
70
|
+
// /sessions/{session_id}/debug
|
|
71
|
+
/^sessions\/[^\/]+\/network\/logs$/,
|
|
72
|
+
// /sessions/{session_id}/network/logs
|
|
73
|
+
/^sessions\/[^\/]+\/offset$/,
|
|
74
|
+
// /sessions/{session_id}/offset
|
|
75
|
+
/^sessions\/[^\/]+\/page\/execute$/,
|
|
76
|
+
// /sessions/{session_id}/page/execute
|
|
77
|
+
/^sessions\/[^\/]+\/page\/observe$/,
|
|
78
|
+
// /sessions/{session_id}/page/observe
|
|
79
|
+
/^sessions\/[^\/]+\/page\/scrape$/,
|
|
80
|
+
// /sessions/{session_id}/page/scrape
|
|
81
|
+
/^sessions\/[^\/]+\/page\/screenshot$/,
|
|
82
|
+
// /sessions/{session_id}/page/screenshot
|
|
83
|
+
/^sessions\/[^\/]+\/replay$/,
|
|
84
|
+
// /sessions/{session_id}/replay
|
|
85
|
+
/^sessions\/[^\/]+\/stop$/,
|
|
86
|
+
// /sessions/{session_id}/stop
|
|
87
|
+
/^sessions\/[^\/]+\/workflow\/code$/,
|
|
88
|
+
// /sessions/{session_id}/workflow/code
|
|
89
|
+
/^sessions\/start$/,
|
|
90
|
+
// /sessions/start
|
|
91
|
+
// Storage endpoints
|
|
92
|
+
/^storage\/[^\/]+\/downloads$/,
|
|
93
|
+
// /storage/{session_id}/downloads
|
|
94
|
+
/^storage\/[^\/]+\/downloads\/[^\/]+$/,
|
|
95
|
+
// /storage/{session_id}/downloads/{filename}
|
|
96
|
+
/^storage\/uploads$/,
|
|
97
|
+
// /storage/uploads
|
|
98
|
+
/^storage\/uploads\/[^\/]+$/,
|
|
99
|
+
// /storage/uploads/{filename}
|
|
100
|
+
// Usage endpoints
|
|
101
|
+
/^usage$/,
|
|
102
|
+
// /usage
|
|
103
|
+
/^usage\/logs$/,
|
|
104
|
+
// /usage/logs
|
|
105
|
+
// Vaults endpoints
|
|
106
|
+
/^vaults$/,
|
|
107
|
+
// /vaults
|
|
108
|
+
/^vaults\/[^\/]+$/,
|
|
109
|
+
// /vaults/{vault_id}
|
|
110
|
+
/^vaults\/[^\/]+\/card$/,
|
|
111
|
+
// /vaults/{vault_id}/card
|
|
112
|
+
/^vaults\/[^\/]+\/credentials$/,
|
|
113
|
+
// /vaults/{vault_id}/credentials
|
|
114
|
+
/^vaults\/create$/
|
|
115
|
+
// /vaults/create
|
|
116
|
+
];
|
|
117
|
+
function validatePath(pathSegments, allowedPatterns) {
|
|
118
|
+
const path = pathSegments.join("/");
|
|
119
|
+
if (path.includes("..") || path.includes("//") || path.startsWith("/")) {
|
|
120
|
+
return {
|
|
121
|
+
isValid: false,
|
|
122
|
+
error: "Invalid path: directory traversal detected"
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const isValidPattern = allowedPatterns.some((pattern) => pattern.test(path));
|
|
126
|
+
if (!isValidPattern) {
|
|
127
|
+
return {
|
|
128
|
+
isValid: false,
|
|
129
|
+
error: `Invalid path: ${path} does not match any allowed endpoint pattern`
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return { isValid: true };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/proxy/types.ts
|
|
136
|
+
var NotteProxyAuthError = class extends Error {
|
|
137
|
+
constructor(message = "Unauthorized") {
|
|
138
|
+
super(message);
|
|
139
|
+
this.name = "NotteProxyAuthError";
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// src/proxy/core.ts
|
|
144
|
+
var DEFAULT_API_URL = "https://api.notte.cc";
|
|
145
|
+
var DEFAULT_FORWARD_HEADERS = ["content-type", "accept"];
|
|
146
|
+
var METHODS_WITH_BODY = ["POST", "PUT", "PATCH"];
|
|
147
|
+
async function handleProxyRequest(request, pathSegments, config) {
|
|
148
|
+
try {
|
|
149
|
+
if (config.allowedPatterns !== false) {
|
|
150
|
+
const patterns = config.allowedPatterns || DEFAULT_ALLOWED_PATTERNS;
|
|
151
|
+
const pathValidation = validatePath(pathSegments, patterns);
|
|
152
|
+
if (!pathValidation.isValid) {
|
|
153
|
+
return new Response(
|
|
154
|
+
JSON.stringify({
|
|
155
|
+
error: "Invalid API endpoint",
|
|
156
|
+
details: pathValidation.error
|
|
157
|
+
}),
|
|
158
|
+
{
|
|
159
|
+
status: 400,
|
|
160
|
+
headers: { "Content-Type": "application/json" }
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
let apiKey = config.apiKey || process.env.NOTTE_API_KEY;
|
|
166
|
+
if (config.authenticate) {
|
|
167
|
+
const result = await config.authenticate(request);
|
|
168
|
+
if (typeof result === "string") {
|
|
169
|
+
apiKey = result;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (!apiKey) {
|
|
173
|
+
return new Response(
|
|
174
|
+
JSON.stringify({ error: "No API key configured" }),
|
|
175
|
+
{
|
|
176
|
+
status: 401,
|
|
177
|
+
headers: { "Content-Type": "application/json" }
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
const apiUrl = (config.apiUrl || DEFAULT_API_URL).replace(/\/$/, "");
|
|
182
|
+
const path = pathSegments.join("/");
|
|
183
|
+
const incomingUrl = new URL(request.url);
|
|
184
|
+
const searchParams = incomingUrl.searchParams.toString();
|
|
185
|
+
const targetUrl = `${apiUrl}/${path}${searchParams ? `?${searchParams}` : ""}`;
|
|
186
|
+
const forwardHeaders = new Headers();
|
|
187
|
+
forwardHeaders.set("Authorization", `Bearer ${apiKey}`);
|
|
188
|
+
forwardHeaders.set(
|
|
189
|
+
"x-notte-request-origin",
|
|
190
|
+
config.requestOrigin || "sdk-proxy"
|
|
191
|
+
);
|
|
192
|
+
const headersToForward = config.forwardHeaders || DEFAULT_FORWARD_HEADERS;
|
|
193
|
+
for (const header of headersToForward) {
|
|
194
|
+
const value = request.headers.get(header);
|
|
195
|
+
if (value) {
|
|
196
|
+
forwardHeaders.set(header, value);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (config.onBeforeRequest) {
|
|
200
|
+
await config.onBeforeRequest({
|
|
201
|
+
path,
|
|
202
|
+
method: request.method,
|
|
203
|
+
headers: forwardHeaders,
|
|
204
|
+
url: targetUrl,
|
|
205
|
+
incomingRequest: request
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
const requestOptions = {
|
|
209
|
+
method: request.method,
|
|
210
|
+
headers: forwardHeaders
|
|
211
|
+
};
|
|
212
|
+
if (METHODS_WITH_BODY.includes(request.method) && request.body) {
|
|
213
|
+
requestOptions.body = request.body;
|
|
214
|
+
requestOptions.duplex = "half";
|
|
215
|
+
}
|
|
216
|
+
let response;
|
|
217
|
+
try {
|
|
218
|
+
response = await fetch(targetUrl, requestOptions);
|
|
219
|
+
} catch (fetchError) {
|
|
220
|
+
const errorMessage = fetchError instanceof Error ? fetchError.message : "Unknown fetch error";
|
|
221
|
+
return new Response(
|
|
222
|
+
JSON.stringify({
|
|
223
|
+
error: "Failed to forward request to Notte API",
|
|
224
|
+
details: errorMessage
|
|
225
|
+
}),
|
|
226
|
+
{
|
|
227
|
+
status: 502,
|
|
228
|
+
headers: { "Content-Type": "application/json" }
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
const contentType = response.headers.get("content-type") || "";
|
|
233
|
+
const isBinaryResponse = contentType.startsWith("image/") || contentType.startsWith("video/") || contentType.startsWith("audio/") || contentType.includes("octet-stream");
|
|
234
|
+
const isStreamingResponse = contentType.includes("text/event-stream");
|
|
235
|
+
if (isStreamingResponse && response.body) {
|
|
236
|
+
return new Response(response.body, {
|
|
237
|
+
status: response.status,
|
|
238
|
+
statusText: response.statusText,
|
|
239
|
+
headers: {
|
|
240
|
+
"Content-Type": contentType,
|
|
241
|
+
"Cache-Control": "no-cache",
|
|
242
|
+
Connection: "keep-alive"
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
if (response.body) {
|
|
247
|
+
return new Response(response.body, {
|
|
248
|
+
status: response.status,
|
|
249
|
+
statusText: response.statusText,
|
|
250
|
+
headers: {
|
|
251
|
+
"Content-Type": contentType
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
if (isBinaryResponse) {
|
|
256
|
+
const data2 = await response.arrayBuffer();
|
|
257
|
+
return new Response(data2, {
|
|
258
|
+
status: response.status,
|
|
259
|
+
statusText: response.statusText,
|
|
260
|
+
headers: { "Content-Type": contentType }
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
const data = await response.text();
|
|
264
|
+
return new Response(data, {
|
|
265
|
+
status: response.status,
|
|
266
|
+
statusText: response.statusText,
|
|
267
|
+
headers: { "Content-Type": contentType }
|
|
268
|
+
});
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if (error instanceof NotteProxyAuthError) {
|
|
271
|
+
return new Response(
|
|
272
|
+
JSON.stringify({ error: error.message }),
|
|
273
|
+
{
|
|
274
|
+
status: 401,
|
|
275
|
+
headers: { "Content-Type": "application/json" }
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
export {
|
|
283
|
+
handleProxyRequest
|
|
284
|
+
};
|
|
285
|
+
//# sourceMappingURL=core.mjs.map
|