@pandait.tech/payment-nuvei 0.1.1 → 0.2.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/README.md +29 -0
- package/dist/adapters/index.cjs +54 -0
- package/dist/adapters/index.cjs.map +1 -0
- package/dist/adapters/index.d.cts +35 -0
- package/dist/adapters/index.d.ts +35 -0
- package/dist/adapters/index.js +51 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/handlers/index.cjs +188 -105
- package/dist/handlers/index.cjs.map +1 -1
- package/dist/handlers/index.d.cts +29 -117
- package/dist/handlers/index.d.ts +29 -117
- package/dist/handlers/index.js +188 -106
- package/dist/handlers/index.js.map +1 -1
- package/package.json +17 -9
package/README.md
CHANGED
|
@@ -110,6 +110,35 @@ export const POST = createChargeHandler({
|
|
|
110
110
|
|
|
111
111
|
Apply the same pattern to the remaining handlers — each one is documented inline with its own `XHandlerDeps` interface.
|
|
112
112
|
|
|
113
|
+
## Hosting & deployment (3DS callback + Nuvei egress)
|
|
114
|
+
|
|
115
|
+
This package is **hosting-agnostic by design** — the payment logic doesn't care where it runs. But two parts of the Nuvei flow may need to run *outside your app's request edge* depending on **where you host**, because of how the host's infrastructure (not this package) handles certain traffic:
|
|
116
|
+
|
|
117
|
+
### 1. The 3DS callback (`termUrl`)
|
|
118
|
+
|
|
119
|
+
After a 3DS challenge, the **bank's ACS makes an external, cross-origin `POST`** to the `termUrl`. The `termUrl` is built by `createChargeHandler` as:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
${cloudFunctionsBaseUrl}/threeDSCallback?orderId=<id>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The callback must: read the `cres` from the POST (form-urlencoded **or** JSON, casing varies by issuer), decode `transStatus` from the base64url CRES payload, persist `threeDSCres` + `threeDSTransStatus` on the order (only while `status === "3ds-pending"`), and return HTML that `postMessage`s `{ type: "3DS_COMPLETE", orderId, transStatus }` to the parent checkout window (no redirect).
|
|
126
|
+
|
|
127
|
+
| Your host | How to expose the callback |
|
|
128
|
+
|---|---|
|
|
129
|
+
| **Firebase App Hosting / Cloud Run** | App Hosting **returns 403** to the bank's external POST before your code runs (infra-level block). You **must** deploy the callback as a standalone **Cloud Function** with permissive ingress (`cors: true`) and point `cloudFunctionsBaseUrl` at `https://<region>-<project>.cloudfunctions.net`. The package's `create3dsCallbackHandler` (a Next.js route) is **not** usable as `termUrl` here. |
|
|
130
|
+
| **Vercel / Netlify / plain Node / properly-configured Cloud Run** | The Next.js route from `create3dsCallbackHandler` accepts the external POST fine. Mount it at `/api/payment/3ds-callback` and set `cloudFunctionsBaseUrl` to your own origin + that path. |
|
|
131
|
+
|
|
132
|
+
### 2. Nuvei API egress
|
|
133
|
+
|
|
134
|
+
Some hosts can't reach Nuvei's API directly. **Cloud Run (App Hosting) returns 500** calling `ccapi.paymentez.com` (egress/TLS incompatibility). Work around it with a tiny proxy (a Cloud Function works) and set the `NUVEI_PROXY_URL` env — the SDK routes through it automatically when present. On hosts without this limitation, leave `NUVEI_PROXY_URL` unset and the SDK calls Nuvei directly.
|
|
135
|
+
|
|
136
|
+
### Reference implementation
|
|
137
|
+
|
|
138
|
+
A production-validated implementation of both (`threeDSCallback` + `nuveiProxy`, Firebase Functions v2) lives in `pauhenriques-website/functions/index.js`. The logic is business-agnostic — copy it and point your env vars at the deployed functions.
|
|
139
|
+
|
|
140
|
+
> **Roadmap (v0.next):** these will ship from the package as framework-neutral handlers (Web-standard `Request → Response`) plus thin runtime adapters (`toCloudFunction`, `toExpress`), so a **single source of truth** can deploy to a Next.js route, a Cloud Function, or any runtime — eliminating the hand-maintained `functions/index.js` duplication. The package will stay **agnostic to hosting and framework**; it remains coupled to **Firebase (Firestore/Auth) as the data layer by design**.
|
|
141
|
+
|
|
113
142
|
## UI components — usage
|
|
114
143
|
|
|
115
144
|
All `/ui` exports are client components (`"use client"`) and expect a Tailwind setup. The components consume CSS variables for theming so the same components work for every white-label client with their own branding.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/adapters/index.ts
|
|
4
|
+
function buildRequest(req) {
|
|
5
|
+
const headerList = req.headers ?? {};
|
|
6
|
+
const host = (Array.isArray(headerList.host) ? headerList.host[0] : headerList.host) || "localhost";
|
|
7
|
+
const protoHeader = headerList["x-forwarded-proto"] ?? headerList["X-Forwarded-Proto"];
|
|
8
|
+
const proto = (Array.isArray(protoHeader) ? protoHeader[0] : protoHeader) || "https";
|
|
9
|
+
const path = req.originalUrl ?? req.url ?? "/";
|
|
10
|
+
const url = new URL(path, `${proto}://${host}`).toString();
|
|
11
|
+
const headers = new Headers();
|
|
12
|
+
for (const [key, value] of Object.entries(headerList)) {
|
|
13
|
+
if (value === void 0) continue;
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
for (const v of value) headers.append(key, v);
|
|
16
|
+
} else {
|
|
17
|
+
headers.set(key, value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
21
|
+
let body;
|
|
22
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
23
|
+
if (req.rawBody) {
|
|
24
|
+
body = new TextDecoder().decode(req.rawBody);
|
|
25
|
+
} else if (req.body !== void 0 && req.body !== null) {
|
|
26
|
+
body = typeof req.body === "string" ? req.body : JSON.stringify(req.body);
|
|
27
|
+
if (!headers.has("content-type") && typeof req.body !== "string") {
|
|
28
|
+
headers.set("content-type", "application/json");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return new Request(url, { method, headers, body });
|
|
33
|
+
}
|
|
34
|
+
async function writeResponse(response, res) {
|
|
35
|
+
res.statusCode = response.status;
|
|
36
|
+
response.headers.forEach((value, key) => {
|
|
37
|
+
res.setHeader(key, value);
|
|
38
|
+
});
|
|
39
|
+
const buf = new Uint8Array(await response.arrayBuffer());
|
|
40
|
+
res.end(buf);
|
|
41
|
+
}
|
|
42
|
+
function toExpress(handler) {
|
|
43
|
+
return async (req, res) => {
|
|
44
|
+
const request = buildRequest(req);
|
|
45
|
+
const response = await handler(request);
|
|
46
|
+
await writeResponse(response, res);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
var toCloudFunction = toExpress;
|
|
50
|
+
|
|
51
|
+
exports.toCloudFunction = toCloudFunction;
|
|
52
|
+
exports.toExpress = toExpress;
|
|
53
|
+
//# sourceMappingURL=index.cjs.map
|
|
54
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["// @pandait.tech/payment-nuvei/adapters\n//\n// Runtime adapters that let the package's Web-standard handlers\n// (`(req: Request) => Promise<Response>`) run on Node/Express-style runtimes\n// such as Firebase Cloud Functions v2 (`onRequest`) or Express.\n//\n// Next.js App Router needs NO adapter — its route handlers already receive a\n// `Request` and accept a `Response`, so you export the handler directly:\n// export const POST = createChargeHandler(deps);\n//\n// Cloud Functions v2 / Express need this bridge because they use the\n// `(req, res)` signature instead of `Request -> Response`:\n// exports.threeDSCallback = onRequest({ cors: true },\n// toCloudFunction(create3dsCallbackHandler({ firebase: { db } }).POST));\n\n/** Minimal structural type for a Node/Express-style request. */\nexport interface NodeRequestLike {\n method?: string;\n url?: string;\n originalUrl?: string;\n headers: Record<string, string | string[] | undefined>;\n /** Raw request body. Cloud Functions v2 provides this as a Buffer. */\n rawBody?: Buffer | Uint8Array;\n /** Parsed body (Express/Functions populate this for JSON/urlencoded). */\n body?: unknown;\n}\n\n/** Minimal structural type for a Node/Express-style response. */\nexport interface NodeResponseLike {\n statusCode: number;\n setHeader(name: string, value: string): void;\n end(chunk?: string | Uint8Array): void;\n}\n\n/** A Web-standard handler: takes a Fetch `Request`, returns a `Response`. */\nexport type WebHandler = (request: Request) => Promise<Response> | Response;\n\n/** A Node/Express-style handler: `(req, res)`. */\nexport type NodeHandler = (\n req: NodeRequestLike,\n res: NodeResponseLike,\n) => Promise<void>;\n\nfunction buildRequest(req: NodeRequestLike): Request {\n const headerList = req.headers ?? {};\n const host =\n (Array.isArray(headerList.host) ? headerList.host[0] : headerList.host) ||\n \"localhost\";\n const protoHeader =\n headerList[\"x-forwarded-proto\"] ?? headerList[\"X-Forwarded-Proto\"];\n const proto =\n (Array.isArray(protoHeader) ? protoHeader[0] : protoHeader) || \"https\";\n const path = req.originalUrl ?? req.url ?? \"/\";\n const url = new URL(path, `${proto}://${host}`).toString();\n\n const headers = new Headers();\n for (const [key, value] of Object.entries(headerList)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const v of value) headers.append(key, v);\n } else {\n headers.set(key, value);\n }\n }\n\n const method = (req.method ?? \"GET\").toUpperCase();\n let body: string | undefined;\n if (method !== \"GET\" && method !== \"HEAD\") {\n if (req.rawBody) {\n // Bodies handled by this package are always text (JSON / form-urlencoded);\n // decoding to a string keeps the original bytes intact and the preserved\n // content-type header lets Request.json()/.formData()/.text() parse it.\n body = new TextDecoder().decode(req.rawBody);\n } else if (req.body !== undefined && req.body !== null) {\n body =\n typeof req.body === \"string\" ? req.body : JSON.stringify(req.body);\n if (!headers.has(\"content-type\") && typeof req.body !== \"string\") {\n headers.set(\"content-type\", \"application/json\");\n }\n }\n }\n\n return new Request(url, { method, headers, body });\n}\n\nasync function writeResponse(\n response: Response,\n res: NodeResponseLike,\n): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n // Note: these payment handlers never emit Set-Cookie, so the single-value\n // Headers.forEach is sufficient. If a future handler sets cookies, switch\n // to response.headers.getSetCookie() for the Set-Cookie header.\n res.setHeader(key, value);\n });\n const buf = new Uint8Array(await response.arrayBuffer());\n res.end(buf);\n}\n\n/**\n * Wrap a Web-standard handler so it runs as a Node/Express-style `(req, res)`\n * handler. Works for Express and any runtime with a compatible req/res\n * (Node `http.ServerResponse` exposes `statusCode`/`setHeader`/`end`).\n */\nexport function toExpress(handler: WebHandler): NodeHandler {\n return async (req, res) => {\n const request = buildRequest(req);\n const response = await handler(request);\n await writeResponse(response, res);\n };\n}\n\n/**\n * Wrap a Web-standard handler for Firebase Cloud Functions v2 `onRequest`.\n * Identical bridge to {@link toExpress} (Functions v2 req/res are Express-based);\n * provided as a named export for intent at the call site.\n */\nexport const toCloudFunction = toExpress;\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Minimal structural type for a Node/Express-style request. */
|
|
2
|
+
interface NodeRequestLike {
|
|
3
|
+
method?: string;
|
|
4
|
+
url?: string;
|
|
5
|
+
originalUrl?: string;
|
|
6
|
+
headers: Record<string, string | string[] | undefined>;
|
|
7
|
+
/** Raw request body. Cloud Functions v2 provides this as a Buffer. */
|
|
8
|
+
rawBody?: Buffer | Uint8Array;
|
|
9
|
+
/** Parsed body (Express/Functions populate this for JSON/urlencoded). */
|
|
10
|
+
body?: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** Minimal structural type for a Node/Express-style response. */
|
|
13
|
+
interface NodeResponseLike {
|
|
14
|
+
statusCode: number;
|
|
15
|
+
setHeader(name: string, value: string): void;
|
|
16
|
+
end(chunk?: string | Uint8Array): void;
|
|
17
|
+
}
|
|
18
|
+
/** A Web-standard handler: takes a Fetch `Request`, returns a `Response`. */
|
|
19
|
+
type WebHandler = (request: Request) => Promise<Response> | Response;
|
|
20
|
+
/** A Node/Express-style handler: `(req, res)`. */
|
|
21
|
+
type NodeHandler = (req: NodeRequestLike, res: NodeResponseLike) => Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Wrap a Web-standard handler so it runs as a Node/Express-style `(req, res)`
|
|
24
|
+
* handler. Works for Express and any runtime with a compatible req/res
|
|
25
|
+
* (Node `http.ServerResponse` exposes `statusCode`/`setHeader`/`end`).
|
|
26
|
+
*/
|
|
27
|
+
declare function toExpress(handler: WebHandler): NodeHandler;
|
|
28
|
+
/**
|
|
29
|
+
* Wrap a Web-standard handler for Firebase Cloud Functions v2 `onRequest`.
|
|
30
|
+
* Identical bridge to {@link toExpress} (Functions v2 req/res are Express-based);
|
|
31
|
+
* provided as a named export for intent at the call site.
|
|
32
|
+
*/
|
|
33
|
+
declare const toCloudFunction: typeof toExpress;
|
|
34
|
+
|
|
35
|
+
export { type NodeHandler, type NodeRequestLike, type NodeResponseLike, type WebHandler, toCloudFunction, toExpress };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Minimal structural type for a Node/Express-style request. */
|
|
2
|
+
interface NodeRequestLike {
|
|
3
|
+
method?: string;
|
|
4
|
+
url?: string;
|
|
5
|
+
originalUrl?: string;
|
|
6
|
+
headers: Record<string, string | string[] | undefined>;
|
|
7
|
+
/** Raw request body. Cloud Functions v2 provides this as a Buffer. */
|
|
8
|
+
rawBody?: Buffer | Uint8Array;
|
|
9
|
+
/** Parsed body (Express/Functions populate this for JSON/urlencoded). */
|
|
10
|
+
body?: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** Minimal structural type for a Node/Express-style response. */
|
|
13
|
+
interface NodeResponseLike {
|
|
14
|
+
statusCode: number;
|
|
15
|
+
setHeader(name: string, value: string): void;
|
|
16
|
+
end(chunk?: string | Uint8Array): void;
|
|
17
|
+
}
|
|
18
|
+
/** A Web-standard handler: takes a Fetch `Request`, returns a `Response`. */
|
|
19
|
+
type WebHandler = (request: Request) => Promise<Response> | Response;
|
|
20
|
+
/** A Node/Express-style handler: `(req, res)`. */
|
|
21
|
+
type NodeHandler = (req: NodeRequestLike, res: NodeResponseLike) => Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Wrap a Web-standard handler so it runs as a Node/Express-style `(req, res)`
|
|
24
|
+
* handler. Works for Express and any runtime with a compatible req/res
|
|
25
|
+
* (Node `http.ServerResponse` exposes `statusCode`/`setHeader`/`end`).
|
|
26
|
+
*/
|
|
27
|
+
declare function toExpress(handler: WebHandler): NodeHandler;
|
|
28
|
+
/**
|
|
29
|
+
* Wrap a Web-standard handler for Firebase Cloud Functions v2 `onRequest`.
|
|
30
|
+
* Identical bridge to {@link toExpress} (Functions v2 req/res are Express-based);
|
|
31
|
+
* provided as a named export for intent at the call site.
|
|
32
|
+
*/
|
|
33
|
+
declare const toCloudFunction: typeof toExpress;
|
|
34
|
+
|
|
35
|
+
export { type NodeHandler, type NodeRequestLike, type NodeResponseLike, type WebHandler, toCloudFunction, toExpress };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// src/adapters/index.ts
|
|
2
|
+
function buildRequest(req) {
|
|
3
|
+
const headerList = req.headers ?? {};
|
|
4
|
+
const host = (Array.isArray(headerList.host) ? headerList.host[0] : headerList.host) || "localhost";
|
|
5
|
+
const protoHeader = headerList["x-forwarded-proto"] ?? headerList["X-Forwarded-Proto"];
|
|
6
|
+
const proto = (Array.isArray(protoHeader) ? protoHeader[0] : protoHeader) || "https";
|
|
7
|
+
const path = req.originalUrl ?? req.url ?? "/";
|
|
8
|
+
const url = new URL(path, `${proto}://${host}`).toString();
|
|
9
|
+
const headers = new Headers();
|
|
10
|
+
for (const [key, value] of Object.entries(headerList)) {
|
|
11
|
+
if (value === void 0) continue;
|
|
12
|
+
if (Array.isArray(value)) {
|
|
13
|
+
for (const v of value) headers.append(key, v);
|
|
14
|
+
} else {
|
|
15
|
+
headers.set(key, value);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
19
|
+
let body;
|
|
20
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
21
|
+
if (req.rawBody) {
|
|
22
|
+
body = new TextDecoder().decode(req.rawBody);
|
|
23
|
+
} else if (req.body !== void 0 && req.body !== null) {
|
|
24
|
+
body = typeof req.body === "string" ? req.body : JSON.stringify(req.body);
|
|
25
|
+
if (!headers.has("content-type") && typeof req.body !== "string") {
|
|
26
|
+
headers.set("content-type", "application/json");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return new Request(url, { method, headers, body });
|
|
31
|
+
}
|
|
32
|
+
async function writeResponse(response, res) {
|
|
33
|
+
res.statusCode = response.status;
|
|
34
|
+
response.headers.forEach((value, key) => {
|
|
35
|
+
res.setHeader(key, value);
|
|
36
|
+
});
|
|
37
|
+
const buf = new Uint8Array(await response.arrayBuffer());
|
|
38
|
+
res.end(buf);
|
|
39
|
+
}
|
|
40
|
+
function toExpress(handler) {
|
|
41
|
+
return async (req, res) => {
|
|
42
|
+
const request = buildRequest(req);
|
|
43
|
+
const response = await handler(request);
|
|
44
|
+
await writeResponse(response, res);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
var toCloudFunction = toExpress;
|
|
48
|
+
|
|
49
|
+
export { toCloudFunction, toExpress };
|
|
50
|
+
//# sourceMappingURL=index.js.map
|
|
51
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// @pandait.tech/payment-nuvei/adapters\n//\n// Runtime adapters that let the package's Web-standard handlers\n// (`(req: Request) => Promise<Response>`) run on Node/Express-style runtimes\n// such as Firebase Cloud Functions v2 (`onRequest`) or Express.\n//\n// Next.js App Router needs NO adapter — its route handlers already receive a\n// `Request` and accept a `Response`, so you export the handler directly:\n// export const POST = createChargeHandler(deps);\n//\n// Cloud Functions v2 / Express need this bridge because they use the\n// `(req, res)` signature instead of `Request -> Response`:\n// exports.threeDSCallback = onRequest({ cors: true },\n// toCloudFunction(create3dsCallbackHandler({ firebase: { db } }).POST));\n\n/** Minimal structural type for a Node/Express-style request. */\nexport interface NodeRequestLike {\n method?: string;\n url?: string;\n originalUrl?: string;\n headers: Record<string, string | string[] | undefined>;\n /** Raw request body. Cloud Functions v2 provides this as a Buffer. */\n rawBody?: Buffer | Uint8Array;\n /** Parsed body (Express/Functions populate this for JSON/urlencoded). */\n body?: unknown;\n}\n\n/** Minimal structural type for a Node/Express-style response. */\nexport interface NodeResponseLike {\n statusCode: number;\n setHeader(name: string, value: string): void;\n end(chunk?: string | Uint8Array): void;\n}\n\n/** A Web-standard handler: takes a Fetch `Request`, returns a `Response`. */\nexport type WebHandler = (request: Request) => Promise<Response> | Response;\n\n/** A Node/Express-style handler: `(req, res)`. */\nexport type NodeHandler = (\n req: NodeRequestLike,\n res: NodeResponseLike,\n) => Promise<void>;\n\nfunction buildRequest(req: NodeRequestLike): Request {\n const headerList = req.headers ?? {};\n const host =\n (Array.isArray(headerList.host) ? headerList.host[0] : headerList.host) ||\n \"localhost\";\n const protoHeader =\n headerList[\"x-forwarded-proto\"] ?? headerList[\"X-Forwarded-Proto\"];\n const proto =\n (Array.isArray(protoHeader) ? protoHeader[0] : protoHeader) || \"https\";\n const path = req.originalUrl ?? req.url ?? \"/\";\n const url = new URL(path, `${proto}://${host}`).toString();\n\n const headers = new Headers();\n for (const [key, value] of Object.entries(headerList)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const v of value) headers.append(key, v);\n } else {\n headers.set(key, value);\n }\n }\n\n const method = (req.method ?? \"GET\").toUpperCase();\n let body: string | undefined;\n if (method !== \"GET\" && method !== \"HEAD\") {\n if (req.rawBody) {\n // Bodies handled by this package are always text (JSON / form-urlencoded);\n // decoding to a string keeps the original bytes intact and the preserved\n // content-type header lets Request.json()/.formData()/.text() parse it.\n body = new TextDecoder().decode(req.rawBody);\n } else if (req.body !== undefined && req.body !== null) {\n body =\n typeof req.body === \"string\" ? req.body : JSON.stringify(req.body);\n if (!headers.has(\"content-type\") && typeof req.body !== \"string\") {\n headers.set(\"content-type\", \"application/json\");\n }\n }\n }\n\n return new Request(url, { method, headers, body });\n}\n\nasync function writeResponse(\n response: Response,\n res: NodeResponseLike,\n): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n // Note: these payment handlers never emit Set-Cookie, so the single-value\n // Headers.forEach is sufficient. If a future handler sets cookies, switch\n // to response.headers.getSetCookie() for the Set-Cookie header.\n res.setHeader(key, value);\n });\n const buf = new Uint8Array(await response.arrayBuffer());\n res.end(buf);\n}\n\n/**\n * Wrap a Web-standard handler so it runs as a Node/Express-style `(req, res)`\n * handler. Works for Express and any runtime with a compatible req/res\n * (Node `http.ServerResponse` exposes `statusCode`/`setHeader`/`end`).\n */\nexport function toExpress(handler: WebHandler): NodeHandler {\n return async (req, res) => {\n const request = buildRequest(req);\n const response = await handler(request);\n await writeResponse(response, res);\n };\n}\n\n/**\n * Wrap a Web-standard handler for Firebase Cloud Functions v2 `onRequest`.\n * Identical bridge to {@link toExpress} (Functions v2 req/res are Express-based);\n * provided as a named export for intent at the call site.\n */\nexport const toCloudFunction = toExpress;\n"]}
|