notte-sdk 0.0.22 → 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/LICENSE +21 -0
- package/README.md +594 -102
- package/dist/index.d.mts +2529 -254
- package/dist/index.d.ts +2529 -254
- package/dist/index.js +4219 -1417
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4161 -1422
- package/dist/index.mjs.map +1 -1
- package/dist/proxy/core.d.mts +1 -1
- package/dist/proxy/core.d.ts +1 -1
- package/dist/proxy/core.js +27 -4
- package/dist/proxy/core.js.map +1 -1
- package/dist/proxy/core.mjs +27 -4
- package/dist/proxy/core.mjs.map +1 -1
- package/dist/proxy/next.d.mts +12 -7
- package/dist/proxy/next.d.ts +12 -7
- package/dist/proxy/next.js +27 -4
- package/dist/proxy/next.js.map +1 -1
- package/dist/proxy/next.mjs +27 -4
- package/dist/proxy/next.mjs.map +1 -1
- package/dist/{types-DmqqdEMP.d.mts → types-C5V9BOux.d.mts} +3 -1
- package/dist/{types-DmqqdEMP.d.ts → types-C5V9BOux.d.ts} +3 -1
- package/package.json +33 -4
package/dist/proxy/core.d.mts
CHANGED
package/dist/proxy/core.d.ts
CHANGED
package/dist/proxy/core.js
CHANGED
|
@@ -83,6 +83,21 @@ var DEFAULT_ALLOWED_PATTERNS = [
|
|
|
83
83
|
// /managed-auth/connect-links/status
|
|
84
84
|
/^managed-auth\/connect-links\/submit$/,
|
|
85
85
|
// /managed-auth/connect-links/submit
|
|
86
|
+
/^managed-auth\/connections\/[^\/]+\/check$/,
|
|
87
|
+
// /managed-auth/connections/{connection_id}/check
|
|
88
|
+
/^managed-auth\/connections\/[^\/]+\/reauthenticate$/,
|
|
89
|
+
// /managed-auth/connections/{connection_id}/reauthenticate
|
|
90
|
+
/^managed-auth\/connections\/[^\/]+\/refresh$/,
|
|
91
|
+
// /managed-auth/connections/{connection_id}/refresh
|
|
92
|
+
/^managed-auth\/operations\/[^\/]+$/,
|
|
93
|
+
// /managed-auth/operations/{operation_id}
|
|
94
|
+
// Payments endpoints
|
|
95
|
+
/^payments\/[^\/]+$/,
|
|
96
|
+
// /payments/{payment_id}
|
|
97
|
+
/^payments\/connect$/,
|
|
98
|
+
// /payments/connect
|
|
99
|
+
/^payments\/disconnect$/,
|
|
100
|
+
// /payments/disconnect
|
|
86
101
|
// Personas endpoints
|
|
87
102
|
/^personas$/,
|
|
88
103
|
// /personas
|
|
@@ -110,9 +125,6 @@ var DEFAULT_ALLOWED_PATTERNS = [
|
|
|
110
125
|
// /prompts/improve
|
|
111
126
|
/^prompts\/nudge$/,
|
|
112
127
|
// /prompts/nudge
|
|
113
|
-
// Proxies endpoints
|
|
114
|
-
/^proxies\/gateway\/credentials$/,
|
|
115
|
-
// /proxies/gateway/credentials
|
|
116
128
|
// Ready endpoints
|
|
117
129
|
/^ready$/,
|
|
118
130
|
// /ready
|
|
@@ -138,6 +150,8 @@ var DEFAULT_ALLOWED_PATTERNS = [
|
|
|
138
150
|
// /sessions
|
|
139
151
|
/^sessions\/[^\/]+$/,
|
|
140
152
|
// /sessions/{session_id}
|
|
153
|
+
/^sessions\/[^\/]+\/auth$/,
|
|
154
|
+
// /sessions/{session_id}/auth
|
|
141
155
|
/^sessions\/[^\/]+\/cookies$/,
|
|
142
156
|
// /sessions/{session_id}/cookies
|
|
143
157
|
/^sessions\/[^\/]+\/debug$/,
|
|
@@ -158,6 +172,10 @@ var DEFAULT_ALLOWED_PATTERNS = [
|
|
|
158
172
|
// /sessions/{session_id}/page/scrape
|
|
159
173
|
/^sessions\/[^\/]+\/page\/screenshot$/,
|
|
160
174
|
// /sessions/{session_id}/page/screenshot
|
|
175
|
+
/^sessions\/[^\/]+\/payments$/,
|
|
176
|
+
// /sessions/{session_id}/payments
|
|
177
|
+
/^sessions\/[^\/]+\/profile-preview$/,
|
|
178
|
+
// /sessions/{session_id}/profile-preview
|
|
161
179
|
/^sessions\/[^\/]+\/replay$/,
|
|
162
180
|
// /sessions/{session_id}/replay
|
|
163
181
|
/^sessions\/[^\/]+\/stop$/,
|
|
@@ -231,6 +249,9 @@ async function handleProxyRequest(request, pathSegments, config) {
|
|
|
231
249
|
);
|
|
232
250
|
}
|
|
233
251
|
}
|
|
252
|
+
if (typeof config.authenticate !== "function") {
|
|
253
|
+
throw new NotteProxyAuthError("Proxy caller authentication is required");
|
|
254
|
+
}
|
|
234
255
|
let apiKey = config.apiKey || process.env.NOTTE_API_KEY;
|
|
235
256
|
if (config.authenticate) {
|
|
236
257
|
const result = await config.authenticate(request);
|
|
@@ -254,6 +275,7 @@ async function handleProxyRequest(request, pathSegments, config) {
|
|
|
254
275
|
const targetUrl = `${apiUrl}/${path}${searchParams ? `?${searchParams}` : ""}`;
|
|
255
276
|
const forwardHeaders = new Headers();
|
|
256
277
|
forwardHeaders.set("Authorization", `Bearer ${apiKey}`);
|
|
278
|
+
forwardHeaders.set("x-notte-api-key", apiKey);
|
|
257
279
|
forwardHeaders.set(
|
|
258
280
|
"x-notte-request-origin",
|
|
259
281
|
config.requestOrigin || "sdk-proxy"
|
|
@@ -276,7 +298,8 @@ async function handleProxyRequest(request, pathSegments, config) {
|
|
|
276
298
|
}
|
|
277
299
|
const requestOptions = {
|
|
278
300
|
method: request.method,
|
|
279
|
-
headers: forwardHeaders
|
|
301
|
+
headers: forwardHeaders,
|
|
302
|
+
signal: request.signal
|
|
280
303
|
};
|
|
281
304
|
if (METHODS_WITH_BODY.includes(request.method) && request.body) {
|
|
282
305
|
requestOptions.body = request.body;
|
package/dist/proxy/core.js.map
CHANGED
|
@@ -1 +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\\/[^\\/]+\\/rollback$/, // /functions/{function_id}/rollback\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 /^functions\\/health$/, // /functions/health\n\n // Mailboxes endpoints\n /^mailboxes$/, // /mailboxes\n /^mailboxes\\/[^\\/]+$/, // /mailboxes/{mailbox_id}\n /^mailboxes\\/connect-token$/, // /mailboxes/connect-token\n /^mailboxes\\/sync$/, // /mailboxes/sync\n\n // Managed-auth endpoints\n /^managed-auth\\/connect-links$/, // /managed-auth/connect-links\n /^managed-auth\\/connect-links\\/context$/, // /managed-auth/connect-links/context\n /^managed-auth\\/connect-links\\/mailboxes$/, // /managed-auth/connect-links/mailboxes\n /^managed-auth\\/connect-links\\/mailboxes\\/connect$/, // /managed-auth/connect-links/mailboxes/connect\n /^managed-auth\\/connect-links\\/mailboxes\\/sync$/, // /managed-auth/connect-links/mailboxes/sync\n /^managed-auth\\/connect-links\\/status$/, // /managed-auth/connect-links/status\n /^managed-auth\\/connect-links\\/submit$/, // /managed-auth/connect-links/submit\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\\/[^\\/]+\\/cookies$/, // /profiles/{profile_id}/cookies\n /^profiles\\/[^\\/]+\\/duplicate$/, // /profiles/{profile_id}/duplicate\n /^profiles\\/create$/, // /profiles/create\n\n // Prompts endpoints\n /^prompts\\/improve$/, // /prompts/improve\n /^prompts\\/nudge$/, // /prompts/nudge\n\n // Proxies endpoints\n /^proxies\\/gateway\\/credentials$/, // /proxies/gateway/credentials\n\n // Ready endpoints\n /^ready$/, // /ready\n\n // Root endpoints\n /^health$/, // /health\n /^scrape$/, // /scrape\n /^scrape_from_html$/, // /scrape_from_html\n\n // Search endpoints\n /^search$/, // /search\n\n // Secrets endpoints\n /^secrets$/, // /secrets\n /^secrets\\/[^\\/]+$/, // /secrets/{name}\n /^secrets\\/[^\\/]+$/, // /secrets/{secret_id}\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\\/[^\\/]+\\/files$/, // /sessions/{session_id}/files\n /^sessions\\/[^\\/]+\\/files\\/[^\\/]+$/, // /sessions/{session_id}/files/{file_id}\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 // 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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACOO,IAAM,2BAAqC;AAAA;AAAA,EAEhD;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;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","names":["data"]}
|
|
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 if (typeof config.authenticate !== 'function') {\n throw new NotteProxyAuthError('Proxy caller authentication is required');\n }\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 // Function run endpoints authenticate with their own header (the API does\n // not forward `Authorization` to the runtime), so inject it as well.\n forwardHeaders.set('x-notte-api-key', 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 signal: request.signal,\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\\/[^\\/]+\\/rollback$/, // /functions/{function_id}/rollback\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 /^functions\\/health$/, // /functions/health\n\n // Mailboxes endpoints\n /^mailboxes$/, // /mailboxes\n /^mailboxes\\/[^\\/]+$/, // /mailboxes/{mailbox_id}\n /^mailboxes\\/connect-token$/, // /mailboxes/connect-token\n /^mailboxes\\/sync$/, // /mailboxes/sync\n\n // Managed-auth endpoints\n /^managed-auth\\/connect-links$/, // /managed-auth/connect-links\n /^managed-auth\\/connect-links\\/context$/, // /managed-auth/connect-links/context\n /^managed-auth\\/connect-links\\/mailboxes$/, // /managed-auth/connect-links/mailboxes\n /^managed-auth\\/connect-links\\/mailboxes\\/connect$/, // /managed-auth/connect-links/mailboxes/connect\n /^managed-auth\\/connect-links\\/mailboxes\\/sync$/, // /managed-auth/connect-links/mailboxes/sync\n /^managed-auth\\/connect-links\\/status$/, // /managed-auth/connect-links/status\n /^managed-auth\\/connect-links\\/submit$/, // /managed-auth/connect-links/submit\n /^managed-auth\\/connections\\/[^\\/]+\\/check$/, // /managed-auth/connections/{connection_id}/check\n /^managed-auth\\/connections\\/[^\\/]+\\/reauthenticate$/, // /managed-auth/connections/{connection_id}/reauthenticate\n /^managed-auth\\/connections\\/[^\\/]+\\/refresh$/, // /managed-auth/connections/{connection_id}/refresh\n /^managed-auth\\/operations\\/[^\\/]+$/, // /managed-auth/operations/{operation_id}\n\n // Payments endpoints\n /^payments\\/[^\\/]+$/, // /payments/{payment_id}\n /^payments\\/connect$/, // /payments/connect\n /^payments\\/disconnect$/, // /payments/disconnect\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\\/[^\\/]+\\/cookies$/, // /profiles/{profile_id}/cookies\n /^profiles\\/[^\\/]+\\/duplicate$/, // /profiles/{profile_id}/duplicate\n /^profiles\\/create$/, // /profiles/create\n\n // Prompts endpoints\n /^prompts\\/improve$/, // /prompts/improve\n /^prompts\\/nudge$/, // /prompts/nudge\n\n // Ready endpoints\n /^ready$/, // /ready\n\n // Root endpoints\n /^health$/, // /health\n /^scrape$/, // /scrape\n /^scrape_from_html$/, // /scrape_from_html\n\n // Search endpoints\n /^search$/, // /search\n\n // Secrets endpoints\n /^secrets$/, // /secrets\n /^secrets\\/[^\\/]+$/, // /secrets/{name}\n /^secrets\\/[^\\/]+$/, // /secrets/{secret_id}\n\n // Sessions endpoints\n /^sessions$/, // /sessions\n /^sessions\\/[^\\/]+$/, // /sessions/{session_id}\n /^sessions\\/[^\\/]+\\/auth$/, // /sessions/{session_id}/auth\n /^sessions\\/[^\\/]+\\/cookies$/, // /sessions/{session_id}/cookies\n /^sessions\\/[^\\/]+\\/debug$/, // /sessions/{session_id}/debug\n /^sessions\\/[^\\/]+\\/files$/, // /sessions/{session_id}/files\n /^sessions\\/[^\\/]+\\/files\\/[^\\/]+$/, // /sessions/{session_id}/files/{file_id}\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\\/[^\\/]+\\/payments$/, // /sessions/{session_id}/payments\n /^sessions\\/[^\\/]+\\/profile-preview$/, // /sessions/{session_id}/profile-preview\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 // 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 * Authenticate and authorize incoming requests. Missing callbacks fail closed (401).\n * Check resource ownership as well as caller identity. With a shared key, every\n * authorized caller otherwise has access to the same account resources.\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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACOO,IAAM,2BAAqC;AAAA;AAAA,EAEhD;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;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","names":["data"]}
|
package/dist/proxy/core.mjs
CHANGED
|
@@ -57,6 +57,21 @@ var DEFAULT_ALLOWED_PATTERNS = [
|
|
|
57
57
|
// /managed-auth/connect-links/status
|
|
58
58
|
/^managed-auth\/connect-links\/submit$/,
|
|
59
59
|
// /managed-auth/connect-links/submit
|
|
60
|
+
/^managed-auth\/connections\/[^\/]+\/check$/,
|
|
61
|
+
// /managed-auth/connections/{connection_id}/check
|
|
62
|
+
/^managed-auth\/connections\/[^\/]+\/reauthenticate$/,
|
|
63
|
+
// /managed-auth/connections/{connection_id}/reauthenticate
|
|
64
|
+
/^managed-auth\/connections\/[^\/]+\/refresh$/,
|
|
65
|
+
// /managed-auth/connections/{connection_id}/refresh
|
|
66
|
+
/^managed-auth\/operations\/[^\/]+$/,
|
|
67
|
+
// /managed-auth/operations/{operation_id}
|
|
68
|
+
// Payments endpoints
|
|
69
|
+
/^payments\/[^\/]+$/,
|
|
70
|
+
// /payments/{payment_id}
|
|
71
|
+
/^payments\/connect$/,
|
|
72
|
+
// /payments/connect
|
|
73
|
+
/^payments\/disconnect$/,
|
|
74
|
+
// /payments/disconnect
|
|
60
75
|
// Personas endpoints
|
|
61
76
|
/^personas$/,
|
|
62
77
|
// /personas
|
|
@@ -84,9 +99,6 @@ var DEFAULT_ALLOWED_PATTERNS = [
|
|
|
84
99
|
// /prompts/improve
|
|
85
100
|
/^prompts\/nudge$/,
|
|
86
101
|
// /prompts/nudge
|
|
87
|
-
// Proxies endpoints
|
|
88
|
-
/^proxies\/gateway\/credentials$/,
|
|
89
|
-
// /proxies/gateway/credentials
|
|
90
102
|
// Ready endpoints
|
|
91
103
|
/^ready$/,
|
|
92
104
|
// /ready
|
|
@@ -112,6 +124,8 @@ var DEFAULT_ALLOWED_PATTERNS = [
|
|
|
112
124
|
// /sessions
|
|
113
125
|
/^sessions\/[^\/]+$/,
|
|
114
126
|
// /sessions/{session_id}
|
|
127
|
+
/^sessions\/[^\/]+\/auth$/,
|
|
128
|
+
// /sessions/{session_id}/auth
|
|
115
129
|
/^sessions\/[^\/]+\/cookies$/,
|
|
116
130
|
// /sessions/{session_id}/cookies
|
|
117
131
|
/^sessions\/[^\/]+\/debug$/,
|
|
@@ -132,6 +146,10 @@ var DEFAULT_ALLOWED_PATTERNS = [
|
|
|
132
146
|
// /sessions/{session_id}/page/scrape
|
|
133
147
|
/^sessions\/[^\/]+\/page\/screenshot$/,
|
|
134
148
|
// /sessions/{session_id}/page/screenshot
|
|
149
|
+
/^sessions\/[^\/]+\/payments$/,
|
|
150
|
+
// /sessions/{session_id}/payments
|
|
151
|
+
/^sessions\/[^\/]+\/profile-preview$/,
|
|
152
|
+
// /sessions/{session_id}/profile-preview
|
|
135
153
|
/^sessions\/[^\/]+\/replay$/,
|
|
136
154
|
// /sessions/{session_id}/replay
|
|
137
155
|
/^sessions\/[^\/]+\/stop$/,
|
|
@@ -205,6 +223,9 @@ async function handleProxyRequest(request, pathSegments, config) {
|
|
|
205
223
|
);
|
|
206
224
|
}
|
|
207
225
|
}
|
|
226
|
+
if (typeof config.authenticate !== "function") {
|
|
227
|
+
throw new NotteProxyAuthError("Proxy caller authentication is required");
|
|
228
|
+
}
|
|
208
229
|
let apiKey = config.apiKey || process.env.NOTTE_API_KEY;
|
|
209
230
|
if (config.authenticate) {
|
|
210
231
|
const result = await config.authenticate(request);
|
|
@@ -228,6 +249,7 @@ async function handleProxyRequest(request, pathSegments, config) {
|
|
|
228
249
|
const targetUrl = `${apiUrl}/${path}${searchParams ? `?${searchParams}` : ""}`;
|
|
229
250
|
const forwardHeaders = new Headers();
|
|
230
251
|
forwardHeaders.set("Authorization", `Bearer ${apiKey}`);
|
|
252
|
+
forwardHeaders.set("x-notte-api-key", apiKey);
|
|
231
253
|
forwardHeaders.set(
|
|
232
254
|
"x-notte-request-origin",
|
|
233
255
|
config.requestOrigin || "sdk-proxy"
|
|
@@ -250,7 +272,8 @@ async function handleProxyRequest(request, pathSegments, config) {
|
|
|
250
272
|
}
|
|
251
273
|
const requestOptions = {
|
|
252
274
|
method: request.method,
|
|
253
|
-
headers: forwardHeaders
|
|
275
|
+
headers: forwardHeaders,
|
|
276
|
+
signal: request.signal
|
|
254
277
|
};
|
|
255
278
|
if (METHODS_WITH_BODY.includes(request.method) && request.body) {
|
|
256
279
|
requestOptions.body = request.body;
|
package/dist/proxy/core.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/proxy/patterns.ts","../../src/proxy/types.ts","../../src/proxy/core.ts"],"sourcesContent":["/**\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\\/[^\\/]+\\/rollback$/, // /functions/{function_id}/rollback\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 /^functions\\/health$/, // /functions/health\n\n // Mailboxes endpoints\n /^mailboxes$/, // /mailboxes\n /^mailboxes\\/[^\\/]+$/, // /mailboxes/{mailbox_id}\n /^mailboxes\\/connect-token$/, // /mailboxes/connect-token\n /^mailboxes\\/sync$/, // /mailboxes/sync\n\n // Managed-auth endpoints\n /^managed-auth\\/connect-links$/, // /managed-auth/connect-links\n /^managed-auth\\/connect-links\\/context$/, // /managed-auth/connect-links/context\n /^managed-auth\\/connect-links\\/mailboxes$/, // /managed-auth/connect-links/mailboxes\n /^managed-auth\\/connect-links\\/mailboxes\\/connect$/, // /managed-auth/connect-links/mailboxes/connect\n /^managed-auth\\/connect-links\\/mailboxes\\/sync$/, // /managed-auth/connect-links/mailboxes/sync\n /^managed-auth\\/connect-links\\/status$/, // /managed-auth/connect-links/status\n /^managed-auth\\/connect-links\\/submit$/, // /managed-auth/connect-links/submit\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\\/[^\\/]+\\/cookies$/, // /profiles/{profile_id}/cookies\n /^profiles\\/[^\\/]+\\/duplicate$/, // /profiles/{profile_id}/duplicate\n /^profiles\\/create$/, // /profiles/create\n\n // Prompts endpoints\n /^prompts\\/improve$/, // /prompts/improve\n /^prompts\\/nudge$/, // /prompts/nudge\n\n // Proxies endpoints\n /^proxies\\/gateway\\/credentials$/, // /proxies/gateway/credentials\n\n // Ready endpoints\n /^ready$/, // /ready\n\n // Root endpoints\n /^health$/, // /health\n /^scrape$/, // /scrape\n /^scrape_from_html$/, // /scrape_from_html\n\n // Search endpoints\n /^search$/, // /search\n\n // Secrets endpoints\n /^secrets$/, // /secrets\n /^secrets\\/[^\\/]+$/, // /secrets/{name}\n /^secrets\\/[^\\/]+$/, // /secrets/{secret_id}\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\\/[^\\/]+\\/files$/, // /sessions/{session_id}/files\n /^sessions\\/[^\\/]+\\/files\\/[^\\/]+$/, // /sessions/{session_id}/files/{file_id}\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 // 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","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"],"mappings":";AAOO,IAAM,2BAAqC;AAAA;AAAA,EAEhD;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAWO,SAAS,aACd,cACA,iBACsB;AACtB,QAAM,OAAO,aAAa,KAAK,GAAG;AAGlC,MAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,WAAW,GAAG,GAAG;AACtE,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,iBAAiB,gBAAgB,KAAK,CAAC,YAAY,QAAQ,KAAK,IAAI,CAAC;AAC3E,MAAI,CAAC,gBAAgB;AACnB,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,iBAAiB,IAAI;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,KAAK;AACzB;;;ACpIO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAC7C,YAAY,UAAU,gBAAgB;AACpC,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;;;ACbA,IAAM,kBAAkB;AACxB,IAAM,0BAA0B,CAAC,gBAAgB,QAAQ;AACzD,IAAM,oBAAoB,CAAC,QAAQ,OAAO,OAAO;AAcjD,eAAsB,mBACpB,SACA,cACA,QACmB;AACnB,MAAI;AAEF,QAAI,OAAO,oBAAoB,OAAO;AACpC,YAAM,WAAW,OAAO,mBAAmB;AAC3C,YAAM,iBAAiB,aAAa,cAAc,QAAQ;AAE1D,UAAI,CAAC,eAAe,SAAS;AAC3B,eAAO,IAAI;AAAA,UACT,KAAK,UAAU;AAAA,YACb,OAAO;AAAA,YACP,SAAS,eAAe;AAAA,UAC1B,CAAC;AAAA,UACD;AAAA,YACE,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAChD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,SAAS,OAAO,UAAU,QAAQ,IAAI;AAE1C,QAAI,OAAO,cAAc;AACvB,YAAM,SAAS,MAAM,OAAO,aAAa,OAAO;AAChD,UAAI,OAAO,WAAW,UAAU;AAC9B,iBAAS;AAAA,MACX;AAAA,IACF;AAEA,QAAI,CAAC,QAAQ;AACX,aAAO,IAAI;AAAA,QACT,KAAK,UAAU,EAAE,OAAO,wBAAwB,CAAC;AAAA,QACjD;AAAA,UACE,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AAGA,UAAM,UAAU,OAAO,UAAU,iBAAiB,QAAQ,OAAO,EAAE;AACnE,UAAM,OAAO,aAAa,KAAK,GAAG;AAClC,UAAM,cAAc,IAAI,IAAI,QAAQ,GAAG;AACvC,UAAM,eAAe,YAAY,aAAa,SAAS;AACvD,UAAM,YAAY,GAAG,MAAM,IAAI,IAAI,GAAG,eAAe,IAAI,YAAY,KAAK,EAAE;AAG5E,UAAM,iBAAiB,IAAI,QAAQ;AACnC,mBAAe,IAAI,iBAAiB,UAAU,MAAM,EAAE;AACtD,mBAAe;AAAA,MACb;AAAA,MACA,OAAO,iBAAiB;AAAA,IAC1B;AAEA,UAAM,mBACJ,OAAO,kBAAkB;AAC3B,eAAW,UAAU,kBAAkB;AACrC,YAAM,QAAQ,QAAQ,QAAQ,IAAI,MAAM;AACxC,UAAI,OAAO;AACT,uBAAe,IAAI,QAAQ,KAAK;AAAA,MAClC;AAAA,IACF;AAGA,QAAI,OAAO,iBAAiB;AAC1B,YAAM,OAAO,gBAAgB;AAAA,QAC3B;AAAA,QACA,QAAQ,QAAQ;AAAA,QAChB,SAAS;AAAA,QACT,KAAK;AAAA,QACL,iBAAiB;AAAA,MACnB,CAAC;AAAA,IACH;AAGA,UAAM,iBAA8B;AAAA,MAClC,QAAQ,QAAQ;AAAA,MAChB,SAAS;AAAA,IACX;AAEA,QAAI,kBAAkB,SAAS,QAAQ,MAAM,KAAK,QAAQ,MAAM;AAC9D,qBAAe,OAAO,QAAQ;AAC9B,MAAC,eAAuB,SAAS;AAAA,IACnC;AAEA,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,MAAM,WAAW,cAAc;AAAA,IAClD,SAAS,YAAY;AACnB,YAAM,eACJ,sBAAsB,QAClB,WAAW,UACX;AACN,aAAO,IAAI;AAAA,QACT,KAAK,UAAU;AAAA,UACb,OAAO;AAAA,UACP,SAAS;AAAA,QACX,CAAC;AAAA,QACD;AAAA,UACE,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AAGA,UAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;AAE5D,UAAM,mBACJ,YAAY,WAAW,QAAQ,KAC/B,YAAY,WAAW,QAAQ,KAC/B,YAAY,WAAW,QAAQ,KAC/B,YAAY,SAAS,cAAc;AAErC,UAAM,sBAAsB,YAAY,SAAS,mBAAmB;AAGpE,QAAI,uBAAuB,SAAS,MAAM;AACxC,aAAO,IAAI,SAAS,SAAS,MAAM;AAAA,QACjC,QAAQ,SAAS;AAAA,QACjB,YAAY,SAAS;AAAA,QACrB,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,YAAY;AAAA,QACd;AAAA,MACF,CAAC;AAAA,IACH;AAGA,QAAI,SAAS,MAAM;AACjB,aAAO,IAAI,SAAS,SAAS,MAAM;AAAA,QACjC,QAAQ,SAAS;AAAA,QACjB,YAAY,SAAS;AAAA,QACrB,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAGA,QAAI,kBAAkB;AACpB,YAAMA,QAAO,MAAM,SAAS,YAAY;AACxC,aAAO,IAAI,SAASA,OAAM;AAAA,QACxB,QAAQ,SAAS;AAAA,QACjB,YAAY,SAAS;AAAA,QACrB,SAAS,EAAE,gBAAgB,YAAY;AAAA,MACzC,CAAC;AAAA,IACH;AAGA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,WAAO,IAAI,SAAS,MAAM;AAAA,MACxB,QAAQ,SAAS;AAAA,MACjB,YAAY,SAAS;AAAA,MACrB,SAAS,EAAE,gBAAgB,YAAY;AAAA,IACzC,CAAC;AAAA,EACH,SAAS,OAAO;AAEd,QAAI,iBAAiB,qBAAqB;AACxC,aAAO,IAAI;AAAA,QACT,KAAK,UAAU,EAAE,OAAO,MAAM,QAAQ,CAAC;AAAA,QACvC;AAAA,UACE,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AAGA,UAAM;AAAA,EACR;AACF;","names":["data"]}
|
|
1
|
+
{"version":3,"sources":["../../src/proxy/patterns.ts","../../src/proxy/types.ts","../../src/proxy/core.ts"],"sourcesContent":["/**\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\\/[^\\/]+\\/rollback$/, // /functions/{function_id}/rollback\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 /^functions\\/health$/, // /functions/health\n\n // Mailboxes endpoints\n /^mailboxes$/, // /mailboxes\n /^mailboxes\\/[^\\/]+$/, // /mailboxes/{mailbox_id}\n /^mailboxes\\/connect-token$/, // /mailboxes/connect-token\n /^mailboxes\\/sync$/, // /mailboxes/sync\n\n // Managed-auth endpoints\n /^managed-auth\\/connect-links$/, // /managed-auth/connect-links\n /^managed-auth\\/connect-links\\/context$/, // /managed-auth/connect-links/context\n /^managed-auth\\/connect-links\\/mailboxes$/, // /managed-auth/connect-links/mailboxes\n /^managed-auth\\/connect-links\\/mailboxes\\/connect$/, // /managed-auth/connect-links/mailboxes/connect\n /^managed-auth\\/connect-links\\/mailboxes\\/sync$/, // /managed-auth/connect-links/mailboxes/sync\n /^managed-auth\\/connect-links\\/status$/, // /managed-auth/connect-links/status\n /^managed-auth\\/connect-links\\/submit$/, // /managed-auth/connect-links/submit\n /^managed-auth\\/connections\\/[^\\/]+\\/check$/, // /managed-auth/connections/{connection_id}/check\n /^managed-auth\\/connections\\/[^\\/]+\\/reauthenticate$/, // /managed-auth/connections/{connection_id}/reauthenticate\n /^managed-auth\\/connections\\/[^\\/]+\\/refresh$/, // /managed-auth/connections/{connection_id}/refresh\n /^managed-auth\\/operations\\/[^\\/]+$/, // /managed-auth/operations/{operation_id}\n\n // Payments endpoints\n /^payments\\/[^\\/]+$/, // /payments/{payment_id}\n /^payments\\/connect$/, // /payments/connect\n /^payments\\/disconnect$/, // /payments/disconnect\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\\/[^\\/]+\\/cookies$/, // /profiles/{profile_id}/cookies\n /^profiles\\/[^\\/]+\\/duplicate$/, // /profiles/{profile_id}/duplicate\n /^profiles\\/create$/, // /profiles/create\n\n // Prompts endpoints\n /^prompts\\/improve$/, // /prompts/improve\n /^prompts\\/nudge$/, // /prompts/nudge\n\n // Ready endpoints\n /^ready$/, // /ready\n\n // Root endpoints\n /^health$/, // /health\n /^scrape$/, // /scrape\n /^scrape_from_html$/, // /scrape_from_html\n\n // Search endpoints\n /^search$/, // /search\n\n // Secrets endpoints\n /^secrets$/, // /secrets\n /^secrets\\/[^\\/]+$/, // /secrets/{name}\n /^secrets\\/[^\\/]+$/, // /secrets/{secret_id}\n\n // Sessions endpoints\n /^sessions$/, // /sessions\n /^sessions\\/[^\\/]+$/, // /sessions/{session_id}\n /^sessions\\/[^\\/]+\\/auth$/, // /sessions/{session_id}/auth\n /^sessions\\/[^\\/]+\\/cookies$/, // /sessions/{session_id}/cookies\n /^sessions\\/[^\\/]+\\/debug$/, // /sessions/{session_id}/debug\n /^sessions\\/[^\\/]+\\/files$/, // /sessions/{session_id}/files\n /^sessions\\/[^\\/]+\\/files\\/[^\\/]+$/, // /sessions/{session_id}/files/{file_id}\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\\/[^\\/]+\\/payments$/, // /sessions/{session_id}/payments\n /^sessions\\/[^\\/]+\\/profile-preview$/, // /sessions/{session_id}/profile-preview\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 // 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 * Authenticate and authorize incoming requests. Missing callbacks fail closed (401).\n * Check resource ownership as well as caller identity. With a shared key, every\n * authorized caller otherwise has access to the same account resources.\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","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 if (typeof config.authenticate !== 'function') {\n throw new NotteProxyAuthError('Proxy caller authentication is required');\n }\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 // Function run endpoints authenticate with their own header (the API does\n // not forward `Authorization` to the runtime), so inject it as well.\n forwardHeaders.set('x-notte-api-key', 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 signal: request.signal,\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"],"mappings":";AAOO,IAAM,2BAAqC;AAAA;AAAA,EAEhD;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;A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","names":["data"]}
|
package/dist/proxy/next.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as NotteProxyConfig } from '../types-
|
|
2
|
-
export { N as NotteProxyAuthError } from '../types-
|
|
1
|
+
import { a as NotteProxyConfig } from '../types-C5V9BOux.mjs';
|
|
2
|
+
export { N as NotteProxyAuthError } from '../types-C5V9BOux.mjs';
|
|
3
3
|
export { handleProxyRequest } from './core.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -39,22 +39,28 @@ type NextRouteHandler = (request: Request, context: {
|
|
|
39
39
|
* @example
|
|
40
40
|
* ```typescript
|
|
41
41
|
* // app/api/notte/[...path]/route.ts
|
|
42
|
-
* import { createNotteProxy } from '
|
|
42
|
+
* import { createNotteProxy, NotteProxyAuthError } from 'notte-sdk/next';
|
|
43
|
+
* import { getUser } from '@/lib/auth'; // Your server-side authentication implementation
|
|
43
44
|
*
|
|
44
45
|
* export const { GET, POST, PUT, DELETE, PATCH } = createNotteProxy({
|
|
45
|
-
*
|
|
46
|
+
* authenticate: async (request) => {
|
|
47
|
+
* const user = await getUser(request);
|
|
48
|
+
* if (!user?.notteApiKey) throw new NotteProxyAuthError();
|
|
49
|
+
* return user.notteApiKey;
|
|
50
|
+
* },
|
|
46
51
|
* });
|
|
47
52
|
* ```
|
|
48
53
|
*
|
|
49
54
|
* @example With authentication
|
|
50
55
|
* ```typescript
|
|
51
|
-
* import { createNotteProxy, NotteProxyAuthError } from '
|
|
56
|
+
* import { createNotteProxy, NotteProxyAuthError } from 'notte-sdk/next';
|
|
52
57
|
*
|
|
53
58
|
* export const { GET, POST, PUT, DELETE, PATCH } = createNotteProxy({
|
|
54
59
|
* apiKey: process.env.NOTTE_API_KEY!,
|
|
55
60
|
* authenticate: async (request) => {
|
|
56
61
|
* const session = await getSession(request);
|
|
57
62
|
* if (!session) throw new NotteProxyAuthError('Not logged in');
|
|
63
|
+
* await authorizeNotteRequest(session, request); // Enforce resource ownership
|
|
58
64
|
* },
|
|
59
65
|
* });
|
|
60
66
|
* ```
|
|
@@ -62,10 +68,9 @@ type NextRouteHandler = (request: Request, context: {
|
|
|
62
68
|
* @example With per-user API keys
|
|
63
69
|
* ```typescript
|
|
64
70
|
* export const { GET, POST, PUT, DELETE, PATCH } = createNotteProxy({
|
|
65
|
-
* apiKey: 'fallback-key', // pragma: allowlist secret
|
|
66
71
|
* authenticate: async (request) => {
|
|
67
72
|
* const user = await getUser(request);
|
|
68
|
-
* if (!user) throw new NotteProxyAuthError();
|
|
73
|
+
* if (!user?.notteApiKey) throw new NotteProxyAuthError();
|
|
69
74
|
* return user.notteApiKey; // returned string overrides apiKey
|
|
70
75
|
* },
|
|
71
76
|
* });
|
package/dist/proxy/next.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as NotteProxyConfig } from '../types-
|
|
2
|
-
export { N as NotteProxyAuthError } from '../types-
|
|
1
|
+
import { a as NotteProxyConfig } from '../types-C5V9BOux.js';
|
|
2
|
+
export { N as NotteProxyAuthError } from '../types-C5V9BOux.js';
|
|
3
3
|
export { handleProxyRequest } from './core.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -39,22 +39,28 @@ type NextRouteHandler = (request: Request, context: {
|
|
|
39
39
|
* @example
|
|
40
40
|
* ```typescript
|
|
41
41
|
* // app/api/notte/[...path]/route.ts
|
|
42
|
-
* import { createNotteProxy } from '
|
|
42
|
+
* import { createNotteProxy, NotteProxyAuthError } from 'notte-sdk/next';
|
|
43
|
+
* import { getUser } from '@/lib/auth'; // Your server-side authentication implementation
|
|
43
44
|
*
|
|
44
45
|
* export const { GET, POST, PUT, DELETE, PATCH } = createNotteProxy({
|
|
45
|
-
*
|
|
46
|
+
* authenticate: async (request) => {
|
|
47
|
+
* const user = await getUser(request);
|
|
48
|
+
* if (!user?.notteApiKey) throw new NotteProxyAuthError();
|
|
49
|
+
* return user.notteApiKey;
|
|
50
|
+
* },
|
|
46
51
|
* });
|
|
47
52
|
* ```
|
|
48
53
|
*
|
|
49
54
|
* @example With authentication
|
|
50
55
|
* ```typescript
|
|
51
|
-
* import { createNotteProxy, NotteProxyAuthError } from '
|
|
56
|
+
* import { createNotteProxy, NotteProxyAuthError } from 'notte-sdk/next';
|
|
52
57
|
*
|
|
53
58
|
* export const { GET, POST, PUT, DELETE, PATCH } = createNotteProxy({
|
|
54
59
|
* apiKey: process.env.NOTTE_API_KEY!,
|
|
55
60
|
* authenticate: async (request) => {
|
|
56
61
|
* const session = await getSession(request);
|
|
57
62
|
* if (!session) throw new NotteProxyAuthError('Not logged in');
|
|
63
|
+
* await authorizeNotteRequest(session, request); // Enforce resource ownership
|
|
58
64
|
* },
|
|
59
65
|
* });
|
|
60
66
|
* ```
|
|
@@ -62,10 +68,9 @@ type NextRouteHandler = (request: Request, context: {
|
|
|
62
68
|
* @example With per-user API keys
|
|
63
69
|
* ```typescript
|
|
64
70
|
* export const { GET, POST, PUT, DELETE, PATCH } = createNotteProxy({
|
|
65
|
-
* apiKey: 'fallback-key', // pragma: allowlist secret
|
|
66
71
|
* authenticate: async (request) => {
|
|
67
72
|
* const user = await getUser(request);
|
|
68
|
-
* if (!user) throw new NotteProxyAuthError();
|
|
73
|
+
* if (!user?.notteApiKey) throw new NotteProxyAuthError();
|
|
69
74
|
* return user.notteApiKey; // returned string overrides apiKey
|
|
70
75
|
* },
|
|
71
76
|
* });
|
package/dist/proxy/next.js
CHANGED
|
@@ -87,6 +87,21 @@ var DEFAULT_ALLOWED_PATTERNS = [
|
|
|
87
87
|
// /managed-auth/connect-links/status
|
|
88
88
|
/^managed-auth\/connect-links\/submit$/,
|
|
89
89
|
// /managed-auth/connect-links/submit
|
|
90
|
+
/^managed-auth\/connections\/[^\/]+\/check$/,
|
|
91
|
+
// /managed-auth/connections/{connection_id}/check
|
|
92
|
+
/^managed-auth\/connections\/[^\/]+\/reauthenticate$/,
|
|
93
|
+
// /managed-auth/connections/{connection_id}/reauthenticate
|
|
94
|
+
/^managed-auth\/connections\/[^\/]+\/refresh$/,
|
|
95
|
+
// /managed-auth/connections/{connection_id}/refresh
|
|
96
|
+
/^managed-auth\/operations\/[^\/]+$/,
|
|
97
|
+
// /managed-auth/operations/{operation_id}
|
|
98
|
+
// Payments endpoints
|
|
99
|
+
/^payments\/[^\/]+$/,
|
|
100
|
+
// /payments/{payment_id}
|
|
101
|
+
/^payments\/connect$/,
|
|
102
|
+
// /payments/connect
|
|
103
|
+
/^payments\/disconnect$/,
|
|
104
|
+
// /payments/disconnect
|
|
90
105
|
// Personas endpoints
|
|
91
106
|
/^personas$/,
|
|
92
107
|
// /personas
|
|
@@ -114,9 +129,6 @@ var DEFAULT_ALLOWED_PATTERNS = [
|
|
|
114
129
|
// /prompts/improve
|
|
115
130
|
/^prompts\/nudge$/,
|
|
116
131
|
// /prompts/nudge
|
|
117
|
-
// Proxies endpoints
|
|
118
|
-
/^proxies\/gateway\/credentials$/,
|
|
119
|
-
// /proxies/gateway/credentials
|
|
120
132
|
// Ready endpoints
|
|
121
133
|
/^ready$/,
|
|
122
134
|
// /ready
|
|
@@ -142,6 +154,8 @@ var DEFAULT_ALLOWED_PATTERNS = [
|
|
|
142
154
|
// /sessions
|
|
143
155
|
/^sessions\/[^\/]+$/,
|
|
144
156
|
// /sessions/{session_id}
|
|
157
|
+
/^sessions\/[^\/]+\/auth$/,
|
|
158
|
+
// /sessions/{session_id}/auth
|
|
145
159
|
/^sessions\/[^\/]+\/cookies$/,
|
|
146
160
|
// /sessions/{session_id}/cookies
|
|
147
161
|
/^sessions\/[^\/]+\/debug$/,
|
|
@@ -162,6 +176,10 @@ var DEFAULT_ALLOWED_PATTERNS = [
|
|
|
162
176
|
// /sessions/{session_id}/page/scrape
|
|
163
177
|
/^sessions\/[^\/]+\/page\/screenshot$/,
|
|
164
178
|
// /sessions/{session_id}/page/screenshot
|
|
179
|
+
/^sessions\/[^\/]+\/payments$/,
|
|
180
|
+
// /sessions/{session_id}/payments
|
|
181
|
+
/^sessions\/[^\/]+\/profile-preview$/,
|
|
182
|
+
// /sessions/{session_id}/profile-preview
|
|
165
183
|
/^sessions\/[^\/]+\/replay$/,
|
|
166
184
|
// /sessions/{session_id}/replay
|
|
167
185
|
/^sessions\/[^\/]+\/stop$/,
|
|
@@ -235,6 +253,9 @@ async function handleProxyRequest(request, pathSegments, config) {
|
|
|
235
253
|
);
|
|
236
254
|
}
|
|
237
255
|
}
|
|
256
|
+
if (typeof config.authenticate !== "function") {
|
|
257
|
+
throw new NotteProxyAuthError("Proxy caller authentication is required");
|
|
258
|
+
}
|
|
238
259
|
let apiKey = config.apiKey || process.env.NOTTE_API_KEY;
|
|
239
260
|
if (config.authenticate) {
|
|
240
261
|
const result = await config.authenticate(request);
|
|
@@ -258,6 +279,7 @@ async function handleProxyRequest(request, pathSegments, config) {
|
|
|
258
279
|
const targetUrl = `${apiUrl}/${path}${searchParams ? `?${searchParams}` : ""}`;
|
|
259
280
|
const forwardHeaders = new Headers();
|
|
260
281
|
forwardHeaders.set("Authorization", `Bearer ${apiKey}`);
|
|
282
|
+
forwardHeaders.set("x-notte-api-key", apiKey);
|
|
261
283
|
forwardHeaders.set(
|
|
262
284
|
"x-notte-request-origin",
|
|
263
285
|
config.requestOrigin || "sdk-proxy"
|
|
@@ -280,7 +302,8 @@ async function handleProxyRequest(request, pathSegments, config) {
|
|
|
280
302
|
}
|
|
281
303
|
const requestOptions = {
|
|
282
304
|
method: request.method,
|
|
283
|
-
headers: forwardHeaders
|
|
305
|
+
headers: forwardHeaders,
|
|
306
|
+
signal: request.signal
|
|
284
307
|
};
|
|
285
308
|
if (METHODS_WITH_BODY.includes(request.method) && request.body) {
|
|
286
309
|
requestOptions.body = request.body;
|