@playcademy/sdk 0.0.1-beta.2 → 0.0.1-beta.21
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/README.md +11 -7
- package/dist/core/client.d.ts +216 -38
- package/dist/core/namespaces/admin.d.ts +377 -0
- package/dist/core/namespaces/auth.d.ts +23 -0
- package/dist/core/namespaces/dev.d.ts +152 -0
- package/dist/core/namespaces/games.d.ts +97 -0
- package/dist/core/namespaces/index.d.ts +9 -0
- package/dist/core/namespaces/maps.d.ts +37 -0
- package/dist/core/namespaces/runtime.d.ts +45 -0
- package/dist/core/namespaces/shop.d.ts +26 -0
- package/dist/core/namespaces/telemetry.d.ts +28 -0
- package/dist/core/namespaces/users.d.ts +84 -0
- package/dist/core/request.d.ts +1 -1
- package/dist/core/static/index.d.ts +2 -0
- package/dist/core/static/init.d.ts +21 -0
- package/dist/core/static/login.d.ts +24 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +584 -0
- package/dist/messaging.d.ts +510 -0
- package/dist/types.d.ts +31 -3
- package/package.json +11 -10
- package/dist/bus.d.ts +0 -37
- package/dist/runtime.d.ts +0 -7
- package/dist/runtime.js +0 -370
package/dist/runtime.js
DELETED
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
// src/bus.ts
|
|
2
|
-
var BusEvents;
|
|
3
|
-
((BusEvents2) => {
|
|
4
|
-
BusEvents2["INIT"] = "PLAYCADEMY_INIT";
|
|
5
|
-
BusEvents2["TOKEN_REFRESH"] = "PLAYCADEMY_TOKEN_REFRESH";
|
|
6
|
-
BusEvents2["PAUSE"] = "PLAYCADEMY_PAUSE";
|
|
7
|
-
BusEvents2["RESUME"] = "PLAYCADEMY_RESUME";
|
|
8
|
-
BusEvents2["FORCE_EXIT"] = "PLAYCADEMY_FORCE_EXIT";
|
|
9
|
-
BusEvents2["OVERLAY"] = "PLAYCADEMY_OVERLAY";
|
|
10
|
-
BusEvents2["READY"] = "PLAYCADEMY_READY";
|
|
11
|
-
BusEvents2["EXIT"] = "PLAYCADEMY_EXIT";
|
|
12
|
-
BusEvents2["TELEMETRY"] = "PLAYCADEMY_TELEMETRY";
|
|
13
|
-
})(BusEvents ||= {});
|
|
14
|
-
var busListeners = new Map;
|
|
15
|
-
var bus = {
|
|
16
|
-
emit(type, payload) {
|
|
17
|
-
window.dispatchEvent(new CustomEvent(type, { detail: payload }));
|
|
18
|
-
},
|
|
19
|
-
on(type, handler) {
|
|
20
|
-
const listener = (event) => handler(event.detail);
|
|
21
|
-
if (!busListeners.has(type)) {
|
|
22
|
-
busListeners.set(type, new Map);
|
|
23
|
-
}
|
|
24
|
-
busListeners.get(type).set(handler, listener);
|
|
25
|
-
window.addEventListener(type, listener);
|
|
26
|
-
},
|
|
27
|
-
off(type, handler) {
|
|
28
|
-
const typeListeners = busListeners.get(type);
|
|
29
|
-
if (!typeListeners || !typeListeners.has(handler)) {
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const actualListener = typeListeners.get(handler);
|
|
33
|
-
window.removeEventListener(type, actualListener);
|
|
34
|
-
typeListeners.delete(handler);
|
|
35
|
-
if (typeListeners.size === 0) {
|
|
36
|
-
busListeners.delete(type);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// src/core/errors.ts
|
|
42
|
-
class PlaycademyError extends Error {
|
|
43
|
-
constructor(message) {
|
|
44
|
-
super(message);
|
|
45
|
-
this.name = "PlaycademyError";
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
class ApiError extends Error {
|
|
50
|
-
status;
|
|
51
|
-
details;
|
|
52
|
-
constructor(status, message, details) {
|
|
53
|
-
super(`${status} ${message}`);
|
|
54
|
-
this.status = status;
|
|
55
|
-
this.details = details;
|
|
56
|
-
Object.setPrototypeOf(this, ApiError.prototype);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// src/core/request.ts
|
|
61
|
-
import { ManifestV1Schema } from "@playcademy/data/schemas";
|
|
62
|
-
async function request({
|
|
63
|
-
path,
|
|
64
|
-
baseUrl,
|
|
65
|
-
token,
|
|
66
|
-
method = "GET",
|
|
67
|
-
body,
|
|
68
|
-
extraHeaders = {}
|
|
69
|
-
}) {
|
|
70
|
-
const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
|
|
71
|
-
const headers = { ...extraHeaders };
|
|
72
|
-
let payload;
|
|
73
|
-
if (body instanceof FormData) {
|
|
74
|
-
payload = body;
|
|
75
|
-
} else if (body !== undefined && body !== null) {
|
|
76
|
-
payload = JSON.stringify(body);
|
|
77
|
-
headers["Content-Type"] = "application/json";
|
|
78
|
-
}
|
|
79
|
-
if (token)
|
|
80
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
81
|
-
const res = await fetch(url, {
|
|
82
|
-
method,
|
|
83
|
-
headers,
|
|
84
|
-
body: payload,
|
|
85
|
-
credentials: "omit"
|
|
86
|
-
});
|
|
87
|
-
if (!res.ok) {
|
|
88
|
-
const errorBody = await res.clone().json().catch(() => res.text().catch(() => {
|
|
89
|
-
return;
|
|
90
|
-
})) ?? undefined;
|
|
91
|
-
throw new ApiError(res.status, res.statusText, errorBody);
|
|
92
|
-
}
|
|
93
|
-
if (res.status === 204)
|
|
94
|
-
return;
|
|
95
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
96
|
-
if (contentType.includes("application/json")) {
|
|
97
|
-
return await res.json();
|
|
98
|
-
}
|
|
99
|
-
return await res.text();
|
|
100
|
-
}
|
|
101
|
-
async function fetchManifest(assetBundleBase) {
|
|
102
|
-
const manifestUrl = `${assetBundleBase.replace(/\/$/, "")}/playcademy.manifest.json`;
|
|
103
|
-
try {
|
|
104
|
-
const response = await fetch(manifestUrl);
|
|
105
|
-
if (!response.ok) {
|
|
106
|
-
console.error(`[fetchManifest] Failed to fetch manifest from ${manifestUrl}. Status: ${response.status}`);
|
|
107
|
-
throw new PlaycademyError(`Failed to fetch manifest: ${response.status} ${response.statusText}`);
|
|
108
|
-
}
|
|
109
|
-
const manifestJson = await response.json();
|
|
110
|
-
const validationResult = ManifestV1Schema.safeParse(manifestJson);
|
|
111
|
-
if (!validationResult.success) {
|
|
112
|
-
console.error(`[fetchManifest] Invalid manifest file at ${manifestUrl}: `, validationResult.error.flatten());
|
|
113
|
-
throw new PlaycademyError("Invalid manifest file format");
|
|
114
|
-
}
|
|
115
|
-
return validationResult.data;
|
|
116
|
-
} catch (error) {
|
|
117
|
-
if (error instanceof PlaycademyError) {
|
|
118
|
-
throw error;
|
|
119
|
-
}
|
|
120
|
-
console.error(`[fetchManifest] Error fetching or parsing manifest from ${manifestUrl}:`, error);
|
|
121
|
-
throw new PlaycademyError("Failed to load or parse game manifest");
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// src/core/client.ts
|
|
126
|
-
class PlaycademyClient {
|
|
127
|
-
baseUrl;
|
|
128
|
-
token;
|
|
129
|
-
gameId;
|
|
130
|
-
listeners = {};
|
|
131
|
-
internalClientSessionId;
|
|
132
|
-
constructor(config) {
|
|
133
|
-
this.baseUrl = config.baseUrl;
|
|
134
|
-
this.token = config.token;
|
|
135
|
-
this.gameId = config.gameId;
|
|
136
|
-
if (this.gameId) {
|
|
137
|
-
this._initializeInternalSession().catch((error) => {
|
|
138
|
-
console.error("[SDK] Background initialization of auto-session failed:", error);
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
async _initializeInternalSession() {
|
|
143
|
-
if (!this.gameId || this.internalClientSessionId) {
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
try {
|
|
147
|
-
const response = await this.games.startSession(this.gameId);
|
|
148
|
-
this.internalClientSessionId = response.sessionId;
|
|
149
|
-
} catch (error) {
|
|
150
|
-
console.error("[SDK] Auto-starting session failed for game", this.gameId, error);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
getBaseUrl() {
|
|
154
|
-
const isRelative = this.baseUrl.startsWith("/");
|
|
155
|
-
const isBrowser = typeof window !== "undefined";
|
|
156
|
-
return isRelative && isBrowser ? `${window.location.origin}${this.baseUrl}` : this.baseUrl;
|
|
157
|
-
}
|
|
158
|
-
on(event, callback) {
|
|
159
|
-
this.listeners[event] = this.listeners[event] ?? [];
|
|
160
|
-
this.listeners[event].push(callback);
|
|
161
|
-
}
|
|
162
|
-
emit(event, payload) {
|
|
163
|
-
(this.listeners[event] ?? []).forEach((listener) => {
|
|
164
|
-
listener(payload);
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
setToken(token) {
|
|
168
|
-
this.token = token ?? undefined;
|
|
169
|
-
this.emit("authChange", { token: this.token ?? null });
|
|
170
|
-
}
|
|
171
|
-
onAuthChange(callback) {
|
|
172
|
-
this.on("authChange", (payload) => callback(payload.token));
|
|
173
|
-
}
|
|
174
|
-
async request(path, method, body, headers) {
|
|
175
|
-
const effectiveHeaders = { ...headers };
|
|
176
|
-
return request({
|
|
177
|
-
path,
|
|
178
|
-
method,
|
|
179
|
-
body,
|
|
180
|
-
baseUrl: this.baseUrl,
|
|
181
|
-
token: this.token,
|
|
182
|
-
extraHeaders: effectiveHeaders
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
_ensureGameId() {
|
|
186
|
-
if (!this.gameId) {
|
|
187
|
-
throw new PlaycademyError("This operation requires a gameId, but none was provided when initializing the client.");
|
|
188
|
-
}
|
|
189
|
-
return this.gameId;
|
|
190
|
-
}
|
|
191
|
-
auth = {
|
|
192
|
-
logout: async () => {
|
|
193
|
-
await this.request(`/auth/logout`, "DELETE");
|
|
194
|
-
this.setToken(null);
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
runtime = {
|
|
198
|
-
getGameToken: async (gameId, options) => {
|
|
199
|
-
const res = await this.request(`/games/${gameId}/token`, "POST");
|
|
200
|
-
if (options?.apply) {
|
|
201
|
-
this.setToken(res.token);
|
|
202
|
-
}
|
|
203
|
-
return res;
|
|
204
|
-
},
|
|
205
|
-
exit: async () => {
|
|
206
|
-
if (this.internalClientSessionId && this.gameId) {
|
|
207
|
-
try {
|
|
208
|
-
await this.games.endSession(this.internalClientSessionId, this.gameId);
|
|
209
|
-
} catch (error) {
|
|
210
|
-
console.error("[SDK] Failed to auto-end session:", this.internalClientSessionId, error);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
bus.emit("PLAYCADEMY_EXIT" /* EXIT */, undefined);
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
games = {
|
|
217
|
-
fetch: async (gameIdOrSlug) => {
|
|
218
|
-
const baseGameData = await this.request(`/games/${gameIdOrSlug}`, "GET");
|
|
219
|
-
const manifestData = await fetchManifest(baseGameData.assetBundleBase);
|
|
220
|
-
return {
|
|
221
|
-
...baseGameData,
|
|
222
|
-
manifest: manifestData
|
|
223
|
-
};
|
|
224
|
-
},
|
|
225
|
-
list: () => this.request("/games", "GET"),
|
|
226
|
-
saveState: async (state) => {
|
|
227
|
-
const gameId = this._ensureGameId();
|
|
228
|
-
await this.request(`/games/${gameId}/state`, "POST", state);
|
|
229
|
-
},
|
|
230
|
-
loadState: async () => {
|
|
231
|
-
const gameId = this._ensureGameId();
|
|
232
|
-
return this.request(`/games/${gameId}/state`, "GET");
|
|
233
|
-
},
|
|
234
|
-
startSession: async (gameId) => {
|
|
235
|
-
const idToUse = gameId ?? this._ensureGameId();
|
|
236
|
-
return this.request(`/games/${idToUse}/sessions`, "POST", {});
|
|
237
|
-
},
|
|
238
|
-
endSession: async (sessionId, gameId) => {
|
|
239
|
-
const effectiveGameIdToEnd = gameId ?? this._ensureGameId();
|
|
240
|
-
if (this.internalClientSessionId && sessionId === this.internalClientSessionId && effectiveGameIdToEnd === this.gameId) {
|
|
241
|
-
this.internalClientSessionId = undefined;
|
|
242
|
-
}
|
|
243
|
-
await this.request(`/games/${effectiveGameIdToEnd}/sessions/${sessionId}/end`, "POST");
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
users = {
|
|
247
|
-
me: async () => {
|
|
248
|
-
return this.request("/users/me", "GET");
|
|
249
|
-
},
|
|
250
|
-
inventory: {
|
|
251
|
-
get: async () => this.request(`/inventory`, "GET"),
|
|
252
|
-
add: async (rewardId, qty) => {
|
|
253
|
-
const res = await this.request(`/inventory/add`, "POST", { rewardId, qty });
|
|
254
|
-
this.emit("inventoryChange", {
|
|
255
|
-
rewardId,
|
|
256
|
-
delta: qty,
|
|
257
|
-
newTotal: res.newTotal
|
|
258
|
-
});
|
|
259
|
-
return res;
|
|
260
|
-
},
|
|
261
|
-
spend: async (rewardId, qty) => {
|
|
262
|
-
const res = await this.request(`/inventory/spend`, "POST", { rewardId, qty });
|
|
263
|
-
this.emit("inventoryChange", {
|
|
264
|
-
rewardId,
|
|
265
|
-
delta: -qty,
|
|
266
|
-
newTotal: res.newTotal
|
|
267
|
-
});
|
|
268
|
-
return res;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
dev = {
|
|
273
|
-
auth: {
|
|
274
|
-
applyForDeveloper: () => this.request("/dev/apply", "POST"),
|
|
275
|
-
getDeveloperStatus: async () => {
|
|
276
|
-
const response = await this.request("/dev/status", "GET");
|
|
277
|
-
return response.status;
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
|
-
games: {
|
|
281
|
-
upsert: (slug, metadata, file) => {
|
|
282
|
-
const form = new FormData;
|
|
283
|
-
form.append("metadata", JSON.stringify(metadata));
|
|
284
|
-
form.append("file", file);
|
|
285
|
-
return this.request(`/games/${slug}`, "PUT", form);
|
|
286
|
-
},
|
|
287
|
-
update: (gameId, props) => this.request(`/games/${gameId}`, "PATCH", props),
|
|
288
|
-
delete: (gameId) => this.request(`/games/${gameId}`, "DELETE")
|
|
289
|
-
},
|
|
290
|
-
keys: {
|
|
291
|
-
createKey: (gameId, label) => this.request(`/dev/games/${gameId}/keys`, "POST", { label }),
|
|
292
|
-
listKeys: (gameId) => this.request(`/dev/games/${gameId}/keys`, "GET"),
|
|
293
|
-
revokeKey: (keyId) => this.request(`/dev/keys/${keyId}`, "DELETE")
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
maps = {
|
|
297
|
-
elements: (mapId) => this.request(`/map/elements?mapId=${mapId}`, "GET")
|
|
298
|
-
};
|
|
299
|
-
admin = {
|
|
300
|
-
games: {
|
|
301
|
-
pauseGame: (gameId) => this.request(`/admin/games/${gameId}/pause`, "POST"),
|
|
302
|
-
resumeGame: (gameId) => this.request(`/admin/games/${gameId}/resume`, "POST")
|
|
303
|
-
},
|
|
304
|
-
rewards: {
|
|
305
|
-
createReward: (props) => this.request("/rewards", "POST", props),
|
|
306
|
-
getReward: (rewardId) => this.request(`/rewards/${rewardId}`, "GET"),
|
|
307
|
-
listRewards: () => this.request("/rewards", "GET"),
|
|
308
|
-
updateReward: (rewardId, props) => this.request(`/rewards/${rewardId}`, "PATCH", props),
|
|
309
|
-
deleteReward: (rewardId) => this.request(`/rewards/${rewardId}`, "DELETE")
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
telemetry = {
|
|
313
|
-
pushMetrics: (metrics) => this.request(`/telemetry/metrics`, "POST", metrics)
|
|
314
|
-
};
|
|
315
|
-
ping() {
|
|
316
|
-
return "pong";
|
|
317
|
-
}
|
|
318
|
-
static async login(baseUrl, email, password) {
|
|
319
|
-
let url = baseUrl;
|
|
320
|
-
if (baseUrl.startsWith("/") && typeof window !== "undefined") {
|
|
321
|
-
url = window.location.origin + baseUrl;
|
|
322
|
-
}
|
|
323
|
-
url = url + "/auth/login";
|
|
324
|
-
const response = await fetch(url, {
|
|
325
|
-
method: "POST",
|
|
326
|
-
headers: {
|
|
327
|
-
"Content-Type": "application/json"
|
|
328
|
-
},
|
|
329
|
-
body: JSON.stringify({ email, password })
|
|
330
|
-
});
|
|
331
|
-
if (!response.ok) {
|
|
332
|
-
try {
|
|
333
|
-
const errorData = await response.json();
|
|
334
|
-
const errorMessage = errorData && errorData.message ? String(errorData.message) : response.statusText;
|
|
335
|
-
throw new PlaycademyError(errorMessage);
|
|
336
|
-
} catch (error) {
|
|
337
|
-
console.error("[SDK] Failed to parse error response JSON, using status text instead:", error);
|
|
338
|
-
throw new PlaycademyError(response.statusText);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
return response.json();
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// src/runtime.ts
|
|
346
|
-
async function initFromWindow() {
|
|
347
|
-
if (typeof window === "undefined") {
|
|
348
|
-
throw new Error("initFromWindow must run in a browser context");
|
|
349
|
-
}
|
|
350
|
-
const preloaded = window.PLAYCADEMY;
|
|
351
|
-
const config = preloaded?.token ? preloaded : await new Promise((resolve) => bus.on("PLAYCADEMY_INIT" /* INIT */, resolve));
|
|
352
|
-
const client = new PlaycademyClient({
|
|
353
|
-
baseUrl: config.baseUrl,
|
|
354
|
-
token: config.token,
|
|
355
|
-
gameId: config.gameId
|
|
356
|
-
});
|
|
357
|
-
bus.on("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, ({ token }) => client.setToken(token));
|
|
358
|
-
bus.emit("PLAYCADEMY_READY" /* READY */, undefined);
|
|
359
|
-
if (import.meta.env?.MODE === "development") {
|
|
360
|
-
window.PLAYCADEMY_CLIENT = client;
|
|
361
|
-
}
|
|
362
|
-
return client;
|
|
363
|
-
}
|
|
364
|
-
export {
|
|
365
|
-
initFromWindow,
|
|
366
|
-
bus,
|
|
367
|
-
PlaycademyError,
|
|
368
|
-
PlaycademyClient,
|
|
369
|
-
BusEvents
|
|
370
|
-
};
|