@playcademy/sdk 0.3.2 → 0.3.4

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.
package/dist/index.d.ts CHANGED
@@ -7,6 +7,21 @@ import { AUTH_PROVIDER_IDS } from '@playcademy/constants';
7
7
  declare class PlaycademyError extends Error {
8
8
  constructor(message: string);
9
9
  }
10
+ type CorsProbeResult = 'reachable' | 'unreachable' | 'skipped';
11
+ interface ManifestErrorDetails {
12
+ manifestUrl: string;
13
+ manifestHost: string;
14
+ deploymentUrl: string;
15
+ fetchOutcome: 'network_error' | 'bad_status' | 'invalid_body';
16
+ retryCount: number;
17
+ durationMs: number;
18
+ fetchErrorMessage?: string;
19
+ corsProbe?: CorsProbeResult;
20
+ status?: number;
21
+ contentType?: string;
22
+ cfRay?: string;
23
+ redirected?: boolean;
24
+ }
10
25
  /**
11
26
  * Error codes returned by the API.
12
27
  * These map to specific error types and HTTP status codes.
@@ -855,6 +870,7 @@ declare abstract class PlaycademyBaseClient {
855
870
  };
856
871
  protected initPayload?: InitPayload;
857
872
  protected connectionManager?: ConnectionManager;
873
+ protected launchId?: string;
858
874
  /**
859
875
  * Internal session manager for automatic session lifecycle.
860
876
  * @private
@@ -883,6 +899,7 @@ declare abstract class PlaycademyBaseClient {
883
899
  * Sets the authentication token for API requests.
884
900
  */
885
901
  setToken(token: string | null, tokenType?: TokenType): void;
902
+ setLaunchId(launchId: string | null | undefined): void;
886
903
  /**
887
904
  * Gets the current token type.
888
905
  */
@@ -1308,6 +1325,7 @@ interface ClientConfig {
1308
1325
  token?: string;
1309
1326
  tokenType?: TokenType;
1310
1327
  gameId?: string;
1328
+ launchId?: string;
1311
1329
  autoStartSession?: boolean;
1312
1330
  onDisconnect?: DisconnectHandler;
1313
1331
  enableConnectionMonitoring?: boolean;
@@ -2301,4 +2319,4 @@ declare class PlaycademyMessaging {
2301
2319
  declare const messaging: PlaycademyMessaging;
2302
2320
 
2303
2321
  export { ApiError, ConnectionManager, ConnectionMonitor, MessageEvents, PlaycademyClient, PlaycademyError, extractApiErrorInfo, messaging };
2304
- export type { ApiErrorCode, ApiErrorInfo, ConnectionMonitorConfig, ConnectionState, ConnectionStatePayload, DevUploadEvent, DevUploadHooks, DisconnectContext, DisconnectHandler, DisplayAlertPayload, ErrorResponseBody };
2322
+ export type { ApiErrorCode, ApiErrorInfo, ConnectionMonitorConfig, ConnectionState, ConnectionStatePayload, DevUploadEvent, DevUploadHooks, DisconnectContext, DisconnectHandler, DisplayAlertPayload, ErrorResponseBody, ManifestErrorDetails };
package/dist/index.js CHANGED
@@ -407,6 +407,18 @@ class PlaycademyError extends Error {
407
407
  }
408
408
  }
409
409
 
410
+ class ManifestError extends PlaycademyError {
411
+ kind;
412
+ details;
413
+ constructor(message, kind, details) {
414
+ super(message);
415
+ this.name = "ManifestError";
416
+ this.kind = kind;
417
+ this.details = details;
418
+ Object.setPrototypeOf(this, ManifestError.prototype);
419
+ }
420
+ }
421
+
410
422
  class ApiError extends Error {
411
423
  code;
412
424
  details;
@@ -1915,7 +1927,158 @@ class ConnectionManager {
1915
1927
  }
1916
1928
  }
1917
1929
  }
