diff --git a/README.md b/README.md
index 8fba584..b269785 100644
--- a/README.md
+++ b/README.md
@@ -33,18 +33,27 @@ make sure the contents of `dist/` are deployed.
## Configuring the Pollinations token
-Pollinations models that require tiered access need a token on every request. The application now
-expects the token to be provided at runtime so it is never bundled into the static assets.
+Pollinations models that require tiered access expect the token to be supplied as a request
+parameter. The demo can resolve the token at runtime (via URL parameters, meta tags, or injected
+globals) and also honours build-time environment variables when you want to bake the token into the
+bundle.
- **GitHub Pages / production** – Provide the `POLLI_TOKEN` secret in the repository (or Pages
- environment). The included Pages Function at `.github/functions/polli-token.js` exposes the token
- at runtime via `/api/polli-token`, and responses are marked as non-cacheable.
-- **Local development** – Either define `POLLI_TOKEN`/`VITE_POLLI_TOKEN` in your shell when running
- `npm run dev`, add a `` tag to `index.html`, or inject
- `window.__POLLINATIONS_TOKEN__` before the application bootstraps.
-- **Static overrides** – When a dynamic endpoint is unavailable, append a `token` query parameter
- to the page URL (e.g. `https://example.github.io/chatdemo/?token=your-secret`). The application
- will capture the token, remove it from the visible URL, and apply it to subsequent Pollinations
- requests.
-
-If the token cannot be resolved the UI remains disabled and an error is shown in the status banner.
+ environment). You can surface the token to the client by setting `window.__POLLINATIONS_TOKEN__`,
+ defining a `` tag, adding a `token=...` query
+ parameter to the published URL (e.g. `https://example.github.io/chatdemo/?token=your-secret`), or
+ injecting `POLLI_TOKEN`/`VITE_POLLI_TOKEN` during the build so the token ships with the bundle.
+ The token is removed from the visible URL after it is captured.
+- **Local development** – Define `POLLI_TOKEN`/`VITE_POLLI_TOKEN` in your shell when running
+ `npm run dev`, add a meta tag as above, or inject `window.__POLLINATIONS_TOKEN__` before the
+ application bootstraps. Build-time environment variables also work in development.
+- **Optional runtime endpoint** – If you expose the token via a custom endpoint, configure its URL
+ with `POLLI_TOKEN_ENDPOINT`/`VITE_POLLI_TOKEN_ENDPOINT` (environment variables),
+ `window.__POLLINATIONS_TOKEN_ENDPOINT__`, or a `` tag.
+ When present, the client will fetch the token from that endpoint.
+
+If the token cannot be resolved the application continues without one, allowing you to browse public
+models while gated Pollinations models remain unavailable until a token is supplied.
+
+All chat and image requests automatically include a random eight-digit `seed` parameter so they
+match Pollinations' expected request format.
diff --git a/src/main.js b/src/main.js
index 5b2a636..01cf6b9 100644
--- a/src/main.js
+++ b/src/main.js
@@ -6,6 +6,7 @@ import {
matchesModelIdentifier,
normalizeTextCatalog,
} from './model-catalog.js';
+import { generateSeed } from './seed.js';
const FALLBACK_MODELS = [
createFallbackModel('openai', 'OpenAI GPT-5 Nano (fallback)'),
@@ -443,6 +444,7 @@ async function handleChatResponse(initialResponse, model, endpoint) {
messages: state.conversation,
tools: [IMAGE_TOOL],
tool_choice: 'auto',
+ seed: generateSeed(),
},
client,
);
@@ -499,7 +501,7 @@ async function handleToolCalls(toolCalls) {
const caption = String(args.caption ?? prompt).trim() || prompt;
try {
- const { dataUrl } = await generateImageAsset(prompt, {
+ const { dataUrl, seed } = await generateImageAsset(prompt, {
width,
height,
model: args.model,
@@ -520,6 +522,7 @@ async function handleToolCalls(toolCalls) {
prompt,
width,
height,
+ seed,
}),
});
} catch (error) {
@@ -545,12 +548,14 @@ async function generateImageAsset(prompt, { width, height, model: imageModel } =
if (!client) {
throw new Error('Pollinations client is not ready.');
}
+ const seed = generateSeed();
const binary = await image(
prompt,
{
width,
height,
model: imageModel,
+ seed,
nologo: true,
private: true,
enhance: true,
@@ -559,7 +564,7 @@ async function generateImageAsset(prompt, { width, height, model: imageModel } =
);
const dataUrl = binary.toDataUrl();
resetStatusIfIdle();
- return { dataUrl };
+ return { dataUrl, seed };
} catch (error) {
console.error('Image generation failed', error);
throw error;
@@ -770,6 +775,7 @@ async function requestChatCompletion(model, endpoints) {
const attemptErrors = [];
for (const endpoint of endpoints) {
try {
+ const requestSeed = generateSeed();
const response = await chat(
{
model: model.id,
@@ -777,6 +783,7 @@ async function requestChatCompletion(model, endpoints) {
messages: state.conversation,
tools: [IMAGE_TOOL],
tool_choice: 'auto',
+ seed: requestSeed,
},
client,
);
@@ -907,11 +914,27 @@ async function initializeApp() {
els.voicePlayback.checked = false;
}
+ let tokenSource = null;
+ let tokenMessages = [];
+
try {
- const { client: polliClient, tokenSource } = await createPollinationsClient();
+ const {
+ client: polliClient,
+ tokenSource: resolvedTokenSource,
+ tokenMessages: resolvedTokenMessages,
+ } = await createPollinationsClient();
client = polliClient;
+ tokenSource = resolvedTokenSource;
+ tokenMessages = Array.isArray(resolvedTokenMessages) ? resolvedTokenMessages : [];
if (tokenSource) {
console.info('Pollinations token loaded via %s.', tokenSource);
+ } else if (tokenMessages.length) {
+ console.warn(
+ 'Proceeding without a Pollinations token. Attempts: %s',
+ tokenMessages.join('; '),
+ );
+ } else {
+ console.info('Proceeding without a Pollinations token.');
}
} catch (error) {
console.error('Failed to configure Pollinations client', error);
@@ -928,6 +951,10 @@ async function initializeApp() {
setLoading(false);
}
+ if (!tokenSource && !state.statusError) {
+ setStatus('Ready. Pollinations token not configured; only public models are available.');
+ }
+
try {
setupRecognition();
} catch (error) {
@@ -955,7 +982,7 @@ els.form.addEventListener('submit', async event => {
if (!prompt) {
throw new Error('Provide a prompt after /image');
}
- const { dataUrl } = await generateImageAsset(prompt);
+ const { dataUrl, seed } = await generateImageAsset(prompt);
addMessage({
role: 'assistant',
type: 'image',
@@ -963,6 +990,7 @@ els.form.addEventListener('submit', async event => {
alt: prompt,
caption: prompt,
});
+ console.info('Generated Pollinations image with seed %s.', seed);
resetStatusIfIdle();
} else {
await sendPrompt(raw);
diff --git a/src/pollinations-client.js b/src/pollinations-client.js
index ea4db81..d3dce8c 100644
--- a/src/pollinations-client.js
+++ b/src/pollinations-client.js
@@ -1,34 +1,49 @@
import { PolliClient } from '../Libs/pollilib/index.js';
let tokenPromise = null;
-let cachedToken = null;
-let cachedSource = null;
+let cachedResult = null;
export async function createPollinationsClient({ referrer } = {}) {
- const { token, source } = await ensureToken();
- const getToken = async () => token;
- const client = new PolliClient({
- auth: {
+ const tokenResult = await ensureToken();
+ const { token, source, messages = [], errors = [] } = tokenResult;
+ const inferredReferrer = referrer ?? inferReferrer();
+
+ const clientOptions = {};
+ if (token) {
+ clientOptions.auth = {
mode: 'token',
placement: 'query',
- getToken,
- referrer: referrer ?? inferReferrer(),
- },
- });
- return { client, tokenSource: source };
+ getToken: async () => token,
+ referrer: inferredReferrer ?? undefined,
+ };
+ } else if (inferredReferrer) {
+ clientOptions.referrer = inferredReferrer;
+ }
+
+ const client = new PolliClient(clientOptions);
+ return {
+ client,
+ tokenSource: token ? source : null,
+ tokenMessages: messages,
+ tokenErrors: errors,
+ };
}
async function ensureToken() {
- if (cachedToken) {
- return { token: cachedToken, source: cachedSource };
+ if (cachedResult) {
+ return cachedResult;
}
if (!tokenPromise) {
- tokenPromise = resolveToken();
- }
- const result = await tokenPromise;
- cachedToken = result.token;
- cachedSource = result.source;
- return result;
+ tokenPromise = resolveToken()
+ .then(result => {
+ cachedResult = result;
+ return result;
+ })
+ .finally(() => {
+ tokenPromise = null;
+ });
+ }
+ return tokenPromise;
}
async function resolveToken() {
@@ -45,9 +60,14 @@ async function resolveToken() {
try {
const result = await attempt();
if (result?.token) {
+ const messages = errors
+ .map(entry => formatError(entry.source, entry.error))
+ .filter(Boolean);
return {
token: result.token,
source: result.source ?? attempt.name ?? 'unknown',
+ errors,
+ messages,
};
}
if (result?.error) {
@@ -61,21 +81,24 @@ async function resolveToken() {
const messages = errors
.map(entry => formatError(entry.source, entry.error))
.filter(Boolean);
- const message =
- messages.length > 0
- ? `Unable to load Pollinations token. Attempts: ${messages.join('; ')}`
- : 'Unable to load Pollinations token.';
- const failure = new Error(message);
- failure.causes = errors;
- throw failure;
+ return {
+ token: null,
+ source: null,
+ errors,
+ messages,
+ };
}
async function fetchTokenFromApi() {
+ const endpoint = resolveTokenEndpoint();
+ if (!endpoint) {
+ return { token: null, source: 'api' };
+ }
if (typeof fetch !== 'function') {
return { token: null, source: 'api', error: new Error('Fetch is unavailable in this environment.') };
}
try {
- const response = await fetch('/api/polli-token', {
+ const response = await fetch(endpoint, {
method: 'GET',
headers: { Accept: 'application/json' },
cache: 'no-store',
@@ -110,7 +133,7 @@ async function fetchTokenFromApi() {
function readTokenFromUrl() {
const location = getCurrentLocation();
if (!location) {
- return { token: null, source: 'url', error: new Error('Location is unavailable.') };
+ return { token: null, source: 'url' };
}
const { url, searchParams, hashParams, rawFragments } = parseLocation(location);
@@ -143,7 +166,7 @@ function readTokenFromUrl() {
function readTokenFromMeta() {
if (typeof document === 'undefined') {
- return { token: null, source: 'meta', error: new Error('Document is unavailable.') };
+ return { token: null, source: 'meta' };
}
const meta = document.querySelector('meta[name="pollinations-token"]');
if (!meta) {
@@ -164,7 +187,7 @@ function readTokenFromMeta() {
function readTokenFromWindow() {
if (typeof window === 'undefined') {
- return { token: null, source: 'window', error: new Error('Window is unavailable.') };
+ return { token: null, source: 'window' };
}
const candidate = window.__POLLINATIONS_TOKEN__ ?? window.POLLI_TOKEN ?? null;
const token = extractTokenValue(candidate);
@@ -188,26 +211,32 @@ function readTokenFromEnv() {
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 sources = [];
+ if (importMetaEnv) {
+ sources.push([
+ importMetaEnv.VITE_POLLI_TOKEN,
+ importMetaEnv.POLLI_TOKEN,
+ importMetaEnv.VITE_POLLINATIONS_TOKEN,
+ importMetaEnv.POLLINATIONS_TOKEN,
+ ]);
+ }
+ if (processEnv) {
+ sources.push([
+ processEnv.VITE_POLLI_TOKEN,
+ processEnv.POLLI_TOKEN,
+ processEnv.VITE_POLLINATIONS_TOKEN,
+ processEnv.POLLINATIONS_TOKEN,
+ ]);
}
- 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' };
+ for (const group of sources) {
+ const token = extractTokenValue(group);
+ if (token) {
+ return { token, source: 'env' };
+ }
}
- return { token, source: 'env' };
+
+ return { token: null, source: 'env' };
}
function getCurrentLocation() {
@@ -360,19 +389,49 @@ function sanitizeUrlToken(location, url, tokenKeys) {
}
}
-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';
+function resolveTokenEndpoint() {
+ const importMetaEnv = typeof import.meta !== 'undefined' ? import.meta.env ?? undefined : undefined;
+ const processEnv = typeof process !== 'undefined' && process?.env ? process.env : undefined;
+ const envCandidates = [
+ importMetaEnv?.VITE_POLLI_TOKEN_ENDPOINT,
+ importMetaEnv?.POLLI_TOKEN_ENDPOINT,
+ importMetaEnv?.VITE_POLLINATIONS_TOKEN_ENDPOINT,
+ importMetaEnv?.POLLINATIONS_TOKEN_ENDPOINT,
+ processEnv?.VITE_POLLI_TOKEN_ENDPOINT,
+ processEnv?.POLLI_TOKEN_ENDPOINT,
+ processEnv?.VITE_POLLINATIONS_TOKEN_ENDPOINT,
+ processEnv?.POLLINATIONS_TOKEN_ENDPOINT,
+ ];
+
+ const windowCandidates = [];
+ if (typeof window !== 'undefined') {
+ windowCandidates.push(
+ window.__POLLINATIONS_TOKEN_ENDPOINT__,
+ window.POLLI_TOKEN_ENDPOINT,
+ window.POLLINATIONS_TOKEN_ENDPOINT,
+ );
+ }
+
+ const metaCandidates = [];
+ if (typeof document !== 'undefined' && document?.querySelector) {
+ const names = ['pollinations-token-endpoint', 'polli-token-endpoint'];
+ for (const name of names) {
+ const meta = document.querySelector(`meta[name="${name}"]`);
+ if (!meta) continue;
+ const content = meta.getAttribute('content');
+ if (typeof content === 'string') {
+ metaCandidates.push(content);
+ }
}
}
- return false;
+
+ const candidates = [...envCandidates, ...windowCandidates, ...metaCandidates];
+ for (const candidate of candidates) {
+ if (typeof candidate !== 'string') continue;
+ const trimmed = candidate.trim();
+ if (trimmed) return trimmed;
+ }
+ return null;
}
function extractTokenValue(value) {
@@ -380,6 +439,10 @@ function extractTokenValue(value) {
if (typeof value === 'string') {
const trimmed = value.trim();
if (!trimmed) return null;
+ const lower = trimmed.toLowerCase();
+ if (lower === 'undefined' || lower === 'null') {
+ return null;
+ }
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
try {
return extractTokenValue(JSON.parse(trimmed));
@@ -429,11 +492,9 @@ function inferReferrer() {
function resetTokenCache() {
tokenPromise = null;
- cachedToken = null;
- cachedSource = null;
+ cachedResult = null;
}
export const __testing = {
resetTokenCache,
- determineDevelopmentEnvironment,
};
diff --git a/src/seed.js b/src/seed.js
new file mode 100644
index 0000000..f48073f
--- /dev/null
+++ b/src/seed.js
@@ -0,0 +1,10 @@
+const MIN_SEED = 10_000_000;
+const MAX_SEED = 99_999_999;
+
+export function generateSeed(random = Math.random) {
+ const fn = typeof random === 'function' ? random : Math.random;
+ const value = fn();
+ const clamped = Number.isFinite(value) ? Math.max(0, Math.min(0.999999999999, value)) : 0;
+ const span = MAX_SEED - MIN_SEED + 1;
+ return Math.floor(clamped * span + MIN_SEED);
+}
diff --git a/tests/pollinations-token-env.test.mjs b/tests/pollinations-token-env.test.mjs
index 9713e1b..81ae803 100644
--- a/tests/pollinations-token-env.test.mjs
+++ b/tests/pollinations-token-env.test.mjs
@@ -1,7 +1,7 @@
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';
+export const name = 'Pollinations client resolves tokens from environment variables';
function createStubResponse(status = 404) {
return {
@@ -24,12 +24,16 @@ function createStubResponse(status = 404) {
export async function run() {
const originalFetch = globalThis.fetch;
const originalToken = process.env.POLLI_TOKEN;
+ const originalViteToken = process.env.VITE_POLLI_TOKEN;
+ const originalVitePollinationsToken = process.env.VITE_POLLINATIONS_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';
+ process.env.VITE_POLLI_TOKEN = 'undefined';
+ process.env.VITE_POLLINATIONS_TOKEN = 'null';
+ process.env.NODE_ENV = 'production';
__testing.resetTokenCache();
const { client, tokenSource } = await createPollinationsClient();
@@ -50,10 +54,18 @@ export async function run() {
process.env.POLLI_TOKEN = originalToken;
}
- if (typeof originalNodeEnv === 'undefined') {
- delete process.env.NODE_ENV;
- } else {
- process.env.NODE_ENV = originalNodeEnv;
+ const envKeys = [
+ ['POLLI_TOKEN', originalToken],
+ ['VITE_POLLI_TOKEN', originalViteToken],
+ ['VITE_POLLINATIONS_TOKEN', originalVitePollinationsToken],
+ ['NODE_ENV', originalNodeEnv],
+ ];
+ for (const [key, original] of envKeys) {
+ if (typeof original === 'undefined') {
+ delete process.env[key];
+ } else {
+ process.env[key] = original;
+ }
}
__testing.resetTokenCache();
diff --git a/tests/pollinations-token-optional.test.mjs b/tests/pollinations-token-optional.test.mjs
new file mode 100644
index 0000000..5247335
--- /dev/null
+++ b/tests/pollinations-token-optional.test.mjs
@@ -0,0 +1,154 @@
+import assert from 'node:assert/strict';
+import { createPollinationsClient, __testing } from '../src/pollinations-client.js';
+
+export const name =
+ 'Pollinations client falls back to unauthenticated access when no token endpoint is configured';
+
+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 originalWindow = globalThis.window;
+ const originalDocument = globalThis.document;
+ const originalLocation = globalThis.location;
+ const originalHistory = globalThis.history;
+ const originalNodeEnv = process.env.NODE_ENV;
+ const originalGlobalEndpoints = {
+ __POLLINATIONS_TOKEN_ENDPOINT__: globalThis.__POLLINATIONS_TOKEN_ENDPOINT__,
+ POLLI_TOKEN_ENDPOINT: globalThis.POLLI_TOKEN_ENDPOINT,
+ POLLINATIONS_TOKEN_ENDPOINT: globalThis.POLLINATIONS_TOKEN_ENDPOINT,
+ };
+ const tokenEnvKeys = [
+ 'POLLI_TOKEN',
+ 'VITE_POLLI_TOKEN',
+ 'POLLINATIONS_TOKEN',
+ 'VITE_POLLINATIONS_TOKEN',
+ ];
+ const endpointEnvKeys = [
+ 'POLLI_TOKEN_ENDPOINT',
+ 'VITE_POLLI_TOKEN_ENDPOINT',
+ 'POLLINATIONS_TOKEN_ENDPOINT',
+ 'VITE_POLLINATIONS_TOKEN_ENDPOINT',
+ ];
+ const originalEnv = Object.fromEntries(
+ [...tokenEnvKeys, ...endpointEnvKeys].map(key => [key, process.env[key]]),
+ );
+
+ try {
+ let fetchCalled = 0;
+ const fetchUrls = [];
+ globalThis.fetch = async (...args) => {
+ fetchCalled += 1;
+ fetchUrls.push(args[0]);
+ return createStubResponse(404);
+ };
+ delete globalThis.window;
+ delete globalThis.document;
+ delete globalThis.location;
+ delete globalThis.history;
+ for (const key of tokenEnvKeys) {
+ delete process.env[key];
+ }
+ for (const key of endpointEnvKeys) {
+ delete process.env[key];
+ }
+ process.env.POLLI_TOKEN = 'undefined';
+ process.env.VITE_POLLI_TOKEN = 'null';
+ delete process.env.NODE_ENV;
+ delete globalThis.__POLLINATIONS_TOKEN_ENDPOINT__;
+ delete globalThis.POLLI_TOKEN_ENDPOINT;
+ delete globalThis.POLLINATIONS_TOKEN_ENDPOINT;
+
+ __testing.resetTokenCache();
+
+ const { client, tokenSource, tokenMessages } = await createPollinationsClient();
+
+ assert.equal(tokenSource, null);
+ assert.equal(client.authMode, 'none');
+ assert.ok(Array.isArray(tokenMessages));
+ assert.equal(tokenMessages.length, 0, `Unexpected messages: ${tokenMessages.join('; ')}`);
+ if (fetchCalled !== 0) {
+ throw new Error(`Unexpected token fetch attempts: ${fetchUrls.join(', ')}`);
+ }
+ } finally {
+ if (originalFetch) {
+ globalThis.fetch = originalFetch;
+ } else {
+ delete globalThis.fetch;
+ }
+
+ if (typeof originalWindow === 'undefined') {
+ delete globalThis.window;
+ } else {
+ globalThis.window = originalWindow;
+ }
+
+ if (typeof originalDocument === 'undefined') {
+ delete globalThis.document;
+ } else {
+ globalThis.document = originalDocument;
+ }
+
+ if (typeof originalLocation === 'undefined') {
+ delete globalThis.location;
+ } else {
+ globalThis.location = originalLocation;
+ }
+
+ if (typeof originalHistory === 'undefined') {
+ delete globalThis.history;
+ } else {
+ globalThis.history = originalHistory;
+ }
+
+ for (const key of [...tokenEnvKeys, ...endpointEnvKeys]) {
+ if (typeof originalEnv[key] === 'undefined') {
+ delete process.env[key];
+ } else {
+ process.env[key] = originalEnv[key];
+ }
+ }
+
+ if (typeof originalNodeEnv === 'undefined') {
+ delete process.env.NODE_ENV;
+ } else {
+ process.env.NODE_ENV = originalNodeEnv;
+ }
+
+ if (typeof originalGlobalEndpoints.__POLLINATIONS_TOKEN_ENDPOINT__ === 'undefined') {
+ delete globalThis.__POLLINATIONS_TOKEN_ENDPOINT__;
+ } else {
+ globalThis.__POLLINATIONS_TOKEN_ENDPOINT__ =
+ originalGlobalEndpoints.__POLLINATIONS_TOKEN_ENDPOINT__;
+ }
+ if (typeof originalGlobalEndpoints.POLLI_TOKEN_ENDPOINT === 'undefined') {
+ delete globalThis.POLLI_TOKEN_ENDPOINT;
+ } else {
+ globalThis.POLLI_TOKEN_ENDPOINT = originalGlobalEndpoints.POLLI_TOKEN_ENDPOINT;
+ }
+ if (typeof originalGlobalEndpoints.POLLINATIONS_TOKEN_ENDPOINT === 'undefined') {
+ delete globalThis.POLLINATIONS_TOKEN_ENDPOINT;
+ } else {
+ globalThis.POLLINATIONS_TOKEN_ENDPOINT =
+ originalGlobalEndpoints.POLLINATIONS_TOKEN_ENDPOINT;
+ }
+
+ __testing.resetTokenCache();
+ }
+}
diff --git a/tests/seed-generator.test.mjs b/tests/seed-generator.test.mjs
new file mode 100644
index 0000000..139bc96
--- /dev/null
+++ b/tests/seed-generator.test.mjs
@@ -0,0 +1,23 @@
+import assert from 'node:assert/strict';
+import { generateSeed } from '../src/seed.js';
+
+export const name = 'Seed generator produces eight-digit integers';
+
+export async function run() {
+ const minimum = generateSeed(() => 0);
+ const maximum = generateSeed(() => 0.999999999999);
+ const clampHigh = generateSeed(() => 1.5);
+ const clampLow = generateSeed(() => -0.5);
+ const mid = generateSeed(() => 0.42);
+
+ for (const value of [minimum, maximum, clampHigh, clampLow, mid]) {
+ assert(Number.isInteger(value), `Seed should be an integer (received ${value})`);
+ assert(value >= 10_000_000 && value <= 99_999_999, `Seed ${value} must be eight digits`);
+ assert.equal(String(value).length, 8, `Seed ${value} should contain exactly eight digits`);
+ }
+
+ assert.equal(minimum, 10_000_000);
+ assert.equal(maximum, 99_999_999);
+ assert.equal(clampHigh, 99_999_999);
+ assert.equal(clampLow, 10_000_000);
+}