@astrasyncai/adapter-edge 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cloudflare.d.ts +2 -2
- package/dist/cloudflare.js +2 -0
- package/dist/cloudflare.js.map +1 -1
- package/dist/fastly.d.ts +2 -2
- package/dist/fastly.js +2 -0
- package/dist/fastly.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/{types-7lIOikBV.d.ts → types-BhaNxFeN.d.ts} +7 -0
- package/dist/vercel.d.ts +2 -2
- package/dist/vercel.js +2 -0
- package/dist/vercel.js.map +1 -1
- package/dist/{web-BjPu7biw.d.ts → web-BR_TKwJs.d.ts} +1 -1
- package/package.json +2 -2
package/dist/cloudflare.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { E as EdgeGatewayOptions } from './types-
|
|
2
|
-
export { G as GatewayPass } from './types-
|
|
1
|
+
import { E as EdgeGatewayOptions } from './types-BhaNxFeN.js';
|
|
2
|
+
export { G as GatewayPass } from './types-BhaNxFeN.js';
|
|
3
3
|
import '@astrasyncai/verification-gateway/edge-config';
|
|
4
4
|
import '@astrasyncai/verification-gateway/edge-core';
|
|
5
5
|
|
package/dist/cloudflare.js
CHANGED
|
@@ -17,6 +17,7 @@ function resolveOptions(options, env, logPrefix) {
|
|
|
17
17
|
return {
|
|
18
18
|
...options,
|
|
19
19
|
apiBaseUrl: options.apiBaseUrl ?? e.ASTRASYNC_API_BASE_URL ?? DEFAULT_API_BASE_URL,
|
|
20
|
+
publicBaseUrl: options.publicBaseUrl ?? e.ASTRASYNC_PUBLIC_BASE_URL,
|
|
20
21
|
counterpartyId: options.counterpartyId ?? e.ASTRASYNC_COUNTERPARTY_ID,
|
|
21
22
|
counterpartyUrl: options.counterpartyUrl ?? e.ASTRASYNC_COUNTERPARTY_URL,
|
|
22
23
|
apiKey: options.apiKey ?? e.ASTRASYNC_API_KEY,
|
|
@@ -165,6 +166,7 @@ function createGatewayHandler(options = {}) {
|
|
|
165
166
|
const { application } = await evaluateEdgeRequest(
|
|
166
167
|
{
|
|
167
168
|
apiBaseUrl: resolved.apiBaseUrl,
|
|
169
|
+
publicBaseUrl: resolved.publicBaseUrl,
|
|
168
170
|
counterpartyId: resolved.counterpartyId,
|
|
169
171
|
counterpartyUrl: resolved.counterpartyUrl,
|
|
170
172
|
budgets: resolved.budgets,
|
package/dist/cloudflare.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cloudflare.ts","../src/config.ts","../src/web.ts"],"sourcesContent":["/**\n * AstraSync Trusted Agent Gateway — Cloudflare Workers adapter.\n *\n * ```ts\n * import { createGatewayHandler } from '@astrasyncai/adapter-edge/cloudflare';\n *\n * export default { fetch: createGatewayHandler() };\n * ```\n *\n * Configuration comes from the worker's environment bindings\n * (`ASTRASYNC_API_KEY`, `ASTRASYNC_COUNTERPARTY_ID`,\n * `ASTRASYNC_COUNTERPARTY_URL`, optional `ASTRASYNC_API_BASE_URL`) or from\n * explicit options, which win. The handler NEVER blocks traffic outside\n * enforce mode and fails open on any internal error.\n */\n\nimport { evaluateEdgeRequest } from '@astrasyncai/verification-gateway/edge-core';\nimport { resolveOptions, isConfigured, loadConfig } from './config';\nimport type { EdgeGatewayOptions } from './types';\nimport { normalizeWebRequest, buildPassRequest, toJsonResponse } from './web';\n\nexport type { EdgeGatewayOptions, GatewayPass } from './types';\n\n/** The subset of `request.cf` connection properties the gateway captures. */\ninterface CfConnectionProperties {\n country?: string;\n asn?: number | string;\n asOrganization?: string;\n tlsVersion?: string;\n tlsCipher?: string;\n httpProtocol?: string;\n city?: string;\n colo?: string;\n timezone?: string;\n regionCode?: string;\n continent?: string;\n clientTcpRtt?: number;\n /** Present only on zones with Bot Management entitlement. */\n botManagement?: {\n ja3Hash?: string;\n ja4?: string;\n };\n}\n\n/** Structural subset of the Workers `ExecutionContext`. */\nexport interface CloudflareExecutionContext {\n waitUntil(promise: Promise<unknown>): void;\n}\n\nexport type CloudflareEnv = Record<string, unknown>;\n\nexport type CloudflareFetchHandler = (\n request: Request,\n env?: CloudflareEnv,\n ctx?: CloudflareExecutionContext\n) => Promise<Response>;\n\n/**\n * Connection-layer signals Cloudflare exposes on `request.cf` plus the\n * `CF-Connecting-IP` header. Absent values are simply omitted.\n */\nexport function collectCloudflareConnection(request: Request): Record<string, string> {\n const cf = (request as Request & { cf?: CfConnectionProperties }).cf;\n const out: Record<string, string> = {};\n const ip = request.headers.get('cf-connecting-ip');\n if (ip) out.ip = ip;\n if (!cf) return out;\n if (cf.country) out.country = cf.country;\n if (cf.city) out.city = cf.city;\n if (cf.asn !== undefined && cf.asn !== null && cf.asn !== '') out.asn = String(cf.asn);\n if (cf.asOrganization) out.asOrg = cf.asOrganization;\n if (cf.tlsVersion) out.tlsVersion = cf.tlsVersion;\n if (cf.tlsCipher) out.tlsCipher = cf.tlsCipher;\n if (cf.httpProtocol) out.httpVersion = cf.httpProtocol;\n if (cf.colo) out.colo = cf.colo;\n if (cf.timezone) out.timeZone = cf.timezone;\n if (cf.regionCode) out.region = cf.regionCode;\n if (cf.continent) out.continent = cf.continent;\n if (typeof cf.clientTcpRtt === 'number') out.clientTcpRtt = String(cf.clientTcpRtt);\n // JA3/JA4 ride request.cf only on Bot Management zones — harmless elsewhere.\n if (cf.botManagement?.ja3Hash) out.tlsFingerprint = cf.botManagement.ja3Hash;\n if (cf.botManagement?.ja4) out.ja4 = cf.botManagement.ja4;\n return out;\n}\n\nfunction envStrings(env: CloudflareEnv | undefined): Record<string, string | undefined> {\n const out: Record<string, string | undefined> = {};\n for (const [key, value] of Object.entries(env ?? {})) {\n if (typeof value === 'string') out[key] = value;\n }\n return out;\n}\n\n/**\n * Create the worker `fetch` handler. Pass traffic is forwarded to the\n * origin with `fetch(mutatedRequest)` (spoofable trust headers stripped,\n * attestation headers injected); enforce-mode denials short-circuit with a\n * machine-readable JSON response. Telemetry beacons are scheduled through\n * `ctx.waitUntil`, so they never add request latency.\n */\nexport function createGatewayHandler(options: EdgeGatewayOptions = {}): CloudflareFetchHandler {\n return async function fetchHandler(request, env, ctx) {\n const resolved = resolveOptions(options, envStrings(env), 'astrasync-edge/cloudflare');\n try {\n if (!isConfigured(resolved)) return fetch(request);\n\n const config = await loadConfig(resolved);\n const normalized = await normalizeWebRequest(request, {\n maxBodyBytes: resolved.maxBodyBytes,\n clientIp: request.headers.get('cf-connecting-ip') ?? undefined,\n connection: collectCloudflareConnection(request),\n });\n\n const { application } = await evaluateEdgeRequest(\n {\n apiBaseUrl: resolved.apiBaseUrl,\n counterpartyId: resolved.counterpartyId,\n counterpartyUrl: resolved.counterpartyUrl,\n budgets: resolved.budgets,\n random: resolved.random,\n log: resolved.log,\n ...(ctx && { waitUntil: (p: Promise<unknown>) => ctx.waitUntil(p) }),\n },\n normalized,\n config,\n resolved.apiKey\n );\n\n if (application.action === 'respond') return toJsonResponse(application);\n return fetch(buildPassRequest(request, application));\n } catch (err) {\n // Fail-open, unconditionally: the gateway must never take the site down.\n resolved.log('error', 'gateway error — passing request through', {\n error: err instanceof Error ? err.message : String(err),\n });\n return fetch(request);\n }\n };\n}\n","/**\n * Option/environment resolution and dashboard EdgeConfig loading shared by\n * the platform adapters.\n */\n\nimport {\n getEdgeConfig,\n DEFAULT_EDGE_CONFIG,\n type EdgeConfig,\n} from '@astrasyncai/verification-gateway/edge-config';\nimport type { EdgeLogger } from '@astrasyncai/verification-gateway/edge-core';\nimport type { EdgeGatewayOptions } from './types';\n\nexport const DEFAULT_API_BASE_URL = 'https://astrasync.ai/api';\n\nexport interface ResolvedGatewayOptions extends EdgeGatewayOptions {\n apiBaseUrl: string;\n log: EdgeLogger;\n}\n\nexport function defaultLogger(prefix: string): EdgeLogger {\n return (level, message, data) => {\n console[level === 'info' ? 'log' : level](`[${prefix}] ${message}`, data ?? '');\n };\n}\n\n/**\n * Merge explicit options with runtime environment variables (options win)\n * and apply defaults. `env` is the platform's environment map — Cloudflare\n * passes the worker `env` binding object, Vercel passes `process.env`,\n * Fastly Compute has no ambient env map (configure via options).\n */\nexport function resolveOptions(\n options: EdgeGatewayOptions,\n env: Record<string, string | undefined> | undefined,\n logPrefix: string\n): ResolvedGatewayOptions {\n const e = env ?? {};\n return {\n ...options,\n apiBaseUrl: options.apiBaseUrl ?? e.ASTRASYNC_API_BASE_URL ?? DEFAULT_API_BASE_URL,\n counterpartyId: options.counterpartyId ?? e.ASTRASYNC_COUNTERPARTY_ID,\n counterpartyUrl: options.counterpartyUrl ?? e.ASTRASYNC_COUNTERPARTY_URL,\n apiKey: options.apiKey ?? e.ASTRASYNC_API_KEY,\n log: options.log ?? defaultLogger(logPrefix),\n };\n}\n\n/**\n * Missing identity means the gateway cannot evaluate anything — it fails\n * OPEN (all traffic passes untouched) and says why, once per request.\n */\nexport function isConfigured(\n resolved: ResolvedGatewayOptions\n): resolved is ResolvedGatewayOptions & { counterpartyId: string; counterpartyUrl: string } {\n if (resolved.counterpartyId && resolved.counterpartyUrl) return true;\n resolved.log(\n 'error',\n 'counterpartyId/counterpartyUrl are not configured — passing all traffic through. ' +\n 'Set the ASTRASYNC_COUNTERPARTY_ID / ASTRASYNC_COUNTERPARTY_URL environment variables ' +\n 'or pass them as options.'\n );\n return false;\n}\n\n/**\n * Load the effective EdgeConfig. Delegates caching/fallback/degradation to\n * the SDK (60s TTL, stale-while-revalidate, enforce→observe degradation on\n * prolonged staleness) and never throws.\n */\nexport async function loadConfig(\n resolved: ResolvedGatewayOptions & { counterpartyId: string }\n): Promise<EdgeConfig> {\n try {\n return await getEdgeConfig(\n { apiBaseUrl: resolved.apiBaseUrl, apiKey: resolved.apiKey },\n resolved.counterpartyId,\n { ttlMs: resolved.configTtlMs }\n );\n } catch {\n return resolved.fallbackConfig ?? DEFAULT_EDGE_CONFIG;\n }\n}\n","/**\n * Shared web-standard platform I/O: converts a fetch-API `Request` into\n * the edge core's `NormalizedRequest`, and applies the core's abstract\n * outcome application back onto fetch-API primitives.\n */\n\nimport type {\n NormalizedRequest,\n OutcomeApplication,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport type { GatewayPass } from './types';\n\n/** Default request-body read cap for commerce-protocol detection. */\nexport const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;\n\nexport interface WebNormalizeOptions {\n /** Body read cap in bytes. Default {@link DEFAULT_MAX_BODY_BYTES}. */\n maxBodyBytes?: number;\n /** Client IP as the platform reports it. */\n clientIp?: string;\n /** Connection-layer metadata the platform collected (country, asn, …). */\n connection?: Record<string, string>;\n}\n\nasync function readBodyCapped(\n request: Request,\n cap: number\n): Promise<{ text?: string; truncated: boolean }> {\n if (request.method === 'GET' || request.method === 'HEAD') return { truncated: false };\n const body = request.clone().body;\n if (!body) return { truncated: false };\n\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n let truncated = false;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (total + value.byteLength > cap) {\n chunks.push(value.subarray(0, cap - total));\n total = cap;\n truncated = true;\n // Fire-and-forget: cancelling one branch of a cloned (tee'd) body can\n // only settle once the other branch is consumed — never await it.\n void reader.cancel().catch(() => {});\n break;\n }\n chunks.push(value);\n total += value.byteLength;\n }\n if (total === 0 && !truncated) return { truncated: false };\n\n const joined = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n joined.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return { text: new TextDecoder().decode(joined), truncated };\n}\n\n/**\n * Normalize a fetch-API `Request` into the edge core's request shape.\n * Header names are already lowercased (and multi-valued headers joined\n * with `\", \"`) by the `Headers` class. The body is read from a CLONE — the\n * original request stays forwardable to the origin — and capped at\n * `maxBodyBytes` (detection sees a flagged, truncated prefix beyond it).\n */\nexport async function normalizeWebRequest(\n request: Request,\n options: WebNormalizeOptions = {}\n): Promise<NormalizedRequest> {\n const url = new URL(request.url);\n const headers: Record<string, string> = {};\n request.headers.forEach((value, name) => {\n headers[name] = value;\n });\n const body = await readBodyCapped(request, options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);\n const connection = options.connection ?? {};\n return {\n method: request.method,\n uri: url.pathname,\n querystring: url.search.startsWith('?') ? url.search.slice(1) : url.search,\n headers,\n ...(body.text !== undefined && { bodyText: body.text }),\n ...(body.truncated && { bodyTruncated: true }),\n ...(options.clientIp && { clientIp: options.clientIp }),\n ...(Object.keys(connection).length > 0 && { connection }),\n };\n}\n\n/**\n * Apply a pass application to a header set: strip every header matching\n * one of the spoofable prefixes, then set the injected trust headers.\n * Returns a NEW `Headers` instance; the input is not mutated.\n */\nexport function applyGatewayPass(headers: Headers, pass: GatewayPass): Headers {\n const out = new Headers(headers);\n for (const prefix of pass.stripHeaderPrefixes) {\n const p = prefix.toLowerCase();\n for (const name of [...out.keys()]) {\n if (name.startsWith(p)) out.delete(name);\n }\n }\n for (const [name, value] of Object.entries(pass.injectHeaders)) {\n out.set(name, value);\n }\n return out;\n}\n\n/**\n * Build the origin-bound request for a pass application: the same request\n * with spoofable headers stripped and trust headers injected.\n */\nexport function buildPassRequest(request: Request, pass: GatewayPass): Request {\n if (pass.stripHeaderPrefixes.length === 0 && Object.keys(pass.injectHeaders).length === 0) {\n return request;\n }\n return new Request(request, { headers: applyGatewayPass(request.headers, pass) });\n}\n\n/** Build the short-circuit JSON response for a respond application. */\nexport function toJsonResponse(\n application: Extract<OutcomeApplication, { action: 'respond' }>\n): Response {\n return new Response(JSON.stringify(application.bodyJson), {\n status: application.status,\n headers: { 'content-type': 'application/json', ...application.headers },\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/cloudflare.ts","../src/config.ts","../src/web.ts"],"sourcesContent":["/**\n * AstraSync Trusted Agent Gateway — Cloudflare Workers adapter.\n *\n * ```ts\n * import { createGatewayHandler } from '@astrasyncai/adapter-edge/cloudflare';\n *\n * export default { fetch: createGatewayHandler() };\n * ```\n *\n * Configuration comes from the worker's environment bindings\n * (`ASTRASYNC_API_KEY`, `ASTRASYNC_COUNTERPARTY_ID`,\n * `ASTRASYNC_COUNTERPARTY_URL`, optional `ASTRASYNC_API_BASE_URL`) or from\n * explicit options, which win. The handler NEVER blocks traffic outside\n * enforce mode and fails open on any internal error.\n */\n\nimport { evaluateEdgeRequest } from '@astrasyncai/verification-gateway/edge-core';\nimport { resolveOptions, isConfigured, loadConfig } from './config';\nimport type { EdgeGatewayOptions } from './types';\nimport { normalizeWebRequest, buildPassRequest, toJsonResponse } from './web';\n\nexport type { EdgeGatewayOptions, GatewayPass } from './types';\n\n/** The subset of `request.cf` connection properties the gateway captures. */\ninterface CfConnectionProperties {\n country?: string;\n asn?: number | string;\n asOrganization?: string;\n tlsVersion?: string;\n tlsCipher?: string;\n httpProtocol?: string;\n city?: string;\n colo?: string;\n timezone?: string;\n regionCode?: string;\n continent?: string;\n clientTcpRtt?: number;\n /** Present only on zones with Bot Management entitlement. */\n botManagement?: {\n ja3Hash?: string;\n ja4?: string;\n };\n}\n\n/** Structural subset of the Workers `ExecutionContext`. */\nexport interface CloudflareExecutionContext {\n waitUntil(promise: Promise<unknown>): void;\n}\n\nexport type CloudflareEnv = Record<string, unknown>;\n\nexport type CloudflareFetchHandler = (\n request: Request,\n env?: CloudflareEnv,\n ctx?: CloudflareExecutionContext\n) => Promise<Response>;\n\n/**\n * Connection-layer signals Cloudflare exposes on `request.cf` plus the\n * `CF-Connecting-IP` header. Absent values are simply omitted.\n */\nexport function collectCloudflareConnection(request: Request): Record<string, string> {\n const cf = (request as Request & { cf?: CfConnectionProperties }).cf;\n const out: Record<string, string> = {};\n const ip = request.headers.get('cf-connecting-ip');\n if (ip) out.ip = ip;\n if (!cf) return out;\n if (cf.country) out.country = cf.country;\n if (cf.city) out.city = cf.city;\n if (cf.asn !== undefined && cf.asn !== null && cf.asn !== '') out.asn = String(cf.asn);\n if (cf.asOrganization) out.asOrg = cf.asOrganization;\n if (cf.tlsVersion) out.tlsVersion = cf.tlsVersion;\n if (cf.tlsCipher) out.tlsCipher = cf.tlsCipher;\n if (cf.httpProtocol) out.httpVersion = cf.httpProtocol;\n if (cf.colo) out.colo = cf.colo;\n if (cf.timezone) out.timeZone = cf.timezone;\n if (cf.regionCode) out.region = cf.regionCode;\n if (cf.continent) out.continent = cf.continent;\n if (typeof cf.clientTcpRtt === 'number') out.clientTcpRtt = String(cf.clientTcpRtt);\n // JA3/JA4 ride request.cf only on Bot Management zones — harmless elsewhere.\n if (cf.botManagement?.ja3Hash) out.tlsFingerprint = cf.botManagement.ja3Hash;\n if (cf.botManagement?.ja4) out.ja4 = cf.botManagement.ja4;\n return out;\n}\n\nfunction envStrings(env: CloudflareEnv | undefined): Record<string, string | undefined> {\n const out: Record<string, string | undefined> = {};\n for (const [key, value] of Object.entries(env ?? {})) {\n if (typeof value === 'string') out[key] = value;\n }\n return out;\n}\n\n/**\n * Create the worker `fetch` handler. Pass traffic is forwarded to the\n * origin with `fetch(mutatedRequest)` (spoofable trust headers stripped,\n * attestation headers injected); enforce-mode denials short-circuit with a\n * machine-readable JSON response. Telemetry beacons are scheduled through\n * `ctx.waitUntil`, so they never add request latency.\n */\nexport function createGatewayHandler(options: EdgeGatewayOptions = {}): CloudflareFetchHandler {\n return async function fetchHandler(request, env, ctx) {\n const resolved = resolveOptions(options, envStrings(env), 'astrasync-edge/cloudflare');\n try {\n if (!isConfigured(resolved)) return fetch(request);\n\n const config = await loadConfig(resolved);\n const normalized = await normalizeWebRequest(request, {\n maxBodyBytes: resolved.maxBodyBytes,\n clientIp: request.headers.get('cf-connecting-ip') ?? undefined,\n connection: collectCloudflareConnection(request),\n });\n\n const { application } = await evaluateEdgeRequest(\n {\n apiBaseUrl: resolved.apiBaseUrl,\n publicBaseUrl: resolved.publicBaseUrl,\n counterpartyId: resolved.counterpartyId,\n counterpartyUrl: resolved.counterpartyUrl,\n budgets: resolved.budgets,\n random: resolved.random,\n log: resolved.log,\n ...(ctx && { waitUntil: (p: Promise<unknown>) => ctx.waitUntil(p) }),\n },\n normalized,\n config,\n resolved.apiKey\n );\n\n if (application.action === 'respond') return toJsonResponse(application);\n return fetch(buildPassRequest(request, application));\n } catch (err) {\n // Fail-open, unconditionally: the gateway must never take the site down.\n resolved.log('error', 'gateway error — passing request through', {\n error: err instanceof Error ? err.message : String(err),\n });\n return fetch(request);\n }\n };\n}\n","/**\n * Option/environment resolution and dashboard EdgeConfig loading shared by\n * the platform adapters.\n */\n\nimport {\n getEdgeConfig,\n DEFAULT_EDGE_CONFIG,\n type EdgeConfig,\n} from '@astrasyncai/verification-gateway/edge-config';\nimport type { EdgeLogger } from '@astrasyncai/verification-gateway/edge-core';\nimport type { EdgeGatewayOptions } from './types';\n\nexport const DEFAULT_API_BASE_URL = 'https://astrasync.ai/api';\n\nexport interface ResolvedGatewayOptions extends EdgeGatewayOptions {\n apiBaseUrl: string;\n log: EdgeLogger;\n}\n\nexport function defaultLogger(prefix: string): EdgeLogger {\n return (level, message, data) => {\n console[level === 'info' ? 'log' : level](`[${prefix}] ${message}`, data ?? '');\n };\n}\n\n/**\n * Merge explicit options with runtime environment variables (options win)\n * and apply defaults. `env` is the platform's environment map — Cloudflare\n * passes the worker `env` binding object, Vercel passes `process.env`,\n * Fastly Compute has no ambient env map (configure via options).\n */\nexport function resolveOptions(\n options: EdgeGatewayOptions,\n env: Record<string, string | undefined> | undefined,\n logPrefix: string\n): ResolvedGatewayOptions {\n const e = env ?? {};\n return {\n ...options,\n apiBaseUrl: options.apiBaseUrl ?? e.ASTRASYNC_API_BASE_URL ?? DEFAULT_API_BASE_URL,\n publicBaseUrl: options.publicBaseUrl ?? e.ASTRASYNC_PUBLIC_BASE_URL,\n counterpartyId: options.counterpartyId ?? e.ASTRASYNC_COUNTERPARTY_ID,\n counterpartyUrl: options.counterpartyUrl ?? e.ASTRASYNC_COUNTERPARTY_URL,\n apiKey: options.apiKey ?? e.ASTRASYNC_API_KEY,\n log: options.log ?? defaultLogger(logPrefix),\n };\n}\n\n/**\n * Missing identity means the gateway cannot evaluate anything — it fails\n * OPEN (all traffic passes untouched) and says why, once per request.\n */\nexport function isConfigured(\n resolved: ResolvedGatewayOptions\n): resolved is ResolvedGatewayOptions & { counterpartyId: string; counterpartyUrl: string } {\n if (resolved.counterpartyId && resolved.counterpartyUrl) return true;\n resolved.log(\n 'error',\n 'counterpartyId/counterpartyUrl are not configured — passing all traffic through. ' +\n 'Set the ASTRASYNC_COUNTERPARTY_ID / ASTRASYNC_COUNTERPARTY_URL environment variables ' +\n 'or pass them as options.'\n );\n return false;\n}\n\n/**\n * Load the effective EdgeConfig. Delegates caching/fallback/degradation to\n * the SDK (60s TTL, stale-while-revalidate, enforce→observe degradation on\n * prolonged staleness) and never throws.\n */\nexport async function loadConfig(\n resolved: ResolvedGatewayOptions & { counterpartyId: string }\n): Promise<EdgeConfig> {\n try {\n return await getEdgeConfig(\n { apiBaseUrl: resolved.apiBaseUrl, apiKey: resolved.apiKey },\n resolved.counterpartyId,\n { ttlMs: resolved.configTtlMs }\n );\n } catch {\n return resolved.fallbackConfig ?? DEFAULT_EDGE_CONFIG;\n }\n}\n","/**\n * Shared web-standard platform I/O: converts a fetch-API `Request` into\n * the edge core's `NormalizedRequest`, and applies the core's abstract\n * outcome application back onto fetch-API primitives.\n */\n\nimport type {\n NormalizedRequest,\n OutcomeApplication,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport type { GatewayPass } from './types';\n\n/** Default request-body read cap for commerce-protocol detection. */\nexport const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;\n\nexport interface WebNormalizeOptions {\n /** Body read cap in bytes. Default {@link DEFAULT_MAX_BODY_BYTES}. */\n maxBodyBytes?: number;\n /** Client IP as the platform reports it. */\n clientIp?: string;\n /** Connection-layer metadata the platform collected (country, asn, …). */\n connection?: Record<string, string>;\n}\n\nasync function readBodyCapped(\n request: Request,\n cap: number\n): Promise<{ text?: string; truncated: boolean }> {\n if (request.method === 'GET' || request.method === 'HEAD') return { truncated: false };\n const body = request.clone().body;\n if (!body) return { truncated: false };\n\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n let truncated = false;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (total + value.byteLength > cap) {\n chunks.push(value.subarray(0, cap - total));\n total = cap;\n truncated = true;\n // Fire-and-forget: cancelling one branch of a cloned (tee'd) body can\n // only settle once the other branch is consumed — never await it.\n void reader.cancel().catch(() => {});\n break;\n }\n chunks.push(value);\n total += value.byteLength;\n }\n if (total === 0 && !truncated) return { truncated: false };\n\n const joined = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n joined.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return { text: new TextDecoder().decode(joined), truncated };\n}\n\n/**\n * Normalize a fetch-API `Request` into the edge core's request shape.\n * Header names are already lowercased (and multi-valued headers joined\n * with `\", \"`) by the `Headers` class. The body is read from a CLONE — the\n * original request stays forwardable to the origin — and capped at\n * `maxBodyBytes` (detection sees a flagged, truncated prefix beyond it).\n */\nexport async function normalizeWebRequest(\n request: Request,\n options: WebNormalizeOptions = {}\n): Promise<NormalizedRequest> {\n const url = new URL(request.url);\n const headers: Record<string, string> = {};\n request.headers.forEach((value, name) => {\n headers[name] = value;\n });\n const body = await readBodyCapped(request, options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);\n const connection = options.connection ?? {};\n return {\n method: request.method,\n uri: url.pathname,\n querystring: url.search.startsWith('?') ? url.search.slice(1) : url.search,\n headers,\n ...(body.text !== undefined && { bodyText: body.text }),\n ...(body.truncated && { bodyTruncated: true }),\n ...(options.clientIp && { clientIp: options.clientIp }),\n ...(Object.keys(connection).length > 0 && { connection }),\n };\n}\n\n/**\n * Apply a pass application to a header set: strip every header matching\n * one of the spoofable prefixes, then set the injected trust headers.\n * Returns a NEW `Headers` instance; the input is not mutated.\n */\nexport function applyGatewayPass(headers: Headers, pass: GatewayPass): Headers {\n const out = new Headers(headers);\n for (const prefix of pass.stripHeaderPrefixes) {\n const p = prefix.toLowerCase();\n for (const name of [...out.keys()]) {\n if (name.startsWith(p)) out.delete(name);\n }\n }\n for (const [name, value] of Object.entries(pass.injectHeaders)) {\n out.set(name, value);\n }\n return out;\n}\n\n/**\n * Build the origin-bound request for a pass application: the same request\n * with spoofable headers stripped and trust headers injected.\n */\nexport function buildPassRequest(request: Request, pass: GatewayPass): Request {\n if (pass.stripHeaderPrefixes.length === 0 && Object.keys(pass.injectHeaders).length === 0) {\n return request;\n }\n return new Request(request, { headers: applyGatewayPass(request.headers, pass) });\n}\n\n/** Build the short-circuit JSON response for a respond application. */\nexport function toJsonResponse(\n application: Extract<OutcomeApplication, { action: 'respond' }>\n): Response {\n return new Response(JSON.stringify(application.bodyJson), {\n status: application.status,\n headers: { 'content-type': 'application/json', ...application.headers },\n });\n}\n"],"mappings":"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","names":[]}
|
package/dist/fastly.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { E as EdgeGatewayOptions } from './types-
|
|
2
|
-
export { G as GatewayPass } from './types-
|
|
1
|
+
import { E as EdgeGatewayOptions } from './types-BhaNxFeN.js';
|
|
2
|
+
export { G as GatewayPass } from './types-BhaNxFeN.js';
|
|
3
3
|
import '@astrasyncai/verification-gateway/edge-config';
|
|
4
4
|
import '@astrasyncai/verification-gateway/edge-core';
|
|
5
5
|
|
package/dist/fastly.js
CHANGED
|
@@ -17,6 +17,7 @@ function resolveOptions(options, env, logPrefix) {
|
|
|
17
17
|
return {
|
|
18
18
|
...options,
|
|
19
19
|
apiBaseUrl: options.apiBaseUrl ?? e.ASTRASYNC_API_BASE_URL ?? DEFAULT_API_BASE_URL,
|
|
20
|
+
publicBaseUrl: options.publicBaseUrl ?? e.ASTRASYNC_PUBLIC_BASE_URL,
|
|
20
21
|
counterpartyId: options.counterpartyId ?? e.ASTRASYNC_COUNTERPARTY_ID,
|
|
21
22
|
counterpartyUrl: options.counterpartyUrl ?? e.ASTRASYNC_COUNTERPARTY_URL,
|
|
22
23
|
apiKey: options.apiKey ?? e.ASTRASYNC_API_KEY,
|
|
@@ -166,6 +167,7 @@ function createGatewayHandler(options) {
|
|
|
166
167
|
const { application } = await evaluateEdgeRequest(
|
|
167
168
|
{
|
|
168
169
|
apiBaseUrl: resolved.apiBaseUrl,
|
|
170
|
+
publicBaseUrl: resolved.publicBaseUrl,
|
|
169
171
|
counterpartyId: resolved.counterpartyId,
|
|
170
172
|
counterpartyUrl: resolved.counterpartyUrl,
|
|
171
173
|
budgets: resolved.budgets,
|
package/dist/fastly.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/fastly.ts","../src/config.ts","../src/web.ts"],"sourcesContent":["/**\n * AstraSync Trusted Agent Gateway — Fastly Compute (JavaScript) adapter.\n *\n * ```ts\n * import { createGatewayHandler } from '@astrasyncai/adapter-edge/fastly';\n *\n * const gateway = createGatewayHandler({\n * originBackend: 'origin', // the backend name configured on your service\n * counterpartyId: 'ASTRAE-…',\n * counterpartyUrl: 'https://example.shop',\n * apiKey: '…',\n * });\n *\n * addEventListener('fetch', (event) => event.respondWith(gateway(event)));\n * ```\n *\n * Fastly Compute requires every `fetch` to name a configured backend, so\n * `originBackend` is REQUIRED (the AstraSync API must also be reachable —\n * see the README for the api backend setup). Fastly has no ambient\n * environment map; configure via options.\n *\n * Geolocation enrichment (`fastly:geolocation`) is available behind the\n * opt-in `geo` flag: the import is dynamic, so the base build carries no\n * `fastly:*` imports and runs in any test runner.\n */\n\nimport { evaluateEdgeRequest } from '@astrasyncai/verification-gateway/edge-core';\nimport { resolveOptions, isConfigured, loadConfig } from './config';\nimport type { EdgeGatewayOptions } from './types';\nimport { normalizeWebRequest, buildPassRequest, toJsonResponse } from './web';\n\nexport type { EdgeGatewayOptions, GatewayPass } from './types';\n\nexport interface FastlyGatewayOptions extends EdgeGatewayOptions {\n /**\n * REQUIRED. The name of the backend (as configured on the Fastly\n * service) that origin-bound traffic is forwarded to — Fastly Compute\n * fetches must name a backend explicitly.\n */\n originBackend: string;\n /**\n * Opt-in geolocation enrichment via the `fastly:geolocation` runtime\n * module (country/city/ASN on the connection capture). Off by default so\n * the base build has no `fastly:*` imports.\n */\n geo?: boolean;\n}\n\n/** Structural subset of the Fastly Compute fetch event. */\nexport interface FastlyFetchEvent {\n request: Request;\n client?: { address?: string };\n waitUntil?(promise: Promise<unknown>): void;\n}\n\nexport type FastlyGatewayHandler = (event: FastlyFetchEvent) => Promise<Response>;\n\ninterface FastlyGeolocation {\n country_code?: string | null;\n city?: string | null;\n as_number?: number | null;\n as_name?: string | null;\n region?: string | null;\n metro_code?: number | null;\n utc_offset?: number | null;\n}\n\nasync function collectFastlyConnection(\n clientIp: string | undefined,\n geo: boolean | undefined\n): Promise<Record<string, string>> {\n const out: Record<string, string> = {};\n if (clientIp) out.ip = clientIp;\n if (!geo || !clientIp) return out;\n try {\n // Dynamic, variable-specifier import: only resolved inside the Fastly\n // runtime, invisible to bundlers/test runners when `geo` is off.\n const specifier = 'fastly:geolocation';\n const mod = (await import(/* @vite-ignore */ specifier)) as {\n getGeolocationForIpAddress(ip: string): FastlyGeolocation | null;\n };\n const g = mod.getGeolocationForIpAddress(clientIp);\n if (g?.country_code) out.country = g.country_code;\n if (g?.city) out.city = g.city;\n if (g?.as_number != null) out.asn = String(g.as_number);\n if (g?.as_name) out.asOrg = g.as_name;\n if (g?.region) out.region = g.region;\n if (g?.metro_code != null) out.metroCode = String(g.metro_code);\n // utc_offset is minutes-from-UTC (e.g. 600), not an IANA zone — keep the\n // raw numeric form; the shared timeZone key carries IANA zones only.\n if (g?.utc_offset != null) out.utcOffset = String(g.utc_offset);\n } catch {\n // Geolocation unavailable (non-Fastly runtime) — the partial block is fine.\n }\n return out;\n}\n\n/**\n * Create the fetch-event handler. Pass traffic is forwarded with\n * `fetch(mutatedRequest, { backend: originBackend })`; enforce-mode\n * denials short-circuit with a machine-readable JSON response. Telemetry\n * beacons are scheduled through `event.waitUntil` when available.\n */\nexport function createGatewayHandler(options: FastlyGatewayOptions): FastlyGatewayHandler {\n if (!options.originBackend) {\n throw new Error(\n '@astrasyncai/adapter-edge/fastly: `originBackend` is required — Fastly Compute ' +\n 'fetches must name a backend configured on the service.'\n );\n }\n const backendInit = { backend: options.originBackend } as RequestInit;\n\n return async function handleFetchEvent(event) {\n const request = event.request;\n const resolved = resolveOptions(options, undefined, 'astrasync-edge/fastly');\n try {\n if (!isConfigured(resolved)) return fetch(request, backendInit);\n\n const clientIp =\n request.headers.get('fastly-client-ip') ?? event.client?.address ?? undefined;\n const config = await loadConfig(resolved);\n const normalized = await normalizeWebRequest(request, {\n maxBodyBytes: resolved.maxBodyBytes,\n clientIp,\n connection: await collectFastlyConnection(clientIp, options.geo),\n });\n\n const { application } = await evaluateEdgeRequest(\n {\n apiBaseUrl: resolved.apiBaseUrl,\n counterpartyId: resolved.counterpartyId,\n counterpartyUrl: resolved.counterpartyUrl,\n budgets: resolved.budgets,\n random: resolved.random,\n log: resolved.log,\n ...(event.waitUntil && { waitUntil: (p: Promise<unknown>) => event.waitUntil!(p) }),\n },\n normalized,\n config,\n resolved.apiKey\n );\n\n if (application.action === 'respond') return toJsonResponse(application);\n return fetch(buildPassRequest(request, application), backendInit);\n } catch (err) {\n // Fail-open, unconditionally: the gateway must never take the site down.\n resolved.log('error', 'gateway error — passing request through', {\n error: err instanceof Error ? err.message : String(err),\n });\n return fetch(request, backendInit);\n }\n };\n}\n","/**\n * Option/environment resolution and dashboard EdgeConfig loading shared by\n * the platform adapters.\n */\n\nimport {\n getEdgeConfig,\n DEFAULT_EDGE_CONFIG,\n type EdgeConfig,\n} from '@astrasyncai/verification-gateway/edge-config';\nimport type { EdgeLogger } from '@astrasyncai/verification-gateway/edge-core';\nimport type { EdgeGatewayOptions } from './types';\n\nexport const DEFAULT_API_BASE_URL = 'https://astrasync.ai/api';\n\nexport interface ResolvedGatewayOptions extends EdgeGatewayOptions {\n apiBaseUrl: string;\n log: EdgeLogger;\n}\n\nexport function defaultLogger(prefix: string): EdgeLogger {\n return (level, message, data) => {\n console[level === 'info' ? 'log' : level](`[${prefix}] ${message}`, data ?? '');\n };\n}\n\n/**\n * Merge explicit options with runtime environment variables (options win)\n * and apply defaults. `env` is the platform's environment map — Cloudflare\n * passes the worker `env` binding object, Vercel passes `process.env`,\n * Fastly Compute has no ambient env map (configure via options).\n */\nexport function resolveOptions(\n options: EdgeGatewayOptions,\n env: Record<string, string | undefined> | undefined,\n logPrefix: string\n): ResolvedGatewayOptions {\n const e = env ?? {};\n return {\n ...options,\n apiBaseUrl: options.apiBaseUrl ?? e.ASTRASYNC_API_BASE_URL ?? DEFAULT_API_BASE_URL,\n counterpartyId: options.counterpartyId ?? e.ASTRASYNC_COUNTERPARTY_ID,\n counterpartyUrl: options.counterpartyUrl ?? e.ASTRASYNC_COUNTERPARTY_URL,\n apiKey: options.apiKey ?? e.ASTRASYNC_API_KEY,\n log: options.log ?? defaultLogger(logPrefix),\n };\n}\n\n/**\n * Missing identity means the gateway cannot evaluate anything — it fails\n * OPEN (all traffic passes untouched) and says why, once per request.\n */\nexport function isConfigured(\n resolved: ResolvedGatewayOptions\n): resolved is ResolvedGatewayOptions & { counterpartyId: string; counterpartyUrl: string } {\n if (resolved.counterpartyId && resolved.counterpartyUrl) return true;\n resolved.log(\n 'error',\n 'counterpartyId/counterpartyUrl are not configured — passing all traffic through. ' +\n 'Set the ASTRASYNC_COUNTERPARTY_ID / ASTRASYNC_COUNTERPARTY_URL environment variables ' +\n 'or pass them as options.'\n );\n return false;\n}\n\n/**\n * Load the effective EdgeConfig. Delegates caching/fallback/degradation to\n * the SDK (60s TTL, stale-while-revalidate, enforce→observe degradation on\n * prolonged staleness) and never throws.\n */\nexport async function loadConfig(\n resolved: ResolvedGatewayOptions & { counterpartyId: string }\n): Promise<EdgeConfig> {\n try {\n return await getEdgeConfig(\n { apiBaseUrl: resolved.apiBaseUrl, apiKey: resolved.apiKey },\n resolved.counterpartyId,\n { ttlMs: resolved.configTtlMs }\n );\n } catch {\n return resolved.fallbackConfig ?? DEFAULT_EDGE_CONFIG;\n }\n}\n","/**\n * Shared web-standard platform I/O: converts a fetch-API `Request` into\n * the edge core's `NormalizedRequest`, and applies the core's abstract\n * outcome application back onto fetch-API primitives.\n */\n\nimport type {\n NormalizedRequest,\n OutcomeApplication,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport type { GatewayPass } from './types';\n\n/** Default request-body read cap for commerce-protocol detection. */\nexport const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;\n\nexport interface WebNormalizeOptions {\n /** Body read cap in bytes. Default {@link DEFAULT_MAX_BODY_BYTES}. */\n maxBodyBytes?: number;\n /** Client IP as the platform reports it. */\n clientIp?: string;\n /** Connection-layer metadata the platform collected (country, asn, …). */\n connection?: Record<string, string>;\n}\n\nasync function readBodyCapped(\n request: Request,\n cap: number\n): Promise<{ text?: string; truncated: boolean }> {\n if (request.method === 'GET' || request.method === 'HEAD') return { truncated: false };\n const body = request.clone().body;\n if (!body) return { truncated: false };\n\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n let truncated = false;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (total + value.byteLength > cap) {\n chunks.push(value.subarray(0, cap - total));\n total = cap;\n truncated = true;\n // Fire-and-forget: cancelling one branch of a cloned (tee'd) body can\n // only settle once the other branch is consumed — never await it.\n void reader.cancel().catch(() => {});\n break;\n }\n chunks.push(value);\n total += value.byteLength;\n }\n if (total === 0 && !truncated) return { truncated: false };\n\n const joined = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n joined.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return { text: new TextDecoder().decode(joined), truncated };\n}\n\n/**\n * Normalize a fetch-API `Request` into the edge core's request shape.\n * Header names are already lowercased (and multi-valued headers joined\n * with `\", \"`) by the `Headers` class. The body is read from a CLONE — the\n * original request stays forwardable to the origin — and capped at\n * `maxBodyBytes` (detection sees a flagged, truncated prefix beyond it).\n */\nexport async function normalizeWebRequest(\n request: Request,\n options: WebNormalizeOptions = {}\n): Promise<NormalizedRequest> {\n const url = new URL(request.url);\n const headers: Record<string, string> = {};\n request.headers.forEach((value, name) => {\n headers[name] = value;\n });\n const body = await readBodyCapped(request, options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);\n const connection = options.connection ?? {};\n return {\n method: request.method,\n uri: url.pathname,\n querystring: url.search.startsWith('?') ? url.search.slice(1) : url.search,\n headers,\n ...(body.text !== undefined && { bodyText: body.text }),\n ...(body.truncated && { bodyTruncated: true }),\n ...(options.clientIp && { clientIp: options.clientIp }),\n ...(Object.keys(connection).length > 0 && { connection }),\n };\n}\n\n/**\n * Apply a pass application to a header set: strip every header matching\n * one of the spoofable prefixes, then set the injected trust headers.\n * Returns a NEW `Headers` instance; the input is not mutated.\n */\nexport function applyGatewayPass(headers: Headers, pass: GatewayPass): Headers {\n const out = new Headers(headers);\n for (const prefix of pass.stripHeaderPrefixes) {\n const p = prefix.toLowerCase();\n for (const name of [...out.keys()]) {\n if (name.startsWith(p)) out.delete(name);\n }\n }\n for (const [name, value] of Object.entries(pass.injectHeaders)) {\n out.set(name, value);\n }\n return out;\n}\n\n/**\n * Build the origin-bound request for a pass application: the same request\n * with spoofable headers stripped and trust headers injected.\n */\nexport function buildPassRequest(request: Request, pass: GatewayPass): Request {\n if (pass.stripHeaderPrefixes.length === 0 && Object.keys(pass.injectHeaders).length === 0) {\n return request;\n }\n return new Request(request, { headers: applyGatewayPass(request.headers, pass) });\n}\n\n/** Build the short-circuit JSON response for a respond application. */\nexport function toJsonResponse(\n application: Extract<OutcomeApplication, { action: 'respond' }>\n): Response {\n return new Response(JSON.stringify(application.bodyJson), {\n status: application.status,\n headers: { 'content-type': 'application/json', ...application.headers },\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/fastly.ts","../src/config.ts","../src/web.ts"],"sourcesContent":["/**\n * AstraSync Trusted Agent Gateway — Fastly Compute (JavaScript) adapter.\n *\n * ```ts\n * import { createGatewayHandler } from '@astrasyncai/adapter-edge/fastly';\n *\n * const gateway = createGatewayHandler({\n * originBackend: 'origin', // the backend name configured on your service\n * counterpartyId: 'ASTRAE-…',\n * counterpartyUrl: 'https://example.shop',\n * apiKey: '…',\n * });\n *\n * addEventListener('fetch', (event) => event.respondWith(gateway(event)));\n * ```\n *\n * Fastly Compute requires every `fetch` to name a configured backend, so\n * `originBackend` is REQUIRED (the AstraSync API must also be reachable —\n * see the README for the api backend setup). Fastly has no ambient\n * environment map; configure via options.\n *\n * Geolocation enrichment (`fastly:geolocation`) is available behind the\n * opt-in `geo` flag: the import is dynamic, so the base build carries no\n * `fastly:*` imports and runs in any test runner.\n */\n\nimport { evaluateEdgeRequest } from '@astrasyncai/verification-gateway/edge-core';\nimport { resolveOptions, isConfigured, loadConfig } from './config';\nimport type { EdgeGatewayOptions } from './types';\nimport { normalizeWebRequest, buildPassRequest, toJsonResponse } from './web';\n\nexport type { EdgeGatewayOptions, GatewayPass } from './types';\n\nexport interface FastlyGatewayOptions extends EdgeGatewayOptions {\n /**\n * REQUIRED. The name of the backend (as configured on the Fastly\n * service) that origin-bound traffic is forwarded to — Fastly Compute\n * fetches must name a backend explicitly.\n */\n originBackend: string;\n /**\n * Opt-in geolocation enrichment via the `fastly:geolocation` runtime\n * module (country/city/ASN on the connection capture). Off by default so\n * the base build has no `fastly:*` imports.\n */\n geo?: boolean;\n}\n\n/** Structural subset of the Fastly Compute fetch event. */\nexport interface FastlyFetchEvent {\n request: Request;\n client?: { address?: string };\n waitUntil?(promise: Promise<unknown>): void;\n}\n\nexport type FastlyGatewayHandler = (event: FastlyFetchEvent) => Promise<Response>;\n\ninterface FastlyGeolocation {\n country_code?: string | null;\n city?: string | null;\n as_number?: number | null;\n as_name?: string | null;\n region?: string | null;\n metro_code?: number | null;\n utc_offset?: number | null;\n}\n\nasync function collectFastlyConnection(\n clientIp: string | undefined,\n geo: boolean | undefined\n): Promise<Record<string, string>> {\n const out: Record<string, string> = {};\n if (clientIp) out.ip = clientIp;\n if (!geo || !clientIp) return out;\n try {\n // Dynamic, variable-specifier import: only resolved inside the Fastly\n // runtime, invisible to bundlers/test runners when `geo` is off.\n const specifier = 'fastly:geolocation';\n const mod = (await import(/* @vite-ignore */ specifier)) as {\n getGeolocationForIpAddress(ip: string): FastlyGeolocation | null;\n };\n const g = mod.getGeolocationForIpAddress(clientIp);\n if (g?.country_code) out.country = g.country_code;\n if (g?.city) out.city = g.city;\n if (g?.as_number != null) out.asn = String(g.as_number);\n if (g?.as_name) out.asOrg = g.as_name;\n if (g?.region) out.region = g.region;\n if (g?.metro_code != null) out.metroCode = String(g.metro_code);\n // utc_offset is minutes-from-UTC (e.g. 600), not an IANA zone — keep the\n // raw numeric form; the shared timeZone key carries IANA zones only.\n if (g?.utc_offset != null) out.utcOffset = String(g.utc_offset);\n } catch {\n // Geolocation unavailable (non-Fastly runtime) — the partial block is fine.\n }\n return out;\n}\n\n/**\n * Create the fetch-event handler. Pass traffic is forwarded with\n * `fetch(mutatedRequest, { backend: originBackend })`; enforce-mode\n * denials short-circuit with a machine-readable JSON response. Telemetry\n * beacons are scheduled through `event.waitUntil` when available.\n */\nexport function createGatewayHandler(options: FastlyGatewayOptions): FastlyGatewayHandler {\n if (!options.originBackend) {\n throw new Error(\n '@astrasyncai/adapter-edge/fastly: `originBackend` is required — Fastly Compute ' +\n 'fetches must name a backend configured on the service.'\n );\n }\n const backendInit = { backend: options.originBackend } as RequestInit;\n\n return async function handleFetchEvent(event) {\n const request = event.request;\n const resolved = resolveOptions(options, undefined, 'astrasync-edge/fastly');\n try {\n if (!isConfigured(resolved)) return fetch(request, backendInit);\n\n const clientIp =\n request.headers.get('fastly-client-ip') ?? event.client?.address ?? undefined;\n const config = await loadConfig(resolved);\n const normalized = await normalizeWebRequest(request, {\n maxBodyBytes: resolved.maxBodyBytes,\n clientIp,\n connection: await collectFastlyConnection(clientIp, options.geo),\n });\n\n const { application } = await evaluateEdgeRequest(\n {\n apiBaseUrl: resolved.apiBaseUrl,\n publicBaseUrl: resolved.publicBaseUrl,\n counterpartyId: resolved.counterpartyId,\n counterpartyUrl: resolved.counterpartyUrl,\n budgets: resolved.budgets,\n random: resolved.random,\n log: resolved.log,\n ...(event.waitUntil && { waitUntil: (p: Promise<unknown>) => event.waitUntil!(p) }),\n },\n normalized,\n config,\n resolved.apiKey\n );\n\n if (application.action === 'respond') return toJsonResponse(application);\n return fetch(buildPassRequest(request, application), backendInit);\n } catch (err) {\n // Fail-open, unconditionally: the gateway must never take the site down.\n resolved.log('error', 'gateway error — passing request through', {\n error: err instanceof Error ? err.message : String(err),\n });\n return fetch(request, backendInit);\n }\n };\n}\n","/**\n * Option/environment resolution and dashboard EdgeConfig loading shared by\n * the platform adapters.\n */\n\nimport {\n getEdgeConfig,\n DEFAULT_EDGE_CONFIG,\n type EdgeConfig,\n} from '@astrasyncai/verification-gateway/edge-config';\nimport type { EdgeLogger } from '@astrasyncai/verification-gateway/edge-core';\nimport type { EdgeGatewayOptions } from './types';\n\nexport const DEFAULT_API_BASE_URL = 'https://astrasync.ai/api';\n\nexport interface ResolvedGatewayOptions extends EdgeGatewayOptions {\n apiBaseUrl: string;\n log: EdgeLogger;\n}\n\nexport function defaultLogger(prefix: string): EdgeLogger {\n return (level, message, data) => {\n console[level === 'info' ? 'log' : level](`[${prefix}] ${message}`, data ?? '');\n };\n}\n\n/**\n * Merge explicit options with runtime environment variables (options win)\n * and apply defaults. `env` is the platform's environment map — Cloudflare\n * passes the worker `env` binding object, Vercel passes `process.env`,\n * Fastly Compute has no ambient env map (configure via options).\n */\nexport function resolveOptions(\n options: EdgeGatewayOptions,\n env: Record<string, string | undefined> | undefined,\n logPrefix: string\n): ResolvedGatewayOptions {\n const e = env ?? {};\n return {\n ...options,\n apiBaseUrl: options.apiBaseUrl ?? e.ASTRASYNC_API_BASE_URL ?? DEFAULT_API_BASE_URL,\n publicBaseUrl: options.publicBaseUrl ?? e.ASTRASYNC_PUBLIC_BASE_URL,\n counterpartyId: options.counterpartyId ?? e.ASTRASYNC_COUNTERPARTY_ID,\n counterpartyUrl: options.counterpartyUrl ?? e.ASTRASYNC_COUNTERPARTY_URL,\n apiKey: options.apiKey ?? e.ASTRASYNC_API_KEY,\n log: options.log ?? defaultLogger(logPrefix),\n };\n}\n\n/**\n * Missing identity means the gateway cannot evaluate anything — it fails\n * OPEN (all traffic passes untouched) and says why, once per request.\n */\nexport function isConfigured(\n resolved: ResolvedGatewayOptions\n): resolved is ResolvedGatewayOptions & { counterpartyId: string; counterpartyUrl: string } {\n if (resolved.counterpartyId && resolved.counterpartyUrl) return true;\n resolved.log(\n 'error',\n 'counterpartyId/counterpartyUrl are not configured — passing all traffic through. ' +\n 'Set the ASTRASYNC_COUNTERPARTY_ID / ASTRASYNC_COUNTERPARTY_URL environment variables ' +\n 'or pass them as options.'\n );\n return false;\n}\n\n/**\n * Load the effective EdgeConfig. Delegates caching/fallback/degradation to\n * the SDK (60s TTL, stale-while-revalidate, enforce→observe degradation on\n * prolonged staleness) and never throws.\n */\nexport async function loadConfig(\n resolved: ResolvedGatewayOptions & { counterpartyId: string }\n): Promise<EdgeConfig> {\n try {\n return await getEdgeConfig(\n { apiBaseUrl: resolved.apiBaseUrl, apiKey: resolved.apiKey },\n resolved.counterpartyId,\n { ttlMs: resolved.configTtlMs }\n );\n } catch {\n return resolved.fallbackConfig ?? DEFAULT_EDGE_CONFIG;\n }\n}\n","/**\n * Shared web-standard platform I/O: converts a fetch-API `Request` into\n * the edge core's `NormalizedRequest`, and applies the core's abstract\n * outcome application back onto fetch-API primitives.\n */\n\nimport type {\n NormalizedRequest,\n OutcomeApplication,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport type { GatewayPass } from './types';\n\n/** Default request-body read cap for commerce-protocol detection. */\nexport const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;\n\nexport interface WebNormalizeOptions {\n /** Body read cap in bytes. Default {@link DEFAULT_MAX_BODY_BYTES}. */\n maxBodyBytes?: number;\n /** Client IP as the platform reports it. */\n clientIp?: string;\n /** Connection-layer metadata the platform collected (country, asn, …). */\n connection?: Record<string, string>;\n}\n\nasync function readBodyCapped(\n request: Request,\n cap: number\n): Promise<{ text?: string; truncated: boolean }> {\n if (request.method === 'GET' || request.method === 'HEAD') return { truncated: false };\n const body = request.clone().body;\n if (!body) return { truncated: false };\n\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n let truncated = false;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (total + value.byteLength > cap) {\n chunks.push(value.subarray(0, cap - total));\n total = cap;\n truncated = true;\n // Fire-and-forget: cancelling one branch of a cloned (tee'd) body can\n // only settle once the other branch is consumed — never await it.\n void reader.cancel().catch(() => {});\n break;\n }\n chunks.push(value);\n total += value.byteLength;\n }\n if (total === 0 && !truncated) return { truncated: false };\n\n const joined = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n joined.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return { text: new TextDecoder().decode(joined), truncated };\n}\n\n/**\n * Normalize a fetch-API `Request` into the edge core's request shape.\n * Header names are already lowercased (and multi-valued headers joined\n * with `\", \"`) by the `Headers` class. The body is read from a CLONE — the\n * original request stays forwardable to the origin — and capped at\n * `maxBodyBytes` (detection sees a flagged, truncated prefix beyond it).\n */\nexport async function normalizeWebRequest(\n request: Request,\n options: WebNormalizeOptions = {}\n): Promise<NormalizedRequest> {\n const url = new URL(request.url);\n const headers: Record<string, string> = {};\n request.headers.forEach((value, name) => {\n headers[name] = value;\n });\n const body = await readBodyCapped(request, options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);\n const connection = options.connection ?? {};\n return {\n method: request.method,\n uri: url.pathname,\n querystring: url.search.startsWith('?') ? url.search.slice(1) : url.search,\n headers,\n ...(body.text !== undefined && { bodyText: body.text }),\n ...(body.truncated && { bodyTruncated: true }),\n ...(options.clientIp && { clientIp: options.clientIp }),\n ...(Object.keys(connection).length > 0 && { connection }),\n };\n}\n\n/**\n * Apply a pass application to a header set: strip every header matching\n * one of the spoofable prefixes, then set the injected trust headers.\n * Returns a NEW `Headers` instance; the input is not mutated.\n */\nexport function applyGatewayPass(headers: Headers, pass: GatewayPass): Headers {\n const out = new Headers(headers);\n for (const prefix of pass.stripHeaderPrefixes) {\n const p = prefix.toLowerCase();\n for (const name of [...out.keys()]) {\n if (name.startsWith(p)) out.delete(name);\n }\n }\n for (const [name, value] of Object.entries(pass.injectHeaders)) {\n out.set(name, value);\n }\n return out;\n}\n\n/**\n * Build the origin-bound request for a pass application: the same request\n * with spoofable headers stripped and trust headers injected.\n */\nexport function buildPassRequest(request: Request, pass: GatewayPass): Request {\n if (pass.stripHeaderPrefixes.length === 0 && Object.keys(pass.injectHeaders).length === 0) {\n return request;\n }\n return new Request(request, { headers: applyGatewayPass(request.headers, pass) });\n}\n\n/** Build the short-circuit JSON response for a respond application. */\nexport function toJsonResponse(\n application: Extract<OutcomeApplication, { action: 'respond' }>\n): Response {\n return new Response(JSON.stringify(application.bodyJson), {\n status: application.status,\n headers: { 'content-type': 'application/json', ...application.headers },\n });\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { E as EdgeGatewayOptions, G as GatewayPass } from './types-
|
|
2
|
-
export { D as DEFAULT_MAX_BODY_BYTES, W as WebNormalizeOptions, a as applyGatewayPass, b as buildPassRequest, n as normalizeWebRequest, t as toJsonResponse } from './web-
|
|
1
|
+
export { E as EdgeGatewayOptions, G as GatewayPass } from './types-BhaNxFeN.js';
|
|
2
|
+
export { D as DEFAULT_MAX_BODY_BYTES, W as WebNormalizeOptions, a as applyGatewayPass, b as buildPassRequest, n as normalizeWebRequest, t as toJsonResponse } from './web-BR_TKwJs.js';
|
|
3
3
|
export { Classification, EdgeDecision, EdgeLogger, EffectivePolicy, NormalizedRequest, OutcomeApplication, TierOutcome, TrafficTier } from '@astrasyncai/verification-gateway/edge-core';
|
|
4
4
|
import '@astrasyncai/verification-gateway/edge-config';
|
|
5
5
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/web.ts"],"sourcesContent":["/**\n * Option/environment resolution and dashboard EdgeConfig loading shared by\n * the platform adapters.\n */\n\nimport {\n getEdgeConfig,\n DEFAULT_EDGE_CONFIG,\n type EdgeConfig,\n} from '@astrasyncai/verification-gateway/edge-config';\nimport type { EdgeLogger } from '@astrasyncai/verification-gateway/edge-core';\nimport type { EdgeGatewayOptions } from './types';\n\nexport const DEFAULT_API_BASE_URL = 'https://astrasync.ai/api';\n\nexport interface ResolvedGatewayOptions extends EdgeGatewayOptions {\n apiBaseUrl: string;\n log: EdgeLogger;\n}\n\nexport function defaultLogger(prefix: string): EdgeLogger {\n return (level, message, data) => {\n console[level === 'info' ? 'log' : level](`[${prefix}] ${message}`, data ?? '');\n };\n}\n\n/**\n * Merge explicit options with runtime environment variables (options win)\n * and apply defaults. `env` is the platform's environment map — Cloudflare\n * passes the worker `env` binding object, Vercel passes `process.env`,\n * Fastly Compute has no ambient env map (configure via options).\n */\nexport function resolveOptions(\n options: EdgeGatewayOptions,\n env: Record<string, string | undefined> | undefined,\n logPrefix: string\n): ResolvedGatewayOptions {\n const e = env ?? {};\n return {\n ...options,\n apiBaseUrl: options.apiBaseUrl ?? e.ASTRASYNC_API_BASE_URL ?? DEFAULT_API_BASE_URL,\n counterpartyId: options.counterpartyId ?? e.ASTRASYNC_COUNTERPARTY_ID,\n counterpartyUrl: options.counterpartyUrl ?? e.ASTRASYNC_COUNTERPARTY_URL,\n apiKey: options.apiKey ?? e.ASTRASYNC_API_KEY,\n log: options.log ?? defaultLogger(logPrefix),\n };\n}\n\n/**\n * Missing identity means the gateway cannot evaluate anything — it fails\n * OPEN (all traffic passes untouched) and says why, once per request.\n */\nexport function isConfigured(\n resolved: ResolvedGatewayOptions\n): resolved is ResolvedGatewayOptions & { counterpartyId: string; counterpartyUrl: string } {\n if (resolved.counterpartyId && resolved.counterpartyUrl) return true;\n resolved.log(\n 'error',\n 'counterpartyId/counterpartyUrl are not configured — passing all traffic through. ' +\n 'Set the ASTRASYNC_COUNTERPARTY_ID / ASTRASYNC_COUNTERPARTY_URL environment variables ' +\n 'or pass them as options.'\n );\n return false;\n}\n\n/**\n * Load the effective EdgeConfig. Delegates caching/fallback/degradation to\n * the SDK (60s TTL, stale-while-revalidate, enforce→observe degradation on\n * prolonged staleness) and never throws.\n */\nexport async function loadConfig(\n resolved: ResolvedGatewayOptions & { counterpartyId: string }\n): Promise<EdgeConfig> {\n try {\n return await getEdgeConfig(\n { apiBaseUrl: resolved.apiBaseUrl, apiKey: resolved.apiKey },\n resolved.counterpartyId,\n { ttlMs: resolved.configTtlMs }\n );\n } catch {\n return resolved.fallbackConfig ?? DEFAULT_EDGE_CONFIG;\n }\n}\n","/**\n * Shared web-standard platform I/O: converts a fetch-API `Request` into\n * the edge core's `NormalizedRequest`, and applies the core's abstract\n * outcome application back onto fetch-API primitives.\n */\n\nimport type {\n NormalizedRequest,\n OutcomeApplication,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport type { GatewayPass } from './types';\n\n/** Default request-body read cap for commerce-protocol detection. */\nexport const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;\n\nexport interface WebNormalizeOptions {\n /** Body read cap in bytes. Default {@link DEFAULT_MAX_BODY_BYTES}. */\n maxBodyBytes?: number;\n /** Client IP as the platform reports it. */\n clientIp?: string;\n /** Connection-layer metadata the platform collected (country, asn, …). */\n connection?: Record<string, string>;\n}\n\nasync function readBodyCapped(\n request: Request,\n cap: number\n): Promise<{ text?: string; truncated: boolean }> {\n if (request.method === 'GET' || request.method === 'HEAD') return { truncated: false };\n const body = request.clone().body;\n if (!body) return { truncated: false };\n\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n let truncated = false;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (total + value.byteLength > cap) {\n chunks.push(value.subarray(0, cap - total));\n total = cap;\n truncated = true;\n // Fire-and-forget: cancelling one branch of a cloned (tee'd) body can\n // only settle once the other branch is consumed — never await it.\n void reader.cancel().catch(() => {});\n break;\n }\n chunks.push(value);\n total += value.byteLength;\n }\n if (total === 0 && !truncated) return { truncated: false };\n\n const joined = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n joined.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return { text: new TextDecoder().decode(joined), truncated };\n}\n\n/**\n * Normalize a fetch-API `Request` into the edge core's request shape.\n * Header names are already lowercased (and multi-valued headers joined\n * with `\", \"`) by the `Headers` class. The body is read from a CLONE — the\n * original request stays forwardable to the origin — and capped at\n * `maxBodyBytes` (detection sees a flagged, truncated prefix beyond it).\n */\nexport async function normalizeWebRequest(\n request: Request,\n options: WebNormalizeOptions = {}\n): Promise<NormalizedRequest> {\n const url = new URL(request.url);\n const headers: Record<string, string> = {};\n request.headers.forEach((value, name) => {\n headers[name] = value;\n });\n const body = await readBodyCapped(request, options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);\n const connection = options.connection ?? {};\n return {\n method: request.method,\n uri: url.pathname,\n querystring: url.search.startsWith('?') ? url.search.slice(1) : url.search,\n headers,\n ...(body.text !== undefined && { bodyText: body.text }),\n ...(body.truncated && { bodyTruncated: true }),\n ...(options.clientIp && { clientIp: options.clientIp }),\n ...(Object.keys(connection).length > 0 && { connection }),\n };\n}\n\n/**\n * Apply a pass application to a header set: strip every header matching\n * one of the spoofable prefixes, then set the injected trust headers.\n * Returns a NEW `Headers` instance; the input is not mutated.\n */\nexport function applyGatewayPass(headers: Headers, pass: GatewayPass): Headers {\n const out = new Headers(headers);\n for (const prefix of pass.stripHeaderPrefixes) {\n const p = prefix.toLowerCase();\n for (const name of [...out.keys()]) {\n if (name.startsWith(p)) out.delete(name);\n }\n }\n for (const [name, value] of Object.entries(pass.injectHeaders)) {\n out.set(name, value);\n }\n return out;\n}\n\n/**\n * Build the origin-bound request for a pass application: the same request\n * with spoofable headers stripped and trust headers injected.\n */\nexport function buildPassRequest(request: Request, pass: GatewayPass): Request {\n if (pass.stripHeaderPrefixes.length === 0 && Object.keys(pass.injectHeaders).length === 0) {\n return request;\n }\n return new Request(request, { headers: applyGatewayPass(request.headers, pass) });\n}\n\n/** Build the short-circuit JSON response for a respond application. */\nexport function toJsonResponse(\n application: Extract<OutcomeApplication, { action: 'respond' }>\n): Response {\n return new Response(JSON.stringify(application.bodyJson), {\n status: application.status,\n headers: { 'content-type': 'application/json', ...application.headers },\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/web.ts"],"sourcesContent":["/**\n * Option/environment resolution and dashboard EdgeConfig loading shared by\n * the platform adapters.\n */\n\nimport {\n getEdgeConfig,\n DEFAULT_EDGE_CONFIG,\n type EdgeConfig,\n} from '@astrasyncai/verification-gateway/edge-config';\nimport type { EdgeLogger } from '@astrasyncai/verification-gateway/edge-core';\nimport type { EdgeGatewayOptions } from './types';\n\nexport const DEFAULT_API_BASE_URL = 'https://astrasync.ai/api';\n\nexport interface ResolvedGatewayOptions extends EdgeGatewayOptions {\n apiBaseUrl: string;\n log: EdgeLogger;\n}\n\nexport function defaultLogger(prefix: string): EdgeLogger {\n return (level, message, data) => {\n console[level === 'info' ? 'log' : level](`[${prefix}] ${message}`, data ?? '');\n };\n}\n\n/**\n * Merge explicit options with runtime environment variables (options win)\n * and apply defaults. `env` is the platform's environment map — Cloudflare\n * passes the worker `env` binding object, Vercel passes `process.env`,\n * Fastly Compute has no ambient env map (configure via options).\n */\nexport function resolveOptions(\n options: EdgeGatewayOptions,\n env: Record<string, string | undefined> | undefined,\n logPrefix: string\n): ResolvedGatewayOptions {\n const e = env ?? {};\n return {\n ...options,\n apiBaseUrl: options.apiBaseUrl ?? e.ASTRASYNC_API_BASE_URL ?? DEFAULT_API_BASE_URL,\n publicBaseUrl: options.publicBaseUrl ?? e.ASTRASYNC_PUBLIC_BASE_URL,\n counterpartyId: options.counterpartyId ?? e.ASTRASYNC_COUNTERPARTY_ID,\n counterpartyUrl: options.counterpartyUrl ?? e.ASTRASYNC_COUNTERPARTY_URL,\n apiKey: options.apiKey ?? e.ASTRASYNC_API_KEY,\n log: options.log ?? defaultLogger(logPrefix),\n };\n}\n\n/**\n * Missing identity means the gateway cannot evaluate anything — it fails\n * OPEN (all traffic passes untouched) and says why, once per request.\n */\nexport function isConfigured(\n resolved: ResolvedGatewayOptions\n): resolved is ResolvedGatewayOptions & { counterpartyId: string; counterpartyUrl: string } {\n if (resolved.counterpartyId && resolved.counterpartyUrl) return true;\n resolved.log(\n 'error',\n 'counterpartyId/counterpartyUrl are not configured — passing all traffic through. ' +\n 'Set the ASTRASYNC_COUNTERPARTY_ID / ASTRASYNC_COUNTERPARTY_URL environment variables ' +\n 'or pass them as options.'\n );\n return false;\n}\n\n/**\n * Load the effective EdgeConfig. Delegates caching/fallback/degradation to\n * the SDK (60s TTL, stale-while-revalidate, enforce→observe degradation on\n * prolonged staleness) and never throws.\n */\nexport async function loadConfig(\n resolved: ResolvedGatewayOptions & { counterpartyId: string }\n): Promise<EdgeConfig> {\n try {\n return await getEdgeConfig(\n { apiBaseUrl: resolved.apiBaseUrl, apiKey: resolved.apiKey },\n resolved.counterpartyId,\n { ttlMs: resolved.configTtlMs }\n );\n } catch {\n return resolved.fallbackConfig ?? DEFAULT_EDGE_CONFIG;\n }\n}\n","/**\n * Shared web-standard platform I/O: converts a fetch-API `Request` into\n * the edge core's `NormalizedRequest`, and applies the core's abstract\n * outcome application back onto fetch-API primitives.\n */\n\nimport type {\n NormalizedRequest,\n OutcomeApplication,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport type { GatewayPass } from './types';\n\n/** Default request-body read cap for commerce-protocol detection. */\nexport const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;\n\nexport interface WebNormalizeOptions {\n /** Body read cap in bytes. Default {@link DEFAULT_MAX_BODY_BYTES}. */\n maxBodyBytes?: number;\n /** Client IP as the platform reports it. */\n clientIp?: string;\n /** Connection-layer metadata the platform collected (country, asn, …). */\n connection?: Record<string, string>;\n}\n\nasync function readBodyCapped(\n request: Request,\n cap: number\n): Promise<{ text?: string; truncated: boolean }> {\n if (request.method === 'GET' || request.method === 'HEAD') return { truncated: false };\n const body = request.clone().body;\n if (!body) return { truncated: false };\n\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n let truncated = false;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (total + value.byteLength > cap) {\n chunks.push(value.subarray(0, cap - total));\n total = cap;\n truncated = true;\n // Fire-and-forget: cancelling one branch of a cloned (tee'd) body can\n // only settle once the other branch is consumed — never await it.\n void reader.cancel().catch(() => {});\n break;\n }\n chunks.push(value);\n total += value.byteLength;\n }\n if (total === 0 && !truncated) return { truncated: false };\n\n const joined = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n joined.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return { text: new TextDecoder().decode(joined), truncated };\n}\n\n/**\n * Normalize a fetch-API `Request` into the edge core's request shape.\n * Header names are already lowercased (and multi-valued headers joined\n * with `\", \"`) by the `Headers` class. The body is read from a CLONE — the\n * original request stays forwardable to the origin — and capped at\n * `maxBodyBytes` (detection sees a flagged, truncated prefix beyond it).\n */\nexport async function normalizeWebRequest(\n request: Request,\n options: WebNormalizeOptions = {}\n): Promise<NormalizedRequest> {\n const url = new URL(request.url);\n const headers: Record<string, string> = {};\n request.headers.forEach((value, name) => {\n headers[name] = value;\n });\n const body = await readBodyCapped(request, options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);\n const connection = options.connection ?? {};\n return {\n method: request.method,\n uri: url.pathname,\n querystring: url.search.startsWith('?') ? url.search.slice(1) : url.search,\n headers,\n ...(body.text !== undefined && { bodyText: body.text }),\n ...(body.truncated && { bodyTruncated: true }),\n ...(options.clientIp && { clientIp: options.clientIp }),\n ...(Object.keys(connection).length > 0 && { connection }),\n };\n}\n\n/**\n * Apply a pass application to a header set: strip every header matching\n * one of the spoofable prefixes, then set the injected trust headers.\n * Returns a NEW `Headers` instance; the input is not mutated.\n */\nexport function applyGatewayPass(headers: Headers, pass: GatewayPass): Headers {\n const out = new Headers(headers);\n for (const prefix of pass.stripHeaderPrefixes) {\n const p = prefix.toLowerCase();\n for (const name of [...out.keys()]) {\n if (name.startsWith(p)) out.delete(name);\n }\n }\n for (const [name, value] of Object.entries(pass.injectHeaders)) {\n out.set(name, value);\n }\n return out;\n}\n\n/**\n * Build the origin-bound request for a pass application: the same request\n * with spoofable headers stripped and trust headers injected.\n */\nexport function buildPassRequest(request: Request, pass: GatewayPass): Request {\n if (pass.stripHeaderPrefixes.length === 0 && Object.keys(pass.injectHeaders).length === 0) {\n return request;\n }\n return new Request(request, { headers: applyGatewayPass(request.headers, pass) });\n}\n\n/** Build the short-circuit JSON response for a respond application. */\nexport function toJsonResponse(\n application: Extract<OutcomeApplication, { action: 'respond' }>\n): Response {\n return new Response(JSON.stringify(application.bodyJson), {\n status: application.status,\n headers: { 'content-type': 'application/json', ...application.headers },\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -27,6 +27,13 @@ type GatewayPass = Extract<OutcomeApplication, {
|
|
|
27
27
|
interface EdgeGatewayOptions {
|
|
28
28
|
/** AstraSync API base. Default `https://astrasync.ai/api`. */
|
|
29
29
|
apiBaseUrl?: string;
|
|
30
|
+
/**
|
|
31
|
+
* 1.4.0: public host for AGENT-VISIBLE deny-body guidance URLs. Set when
|
|
32
|
+
* `apiBaseUrl` is a direct/origin host agents must never be steered to;
|
|
33
|
+
* unset → guidance derives from `apiBaseUrl` (prior behavior). Env:
|
|
34
|
+
* `ASTRASYNC_PUBLIC_BASE_URL`.
|
|
35
|
+
*/
|
|
36
|
+
publicBaseUrl?: string;
|
|
30
37
|
/** The protected property's ASTRAE endpoint id. */
|
|
31
38
|
counterpartyId?: string;
|
|
32
39
|
/** Canonical public URL of the protected property, e.g. `https://example.shop`. */
|
package/dist/vercel.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { G as GatewayPass, E as EdgeGatewayOptions } from './types-
|
|
2
|
-
export { a as applyGatewayPass } from './web-
|
|
1
|
+
import { G as GatewayPass, E as EdgeGatewayOptions } from './types-BhaNxFeN.js';
|
|
2
|
+
export { a as applyGatewayPass } from './web-BR_TKwJs.js';
|
|
3
3
|
import '@astrasyncai/verification-gateway/edge-config';
|
|
4
4
|
import '@astrasyncai/verification-gateway/edge-core';
|
|
5
5
|
|
package/dist/vercel.js
CHANGED
|
@@ -17,6 +17,7 @@ function resolveOptions(options, env, logPrefix) {
|
|
|
17
17
|
return {
|
|
18
18
|
...options,
|
|
19
19
|
apiBaseUrl: options.apiBaseUrl ?? e.ASTRASYNC_API_BASE_URL ?? DEFAULT_API_BASE_URL,
|
|
20
|
+
publicBaseUrl: options.publicBaseUrl ?? e.ASTRASYNC_PUBLIC_BASE_URL,
|
|
20
21
|
counterpartyId: options.counterpartyId ?? e.ASTRASYNC_COUNTERPARTY_ID,
|
|
21
22
|
counterpartyUrl: options.counterpartyUrl ?? e.ASTRASYNC_COUNTERPARTY_URL,
|
|
22
23
|
apiKey: options.apiKey ?? e.ASTRASYNC_API_KEY,
|
|
@@ -155,6 +156,7 @@ function createGatewayMiddleware(options = {}) {
|
|
|
155
156
|
const { application } = await evaluateEdgeRequest(
|
|
156
157
|
{
|
|
157
158
|
apiBaseUrl: resolved.apiBaseUrl,
|
|
159
|
+
publicBaseUrl: resolved.publicBaseUrl,
|
|
158
160
|
counterpartyId: resolved.counterpartyId,
|
|
159
161
|
counterpartyUrl: resolved.counterpartyUrl,
|
|
160
162
|
budgets: resolved.budgets,
|
package/dist/vercel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vercel.ts","../src/config.ts","../src/web.ts"],"sourcesContent":["/**\n * AstraSync Trusted Agent Gateway — Vercel Edge Middleware adapter.\n *\n * This package deliberately does NOT import `next`, so the middleware\n * cannot return `NextResponse.next()` itself. It returns either a\n * `Response` (enforce-mode short-circuit — return it as-is) or a\n * {@link GatewayPass} describing the header changes to apply before\n * continuing. Wire it up with a two-line wrapper:\n *\n * ```ts\n * // middleware.ts\n * import { NextResponse, type NextRequest } from 'next/server';\n * import { createGatewayMiddleware, applyGatewayPass } from '@astrasyncai/adapter-edge/vercel';\n *\n * const gateway = createGatewayMiddleware();\n *\n * export async function middleware(request: NextRequest) {\n * const result = await gateway(request);\n * if (result instanceof Response) return result;\n * return NextResponse.next({ request: { headers: applyGatewayPass(request.headers, result) } });\n * }\n * ```\n *\n * Alternatively pass a `next` factory in the options to get back a plain\n * `Response | undefined` middleware.\n *\n * Configuration comes from `process.env` (`ASTRASYNC_API_KEY`,\n * `ASTRASYNC_COUNTERPARTY_ID`, `ASTRASYNC_COUNTERPARTY_URL`, optional\n * `ASTRASYNC_API_BASE_URL`) or explicit options, which win.\n */\n\nimport { evaluateEdgeRequest } from '@astrasyncai/verification-gateway/edge-core';\nimport { resolveOptions, isConfigured, loadConfig } from './config';\nimport type { EdgeGatewayOptions, GatewayPass } from './types';\nimport { normalizeWebRequest, applyGatewayPass, toJsonResponse } from './web';\n\nexport { applyGatewayPass };\nexport type { EdgeGatewayOptions, GatewayPass } from './types';\n\nexport interface VercelGatewayOptions extends EdgeGatewayOptions {\n /**\n * Optional continue-factory. When provided, pass outcomes return\n * `next(pass, request)` instead of the raw {@link GatewayPass} — e.g.\n * `(pass, req) => NextResponse.next({ request: { headers: applyGatewayPass(req.headers, pass) } })`.\n * Kept injectable so this package stays dependency-free of `next`.\n */\n next?: (pass: GatewayPass, request: Request) => Response | undefined;\n}\n\n/** Structural subset of Vercel's middleware fetch event (for waitUntil). */\nexport interface VercelFetchEvent {\n waitUntil?(promise: Promise<unknown>): void;\n}\n\nexport type VercelGatewayMiddleware = (\n request: Request,\n event?: VercelFetchEvent\n) => Promise<Response | GatewayPass | undefined>;\n\nconst UNTOUCHED_PASS: GatewayPass = Object.freeze({\n action: 'pass' as const,\n injectHeaders: {},\n stripHeaderPrefixes: [],\n});\n\n/**\n * Connection-layer signals Vercel exposes as request headers. Absent\n * values are simply omitted.\n */\nexport function collectVercelConnection(request: Request): Record<string, string> {\n const out: Record<string, string> = {};\n const put = (key: string, headerName: string) => {\n const v = request.headers.get(headerName);\n if (v) out[key] = v;\n };\n put('ip', 'x-real-ip');\n put('country', 'x-vercel-ip-country');\n put('region', 'x-vercel-ip-country-region');\n put('city', 'x-vercel-ip-city');\n put('timeZone', 'x-vercel-ip-timezone');\n put('continent', 'x-vercel-ip-continent');\n // Present only on deployments with Vercel BotID enabled — harmless elsewhere.\n put('ja4', 'x-vercel-ja4-digest');\n return out;\n}\n\n/**\n * Create the middleware. NEVER blocks traffic outside enforce mode and\n * fails open (an untouched pass) on any internal error. Telemetry beacons\n * are scheduled through `event.waitUntil` when the runtime provides one.\n */\nexport function createGatewayMiddleware(\n options: VercelGatewayOptions = {}\n): VercelGatewayMiddleware {\n return async function middleware(request, event) {\n const resolved = resolveOptions(\n options,\n typeof process !== 'undefined' ? process.env : undefined,\n 'astrasync-edge/vercel'\n );\n const pass = (result: GatewayPass): Response | GatewayPass | undefined =>\n options.next ? options.next(result, request) : result;\n try {\n if (!isConfigured(resolved)) return pass(UNTOUCHED_PASS);\n\n const config = await loadConfig(resolved);\n const normalized = await normalizeWebRequest(request, {\n maxBodyBytes: resolved.maxBodyBytes,\n clientIp: request.headers.get('x-real-ip') ?? undefined,\n connection: collectVercelConnection(request),\n });\n\n const { application } = await evaluateEdgeRequest(\n {\n apiBaseUrl: resolved.apiBaseUrl,\n counterpartyId: resolved.counterpartyId,\n counterpartyUrl: resolved.counterpartyUrl,\n budgets: resolved.budgets,\n random: resolved.random,\n log: resolved.log,\n ...(event?.waitUntil && { waitUntil: (p: Promise<unknown>) => event.waitUntil!(p) }),\n },\n normalized,\n config,\n resolved.apiKey\n );\n\n if (application.action === 'respond') return toJsonResponse(application);\n return pass(application);\n } catch (err) {\n // Fail-open, unconditionally: the gateway must never take the site down.\n resolved.log('error', 'gateway error — passing request through', {\n error: err instanceof Error ? err.message : String(err),\n });\n return pass(UNTOUCHED_PASS);\n }\n };\n}\n","/**\n * Option/environment resolution and dashboard EdgeConfig loading shared by\n * the platform adapters.\n */\n\nimport {\n getEdgeConfig,\n DEFAULT_EDGE_CONFIG,\n type EdgeConfig,\n} from '@astrasyncai/verification-gateway/edge-config';\nimport type { EdgeLogger } from '@astrasyncai/verification-gateway/edge-core';\nimport type { EdgeGatewayOptions } from './types';\n\nexport const DEFAULT_API_BASE_URL = 'https://astrasync.ai/api';\n\nexport interface ResolvedGatewayOptions extends EdgeGatewayOptions {\n apiBaseUrl: string;\n log: EdgeLogger;\n}\n\nexport function defaultLogger(prefix: string): EdgeLogger {\n return (level, message, data) => {\n console[level === 'info' ? 'log' : level](`[${prefix}] ${message}`, data ?? '');\n };\n}\n\n/**\n * Merge explicit options with runtime environment variables (options win)\n * and apply defaults. `env` is the platform's environment map — Cloudflare\n * passes the worker `env` binding object, Vercel passes `process.env`,\n * Fastly Compute has no ambient env map (configure via options).\n */\nexport function resolveOptions(\n options: EdgeGatewayOptions,\n env: Record<string, string | undefined> | undefined,\n logPrefix: string\n): ResolvedGatewayOptions {\n const e = env ?? {};\n return {\n ...options,\n apiBaseUrl: options.apiBaseUrl ?? e.ASTRASYNC_API_BASE_URL ?? DEFAULT_API_BASE_URL,\n counterpartyId: options.counterpartyId ?? e.ASTRASYNC_COUNTERPARTY_ID,\n counterpartyUrl: options.counterpartyUrl ?? e.ASTRASYNC_COUNTERPARTY_URL,\n apiKey: options.apiKey ?? e.ASTRASYNC_API_KEY,\n log: options.log ?? defaultLogger(logPrefix),\n };\n}\n\n/**\n * Missing identity means the gateway cannot evaluate anything — it fails\n * OPEN (all traffic passes untouched) and says why, once per request.\n */\nexport function isConfigured(\n resolved: ResolvedGatewayOptions\n): resolved is ResolvedGatewayOptions & { counterpartyId: string; counterpartyUrl: string } {\n if (resolved.counterpartyId && resolved.counterpartyUrl) return true;\n resolved.log(\n 'error',\n 'counterpartyId/counterpartyUrl are not configured — passing all traffic through. ' +\n 'Set the ASTRASYNC_COUNTERPARTY_ID / ASTRASYNC_COUNTERPARTY_URL environment variables ' +\n 'or pass them as options.'\n );\n return false;\n}\n\n/**\n * Load the effective EdgeConfig. Delegates caching/fallback/degradation to\n * the SDK (60s TTL, stale-while-revalidate, enforce→observe degradation on\n * prolonged staleness) and never throws.\n */\nexport async function loadConfig(\n resolved: ResolvedGatewayOptions & { counterpartyId: string }\n): Promise<EdgeConfig> {\n try {\n return await getEdgeConfig(\n { apiBaseUrl: resolved.apiBaseUrl, apiKey: resolved.apiKey },\n resolved.counterpartyId,\n { ttlMs: resolved.configTtlMs }\n );\n } catch {\n return resolved.fallbackConfig ?? DEFAULT_EDGE_CONFIG;\n }\n}\n","/**\n * Shared web-standard platform I/O: converts a fetch-API `Request` into\n * the edge core's `NormalizedRequest`, and applies the core's abstract\n * outcome application back onto fetch-API primitives.\n */\n\nimport type {\n NormalizedRequest,\n OutcomeApplication,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport type { GatewayPass } from './types';\n\n/** Default request-body read cap for commerce-protocol detection. */\nexport const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;\n\nexport interface WebNormalizeOptions {\n /** Body read cap in bytes. Default {@link DEFAULT_MAX_BODY_BYTES}. */\n maxBodyBytes?: number;\n /** Client IP as the platform reports it. */\n clientIp?: string;\n /** Connection-layer metadata the platform collected (country, asn, …). */\n connection?: Record<string, string>;\n}\n\nasync function readBodyCapped(\n request: Request,\n cap: number\n): Promise<{ text?: string; truncated: boolean }> {\n if (request.method === 'GET' || request.method === 'HEAD') return { truncated: false };\n const body = request.clone().body;\n if (!body) return { truncated: false };\n\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n let truncated = false;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (total + value.byteLength > cap) {\n chunks.push(value.subarray(0, cap - total));\n total = cap;\n truncated = true;\n // Fire-and-forget: cancelling one branch of a cloned (tee'd) body can\n // only settle once the other branch is consumed — never await it.\n void reader.cancel().catch(() => {});\n break;\n }\n chunks.push(value);\n total += value.byteLength;\n }\n if (total === 0 && !truncated) return { truncated: false };\n\n const joined = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n joined.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return { text: new TextDecoder().decode(joined), truncated };\n}\n\n/**\n * Normalize a fetch-API `Request` into the edge core's request shape.\n * Header names are already lowercased (and multi-valued headers joined\n * with `\", \"`) by the `Headers` class. The body is read from a CLONE — the\n * original request stays forwardable to the origin — and capped at\n * `maxBodyBytes` (detection sees a flagged, truncated prefix beyond it).\n */\nexport async function normalizeWebRequest(\n request: Request,\n options: WebNormalizeOptions = {}\n): Promise<NormalizedRequest> {\n const url = new URL(request.url);\n const headers: Record<string, string> = {};\n request.headers.forEach((value, name) => {\n headers[name] = value;\n });\n const body = await readBodyCapped(request, options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);\n const connection = options.connection ?? {};\n return {\n method: request.method,\n uri: url.pathname,\n querystring: url.search.startsWith('?') ? url.search.slice(1) : url.search,\n headers,\n ...(body.text !== undefined && { bodyText: body.text }),\n ...(body.truncated && { bodyTruncated: true }),\n ...(options.clientIp && { clientIp: options.clientIp }),\n ...(Object.keys(connection).length > 0 && { connection }),\n };\n}\n\n/**\n * Apply a pass application to a header set: strip every header matching\n * one of the spoofable prefixes, then set the injected trust headers.\n * Returns a NEW `Headers` instance; the input is not mutated.\n */\nexport function applyGatewayPass(headers: Headers, pass: GatewayPass): Headers {\n const out = new Headers(headers);\n for (const prefix of pass.stripHeaderPrefixes) {\n const p = prefix.toLowerCase();\n for (const name of [...out.keys()]) {\n if (name.startsWith(p)) out.delete(name);\n }\n }\n for (const [name, value] of Object.entries(pass.injectHeaders)) {\n out.set(name, value);\n }\n return out;\n}\n\n/**\n * Build the origin-bound request for a pass application: the same request\n * with spoofable headers stripped and trust headers injected.\n */\nexport function buildPassRequest(request: Request, pass: GatewayPass): Request {\n if (pass.stripHeaderPrefixes.length === 0 && Object.keys(pass.injectHeaders).length === 0) {\n return request;\n }\n return new Request(request, { headers: applyGatewayPass(request.headers, pass) });\n}\n\n/** Build the short-circuit JSON response for a respond application. */\nexport function toJsonResponse(\n application: Extract<OutcomeApplication, { action: 'respond' }>\n): Response {\n return new Response(JSON.stringify(application.bodyJson), {\n status: application.status,\n headers: { 'content-type': 'application/json', ...application.headers },\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/vercel.ts","../src/config.ts","../src/web.ts"],"sourcesContent":["/**\n * AstraSync Trusted Agent Gateway — Vercel Edge Middleware adapter.\n *\n * This package deliberately does NOT import `next`, so the middleware\n * cannot return `NextResponse.next()` itself. It returns either a\n * `Response` (enforce-mode short-circuit — return it as-is) or a\n * {@link GatewayPass} describing the header changes to apply before\n * continuing. Wire it up with a two-line wrapper:\n *\n * ```ts\n * // middleware.ts\n * import { NextResponse, type NextRequest } from 'next/server';\n * import { createGatewayMiddleware, applyGatewayPass } from '@astrasyncai/adapter-edge/vercel';\n *\n * const gateway = createGatewayMiddleware();\n *\n * export async function middleware(request: NextRequest) {\n * const result = await gateway(request);\n * if (result instanceof Response) return result;\n * return NextResponse.next({ request: { headers: applyGatewayPass(request.headers, result) } });\n * }\n * ```\n *\n * Alternatively pass a `next` factory in the options to get back a plain\n * `Response | undefined` middleware.\n *\n * Configuration comes from `process.env` (`ASTRASYNC_API_KEY`,\n * `ASTRASYNC_COUNTERPARTY_ID`, `ASTRASYNC_COUNTERPARTY_URL`, optional\n * `ASTRASYNC_API_BASE_URL`) or explicit options, which win.\n */\n\nimport { evaluateEdgeRequest } from '@astrasyncai/verification-gateway/edge-core';\nimport { resolveOptions, isConfigured, loadConfig } from './config';\nimport type { EdgeGatewayOptions, GatewayPass } from './types';\nimport { normalizeWebRequest, applyGatewayPass, toJsonResponse } from './web';\n\nexport { applyGatewayPass };\nexport type { EdgeGatewayOptions, GatewayPass } from './types';\n\nexport interface VercelGatewayOptions extends EdgeGatewayOptions {\n /**\n * Optional continue-factory. When provided, pass outcomes return\n * `next(pass, request)` instead of the raw {@link GatewayPass} — e.g.\n * `(pass, req) => NextResponse.next({ request: { headers: applyGatewayPass(req.headers, pass) } })`.\n * Kept injectable so this package stays dependency-free of `next`.\n */\n next?: (pass: GatewayPass, request: Request) => Response | undefined;\n}\n\n/** Structural subset of Vercel's middleware fetch event (for waitUntil). */\nexport interface VercelFetchEvent {\n waitUntil?(promise: Promise<unknown>): void;\n}\n\nexport type VercelGatewayMiddleware = (\n request: Request,\n event?: VercelFetchEvent\n) => Promise<Response | GatewayPass | undefined>;\n\nconst UNTOUCHED_PASS: GatewayPass = Object.freeze({\n action: 'pass' as const,\n injectHeaders: {},\n stripHeaderPrefixes: [],\n});\n\n/**\n * Connection-layer signals Vercel exposes as request headers. Absent\n * values are simply omitted.\n */\nexport function collectVercelConnection(request: Request): Record<string, string> {\n const out: Record<string, string> = {};\n const put = (key: string, headerName: string) => {\n const v = request.headers.get(headerName);\n if (v) out[key] = v;\n };\n put('ip', 'x-real-ip');\n put('country', 'x-vercel-ip-country');\n put('region', 'x-vercel-ip-country-region');\n put('city', 'x-vercel-ip-city');\n put('timeZone', 'x-vercel-ip-timezone');\n put('continent', 'x-vercel-ip-continent');\n // Present only on deployments with Vercel BotID enabled — harmless elsewhere.\n put('ja4', 'x-vercel-ja4-digest');\n return out;\n}\n\n/**\n * Create the middleware. NEVER blocks traffic outside enforce mode and\n * fails open (an untouched pass) on any internal error. Telemetry beacons\n * are scheduled through `event.waitUntil` when the runtime provides one.\n */\nexport function createGatewayMiddleware(\n options: VercelGatewayOptions = {}\n): VercelGatewayMiddleware {\n return async function middleware(request, event) {\n const resolved = resolveOptions(\n options,\n typeof process !== 'undefined' ? process.env : undefined,\n 'astrasync-edge/vercel'\n );\n const pass = (result: GatewayPass): Response | GatewayPass | undefined =>\n options.next ? options.next(result, request) : result;\n try {\n if (!isConfigured(resolved)) return pass(UNTOUCHED_PASS);\n\n const config = await loadConfig(resolved);\n const normalized = await normalizeWebRequest(request, {\n maxBodyBytes: resolved.maxBodyBytes,\n clientIp: request.headers.get('x-real-ip') ?? undefined,\n connection: collectVercelConnection(request),\n });\n\n const { application } = await evaluateEdgeRequest(\n {\n apiBaseUrl: resolved.apiBaseUrl,\n publicBaseUrl: resolved.publicBaseUrl,\n counterpartyId: resolved.counterpartyId,\n counterpartyUrl: resolved.counterpartyUrl,\n budgets: resolved.budgets,\n random: resolved.random,\n log: resolved.log,\n ...(event?.waitUntil && { waitUntil: (p: Promise<unknown>) => event.waitUntil!(p) }),\n },\n normalized,\n config,\n resolved.apiKey\n );\n\n if (application.action === 'respond') return toJsonResponse(application);\n return pass(application);\n } catch (err) {\n // Fail-open, unconditionally: the gateway must never take the site down.\n resolved.log('error', 'gateway error — passing request through', {\n error: err instanceof Error ? err.message : String(err),\n });\n return pass(UNTOUCHED_PASS);\n }\n };\n}\n","/**\n * Option/environment resolution and dashboard EdgeConfig loading shared by\n * the platform adapters.\n */\n\nimport {\n getEdgeConfig,\n DEFAULT_EDGE_CONFIG,\n type EdgeConfig,\n} from '@astrasyncai/verification-gateway/edge-config';\nimport type { EdgeLogger } from '@astrasyncai/verification-gateway/edge-core';\nimport type { EdgeGatewayOptions } from './types';\n\nexport const DEFAULT_API_BASE_URL = 'https://astrasync.ai/api';\n\nexport interface ResolvedGatewayOptions extends EdgeGatewayOptions {\n apiBaseUrl: string;\n log: EdgeLogger;\n}\n\nexport function defaultLogger(prefix: string): EdgeLogger {\n return (level, message, data) => {\n console[level === 'info' ? 'log' : level](`[${prefix}] ${message}`, data ?? '');\n };\n}\n\n/**\n * Merge explicit options with runtime environment variables (options win)\n * and apply defaults. `env` is the platform's environment map — Cloudflare\n * passes the worker `env` binding object, Vercel passes `process.env`,\n * Fastly Compute has no ambient env map (configure via options).\n */\nexport function resolveOptions(\n options: EdgeGatewayOptions,\n env: Record<string, string | undefined> | undefined,\n logPrefix: string\n): ResolvedGatewayOptions {\n const e = env ?? {};\n return {\n ...options,\n apiBaseUrl: options.apiBaseUrl ?? e.ASTRASYNC_API_BASE_URL ?? DEFAULT_API_BASE_URL,\n publicBaseUrl: options.publicBaseUrl ?? e.ASTRASYNC_PUBLIC_BASE_URL,\n counterpartyId: options.counterpartyId ?? e.ASTRASYNC_COUNTERPARTY_ID,\n counterpartyUrl: options.counterpartyUrl ?? e.ASTRASYNC_COUNTERPARTY_URL,\n apiKey: options.apiKey ?? e.ASTRASYNC_API_KEY,\n log: options.log ?? defaultLogger(logPrefix),\n };\n}\n\n/**\n * Missing identity means the gateway cannot evaluate anything — it fails\n * OPEN (all traffic passes untouched) and says why, once per request.\n */\nexport function isConfigured(\n resolved: ResolvedGatewayOptions\n): resolved is ResolvedGatewayOptions & { counterpartyId: string; counterpartyUrl: string } {\n if (resolved.counterpartyId && resolved.counterpartyUrl) return true;\n resolved.log(\n 'error',\n 'counterpartyId/counterpartyUrl are not configured — passing all traffic through. ' +\n 'Set the ASTRASYNC_COUNTERPARTY_ID / ASTRASYNC_COUNTERPARTY_URL environment variables ' +\n 'or pass them as options.'\n );\n return false;\n}\n\n/**\n * Load the effective EdgeConfig. Delegates caching/fallback/degradation to\n * the SDK (60s TTL, stale-while-revalidate, enforce→observe degradation on\n * prolonged staleness) and never throws.\n */\nexport async function loadConfig(\n resolved: ResolvedGatewayOptions & { counterpartyId: string }\n): Promise<EdgeConfig> {\n try {\n return await getEdgeConfig(\n { apiBaseUrl: resolved.apiBaseUrl, apiKey: resolved.apiKey },\n resolved.counterpartyId,\n { ttlMs: resolved.configTtlMs }\n );\n } catch {\n return resolved.fallbackConfig ?? DEFAULT_EDGE_CONFIG;\n }\n}\n","/**\n * Shared web-standard platform I/O: converts a fetch-API `Request` into\n * the edge core's `NormalizedRequest`, and applies the core's abstract\n * outcome application back onto fetch-API primitives.\n */\n\nimport type {\n NormalizedRequest,\n OutcomeApplication,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport type { GatewayPass } from './types';\n\n/** Default request-body read cap for commerce-protocol detection. */\nexport const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;\n\nexport interface WebNormalizeOptions {\n /** Body read cap in bytes. Default {@link DEFAULT_MAX_BODY_BYTES}. */\n maxBodyBytes?: number;\n /** Client IP as the platform reports it. */\n clientIp?: string;\n /** Connection-layer metadata the platform collected (country, asn, …). */\n connection?: Record<string, string>;\n}\n\nasync function readBodyCapped(\n request: Request,\n cap: number\n): Promise<{ text?: string; truncated: boolean }> {\n if (request.method === 'GET' || request.method === 'HEAD') return { truncated: false };\n const body = request.clone().body;\n if (!body) return { truncated: false };\n\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n let truncated = false;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (total + value.byteLength > cap) {\n chunks.push(value.subarray(0, cap - total));\n total = cap;\n truncated = true;\n // Fire-and-forget: cancelling one branch of a cloned (tee'd) body can\n // only settle once the other branch is consumed — never await it.\n void reader.cancel().catch(() => {});\n break;\n }\n chunks.push(value);\n total += value.byteLength;\n }\n if (total === 0 && !truncated) return { truncated: false };\n\n const joined = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n joined.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return { text: new TextDecoder().decode(joined), truncated };\n}\n\n/**\n * Normalize a fetch-API `Request` into the edge core's request shape.\n * Header names are already lowercased (and multi-valued headers joined\n * with `\", \"`) by the `Headers` class. The body is read from a CLONE — the\n * original request stays forwardable to the origin — and capped at\n * `maxBodyBytes` (detection sees a flagged, truncated prefix beyond it).\n */\nexport async function normalizeWebRequest(\n request: Request,\n options: WebNormalizeOptions = {}\n): Promise<NormalizedRequest> {\n const url = new URL(request.url);\n const headers: Record<string, string> = {};\n request.headers.forEach((value, name) => {\n headers[name] = value;\n });\n const body = await readBodyCapped(request, options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);\n const connection = options.connection ?? {};\n return {\n method: request.method,\n uri: url.pathname,\n querystring: url.search.startsWith('?') ? url.search.slice(1) : url.search,\n headers,\n ...(body.text !== undefined && { bodyText: body.text }),\n ...(body.truncated && { bodyTruncated: true }),\n ...(options.clientIp && { clientIp: options.clientIp }),\n ...(Object.keys(connection).length > 0 && { connection }),\n };\n}\n\n/**\n * Apply a pass application to a header set: strip every header matching\n * one of the spoofable prefixes, then set the injected trust headers.\n * Returns a NEW `Headers` instance; the input is not mutated.\n */\nexport function applyGatewayPass(headers: Headers, pass: GatewayPass): Headers {\n const out = new Headers(headers);\n for (const prefix of pass.stripHeaderPrefixes) {\n const p = prefix.toLowerCase();\n for (const name of [...out.keys()]) {\n if (name.startsWith(p)) out.delete(name);\n }\n }\n for (const [name, value] of Object.entries(pass.injectHeaders)) {\n out.set(name, value);\n }\n return out;\n}\n\n/**\n * Build the origin-bound request for a pass application: the same request\n * with spoofable headers stripped and trust headers injected.\n */\nexport function buildPassRequest(request: Request, pass: GatewayPass): Request {\n if (pass.stripHeaderPrefixes.length === 0 && Object.keys(pass.injectHeaders).length === 0) {\n return request;\n }\n return new Request(request, { headers: applyGatewayPass(request.headers, pass) });\n}\n\n/** Build the short-circuit JSON response for a respond application. */\nexport function toJsonResponse(\n application: Extract<OutcomeApplication, { action: 'respond' }>\n): Response {\n return new Response(JSON.stringify(application.bodyJson), {\n status: application.status,\n headers: { 'content-type': 'application/json', ...application.headers },\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NormalizedRequest, OutcomeApplication } from '@astrasyncai/verification-gateway/edge-core';
|
|
2
|
-
import { G as GatewayPass } from './types-
|
|
2
|
+
import { G as GatewayPass } from './types-BhaNxFeN.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Shared web-standard platform I/O: converts a fetch-API `Request` into
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrasyncai/adapter-edge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "AstraSync Trusted Agent Gateway — edge adapters for Vercel Edge, Cloudflare Workers, and Fastly Compute",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"directory": "packages/adapter-edge"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@astrasyncai/verification-gateway": "^5.
|
|
57
|
+
"@astrasyncai/verification-gateway": "^5.2.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^20.10.0",
|