@playcademy/sdk 0.3.2 → 0.3.3
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 +3 -0
- package/dist/index.js +81 -31
- package/dist/internal.d.ts +9 -1
- package/dist/internal.js +82 -31
- package/dist/server.js +10 -0
- package/dist/types.d.ts +3 -0
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -855,6 +855,7 @@ declare abstract class PlaycademyBaseClient {
|
|
|
855
855
|
};
|
|
856
856
|
protected initPayload?: InitPayload;
|
|
857
857
|
protected connectionManager?: ConnectionManager;
|
|
858
|
+
protected launchId?: string;
|
|
858
859
|
/**
|
|
859
860
|
* Internal session manager for automatic session lifecycle.
|
|
860
861
|
* @private
|
|
@@ -883,6 +884,7 @@ declare abstract class PlaycademyBaseClient {
|
|
|
883
884
|
* Sets the authentication token for API requests.
|
|
884
885
|
*/
|
|
885
886
|
setToken(token: string | null, tokenType?: TokenType): void;
|
|
887
|
+
setLaunchId(launchId: string | null | undefined): void;
|
|
886
888
|
/**
|
|
887
889
|
* Gets the current token type.
|
|
888
890
|
*/
|
|
@@ -1308,6 +1310,7 @@ interface ClientConfig {
|
|
|
1308
1310
|
token?: string;
|
|
1309
1311
|
tokenType?: TokenType;
|
|
1310
1312
|
gameId?: string;
|
|
1313
|
+
launchId?: string;
|
|
1311
1314
|
autoStartSession?: boolean;
|
|
1312
1315
|
onDisconnect?: DisconnectHandler;
|
|
1313
1316
|
enableConnectionMonitoring?: boolean;
|
package/dist/index.js
CHANGED
|
@@ -407,6 +407,16 @@ class PlaycademyError extends Error {
|
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
+
class ManifestError extends PlaycademyError {
|
|
411
|
+
kind;
|
|
412
|
+
constructor(message, kind) {
|
|
413
|
+
super(message);
|
|
414
|
+
this.name = "ManifestError";
|
|
415
|
+
this.kind = kind;
|
|
416
|
+
Object.setPrototypeOf(this, ManifestError.prototype);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
410
420
|
class ApiError extends Error {
|
|
411
421
|
code;
|
|
412
422
|
details;
|
|
@@ -1916,6 +1926,54 @@ class ConnectionManager {
|
|
|
1916
1926
|
}
|
|
1917
1927
|
}
|
|
1918
1928
|
// src/core/request.ts
|
|
1929
|
+
var RETRY_DELAYS_MS = [500, 1500];
|
|
1930
|
+
function wait(ms) {
|
|
1931
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1932
|
+
}
|
|
1933
|
+
var retryRuntime = { wait };
|
|
1934
|
+
function isRetryableStatus(status) {
|
|
1935
|
+
return status === 429 || status >= 500;
|
|
1936
|
+
}
|
|
1937
|
+
async function fetchWithRetry(url, init2) {
|
|
1938
|
+
for (let attempt = 0;attempt <= RETRY_DELAYS_MS.length; attempt++) {
|
|
1939
|
+
const retryDelayMs = RETRY_DELAYS_MS[attempt];
|
|
1940
|
+
const canRetry = init2.method === "GET" && retryDelayMs !== undefined;
|
|
1941
|
+
try {
|
|
1942
|
+
const response = await fetch(url, init2);
|
|
1943
|
+
if (canRetry && isRetryableStatus(response.status)) {
|
|
1944
|
+
await retryRuntime.wait(retryDelayMs);
|
|
1945
|
+
} else {
|
|
1946
|
+
return response;
|
|
1947
|
+
}
|
|
1948
|
+
} catch (error) {
|
|
1949
|
+
if (canRetry && error instanceof TypeError) {
|
|
1950
|
+
await retryRuntime.wait(retryDelayMs);
|
|
1951
|
+
} else {
|
|
1952
|
+
throw error;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
throw new PlaycademyError("Request failed after exhausting retries");
|
|
1957
|
+
}
|
|
1958
|
+
function prepareRequestBody(body, headers) {
|
|
1959
|
+
if (body instanceof FormData) {
|
|
1960
|
+
return body;
|
|
1961
|
+
}
|
|
1962
|
+
if (body instanceof ArrayBuffer || body instanceof Blob || ArrayBuffer.isView(body)) {
|
|
1963
|
+
if (!headers["Content-Type"]) {
|
|
1964
|
+
headers["Content-Type"] = "application/octet-stream";
|
|
1965
|
+
}
|
|
1966
|
+
return body;
|
|
1967
|
+
}
|
|
1968
|
+
if (body !== undefined && body !== null) {
|
|
1969
|
+
if (headers["Content-Type"]) {
|
|
1970
|
+
return typeof body === "string" ? body : JSON.stringify(body);
|
|
1971
|
+
}
|
|
1972
|
+
headers["Content-Type"] = "application/json";
|
|
1973
|
+
return JSON.stringify(body);
|
|
1974
|
+
}
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1919
1977
|
function checkDevWarnings(data) {
|
|
1920
1978
|
if (!data || typeof data !== "object") {
|
|
1921
1979
|
return;
|
|
@@ -1943,25 +2001,6 @@ function checkDevWarnings(data) {
|
|
|
1943
2001
|
}
|
|
1944
2002
|
}
|
|
1945
2003
|
}
|
|
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
2004
|
async function request({
|
|
1966
2005
|
path,
|
|
1967
2006
|
baseUrl,
|
|
@@ -1973,7 +2012,7 @@ async function request({
|
|
|
1973
2012
|
const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
|
|
1974
2013
|
const headers = { ...extraHeaders };
|
|
1975
2014
|
const payload = prepareRequestBody(body, headers);
|
|
1976
|
-
const res = await
|
|
2015
|
+
const res = await fetchWithRetry(url, {
|
|
1977
2016
|
method,
|
|
1978
2017
|
headers,
|
|
1979
2018
|
body: payload,
|
|
@@ -2010,21 +2049,26 @@ async function request({
|
|
|
2010
2049
|
}
|
|
2011
2050
|
async function fetchManifest(deploymentUrl) {
|
|
2012
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
|
+
}
|
|
2013
2065
|
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
2066
|
return await response.json();
|
|
2020
2067
|
} catch (error) {
|
|
2021
|
-
|
|
2022
|
-
throw error;
|
|
2023
|
-
}
|
|
2024
|
-
log.error(`[Playcademy SDK] Error fetching or parsing manifest from ${manifestUrl}:`, {
|
|
2068
|
+
log.error(`[Playcademy SDK] Error parsing manifest from ${manifestUrl}:`, {
|
|
2025
2069
|
error
|
|
2026
2070
|
});
|
|
2027
|
-
throw new
|
|
2071
|
+
throw new ManifestError("Failed to parse game manifest", "permanent");
|
|
2028
2072
|
}
|
|
2029
2073
|
}
|
|
2030
2074
|
|
|
@@ -2040,6 +2084,7 @@ class PlaycademyBaseClient {
|
|
|
2040
2084
|
authContext;
|
|
2041
2085
|
initPayload;
|
|
2042
2086
|
connectionManager;
|
|
2087
|
+
launchId;
|
|
2043
2088
|
_sessionManager = {
|
|
2044
2089
|
startSession: async (gameId) => this.request(`/games/${gameId}/sessions`, "POST"),
|
|
2045
2090
|
endSession: async (sessionId, gameId) => this.request(`/games/${gameId}/sessions/${sessionId}`, "DELETE")
|
|
@@ -2048,6 +2093,7 @@ class PlaycademyBaseClient {
|
|
|
2048
2093
|
this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
|
|
2049
2094
|
this.gameUrl = config?.gameUrl;
|
|
2050
2095
|
this.gameId = config?.gameId;
|
|
2096
|
+
this.launchId = config?.launchId ?? undefined;
|
|
2051
2097
|
this.config = config || {};
|
|
2052
2098
|
this.authStrategy = createAuthStrategy(config?.token ?? null, config?.tokenType);
|
|
2053
2099
|
this._detectAuthContext();
|
|
@@ -2075,6 +2121,9 @@ class PlaycademyBaseClient {
|
|
|
2075
2121
|
this.authStrategy = createAuthStrategy(token, tokenType);
|
|
2076
2122
|
this.emit("authChange", { token });
|
|
2077
2123
|
}
|
|
2124
|
+
setLaunchId(launchId) {
|
|
2125
|
+
this.launchId = launchId ?? undefined;
|
|
2126
|
+
}
|
|
2078
2127
|
getTokenType() {
|
|
2079
2128
|
return this.authStrategy.getType();
|
|
2080
2129
|
}
|
|
@@ -2117,7 +2166,8 @@ class PlaycademyBaseClient {
|
|
|
2117
2166
|
async request(path, method, options) {
|
|
2118
2167
|
const effectiveHeaders = {
|
|
2119
2168
|
...options?.headers,
|
|
2120
|
-
...this.authStrategy.getHeaders()
|
|
2169
|
+
...this.authStrategy.getHeaders(),
|
|
2170
|
+
...this.launchId ? { "x-playcademy-launch-id": this.launchId } : {}
|
|
2121
2171
|
};
|
|
2122
2172
|
try {
|
|
2123
2173
|
const result = await request({
|
package/dist/internal.d.ts
CHANGED
|
@@ -941,6 +941,11 @@ interface SpriteConfigWithDimensions {
|
|
|
941
941
|
declare class PlaycademyError extends Error {
|
|
942
942
|
constructor(message: string);
|
|
943
943
|
}
|
|
944
|
+
type ManifestErrorKind = 'temporary' | 'permanent';
|
|
945
|
+
declare class ManifestError extends PlaycademyError {
|
|
946
|
+
readonly kind: ManifestErrorKind;
|
|
947
|
+
constructor(message: string, kind: ManifestErrorKind);
|
|
948
|
+
}
|
|
944
949
|
/**
|
|
945
950
|
* Error codes returned by the API.
|
|
946
951
|
* These map to specific error types and HTTP status codes.
|
|
@@ -5629,6 +5634,7 @@ declare abstract class PlaycademyBaseClient {
|
|
|
5629
5634
|
};
|
|
5630
5635
|
protected initPayload?: InitPayload;
|
|
5631
5636
|
protected connectionManager?: ConnectionManager;
|
|
5637
|
+
protected launchId?: string;
|
|
5632
5638
|
/**
|
|
5633
5639
|
* Internal session manager for automatic session lifecycle.
|
|
5634
5640
|
* @private
|
|
@@ -5657,6 +5663,7 @@ declare abstract class PlaycademyBaseClient {
|
|
|
5657
5663
|
* Sets the authentication token for API requests.
|
|
5658
5664
|
*/
|
|
5659
5665
|
setToken(token: string | null, tokenType?: TokenType): void;
|
|
5666
|
+
setLaunchId(launchId: string | null | undefined): void;
|
|
5660
5667
|
/**
|
|
5661
5668
|
* Gets the current token type.
|
|
5662
5669
|
*/
|
|
@@ -5954,6 +5961,7 @@ interface ClientConfig {
|
|
|
5954
5961
|
token?: string;
|
|
5955
5962
|
tokenType?: TokenType;
|
|
5956
5963
|
gameId?: string;
|
|
5964
|
+
launchId?: string;
|
|
5957
5965
|
autoStartSession?: boolean;
|
|
5958
5966
|
onDisconnect?: DisconnectHandler;
|
|
5959
5967
|
enableConnectionMonitoring?: boolean;
|
|
@@ -7629,5 +7637,5 @@ declare class PlaycademyInternalClient extends PlaycademyBaseClient {
|
|
|
7629
7637
|
};
|
|
7630
7638
|
}
|
|
7631
7639
|
|
|
7632
|
-
export { AchievementCompletionType, ApiError, ConnectionManager, ConnectionMonitor, MessageEvents, NotificationStatus, NotificationType, PlaycademyInternalClient as PlaycademyClient, PlaycademyError, PlaycademyInternalClient, extractApiErrorInfo, messaging };
|
|
7640
|
+
export { AchievementCompletionType, ApiError, ConnectionManager, ConnectionMonitor, ManifestError, MessageEvents, NotificationStatus, NotificationType, PlaycademyInternalClient as PlaycademyClient, PlaycademyError, PlaycademyInternalClient, extractApiErrorInfo, messaging };
|
|
7633
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 };
|
package/dist/internal.js
CHANGED
|
@@ -407,6 +407,16 @@ class PlaycademyError extends Error {
|
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
+
class ManifestError extends PlaycademyError {
|
|
411
|
+
kind;
|
|
412
|
+
constructor(message, kind) {
|
|
413
|
+
super(message);
|
|
414
|
+
this.name = "ManifestError";
|
|
415
|
+
this.kind = kind;
|
|
416
|
+
Object.setPrototypeOf(this, ManifestError.prototype);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
410
420
|
class ApiError extends Error {
|
|
411
421
|
code;
|
|
412
422
|
details;
|
|
@@ -2027,6 +2037,54 @@ function createDevNamespace(client) {
|
|
|
2027
2037
|
};
|
|
2028
2038
|
}
|
|
2029
2039
|
// src/core/request.ts
|
|
2040
|
+
var RETRY_DELAYS_MS = [500, 1500];
|
|
2041
|
+
function wait(ms) {
|
|
2042
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2043
|
+
}
|
|
2044
|
+
var retryRuntime = { wait };
|
|
2045
|
+
function isRetryableStatus(status) {
|
|
2046
|
+
return status === 429 || status >= 500;
|
|
2047
|
+
}
|
|
2048
|
+
async function fetchWithRetry(url, init2) {
|
|
2049
|
+
for (let attempt = 0;attempt <= RETRY_DELAYS_MS.length; attempt++) {
|
|
2050
|
+
const retryDelayMs = RETRY_DELAYS_MS[attempt];
|
|
2051
|
+
const canRetry = init2.method === "GET" && retryDelayMs !== undefined;
|
|
2052
|
+
try {
|
|
2053
|
+
const response = await fetch(url, init2);
|
|
2054
|
+
if (canRetry && isRetryableStatus(response.status)) {
|
|
2055
|
+
await retryRuntime.wait(retryDelayMs);
|
|
2056
|
+
} else {
|
|
2057
|
+
return response;
|
|
2058
|
+
}
|
|
2059
|
+
} catch (error) {
|
|
2060
|
+
if (canRetry && error instanceof TypeError) {
|
|
2061
|
+
await retryRuntime.wait(retryDelayMs);
|
|
2062
|
+
} else {
|
|
2063
|
+
throw error;
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
throw new PlaycademyError("Request failed after exhausting retries");
|
|
2068
|
+
}
|
|
2069
|
+
function prepareRequestBody(body, headers) {
|
|
2070
|
+
if (body instanceof FormData) {
|
|
2071
|
+
return body;
|
|
2072
|
+
}
|
|
2073
|
+
if (body instanceof ArrayBuffer || body instanceof Blob || ArrayBuffer.isView(body)) {
|
|
2074
|
+
if (!headers["Content-Type"]) {
|
|
2075
|
+
headers["Content-Type"] = "application/octet-stream";
|
|
2076
|
+
}
|
|
2077
|
+
return body;
|
|
2078
|
+
}
|
|
2079
|
+
if (body !== undefined && body !== null) {
|
|
2080
|
+
if (headers["Content-Type"]) {
|
|
2081
|
+
return typeof body === "string" ? body : JSON.stringify(body);
|
|
2082
|
+
}
|
|
2083
|
+
headers["Content-Type"] = "application/json";
|
|
2084
|
+
return JSON.stringify(body);
|
|
2085
|
+
}
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2030
2088
|
function checkDevWarnings(data) {
|
|
2031
2089
|
if (!data || typeof data !== "object") {
|
|
2032
2090
|
return;
|
|
@@ -2054,25 +2112,6 @@ function checkDevWarnings(data) {
|
|
|
2054
2112
|
}
|
|
2055
2113
|
}
|
|
2056
2114
|
}
|
|
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
2115
|
async function request({
|
|
2077
2116
|
path,
|
|
2078
2117
|
baseUrl,
|
|
@@ -2084,7 +2123,7 @@ async function request({
|
|
|
2084
2123
|
const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
|
|
2085
2124
|
const headers = { ...extraHeaders };
|
|
2086
2125
|
const payload = prepareRequestBody(body, headers);
|
|
2087
|
-
const res = await
|
|
2126
|
+
const res = await fetchWithRetry(url, {
|
|
2088
2127
|
method,
|
|
2089
2128
|
headers,
|
|
2090
2129
|
body: payload,
|
|
@@ -2121,21 +2160,26 @@ async function request({
|
|
|
2121
2160
|
}
|
|
2122
2161
|
async function fetchManifest(deploymentUrl) {
|
|
2123
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
|
+
}
|
|
2124
2176
|
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
2177
|
return await response.json();
|
|
2131
2178
|
} catch (error) {
|
|
2132
|
-
|
|
2133
|
-
throw error;
|
|
2134
|
-
}
|
|
2135
|
-
log.error(`[Playcademy SDK] Error fetching or parsing manifest from ${manifestUrl}:`, {
|
|
2179
|
+
log.error(`[Playcademy SDK] Error parsing manifest from ${manifestUrl}:`, {
|
|
2136
2180
|
error
|
|
2137
2181
|
});
|
|
2138
|
-
throw new
|
|
2182
|
+
throw new ManifestError("Failed to parse game manifest", "permanent");
|
|
2139
2183
|
}
|
|
2140
2184
|
}
|
|
2141
2185
|
|
|
@@ -2956,6 +3000,7 @@ class PlaycademyBaseClient {
|
|
|
2956
3000
|
authContext;
|
|
2957
3001
|
initPayload;
|
|
2958
3002
|
connectionManager;
|
|
3003
|
+
launchId;
|
|
2959
3004
|
_sessionManager = {
|
|
2960
3005
|
startSession: async (gameId) => this.request(`/games/${gameId}/sessions`, "POST"),
|
|
2961
3006
|
endSession: async (sessionId, gameId) => this.request(`/games/${gameId}/sessions/${sessionId}`, "DELETE")
|
|
@@ -2964,6 +3009,7 @@ class PlaycademyBaseClient {
|
|
|
2964
3009
|
this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
|
|
2965
3010
|
this.gameUrl = config?.gameUrl;
|
|
2966
3011
|
this.gameId = config?.gameId;
|
|
3012
|
+
this.launchId = config?.launchId ?? undefined;
|
|
2967
3013
|
this.config = config || {};
|
|
2968
3014
|
this.authStrategy = createAuthStrategy(config?.token ?? null, config?.tokenType);
|
|
2969
3015
|
this._detectAuthContext();
|
|
@@ -2991,6 +3037,9 @@ class PlaycademyBaseClient {
|
|
|
2991
3037
|
this.authStrategy = createAuthStrategy(token, tokenType);
|
|
2992
3038
|
this.emit("authChange", { token });
|
|
2993
3039
|
}
|
|
3040
|
+
setLaunchId(launchId) {
|
|
3041
|
+
this.launchId = launchId ?? undefined;
|
|
3042
|
+
}
|
|
2994
3043
|
getTokenType() {
|
|
2995
3044
|
return this.authStrategy.getType();
|
|
2996
3045
|
}
|
|
@@ -3033,7 +3082,8 @@ class PlaycademyBaseClient {
|
|
|
3033
3082
|
async request(path, method, options) {
|
|
3034
3083
|
const effectiveHeaders = {
|
|
3035
3084
|
...options?.headers,
|
|
3036
|
-
...this.authStrategy.getHeaders()
|
|
3085
|
+
...this.authStrategy.getHeaders(),
|
|
3086
|
+
...this.launchId ? { "x-playcademy-launch-id": this.launchId } : {}
|
|
3037
3087
|
};
|
|
3038
3088
|
try {
|
|
3039
3089
|
const result = await request({
|
|
@@ -3160,6 +3210,7 @@ export {
|
|
|
3160
3210
|
PlaycademyError,
|
|
3161
3211
|
PlaycademyInternalClient as PlaycademyClient,
|
|
3162
3212
|
MessageEvents,
|
|
3213
|
+
ManifestError,
|
|
3163
3214
|
ConnectionMonitor,
|
|
3164
3215
|
ConnectionManager,
|
|
3165
3216
|
ApiError
|
package/dist/server.js
CHANGED
|
@@ -81,6 +81,16 @@ class PlaycademyError extends Error {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
class ManifestError extends PlaycademyError {
|
|
85
|
+
kind;
|
|
86
|
+
constructor(message, kind) {
|
|
87
|
+
super(message);
|
|
88
|
+
this.name = "ManifestError";
|
|
89
|
+
this.kind = kind;
|
|
90
|
+
Object.setPrototypeOf(this, ManifestError.prototype);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
84
94
|
class ApiError extends Error {
|
|
85
95
|
code;
|
|
86
96
|
details;
|
package/dist/types.d.ts
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.3.3",
|
|
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",
|