1918
- // src/core/request.ts
1930
+ // src/core/transport/retry.ts
1931
+ var RETRY_DELAYS_MS = [500, 1500];
1932
+ function wait(ms) {
1933
+ return new Promise((resolve) => setTimeout(resolve, ms));
1934
+ }
1935
+ var retryRuntime = { wait };
1936
+ function isRetryableStatus(status) {
1937
+ return status === 429 || status >= 500;
1938
+ }
1939
+ async function fetchWithRetry(url, init2) {
1940
+ const startedAt = Date.now();
1941
+ for (let attempt = 0;attempt <= RETRY_DELAYS_MS.length; attempt++) {
1942
+ const retryDelayMs = RETRY_DELAYS_MS[attempt];
1943
+ const canRetry = init2.method === "GET" && retryDelayMs !== undefined;
1944
+ try {
1945
+ const response = await fetch(url, init2);
1946
+ if (canRetry && isRetryableStatus(response.status)) {
1947
+ await retryRuntime.wait(retryDelayMs);
1948
+ } else {
1949
+ return { response, retryCount: attempt, durationMs: Date.now() - startedAt };
1950
+ }
1951
+ } catch (error) {
1952
+ if (canRetry && error instanceof TypeError) {
1953
+ await retryRuntime.wait(retryDelayMs);
1954
+ } else {
1955
+ return { error, retryCount: attempt, durationMs: Date.now() - startedAt };
1956
+ }
1957
+ }
1958
+ }
1959
+ return {
1960
+ error: new PlaycademyError("Request failed after exhausting retries"),
1961
+ retryCount: RETRY_DELAYS_MS.length,
1962
+ durationMs: Date.now() - startedAt
1963
+ };
1964
+ }
1965
+ function unwrapFetchResult(result) {
1966
+ if (result.error) {
1967
+ throw result.error;
1968
+ }
1969
+ if (!result.response) {
1970
+ throw new PlaycademyError("Request failed after exhausting retries");
1971
+ }
1972
+ return result.response;
1973
+ }
1974
+ // src/core/transport/manifest.ts
1975
+ var CORS_PROBE_TIMEOUT_MS = 3000;
1976
+ async function probeCorsReachability(url) {
1977
+ if (typeof globalThis.AbortController === "undefined") {
1978
+ return "skipped";
1979
+ }
1980
+ const controller = new AbortController;
1981
+ const timer = setTimeout(() => controller.abort(), CORS_PROBE_TIMEOUT_MS);
1982
+ try {
1983
+ await fetch(url, { mode: "no-cors", signal: controller.signal });
1984
+ return "reachable";
1985
+ } catch {
1986
+ return "unreachable";
1987
+ } finally {
1988
+ clearTimeout(timer);
1989
+ }
1990
+ }
1991
+ var MAX_FETCH_ERROR_MESSAGE_LENGTH = 512;
1992
+ function getFetchErrorMessage(error) {
1993
+ let raw;
1994
+ if (error instanceof Error) {
1995
+ raw = error.message;
1996
+ } else if (typeof error === "string") {
1997
+ raw = error;
1998
+ }
1999
+ if (!raw) {
2000
+ return;
2001
+ }
2002
+ const normalized = raw.replace(/\s+/g, " ").trim();
2003
+ if (!normalized) {
2004
+ return;
2005
+ }
2006
+ return normalized.slice(0, MAX_FETCH_ERROR_MESSAGE_LENGTH);
2007
+ }
2008
+ async function fetchManifest(deploymentUrl) {
2009
+ const manifestUrl = `${deploymentUrl.replace(/\/$/, "")}/playcademy.manifest.json`;
2010
+ const result = await fetchWithRetry(manifestUrl, { method: "GET" });
2011
+ let manifestHost;
2012
+ try {
2013
+ manifestHost = new URL(manifestUrl).host;
2014
+ } catch {
2015
+ manifestHost = manifestUrl;
2016
+ }
2017
+ const base = {
2018
+ manifestUrl,
2019
+ manifestHost,
2020
+ deploymentUrl,
2021
+ retryCount: result.retryCount,
2022
+ durationMs: result.durationMs
2023
+ };
2024
+ if (result.error || !result.response) {
2025
+ log.error(`[Playcademy SDK] Error fetching manifest from ${manifestUrl}:`, {
2026
+ error: result.error
2027
+ });
2028
+ throw new ManifestError("Failed to load game manifest", "temporary", {
2029
+ ...base,
2030
+ fetchOutcome: "network_error",
2031
+ ...result.error ? { fetchErrorMessage: getFetchErrorMessage(result.error) } : {}
2032
+ });
2033
+ }
2034
+ const response = result.response;
2035
+ if (!response.ok) {
2036
+ log.error(`[fetchManifest] Failed to fetch manifest from ${manifestUrl}. Status: ${response.status}`);
2037
+ throw new ManifestError(`Failed to fetch manifest: ${response.status} ${response.statusText}`, isRetryableStatus(response.status) ? "temporary" : "permanent", {
2038
+ ...base,
2039
+ fetchOutcome: "bad_status",
2040
+ status: response.status,
2041
+ contentType: response.headers.get("content-type") ?? undefined,
2042
+ cfRay: response.headers.get("cf-ray") ?? undefined,
2043
+ redirected: response.redirected
2044
+ });
2045
+ }
2046
+ try {
2047
+ return await response.json();
2048
+ } catch (error) {
2049
+ log.error(`[Playcademy SDK] Error parsing manifest from ${manifestUrl}:`, {
2050
+ error
2051
+ });
2052
+ throw new ManifestError("Failed to parse game manifest", "permanent", {
2053
+ ...base,
2054
+ fetchOutcome: "invalid_body",
2055
+ status: response.status,
2056
+ contentType: response.headers.get("content-type") ?? undefined,
2057
+ cfRay: response.headers.get("cf-ray") ?? undefined,
2058
+ redirected: response.redirected
2059
+ });
2060
+ }
2061
+ }
2062
+ // src/core/transport/request.ts
2063
+ function prepareRequestBody(body, headers) {
2064
+ if (body instanceof FormData) {
2065
+ return body;
2066
+ }
2067
+ if (body instanceof ArrayBuffer || body instanceof Blob || ArrayBuffer.isView(body)) {
2068
+ if (!headers["Content-Type"]) {
2069
+ headers["Content-Type"] = "application/octet-stream";
2070
+ }
2071
+ return body;
2072
+ }
2073
+ if (body !== undefined && body !== null) {
2074
+ if (headers["Content-Type"]) {
2075
+ return typeof body === "string" ? body : JSON.stringify(body);
2076
+ }
2077
+ headers["Content-Type"] = "application/json";
2078
+ return JSON.stringify(body);
2079
+ }
2080
+ return;
2081
+ }
1919
2082
  function checkDevWarnings(data) {
1920
2083
  if (!data || typeof data !== "object") {
1921
2084
  return;
@@ -1943,25 +2106,6 @@ function checkDevWarnings(data) {
1943
2106
  }
1944
2107
  }
1945
2108
  }
