diff --git a/.github/functions/polli-token.js b/.github/functions/polli-token.js index 6a8e98b..8321b8f 100644 --- a/.github/functions/polli-token.js +++ b/.github/functions/polli-token.js @@ -1,5 +1,25 @@ +function readTokenFromEnvironment(context) { + const envSources = []; + if (context?.env) envSources.push(context.env); + if (typeof process !== 'undefined' && process?.env) envSources.push(process.env); + + for (const env of envSources) { + const candidate = + env?.POLLI_TOKEN ?? + env?.VITE_POLLI_TOKEN ?? + env?.POLLINATIONS_TOKEN ?? + env?.VITE_POLLINATIONS_TOKEN ?? + null; + if (candidate != null) { + const value = String(candidate).trim(); + if (value) return value; + } + } + return null; +} + export async function onRequest(context) { - const token = context?.env?.POLLI_TOKEN ?? context?.env?.VITE_POLLI_TOKEN ?? null; + const token = readTokenFromEnvironment(context); if (!token) { return new Response(JSON.stringify({ error: 'Pollinations token is not configured.' }), { status: 404, diff --git a/src/pollinations-client.js b/src/pollinations-client.js index 3050dc1..2fb0975 100644 --- a/src/pollinations-client.js +++ b/src/pollinations-client.js @@ -145,23 +145,46 @@ function readTokenFromWindow() { } function readTokenFromEnv() { - if (!import.meta?.env?.DEV) { + const importMetaEnv = typeof import.meta !== 'undefined' ? import.meta.env ?? undefined : undefined; + const processEnv = typeof process !== 'undefined' && process?.env ? process.env : undefined; + + const isDev = determineDevelopmentEnvironment(importMetaEnv, processEnv); + if (!isDev) { return { token: null, source: 'env' }; } - const env = import.meta.env ?? {}; - const candidate = - env.VITE_POLLI_TOKEN ?? - env.POLLI_TOKEN ?? - env.VITE_POLLINATIONS_TOKEN ?? - env.POLLINATIONS_TOKEN ?? - null; - const token = extractTokenValue(candidate); + + const token = extractTokenValue([ + importMetaEnv?.VITE_POLLI_TOKEN, + importMetaEnv?.POLLI_TOKEN, + importMetaEnv?.VITE_POLLINATIONS_TOKEN, + importMetaEnv?.POLLINATIONS_TOKEN, + processEnv?.VITE_POLLI_TOKEN, + processEnv?.POLLI_TOKEN, + processEnv?.VITE_POLLINATIONS_TOKEN, + processEnv?.POLLINATIONS_TOKEN, + ]); + if (!token) { return { token: null, source: 'env' }; } return { token, source: 'env' }; } +function determineDevelopmentEnvironment(importMetaEnv, processEnv) { + if (importMetaEnv && typeof importMetaEnv.DEV !== 'undefined') { + return !!importMetaEnv.DEV; + } + if (processEnv) { + if (typeof processEnv.VITE_DEV_SERVER_URL !== 'undefined') { + return true; + } + if (typeof processEnv.NODE_ENV !== 'undefined') { + return processEnv.NODE_ENV !== 'production'; + } + } + return false; +} + function extractTokenValue(value) { if (value == null) return null; if (typeof value === 'string') { @@ -213,3 +236,14 @@ function inferReferrer() { } return null; } + +function resetTokenCache() { + tokenPromise = null; + cachedToken = null; + cachedSource = null; +} + +export const __testing = { + resetTokenCache, + determineDevelopmentEnvironment, +}; diff --git a/tests/polli-token-function-env.test.mjs b/tests/polli-token-function-env.test.mjs new file mode 100644 index 0000000..f9dc8b8 --- /dev/null +++ b/tests/polli-token-function-env.test.mjs @@ -0,0 +1,32 @@ +import assert from 'node:assert/strict'; +import { onRequest } from '../.github/functions/polli-token.js'; + +export const name = 'Pollinations token function reads secrets from multiple environments'; + +export async function run() { + const originalToken = process.env.POLLI_TOKEN; + const originalViteToken = process.env.VITE_POLLI_TOKEN; + + try { + delete process.env.VITE_POLLI_TOKEN; + process.env.POLLI_TOKEN = 'function-process-token'; + + const response = await onRequest({ env: {} }); + assert.equal(response.status, 200); + + const payload = await response.json(); + assert.deepEqual(payload, { token: 'function-process-token' }); + } finally { + if (typeof originalToken === 'undefined') { + delete process.env.POLLI_TOKEN; + } else { + process.env.POLLI_TOKEN = originalToken; + } + + if (typeof originalViteToken === 'undefined') { + delete process.env.VITE_POLLI_TOKEN; + } else { + process.env.VITE_POLLI_TOKEN = originalViteToken; + } + } +} diff --git a/tests/pollinations-token-env.test.mjs b/tests/pollinations-token-env.test.mjs new file mode 100644 index 0000000..9713e1b --- /dev/null +++ b/tests/pollinations-token-env.test.mjs @@ -0,0 +1,61 @@ +import assert from 'node:assert/strict'; +import { createPollinationsClient, __testing } from '../src/pollinations-client.js'; + +export const name = 'Pollinations client resolves tokens from development environment variables'; + +function createStubResponse(status = 404) { + return { + status, + ok: status >= 200 && status < 300, + headers: { + get() { + return null; + }, + }, + async json() { + return {}; + }, + async text() { + return ''; + }, + }; +} + +export async function run() { + const originalFetch = globalThis.fetch; + const originalToken = process.env.POLLI_TOKEN; + const originalNodeEnv = process.env.NODE_ENV; + + try { + globalThis.fetch = async () => createStubResponse(404); + process.env.POLLI_TOKEN = 'process-env-token'; + process.env.NODE_ENV = 'development'; + __testing.resetTokenCache(); + + const { client, tokenSource } = await createPollinationsClient(); + assert.equal(tokenSource, 'env'); + + const token = await client._auth.getToken(); + assert.equal(token, 'process-env-token'); + } finally { + if (originalFetch) { + globalThis.fetch = originalFetch; + } else { + delete globalThis.fetch; + } + + if (typeof originalToken === 'undefined') { + delete process.env.POLLI_TOKEN; + } else { + process.env.POLLI_TOKEN = originalToken; + } + + if (typeof originalNodeEnv === 'undefined') { + delete process.env.NODE_ENV; + } else { + process.env.NODE_ENV = originalNodeEnv; + } + + __testing.resetTokenCache(); + } +} diff --git a/vite.config.mjs b/vite.config.mjs index 29554a7..2ee533b 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -3,6 +3,7 @@ import { defineConfig } from 'vite'; export default defineConfig({ root: '.', base: './', + envPrefix: ['VITE_', 'POLLI_', 'POLLINATIONS_'], build: { outDir: 'dist', emptyOutDir: true,