@pixelume/sdk 0.3.0 → 0.4.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.
@@ -8,6 +8,7 @@ export interface ResolvedConfig {
8
8
  gameId: string | null;
9
9
  clientId: string | null;
10
10
  redirectUri: string | null;
11
+ scope: 'profile' | 'profile saves';
11
12
  authUrl: string;
12
13
  restoreSession: boolean;
13
14
  allowInsecureHttp: boolean;
@@ -29,6 +30,6 @@ export declare function normalizeRedirectUri(value: string, allowInsecureHttp: b
29
30
  */
30
31
  export declare function validateGameId(value: string): string;
31
32
  export declare function validateClientId(value: string): string;
32
- /** For feature modules that need a game context (future phases). */
33
+ /** For feature modules that need a game context (Cloud Saves). */
33
34
  export declare function requireGameId(config: Pick<ResolvedConfig, 'gameId'>, feature: string): string;
34
35
  export declare function validateTimeout(value: number): number;
@@ -15,6 +15,7 @@ export function resolveConfig(options) {
15
15
  gameId,
16
16
  clientId: options.clientId === undefined ? gameId : validateClientId(options.clientId),
17
17
  redirectUri: resolveRedirectUri(options, allowInsecureHttp),
18
+ scope: validateScope(options.scope ?? 'profile'),
18
19
  restoreSession: validateBoolean('restoreSession', options.restoreSession ?? true),
19
20
  allowInsecureHttp,
20
21
  browser: options.browser,
@@ -27,6 +28,12 @@ function resolveRedirectUri(options, allowInsecureHttp) {
27
28
  const value = options.redirectUri ?? defaultRedirectUri(options.browser);
28
29
  return value === null ? null : normalizeRedirectUri(value, allowInsecureHttp);
29
30
  }
31
+ function validateScope(value) {
32
+ if (value !== 'profile' && value !== 'profile saves') {
33
+ throw new PixelumeConfigError('scope must be "profile" or "profile saves"');
34
+ }
35
+ return value;
36
+ }
30
37
  function validateBoolean(name, value) {
31
38
  if (typeof value !== 'boolean')
32
39
  throw new PixelumeConfigError(`${name} must be a boolean`);
@@ -123,7 +130,7 @@ export function validateClientId(value) {
123
130
  }
124
131
  return value;
125
132
  }
126
- /** For feature modules that need a game context (future phases). */
133
+ /** For feature modules that need a game context (Cloud Saves). */
127
134
  export function requireGameId(config, feature) {
128
135
  if (config.gameId === null) {
129
136
  throw new PixelumeConfigError(`${feature} requires the client to be created with a gameId`);
@@ -1 +1 @@
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"]}
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_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 scope: 'profile' | 'profile saves';\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 scope: validateScope(options.scope ?? 'profile'),\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 validateScope(value: unknown): 'profile' | 'profile saves' {\n if (value !== 'profile' && value !== 'profile saves') {\n throw new PixelumeConfigError('scope must be \"profile\" or \"profile saves\"');\n }\n return value;\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 (Cloud Saves). */\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"]}
@@ -7,12 +7,23 @@
7
7
  * {@link PixelumeHttpError.payload} as a non-enumerable property for programmatic inspection only.
8
8
  */
9
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
+ export type PixelumeAuthErrorReason = 'browser_unavailable' | 'storage_unavailable' | 'crypto_unavailable' | 'invalid_callback' | 'state_mismatch' | 'authorization_expired'
11
+ /** An operation needs a signed-in player and there is none. No request was sent. */
12
+ | 'not_authenticated'
13
+ /** The session ended and cannot be renewed (expired, revoked, logged out elsewhere). */
14
+ | 'session_expired'
15
+ /** The API rejected renewal because the game is disabled or unknown (`invalid_client`). */
16
+ | 'game_unavailable'
17
+ /**
18
+ * Renewal is not possible: the API rejected it for a non-recoverable reason (scope, origin,
19
+ * request) or the session has no refresh token (legacy authorization).
20
+ */
21
+ | 'renewal_rejected';
11
22
  /**
12
23
  * Error codes documented in the Pixelume API OpenAPI `ErrorEnvelope`.
13
24
  * The API may introduce new codes, so any string is accepted at runtime.
14
25
  */
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 & {});
26
+ 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' | 'OAUTH_UNSUPPORTED_GRANT_TYPE' | 'OAUTH_INSUFFICIENT_SCOPE' | 'FORBIDDEN_GAME' | 'GAME_INACTIVE' | 'SAVE_NOT_FOUND' | 'SAVE_REVISION_CONFLICT' | 'SAVE_TOO_LARGE' | 'SAVE_QUOTA_EXCEEDED' | 'INTERNAL_ERROR' | 'NOT_FOUND' | (string & {});
16
27
  /** Identifies the request that failed. `path` never contains the query string. */
17
28
  export interface PixelumeRequestInfo {
18
29
  readonly method: string;
@@ -56,6 +67,11 @@ export declare class PixelumeHttpError extends PixelumeRequestError {
56
67
  readonly apiMessage: string | null;
57
68
  /** Request ID from the envelope or the `x-request-id` header, useful for support. */
58
69
  readonly requestId: string | null;
70
+ /**
71
+ * `error.details` from the API envelope (e.g. `oauthError`, `quota`, conflict metadata), or
72
+ * `null`. The API only exposes non-sensitive details here.
73
+ */
74
+ readonly details: Readonly<Record<string, unknown>> | null;
59
75
  /**
60
76
  * Decoded response body (JSON value, text or `undefined`). Non-enumerable so that
61
77
  * `JSON.stringify(error)` and structured loggers do not dump it by accident.
@@ -67,9 +83,35 @@ export declare class PixelumeHttpError extends PixelumeRequestError {
67
83
  code: string | null;
68
84
  apiMessage: string | null;
69
85
  requestId: string | null;
86
+ details?: Readonly<Record<string, unknown>> | null;
70
87
  payload: unknown;
71
88
  });
72
89
  }
90
+ /** Server-side metadata of a cloud save, as reported by the API. */
91
+ export interface PixelumeSaveMetadata {
92
+ readonly gameId: string;
93
+ readonly slotId: string;
94
+ /** Version of the save format, chosen by the game (1–1 000 000). */
95
+ readonly schemaVersion: number;
96
+ /** Monotonic revision assigned by the server; never restarts, even after a delete. */
97
+ readonly revision: number;
98
+ /** UTF-8 size of the canonical JSON of `data`. */
99
+ readonly sizeBytes: number;
100
+ /** ISO 8601 timestamps assigned by the server. */
101
+ readonly createdAt: string;
102
+ readonly updatedAt: string;
103
+ }
104
+ /**
105
+ * `409 SAVE_REVISION_CONFLICT`: the slot changed since `expectedRevision` (another device, tab or a
106
+ * write whose response was lost). The SDK never retries or overwrites; the game decides.
107
+ */
108
+ export declare class PixelumeSaveConflictError extends PixelumeHttpError {
109
+ /** Current remote metadata, or `null` when the slot no longer has a live save. */
110
+ readonly current: PixelumeSaveMetadata | null;
111
+ /** The revision the rejected operation expected. */
112
+ readonly expectedRevision: number;
113
+ constructor(error: PixelumeHttpError, current: PixelumeSaveMetadata | null, expectedRevision: number);
114
+ }
73
115
  /**
74
116
  * The request did not produce an HTTP response: offline, DNS/TLS failure, connection reset,
75
117
  * or a browser CORS rejection (browsers do not distinguish these for security reasons).
@@ -52,6 +52,11 @@ export class PixelumeHttpError extends PixelumeRequestError {
52
52
  apiMessage;
53
53
  /** Request ID from the envelope or the `x-request-id` header, useful for support. */
54
54
  requestId;
55
+ /**
56
+ * `error.details` from the API envelope (e.g. `oauthError`, `quota`, conflict metadata), or
57
+ * `null`. The API only exposes non-sensitive details here.
58
+ */
59
+ details;
55
60
  constructor(details) {
56
61
  const suffix = details.code ? ` (${details.code})` : '';
57
62
  super('PixelumeHttpError', `Pixelume API request ${details.request.method} ${details.request.path} failed with status ${details.status}${suffix}`, details.request);
@@ -59,6 +64,7 @@ export class PixelumeHttpError extends PixelumeRequestError {
59
64
  this.code = details.code;
60
65
  this.apiMessage = details.apiMessage;
61
66
  this.requestId = details.requestId;
67
+ this.details = details.details ?? null;
62
68
  Object.defineProperty(this, 'payload', {
63
69
  value: details.payload,
64
70
  enumerable: false,
@@ -66,6 +72,30 @@ export class PixelumeHttpError extends PixelumeRequestError {
66
72
  });
67
73
  }
68
74
  }
75
+ /**
76
+ * `409 SAVE_REVISION_CONFLICT`: the slot changed since `expectedRevision` (another device, tab or a
77
+ * write whose response was lost). The SDK never retries or overwrites; the game decides.
78
+ */
79
+ export class PixelumeSaveConflictError extends PixelumeHttpError {
80
+ /** Current remote metadata, or `null` when the slot no longer has a live save. */
81
+ current;
82
+ /** The revision the rejected operation expected. */
83
+ expectedRevision;
84
+ constructor(error, current, expectedRevision) {
85
+ super({
86
+ request: { method: error.method, path: error.path },
87
+ status: error.status,
88
+ code: error.code,
89
+ apiMessage: error.apiMessage,
90
+ requestId: error.requestId,
91
+ details: error.details,
92
+ payload: error.payload,
93
+ });
94
+ this.name = 'PixelumeSaveConflictError';
95
+ this.current = current;
96
+ this.expectedRevision = expectedRevision;
97
+ }
98
+ }
69
99
  /**
70
100
  * The request did not produce an HTTP response: offline, DNS/TLS failure, connection reset,
71
101
  * or a browser CORS rejection (browsers do not distinguish these for security reasons).
@@ -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' | '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
+ {"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 /** An operation needs a signed-in player and there is none. No request was sent. */\n | 'not_authenticated'\n /** The session ended and cannot be renewed (expired, revoked, logged out elsewhere). */\n | 'session_expired'\n /** The API rejected renewal because the game is disabled or unknown (`invalid_client`). */\n | 'game_unavailable'\n /**\n * Renewal is not possible: the API rejected it for a non-recoverable reason (scope, origin,\n * request) or the session has no refresh token (legacy authorization).\n */\n | 'renewal_rejected';\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 | 'OAUTH_UNSUPPORTED_GRANT_TYPE'\n | 'OAUTH_INSUFFICIENT_SCOPE'\n | 'FORBIDDEN_GAME'\n | 'GAME_INACTIVE'\n | 'SAVE_NOT_FOUND'\n | 'SAVE_REVISION_CONFLICT'\n | 'SAVE_TOO_LARGE'\n | 'SAVE_QUOTA_EXCEEDED'\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 * `error.details` from the API envelope (e.g. `oauthError`, `quota`, conflict metadata), or\n * `null`. The API only exposes non-sensitive details here.\n */\n readonly details: Readonly<Record<string, unknown>> | 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 details?: Readonly<Record<string, unknown>> | 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 this.details = details.details ?? null;\n Object.defineProperty(this, 'payload', {\n value: details.payload,\n enumerable: false,\n writable: false,\n });\n }\n}\n\n/** Server-side metadata of a cloud save, as reported by the API. */\nexport interface PixelumeSaveMetadata {\n readonly gameId: string;\n readonly slotId: string;\n /** Version of the save format, chosen by the game (1–1 000 000). */\n readonly schemaVersion: number;\n /** Monotonic revision assigned by the server; never restarts, even after a delete. */\n readonly revision: number;\n /** UTF-8 size of the canonical JSON of `data`. */\n readonly sizeBytes: number;\n /** ISO 8601 timestamps assigned by the server. */\n readonly createdAt: string;\n readonly updatedAt: string;\n}\n\n/**\n * `409 SAVE_REVISION_CONFLICT`: the slot changed since `expectedRevision` (another device, tab or a\n * write whose response was lost). The SDK never retries or overwrites; the game decides.\n */\nexport class PixelumeSaveConflictError extends PixelumeHttpError {\n /** Current remote metadata, or `null` when the slot no longer has a live save. */\n readonly current: PixelumeSaveMetadata | null;\n /** The revision the rejected operation expected. */\n readonly expectedRevision: number;\n\n constructor(\n error: PixelumeHttpError,\n current: PixelumeSaveMetadata | null,\n expectedRevision: number,\n ) {\n super({\n request: { method: error.method, path: error.path },\n status: error.status,\n code: error.code,\n apiMessage: error.apiMessage,\n requestId: error.requestId,\n details: error.details,\n payload: error.payload,\n });\n this.name = 'PixelumeSaveConflictError';\n this.current = current;\n this.expectedRevision = expectedRevision;\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"]}
@@ -17,6 +17,12 @@ export interface PixelumeBrowserAdapter {
17
17
  replaceUrl(url: string): void;
18
18
  readonly sessionStorage: PixelumeSessionStorage;
19
19
  readonly crypto: Crypto;
20
+ /**
21
+ * Optional: calls `onResume` when the page may have been suspended (tab visible again, restored
22
+ * from the back/forward cache, back online) and returns an unsubscribe function. When omitted,
23
+ * the SDK listens to `visibilitychange`, `pageshow` and `online` on the browser globals.
24
+ */
25
+ subscribeResume?(onResume: () => void): () => void;
20
26
  }
21
27
  export interface PixelumeClientOptions {
22
28
  /**
@@ -37,6 +43,11 @@ export interface PixelumeClientOptions {
37
43
  clientId?: string;
38
44
  /** Exact callback URI registered for the OAuth client. Defaults to `<origin>/oauth/callback`. */
39
45
  redirectUri?: string;
46
+ /**
47
+ * Scope requested by `auth.login()`. Use `'profile saves'` for Cloud Saves; the game must have
48
+ * `saves` enabled in the Pixelume Game Registry. Defaults to `'profile'`.
49
+ */
50
+ scope?: 'profile' | 'profile saves';
40
51
  /** Pixelume Auth portal origin. Defaults to `https://auth.pixelume.com.br`. */
41
52
  authUrl?: string;
42
53
  /**
@@ -65,6 +76,7 @@ export interface PixelumeClientConfig {
65
76
  readonly gameId: string | null;
66
77
  readonly clientId: string | null;
67
78
  readonly redirectUri: string | null;
79
+ readonly scope: 'profile' | 'profile saves';
68
80
  readonly authUrl: string;
69
81
  readonly restoreSession: boolean;
70
82
  readonly timeoutMs: number;
@@ -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\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"]}
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 * Optional: calls `onResume` when the page may have been suspended (tab visible again, restored\n * from the back/forward cache, back online) and returns an unsubscribe function. When omitted,\n * the SDK listens to `visibilitychange`, `pageshow` and `online` on the browser globals.\n */\n subscribeResume?(onResume: () => void): () => void;\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 /**\n * Scope requested by `auth.login()`. Use `'profile saves'` for Cloud Saves; the game must have\n * `saves` enabled in the Pixelume Game Registry. Defaults to `'profile'`.\n */\n scope?: 'profile' | 'profile saves';\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 scope: 'profile' | 'profile saves';\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"]}
@@ -151,6 +151,7 @@ function toHttpError(info, response, body) {
151
151
  code: envelope?.error.code ?? null,
152
152
  apiMessage: envelope?.error.message ?? null,
153
153
  requestId: envelope?.error.requestId ?? response.headers.get('x-request-id'),
154
+ details: envelope?.error.details ?? null,
154
155
  payload: body.value,
155
156
  });
156
157
  }
@@ -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 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"]}
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 details: envelope?.error.details ?? null,\n payload: body.value,\n });\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  export { PixelumeClient, Pixelume } from './core/PixelumeClient.js';
2
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, PixelumeAuthError, PixelumeRequestError, PixelumeHttpError, PixelumeNetworkError, PixelumeTimeoutError, PixelumeAbortError, PixelumeInvalidResponseError, isPixelumeError, } from './core/errors.js';
5
- export type { PixelumeErrorKind, PixelumeApiErrorCode, PixelumeAuthErrorReason, PixelumeRequestInfo, } from './core/errors.js';
4
+ export { PixelumeError, PixelumeConfigError, PixelumeAuthError, PixelumeRequestError, PixelumeHttpError, PixelumeNetworkError, PixelumeTimeoutError, PixelumeAbortError, PixelumeInvalidResponseError, PixelumeSaveConflictError, isPixelumeError, } from './core/errors.js';
5
+ export type { PixelumeErrorKind, PixelumeApiErrorCode, PixelumeAuthErrorReason, PixelumeRequestInfo, PixelumeSaveMetadata, } from './core/errors.js';
6
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';
8
+ export type { PixelumeAuthApi, PixelumeAuthState, PixelumeAuthListener, PixelumeAnonymousReason, PixelumeLoginOptions, PixelumePlayer, PixelumeScopeRequest, } from './modules/auth/AuthModule.js';
9
+ export type { PixelumeScope, PixelumeSessionState, PixelumeSessionListener, } from './modules/auth/SessionManager.js';
10
+ export type { PixelumeSavesApi, PixelumeSave, PixelumeSaveList, PixelumeSaveWrite, PixelumeSaveResult, PixelumeSaveDeleteOptions, PixelumeSaveDeleteResult, PixelumeSavesActivity, PixelumeSavesListener, PixelumeSavesOperation, } from './modules/saves/SavesModule.js';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { PixelumeClient, Pixelume } from './core/PixelumeClient.js';
2
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, PixelumeAuthError, PixelumeRequestError, PixelumeHttpError, PixelumeNetworkError, PixelumeTimeoutError, PixelumeAbortError, PixelumeInvalidResponseError, isPixelumeError, } from './core/errors.js';
4
+ export { PixelumeError, PixelumeConfigError, PixelumeAuthError, PixelumeRequestError, PixelumeHttpError, PixelumeNetworkError, PixelumeTimeoutError, PixelumeAbortError, PixelumeInvalidResponseError, PixelumeSaveConflictError, 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,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"]}
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,yBAAyB,EACzB,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 PixelumeSaveConflictError,\n isPixelumeError,\n} from './core/errors.js';\nexport type {\n PixelumeErrorKind,\n PixelumeApiErrorCode,\n PixelumeAuthErrorReason,\n PixelumeRequestInfo,\n PixelumeSaveMetadata,\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 PixelumeScopeRequest,\n} from './modules/auth/AuthModule.js';\nexport type {\n PixelumeScope,\n PixelumeSessionState,\n PixelumeSessionListener,\n} from './modules/auth/SessionManager.js';\nexport type {\n PixelumeSavesApi,\n PixelumeSave,\n PixelumeSaveList,\n PixelumeSaveWrite,\n PixelumeSaveResult,\n PixelumeSaveDeleteOptions,\n PixelumeSaveDeleteResult,\n PixelumeSavesActivity,\n PixelumeSavesListener,\n PixelumeSavesOperation,\n} from './modules/saves/SavesModule.js';\n"]}
@@ -2,6 +2,7 @@ import { type PixelumeError } from '../../core/errors.js';
2
2
  import type { PixelumeRequestOptions } from '../../core/types.js';
3
3
  import type { ResolvedConfig } from '../../core/config.js';
4
4
  import type { HttpClient } from '../../http/HttpClient.js';
5
+ import { type PixelumeSessionListener, type PixelumeSessionState } from './SessionManager.js';
5
6
  export interface PixelumePlayer {
6
7
  readonly id: string;
7
8
  readonly displayName: string;
@@ -27,9 +28,14 @@ export type PixelumeAuthState = {
27
28
  readonly player: PixelumePlayer;
28
29
  };
29
30
  export type PixelumeAuthListener = (state: PixelumeAuthState, previous: PixelumeAuthState) => void;
31
+ /**
32
+ * OAuth scope requested at login. `profile` identifies the player; `profile saves` also allows
33
+ * Cloud Saves and requires `saves` to be enabled for the game in the Pixelume Game Registry.
34
+ */
35
+ export type PixelumeScopeRequest = 'profile' | 'profile saves';
30
36
  export interface PixelumeLoginOptions {
31
- /** OAuth scope. The current API accepts only `profile`. */
32
- readonly scope?: 'profile';
37
+ /** Defaults to the client `scope` option (`profile` unless configured). */
38
+ readonly scope?: PixelumeScopeRequest;
33
39
  }
34
40
  export interface PixelumeAuthApi {
35
41
  /** Current synchronous authentication state. */
@@ -38,15 +44,34 @@ export interface PixelumeAuthApi {
38
44
  login(options?: PixelumeLoginOptions): Promise<void>;
39
45
  /** Returns the current player, revalidating the memory-only access token with the API. */
40
46
  getPlayer(options?: PixelumeRequestOptions): Promise<PixelumePlayer | null>;
41
- /** Revokes this game's access token and clears local authentication state. */
47
+ /**
48
+ * Revokes this game's authorization (refresh token family and access tokens) and clears local
49
+ * authentication state. The central Pixelume session in the portal is not affected.
50
+ */
42
51
  logout(options?: PixelumeRequestOptions): Promise<void>;
43
52
  /** Subscribes to state changes. The returned function removes the listener. */
44
53
  subscribe(listener: PixelumeAuthListener): () => void;
54
+ /**
55
+ * Token lifecycle of the signed-in player (scopes, expiry, renewal in flight, last transient
56
+ * renewal error). Never exposes tokens.
57
+ */
58
+ readonly session: PixelumeSessionState;
59
+ /** Subscribes to `session` changes. The returned function removes the listener. */
60
+ subscribeSession(listener: PixelumeSessionListener): () => void;
61
+ /**
62
+ * Renews the access token now. Renewal is automatic; call this only to renew ahead of a
63
+ * critical operation. Rejects with the transient error (the session is kept and retried) or
64
+ * with `PixelumeAuthError` when there is no session or it ended.
65
+ */
66
+ refresh(options?: PixelumeRequestOptions): Promise<void>;
45
67
  }
46
68
  export declare class AuthModule implements PixelumeAuthApi {
47
69
  #private;
48
70
  constructor(http: HttpClient, config: ResolvedConfig);
49
71
  get state(): PixelumeAuthState;
72
+ get session(): PixelumeSessionState;
73
+ subscribeSession(listener: PixelumeSessionListener): () => void;
74
+ refresh(options?: PixelumeRequestOptions): Promise<void>;
50
75
  initialize(): Promise<PixelumeAuthState>;
51
76
  subscribe(listener: PixelumeAuthListener): () => void;
52
77
  login(options?: PixelumeLoginOptions): Promise<void>;