1946
- function prepareRequestBody(body, headers) {
1947
- if (body instanceof FormData) {
1948
- return body;
1949
- }
1950
- if (body instanceof ArrayBuffer || body instanceof Blob || ArrayBuffer.isView(body)) {
1951
- if (!headers["Content-Type"]) {
1952
- headers["Content-Type"] = "application/octet-stream";
1953
- }
1954
- return body;
1955
- }
1956
- if (body !== undefined && body !== null) {
1957
- if (headers["Content-Type"]) {
1958
- return typeof body === "string" ? body : JSON.stringify(body);
1959
- }
1960
- headers["Content-Type"] = "application/json";
1961
- return JSON.stringify(body);
1962
- }
1963
- return;
1964
- }
1965
2109
  async function request({
1966
2110
  path,
1967
2111
  baseUrl,
@@ -1973,12 +2117,12 @@ async function request({
1973
2117
  const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
1974
2118
  const headers = { ...extraHeaders };
1975
2119
  const payload = prepareRequestBody(body, headers);
1976
- const res = await fetch(url, {
2120
+ const res = unwrapFetchResult(await fetchWithRetry(url, {
1977
2121
  method,
1978
2122
  headers,
1979
2123
  body: payload,
1980
2124
  credentials: "omit"
1981
- });
2125
+ }));
1982
2126
  if (raw) {
1983
2127
  return res;
1984
2128
  }
@@ -2008,26 +2152,6 @@ async function request({
2008
2152
  const rawText = await res.text().catch(() => "");
2009
2153
  return rawText && rawText.length > 0 ? rawText : undefined;
2010
2154
  }
2011
- async function fetchManifest(deploymentUrl) {
2012
- const manifestUrl = `${deploymentUrl.replace(/\/$/, "")}/playcademy.manifest.json`;
2013
- try {
2014
- const response = await fetch(manifestUrl);
2015
- if (!response.ok) {
2016
- log.error(`[fetchManifest] Failed to fetch manifest from ${manifestUrl}. Status: ${response.status}`);
2017
- throw new PlaycademyError(`Failed to fetch manifest: ${response.status} ${response.statusText}`);
2018
- }
2019
- return await response.json();
2020
- } catch (error) {
2021
- if (error instanceof PlaycademyError) {
2022
- throw error;
2023
- }
2024
- log.error(`[Playcademy SDK] Error fetching or parsing manifest from ${manifestUrl}:`, {
2025
- error
2026
- });
2027
- throw new PlaycademyError("Failed to load or parse game manifest");
2028
- }
2029
- }
2030
-
2031
2155
  // src/clients/base.ts
2032
2156
  class PlaycademyBaseClient {
2033
2157
  baseUrl;
@@ -2040,6 +2164,7 @@ class PlaycademyBaseClient {
2040
2164
  authContext;
2041
2165
  initPayload;
2042
2166
  connectionManager;
2167
+ launchId;
2043
2168
  _sessionManager = {
2044
2169
  startSession: async (gameId) => this.request(`/games/${gameId}/sessions`, "POST"),
2045
2170
  endSession: async (sessionId, gameId) => this.request(`/games/${gameId}/sessions/${sessionId}`, "DELETE")
@@ -2048,6 +2173,7 @@ class PlaycademyBaseClient {
2048
2173
  this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
2049
2174
  this.gameUrl = config?.gameUrl;
2050
2175
  this.gameId = config?.gameId;
2176
+ this.launchId = config?.launchId ?? undefined;
2051
2177
  this.config = config || {};
2052
2178
  this.authStrategy = createAuthStrategy(config?.token ?? null, config?.tokenType);
2053
2179
  this._detectAuthContext();
@@ -2075,6 +2201,9 @@ class PlaycademyBaseClient {
2075
2201
  this.authStrategy = createAuthStrategy(token, tokenType);
2076
2202
  this.emit("authChange", { token });
2077
2203
  }
2204
+ setLaunchId(launchId) {
2205
+ this.launchId = launchId ?? undefined;
2206
+ }
2078
2207
  getTokenType() {
2079
2208
  return this.authStrategy.getType();
2080
2209
  }
@@ -2117,7 +2246,8 @@ class PlaycademyBaseClient {
2117
2246
  async request(path, method, options) {
2118
2247
  const effectiveHeaders = {
2119
2248
  ...options?.headers,
2120
- ...this.authStrategy.getHeaders()
2249
+ ...this.authStrategy.getHeaders(),
2250
+ ...this.launchId ? { "x-playcademy-launch-id": this.launchId } : {}
2121
2251
  };
2122
2252
  try {
2123
2253
  const result = await request({
@@ -5,6 +5,9 @@ import { z } from 'zod';
5
5
  import { SchemaInfo } from '@playcademy/cloudflare';
6
6
  import { AUTH_PROVIDER_IDS } from '@playcademy/constants';
7
7
 
8
+ /** Permitted HTTP verbs */
9
+ type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
10
+
8
11
  /**
9
12
  * Game Types
10
13
  *
@@ -82,8 +85,181 @@ interface DomainValidationRecords {
82
85
  }[];
83
86
  }
84
87
 
85
- /** Permitted HTTP verbs */
86
- type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
88
+ /**
89
+ * Base error class for Cademy SDK specific errors.
90
+ */
91
+ declare class PlaycademyError extends Error {
92
+ constructor(message: string);
93
+ }
94
+ type ManifestErrorKind = 'temporary' | 'permanent';
95
+ type CorsProbeResult = 'reachable' | 'unreachable' | 'skipped';
96
+ interface ManifestErrorDetails {
97
+ manifestUrl: string;
98
+ manifestHost: string;
99
+ deploymentUrl: string;
100
+ fetchOutcome: 'network_error' | 'bad_status' | 'invalid_body';
101
+ retryCount: number;
102
+ durationMs: number;
103
+ fetchErrorMessage?: string;
104
+ corsProbe?: CorsProbeResult;
105
+ status?: number;
106
+ contentType?: string;
107
+ cfRay?: string;
108
+ redirected?: boolean;
109
+ }
110
+ declare class ManifestError extends PlaycademyError {
111
+ readonly kind: ManifestErrorKind;
112
+ readonly details: ManifestErrorDetails | undefined;
113
+ constructor(message: string, kind: ManifestErrorKind, details?: ManifestErrorDetails);
114
+ }
115
+ /**
116
+ * Error codes returned by the API.
117
+ * These map to specific error types and HTTP status codes.
118
+ */
119
+ type ApiErrorCode = 'BAD_REQUEST' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'ACCESS_DENIED' | 'NOT_FOUND' | 'METHOD_NOT_ALLOWED' | 'CONFLICT' | 'ALREADY_EXISTS' | 'GONE' | 'PRECONDITION_FAILED' | 'PAYLOAD_TOO_LARGE' | 'VALIDATION_FAILED' | 'TOO_MANY_REQUESTS' | 'RATE_LIMITED' | 'EXPIRED' | 'INTERNAL' | 'INTERNAL_ERROR' | 'NOT_IMPLEMENTED' | 'SERVICE_UNAVAILABLE' | 'TIMEOUT' | string;
120
+ /**
121
+ * Structure of error response bodies returned by API endpoints.
122
+ *
123
+ * @example
124
+ * ```json
125
+ * {
126
+ * "error": {
127
+ * "code": "NOT_FOUND",
128
+ * "message": "Item not found",
129
+ * "details": { "identifier": "abc123" }
130
+ * }
131
+ * }
132
+ * ```
133
+ */
134
+ interface ErrorResponseBody {
135
+ error?: {
136
+ code?: string;
137
+ message?: string;
138
+ details?: unknown;
139
+ };
140
+ }
141
+ /**
142
+ * API error thrown when a request fails.
143
+ *
144
+ * Contains structured error information from the API response:
145
+ * - `status` - HTTP status code (e.g., 404)
146
+ * - `code` - API error code (e.g., "NOT_FOUND")
147
+ * - `message` - Human-readable error message
148
+ * - `details` - Optional additional error context
149
+ *
150
+ * @example
151
+ * ```typescript
152
+ * try {
153
+ * await client.games.get('nonexistent')
154
+ * } catch (error) {
155
+ * if (error instanceof ApiError) {
156
+ * console.log(error.status) // 404
157
+ * console.log(error.code) // "NOT_FOUND"
158
+ * console.log(error.message) // "Game not found"
159
+ * console.log(error.details) // { identifier: "nonexistent" }
160
+ * }
161
+ * }
162
+ * ```
163
+ */
164
+ declare class ApiError extends Error {
165
+ /**
166
+ * API error code (e.g., "NOT_FOUND", "VALIDATION_FAILED").
167
+ * Use this for programmatic error handling.
168
+ */
169
+ readonly code: ApiErrorCode;
170
+ /**
171
+ * Additional error context from the API.
172
+ * Structure varies by error type (e.g., validation errors include field details).
173
+ */
174
+ readonly details: unknown;
175
+ /**
176
+ * Raw response body for debugging.
177
+ * @internal
178
+ */
179
+ readonly rawBody: unknown;
180
+ readonly status: number;
181
+ constructor(
182
+ /** HTTP status code */
183
+ status: number,
184
+ /** API error code */
185
+ code: ApiErrorCode,
186
+ /** Human-readable error message */
187
+ message: string,
188
+ /** Additional error context */
189
+ details?: unknown,
190
+ /** Raw response body */
191
+ rawBody?: unknown);
192
+ /**
193
+ * Create an ApiError from an HTTP response.
194
+ * Parses the structured error response from the API.
195
+ *
196
+ * @internal
197
+ */
198
+ static fromResponse(status: number, statusText: string, body: unknown): ApiError;
199
+ /**
200
+ * Check if this is a specific error type.
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * if (error.is('NOT_FOUND')) {
205
+ * // Handle not found
206
+ * } else if (error.is('VALIDATION_FAILED')) {
207
+ * // Handle validation error
208
+ * }
209
+ * ```
210
+ */
211
+ is(code: ApiErrorCode): boolean;
212
+ /**
213
+ * Check if this is a client error (4xx).
214
+ */
215
+ isClientError(): boolean;
216
+ /**
217
+ * Check if this is a server error (5xx).
218
+ */
219
+ isServerError(): boolean;
220
+ /**
221
+ * Check if this error is retryable.
222
+ * Server errors and rate limits are typically retryable.
223
+ */
224
+ isRetryable(): boolean;
225
+ }
226
+ /**
227
+ * Extracted error information for display purposes.
228
+ */
229
+ interface ApiErrorInfo {
230
+ /** HTTP status code */
231
+ status: number;
232
+ /** API error code */
233
+ code: ApiErrorCode;
234
+ /** Human-readable error message */
235
+ message: string;
236
+ /** Additional error context */
237
+ details?: unknown;
238
+ }
239
+ /**
240
+ * Extract useful error information from an API error.
241
+ * Useful for displaying errors to users in a friendly way.
242
+ *
243
+ * @example
244
+ * ```typescript
245
+ * try {
246
+ * await client.shop.purchase(itemId)
247
+ * } catch (error) {
248
+ * const info = extractApiErrorInfo(error)
249
+ * if (info) {
250
+ * showToast(`Error: ${info.message}`)
251
+ * }
252
+ * }
253
+ * ```
254
+ */
255
+ declare function extractApiErrorInfo(error: unknown): ApiErrorInfo | null;
256
+
257
+ /**
258
+ * Fire-and-forget reachability check using `no-cors` mode.
259
+ * Exported for use by the app telemetry layer — not awaited inside
260
+ * fetchManifest so it doesn't block the error screen.
261
+ */
262
+ declare function probeCorsReachability(url: string): Promise<CorsProbeResult>;
87
263
 
88
264
  /**
89
265
  * User Types
@@ -935,154 +1111,6 @@ interface SpriteConfigWithDimensions {
935
1111
  animations: Record<string, SpriteAnimationFrame>;
936
1112
  }
937
1113
 
938
- /**
939
- * Base error class for Cademy SDK specific errors.
940
- */
941
- declare class PlaycademyError extends Error {
942
- constructor(message: string);
943
- }
944
- /**
945
- * Error codes returned by the API.
946
- * These map to specific error types and HTTP status codes.
947
- */
948
- type ApiErrorCode = 'BAD_REQUEST' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'ACCESS_DENIED' | 'NOT_FOUND' | 'METHOD_NOT_ALLOWED' | 'CONFLICT' | 'ALREADY_EXISTS' | 'GONE' | 'PRECONDITION_FAILED' | 'PAYLOAD_TOO_LARGE' | 'VALIDATION_FAILED' | 'TOO_MANY_REQUESTS' | 'RATE_LIMITED' | 'EXPIRED' | 'INTERNAL' | 'INTERNAL_ERROR' | 'NOT_IMPLEMENTED' | 'SERVICE_UNAVAILABLE' | 'TIMEOUT' | string;
949
- /**
950
- * Structure of error response bodies returned by API endpoints.
951
- *
952
- * @example
953
- * ```json
954
- * {
955
- * "error": {
956
- * "code": "NOT_FOUND",
957
- * "message": "Item not found",
958
- * "details": { "identifier": "abc123" }
959
- * }
960
- * }
961
- * ```
962
- */
963
- interface ErrorResponseBody {
964
- error?: {
965
- code?: string;
966
- message?: string;
967
- details?: unknown;
968
- };
969
- }
970
- /**
971
- * API error thrown when a request fails.
972
- *
973
- * Contains structured error information from the API response:
974
- * - `status` - HTTP status code (e.g., 404)
975
- * - `code` - API error code (e.g., "NOT_FOUND")
976
- * - `message` - Human-readable error message
977
- * - `details` - Optional additional error context
978
- *
979
- * @example
980
- * ```typescript
981
- * try {
982
- * await client.games.get('nonexistent')
983
- * } catch (error) {
984
- * if (error instanceof ApiError) {
985
- * console.log(error.status) // 404
986
- * console.log(error.code) // "NOT_FOUND"
987
- * console.log(error.message) // "Game not found"
988
- * console.log(error.details) // { identifier: "nonexistent" }
989
- * }
990
- * }
991
- * ```
992
- */
993
- declare class ApiError extends Error {
994
- /**
995
- * API error code (e.g., "NOT_FOUND", "VALIDATION_FAILED").
996
- * Use this for programmatic error handling.
997
- */
998
- readonly code: ApiErrorCode;
999
- /**
1000
- * Additional error context from the API.
1001
- * Structure varies by error type (e.g., validation errors include field details).
1002
- */
1003
- readonly details: unknown;
1004
- /**
1005
- * Raw response body for debugging.
1006
- * @internal
1007
- */
1008
- readonly rawBody: unknown;
1009
- readonly status: number;
1010
- constructor(
1011
- /** HTTP status code */
1012
- status: number,
1013
- /** API error code */
1014
- code: ApiErrorCode,
1015
- /** Human-readable error message */
1016
- message: string,
1017
- /** Additional error context */
1018
- details?: unknown,
1019
- /** Raw response body */
1020
- rawBody?: unknown);
1021
- /**
1022
- * Create an ApiError from an HTTP response.
1023
- * Parses the structured error response from the API.
1024
- *
1025
- * @internal
1026
- */
1027
- static fromResponse(status: number, statusText: string, body: unknown): ApiError;
1028
- /**
1029
- * Check if this is a specific error type.
1030
- *
1031
- * @example
1032
- * ```typescript
1033
- * if (error.is('NOT_FOUND')) {
1034
- * // Handle not found
1035
- * } else if (error.is('VALIDATION_FAILED')) {
1036
- * // Handle validation error
1037
- * }
1038
- * ```
1039
- */
1040
- is(code: ApiErrorCode): boolean;
1041
- /**
1042
- * Check if this is a client error (4xx).
1043
- */
1044
- isClientError(): boolean;
1045
- /**
1046
- * Check if this is a server error (5xx).
1047
- */
1048
- isServerError(): boolean;
1049
- /**
1050
- * Check if this error is retryable.
1051
- * Server errors and rate limits are typically retryable.
1052
- */
1053
- isRetryable(): boolean;
1054
- }
1055
- /**
1056
- * Extracted error information for display purposes.
1057
- */
1058
- interface ApiErrorInfo {
1059
- /** HTTP status code */
1060
- status: number;
1061
- /** API error code */
1062
- code: ApiErrorCode;
1063
- /** Human-readable error message */
1064
- message: string;
1065
- /** Additional error context */
1066
- details?: unknown;
1067
- }
1068
- /**
1069
- * Extract useful error information from an API error.
1070
- * Useful for displaying errors to users in a friendly way.
1071
- *
1072
- * @example
1073
- * ```typescript
1074
- * try {
1075
- * await client.shop.purchase(itemId)
1076
- * } catch (error) {
1077
- * const info = extractApiErrorInfo(error)
1078
- * if (info) {
1079
- * showToast(`Error: ${info.message}`)
1080
- * }
1081
- * }
1082
- * ```
1083
- */
1084
- declare function extractApiErrorInfo(error: unknown): ApiErrorInfo | null;
1085
-
1086
1114
  /**
1087
1115
  * Connection monitoring types
1088
1116
  *
@@ -5629,6 +5657,7 @@ declare abstract class PlaycademyBaseClient {
5629
5657
  };
5630
5658
  protected initPayload?: InitPayload;
5631
5659
  protected connectionManager?: ConnectionManager;
5660
+ protected launchId?: string;
5632
5661
  /**
5633
5662
  * Internal session manager for automatic session lifecycle.
5634
5663
  * @private
@@ -5657,6 +5686,7 @@ declare abstract class PlaycademyBaseClient {
5657
5686
  * Sets the authentication token for API requests.
5658
5687
  */
5659
5688
  setToken(token: string | null, tokenType?: TokenType): void;
5689
+ setLaunchId(launchId: string | null | undefined): void;
5660
5690
  /**
5661
5691
  * Gets the current token type.
5662
5692
  */
@@ -5954,6 +5984,7 @@ interface ClientConfig {
5954
5984
  token?: string;
5955
5985
  tokenType?: TokenType;
5956
5986
  gameId?: string;
5987
+ launchId?: string;
5957
5988
  autoStartSession?: boolean;
5958
5989
  onDisconnect?: DisconnectHandler;
5959
5990
  enableConnectionMonitoring?: boolean;
@@ -7629,5 +7660,5 @@ declare class PlaycademyInternalClient extends PlaycademyBaseClient {
7629
7660
  };
7630
7661
  }
7631
7662
 
7632
- export { AchievementCompletionType, ApiError, ConnectionManager, ConnectionMonitor, MessageEvents, NotificationStatus, NotificationType, PlaycademyInternalClient as PlaycademyClient, PlaycademyError, PlaycademyInternalClient, extractApiErrorInfo, messaging };
7633
- export type { AchievementCurrent, AchievementHistoryEntry, AchievementProgressResponse, AchievementScopeType, AchievementWithStatus, ApiErrorCode, ApiErrorInfo, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, AuthenticatedUser, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, CharacterComponentRow as CharacterComponent, CharacterComponentType, CharacterComponentWithSpriteUrl, CharacterComponentsOptions, ClientConfig, ClientEvents, ConnectionMonitorConfig, ConnectionState, ConnectionStatePayload, CourseXp, CreateCharacterData, CreateMapObjectData, CurrencyRow as Currency, DevUploadEvent, DevUploadHooks, DeveloperStatusEnumType, DeveloperStatusResponse, DeveloperStatusValue, DisconnectContext, DisconnectHandler, DisplayAlertPayload, ErrorResponseBody, EventListeners, ExternalGame, FetchedGame, Game, GameContextPayload, GameCustomHostname, GameInitUser, GameLeaderboardEntry, MapRow as GameMap, GamePlatform, GameRow as GameRecord, GameSessionRow as GameSession, GameTimebackIntegration, GameTokenResponse, GameType, GameUser, GetXpOptions, HostedGame, InitPayload, InsertCurrencyInput, InsertItemInput, InsertShopListingInput, InteractionType, InventoryItemRow as InventoryItem, InventoryItemWithItem, InventoryMutationResponse, ItemRow as Item, ItemType, KVKeyEntry, KVKeyMetadata, KVSeedEntry, KVStatsResponse, KeyEventPayload, LeaderboardEntry, LeaderboardOptions, LeaderboardTimeframe, LevelConfigRow as LevelConfig, LevelProgressResponse, LevelUpCheckResult, LoginResponse, ManifestV1, MapData, MapElementRow as MapElement, MapElementMetadata, MapElementWithGame, MapObjectRow as MapObject, MapObjectWithItem, NotificationRow as Notification, NotificationStats, PlaceableItemMetadata, PlatformTimebackUser, PlatformTimebackUserContext, PlaycademyServerClientConfig, PlaycademyServerClientState, PlayerCharacterRow as PlayerCharacter, PlayerCharacterAccessoryRow as PlayerCharacterAccessory, PlayerCurrency, PlayerInventoryItem, PlayerProfile, PlayerSessionPayload, PopulateStudentResponse, RealtimeTokenResponse, ScoreSubmission, ShopCurrency, ShopDisplayItem, ShopListingRow as ShopListing, ShopViewResponse, SpriteAnimationFrame, SpriteConfigWithDimensions, SpriteTemplateRow as SpriteTemplate, SpriteTemplateData, StartSessionResponse, TelemetryPayload, TimebackEnrollment, TimebackInitContext, TimebackOrganization, TimebackUser, TimebackUserContext, TimebackUserXp, TodayXpResponse, TokenRefreshPayload, TokenType, TotalXpResponse, UpdateCharacterData, UpdateCurrencyInput, UpdateItemInput, UpdateShopListingInput, UpsertGameMetadataInput, UserRow as User, UserEnrollment, UserInfo, UserLevelRow as UserLevel, UserLevelWithConfig, UserOrganization, UserRank, UserRankResponse, UserRoleEnumType, UserScore, UserTimebackData, XPAddResult, XpHistoryResponse, XpResponse, XpSummaryResponse };
7663
+ export { AchievementCompletionType, ApiError, ConnectionManager, ConnectionMonitor, ManifestError, MessageEvents, NotificationStatus, NotificationType, PlaycademyInternalClient as PlaycademyClient, PlaycademyError, PlaycademyInternalClient, extractApiErrorInfo, messaging, probeCorsReachability };
7664
+ export type { AchievementCurrent, AchievementHistoryEntry, AchievementProgressResponse, AchievementScopeType, AchievementWithStatus, ApiErrorCode, ApiErrorInfo, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, AuthenticatedUser, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, CharacterComponentRow as CharacterComponent, CharacterComponentType, CharacterComponentWithSpriteUrl, CharacterComponentsOptions, ClientConfig, ClientEvents, ConnectionMonitorConfig, ConnectionState, ConnectionStatePayload, CorsProbeResult, CourseXp, CreateCharacterData, CreateMapObjectData, CurrencyRow as Currency, DevUploadEvent, DevUploadHooks, DeveloperStatusEnumType, DeveloperStatusResponse, DeveloperStatusValue, DisconnectContext, DisconnectHandler, DisplayAlertPayload, ErrorResponseBody, EventListeners, ExternalGame, FetchedGame, Game, GameContextPayload, GameCustomHostname, GameInitUser, GameLeaderboardEntry, MapRow as GameMap, GamePlatform, GameRow as GameRecord, GameSessionRow as GameSession, GameTimebackIntegration, GameTokenResponse, GameType, GameUser, GetXpOptions, HostedGame, InitPayload, InsertCurrencyInput, InsertItemInput, InsertShopListingInput, InteractionType, InventoryItemRow as InventoryItem, InventoryItemWithItem, InventoryMutationResponse, ItemRow as Item, ItemType, KVKeyEntry, KVKeyMetadata, KVSeedEntry, KVStatsResponse, KeyEventPayload, LeaderboardEntry, LeaderboardOptions, LeaderboardTimeframe, LevelConfigRow as LevelConfig, LevelProgressResponse, LevelUpCheckResult, LoginResponse, ManifestErrorDetails, ManifestV1, MapData, MapElementRow as MapElement, MapElementMetadata, MapElementWithGame, MapObjectRow as MapObject, MapObjectWithItem, NotificationRow as Notification, NotificationStats, PlaceableItemMetadata, PlatformTimebackUser, PlatformTimebackUserContext, PlaycademyServerClientConfig, PlaycademyServerClientState, PlayerCharacterRow as PlayerCharacter, PlayerCharacterAccessoryRow as PlayerCharacterAccessory, PlayerCurrency, PlayerInventoryItem, PlayerProfile, PlayerSessionPayload, PopulateStudentResponse, RealtimeTokenResponse, ScoreSubmission, ShopCurrency, ShopDisplayItem, ShopListingRow as ShopListing, ShopViewResponse, SpriteAnimationFrame, SpriteConfigWithDimensions, SpriteTemplateRow as SpriteTemplate, SpriteTemplateData, StartSessionResponse, TelemetryPayload, TimebackEnrollment, TimebackInitContext, TimebackOrganization, TimebackUser, TimebackUserContext, TimebackUserXp, TodayXpResponse, TokenRefreshPayload, TokenType, TotalXpResponse, UpdateCharacterData, UpdateCurrencyInput, UpdateItemInput, UpdateShopListingInput, UpsertGameMetadataInput, UserRow as User, UserEnrollment, UserInfo, UserLevelRow as UserLevel, UserLevelWithConfig, UserOrganization, UserRank, UserRankResponse, UserRoleEnumType, UserScore, UserTimebackData, XPAddResult, XpHistoryResponse, XpResponse, XpSummaryResponse };
package/dist/internal.js CHANGED
@@ -407,6 +407,18 @@ class PlaycademyError extends Error {
407
407
  }
408
408
  }
409
409
 
410
+ class ManifestError extends PlaycademyError {
411
+ kind;
412
+ details;
413
+ constructor(message, kind, details) {
414
+ super(message);
415
+ this.name = "ManifestError";
416
+ this.kind = kind;
417
+ this.details = details;
418
+ Object.setPrototypeOf(this, ManifestError.prototype);
419
+ }
420
+ }
421
+
410
422
  class ApiError extends Error {
411
423
  code;
412
424
  details;
@@ -2026,7 +2038,158 @@ function createDevNamespace(client) {
2026
2038
  }
2027
2039
  };
2028
2040
  }
2029
- // src/core/request.ts
2041
+ // src/core/transport/retry.ts
2042
+ var RETRY_DELAYS_MS = [500, 1500];
2043
+ function wait(ms) {
2044
+ return new Promise((resolve) => setTimeout(resolve, ms));
2045
+ }
2046
+ var retryRuntime = { wait };
2047
+ function isRetryableStatus(status) {
2048
+ return status === 429 || status >= 500;
2049
+ }
2050
+ async function fetchWithRetry(url, init2) {
2051
+ const startedAt = Date.now();
2052
+ for (let attempt = 0;attempt <= RETRY_DELAYS_MS.length; attempt++) {
2053
+ const retryDelayMs = RETRY_DELAYS_MS[attempt];
2054
+ const canRetry = init2.method === "GET" && retryDelayMs !== undefined;
2055
+ try {
2056
+ const response = await fetch(url, init2);
2057
+ if (canRetry && isRetryableStatus(response.status)) {
2058
+ await retryRuntime.wait(retryDelayMs);
2059
+ } else {
2060
+ return { response, retryCount: attempt, durationMs: Date.now() - startedAt };
2061
+ }
2062
+ } catch (error) {
2063
+ if (canRetry && error instanceof TypeError) {
2064
+ await retryRuntime.wait(retryDelayMs);
2065
+ } else {
2066
+ return { error, retryCount: attempt, durationMs: Date.now() - startedAt };
2067
+ }
2068
+ }
2069
+ }
2070
+ return {
2071
+ error: new PlaycademyError("Request failed after exhausting retries"),
2072
+ retryCount: RETRY_DELAYS_MS.length,
2073
+ durationMs: Date.now() - startedAt
2074
+ };
2075
+ }
2076
+ function unwrapFetchResult(result) {
2077
+ if (result.error) {
2078
+ throw result.error;
2079
+ }
2080
+ if (!result.response) {
2081
+ throw new PlaycademyError("Request failed after exhausting retries");
2082
+ }
2083
+ return result.response;
2084
+ }
2085
+ // src/core/transport/manifest.ts
2086
+ var CORS_PROBE_TIMEOUT_MS = 3000;
2087
+ async function probeCorsReachability(url) {
2088
+ if (typeof globalThis.AbortController === "undefined") {
2089
+ return "skipped";
2090
+ }
2091
+ const controller = new AbortController;
2092
+ const timer = setTimeout(() => controller.abort(), CORS_PROBE_TIMEOUT_MS);
2093
+ try {
2094
+ await fetch(url, { mode: "no-cors", signal: controller.signal });
2095
+ return "reachable";
2096
+ } catch {
2097
+ return "unreachable";
2098
+ } finally {
2099
+ clearTimeout(timer);
2100
+ }
2101
+ }
2102
+ var MAX_FETCH_ERROR_MESSAGE_LENGTH = 512;
2103
+ function getFetchErrorMessage(error) {
2104
+ let raw;
2105
+ if (error instanceof Error) {
2106
+ raw = error.message;
2107
+ } else if (typeof error === "string") {
2108
+ raw = error;
2109
+ }
2110
+ if (!raw) {
2111
+ return;
2112
+ }
2113
+ const normalized = raw.replace(/\s+/g, " ").trim();
2114
+ if (!normalized) {
2115
+ return;
2116
+ }
2117
+ return normalized.slice(0, MAX_FETCH_ERROR_MESSAGE_LENGTH);
2118
+ }
2119
+ async function fetchManifest(deploymentUrl) {
2120
+ const manifestUrl = `${deploymentUrl.replace(/\/$/, "")}/playcademy.manifest.json`;
2121
+ const result = await fetchWithRetry(manifestUrl, { method: "GET" });
2122
+ let manifestHost;
2123
+ try {
2124
+ manifestHost = new URL(manifestUrl).host;
2125
+ } catch {
2126
+ manifestHost = manifestUrl;
2127
+ }
2128
+ const base = {
2129
+ manifestUrl,
2130
+ manifestHost,
2131
+ deploymentUrl,
2132
+ retryCount: result.retryCount,
2133
+ durationMs: result.durationMs
2134
+ };
2135
+ if (result.error || !result.response) {
2136
+ log.error(`[Playcademy SDK] Error fetching manifest from ${manifestUrl}:`, {
2137
+ error: result.error
2138
+ });
2139
+ throw new ManifestError("Failed to load game manifest", "temporary", {
2140
+ ...base,
2141
+ fetchOutcome: "network_error",
2142
+ ...result.error ? { fetchErrorMessage: getFetchErrorMessage(result.error) } : {}
2143
+ });
2144
+ }
2145
+ const response = result.response;
2146
+ if (!response.ok) {
2147
+ log.error(`[fetchManifest] Failed to fetch manifest from ${manifestUrl}. Status: ${response.status}`);
2148
+ throw new ManifestError(`Failed to fetch manifest: ${response.status} ${response.statusText}`, isRetryableStatus(response.status) ? "temporary" : "permanent", {
2149
+ ...base,
2150
+ fetchOutcome: "bad_status",
2151
+ status: response.status,
2152
+ contentType: response.headers.get("content-type") ?? undefined,
2153
+ cfRay: response.headers.get("cf-ray") ?? undefined,
2154
+ redirected: response.redirected
2155
+ });
2156
+ }
2157
+ try {
2158
+ return await response.json();
2159
+ } catch (error) {
2160
+ log.error(`[Playcademy SDK] Error parsing manifest from ${manifestUrl}:`, {
2161
+ error
2162
+ });
2163
+ throw new ManifestError("Failed to parse game manifest", "permanent", {
2164
+ ...base,
2165
+ fetchOutcome: "invalid_body",
2166
+ status: response.status,
2167
+ contentType: response.headers.get("content-type") ?? undefined,
2168
+ cfRay: response.headers.get("cf-ray") ?? undefined,
2169
+ redirected: response.redirected
2170
+ });
2171
+ }
2172
+ }
2173
+ // src/core/transport/request.ts
2174
+ function prepareRequestBody(body, headers) {
2175
+ if (body instanceof FormData) {
2176
+ return body;
2177
+ }
2178
+ if (body instanceof ArrayBuffer || body instanceof Blob || ArrayBuffer.isView(body)) {
2179
+ if (!headers["Content-Type"]) {
2180
+ headers["Content-Type"] = "application/octet-stream";
2181
+ }
2182
+ return body;
2183
+ }
2184
+ if (body !== undefined && body !== null) {
2185
+ if (headers["Content-Type"]) {
2186
+ return typeof body === "string" ? body : JSON.stringify(body);
2187
+ }
2188
+ headers["Content-Type"] = "application/json";
2189
+ return JSON.stringify(body);
2190
+ }
2191
+ return;
2192
+ }
2030
2193
  function checkDevWarnings(data) {
2031
2194
  if (!data || typeof data !== "object") {
2032
2195
  return;
@@ -2054,25 +2217,6 @@ function checkDevWarnings(data) {
2054
2217
  }
2055
2218
  }
2056
2219
  }
2057
- function prepareRequestBody(body, headers) {
2058
- if (body instanceof FormData) {
2059
- return body;
2060
- }
2061
- if (body instanceof ArrayBuffer || body instanceof Blob || ArrayBuffer.isView(body)) {
2062
- if (!headers["Content-Type"]) {
2063
- headers["Content-Type"] = "application/octet-stream";
2064
- }
2065
- return body;
2066
- }
2067
- if (body !== undefined && body !== null) {
2068
- if (headers["Content-Type"]) {
2069
- return typeof body === "string" ? body : JSON.stringify(body);
2070
- }
2071
- headers["Content-Type"] = "application/json";
2072
- return JSON.stringify(body);
2073
- }
2074
- return;
2075
- }
2076
2220
  async function request({
2077
2221
  path,
2078
2222
  baseUrl,
@@ -2084,12 +2228,12 @@ async function request({
2084
2228
  const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
2085
2229
  const headers = { ...extraHeaders };
2086
2230
  const payload = prepareRequestBody(body, headers);
2087
- const res = await fetch(url, {
2231
+ const res = unwrapFetchResult(await fetchWithRetry(url, {
2088
2232
  method,
2089
2233
  headers,
2090
2234
  body: payload,
2091
2235
  credentials: "omit"
2092
- });
2236
+ }));
2093
2237
  if (raw) {
2094
2238
  return res;
2095
2239
  }
@@ -2119,26 +2263,6 @@ async function request({
2119
2263
  const rawText = await res.text().catch(() => "");
2120
2264
  return rawText && rawText.length > 0 ? rawText : undefined;
2121
2265
  }
2122
- async function fetchManifest(deploymentUrl) {
2123
- const manifestUrl = `${deploymentUrl.replace(/\/$/, "")}/playcademy.manifest.json`;
2124
- try {
2125
- const response = await fetch(manifestUrl);
2126
- if (!response.ok) {
2127
- log.error(`[fetchManifest] Failed to fetch manifest from ${manifestUrl}. Status: ${response.status}`);
2128
- throw new PlaycademyError(`Failed to fetch manifest: ${response.status} ${response.statusText}`);
2129
- }
2130
- return await response.json();
2131
- } catch (error) {
2132
- if (error instanceof PlaycademyError) {
2133
- throw error;
2134
- }
2135
- log.error(`[Playcademy SDK] Error fetching or parsing manifest from ${manifestUrl}:`, {
2136
- error
2137
- });
2138
- throw new PlaycademyError("Failed to load or parse game manifest");
2139
- }
2140
- }
2141
-
2142
2266
  // src/namespaces/platform/games.ts
2143
2267
  function createGamesNamespace(client) {
2144
2268
  const gamesListCache = createTTLCache({
@@ -2956,6 +3080,7 @@ class PlaycademyBaseClient {
2956
3080
  authContext;
2957
3081
  initPayload;
2958
3082
  connectionManager;
3083
+ launchId;
2959
3084
  _sessionManager = {
2960
3085
  startSession: async (gameId) => this.request(`/games/${gameId}/sessions`, "POST"),
2961
3086
  endSession: async (sessionId, gameId) => this.request(`/games/${gameId}/sessions/${sessionId}`, "DELETE")
@@ -2964,6 +3089,7 @@ class PlaycademyBaseClient {
2964
3089
  this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
2965
3090
  this.gameUrl = config?.gameUrl;
2966
3091
  this.gameId = config?.gameId;
3092
+ this.launchId = config?.launchId ?? undefined;
2967
3093
  this.config = config || {};
2968
3094
  this.authStrategy = createAuthStrategy(config?.token ?? null, config?.tokenType);
2969
3095
  this._detectAuthContext();
@@ -2991,6 +3117,9 @@ class PlaycademyBaseClient {
2991
3117
  this.authStrategy = createAuthStrategy(token, tokenType);
2992
3118
  this.emit("authChange", { token });
2993
3119
  }
3120
+ setLaunchId(launchId) {
3121
+ this.launchId = launchId ?? undefined;
3122
+ }
2994
3123
  getTokenType() {
2995
3124
  return this.authStrategy.getType();
2996
3125
  }
@@ -3033,7 +3162,8 @@ class PlaycademyBaseClient {
3033
3162
  async request(path, method, options) {
3034
3163
  const effectiveHeaders = {
3035
3164
  ...options?.headers,
3036
- ...this.authStrategy.getHeaders()
3165
+ ...this.authStrategy.getHeaders(),
3166
+ ...this.launchId ? { "x-playcademy-launch-id": this.launchId } : {}
3037
3167
  };
3038
3168
  try {
3039
3169
  const result = await request({
@@ -3154,12 +3284,14 @@ class PlaycademyInternalClient extends PlaycademyBaseClient {
3154
3284
  static identity = identity;
3155
3285
  }
3156
3286
  export {
3287
+ probeCorsReachability,
3157
3288
  messaging,
3158
3289
  extractApiErrorInfo,
3159
3290
  PlaycademyInternalClient,
3160
3291
  PlaycademyError,
3161
3292
  PlaycademyInternalClient as PlaycademyClient,
3162
3293
  MessageEvents,
3294
+ ManifestError,
3163
3295
  ConnectionMonitor,
3164
3296
  ConnectionManager,
3165
3297
  ApiError
package/dist/server.js CHANGED
@@ -81,6 +81,18 @@ class PlaycademyError extends Error {
81
81
  }
82
82
  }
83
83
 
84
+ class ManifestError extends PlaycademyError {
85
+ kind;
86
+ details;
87
+ constructor(message, kind, details) {
88
+ super(message);
89
+ this.name = "ManifestError";
90
+ this.kind = kind;
91
+ this.details = details;
92
+ Object.setPrototypeOf(this, ManifestError.prototype);
93
+ }
94
+ }
95
+
84
96
  class ApiError extends Error {
85
97
  code;
86
98
  details;
package/dist/types.d.ts CHANGED
@@ -19,6 +19,9 @@ declare function parseOAuthState(state: string): {
19
19
  data?: Record<string, string>;
20
20
  };
21
21
 
22
+ /** Permitted HTTP verbs */
23
+ type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
24
+
22
25
  /**
23
26
  * Game Types
24
27
  *
@@ -49,9 +52,6 @@ interface DomainValidationRecords {
49
52
  }[];
50
53
  }
51
54
 
52
- /** Permitted HTTP verbs */
53
- type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
54
-
55
55
  /**
56
56
  * User Types
57
57
  *
@@ -4735,6 +4735,7 @@ declare abstract class PlaycademyBaseClient {
4735
4735
  };
4736
4736
  protected initPayload?: InitPayload;
4737
4737
  protected connectionManager?: ConnectionManager;
4738
+ protected launchId?: string;
4738
4739
  /**
4739
4740
  * Internal session manager for automatic session lifecycle.
4740
4741
  * @private
@@ -4763,6 +4764,7 @@ declare abstract class PlaycademyBaseClient {
4763
4764
  * Sets the authentication token for API requests.
4764
4765
  */
4765
4766
  setToken(token: string | null, tokenType?: TokenType): void;
4767
+ setLaunchId(launchId: string | null | undefined): void;
4766
4768
  /**
4767
4769
  * Gets the current token type.
4768
4770
  */
@@ -5188,6 +5190,7 @@ interface ClientConfig {
5188
5190
  token?: string;
5189
5191
  tokenType?: TokenType;
5190
5192
  gameId?: string;
5193
+ launchId?: string;
5191
5194
  autoStartSession?: boolean;
5192
5195
  onDisconnect?: DisconnectHandler;
5193
5196
  enableConnectionMonitoring?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sdk",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -35,10 +35,10 @@
35
35
  ],
36
36
  "scripts": {
37
37
  "build": "bun build.js",
38
+ "docs": "typedoc",
38
39
  "pub": "bun publish.ts",
39
40
  "test": "bun test",
40
- "test:watch": "bun test --watch",
41
- "docs": "typedoc"
41
+ "test:watch": "bun test --watch"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@inquirer/prompts": "^7.8.6",