@pixelume/sdk 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CredentialsPolicy, FetchLike } from '../http/types.js';\nimport { PixelumeConfigError } from './errors.js';\nimport type { PixelumeClientOptions } from './types.js';\n\nexport const DEFAULT_API_URL = 'https://api.pixelume.com.br';\nexport const DEFAULT_TIMEOUT_MS = 10_000;\nconst MAX_TIMEOUT_MS = 2_147_483_647; // setTimeout limit\nconst GAME_ID_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/u;\nconst CREDENTIALS: readonly CredentialsPolicy[] = ['omit', 'same-origin', 'include'];\n\nexport interface ResolvedConfig {\n apiUrl: string;\n gameId: string | null;\n timeoutMs: number;\n credentials: CredentialsPolicy;\n fetch: FetchLike;\n}\n\nexport function resolveConfig(options: PixelumeClientOptions): ResolvedConfig {\n return {\n apiUrl: normalizeApiUrl(options.apiUrl ?? DEFAULT_API_URL, options.allowInsecureHttp === true),\n gameId: options.gameId === undefined ? null : validateGameId(options.gameId),\n timeoutMs:\n options.timeoutMs === undefined ? DEFAULT_TIMEOUT_MS : validateTimeout(options.timeoutMs),\n credentials: validateCredentials(options.credentials ?? 'omit'),\n fetch: resolveFetch(options.fetch),\n };\n}\n\n/**\n * Accepts `https:` URLs, plus `http:` for loopback hosts (`localhost`, `*.localhost`, `127.0.0.0/8`,\n * `[::1]`). Other `http:` hosts (e.g. a LAN IP while testing on a phone) need `allowInsecureHttp`.\n */\nexport function normalizeApiUrl(value: string, allowInsecureHttp: boolean): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new PixelumeConfigError(\n 'apiUrl must be an absolute URL, e.g. https://api.pixelume.com.br',\n );\n }\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\n throw new PixelumeConfigError('apiUrl must use https: (or http: for local development)');\n }\n if (url.protocol === 'http:' && !allowInsecureHttp && !isLoopbackHost(url.hostname)) {\n throw new PixelumeConfigError(\n 'apiUrl must use https:. Plain http: is only accepted for localhost unless allowInsecureHttp is set',\n );\n }\n if (url.username || url.password) {\n throw new PixelumeConfigError('apiUrl must not contain credentials');\n }\n if (url.search || url.hash) {\n throw new PixelumeConfigError('apiUrl must not contain a query string or fragment');\n }\n return `${url.origin}${url.pathname.replace(/\\/+$/u, '')}`;\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n return (\n hostname === 'localhost' ||\n hostname.endsWith('.localhost') ||\n hostname === '[::1]' ||\n /^127(?:\\.\\d{1,3}){3}$/u.test(hostname)\n );\n}\n\n/**\n * Game identifiers are public, non-secret slugs (e.g. `rail-puzzle`): lowercase letters, digits\n * and inner hyphens, 1–64 characters. They identify a game, they never authorize anything.\n */\nexport function validateGameId(value: string): string {\n if (typeof value !== 'string' || !GAME_ID_PATTERN.test(value)) {\n throw new PixelumeConfigError(\n 'gameId must be a lowercase slug (a-z, 0-9 and inner hyphens, up to 64 characters)',\n );\n }\n return value;\n}\n\n/** For feature modules that need a game context (future phases). */\nexport function requireGameId(config: Pick<ResolvedConfig, 'gameId'>, feature: string): string {\n if (config.gameId === null) {\n throw new PixelumeConfigError(`${feature} requires the client to be created with a gameId`);\n }\n return config.gameId;\n}\n\nexport function validateTimeout(value: number): number {\n if (!Number.isInteger(value) || value <= 0 || value > MAX_TIMEOUT_MS) {\n throw new PixelumeConfigError('timeoutMs must be a positive integer number of milliseconds');\n }\n return value;\n}\n\nfunction validateCredentials(value: CredentialsPolicy): CredentialsPolicy {\n if (!CREDENTIALS.includes(value)) {\n throw new PixelumeConfigError(`credentials must be one of: ${CREDENTIALS.join(', ')}`);\n }\n return value;\n}\n\nfunction resolveFetch(custom: FetchLike | undefined): FetchLike {\n if (custom !== undefined) {\n if (typeof custom !== 'function') throw new PixelumeConfigError('fetch must be a function');\n return custom;\n }\n if (typeof globalThis.fetch !== 'function') {\n throw new PixelumeConfigError('No global fetch available; pass the fetch option');\n }\n // Looked up on each call so polyfills or test doubles installed later are honoured.\n return (input, init) => globalThis.fetch(input, init);\n}\n"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,CAAC,MAAM,eAAe,GAAG,6BAA6B,CAAC;AAC7D,MAAM,CAAC,MAAM,gBAAgB,GAAG,8BAA8B,CAAC;AAC/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AACzC,MAAM,cAAc,GAAG,aAAa,CAAC,CAAC,mBAAmB;AACzD,MAAM,eAAe,GAAG,yCAAyC,CAAC;AAClE,MAAM,iBAAiB,GAAG,0BAA0B,CAAC;AACrD,MAAM,WAAW,GAAiC,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;AAgBrF,MAAM,UAAU,aAAa,CAAC,OAA8B;IAC1D,MAAM,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,KAAK,IAAI,CAAC;IAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACpF,OAAO;QACL,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,MAAM,IAAI,eAAe,EAAE,iBAAiB,CAAC;QAC7E,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,OAAO,IAAI,gBAAgB,EAAE,SAAS,EAAE,iBAAiB,CAAC;QAC3F,MAAM;QACN,QAAQ,EAAE,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,QAAQ,CAAC;QACtF,WAAW,EAAE,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,CAAC;QAC3D,cAAc,EAAE,eAAe,CAAC,gBAAgB,EAAE,OAAO,CAAC,cAAc,IAAI,IAAI,CAAC;QACjF,iBAAiB;QACjB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,SAAS,EACP,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,SAAS,CAAC;QAC3F,WAAW,EAAE,mBAAmB,CAAC,OAAO,CAAC,WAAW,IAAI,MAAM,CAAC;QAC/D,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC;KACnC,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CACzB,OAA8B,EAC9B,iBAA0B;IAE1B,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACzE,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;AAChF,CAAC;AAED,SAAS,eAAe,CAAC,IAAY,EAAE,KAAc;IACnD,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,MAAM,IAAI,mBAAmB,CAAC,GAAG,IAAI,oBAAoB,CAAC,CAAC;IAC3F,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,kBAAkB,CAAC,OAAyC;IACnE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,CAAY,CAAC;IAChE,MAAM,OAAO,GACX,OAAO,EAAE,UAAU,EAAE;QACrB,CAAC,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,IAAI,MAAM,IAAI,QAAQ;YACtE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;YACvB,CAAC,CAAC,IAAI,CAAC,CAAC;IACZ,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAC7B,OAAO,GAAG,GAAG,CAAC,MAAM,iBAAiB,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,iBAA0B;IACvE,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,mBAAmB,CAC3B,kEAAkE,CACnE,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC1D,MAAM,IAAI,mBAAmB,CAAC,yDAAyD,CAAC,CAAC;IAC3F,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,iBAAiB,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpF,MAAM,IAAI,mBAAmB,CAC3B,oGAAoG,CACrG,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;QACjC,MAAM,IAAI,mBAAmB,CAAC,qCAAqC,CAAC,CAAC;IACvE,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QAC3B,MAAM,IAAI,mBAAmB,CAAC,oDAAoD,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC;AAC7D,CAAC;AAED,SAAS,eAAe,CAAC,KAAa,EAAE,IAAY,EAAE,iBAA0B;IAC9E,MAAM,UAAU,GAAG,eAAe,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IAChC,IAAI,GAAG,CAAC,QAAQ,KAAK,GAAG,EAAE,CAAC;QACzB,MAAM,IAAI,mBAAmB,CAAC,GAAG,IAAI,mCAAmC,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAa,EAAE,iBAA0B;IAC5E,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,mBAAmB,CAAC,2DAA2D,CAAC,CAAC;IAC7F,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC1D,MAAM,IAAI,mBAAmB,CAAC,8DAA8D,CAAC,CAAC;IAChG,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,iBAAiB,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpF,MAAM,IAAI,mBAAmB,CAAC,+CAA+C,CAAC,CAAC;IACjF,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QAC7C,MAAM,IAAI,mBAAmB,CAAC,wDAAwD,CAAC,CAAC;IAC1F,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,CAAC,EAAE,CAAC;QAClE,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,mBAAmB,CAC3B,8DAA8D,GAAG,GAAG,CACrE,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC;AAED,SAAS,cAAc,CAAC,QAAgB;IACtC,OAAO,CACL,QAAQ,KAAK,WAAW;QACxB,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC/B,QAAQ,KAAK,OAAO;QACpB,wBAAwB,CAAC,IAAI,CAAC,QAAQ,CAAC,CACxC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,mBAAmB,CAC3B,mFAAmF,CACpF,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,mBAAmB,CAC3B,2EAA2E,CAC5E,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,aAAa,CAAC,MAAsC,EAAE,OAAe;IACnF,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QAC3B,MAAM,IAAI,mBAAmB,CAAC,GAAG,OAAO,kDAAkD,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,GAAG,cAAc,EAAE,CAAC;QACrE,MAAM,IAAI,mBAAmB,CAAC,6DAA6D,CAAC,CAAC;IAC/F,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAwB;IACnD,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,mBAAmB,CAAC,+BAA+B,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,YAAY,CAAC,MAA6B;IACjD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,IAAI,OAAO,MAAM,KAAK,UAAU;YAAE,MAAM,IAAI,mBAAmB,CAAC,0BAA0B,CAAC,CAAC;QAC5F,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,IAAI,OAAO,UAAU,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QAC3C,MAAM,IAAI,mBAAmB,CAAC,kDAAkD,CAAC,CAAC;IACpF,CAAC;IACD,oFAAoF;IACpF,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AACxD,CAAC","sourcesContent":["import type { CredentialsPolicy, FetchLike } from '../http/types.js';\nimport { PixelumeConfigError } from './errors.js';\nimport type { PixelumeClientOptions } from './types.js';\n\nexport const DEFAULT_API_URL = 'https://api.pixelume.com.br';\nexport const DEFAULT_AUTH_URL = 'https://auth.pixelume.com.br';\nexport const DEFAULT_TIMEOUT_MS = 10_000;\nconst MAX_TIMEOUT_MS = 2_147_483_647; // setTimeout limit\nconst GAME_ID_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/u;\nconst CLIENT_ID_PATTERN = /^[A-Za-z0-9._-]{1,128}$/u;\nconst CREDENTIALS: readonly CredentialsPolicy[] = ['omit', 'same-origin', 'include'];\n\nexport interface ResolvedConfig {\n apiUrl: string;\n gameId: string | null;\n clientId: string | null;\n redirectUri: string | null;\n authUrl: string;\n restoreSession: boolean;\n allowInsecureHttp: boolean;\n browser: PixelumeClientOptions['browser'];\n timeoutMs: number;\n credentials: CredentialsPolicy;\n fetch: FetchLike;\n}\n\nexport function resolveConfig(options: PixelumeClientOptions): ResolvedConfig {\n const allowInsecureHttp = options.allowInsecureHttp === true;\n const gameId = options.gameId === undefined ? null : validateGameId(options.gameId);\n return {\n apiUrl: normalizeApiUrl(options.apiUrl ?? DEFAULT_API_URL, allowInsecureHttp),\n authUrl: normalizeOrigin(options.authUrl ?? DEFAULT_AUTH_URL, 'authUrl', allowInsecureHttp),\n gameId,\n clientId: options.clientId === undefined ? gameId : validateClientId(options.clientId),\n redirectUri: resolveRedirectUri(options, allowInsecureHttp),\n restoreSession: validateBoolean('restoreSession', options.restoreSession ?? true),\n allowInsecureHttp,\n browser: options.browser,\n timeoutMs:\n options.timeoutMs === undefined ? DEFAULT_TIMEOUT_MS : validateTimeout(options.timeoutMs),\n credentials: validateCredentials(options.credentials ?? 'omit'),\n fetch: resolveFetch(options.fetch),\n };\n}\n\nfunction resolveRedirectUri(\n options: PixelumeClientOptions,\n allowInsecureHttp: boolean,\n): string | null {\n const value = options.redirectUri ?? defaultRedirectUri(options.browser);\n return value === null ? null : normalizeRedirectUri(value, allowInsecureHttp);\n}\n\nfunction validateBoolean(name: string, value: boolean): boolean {\n if (typeof value !== 'boolean') throw new PixelumeConfigError(`${name} must be a boolean`);\n return value;\n}\n\nfunction defaultRedirectUri(browser: PixelumeClientOptions['browser']): string | null {\n const location = Reflect.get(globalThis, 'location') as unknown;\n const current =\n browser?.currentUrl() ??\n (typeof location === 'object' && location !== null && 'href' in location\n ? String(location.href)\n : null);\n if (!current) return null;\n const url = new URL(current);\n return `${url.origin}/oauth/callback`;\n}\n\n/**\n * Accepts `https:` URLs, plus `http:` for loopback hosts (`localhost`, `*.localhost`, `127.0.0.0/8`,\n * `[::1]`). Other `http:` hosts (e.g. a LAN IP while testing on a phone) need `allowInsecureHttp`.\n */\nexport function normalizeApiUrl(value: string, allowInsecureHttp: boolean): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new PixelumeConfigError(\n 'apiUrl must be an absolute URL, e.g. https://api.pixelume.com.br',\n );\n }\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\n throw new PixelumeConfigError('apiUrl must use https: (or http: for local development)');\n }\n if (url.protocol === 'http:' && !allowInsecureHttp && !isLoopbackHost(url.hostname)) {\n throw new PixelumeConfigError(\n 'apiUrl must use https:. Plain http: is only accepted for localhost unless allowInsecureHttp is set',\n );\n }\n if (url.username || url.password) {\n throw new PixelumeConfigError('apiUrl must not contain credentials');\n }\n if (url.search || url.hash) {\n throw new PixelumeConfigError('apiUrl must not contain a query string or fragment');\n }\n return `${url.origin}${url.pathname.replace(/\\/+$/u, '')}`;\n}\n\nfunction normalizeOrigin(value: string, name: string, allowInsecureHttp: boolean): string {\n const normalized = normalizeApiUrl(value, allowInsecureHttp);\n const url = new URL(normalized);\n if (url.pathname !== '/') {\n throw new PixelumeConfigError(`${name} must be an origin without a path`);\n }\n return url.origin;\n}\n\nexport function normalizeRedirectUri(value: string, allowInsecureHttp: boolean): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new PixelumeConfigError('redirectUri must be an absolute URL registered by the API');\n }\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\n throw new PixelumeConfigError('redirectUri must use https: (or http: for local development)');\n }\n if (url.protocol === 'http:' && !allowInsecureHttp && !isLoopbackHost(url.hostname)) {\n throw new PixelumeConfigError('redirectUri must use https: outside localhost');\n }\n if (url.username || url.password || url.hash) {\n throw new PixelumeConfigError('redirectUri must not contain credentials or a fragment');\n }\n for (const key of ['code', 'state', 'error', 'error_description']) {\n if (url.searchParams.has(key)) {\n throw new PixelumeConfigError(\n `redirectUri must not contain the reserved query parameter \"${key}\"`,\n );\n }\n }\n return url.toString();\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n return (\n hostname === 'localhost' ||\n hostname.endsWith('.localhost') ||\n hostname === '[::1]' ||\n /^127(?:\\.\\d{1,3}){3}$/u.test(hostname)\n );\n}\n\n/**\n * Game identifiers are public, non-secret slugs (e.g. `rail-puzzle`): lowercase letters, digits\n * and inner hyphens, 1–64 characters. They identify a game, they never authorize anything.\n */\nexport function validateGameId(value: string): string {\n if (typeof value !== 'string' || !GAME_ID_PATTERN.test(value)) {\n throw new PixelumeConfigError(\n 'gameId must be a lowercase slug (a-z, 0-9 and inner hyphens, up to 64 characters)',\n );\n }\n return value;\n}\n\nexport function validateClientId(value: string): string {\n if (typeof value !== 'string' || !CLIENT_ID_PATTERN.test(value)) {\n throw new PixelumeConfigError(\n 'clientId must contain 1-128 letters, digits, dots, underscores or hyphens',\n );\n }\n return value;\n}\n\n/** For feature modules that need a game context (future phases). */\nexport function requireGameId(config: Pick<ResolvedConfig, 'gameId'>, feature: string): string {\n if (config.gameId === null) {\n throw new PixelumeConfigError(`${feature} requires the client to be created with a gameId`);\n }\n return config.gameId;\n}\n\nexport function validateTimeout(value: number): number {\n if (!Number.isInteger(value) || value <= 0 || value > MAX_TIMEOUT_MS) {\n throw new PixelumeConfigError('timeoutMs must be a positive integer number of milliseconds');\n }\n return value;\n}\n\nfunction validateCredentials(value: CredentialsPolicy): CredentialsPolicy {\n if (!CREDENTIALS.includes(value)) {\n throw new PixelumeConfigError(`credentials must be one of: ${CREDENTIALS.join(', ')}`);\n }\n return value;\n}\n\nfunction resolveFetch(custom: FetchLike | undefined): FetchLike {\n if (custom !== undefined) {\n if (typeof custom !== 'function') throw new PixelumeConfigError('fetch must be a function');\n return custom;\n }\n if (typeof globalThis.fetch !== 'function') {\n throw new PixelumeConfigError('No global fetch available; pass the fetch option');\n }\n // Looked up on each call so polyfills or test doubles installed later are honoured.\n return (input, init) => globalThis.fetch(input, init);\n}\n"]}
@@ -6,12 +6,13 @@
6
6
  * query strings, so they are safe to log. Raw response payloads are kept on
