feat: support single fal_key env var (#10)
* feat: support single fal_key env var * fix: default config and tests
This commit is contained in:
parent
9a0497da30
commit
f075fd914f
@ -1,3 +1,3 @@
|
||||
// @snippet:start(client.proxy.nextjs)
|
||||
// @snippet:start("client.proxy.nextjs")
|
||||
export { config, handler as default } from '@fal-ai/serverless-nextjs';
|
||||
// @snippet:end
|
||||
|
||||
@ -33,10 +33,11 @@ function Error(props) {
|
||||
);
|
||||
}
|
||||
|
||||
const DEFAULT_PROMPT = "a city landscape of a cyberpunk metropolis, raining, purple, pink and teal neon lights, highly detailed, uhd";
|
||||
const DEFAULT_PROMPT =
|
||||
'a city landscape of a cyberpunk metropolis, raining, purple, pink and teal neon lights, highly detailed, uhd';
|
||||
|
||||
export function Index() {
|
||||
// @snippet:start(client.ui.state)
|
||||
// @snippet:start("client.ui.state")
|
||||
// Input state
|
||||
const [prompt, setPrompt] = useState<string>(DEFAULT_PROMPT);
|
||||
// Result state
|
||||
@ -64,7 +65,7 @@ export function Index() {
|
||||
const handleOnClick = async (e) => {
|
||||
e.preventDefault();
|
||||
reset();
|
||||
// @snippet:start(client.queue.subscribe)
|
||||
// @snippet:start("client.queue.subscribe")
|
||||
setLoading(true);
|
||||
const start = Date.now();
|
||||
try {
|
||||
@ -74,10 +75,13 @@ export function Index() {
|
||||
model_name: 'stabilityai/stable-diffusion-xl-base-1.0',
|
||||
image_size: 'square_hd',
|
||||
},
|
||||
onQueueUpdate(status) {
|
||||
onQueueUpdate(update) {
|
||||
setElapsedTime(Date.now() - start);
|
||||
if (status.status === 'IN_PROGRESS') {
|
||||
setLogs(status.logs.map((log) => log.message));
|
||||
if (
|
||||
update.status === 'IN_PROGRESS' ||
|
||||
update.status === 'COMPLETED'
|
||||
) {
|
||||
setLogs(update.logs.map((log) => log.message));
|
||||
}
|
||||
},
|
||||
});
|
||||
@ -91,8 +95,8 @@ export function Index() {
|
||||
// @snippet:end
|
||||
};
|
||||
return (
|
||||
<div className="min-h-screen dark:bg-gray-800 dark:text-gray-50 bg-gray-100 text-gray-800">
|
||||
<main className="flex flex-col items-center justify-center w-full flex-1 px-20 py-10 space-y-8">
|
||||
<div className="min-h-screen dark:bg-gray-900 bg-gray-100">
|
||||
<main className="container dark:text-gray-50 text-gray-900 flex flex-col items-center justify-center w-full flex-1 py-10 space-y-8">
|
||||
<h1 className="text-4xl font-bold mb-8">
|
||||
Hello <code className="font-light text-pink-600">fal</code>
|
||||
</h1>
|
||||
@ -134,7 +138,7 @@ export function Index() {
|
||||
<p className="text-sm text-current/80">
|
||||
{`Elapsed Time (seconds): ${(elapsedTime / 1000).toFixed(2)}`}
|
||||
</p>
|
||||
<pre className="text-sm bg-black/80 text-white/80 font-mono h-60 rounded whitespace-pre overflow-auto w-full">
|
||||
<pre className="text-sm bg-black/70 text-white/80 font-mono h-60 rounded whitespace-pre overflow-auto w-full">
|
||||
{result
|
||||
? JSON.stringify(result, null, 2)
|
||||
: '// result pending...'}
|
||||
@ -143,7 +147,7 @@ export function Index() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-xl font-light">Logs</h3>
|
||||
<pre className="text-sm bg-black/80 text-white/80 font-mono h-60 rounded whitespace-pre overflow-auto w-full">
|
||||
<pre className="text-sm bg-black/70 text-white/80 font-mono h-60 rounded whitespace-pre overflow-auto w-full">
|
||||
{logs.filter(Boolean).join('\n')}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
@ -4,10 +4,7 @@ describe('The config test suite', () => {
|
||||
it('should set the config variables accordingly', () => {
|
||||
const newConfig = {
|
||||
host: 'some-other-host',
|
||||
credentials: {
|
||||
keyId: 'key-id',
|
||||
keySecret: 'key-secret',
|
||||
},
|
||||
credentials: 'key-id:key-secret',
|
||||
};
|
||||
config(newConfig);
|
||||
const currentConfig = getConfig();
|
||||
|
||||
@ -2,15 +2,10 @@ import type { RequestMiddleware } from './middleware';
|
||||
import type { ResponseHandler } from './response';
|
||||
import { defaultResponseHandler } from './response';
|
||||
|
||||
export type Credentials = {
|
||||
keyId: string;
|
||||
keySecret: string;
|
||||
};
|
||||
|
||||
export type CredentialsResolver = () => Credentials;
|
||||
export type CredentialsResolver = () => string | undefined;
|
||||
|
||||
export type Config = {
|
||||
credentials?: Credentials | CredentialsResolver;
|
||||
credentials?: undefined | string | CredentialsResolver;
|
||||
host?: string;
|
||||
requestMiddleware?: RequestMiddleware;
|
||||
responseHandler?: ResponseHandler<any>;
|
||||
@ -28,29 +23,22 @@ function hasEnvVariables(): boolean {
|
||||
return (
|
||||
process &&
|
||||
process.env &&
|
||||
typeof process.env.FAL_KEY_ID !== 'undefined' &&
|
||||
typeof process.env.FAL_KEY_SECRET !== 'undefined'
|
||||
(typeof process.env.FAL_KEY !== 'undefined' ||
|
||||
(typeof process.env.FAL_KEY_ID !== 'undefined' &&
|
||||
typeof process.env.FAL_KEY_SECRET !== 'undefined'))
|
||||
);
|
||||
}
|
||||
|
||||
export const credentialsFromEnv: CredentialsResolver = () => {
|
||||
if (!hasEnvVariables()) {
|
||||
return {
|
||||
keyId: '',
|
||||
keySecret: '',
|
||||
};
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
console.warn(
|
||||
"The fal credentials are exposed in the browser's environment. " +
|
||||
"That's not recommended for production use cases."
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
keyId: process.env.FAL_KEY_ID || '',
|
||||
keySecret: process.env.FAL_KEY_SECRET || '',
|
||||
};
|
||||
if (typeof process.env.FAL_KEY !== 'undefined') {
|
||||
return process.env.FAL_KEY;
|
||||
}
|
||||
|
||||
return `${process.env.FAL_KEY_ID}:${process.env.FAL_KEY_SECRET}`;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -93,6 +81,7 @@ export function config(config: Config) {
|
||||
export function getConfig(): RequiredConfig {
|
||||
if (!configuration) {
|
||||
console.info('Using default configuration for the fal client');
|
||||
return { ...DEFAULT_CONFIG } as RequiredConfig;
|
||||
}
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@ -1,15 +1,7 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import { config, getConfig } from './config';
|
||||
import { getConfig } from './config';
|
||||
import { buildUrl } from './function';
|
||||
|
||||
config({
|
||||
host: 'gateway.alpha.fal.ai',
|
||||
credentials: {
|
||||
keyId: 'a91ff3ca-71bc-4c8c-b400-859f6cbe804d',
|
||||
keySecret: '0123456789abcdfeghijklmnopqrstuv',
|
||||
},
|
||||
});
|
||||
|
||||
describe('The function test suite', () => {
|
||||
it('should build the URL with a function UUIDv4', () => {
|
||||
const id = randomUUID();
|
||||
|
||||
@ -70,17 +70,28 @@ export async function run<Input, Output>(
|
||||
id: string,
|
||||
options: RunOptions<Input> = {}
|
||||
): Promise<Output> {
|
||||
const { credentials, requestMiddleware, responseHandler } = getConfig();
|
||||
const {
|
||||
credentials: credentialsValue,
|
||||
requestMiddleware,
|
||||
responseHandler,
|
||||
} = getConfig();
|
||||
const method = (options.method ?? 'post').toLowerCase();
|
||||
const userAgent = isBrowser() ? {} : { 'User-Agent': getUserAgent() };
|
||||
const { keyId, keySecret } =
|
||||
typeof credentials === 'function' ? credentials() : credentials;
|
||||
const credentials =
|
||||
typeof credentialsValue === 'function'
|
||||
? credentialsValue()
|
||||
: credentialsValue;
|
||||
|
||||
const { url, headers } = await requestMiddleware({
|
||||
url: buildUrl(id, options),
|
||||
});
|
||||
const authHeader =
|
||||
keyId && keySecret ? { Authorization: `Key ${keyId}:${keySecret}` } : {};
|
||||
const authHeader = credentials ? { Authorization: `Key ${credentials}` } : {};
|
||||
if (typeof window !== 'undefined' && credentials) {
|
||||
console.warn(
|
||||
"The fal credentials are exposed in the browser's environment. " +
|
||||
"That's not recommended for production use cases."
|
||||
);
|
||||
}
|
||||
const requestHeaders = {
|
||||
...authHeader,
|
||||
Accept: 'application/json',
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
export { config, getConfig } from './config';
|
||||
export type { Credentials } from './config';
|
||||
export { queue, run } from './function';
|
||||
export { withMiddleware } from './middleware';
|
||||
export type { RequestMiddleware } from './middleware';
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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;
|
||||
@ -28,10 +29,19 @@ function getHeader(request: NextApiRequest, key: string): string | undefined {
|
||||
function cleanUpHeaders(request: NextApiRequest) {
|
||||
delete request.headers['origin'];
|
||||
delete request.headers['referer'];
|
||||
// delete request.headers['transfer-encoding'];
|
||||
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
|
||||
@ -55,15 +65,16 @@ export const handler: NextApiHandler = async (request, response) => {
|
||||
|
||||
cleanUpHeaders(request);
|
||||
|
||||
const authHeader =
|
||||
FAL_KEY_ID && FAL_KEY_SECRET
|
||||
? { authorization: `Key ${FAL_KEY_ID}:${FAL_KEY_SECRET}` }
|
||||
: {};
|
||||
const falKey = getFalKey();
|
||||
if (!falKey) {
|
||||
response.status(401).send('Missing fal.ai credentials');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await fetch(targetUrl, {
|
||||
method: request.method,
|
||||
headers: {
|
||||
...authHeader,
|
||||
authorization: `Key ${falKey}`,
|
||||
accept: 'application/json',
|
||||
'content-type': 'application/json',
|
||||
'x-fal-client-proxy': '@fal-ai/serverless-nextjs',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user