@playcademy/sdk 0.3.3 → 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 +16 -1
- package/dist/index.js +112 -32
- package/dist/internal.d.ts +180 -157
- package/dist/internal.js +113 -32
- package/dist/server.js +3 -1
- package/dist/types.d.ts +3 -3
- package/package.json +1 -1
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.
|
|
@@ -2304,4 +2319,4 @@ declare class PlaycademyMessaging {
|
|
|
2304
2319
|
declare const messaging: PlaycademyMessaging;
|
|
2305
2320
|
|
|
2306
2321
|
export { ApiError, ConnectionManager, ConnectionMonitor, MessageEvents, PlaycademyClient, PlaycademyError, extractApiErrorInfo, messaging };
|
|
2307
|
-
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
|
@@ -409,10 +409,12 @@ class PlaycademyError extends Error {
|
|
|
409
409
|
|
|
410
410
|
class ManifestError extends PlaycademyError {
|
|
411
411
|
kind;
|
|
412
|
-
|
|
412
|
+
details;
|
|
413
|
+
constructor(message, kind, details) {
|
|
413
414
|
super(message);
|
|
414
415
|
this.name = "ManifestError";
|
|
415
416
|
this.kind = kind;
|
|
417
|
+
this.details = details;
|
|
416
418
|
Object.setPrototypeOf(this, ManifestError.prototype);
|
|
417
419
|
}
|
|
418
420
|
}
|
|
@@ -1925,7 +1927,7 @@ class ConnectionManager {
|
|
|
1925
1927
|
}
|
|
1926
1928
|
}
|
|
1927
1929
|
}
|
|
1928
|
-
// src/core/
|
|
1930
|
+
// src/core/transport/retry.ts
|
|
1929
1931
|
var RETRY_DELAYS_MS = [500, 1500];
|
|
1930
1932
|
function wait(ms) {
|
|
1931
1933
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -1935,6 +1937,7 @@ function isRetryableStatus(status) {
|
|
|
1935
1937
|
return status === 429 || status >= 500;
|
|
1936
1938
|
}
|
|
1937
1939
|
async function fetchWithRetry(url, init2) {
|
|
1940
|
+
const startedAt = Date.now();
|
|
1938
1941
|
for (let attempt = 0;attempt <= RETRY_DELAYS_MS.length; attempt++) {
|
|
1939
1942
|
const retryDelayMs = RETRY_DELAYS_MS[attempt];
|
|
1940
1943
|
const canRetry = init2.method === "GET" && retryDelayMs !== undefined;
|
|
@@ -1943,18 +1946,120 @@ async function fetchWithRetry(url, init2) {
|
|
|
1943
1946
|
if (canRetry && isRetryableStatus(response.status)) {
|
|
1944
1947
|
await retryRuntime.wait(retryDelayMs);
|
|
1945
1948
|
} else {
|
|
1946
|
-
return response;
|
|
1949
|
+
return { response, retryCount: attempt, durationMs: Date.now() - startedAt };
|
|
1947
1950
|
}
|
|
1948
1951
|
} catch (error) {
|
|
1949
1952
|
if (canRetry && error instanceof TypeError) {
|
|
1950
1953
|
await retryRuntime.wait(retryDelayMs);
|
|
1951
1954
|
} else {
|
|
1952
|
-
|
|
1955
|
+
return { error, retryCount: attempt, durationMs: Date.now() - startedAt };
|
|
1953
1956
|
}
|
|
1954
1957
|
}
|
|
1955
1958
|
}
|
|
1956
|
-
|
|
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);
|
|
1957
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
|
|
1958
2063
|
function prepareRequestBody(body, headers) {
|
|
1959
2064
|
if (body instanceof FormData) {
|
|
1960
2065
|
return body;
|
|
@@ -2012,12 +2117,12 @@ async function request({
|
|
|
2012
2117
|
const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
|
|
2013
2118
|
const headers = { ...extraHeaders };
|
|
2014
2119
|
const payload = prepareRequestBody(body, headers);
|
|
2015
|
-
const res = await fetchWithRetry(url, {
|
|
2120
|
+
const res = unwrapFetchResult(await fetchWithRetry(url, {
|
|
2016
2121
|
method,
|
|
2017
2122
|
headers,
|
|
2018
2123
|
body: payload,
|
|
2019
2124
|
credentials: "omit"
|
|
2020
|
-
});
|
|
2125
|
+
}));
|
|
2021
2126
|
if (raw) {
|
|
2022
2127
|
return res;
|
|
2023
2128
|
}
|
|
@@ -2047,31 +2152,6 @@ async function request({
|
|
|
2047
2152
|
const rawText = await res.text().catch(() => "");
|
|
2048
2153
|
return rawText && rawText.length > 0 ? rawText : undefined;
|
|
2049
2154
|
}
|
|
2050
|
-
async function fetchManifest(deploymentUrl) {
|
|
2051
|
-
const manifestUrl = `${deploymentUrl.replace(/\/$/, "")}/playcademy.manifest.json`;
|
|
2052
|
-
let response;
|
|
2053
|
-
try {
|
|
2054
|
-
response = await fetchWithRetry(manifestUrl, { method: "GET" });
|
|
2055
|
-
} catch (error) {
|
|
2056
|
-
log.error(`[Playcademy SDK] Error fetching manifest from ${manifestUrl}:`, {
|
|
2057
|
-
error
|
|
2058
|
-
});
|
|
2059
|
-
throw new ManifestError("Failed to load game manifest", "temporary");
|
|
2060
|
-
}
|
|
2061
|
-
if (!response.ok) {
|
|
2062
|
-
log.error(`[fetchManifest] Failed to fetch manifest from ${manifestUrl}. Status: ${response.status}`);
|
|
2063
|
-
throw new ManifestError(`Failed to fetch manifest: ${response.status} ${response.statusText}`, isRetryableStatus(response.status) ? "temporary" : "permanent");
|
|
2064
|
-
}
|
|
2065
|
-
try {
|
|
2066
|
-
return await response.json();
|
|
2067
|
-
} catch (error) {
|
|
2068
|
-
log.error(`[Playcademy SDK] Error parsing manifest from ${manifestUrl}:`, {
|
|
2069
|
-
error
|
|
2070
|
-
});
|
|
2071
|
-
throw new ManifestError("Failed to parse game manifest", "permanent");
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
2155
|
// src/clients/base.ts
|
|
2076
2156
|
class PlaycademyBaseClient {
|
|
2077
2157
|
baseUrl;
|
package/dist/internal.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
86
|
-
|
|
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,159 +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
|
-
type ManifestErrorKind = 'temporary' | 'permanent';
|
|
945
|
-
declare class ManifestError extends PlaycademyError {
|
|
946
|
-
readonly kind: ManifestErrorKind;
|
|
947
|
-
constructor(message: string, kind: ManifestErrorKind);
|
|
948
|
-
}
|
|
949
|
-
/**
|
|
950
|
-
* Error codes returned by the API.
|
|
951
|
-
* These map to specific error types and HTTP status codes.
|
|
952
|
-
*/
|
|
953
|
-
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;
|
|
954
|
-
/**
|
|
955
|
-
* Structure of error response bodies returned by API endpoints.
|
|
956
|
-
*
|
|
957
|
-
* @example
|
|
958
|
-
* ```json
|
|
959
|
-
* {
|
|
960
|
-
* "error": {
|
|
961
|
-
* "code": "NOT_FOUND",
|
|
962
|
-
* "message": "Item not found",
|
|
963
|
-
* "details": { "identifier": "abc123" }
|
|
964
|
-
* }
|
|
965
|
-
* }
|
|
966
|
-
* ```
|
|
967
|
-
*/
|
|
968
|
-
interface ErrorResponseBody {
|
|
969
|
-
error?: {
|
|
970
|
-
code?: string;
|
|
971
|
-
message?: string;
|
|
972
|
-
details?: unknown;
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
/**
|
|
976
|
-
* API error thrown when a request fails.
|
|
977
|
-
*
|
|
978
|
-
* Contains structured error information from the API response:
|
|
979
|
-
* - `status` - HTTP status code (e.g., 404)
|
|
980
|
-
* - `code` - API error code (e.g., "NOT_FOUND")
|
|
981
|
-
* - `message` - Human-readable error message
|
|
982
|
-
* - `details` - Optional additional error context
|
|
983
|
-
*
|
|
984
|
-
* @example
|
|
985
|
-
* ```typescript
|
|
986
|
-
* try {
|
|
987
|
-
* await client.games.get('nonexistent')
|
|
988
|
-
* } catch (error) {
|
|
989
|
-
* if (error instanceof ApiError) {
|
|
990
|
-
* console.log(error.status) // 404
|
|
991
|
-
* console.log(error.code) // "NOT_FOUND"
|
|
992
|
-
* console.log(error.message) // "Game not found"
|
|
993
|
-
* console.log(error.details) // { identifier: "nonexistent" }
|
|
994
|
-
* }
|
|
995
|
-
* }
|
|
996
|
-
* ```
|
|
997
|
-
*/
|
|
998
|
-
declare class ApiError extends Error {
|
|
999
|
-
/**
|
|
1000
|
-
* API error code (e.g., "NOT_FOUND", "VALIDATION_FAILED").
|
|
1001
|
-
* Use this for programmatic error handling.
|
|
1002
|
-
*/
|
|
1003
|
-
readonly code: ApiErrorCode;
|
|
1004
|
-
/**
|
|
1005
|
-
* Additional error context from the API.
|
|
1006
|
-
* Structure varies by error type (e.g., validation errors include field details).
|
|
1007
|
-
*/
|
|
1008
|
-
readonly details: unknown;
|
|
1009
|
-
/**
|
|
1010
|
-
* Raw response body for debugging.
|
|
1011
|
-
* @internal
|
|
1012
|
-
*/
|
|
1013
|
-
readonly rawBody: unknown;
|
|
1014
|
-
readonly status: number;
|
|
1015
|
-
constructor(
|
|
1016
|
-
/** HTTP status code */
|
|
1017
|
-
status: number,
|
|
1018
|
-
/** API error code */
|
|
1019
|
-
code: ApiErrorCode,
|
|
1020
|
-
/** Human-readable error message */
|
|
1021
|
-
message: string,
|
|
1022
|
-
/** Additional error context */
|
|
1023
|
-
details?: unknown,
|
|
1024
|
-
/** Raw response body */
|
|
1025
|
-
rawBody?: unknown);
|
|
1026
|
-
/**
|
|
1027
|
-
* Create an ApiError from an HTTP response.
|
|
1028
|
-
* Parses the structured error response from the API.
|
|
1029
|
-
*
|
|
1030
|
-
* @internal
|
|
1031
|
-
*/
|
|
1032
|
-
static fromResponse(status: number, statusText: string, body: unknown): ApiError;
|
|
1033
|
-
/**
|
|
1034
|
-
* Check if this is a specific error type.
|
|
1035
|
-
*
|
|
1036
|
-
* @example
|
|
1037
|
-
* ```typescript
|
|
1038
|
-
* if (error.is('NOT_FOUND')) {
|
|
1039
|
-
* // Handle not found
|
|
1040
|
-
* } else if (error.is('VALIDATION_FAILED')) {
|
|
1041
|
-
* // Handle validation error
|
|
1042
|
-
* }
|
|
1043
|
-
* ```
|
|
1044
|
-
*/
|
|
1045
|
-
is(code: ApiErrorCode): boolean;
|
|
1046
|
-
/**
|
|
1047
|
-
* Check if this is a client error (4xx).
|
|
1048
|
-
*/
|
|
1049
|
-
isClientError(): boolean;
|
|
1050
|
-
/**
|
|
1051
|
-
* Check if this is a server error (5xx).
|
|
1052
|
-
*/
|
|
1053
|
-
isServerError(): boolean;
|
|
1054
|
-
/**
|
|
1055
|
-
* Check if this error is retryable.
|
|
1056
|
-
* Server errors and rate limits are typically retryable.
|
|
1057
|
-
*/
|
|
1058
|
-
isRetryable(): boolean;
|
|
1059
|
-
}
|
|
1060
|
-
/**
|
|
1061
|
-
* Extracted error information for display purposes.
|
|
1062
|
-
*/
|
|
1063
|
-
interface ApiErrorInfo {
|
|
1064
|
-
/** HTTP status code */
|
|
1065
|
-
status: number;
|
|
1066
|
-
/** API error code */
|
|
1067
|
-
code: ApiErrorCode;
|
|
1068
|
-
/** Human-readable error message */
|
|
1069
|
-
message: string;
|
|
1070
|
-
/** Additional error context */
|
|
1071
|
-
details?: unknown;
|
|
1072
|
-
}
|
|
1073
|
-
/**
|
|
1074
|
-
* Extract useful error information from an API error.
|
|
1075
|
-
* Useful for displaying errors to users in a friendly way.
|
|
1076
|
-
*
|
|
1077
|
-
* @example
|
|
1078
|
-
* ```typescript
|
|
1079
|
-
* try {
|
|
1080
|
-
* await client.shop.purchase(itemId)
|
|
1081
|
-
* } catch (error) {
|
|
1082
|
-
* const info = extractApiErrorInfo(error)
|
|
1083
|
-
* if (info) {
|
|
1084
|
-
* showToast(`Error: ${info.message}`)
|
|
1085
|
-
* }
|
|
1086
|
-
* }
|
|
1087
|
-
* ```
|
|
1088
|
-
*/
|
|
1089
|
-
declare function extractApiErrorInfo(error: unknown): ApiErrorInfo | null;
|
|
1090
|
-
|
|
1091
1114
|
/**
|
|
1092
1115
|
* Connection monitoring types
|
|
1093
1116
|
*
|
|
@@ -7637,5 +7660,5 @@ declare class PlaycademyInternalClient extends PlaycademyBaseClient {
|
|
|
7637
7660
|
};
|
|
7638
7661
|
}
|
|
7639
7662
|
|
|
7640
|
-
export { AchievementCompletionType, ApiError, ConnectionManager, ConnectionMonitor, ManifestError, MessageEvents, NotificationStatus, NotificationType, PlaycademyInternalClient as PlaycademyClient, PlaycademyError, PlaycademyInternalClient, extractApiErrorInfo, messaging };
|
|
7641
|
-
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
|
@@ -409,10 +409,12 @@ class PlaycademyError extends Error {
|
|
|
409
409
|
|
|
410
410
|
class ManifestError extends PlaycademyError {
|
|
411
411
|
kind;
|
|
412
|
-
|
|
412
|
+
details;
|
|
413
|
+
constructor(message, kind, details) {
|
|
413
414
|
super(message);
|
|
414
415
|
this.name = "ManifestError";
|
|
415
416
|
this.kind = kind;
|
|
417
|
+
this.details = details;
|
|
416
418
|
Object.setPrototypeOf(this, ManifestError.prototype);
|
|
417
419
|
}
|
|
418
420
|
}
|
|
@@ -2036,7 +2038,7 @@ function createDevNamespace(client) {
|
|
|
2036
2038
|
}
|
|
2037
2039
|
};
|
|
2038
2040
|
}
|
|
2039
|
-
// src/core/
|
|
2041
|
+
// src/core/transport/retry.ts
|
|
2040
2042
|
var RETRY_DELAYS_MS = [500, 1500];
|
|
2041
2043
|
function wait(ms) {
|
|
2042
2044
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -2046,6 +2048,7 @@ function isRetryableStatus(status) {
|
|
|
2046
2048
|
return status === 429 || status >= 500;
|
|
2047
2049
|
}
|
|
2048
2050
|
async function fetchWithRetry(url, init2) {
|
|
2051
|
+
const startedAt = Date.now();
|
|
2049
2052
|
for (let attempt = 0;attempt <= RETRY_DELAYS_MS.length; attempt++) {
|
|
2050
2053
|
const retryDelayMs = RETRY_DELAYS_MS[attempt];
|
|
2051
2054
|
const canRetry = init2.method === "GET" && retryDelayMs !== undefined;
|
|
@@ -2054,18 +2057,120 @@ async function fetchWithRetry(url, init2) {
|
|
|
2054
2057
|
if (canRetry && isRetryableStatus(response.status)) {
|
|
2055
2058
|
await retryRuntime.wait(retryDelayMs);
|
|
2056
2059
|
} else {
|
|
2057
|
-
return response;
|
|
2060
|
+
return { response, retryCount: attempt, durationMs: Date.now() - startedAt };
|
|
2058
2061
|
}
|
|
2059
2062
|
} catch (error) {
|
|
2060
2063
|
if (canRetry && error instanceof TypeError) {
|
|
2061
2064
|
await retryRuntime.wait(retryDelayMs);
|
|
2062
2065
|
} else {
|
|
2063
|
-
|
|
2066
|
+
return { error, retryCount: attempt, durationMs: Date.now() - startedAt };
|
|
2064
2067
|
}
|
|
2065
2068
|
}
|
|
2066
2069
|
}
|
|
2067
|
-
|
|
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);
|
|
2068
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
|
|
2069
2174
|
function prepareRequestBody(body, headers) {
|
|
2070
2175
|
if (body instanceof FormData) {
|
|
2071
2176
|
return body;
|
|
@@ -2123,12 +2228,12 @@ async function request({
|
|
|
2123
2228
|
const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
|
|
2124
2229
|
const headers = { ...extraHeaders };
|
|
2125
2230
|
const payload = prepareRequestBody(body, headers);
|
|
2126
|
-
const res = await fetchWithRetry(url, {
|
|
2231
|
+
const res = unwrapFetchResult(await fetchWithRetry(url, {
|
|
2127
2232
|
method,
|
|
2128
2233
|
headers,
|
|
2129
2234
|
body: payload,
|
|
2130
2235
|
credentials: "omit"
|
|
2131
|
-
});
|
|
2236
|
+
}));
|
|
2132
2237
|
if (raw) {
|
|
2133
2238
|
return res;
|
|
2134
2239
|
}
|
|
@@ -2158,31 +2263,6 @@ async function request({
|
|
|
2158
2263
|
const rawText = await res.text().catch(() => "");
|
|
2159
2264
|
return rawText && rawText.length > 0 ? rawText : undefined;
|
|
2160
2265
|
}
|
|
2161
|
-
async function fetchManifest(deploymentUrl) {
|
|
2162
|
-
const manifestUrl = `${deploymentUrl.replace(/\/$/, "")}/playcademy.manifest.json`;
|
|
2163
|
-
let response;
|
|
2164
|
-
try {
|
|
2165
|
-
response = await fetchWithRetry(manifestUrl, { method: "GET" });
|
|
2166
|
-
} catch (error) {
|
|
2167
|
-
log.error(`[Playcademy SDK] Error fetching manifest from ${manifestUrl}:`, {
|
|
2168
|
-
error
|
|
2169
|
-
});
|
|
2170
|
-
throw new ManifestError("Failed to load game manifest", "temporary");
|
|
2171
|
-
}
|
|
2172
|
-
if (!response.ok) {
|
|
2173
|
-
log.error(`[fetchManifest] Failed to fetch manifest from ${manifestUrl}. Status: ${response.status}`);
|
|
2174
|
-
throw new ManifestError(`Failed to fetch manifest: ${response.status} ${response.statusText}`, isRetryableStatus(response.status) ? "temporary" : "permanent");
|
|
2175
|
-
}
|
|
2176
|
-
try {
|
|
2177
|
-
return await response.json();
|
|
2178
|
-
} catch (error) {
|
|
2179
|
-
log.error(`[Playcademy SDK] Error parsing manifest from ${manifestUrl}:`, {
|
|
2180
|
-
error
|
|
2181
|
-
});
|
|
2182
|
-
throw new ManifestError("Failed to parse game manifest", "permanent");
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
2266
|
// src/namespaces/platform/games.ts
|
|
2187
2267
|
function createGamesNamespace(client) {
|
|
2188
2268
|
const gamesListCache = createTTLCache({
|
|
@@ -3204,6 +3284,7 @@ class PlaycademyInternalClient extends PlaycademyBaseClient {
|
|
|
3204
3284
|
static identity = identity;
|
|
3205
3285
|
}
|
|
3206
3286
|
export {
|
|
3287
|
+
probeCorsReachability,
|
|
3207
3288
|
messaging,
|
|
3208
3289
|
extractApiErrorInfo,
|
|
3209
3290
|
PlaycademyInternalClient,
|
package/dist/server.js
CHANGED
|
@@ -83,10 +83,12 @@ class PlaycademyError extends Error {
|
|
|
83
83
|
|
|
84
84
|
class ManifestError extends PlaycademyError {
|
|
85
85
|
kind;
|
|
86
|
-
|
|
86
|
+
details;
|
|
87
|
+
constructor(message, kind, details) {
|
|
87
88
|
super(message);
|
|
88
89
|
this.name = "ManifestError";
|
|
89
90
|
this.kind = kind;
|
|
91
|
+
this.details = details;
|
|
90
92
|
Object.setPrototypeOf(this, ManifestError.prototype);
|
|
91
93
|
}
|
|
92
94
|
}
|
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
|
*
|