diff --git a/package-lock.json b/package-lock.json index 7517745b5..61f822b59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5392,11 +5392,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "license": "MIT", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5872,6 +5879,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-properties": { "version": "1.2.0", "license": "MIT", @@ -6136,6 +6159,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { "version": "2.0.1", "dev": true, @@ -7326,13 +7368,18 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "license": "MIT", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7494,6 +7541,7 @@ }, "node_modules/has": { "version": "1.0.3", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.1" @@ -7518,10 +7566,11 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "license": "MIT", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10342,9 +10391,13 @@ } }, "node_modules/object-inspect": { - "version": "1.12.3", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", "dev": true, - "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11541,6 +11594,22 @@ "dev": true, "license": "MIT" }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -11571,13 +11640,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -14454,7 +14528,7 @@ }, "packages/next-on-fleek": { "name": "@fleek-platform/next-on-fleek", - "version": "1.14.5", + "version": "1.14.6", "license": "MIT", "dependencies": { "@fleek-platform/sdk": "^2.1.5", diff --git a/packages/next-on-fleek/package.json b/packages/next-on-fleek/package.json index c55d0004b..8f77b4743 100644 --- a/packages/next-on-fleek/package.json +++ b/packages/next-on-fleek/package.json @@ -1,6 +1,6 @@ { "name": "@fleek-platform/next-on-fleek", - "version": "1.14.6", + "version": "1.14.7", "main": "./dist/index.js", "module": "./dist/index.js", "types": "./dist/src/index.d.ts", diff --git a/packages/next-on-fleek/src/api/getRequestContext.ts b/packages/next-on-fleek/src/api/getRequestContext.ts index 255afbdf9..54bdf760c 100644 --- a/packages/next-on-fleek/src/api/getRequestContext.ts +++ b/packages/next-on-fleek/src/api/getRequestContext.ts @@ -14,8 +14,8 @@ type RequestContext< ctx: Context; }; -const cloudflareRequestContextSymbol = Symbol.for( - '__cloudflare-request-context__', +const fleekRequestContextSymbol = Symbol.for( + '__fleek-request-context__', ); export function getOptionalRequestContext< @@ -24,11 +24,11 @@ export function getOptionalRequestContext< >(): undefined | RequestContext { const cloudflareRequestContext = ( globalThis as unknown as { - [cloudflareRequestContextSymbol]: + [fleekRequestContextSymbol]: | RequestContext | undefined; } - )[cloudflareRequestContextSymbol]; + )[fleekRequestContextSymbol]; if (inferRuntime() === 'nodejs') { // no matter what, we want to throw if either diff --git a/packages/next-on-fleek/src/buildApplication/buildWorkerFile.ts b/packages/next-on-fleek/src/buildApplication/buildWorkerFile.ts index 8e84f418b..f1d10ff06 100644 --- a/packages/next-on-fleek/src/buildApplication/buildWorkerFile.ts +++ b/packages/next-on-fleek/src/buildApplication/buildWorkerFile.ts @@ -57,9 +57,6 @@ export async function buildWorkerFile( minify, }: BuildWorkerFileOpts, ): Promise { - // eslint-disable-next-line no-console - console.log('Building worker file'); - const functionsFile = join( tmpdir(), `functions-${Math.random().toString(36).slice(2)}.js`, diff --git a/packages/next-on-fleek/src/buildApplication/generateGlobalJs.ts b/packages/next-on-fleek/src/buildApplication/generateGlobalJs.ts index b1cae0660..7ef898f7e 100644 --- a/packages/next-on-fleek/src/buildApplication/generateGlobalJs.ts +++ b/packages/next-on-fleek/src/buildApplication/generateGlobalJs.ts @@ -128,38 +128,5 @@ export function generateGlobalJs(): string { globalThis.Buffer = Buffer; }) .catch(() => null); - - const __ALSes_PROMISE__ = import('node:async_hooks').then(({ AsyncLocalStorage }) => { - globalThis.AsyncLocalStorage = AsyncLocalStorage; - - const envAsyncLocalStorage = new AsyncLocalStorage(); - const requestContextAsyncLocalStorage = new AsyncLocalStorage(); - - globalThis.process = { - env: new Proxy( - {}, - { - ownKeys: () => Reflect.ownKeys(envAsyncLocalStorage.getStore()), - getOwnPropertyDescriptor: (_, ...args) => - Reflect.getOwnPropertyDescriptor(envAsyncLocalStorage.getStore(), ...args), - get: (_, property) => Reflect.get(envAsyncLocalStorage.getStore(), property), - set: (_, property, value) => Reflect.set(envAsyncLocalStorage.getStore(), property, value), - }), - }; - - globalThis[Symbol.for('__cloudflare-request-context__')] = new Proxy( - {}, - { - ownKeys: () => Reflect.ownKeys(requestContextAsyncLocalStorage.getStore()), - getOwnPropertyDescriptor: (_, ...args) => - Reflect.getOwnPropertyDescriptor(requestContextAsyncLocalStorage.getStore(), ...args), - get: (_, property) => Reflect.get(requestContextAsyncLocalStorage.getStore(), property), - set: (_, property, value) => Reflect.set(requestContextAsyncLocalStorage.getStore(), property, value), - } - ); - - return { envAsyncLocalStorage, requestContextAsyncLocalStorage }; - }) - .catch(() => null); `; } diff --git a/packages/next-on-fleek/templates/_worker.js/index.ts b/packages/next-on-fleek/templates/_worker.js/index.ts index e1f78df38..ea61198e7 100644 --- a/packages/next-on-fleek/templates/_worker.js/index.ts +++ b/packages/next-on-fleek/templates/_worker.js/index.ts @@ -1,4 +1,4 @@ -import type { AsyncLocalStorage } from 'node:async_hooks'; +import { AsyncLocalStorage } from 'node:async_hooks'; import type { FleekRequest, FleekResponse } from '../types'; import { handleRequest } from './handleRequest'; import { adjustRequestForVercel, handleImageResizingRequest } from './utils'; @@ -9,11 +9,6 @@ declare const __BUILD_OUTPUT__: VercelBuildOutput; declare const __BUILD_METADATA__: NextOnPagesBuildMetadata; -declare const __ALSes_PROMISE__: Promise; - requestContextAsyncLocalStorage: AsyncLocalStorage; -}>; - export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; export type LoggerOptions = { @@ -21,16 +16,66 @@ export type LoggerOptions = { }; export async function main(fleekRequest: FleekRequest): Promise { - const request = adaptFleekRequestToFetch(fleekRequest); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + globalThis.AsyncLocalStorage = AsyncLocalStorage; + const envAsyncLocalStorage: AsyncLocalStorage = + new AsyncLocalStorage(); + const requestContextAsyncLocalStorage: AsyncLocalStorage = + new AsyncLocalStorage(); - const asyncLocalStorages = await __ALSes_PROMISE__; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + globalThis.process = { + env: new Proxy( + {}, + { + ownKeys: () => + Reflect.ownKeys(envAsyncLocalStorage.getStore() as object), + getOwnPropertyDescriptor: (_, ...args) => + Reflect.getOwnPropertyDescriptor( + envAsyncLocalStorage.getStore() as object, + ...args, + ), + get: (_, property) => + Reflect.get(envAsyncLocalStorage.getStore() as object, property), + set: (_, property, value) => + Reflect.set( + envAsyncLocalStorage.getStore() as object, + property, + value, + ), + }, + ), + }; - if (!asyncLocalStorages) { - throw new Error('AsyncLocalStorages not found'); - } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + globalThis[Symbol.for('__fleek-request-context__')] = new Proxy( + {}, + { + ownKeys: () => + Reflect.ownKeys(requestContextAsyncLocalStorage.getStore() as object), + getOwnPropertyDescriptor: (_, ...args) => + Reflect.getOwnPropertyDescriptor( + requestContextAsyncLocalStorage.getStore() as object, + ...args, + ), + get: (_, property) => + Reflect.get( + requestContextAsyncLocalStorage.getStore() as object, + property, + ), + set: (_, property, value) => + Reflect.set( + requestContextAsyncLocalStorage.getStore() as object, + property, + value, + ), + }, + ); - const { envAsyncLocalStorage, requestContextAsyncLocalStorage } = - asyncLocalStorages; + const request = await adaptFleekRequestToFetch(fleekRequest); return envAsyncLocalStorage.run({}, async () => { return requestContextAsyncLocalStorage.run({ request }, async () => { @@ -71,18 +116,132 @@ export async function main(fleekRequest: FleekRequest): Promise { }); } -function adaptFleekRequestToFetch(fleekRequest: FleekRequest): Request { - const url = new URL(`http://0.0.0.0${fleekRequest.path}`); +function parseMultipartString( + multipartString: string, + boundary: string, +): FormData { + // Split the string into lines + const lines = multipartString.split(/\r\n|\n/); + + const result = new FormData(); + let currentField = null; + let currentValue = []; + + for (let i = 1; i < lines.length; i++) { + const line = lines[i]; + + if (line?.startsWith('--' + boundary)) { + // Save the previous field if exists + if (currentField) { + result.append(currentField, currentValue.join('\n').trim()); + } + + // Reset for the next field + currentField = null; + currentValue = []; + + // Check if it's the closing boundary + if (line.endsWith('--')) { + break; + } + } else if (line?.includes(': ')) { + // This is a header line + const [name, value] = line.split(': '); + if (name?.toLowerCase() === 'content-disposition') { + const nameMatch = value?.match(/name="([^"]+)"/); + if (nameMatch) { + currentField = nameMatch[1]; + } + } + } else { + // This is a value line + currentValue.push(line); + } + } + + // Save the last field + if (currentField) { + result.append(currentField, currentValue.join('\n').trim()); + } + + return result; +} + +function isMultipartFormData(contentType: string) { + return /^multipart\/form-data/i.test(contentType); +} + +function isUrlEncodedFormData(contentType: string) { + return /^application\/x-www-form-urlencoded/i.test(contentType); +} + +function getBoundary(contentType: string) { + const boundaryMatch = contentType.match(/boundary=(?:"([^"]+)"|([^;]+))/i); + return boundaryMatch ? boundaryMatch[1] || boundaryMatch[2] : null; +} + +function parseUrlEncodedString(urlEncodedString: string): FormData { + const result = new FormData(); + const pairs = urlEncodedString.split('&'); + + for (const pair of pairs) { + const [key, value] = pair.split('='); + if (!key) { + continue; + } + result.append( + decodeURIComponent(key), + decodeURIComponent(value?.replace(/\+/g, ' ') ?? ''), + ); + } + + return result; +} + +async function adaptFleekRequestToFetch( + fleekRequest: FleekRequest, +): Promise { + let url; + if (fleekRequest.headers?.['origin']) { + url = new URL(`${fleekRequest.headers['origin']}${fleekRequest.path}`); + } else { + url = new URL(`http://0.0.0.0${fleekRequest.path}`); + } // Add query parameters for (const [key, value] of Object.entries(fleekRequest.query ?? {})) { url.searchParams.append(key, value); } + const contentType = fleekRequest.headers?.['content-type']; + + if (!contentType) { + return new Request(url, { + method: fleekRequest.method, + headers: fleekRequest.headers, + body: fleekRequest.body, + }); + } + + let body; + if (isMultipartFormData(contentType)) { + const boundary = getBoundary(contentType); + + if (!boundary) { + throw new Error('Invalid multipart format: boundary not found'); + } + + body = parseMultipartString(fleekRequest.body, boundary); + } else if (isUrlEncodedFormData(contentType)) { + body = parseUrlEncodedString(fleekRequest.body); + } else { + body = JSON.stringify(fleekRequest.body); + } + return new Request(url, { method: fleekRequest.method, headers: fleekRequest.headers, - body: fleekRequest.body, + body: body, }); } diff --git a/packages/next-on-fleek/templates/_worker.js/utils/cache.ts b/packages/next-on-fleek/templates/_worker.js/utils/cache.ts index 40b8192f1..302493291 100644 --- a/packages/next-on-fleek/templates/_worker.js/utils/cache.ts +++ b/packages/next-on-fleek/templates/_worker.js/utils/cache.ts @@ -6,7 +6,7 @@ const CACHE_TAGS_HEADER = 'x-vercel-cache-tags'; // https://github.com/vercel/next.js/blob/ba23d986/packages/next/src/lib/constants.ts#L18 const NEXT_CACHE_SOFT_TAGS_HEADER = 'x-next-cache-soft-tags'; -const REQUEST_CONTEXT_KEY = Symbol.for('__cloudflare-request-context__'); +const REQUEST_CONTEXT_KEY = Symbol.for('__fleek-request-context__'); /** * Handles an internal request to the suspense cache.