From 5918df1f573fc01c845423bfe1b0c35a60f9d5ab Mon Sep 17 00:00:00 2001 From: Nathan GNANKADJA Date: Sat, 13 Jun 2026 07:48:51 +0100 Subject: [PATCH] refactor: [#XX] drop key-prefix environment, derive env from baseUrl --- README.md | 25 +++++++++++++++++++++---- examples/nextjs-server-component.tsx | 2 +- examples/script.ts | 4 ++-- src/client.ts | 24 ++++++++---------------- test/client.test.ts | 8 +------- test/helpers.ts | 2 +- 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 1bed7b4..d7fc9d3 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ npm install @xedo/sdk ```ts import { Xedo } from '@xedo/sdk'; -const xedo = new Xedo({ apiKey: process.env.XEDO_API_KEY! }); // xdk_live_… or xdk_test_… +const xedo = new Xedo({ apiKey: process.env.XEDO_API_KEY! }); // opaque xdk_… key await xedo.ping(); // validate the key const { data, total } = await xedo.products.list({ perPage: 20 }); @@ -23,7 +23,7 @@ const { data, total } = await xedo.products.list({ perPage: 20 }); ## ⚠️ Server-side only -The SDK sends your `xdk_live_…` key as a Bearer token. **Never import it into a +The SDK sends your `xdk_…` key as a Bearer token. **Never import it into a browser bundle** — your key would be exposed to anyone. Call it from a server: Next.js Server Components / Route Handlers, Express, workers, scripts. The constructor throws if it detects a browser environment (override only if you @@ -34,16 +34,33 @@ truly know what you are doing via `dangerouslyAllowBrowser`). ```ts const xedo = new Xedo({ apiKey: process.env.XEDO_API_KEY!, - baseUrl: 'https://systems.xedoapp.com/marketplace', // default + baseUrl: 'https://systems.xedoapp.com/marketplace', // Production (default) maxRetries: 4, // auto-retry on 429 (default) timeoutMs: 30000, // per-request timeout fetch: customFetch, // optional injection (tests, edge) }); -xedo.environment; // 'test' | 'live' | 'unknown' (from key prefix) xedo.lastRateLimit; // { limit, remaining, reset } from the last call ``` +### Production vs Sandbox + +The **`baseUrl`** selects the environment: + +| Environment | `baseUrl` | +|---|---| +| Production (default) | `https://systems.xedoapp.com/marketplace` | +| Sandbox | `https://systems.xedotestnet.space/marketplace` | + +```ts +const sandbox = new Xedo({ + apiKey: process.env.XEDO_API_KEY!, + baseUrl: 'https://systems.xedotestnet.space/marketplace', +}); +``` + +See the [Environments guide](https://developers.xedoapp.com/introduction/environments) for details. + ## Resources | Resource | Methods | diff --git a/examples/nextjs-server-component.tsx b/examples/nextjs-server-component.tsx index 5805606..295e9ad 100644 --- a/examples/nextjs-server-component.tsx +++ b/examples/nextjs-server-component.tsx @@ -1,6 +1,6 @@ /** * Next.js (App Router) Server Component. The Xedo client is instantiated and - * used entirely on the server, so the xdk_live_… key never reaches the browser. + * used entirely on the server, so the xdk_… key never reaches the browser. */ import { Xedo } from '@xedo/sdk'; diff --git a/examples/script.ts b/examples/script.ts index 81a3b59..47ec042 100644 --- a/examples/script.ts +++ b/examples/script.ts @@ -1,5 +1,5 @@ /** - * Plain Node script. Run with: XEDO_API_KEY=xdk_test_… npx tsx examples/script.ts + * Plain Node script. Run with: XEDO_API_KEY=xdk_… npx tsx examples/script.ts */ import { Xedo, XedoNotFoundError } from '@xedo/sdk'; @@ -8,7 +8,7 @@ const xedo = new Xedo({ apiKey: process.env.XEDO_API_KEY! }); async function main() { // 1. Validate the key. const ping = await xedo.ping(); - console.log('marketplace', ping.marketplaceId, '(', xedo.environment, ')'); + console.log('marketplace', ping.marketplaceId); // 2. Inspect the merchant configuration and delivery areas. const profile = await xedo.marketplace.retrieve(); diff --git a/src/client.ts b/src/client.ts index c47a594..ab537b2 100644 --- a/src/client.ts +++ b/src/client.ts @@ -13,9 +13,13 @@ const DEFAULT_MAX_RETRIES = 4; const DEFAULT_TIMEOUT_MS = 30_000; export interface XedoOptions { - /** Developer API key: `xdk_live_…` or `xdk_test_…`. */ + /** Developer API key: an opaque `xdk_…` string. */ apiKey: string; - /** Override the API base URL. Defaults to the production marketplace URL. */ + /** + * Override the API base URL. Defaults to the production marketplace URL. + * The base URL alone selects Production vs Sandbox — the key is opaque and + * carries no environment. See https://developers.xedoapp.com/introduction/environments + */ baseUrl?: string; /** Max automatic retries on `429`. Defaults to 4. */ maxRetries?: number; @@ -25,7 +29,7 @@ export interface XedoOptions { fetch?: FetchLike; /** * Escape hatch to run in a browser. Strongly discouraged: your - * `xdk_live_…` key would be exposed in the client bundle. + * `xdk_…` key would be exposed in the client bundle. */ dangerouslyAllowBrowser?: boolean; } @@ -47,7 +51,6 @@ export class Xedo { readonly deliveryAreas: DeliveryAreas; readonly marketplace: Marketplace; - private readonly apiKey: string; private readonly transport: Transport; constructor(options: XedoOptions) { @@ -67,7 +70,7 @@ export class Xedo { status: 0, message: 'The Xedo SDK is server-side only: running it in a browser would expose your ' + - 'xdk_live_… key in the client bundle. Call it from a server (Next.js Server ' + + 'xdk_… key in the client bundle. Call it from a server (Next.js Server ' + 'Component / Route Handler, Express, a worker, …). See the README on authentication.', }); } @@ -82,7 +85,6 @@ export class Xedo { }); } - this.apiKey = options.apiKey; this.transport = new Transport({ apiKey: options.apiKey, baseUrl: options.baseUrl ?? DEFAULT_BASE_URL, @@ -99,16 +101,6 @@ export class Xedo { this.marketplace = new Marketplace(this.transport); } - /** - * The environment inferred from the key prefix. The rest of the key is - * treated as opaque. - */ - get environment(): 'test' | 'live' | 'unknown' { - if (this.apiKey.startsWith('xdk_live_')) return 'live'; - if (this.apiKey.startsWith('xdk_test_')) return 'test'; - return 'unknown'; - } - /** Rate-limit headers from the most recent response, for monitoring. */ get lastRateLimit(): RateLimitInfo | null { return this.transport.lastRateLimit; diff --git a/test/client.test.ts b/test/client.test.ts index f37b546..08511fe 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -10,12 +10,6 @@ describe('Xedo client', () => { expect(() => new Xedo({})).toThrow(/apiKey/i); }); - it('infers the environment from the key prefix', () => { - expect(makeClient({ apiKey: 'xdk_live_x' }).environment).toBe('live'); - expect(makeClient({ apiKey: 'xdk_test_x' }).environment).toBe('test'); - expect(makeClient({ apiKey: 'whatever' }).environment).toBe('unknown'); - }); - it('sends the Bearer auth header and unwraps `data` on ping', async () => { let authHeader: string | null = null; server.use( @@ -28,7 +22,7 @@ describe('Xedo client', () => { const xedo = makeClient(); const result = await xedo.ping(); - expect(authHeader).toBe('Bearer xdk_test_abc123'); + expect(authHeader).toBe('Bearer xdk_abc123'); expect(result).toEqual({ marketplaceId: 42, timestamp: '2026-05-28T10:15:00.000Z' }); }); diff --git a/test/helpers.ts b/test/helpers.ts index d94e1c2..f63e0a4 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -3,7 +3,7 @@ import { BASE } from './server'; export function makeClient(overrides: Partial = {}): Xedo { return new Xedo({ - apiKey: 'xdk_test_abc123', + apiKey: 'xdk_abc123', baseUrl: BASE, maxRetries: 2, timeoutMs: 1000,