@playcademy/sdk 0.3.3 → 0.3.5
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 +116 -32
- package/dist/internal.d.ts +180 -157
- package/dist/internal.js +117 -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,124 @@ 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, {
|
|
1984
|
+
mode: "no-cors",
|
|
1985
|
+
cache: "no-store",
|
|
1986
|
+
signal: controller.signal
|
|
1987
|
+
});
|
|
1988
|
+
return "reachable";
|
|
1989
|
+
} catch {
|
|
1990
|
+
return "unreachable";
|
|
1991
|
+
} finally {
|
|
1992
|
+
clearTimeout(timer);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
var MAX_FETCH_ERROR_MESSAGE_LENGTH = 512;
|
|
1996
|
+
function getFetchErrorMessage(error) {
|
|
1997
|
+
let raw;
|
|
1998
|
+
if (error instanceof Error) {
|
|
1999
|
+
raw = error.message;
|
|
2000
|
+
} else if (typeof error === "string") {
|
|
2001
|
+
raw = error;
|
|
2002
|
+
}
|
|
2003
|
+
if (!raw) {
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
const normalized = raw.replace(/\s+/g, " ").trim();
|
|
2007
|
+
if (!normalized) {
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
return normalized.slice(0, MAX_FETCH_ERROR_MESSAGE_LENGTH);
|
|
2011
|
+
}
|
|
2012
|
+
async function fetchManifest(deploymentUrl) {
|
|
2013
|
+
const manifestUrl = `${deploymentUrl.replace(/\/$/, "")}/playcademy.manifest.json`;
|
|
2014
|
+
const result = await fetchWithRetry(manifestUrl, { method: "GET" });
|
|
2015
|
+
let manifestHost;
|
|
2016
|
+
try {
|
|
2017
|
+
manifestHost = new URL(manifestUrl).host;
|
|
2018
|
+
} catch {
|
|
2019
|
+
manifestHost = manifestUrl;
|
|
2020
|
+
}
|
|
2021
|
+
const base = {
|
|
2022
|
+
manifestUrl,
|
|
2023
|
+
manifestHost,
|
|
2024
|
+
deploymentUrl,
|
|
2025
|
+
retryCount: result.retryCount,
|
|
2026
|
+
durationMs: result.durationMs
|
|
2027
|
+
};
|
|
2028
|
+
if (result.error || !result.response) {
|
|
2029
|
+
log.error(`[Playcademy SDK] Error fetching manifest from ${manifestUrl}:`, {
|
|
2030
|
+
error: result.error
|
|
2031
|
+
});
|
|
2032
|
+
throw new ManifestError("Failed to load game manifest", "temporary", {
|
|
2033
|
+
...base,
|
|
2034
|
+
fetchOutcome: "network_error",
|
|
2035
|
+
...result.error ? { fetchErrorMessage: getFetchErrorMessage(result.error) } : {}
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
const response = result.response;
|
|
2039
|
+
if (!response.ok) {
|
|
2040
|
+
log.error(`[fetchManifest] Failed to fetch manifest from ${manifestUrl}. Status: ${response.status}`);
|
|
2041
|
+
throw new ManifestError(`Failed to fetch manifest: ${response.status} ${response.statusText}`, isRetryableStatus(response.status) ? "temporary" : "permanent", {
|
|
2042
|
+
...base,
|
|
2043
|
+
fetchOutcome: "bad_status",
|
|
2044
|
+
status: response.status,
|
|
2045
|
+
contentType: response.headers.get("content-type") ?? undefined,
|
|
2046
|
+
cfRay: response.headers.get("cf-ray") ?? undefined,
|
|
2047
|
+
redirected: response.redirected
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
try {
|
|
2051
|
+
return await response.json();
|
|
2052
|
+
} catch (error) {
|
|
2053
|
+
log.error(`[Playcademy SDK] Error parsing manifest from ${manifestUrl}:`, {
|
|
2054
|
+
error
|
|
2055
|
+
});
|
|
2056
|
+
throw new ManifestError("Failed to parse game manifest", "permanent", {
|
|
2057
|
+
...base,
|
|
2058
|
+
fetchOutcome: "invalid_body",
|
|
2059
|
+
status: response.status,
|
|
2060
|
+
contentType: response.headers.get("content-type") ?? undefined,
|
|
2061
|
+
cfRay: response.headers.get("cf-ray") ?? undefined,
|
|
2062
|
+
redirected: response.redirected
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
1957
2065
|
}
|
|
2066
|
+
// src/core/transport/request.ts
|
|
1958
2067
|
function prepareRequestBody(body, headers) {
|
|
1959
2068
|
if (body instanceof FormData) {
|
|
1960
2069
|
return body;
|
|
@@ -2012,12 +2121,12 @@ async function request({
|
|
|
2012
2121
|
const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
|
|
2013
2122
|
const headers = { ...extraHeaders };
|
|
2014
2123
|
const payload = prepareRequestBody(body, headers);
|
|
2015
|
-
const res = await fetchWithRetry(url, {
|
|
2124
|
+
const res = unwrapFetchResult(await fetchWithRetry(url, {
|
|
2016
2125
|
method,
|
|
2017
2126
|
headers,
|
|
2018
2127
|
body: payload,
|
|
2019
2128
|
credentials: "omit"
|
|
2020
|
-
});
|
|
2129
|
+
}));
|
|
2021
2130
|
if (raw) {
|
|
2022
2131
|
return res;
|
|
2023
2132
|
}
|
|
@@ -2047,31 +2156,6 @@ async function request({
|
|
|
2047
2156
|
const rawText = await res.text().catch(() => "");
|
|
2048
2157
|
return rawText && rawText.length > 0 ? rawText : undefined;
|
|
2049
2158
|
}
|
|
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
2159
|
// src/clients/base.ts
|
|
2076
2160
|
class PlaycademyBaseClient {
|
|
2077
2161
|
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,124 @@ 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, {
|
|
2095
|
+
mode: "no-cors",
|
|
2096
|
+
cache: "no-store",
|
|
2097
|
+
signal: controller.signal
|
|
2098
|
+
});
|
|
2099
|
+
return "reachable";
|
|
2100
|
+
} catch {
|
|
2101
|
+
return "unreachable";
|
|
2102
|
+
} finally {
|
|
2103
|
+
clearTimeout(timer);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
var MAX_FETCH_ERROR_MESSAGE_LENGTH = 512;
|
|
2107
|
+
function getFetchErrorMessage(error) {
|
|
2108
|
+
let raw;
|
|
2109
|
+
if (error instanceof Error) {
|
|
2110
|
+
raw = error.message;
|
|
2111
|
+
} else if (typeof error === "string") {
|
|
2112
|
+
raw = error;
|
|
2113
|
+
}
|
|
2114
|
+
if (!raw) {
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
const normalized = raw.replace(/\s+/g, " ").trim();
|
|
2118
|
+
if (!normalized) {
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2121
|
+
return normalized.slice(0, MAX_FETCH_ERROR_MESSAGE_LENGTH);
|
|
2122
|
+
}
|
|
2123
|
+
async function fetchManifest(deploymentUrl) {
|
|
2124
|
+
const manifestUrl = `${deploymentUrl.replace(/\/$/, "")}/playcademy.manifest.json`;
|
|
2125
|
+
const result = await fetchWithRetry(manifestUrl, { method: "GET" });
|
|
2126
|
+
let manifestHost;
|
|
2127
|
+
try {
|
|
2128
|
+
manifestHost = new URL(manifestUrl).host;
|
|
2129
|
+
} catch {
|
|
2130
|
+
manifestHost = manifestUrl;
|
|
2131
|
+
}
|
|
2132
|
+
const base = {
|
|
2133
|
+
manifestUrl,
|
|
2134
|
+
manifestHost,
|
|
2135
|
+
deploymentUrl,
|
|
2136
|
+
retryCount: result.retryCount,
|
|
2137
|
+
durationMs: result.durationMs
|
|
2138
|
+
};
|
|
2139
|
+
if (result.error || !result.response) {
|
|
2140
|
+
log.error(`[Playcademy SDK] Error fetching manifest from ${manifestUrl}:`, {
|
|
2141
|
+
error: result.error
|
|
2142
|
+
});
|
|
2143
|
+
throw new ManifestError("Failed to load game manifest", "temporary", {
|
|
2144
|
+
...base,
|
|
2145
|
+
fetchOutcome: "network_error",
|
|
2146
|
+
...result.error ? { fetchErrorMessage: getFetchErrorMessage(result.error) } : {}
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
const response = result.response;
|
|
2150
|
+
if (!response.ok) {
|
|
2151
|
+
log.error(`[fetchManifest] Failed to fetch manifest from ${manifestUrl}. Status: ${response.status}`);
|
|
2152
|
+
throw new ManifestError(`Failed to fetch manifest: ${response.status} ${response.statusText}`, isRetryableStatus(response.status) ? "temporary" : "permanent", {
|
|
2153
|
+
...base,
|
|
2154
|
+
fetchOutcome: "bad_status",
|
|
2155
|
+
status: response.status,
|
|
2156
|
+
contentType: response.headers.get("content-type") ?? undefined,
|
|
2157
|
+
cfRay: response.headers.get("cf-ray") ?? undefined,
|
|
2158
|
+
redirected: response.redirected
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
try {
|
|
2162
|
+
return await response.json();
|
|
2163
|
+
} catch (error) {
|
|
2164
|
+
log.error(`[Playcademy SDK] Error parsing manifest from ${manifestUrl}:`, {
|
|
2165
|
+
error
|
|
2166
|
+
});
|
|
2167
|
+
throw new ManifestError("Failed to parse game manifest", "permanent", {
|
|
2168
|
+
...base,
|
|
2169
|
+
fetchOutcome: "invalid_body",
|
|
2170
|
+
status: response.status,
|
|
2171
|
+
contentType: response.headers.get("content-type") ?? undefined,
|
|
2172
|
+
cfRay: response.headers.get("cf-ray") ?? undefined,
|
|
2173
|
+
redirected: response.redirected
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2068
2176
|
}
|
|
2177
|
+
// src/core/transport/request.ts
|
|
2069
2178
|
function prepareRequestBody(body, headers) {
|
|
2070
2179
|
if (body instanceof FormData) {
|
|
2071
2180
|
return body;
|
|
@@ -2123,12 +2232,12 @@ async function request({
|
|
|
2123
2232
|
const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
|
|
2124
2233
|
const headers = { ...extraHeaders };
|
|
2125
2234
|
const payload = prepareRequestBody(body, headers);
|
|
2126
|
-
const res = await fetchWithRetry(url, {
|
|
2235
|
+
const res = unwrapFetchResult(await fetchWithRetry(url, {
|
|
2127
2236
|
method,
|
|
2128
2237
|
headers,
|
|
2129
2238
|
body: payload,
|
|
2130
2239
|
credentials: "omit"
|
|
2131
|
-
});
|
|
2240
|
+
}));
|
|
2132
2241
|
if (raw) {
|
|
2133
2242
|
return res;
|
|
2134
2243
|
}
|
|
@@ -2158,31 +2267,6 @@ async function request({
|
|
|
2158
2267
|
const rawText = await res.text().catch(() => "");
|
|
2159
2268
|
return rawText && rawText.length > 0 ? rawText : undefined;
|
|
2160
2269
|
}
|
|
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
2270
|
// src/namespaces/platform/games.ts
|
|
2187
2271
|
function createGamesNamespace(client) {
|
|
2188
2272
|
const gamesListCache = createTTLCache({
|
|
@@ -3204,6 +3288,7 @@ class PlaycademyInternalClient extends PlaycademyBaseClient {
|
|
|
3204
3288
|
static identity = identity;
|
|
3205
3289
|
}
|
|
3206
3290
|
export {
|
|
3291
|
+
probeCorsReachability,
|
|
3207
3292
|
messaging,
|
|
3208
3293
|
extractApiErrorInfo,
|
|
3209
3294
|
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
|
*
|