fix: proxy error json response (#16)

* chore: remove deprecated package

* fix: proxy error json response
This commit is contained in:
Daniel Rochetti 2023-10-10 16:15:14 -07:00 committed by GitHub
parent dcd513c1e7
commit e02118f43f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 4 additions and 322 deletions

View File

@ -1,3 +0,0 @@
{
"presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]]
}

View File

@ -1,18 +0,0 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -1,3 +0,0 @@
# @fal-ai/serverless-nextjs
This package is not longer maintained. Check out the [@fal-ai/serverless-proxy](../proxy/)package, which supports Next.js and other Express-based frameworks.

View File

@ -1,17 +0,0 @@
/* eslint-disable */
export default {
displayName: 'nextjs',
preset: '../../jest.preset.js',
globals: {},
testEnvironment: 'node',
transform: {
'^.+\\.[tj]sx?$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
},
],
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/libs/nextjs',
};

View File

@ -1,32 +0,0 @@
{
"name": "@fal-ai/serverless-nextjs",
"description": "The fal-serverless Next.js integration",
"version": "0.2.4",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/fal-ai/serverless-js.git",
"directory": "libs/nextjs"
},
"keywords": [
"fal",
"serverless",
"client",
"next",
"nextjs",
"proxy"
],
"peerDependencies": {
"next": "^13.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
}

View File

@ -1,41 +0,0 @@
{
"name": "nextjs",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/nextjs/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/nextjs",
"tsConfig": "libs/nextjs/tsconfig.lib.json",
"packageJson": "libs/nextjs/package.json",
"main": "libs/nextjs/src/index.ts",
"assets": ["LICENSE", "CODE_OF_CONDUCT.md", "libs/nextjs/README.md"]
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/nextjs/**/*.ts"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/nextjs/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
}
},
"tags": []
}

View File

@ -1,29 +0,0 @@
import type { RequestMiddleware } from '@fal-ai/serverless-client';
export type NextProxyConfig = {
targetUrl: string;
};
const defaultConfig: NextProxyConfig = {
targetUrl: '/api/_fal/proxy',
};
export const TARGET_URL_HEADER = 'x-fal-target-url';
export function withNextProxy(
config: NextProxyConfig = defaultConfig
): RequestMiddleware {
// when running on the server, we don't need to proxy the request
if (typeof window === 'undefined') {
return (requestConfig) => Promise.resolve(requestConfig);
}
return (requestConfig) =>
Promise.resolve({
...requestConfig,
url: config.targetUrl,
headers: {
[TARGET_URL_HEADER]: requestConfig.url,
...(requestConfig.headers || {}),
},
});
}

View File

@ -1,113 +0,0 @@
import type { NextApiHandler, NextApiRequest, PageConfig } from 'next';
import { TARGET_URL_HEADER } from './config';
const FAL_KEY = process.env.FAL_KEY || process.env.NEXT_PUBLIC_FAL_KEY;
const FAL_KEY_ID = process.env.FAL_KEY_ID || process.env.NEXT_PUBLIC_FAL_KEY_ID;
const FAL_KEY_SECRET =
process.env.FAL_KEY_SECRET || process.env.NEXT_PUBLIC_FAL_KEY_SECRET;
/**
* Utility to get a header value as `string` from a Headers object.
*
* @private
* @param request the Next request object.
* @param key the header key.
* @returns the header value as `string` or `undefined` if the header is not set.
*/
function getHeader(request: NextApiRequest, key: string): string | undefined {
const headerValue = request.headers[key.toLowerCase()];
if (Array.isArray(headerValue)) {
return headerValue[0];
}
return headerValue;
}
/**
* Clean up headers that should not be forwarded to the proxy.
* @param request the Next request object.
*/
function cleanUpHeaders(request: NextApiRequest) {
delete request.headers['origin'];
delete request.headers['referer'];
delete request.headers[TARGET_URL_HEADER];
}
function getFalKey(): string | undefined {
if (FAL_KEY) {
return FAL_KEY;
}
if (FAL_KEY_ID && FAL_KEY_SECRET) {
return `${FAL_KEY_ID}:${FAL_KEY_SECRET}`;
}
return undefined;
}
/**
* A Next request handler that proxies the request to the fal-serverless
* endpoint. This is useful so client-side calls to the fal-serverless endpoint
* can be made without CORS issues and the correct credentials can be added
* effortlessly.
*
* @param request the Next request object.
* @param response the Next response object.
* @returns Promise<any> the promise that will be resolved once the request is done.
*/
export const handler: NextApiHandler = async (request, response) => {
const targetUrl = getHeader(request, TARGET_URL_HEADER);
if (!targetUrl) {
response.status(400).send(`Missing the ${TARGET_URL_HEADER} header`);
return;
}
if (targetUrl.indexOf('fal.ai') === -1) {
response.status(412).send(`Invalid ${TARGET_URL_HEADER} header`);
return;
}
cleanUpHeaders(request);
const falKey = getFalKey();
if (!falKey) {
response.status(401).send('Missing fal.ai credentials');
return;
}
// pass over headers prefixed with x-fal-*
const headers: Record<string, string | string[] | undefined> = {};
Object.keys(request.headers).forEach((key) => {
if (key.toLowerCase().startsWith('x-fal-')) {
headers[key.toLowerCase()] = request.headers[key];
}
});
const res = await fetch(targetUrl, {
method: request.method,
headers: {
...headers,
authorization: getHeader(request, 'authorization') ?? `Key ${falKey}`,
accept: 'application/json',
'content-type': 'application/json',
'x-fal-client-proxy': '@fal-ai/serverless-nextjs',
},
body:
request.method?.toUpperCase() === 'GET'
? undefined
: JSON.stringify(request.body),
});
// copy headers from res to response
res.headers.forEach((value, key) => {
response.setHeader(key, value);
});
if (res.headers.get('content-type') === 'application/json') {
const data = await res.json();
response.status(res.status).json(data);
return;
}
const data = await res.text();
response.status(res.status).send(data);
};
export const config: PageConfig = {
api: {},
};