7
7
  * {@link PixelumeHttpError.payload} as a non-enumerable property for programmatic inspection only.
8
8
  */
9
- export type PixelumeErrorKind = 'config' | 'http' | 'network' | 'timeout' | 'aborted' | 'invalid_response';
9
+ export type PixelumeErrorKind = 'config' | 'auth' | 'http' | 'network' | 'timeout' | 'aborted' | 'invalid_response';
10
+ export type PixelumeAuthErrorReason = 'browser_unavailable' | 'storage_unavailable' | 'crypto_unavailable' | 'invalid_callback' | 'state_mismatch' | 'authorization_expired';
10
11
  /**
11
12
  * Error codes documented in the Pixelume API OpenAPI `ErrorEnvelope`.
12
13
  * The API may introduce new codes, so any string is accepted at runtime.
13
14
  */
14
- export type PixelumeApiErrorCode = 'INVALID_INPUT' | 'UNAUTHENTICATED' | 'FORBIDDEN_ORIGIN' | 'FORBIDDEN_HOST' | 'RATE_LIMITED' | 'REGISTRATION_DISABLED' | 'AUTH_TOKEN_INVALID' | 'INTERNAL_ERROR' | 'NOT_FOUND' | (string & {});
15
+ export type PixelumeApiErrorCode = 'INVALID_INPUT' | 'UNAUTHENTICATED' | 'FORBIDDEN_ORIGIN' | 'FORBIDDEN_HOST' | 'RATE_LIMITED' | 'REGISTRATION_DISABLED' | 'AUTH_TOKEN_INVALID' | 'OAUTH_INVALID_CLIENT' | 'OAUTH_INVALID_REDIRECT_URI' | 'OAUTH_INVALID_REQUEST' | 'OAUTH_INVALID_SCOPE' | 'OAUTH_REQUEST_EXPIRED' | 'OAUTH_INVALID_GRANT' | 'OAUTH_INVALID_TOKEN' | 'INTERNAL_ERROR' | 'NOT_FOUND' | (string & {});
15
16
  /** Identifies the request that failed. `path` never contains the query string. */