View File

@ -1,2 +0,0 @@
export * from './config';
export * from './handler';

View File

@ -1,13 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -1,11 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
}

View File

@ -1,20 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
]
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@fal-ai/serverless-proxy", "name": "@fal-ai/serverless-proxy",
"version": "0.3.3", "version": "0.3.4",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -19,11 +19,10 @@ export const handler: RequestHandler = async (request, response, next) => {
method: request.method, method: request.method,
respondWith: (status, data) => respondWith: (status, data) =>
typeof data === 'string' typeof data === 'string'
? response.status(status).send(data) ? response.status(status).json({ details: data })
: response.status(status).json(data), : response.status(status).json(data),
getHeaders: () => request.headers, getHeaders: () => request.headers,
getHeader: (name) => request.headers[name], getHeader: (name) => request.headers[name],
removeHeader: (name) => response.removeHeader(name),
sendHeader: (name, value) => response.setHeader(name, value), sendHeader: (name, value) => response.setHeader(name, value),
getBody: () => JSON.stringify(request.body), getBody: () => JSON.stringify(request.body),
}); });

View File

@ -18,7 +18,6 @@ export interface ProxyBehavior {
respondWith(status: number, data: string | any): void; respondWith(status: number, data: string | any): void;
getHeaders(): Record<string, string | string[] | undefined>; getHeaders(): Record<string, string | string[] | undefined>;
getHeader(name: string): string | string[] | undefined; getHeader(name: string): string | string[] | undefined;
removeHeader(name: string): void;
sendHeader(name: string, value: string): void; sendHeader(name: string, value: string): void;
getBody(): string | undefined; getBody(): string | undefined;
} }
@ -42,16 +41,6 @@ function singleHeaderValue(
return value; return value;
} }
/**
* Clean up headers that should not be forwarded to the proxy.
* @param behavior The proxy implementation.
*/
function cleanUpHeaders(behavior: ProxyBehavior) {
behavior.removeHeader('origin');
behavior.removeHeader('referer');
behavior.removeHeader(TARGET_URL_HEADER);
}
function getFalKey(): string | undefined { function getFalKey(): string | undefined {
if (FAL_KEY) { if (FAL_KEY) {
return FAL_KEY; return FAL_KEY;
@ -82,8 +71,6 @@ export const handleRequest = async (behavior: ProxyBehavior) => {
return; return;
} }
cleanUpHeaders(behavior);
const falKey = getFalKey(); const falKey = getFalKey();
if (!falKey) { if (!falKey) {
behavior.respondWith(401, 'Missing fal.ai credentials'); behavior.respondWith(401, 'Missing fal.ai credentials');
@ -118,7 +105,7 @@ export const handleRequest = async (behavior: ProxyBehavior) => {
behavior.sendHeader(key, value); behavior.sendHeader(key, value);
}); });
if (res.headers.get('content-type') === 'application/json') { if (res.headers.get('content-type').includes('application/json')) {
const data = await res.json(); const data = await res.json();
behavior.respondWith(res.status, data); behavior.respondWith(res.status, data);
return; return;

View File

@ -19,11 +19,10 @@ export const handler: NextApiHandler = async (request, response) => {
method: request.method, method: request.method,
respondWith: (status, data) => respondWith: (status, data) =>
typeof data === 'string' typeof data === 'string'
? response.status(status).send(data) ? response.status(status).json({ details: data })
: response.status(status).json(data), : response.status(status).json(data),
getHeaders: () => request.headers, getHeaders: () => request.headers,
getHeader: (name) => request.headers[name], getHeader: (name) => request.headers[name],
removeHeader: (name) => response.removeHeader(name),
sendHeader: (name, value) => response.setHeader(name, value), sendHeader: (name, value) => response.setHeader(name, value),
getBody: () => JSON.stringify(request.body), getBody: () => JSON.stringify(request.body),
}); });

View File

@ -16,7 +16,6 @@
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@fal-ai/serverless-client": ["libs/client/src/index.ts"], "@fal-ai/serverless-client": ["libs/client/src/index.ts"],
"@fal-ai/serverless-nextjs": ["libs/nextjs/src/index.ts"],
"@fal-ai/serverless-proxy": ["libs/proxy/src/index.ts"], "@fal-ai/serverless-proxy": ["libs/proxy/src/index.ts"],
"@fal-ai/serverless-proxy/express": ["libs/proxy/src/express.ts"], "@fal-ai/serverless-proxy/express": ["libs/proxy/src/express.ts"],
"@fal-ai/serverless-proxy/nextjs": ["libs/proxy/src/nextjs.ts"] "@fal-ai/serverless-proxy/nextjs": ["libs/proxy/src/nextjs.ts"]