16
17
  export interface PixelumeRequestInfo {
17
18
  readonly method: string;
@@ -29,6 +30,14 @@ export declare class PixelumeConfigError extends PixelumeError {
29
30
  readonly kind: "config";
30
31
  constructor(message: string);
31
32
  }
33
+ /** A local browser authorization failure that happened before an API response was available. */
34
+ export declare class PixelumeAuthError extends PixelumeError {
35
+ readonly kind: "auth";
36
+ readonly reason: PixelumeAuthErrorReason;
37
+ constructor(reason: PixelumeAuthErrorReason, options?: {
38
+ cause?: unknown;
39
+ });
40
+ }
32
41
  /** Base class of errors tied to a specific HTTP request. */
33
42
  export declare abstract class PixelumeRequestError extends PixelumeError {
34
43
  readonly method: string;
@@ -23,6 +23,15 @@ export class PixelumeConfigError extends PixelumeError {
23
23
  super('PixelumeConfigError', message);
24
24
  }
25
25
  }
26
+ /** A local browser authorization failure that happened before an API response was available. */
27
+ export class PixelumeAuthError extends PixelumeError {
28
+ kind = 'auth';
29
+ reason;
30
+ constructor(reason, options) {
31
+ super('PixelumeAuthError', `Pixelume authorization failed (${reason})`, options);
32
+ this.reason = reason;
33
+ }
34
+ }
26
35
  /** Base class of errors tied to a specific HTTP request. */
27
36
  export class PixelumeRequestError extends PixelumeError {
28
37
  method;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Error hierarchy of the SDK.\n *\n * Every error thrown by the SDK is a {@link PixelumeError} with a stable `kind` discriminator.\n * Messages are deterministic and never include request/response bodies, headers, cookies or\n * query strings, so they are safe to log. Raw response payloads are kept on\n * {@link PixelumeHttpError.payload} as a non-enumerable property for programmatic inspection only.\n */\n\nconst PIXELUME_ERROR_BRAND = Symbol.for('pixelume.sdk.error');\n\nexport type PixelumeErrorKind =\n 'config' | 'http' | 'network' | 'timeout' | 'aborted' | 'invalid_response';\n\n/**\n * Error codes documented in the Pixelume API OpenAPI `ErrorEnvelope`.\n * The API may introduce new codes, so any string is accepted at runtime.\n */\nexport type PixelumeApiErrorCode =\n | 'INVALID_INPUT'\n | 'UNAUTHENTICATED'\n | 'FORBIDDEN_ORIGIN'\n | 'FORBIDDEN_HOST'\n | 'RATE_LIMITED'\n | 'REGISTRATION_DISABLED'\n | 'AUTH_TOKEN_INVALID'\n | 'INTERNAL_ERROR'\n | 'NOT_FOUND'\n | (string & {});\n\n/** Identifies the request that failed. `path` never contains the query string. */\nexport interface PixelumeRequestInfo {\n readonly method: string;\n readonly path: string;\n}\n\n/** Base class of every error thrown by the SDK. */\nexport abstract class PixelumeError extends Error {\n abstract readonly kind: PixelumeErrorKind;\n\n protected constructor(name: string, message: string, options?: { cause?: unknown }) {\n super(message, options?.cause === undefined ? undefined : { cause: options.cause });\n // Explicit names survive minification of consumer bundles.\n this.name = name;\n Object.defineProperty(this, PIXELUME_ERROR_BRAND, { value: true });\n }\n}\n\n/** Invalid SDK configuration or invalid use of the SDK. Never caused by the network. */\nexport class PixelumeConfigError extends PixelumeError {\n readonly kind = 'config' as const;\n\n constructor(message: string) {\n super('PixelumeConfigError', message);\n }\n}\n\n/** Base class of errors tied to a specific HTTP request. */\nexport abstract class PixelumeRequestError extends PixelumeError {\n readonly method: string;\n readonly path: string;\n\n protected constructor(\n name: string,\n message: string,\n request: PixelumeRequestInfo,\n options?: { cause?: unknown },\n ) {\n super(name, message, options);\n this.method = request.method;\n this.path = request.path;\n }\n}\n\n/** The API answered with a non-2xx status. */\nexport class PixelumeHttpError extends PixelumeRequestError {\n readonly kind = 'http' as const;\n readonly status: number;\n /** `error.code` from the API error envelope, or `null` when the body is not an envelope. */\n readonly code: PixelumeApiErrorCode | null;\n /** `error.message` from the API error envelope (already sanitized by the API). */\n readonly apiMessage: string | null;\n /** Request ID from the envelope or the `x-request-id` header, useful for support. */\n readonly requestId: string | null;\n /**\n * Decoded response body (JSON value, text or `undefined`). Non-enumerable so that\n * `JSON.stringify(error)` and structured loggers do not dump it by accident.\n */\n declare readonly payload: unknown;\n\n constructor(details: {\n request: PixelumeRequestInfo;\n status: number;\n code: string | null;\n apiMessage: string | null;\n requestId: string | null;\n payload: unknown;\n }) {\n const suffix = details.code ? ` (${details.code})` : '';\n super(\n 'PixelumeHttpError',\n `Pixelume API request ${details.request.method} ${details.request.path} failed with status ${details.status}${suffix}`,\n details.request,\n );\n this.status = details.status;\n this.code = details.code;\n this.apiMessage = details.apiMessage;\n this.requestId = details.requestId;\n Object.defineProperty(this, 'payload', {\n value: details.payload,\n enumerable: false,\n writable: false,\n });\n }\n}\n\n/**\n * The request did not produce an HTTP response: offline, DNS/TLS failure, connection reset,\n * or a browser CORS rejection (browsers do not distinguish these for security reasons).\n */\nexport class PixelumeNetworkError extends PixelumeRequestError {\n readonly kind = 'network' as const;\n\n constructor(request: PixelumeRequestInfo, options?: { cause?: unknown }) {\n super(\n 'PixelumeNetworkError',\n `Pixelume API request ${request.method} ${request.path} failed before a response was received (network failure or blocked by CORS)`,\n request,\n options,\n );\n }\n}\n\n/** The SDK timeout elapsed before the response (including its body) was received. */\nexport class PixelumeTimeoutError extends PixelumeRequestError {\n readonly kind = 'timeout' as const;\n readonly timeoutMs: number;\n\n constructor(request: PixelumeRequestInfo, timeoutMs: number, options?: { cause?: unknown }) {\n super(\n 'PixelumeTimeoutError',\n `Pixelume API request ${request.method} ${request.path} timed out after ${timeoutMs} ms`,\n request,\n options,\n );\n this.timeoutMs = timeoutMs;\n }\n}\n\n/** The caller aborted the request through its `AbortSignal`. `cause` holds the abort reason. */\nexport class PixelumeAbortError extends PixelumeRequestError {\n readonly kind = 'aborted' as const;\n\n constructor(request: PixelumeRequestInfo, options?: { cause?: unknown }) {\n super(\n 'PixelumeAbortError',\n `Pixelume API request ${request.method} ${request.path} was aborted by the caller`,\n request,\n options,\n );\n }\n}\n\n/** A 2xx response did not match the contract expected by the SDK. */\nexport class PixelumeInvalidResponseError extends PixelumeRequestError {\n readonly kind = 'invalid_response' as const;\n readonly status: number;\n /** Short, payload-free description of the contract violation. */\n readonly reason: string;\n\n constructor(request: PixelumeRequestInfo, status: number, reason: string) {\n super(\n 'PixelumeInvalidResponseError',\n `Pixelume API request ${request.method} ${request.path} returned an unexpected response: ${reason}`,\n request,\n );\n this.status = status;\n this.reason = reason;\n }\n}\n\n/**\n * Returns `true` for any error thrown by the SDK. Works across duplicated copies of the package\n * (where `instanceof` would fail) because it checks a global symbol brand.\n */\nexport function isPixelumeError(value: unknown): value is PixelumeError {\n return (\n value instanceof Error &&\n (value as unknown as Record<symbol, unknown>)[PIXELUME_ERROR_BRAND] === true\n );\n}\n"]}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Error hierarchy of the SDK.\n *\n * Every error thrown by the SDK is a {@link PixelumeError} with a stable `kind` discriminator.\n * Messages are deterministic and never include request/response bodies, headers, cookies or\n * query strings, so they are safe to log. Raw response payloads are kept on\n * {@link PixelumeHttpError.payload} as a non-enumerable property for programmatic inspection only.\n */\n\nconst PIXELUME_ERROR_BRAND = Symbol.for('pixelume.sdk.error');\n\nexport type PixelumeErrorKind =\n 'config' | 'auth' | 'http' | 'network' | 'timeout' | 'aborted' | 'invalid_response';\n\nexport type PixelumeAuthErrorReason =\n | 'browser_unavailable'\n | 'storage_unavailable'\n | 'crypto_unavailable'\n | 'invalid_callback'\n | 'state_mismatch'\n | 'authorization_expired';\n\n/**\n * Error codes documented in the Pixelume API OpenAPI `ErrorEnvelope`.\n * The API may introduce new codes, so any string is accepted at runtime.\n */\nexport type PixelumeApiErrorCode =\n | 'INVALID_INPUT'\n | 'UNAUTHENTICATED'\n | 'FORBIDDEN_ORIGIN'\n | 'FORBIDDEN_HOST'\n | 'RATE_LIMITED'\n | 'REGISTRATION_DISABLED'\n | 'AUTH_TOKEN_INVALID'\n | 'OAUTH_INVALID_CLIENT'\n | 'OAUTH_INVALID_REDIRECT_URI'\n | 'OAUTH_INVALID_REQUEST'\n | 'OAUTH_INVALID_SCOPE'\n | 'OAUTH_REQUEST_EXPIRED'\n | 'OAUTH_INVALID_GRANT'\n | 'OAUTH_INVALID_TOKEN'\n | 'INTERNAL_ERROR'\n | 'NOT_FOUND'\n | (string & {});\n\n/** Identifies the request that failed. `path` never contains the query string. */\nexport interface PixelumeRequestInfo {\n readonly method: string;\n readonly path: string;\n}\n\n/** Base class of every error thrown by the SDK. */\nexport abstract class PixelumeError extends Error {\n abstract readonly kind: PixelumeErrorKind;\n\n protected constructor(name: string, message: string, options?: { cause?: unknown }) {\n super(message, options?.cause === undefined ? undefined : { cause: options.cause });\n // Explicit names survive minification of consumer bundles.\n this.name = name;\n Object.defineProperty(this, PIXELUME_ERROR_BRAND, { value: true });\n }\n}\n\n/** Invalid SDK configuration or invalid use of the SDK. Never caused by the network. */\nexport class PixelumeConfigError extends PixelumeError {\n readonly kind = 'config' as const;\n\n constructor(message: string) {\n super('PixelumeConfigError', message);\n }\n}\n\n/** A local browser authorization failure that happened before an API response was available. */\nexport class PixelumeAuthError extends PixelumeError {\n readonly kind = 'auth' as const;\n readonly reason: PixelumeAuthErrorReason;\n\n constructor(reason: PixelumeAuthErrorReason, options?: { cause?: unknown }) {\n super('PixelumeAuthError', `Pixelume authorization failed (${reason})`, options);\n this.reason = reason;\n }\n}\n\n/** Base class of errors tied to a specific HTTP request. */\nexport abstract class PixelumeRequestError extends PixelumeError {\n readonly method: string;\n readonly path: string;\n\n protected constructor(\n name: string,\n message: string,\n request: PixelumeRequestInfo,\n options?: { cause?: unknown },\n ) {\n super(name, message, options);\n this.method = request.method;\n this.path = request.path;\n }\n}\n\n/** The API answered with a non-2xx status. */\nexport class PixelumeHttpError extends PixelumeRequestError {\n readonly kind = 'http' as const;\n readonly status: number;\n /** `error.code` from the API error envelope, or `null` when the body is not an envelope. */\n readonly code: PixelumeApiErrorCode | null;\n /** `error.message` from the API error envelope (already sanitized by the API). */\n readonly apiMessage: string | null;\n /** Request ID from the envelope or the `x-request-id` header, useful for support. */\n readonly requestId: string | null;\n /**\n * Decoded response body (JSON value, text or `undefined`). Non-enumerable so that\n * `JSON.stringify(error)` and structured loggers do not dump it by accident.\n */\n declare readonly payload: unknown;\n\n constructor(details: {\n request: PixelumeRequestInfo;\n status: number;\n code: string | null;\n apiMessage: string | null;\n requestId: string | null;\n payload: unknown;\n }) {\n const suffix = details.code ? ` (${details.code})` : '';\n super(\n 'PixelumeHttpError',\n `Pixelume API request ${details.request.method} ${details.request.path} failed with status ${details.status}${suffix}`,\n details.request,\n );\n this.status = details.status;\n this.code = details.code;\n this.apiMessage = details.apiMessage;\n this.requestId = details.requestId;\n Object.defineProperty(this, 'payload', {\n value: details.payload,\n enumerable: false,\n writable: false,\n });\n }\n}\n\n/**\n * The request did not produce an HTTP response: offline, DNS/TLS failure, connection reset,\n * or a browser CORS rejection (browsers do not distinguish these for security reasons).\n */\nexport class PixelumeNetworkError extends PixelumeRequestError {\n readonly kind = 'network' as const;\n\n constructor(request: PixelumeRequestInfo, options?: { cause?: unknown }) {\n super(\n 'PixelumeNetworkError',\n `Pixelume API request ${request.method} ${request.path} failed before a response was received (network failure or blocked by CORS)`,\n request,\n options,\n );\n }\n}\n\n/** The SDK timeout elapsed before the response (including its body) was received. */\nexport class PixelumeTimeoutError extends PixelumeRequestError {\n readonly kind = 'timeout' as const;\n readonly timeoutMs: number;\n\n constructor(request: PixelumeRequestInfo, timeoutMs: number, options?: { cause?: unknown }) {\n super(\n 'PixelumeTimeoutError',\n `Pixelume API request ${request.method} ${request.path} timed out after ${timeoutMs} ms`,\n request,\n options,\n );\n this.timeoutMs = timeoutMs;\n }\n}\n\n/** The caller aborted the request through its `AbortSignal`. `cause` holds the abort reason. */\nexport class PixelumeAbortError extends PixelumeRequestError {\n readonly kind = 'aborted' as const;\n\n constructor(request: PixelumeRequestInfo, options?: { cause?: unknown }) {\n super(\n 'PixelumeAbortError',\n `Pixelume API request ${request.method} ${request.path} was aborted by the caller`,\n request,\n options,\n );\n }\n}\n\n/** A 2xx response did not match the contract expected by the SDK. */\nexport class PixelumeInvalidResponseError extends PixelumeRequestError {\n readonly kind = 'invalid_response' as const;\n readonly status: number;\n /** Short, payload-free description of the contract violation. */\n readonly reason: string;\n\n constructor(request: PixelumeRequestInfo, status: number, reason: string) {\n super(\n 'PixelumeInvalidResponseError',\n `Pixelume API request ${request.method} ${request.path} returned an unexpected response: ${reason}`,\n request,\n );\n this.status = status;\n this.reason = reason;\n }\n}\n\n/**\n * Returns `true` for any error thrown by the SDK. Works across duplicated copies of the package\n * (where `instanceof` would fail) because it checks a global symbol brand.\n */\nexport function isPixelumeError(value: unknown): value is PixelumeError {\n return (\n value instanceof Error &&\n (value as unknown as Record<symbol, unknown>)[PIXELUME_ERROR_BRAND] === true\n );\n}\n"]}
@@ -1,6 +1,23 @@
1
1
  import type { CredentialsPolicy, FetchLike } from '../http/types.js';
2
2
  export type PixelumeCredentialsPolicy = CredentialsPolicy;
3
3
  export type PixelumeFetch = FetchLike;
4
+ /** Minimal browser storage contract used for transient PKCE state and a non-secret session hint. */
5
+ export interface PixelumeSessionStorage {
6
+ getItem(key: string): string | null;
7
+ setItem(key: string, value: string): void;
8
+ removeItem(key: string): void;
9
+ }
10
+ /**
11
+ * Browser capabilities used by the redirect flow. Usually omitted; injection is useful in tests,
12
+ * embedded runtimes and non-standard webviews.
13
+ */
14
+ export interface PixelumeBrowserAdapter {
15
+ currentUrl(): string;
16
+ navigate(url: string): void;
17
+ replaceUrl(url: string): void;
18
+ readonly sessionStorage: PixelumeSessionStorage;
19
+ readonly crypto: Crypto;
20
+ }
4
21
  export interface PixelumeClientOptions {
5
22
  /**
6
23
  * Base URL of the Pixelume API. Defaults to `https://api.pixelume.com.br`.
@@ -13,6 +30,20 @@ export interface PixelumeClientOptions {
13
30
  * It is an identifier, never an authorization credential.
14
31
  */
15
32
  gameId?: string;
33
+ /**
34
+ * OAuth public client identifier registered by the Pixelume API. Defaults to `gameId`.
35
+ * This is public metadata, never a secret.
36
+ */
37
+ clientId?: string;
38
+ /** Exact callback URI registered for the OAuth client. Defaults to `<origin>/oauth/callback`. */
39
+ redirectUri?: string;
40
+ /** Pixelume Auth portal origin. Defaults to `https://auth.pixelume.com.br`. */
41
+ authUrl?: string;
42
+ /**
43
+ * Reauthorize after a same-tab reload when a previous SDK session existed. The SDK stores only
44
+ * a non-secret hint; access tokens remain memory-only. Defaults to `true`.
45
+ */
46
+ restoreSession?: boolean;
16
47
  /** Per-request timeout in milliseconds. Defaults to 10 000. */
17
48
  timeoutMs?: number;
18
49
  /**
@@ -25,11 +56,17 @@ export interface PixelumeClientOptions {
25
56
  fetch?: PixelumeFetch;
26
57
  /** Allows `http:` for non-loopback hosts (e.g. a LAN IP). Never enable it in production. */
27
58
  allowInsecureHttp?: boolean;
59
+ /** Advanced/test-only browser capability injection. Defaults to the current browser globals. */
60
+ browser?: PixelumeBrowserAdapter;
28
61
  }
29
62
  /** Effective, validated configuration of a client. */
30
63
  export interface PixelumeClientConfig {
31
64
  readonly apiUrl: string;
32
65
  readonly gameId: string | null;
66
+ readonly clientId: string | null;
67
+ readonly redirectUri: string | null;
68
+ readonly authUrl: string;
69
+ readonly restoreSession: boolean;
33
70
  readonly timeoutMs: number;
34
71
  readonly credentials: PixelumeCredentialsPolicy;
35
72
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { CredentialsPolicy, FetchLike } from '../http/types.js';\n\nexport type PixelumeCredentialsPolicy = CredentialsPolicy;\nexport type PixelumeFetch = FetchLike;\n\nexport interface PixelumeClientOptions {\n /**\n * Base URL of the Pixelume API. Defaults to `https://api.pixelume.com.br`.\n * Must be `https:`; `http:` is accepted for loopback hosts during local development.\n */\n apiUrl?: string;\n /**\n * Public identifier of the game, e.g. `rail-puzzle`. Optional in Phase 1 because no current\n * API contract uses it; future modules that need it will fail fast with a config error.\n * It is an identifier, never an authorization credential.\n */\n gameId?: string;\n /** Per-request timeout in milliseconds. Defaults to 10 000. */\n timeoutMs?: number;\n /**\n * `fetch` credentials policy used by modules that do not set their own. Defaults to `'omit'`.\n * Cross-origin cookies are subject to the API's CORS allowlist and to browser policies;\n * the SDK never assumes they work.\n */\n credentials?: PixelumeCredentialsPolicy;\n /** Custom `fetch` implementation (tests, SSR, instrumentation). Defaults to `globalThis.fetch`. */\n fetch?: PixelumeFetch;\n /** Allows `http:` for non-loopback hosts (e.g. a LAN IP). Never enable it in production. */\n allowInsecureHttp?: boolean;\n}\n\n/** Effective, validated configuration of a client. */\nexport interface PixelumeClientConfig {\n readonly apiUrl: string;\n readonly gameId: string | null;\n readonly timeoutMs: number;\n readonly credentials: PixelumeCredentialsPolicy;\n}\n\n/** Options accepted by every SDK operation. */\nexport interface PixelumeRequestOptions {\n /** Aborts the operation; the promise rejects with `PixelumeAbortError`. */\n signal?: AbortSignal;\n /** Overrides the client timeout for this operation. */\n timeoutMs?: number;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { CredentialsPolicy, FetchLike } from '../http/types.js';\n\nexport type PixelumeCredentialsPolicy = CredentialsPolicy;\nexport type PixelumeFetch = FetchLike;\n\n/** Minimal browser storage contract used for transient PKCE state and a non-secret session hint. */\nexport interface PixelumeSessionStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/**\n * Browser capabilities used by the redirect flow. Usually omitted; injection is useful in tests,\n * embedded runtimes and non-standard webviews.\n */\nexport interface PixelumeBrowserAdapter {\n currentUrl(): string;\n navigate(url: string): void;\n replaceUrl(url: string): void;\n readonly sessionStorage: PixelumeSessionStorage;\n readonly crypto: Crypto;\n}\n\nexport interface PixelumeClientOptions {\n /**\n * Base URL of the Pixelume API. Defaults to `https://api.pixelume.com.br`.\n * Must be `https:`; `http:` is accepted for loopback hosts during local development.\n */\n apiUrl?: string;\n /**\n * Public identifier of the game, e.g. `rail-puzzle`. Optional in Phase 1 because no current\n * API contract uses it; future modules that need it will fail fast with a config error.\n * It is an identifier, never an authorization credential.\n */\n gameId?: string;\n /**\n * OAuth public client identifier registered by the Pixelume API. Defaults to `gameId`.\n * This is public metadata, never a secret.\n */\n clientId?: string;\n /** Exact callback URI registered for the OAuth client. Defaults to `<origin>/oauth/callback`. */\n redirectUri?: string;\n /** Pixelume Auth portal origin. Defaults to `https://auth.pixelume.com.br`. */\n authUrl?: string;\n /**\n * Reauthorize after a same-tab reload when a previous SDK session existed. The SDK stores only\n * a non-secret hint; access tokens remain memory-only. Defaults to `true`.\n */\n restoreSession?: boolean;\n /** Per-request timeout in milliseconds. Defaults to 10 000. */\n timeoutMs?: number;\n /**\n * `fetch` credentials policy used by modules that do not set their own. Defaults to `'omit'`.\n * Cross-origin cookies are subject to the API's CORS allowlist and to browser policies;\n * the SDK never assumes they work.\n */\n credentials?: PixelumeCredentialsPolicy;\n /** Custom `fetch` implementation (tests, SSR, instrumentation). Defaults to `globalThis.fetch`. */\n fetch?: PixelumeFetch;\n /** Allows `http:` for non-loopback hosts (e.g. a LAN IP). Never enable it in production. */\n allowInsecureHttp?: boolean;\n /** Advanced/test-only browser capability injection. Defaults to the current browser globals. */\n browser?: PixelumeBrowserAdapter;\n}\n\n/** Effective, validated configuration of a client. */\nexport interface PixelumeClientConfig {\n readonly apiUrl: string;\n readonly gameId: string | null;\n readonly clientId: string | null;\n readonly redirectUri: string | null;\n readonly authUrl: string;\n readonly restoreSession: boolean;\n readonly timeoutMs: number;\n readonly credentials: PixelumeCredentialsPolicy;\n}\n\n/** Options accepted by every SDK operation. */\nexport interface PixelumeRequestOptions {\n /** Aborts the operation; the promise rejects with `PixelumeAbortError`. */\n signal?: AbortSignal;\n /** Overrides the client timeout for this operation. */\n timeoutMs?: number;\n}\n"]}
@@ -37,6 +37,9 @@ export class HttpClient {
37
37
  this.#credentials = options.credentials;
38
38
  }
39
39
  async request(request) {
40
+ if (request.json !== undefined && request.form !== undefined) {
41
+ throw new PixelumeConfigError('HTTP requests cannot contain both json and form bodies');
42
+ }
40
43
  const info = { method: request.method, path: normalizePath(request.path) };
41
44
  const url = buildUrl(this.#baseUrl, request.path, request.query);
42
45
  const timeoutMs = request.timeoutMs === undefined ? this.#timeoutMs : validateTimeout(request.timeoutMs);
@@ -47,6 +50,8 @@ export class HttpClient {
47
50
  };
48
51
  if (request.json !== undefined)
49
52
  init.body = JSON.stringify(request.json);
53
+ if (request.form !== undefined)
54
+ init.body = new URLSearchParams(request.form).toString();
50
55
  const callerSignal = request.signal;
51
56
  if (callerSignal?.aborted)
52
57
  throw new PixelumeAbortError(info, { cause: callerSignal.reason });
@@ -113,6 +118,9 @@ function buildHeaders(request) {
113
118
  }
114
119
  if (request.json !== undefined)
115
120
  headers.set('content-type', 'application/json');
121
+ if (request.form !== undefined) {
122
+ headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
123
+ }
116
124
  return headers;
117
125
  }
118
126
  async function readBody(response, method) {
@@ -1 +1 @@
1
- {"version":3,"file":"HttpClient.js","sourceRoot":"","sources":["../../src/http/HttpClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { parseErrorEnvelope } from '../contracts/index.js';\nimport {\n PixelumeAbortError,\n PixelumeConfigError,\n PixelumeHttpError,\n PixelumeInvalidResponseError,\n PixelumeNetworkError,\n PixelumeTimeoutError,\n type PixelumeRequestInfo,\n} from '../core/errors.js';\nimport { validateTimeout } from '../core/config.js';\nimport type {\n FetchLike,\n HttpClientOptions,\n HttpMethod,\n HttpRequest,\n HttpResponse,\n} from './types.js';\nimport { buildUrl, normalizePath } from './url.js';\n\n/**\n * Headers that only the runtime (or the SDK itself) may set. `origin`/`referer` in particular\n * must never be forged: the API uses them as a CSRF defence and the SDK does not rely on them.\n */\nconst RESERVED_HEADERS = new Set([\n 'content-length',\n 'cookie',\n 'cookie2',\n 'host',\n 'origin',\n 'referer',\n 'set-cookie',\n]);\n\ntype DecodedBody =\n | { readonly type: 'empty'; readonly value: undefined }\n | { readonly type: 'json'; readonly value: unknown }\n | { readonly type: 'text'; readonly value: string }\n | { readonly type: 'invalid_json'; readonly value: string };\n\n/**\n * Small typed HTTP client over `fetch`. Internal to the SDK: feature modules build on it,\n * consumers use the modules.\n *\n * Policy:\n * - timeout covers the whole exchange, including reading the body;\n * - the caller's `AbortSignal` is honoured before and during the request;\n * - no automatic retries (the caller decides; see `docs/architecture.md`);\n * - credentials default to the client policy and can be overridden per request.\n */\nexport class HttpClient {\n readonly #baseUrl: string;\n readonly #fetch: FetchLike;\n readonly #timeoutMs: number;\n readonly #credentials: HttpClientOptions['credentials'];\n\n constructor(options: HttpClientOptions) {\n this.#baseUrl = options.baseUrl;\n this.#fetch = options.fetch;\n this.#timeoutMs = options.timeoutMs;\n this.#credentials = options.credentials;\n }\n\n async request<T>(request: HttpRequest<T>): Promise<HttpResponse<T>> {\n const info: PixelumeRequestInfo = { method: request.method, path: normalizePath(request.path) };\n const url = buildUrl(this.#baseUrl, request.path, request.query);\n const timeoutMs =\n request.timeoutMs === undefined ? this.#timeoutMs : validateTimeout(request.timeoutMs);\n const init: RequestInit = {\n method: request.method,\n headers: buildHeaders(request),\n credentials: request.credentials ?? this.#credentials,\n };\n if (request.json !== undefined) init.body = JSON.stringify(request.json);\n\n const callerSignal = request.signal;\n if (callerSignal?.aborted) throw new PixelumeAbortError(info, { cause: callerSignal.reason });\n\n // Timeout and caller abort share one controller; the first abort reason wins.\n const controller = new AbortController();\n const timeoutReason = new DOMException(`Timed out after ${timeoutMs} ms`, 'TimeoutError');\n const timer = setTimeout(() => {\n controller.abort(timeoutReason);\n }, timeoutMs);\n const onCallerAbort = () => {\n controller.abort(callerSignal?.reason);\n };\n callerSignal?.addEventListener('abort', onCallerAbort, { once: true });\n // Settles on abort even if an injected `fetch` ignores the signal.\n const abortion = new Promise<never>((_, reject) => {\n controller.signal.addEventListener(\n 'abort',\n () => {\n reject(controller.signal.reason as Error);\n },\n { once: true },\n );\n });\n abortion.catch(() => undefined);\n\n // The function is invoked without a receiver on purpose: calling a browser's native `fetch`\n // with `this` bound to another object throws \"Illegal invocation\".\n const fetchFn = this.#fetch;\n let response: Response;\n let body: DecodedBody;\n try {\n const pending = Promise.resolve().then(() =>\n fetchFn(url, { ...init, signal: controller.signal }),\n );\n // Promise.race subscribes to every contender, so a losing promise that rejects later is\n // handled and never surfaces as an unhandled rejection.\n response = await Promise.race([pending, abortion]);\n body = await Promise.race([readBody(response, request.method), abortion]);\n } catch (error) {\n if (controller.signal.reason === timeoutReason) {\n throw new PixelumeTimeoutError(info, timeoutMs, { cause: error });\n }\n if (callerSignal?.aborted) {\n throw new PixelumeAbortError(info, { cause: callerSignal.reason });\n }\n throw new PixelumeNetworkError(info, { cause: error });\n } finally {\n clearTimeout(timer);\n callerSignal?.removeEventListener('abort', onCallerAbort);\n }\n\n if (!response.ok) throw toHttpError(info, response, body);\n if (body.type === 'invalid_json') {\n throw new PixelumeInvalidResponseError(info, response.status, 'body is not valid JSON');\n }\n const parsed = request.parse(body.value);\n if (!parsed.ok) throw new PixelumeInvalidResponseError(info, response.status, parsed.reason);\n return { status: response.status, headers: response.headers, data: parsed.value };\n }\n}\n\nfunction buildHeaders(request: HttpRequest<unknown>): Headers {\n const headers = new Headers({ accept: 'application/json' });\n for (const [name, value] of Object.entries(request.headers ?? {})) {\n if (RESERVED_HEADERS.has(name.toLowerCase())) {\n throw new PixelumeConfigError(`Header \"${name}\" is managed by the runtime and cannot be set`);\n }\n headers.set(name, value);\n }\n if (request.json !== undefined) headers.set('content-type', 'application/json');\n return headers;\n}\n\nasync function readBody(response: Response, method: HttpMethod): Promise<DecodedBody> {\n if (method === 'HEAD' || [204, 205, 304].includes(response.status)) {\n return { type: 'empty', value: undefined };\n }\n const text = await response.text();\n if (text === '') return { type: 'empty', value: undefined };\n if (!isJsonContentType(response.headers.get('content-type')))\n return { type: 'text', value: text };\n try {\n return { type: 'json', value: JSON.parse(text) as unknown };\n } catch {\n return { type: 'invalid_json', value: text };\n }\n}\n\nfunction isJsonContentType(contentType: string | null): boolean {\n const mediaType = contentType?.split(';')[0]?.trim().toLowerCase() ?? '';\n return mediaType === 'application/json' || mediaType.endsWith('+json');\n}\n\nfunction toHttpError(\n info: PixelumeRequestInfo,\n response: Response,\n body: DecodedBody,\n): PixelumeHttpError {\n const envelope = body.type === 'json' ? parseErrorEnvelope(body.value) : null;\n return new PixelumeHttpError({\n request: info,\n status: response.status,\n code: envelope?.error.code ?? null,\n apiMessage: envelope?.error.message ?? null,\n requestId: envelope?.error.requestId ?? response.headers.get('x-request-id'),\n payload: body.value,\n });\n}\n"]}
1
+ {"version":3,"file":"HttpClient.js","sourceRoot":"","sources":["../../src/http/HttpClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { parseErrorEnvelope } from '../contracts/index.js';\nimport {\n PixelumeAbortError,\n PixelumeConfigError,\n PixelumeHttpError,\n PixelumeInvalidResponseError,\n PixelumeNetworkError,\n PixelumeTimeoutError,\n type PixelumeRequestInfo,\n} from '../core/errors.js';\nimport { validateTimeout } from '../core/config.js';\nimport type {\n FetchLike,\n HttpClientOptions,\n HttpMethod,\n HttpRequest,\n HttpResponse,\n} from './types.js';\nimport { buildUrl, normalizePath } from './url.js';\n\n/**\n * Headers that only the runtime (or the SDK itself) may set. `origin`/`referer` in particular\n * must never be forged: the API uses them as a CSRF defence and the SDK does not rely on them.\n */\nconst RESERVED_HEADERS = new Set([\n 'content-length',\n 'cookie',\n 'cookie2',\n 'host',\n 'origin',\n 'referer',\n 'set-cookie',\n]);\n\ntype DecodedBody =\n | { readonly type: 'empty'; readonly value: undefined }\n | { readonly type: 'json'; readonly value: unknown }\n | { readonly type: 'text'; readonly value: string }\n | { readonly type: 'invalid_json'; readonly value: string };\n\n/**\n * Small typed HTTP client over `fetch`. Internal to the SDK: feature modules build on it,\n * consumers use the modules.\n *\n * Policy:\n * - timeout covers the whole exchange, including reading the body;\n * - the caller's `AbortSignal` is honoured before and during the request;\n * - no automatic retries (the caller decides; see `docs/architecture.md`);\n * - credentials default to the client policy and can be overridden per request.\n */\nexport class HttpClient {\n readonly #baseUrl: string;\n readonly #fetch: FetchLike;\n readonly #timeoutMs: number;\n readonly #credentials: HttpClientOptions['credentials'];\n\n constructor(options: HttpClientOptions) {\n this.#baseUrl = options.baseUrl;\n this.#fetch = options.fetch;\n this.#timeoutMs = options.timeoutMs;\n this.#credentials = options.credentials;\n }\n\n async request<T>(request: HttpRequest<T>): Promise<HttpResponse<T>> {\n if (request.json !== undefined && request.form !== undefined) {\n throw new PixelumeConfigError('HTTP requests cannot contain both json and form bodies');\n }\n const info: PixelumeRequestInfo = { method: request.method, path: normalizePath(request.path) };\n const url = buildUrl(this.#baseUrl, request.path, request.query);\n const timeoutMs =\n request.timeoutMs === undefined ? this.#timeoutMs : validateTimeout(request.timeoutMs);\n const init: RequestInit = {\n method: request.method,\n headers: buildHeaders(request),\n credentials: request.credentials ?? this.#credentials,\n };\n if (request.json !== undefined) init.body = JSON.stringify(request.json);\n if (request.form !== undefined) init.body = new URLSearchParams(request.form).toString();\n\n const callerSignal = request.signal;\n if (callerSignal?.aborted) throw new PixelumeAbortError(info, { cause: callerSignal.reason });\n\n // Timeout and caller abort share one controller; the first abort reason wins.\n const controller = new AbortController();\n const timeoutReason = new DOMException(`Timed out after ${timeoutMs} ms`, 'TimeoutError');\n const timer = setTimeout(() => {\n controller.abort(timeoutReason);\n }, timeoutMs);\n const onCallerAbort = () => {\n controller.abort(callerSignal?.reason);\n };\n callerSignal?.addEventListener('abort', onCallerAbort, { once: true });\n // Settles on abort even if an injected `fetch` ignores the signal.\n const abortion = new Promise<never>((_, reject) => {\n controller.signal.addEventListener(\n 'abort',\n () => {\n reject(controller.signal.reason as Error);\n },\n { once: true },\n );\n });\n abortion.catch(() => undefined);\n\n // The function is invoked without a receiver on purpose: calling a browser's native `fetch`\n // with `this` bound to another object throws \"Illegal invocation\".\n const fetchFn = this.#fetch;\n let response: Response;\n let body: DecodedBody;\n try {\n const pending = Promise.resolve().then(() =>\n fetchFn(url, { ...init, signal: controller.signal }),\n );\n // Promise.race subscribes to every contender, so a losing promise that rejects later is\n // handled and never surfaces as an unhandled rejection.\n response = await Promise.race([pending, abortion]);\n body = await Promise.race([readBody(response, request.method), abortion]);\n } catch (error) {\n if (controller.signal.reason === timeoutReason) {\n throw new PixelumeTimeoutError(info, timeoutMs, { cause: error });\n }\n if (callerSignal?.aborted) {\n throw new PixelumeAbortError(info, { cause: callerSignal.reason });\n }\n throw new PixelumeNetworkError(info, { cause: error });\n } finally {\n clearTimeout(timer);\n callerSignal?.removeEventListener('abort', onCallerAbort);\n }\n\n if (!response.ok) throw toHttpError(info, response, body);\n if (body.type === 'invalid_json') {\n throw new PixelumeInvalidResponseError(info, response.status, 'body is not valid JSON');\n }\n const parsed = request.parse(body.value);\n if (!parsed.ok) throw new PixelumeInvalidResponseError(info, response.status, parsed.reason);\n return { status: response.status, headers: response.headers, data: parsed.value };\n }\n}\n\nfunction buildHeaders(request: HttpRequest<unknown>): Headers {\n const headers = new Headers({ accept: 'application/json' });\n for (const [name, value] of Object.entries(request.headers ?? {})) {\n if (RESERVED_HEADERS.has(name.toLowerCase())) {\n throw new PixelumeConfigError(`Header \"${name}\" is managed by the runtime and cannot be set`);\n }\n headers.set(name, value);\n }\n if (request.json !== undefined) headers.set('content-type', 'application/json');\n if (request.form !== undefined) {\n headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');\n }\n return headers;\n}\n\nasync function readBody(response: Response, method: HttpMethod): Promise<DecodedBody> {\n if (method === 'HEAD' || [204, 205, 304].includes(response.status)) {\n return { type: 'empty', value: undefined };\n }\n const text = await response.text();\n if (text === '') return { type: 'empty', value: undefined };\n if (!isJsonContentType(response.headers.get('content-type')))\n return { type: 'text', value: text };\n try {\n return { type: 'json', value: JSON.parse(text) as unknown };\n } catch {\n return { type: 'invalid_json', value: text };\n }\n}\n\nfunction isJsonContentType(contentType: string | null): boolean {\n const mediaType = contentType?.split(';')[0]?.trim().toLowerCase() ?? '';\n return mediaType === 'application/json' || mediaType.endsWith('+json');\n}\n\nfunction toHttpError(\n info: PixelumeRequestInfo,\n response: Response,\n body: DecodedBody,\n): PixelumeHttpError {\n const envelope = body.type === 'json' ? parseErrorEnvelope(body.value) : null;\n return new PixelumeHttpError({\n request: info,\n status: response.status,\n code: envelope?.error.code ?? null,\n apiMessage: envelope?.error.message ?? null,\n requestId: envelope?.error.requestId ?? response.headers.get('x-request-id'),\n payload: body.value,\n });\n}\n"]}
@@ -19,6 +19,8 @@ export interface HttpRequest<T> {
19
19
  query?: Readonly<Record<string, QueryValue>>;
20
20
  /** Serialized with `JSON.stringify` and sent as `application/json`. */
21
21
  json?: unknown;
22
+ /** Serialized as `application/x-www-form-urlencoded`. Mutually exclusive with `json`. */
23
+ form?: Readonly<Record<string, string>>;
22
24
  /** Extra headers. Headers the SDK must control (cookie, origin, host…) are rejected. */
23
25
  headers?: Readonly<Record<string, string>>;
24
26
  signal?: AbortSignal | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/http/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { BodyParser } from '../contracts/index.js';\n\nexport type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\n/** Credentials policy forwarded to `fetch`. */\nexport type CredentialsPolicy = 'omit' | 'same-origin' | 'include';\n\n/** Minimal `fetch` signature used by the SDK, compatible with browsers, Node.js 22+ and workers. */\nexport type FetchLike = (input: string, init: RequestInit) => Promise<Response>;\n\nexport type QueryValue = string | number | boolean | undefined;\n\nexport interface HttpClientOptions {\n /** Absolute, already validated base URL without trailing slash. */\n baseUrl: string;\n fetch: FetchLike;\n timeoutMs: number;\n credentials: CredentialsPolicy;\n}\n\nexport interface HttpRequest<T> {\n method: HttpMethod;\n /** Path relative to the base URL, e.g. `/health`. Absolute URLs are rejected. */\n path: string;\n query?: Readonly<Record<string, QueryValue>>;\n /** Serialized with `JSON.stringify` and sent as `application/json`. */\n json?: unknown;\n /** Extra headers. Headers the SDK must control (cookie, origin, host…) are rejected. */\n headers?: Readonly<Record<string, string>>;\n signal?: AbortSignal | undefined;\n timeoutMs?: number | undefined;\n credentials?: CredentialsPolicy;\n /** Validates the decoded body of a 2xx response (JSON value, text or `undefined` when empty). */\n parse: BodyParser<T>;\n}\n\nexport interface HttpResponse<T> {\n status: number;\n headers: Headers;\n data: T;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/http/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { BodyParser } from '../contracts/index.js';\n\nexport type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\n/** Credentials policy forwarded to `fetch`. */\nexport type CredentialsPolicy = 'omit' | 'same-origin' | 'include';\n\n/** Minimal `fetch` signature used by the SDK, compatible with browsers, Node.js 22+ and workers. */\nexport type FetchLike = (input: string, init: RequestInit) => Promise<Response>;\n\nexport type QueryValue = string | number | boolean | undefined;\n\nexport interface HttpClientOptions {\n /** Absolute, already validated base URL without trailing slash. */\n baseUrl: string;\n fetch: FetchLike;\n timeoutMs: number;\n credentials: CredentialsPolicy;\n}\n\nexport interface HttpRequest<T> {\n method: HttpMethod;\n /** Path relative to the base URL, e.g. `/health`. Absolute URLs are rejected. */\n path: string;\n query?: Readonly<Record<string, QueryValue>>;\n /** Serialized with `JSON.stringify` and sent as `application/json`. */\n json?: unknown;\n /** Serialized as `application/x-www-form-urlencoded`. Mutually exclusive with `json`. */\n form?: Readonly<Record<string, string>>;\n /** Extra headers. Headers the SDK must control (cookie, origin, host…) are rejected. */\n headers?: Readonly<Record<string, string>>;\n signal?: AbortSignal | undefined;\n timeoutMs?: number | undefined;\n credentials?: CredentialsPolicy;\n /** Validates the decoded body of a 2xx response (JSON value, text or `undefined` when empty). */\n parse: BodyParser<T>;\n}\n\nexport interface HttpResponse<T> {\n status: number;\n headers: Headers;\n data: T;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- export { PixelumeClient } from './core/PixelumeClient.js';
2
- export { DEFAULT_API_URL, DEFAULT_TIMEOUT_MS } from './core/config.js';
1
+ export { PixelumeClient, Pixelume } from './core/PixelumeClient.js';
2
+ export { DEFAULT_API_URL, DEFAULT_AUTH_URL, DEFAULT_TIMEOUT_MS } from './core/config.js';
3
3
  export { SDK_VERSION } from './version.js';
4
- export { PixelumeError, PixelumeConfigError, PixelumeRequestError, PixelumeHttpError, PixelumeNetworkError, PixelumeTimeoutError, PixelumeAbortError, PixelumeInvalidResponseError, isPixelumeError, } from './core/errors.js';
5
- export type { PixelumeErrorKind, PixelumeApiErrorCode, PixelumeRequestInfo, } from './core/errors.js';
6
- export type { PixelumeClientOptions, PixelumeClientConfig, PixelumeRequestOptions, PixelumeCredentialsPolicy, PixelumeFetch, } from './core/types.js';
4
+ export { PixelumeError, PixelumeConfigError, PixelumeAuthError, PixelumeRequestError, PixelumeHttpError, PixelumeNetworkError, PixelumeTimeoutError, PixelumeAbortError, PixelumeInvalidResponseError, isPixelumeError, } from './core/errors.js';
5
+ export type { PixelumeErrorKind, PixelumeApiErrorCode, PixelumeAuthErrorReason, PixelumeRequestInfo, } from './core/errors.js';
6
+ export type { PixelumeClientOptions, PixelumeClientConfig, PixelumeRequestOptions, PixelumeCredentialsPolicy, PixelumeFetch, PixelumeBrowserAdapter, PixelumeSessionStorage, } from './core/types.js';
7
7
  export type { PixelumeHealthApi, PixelumeHealthStatus, PixelumeRegistrationMode, } from './modules/health/HealthModule.js';
8
+ export type { PixelumeAuthApi, PixelumeAuthState, PixelumeAuthListener, PixelumeAnonymousReason, PixelumeLoginOptions, PixelumePlayer, } from './modules/auth/AuthModule.js';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export { PixelumeClient } from './core/PixelumeClient.js';
2
- export { DEFAULT_API_URL, DEFAULT_TIMEOUT_MS } from './core/config.js';
1
+ export { PixelumeClient, Pixelume } from './core/PixelumeClient.js';
2
+ export { DEFAULT_API_URL, DEFAULT_AUTH_URL, DEFAULT_TIMEOUT_MS } from './core/config.js';
3
3
  export { SDK_VERSION } from './version.js';
4
- export { PixelumeError, PixelumeConfigError, PixelumeRequestError, PixelumeHttpError, PixelumeNetworkError, PixelumeTimeoutError, PixelumeAbortError, PixelumeInvalidResponseError, isPixelumeError, } from './core/errors.js';
4
+ export { PixelumeError, PixelumeConfigError, PixelumeAuthError, PixelumeRequestError, PixelumeHttpError, PixelumeNetworkError, PixelumeTimeoutError, PixelumeAbortError, PixelumeInvalidResponseError, isPixelumeError, } from './core/errors.js';
5
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,4BAA4B,EAC5B,eAAe,GAChB,MAAM,kBAAkB,CAAC","sourcesContent":["export { PixelumeClient } from './core/PixelumeClient.js';\nexport { DEFAULT_API_URL, DEFAULT_TIMEOUT_MS } from './core/config.js';\nexport { SDK_VERSION } from './version.js';\nexport {\n PixelumeError,\n PixelumeConfigError,\n PixelumeRequestError,\n PixelumeHttpError,\n PixelumeNetworkError,\n PixelumeTimeoutError,\n PixelumeAbortError,\n PixelumeInvalidResponseError,\n isPixelumeError,\n} from './core/errors.js';\nexport type {\n PixelumeErrorKind,\n PixelumeApiErrorCode,\n PixelumeRequestInfo,\n} from './core/errors.js';\nexport type {\n PixelumeClientOptions,\n PixelumeClientConfig,\n PixelumeRequestOptions,\n PixelumeCredentialsPolicy,\n PixelumeFetch,\n} from './core/types.js';\nexport type {\n PixelumeHealthApi,\n PixelumeHealthStatus,\n PixelumeRegistrationMode,\n} from './modules/health/HealthModule.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACzF,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,4BAA4B,EAC5B,eAAe,GAChB,MAAM,kBAAkB,CAAC","sourcesContent":["export { PixelumeClient, Pixelume } from './core/PixelumeClient.js';\nexport { DEFAULT_API_URL, DEFAULT_AUTH_URL, DEFAULT_TIMEOUT_MS } from './core/config.js';\nexport { SDK_VERSION } from './version.js';\nexport {\n PixelumeError,\n PixelumeConfigError,\n PixelumeAuthError,\n PixelumeRequestError,\n PixelumeHttpError,\n PixelumeNetworkError,\n PixelumeTimeoutError,\n PixelumeAbortError,\n PixelumeInvalidResponseError,\n isPixelumeError,\n} from './core/errors.js';\nexport type {\n PixelumeErrorKind,\n PixelumeApiErrorCode,\n PixelumeAuthErrorReason,\n PixelumeRequestInfo,\n} from './core/errors.js';\nexport type {\n PixelumeClientOptions,\n PixelumeClientConfig,\n PixelumeRequestOptions,\n PixelumeCredentialsPolicy,\n PixelumeFetch,\n PixelumeBrowserAdapter,\n PixelumeSessionStorage,\n} from './core/types.js';\nexport type {\n PixelumeHealthApi,\n PixelumeHealthStatus,\n PixelumeRegistrationMode,\n} from './modules/health/HealthModule.js';\nexport type {\n PixelumeAuthApi,\n PixelumeAuthState,\n PixelumeAuthListener,\n PixelumeAnonymousReason,\n PixelumeLoginOptions,\n PixelumePlayer,\n} from './modules/auth/AuthModule.js';\n"]}
@@ -0,0 +1,55 @@
1
+ import { type PixelumeError } from '../../core/errors.js';
2
+ import type { PixelumeRequestOptions } from '../../core/types.js';
3
+ import type { ResolvedConfig } from '../../core/config.js';
4
+ import type { HttpClient } from '../../http/HttpClient.js';
5
+ export interface PixelumePlayer {
6
+ readonly id: string;
7
+ readonly displayName: string;
8
+ readonly avatarUrl: string | null;
9
+ }
10
+ export type PixelumeAnonymousReason = 'visitor' | 'cancelled' | 'expired' | 'logged_out' | 'error';
11
+ export type PixelumeAuthState = {
12
+ readonly status: 'uninitialized';
13
+ readonly player: null;
14
+ } | {
15
+ readonly status: 'redirecting';
16
+ readonly player: null;
17
+ } | {
18
+ readonly status: 'exchanging';
19
+ readonly player: null;
20
+ } | {
21
+ readonly status: 'anonymous';
22
+ readonly player: null;
23
+ readonly reason: PixelumeAnonymousReason;
24
+ readonly error: PixelumeError | null;
25
+ } | {
26
+ readonly status: 'authenticated';
27
+ readonly player: PixelumePlayer;
28
+ };
29
+ export type PixelumeAuthListener = (state: PixelumeAuthState, previous: PixelumeAuthState) => void;
30
+ export interface PixelumeLoginOptions {
31
+ /** OAuth scope. The current API accepts only `profile`. */
32
+ readonly scope?: 'profile';
33
+ }
34
+ export interface PixelumeAuthApi {
35
+ /** Current synchronous authentication state. */
36
+ readonly state: PixelumeAuthState;
37
+ /** Starts a top-level redirect to the API authorization endpoint. */
38
+ login(options?: PixelumeLoginOptions): Promise<void>;
39
+ /** Returns the current player, revalidating the memory-only access token with the API. */
40
+ getPlayer(options?: PixelumeRequestOptions): Promise<PixelumePlayer | null>;
41
+ /** Revokes this game's access token and clears local authentication state. */
42
+ logout(options?: PixelumeRequestOptions): Promise<void>;
43
+ /** Subscribes to state changes. The returned function removes the listener. */
44
+ subscribe(listener: PixelumeAuthListener): () => void;
45
+ }
46
+ export declare class AuthModule implements PixelumeAuthApi {
47
+ #private;
48
+ constructor(http: HttpClient, config: ResolvedConfig);
49
+ get state(): PixelumeAuthState;
50
+ initialize(): Promise<PixelumeAuthState>;
51
+ subscribe(listener: PixelumeAuthListener): () => void;
52
+ login(options?: PixelumeLoginOptions): Promise<void>;
53
+ getPlayer(options?: PixelumeRequestOptions): Promise<PixelumePlayer | null>;
54
+ logout(options?: PixelumeRequestOptions): Promise<void>;
55
+ }