@playcademy/sdk 0.0.1-beta.9 → 0.0.2
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 +511 -124
- package/dist/core/auth/flows/popup.d.ts +14 -0
- package/dist/core/auth/flows/redirect.d.ts +15 -0
- package/dist/core/auth/flows/unified.d.ts +11 -0
- package/dist/core/auth/login.d.ts +20 -0
- package/dist/core/auth/oauth.d.ts +115 -0
- package/dist/core/auth/utils.d.ts +23 -0
- package/dist/core/cache/cooldown-cache.d.ts +31 -0
- package/dist/core/cache/index.d.ts +14 -0
- package/dist/core/cache/permanent-cache.d.ts +39 -0
- package/dist/core/cache/singleton-cache.d.ts +29 -0
- package/dist/core/cache/ttl-cache.d.ts +54 -0
- package/dist/core/cache/types.d.ts +23 -0
- package/dist/core/client.d.ts +444 -68
- package/dist/core/namespaces/achievements.d.ts +84 -0
- package/dist/core/namespaces/admin.d.ts +385 -0
- package/dist/core/namespaces/auth.d.ts +54 -0
- package/dist/core/namespaces/character.d.ts +205 -0
- package/dist/core/namespaces/credits.d.ts +51 -0
- package/dist/core/namespaces/dev.d.ts +323 -0
- package/dist/core/namespaces/games.d.ts +173 -0
- package/dist/core/namespaces/identity.d.ts +91 -0
- package/dist/core/namespaces/index.d.ts +19 -0
- package/dist/core/namespaces/leaderboard.d.ts +48 -0
- package/dist/core/namespaces/levels.d.ts +90 -0
- package/dist/core/namespaces/maps.d.ts +93 -0
- package/dist/core/namespaces/realtime.client.d.ts +129 -0
- package/dist/core/namespaces/realtime.d.ts +90 -0
- package/dist/core/namespaces/runtime.d.ts +222 -0
- package/dist/core/namespaces/scores.d.ts +55 -0
- package/dist/core/namespaces/shop.d.ts +25 -0
- package/dist/core/namespaces/sprites.d.ts +35 -0
- package/dist/core/namespaces/telemetry.d.ts +28 -0
- package/dist/core/namespaces/timeback.d.ts +111 -0
- package/dist/core/namespaces/users.d.ts +172 -0
- package/dist/core/request.d.ts +1 -1
- package/dist/core/static/identity.d.ts +37 -0
- package/dist/core/static/index.d.ts +3 -0
- package/dist/core/static/init.d.ts +21 -0
- package/dist/core/static/login.d.ts +34 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +2846 -0
- package/dist/messaging.d.ts +544 -0
- package/dist/types.d.ts +169 -8
- package/dist/types.js +748 -0
- package/package.json +18 -11
- package/dist/bus.d.ts +0 -37
- package/dist/runtime.d.ts +0 -7
- package/dist/runtime.js +0 -377
package/dist/index.js
ADDED
|
@@ -0,0 +1,2846 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, {
|
|
5
|
+
get: all[name],
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
set: (newValue) => all[name] = () => newValue
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
12
|
+
|
|
13
|
+
// ../data/src/domains/game/table.ts
|
|
14
|
+
import {
|
|
15
|
+
jsonb,
|
|
16
|
+
pgEnum,
|
|
17
|
+
pgTable,
|
|
18
|
+
text,
|
|
19
|
+
timestamp,
|
|
20
|
+
uniqueIndex,
|
|
21
|
+
uuid,
|
|
22
|
+
varchar
|
|
23
|
+
} from "drizzle-orm/pg-core";
|
|
24
|
+
var gamePlatformEnum, gameBootModeEnum, gameTypeEnum, games, gameSessions, gameStates;
|
|
25
|
+
var init_table = __esm(() => {
|
|
26
|
+
init_table3();
|
|
27
|
+
init_table4();
|
|
28
|
+
gamePlatformEnum = pgEnum("game_platform", ["web", "godot", "unity"]);
|
|
29
|
+
gameBootModeEnum = pgEnum("game_boot_mode", ["iframe", "module"]);
|
|
30
|
+
gameTypeEnum = pgEnum("game_type", ["hosted", "external"]);
|
|
31
|
+
games = pgTable("games", {
|
|
32
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
33
|
+
developerId: text("developer_id").references(() => users.id, {
|
|
34
|
+
onDelete: "set null"
|
|
35
|
+
}),
|
|
36
|
+
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
|
37
|
+
displayName: varchar("display_name", { length: 255 }).notNull(),
|
|
38
|
+
version: varchar("version", { length: 50 }).notNull(),
|
|
39
|
+
gameType: gameTypeEnum("game_type").notNull().default("hosted"),
|
|
40
|
+
assetBundleBase: text("asset_bundle_base"),
|
|
41
|
+
externalUrl: text("external_url"),
|
|
42
|
+
platform: gamePlatformEnum("platform").notNull().default("web"),
|
|
43
|
+
mapElementId: uuid("map_element_id").references(() => mapElements.id, {
|
|
44
|
+
onDelete: "set null"
|
|
45
|
+
}),
|
|
46
|
+
metadata: jsonb("metadata").$type().notNull().default({}),
|
|
47
|
+
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
|
48
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow()
|
|
49
|
+
});
|
|
50
|
+
gameSessions = pgTable("game_sessions", {
|
|
51
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
52
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
53
|
+
gameId: uuid("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
|
|
54
|
+
startedAt: timestamp("started_at", { withTimezone: true }).notNull().defaultNow(),
|
|
55
|
+
endedAt: timestamp("ended_at", { withTimezone: true })
|
|
56
|
+
});
|
|
57
|
+
gameStates = pgTable("game_states", {
|
|
58
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
59
|
+
gameId: uuid("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
|
|
60
|
+
data: jsonb("data").default("{}"),
|
|
61
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow()
|
|
62
|
+
}, (table) => [uniqueIndex("unique_user_game_idx").on(table.userId, table.gameId)]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// ../data/src/domains/inventory/table.ts
|
|
66
|
+
import { relations, sql } from "drizzle-orm";
|
|
67
|
+
import {
|
|
68
|
+
boolean,
|
|
69
|
+
integer,
|
|
70
|
+
jsonb as jsonb2,
|
|
71
|
+
pgEnum as pgEnum2,
|
|
72
|
+
pgTable as pgTable2,
|
|
73
|
+
text as text2,
|
|
74
|
+
timestamp as timestamp2,
|
|
75
|
+
uniqueIndex as uniqueIndex2,
|
|
76
|
+
uuid as uuid2
|
|
77
|
+
} from "drizzle-orm/pg-core";
|
|
78
|
+
var itemTypeEnum, items, inventoryItems, currencies, shopListings, itemsRelations, currenciesRelations, shopListingsRelations, inventoryItemsRelations;
|
|
79
|
+
var init_table2 = __esm(() => {
|
|
80
|
+
init_table();
|
|
81
|
+
init_table3();
|
|
82
|
+
init_table4();
|
|
83
|
+
itemTypeEnum = pgEnum2("item_type", [
|
|
84
|
+
"currency",
|
|
85
|
+
"badge",
|
|
86
|
+
"trophy",
|
|
87
|
+
"collectible",
|
|
88
|
+
"consumable",
|
|
89
|
+
"unlock",
|
|
90
|
+
"upgrade",
|
|
91
|
+
"accessory",
|
|
92
|
+
"other"
|
|
93
|
+
]);
|
|
94
|
+
items = pgTable2("items", {
|
|
95
|
+
id: uuid2("id").primaryKey().defaultRandom(),
|
|
96
|
+
slug: text2("slug").notNull(),
|
|
97
|
+
gameId: uuid2("game_id").references(() => games.id, {
|
|
98
|
+
onDelete: "cascade"
|
|
99
|
+
}),
|
|
100
|
+
displayName: text2("display_name").notNull(),
|
|
101
|
+
description: text2("description"),
|
|
102
|
+
type: itemTypeEnum("type").notNull().default("other"),
|
|
103
|
+
isPlaceable: boolean("is_placeable").default(false).notNull(),
|
|
104
|
+
imageUrl: text2("image_url"),
|
|
105
|
+
metadata: jsonb2("metadata").default({}),
|
|
106
|
+
createdAt: timestamp2("created_at").defaultNow().notNull()
|
|
107
|
+
}, (table) => [
|
|
108
|
+
uniqueIndex2("items_game_slug_idx").on(table.gameId, table.slug),
|
|
109
|
+
uniqueIndex2("items_global_slug_idx").on(table.slug).where(sql`game_id IS NULL`)
|
|
110
|
+
]);
|
|
111
|
+
inventoryItems = pgTable2("inventory_items", {
|
|
112
|
+
id: uuid2("id").primaryKey().defaultRandom(),
|
|
113
|
+
userId: text2("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
114
|
+
itemId: uuid2("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
|
|
115
|
+
quantity: integer("quantity").notNull().default(1),
|
|
116
|
+
updatedAt: timestamp2("updated_at", { withTimezone: true }).defaultNow()
|
|
117
|
+
}, (table) => [uniqueIndex2("unique_user_item_idx").on(table.userId, table.itemId)]);
|
|
118
|
+
currencies = pgTable2("currencies", {
|
|
119
|
+
id: uuid2("id").primaryKey().defaultRandom(),
|
|
120
|
+
itemId: uuid2("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
|
|
121
|
+
symbol: text2("symbol"),
|
|
122
|
+
isPrimary: boolean("is_primary").default(false).notNull(),
|
|
123
|
+
createdAt: timestamp2("created_at").defaultNow().notNull(),
|
|
124
|
+
updatedAt: timestamp2("updated_at", { withTimezone: true }).defaultNow().$onUpdate(() => new Date)
|
|
125
|
+
}, (table) => [uniqueIndex2("currency_item_id_idx").on(table.itemId)]);
|
|
126
|
+
shopListings = pgTable2("shop_listings", {
|
|
127
|
+
id: uuid2("id").primaryKey().defaultRandom(),
|
|
128
|
+
itemId: uuid2("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
|
|
129
|
+
currencyId: uuid2("currency_id").notNull().references(() => currencies.id, { onDelete: "restrict" }),
|
|
130
|
+
price: integer("price").notNull(),
|
|
131
|
+
sellBackPercentage: integer("sell_back_percentage"),
|
|
132
|
+
stock: integer("stock"),
|
|
133
|
+
isActive: boolean("is_active").default(true).notNull(),
|
|
134
|
+
availableFrom: timestamp2("available_from", { withTimezone: true }),
|
|
135
|
+
availableUntil: timestamp2("available_until", { withTimezone: true }),
|
|
136
|
+
createdAt: timestamp2("created_at").defaultNow().notNull(),
|
|
137
|
+
updatedAt: timestamp2("updated_at", { withTimezone: true }).defaultNow().$onUpdate(() => new Date)
|
|
138
|
+
}, (table) => [uniqueIndex2("unique_item_currency_listing_idx").on(table.itemId, table.currencyId)]);
|
|
139
|
+
itemsRelations = relations(items, ({ many }) => ({
|
|
140
|
+
shopListings: many(shopListings),
|
|
141
|
+
inventoryItems: many(inventoryItems),
|
|
142
|
+
mapObjects: many(mapObjects)
|
|
143
|
+
}));
|
|
144
|
+
currenciesRelations = relations(currencies, ({ many }) => ({
|
|
145
|
+
shopListings: many(shopListings)
|
|
146
|
+
}));
|
|
147
|
+
shopListingsRelations = relations(shopListings, ({ one }) => ({
|
|
148
|
+
item: one(items, {
|
|
149
|
+
fields: [shopListings.itemId],
|
|
150
|
+
references: [items.id]
|
|
151
|
+
}),
|
|
152
|
+
currency: one(currencies, {
|
|
153
|
+
fields: [shopListings.currencyId],
|
|
154
|
+
references: [currencies.id]
|
|
155
|
+
})
|
|
156
|
+
}));
|
|
157
|
+
inventoryItemsRelations = relations(inventoryItems, ({ one }) => ({
|
|
158
|
+
item: one(items, {
|
|
159
|
+
fields: [inventoryItems.itemId],
|
|
160
|
+
references: [items.id]
|
|
161
|
+
}),
|
|
162
|
+
user: one(users, {
|
|
163
|
+
fields: [inventoryItems.userId],
|
|
164
|
+
references: [users.id]
|
|
165
|
+
})
|
|
166
|
+
}));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ../data/src/domains/map/table.ts
|
|
170
|
+
import { relations as relations2 } from "drizzle-orm";
|
|
171
|
+
import {
|
|
172
|
+
doublePrecision,
|
|
173
|
+
index,
|
|
174
|
+
integer as integer2,
|
|
175
|
+
jsonb as jsonb3,
|
|
176
|
+
pgEnum as pgEnum3,
|
|
177
|
+
pgTable as pgTable3,
|
|
178
|
+
text as text3,
|
|
179
|
+
timestamp as timestamp3,
|
|
180
|
+
uniqueIndex as uniqueIndex3,
|
|
181
|
+
uuid as uuid3,
|
|
182
|
+
varchar as varchar2
|
|
183
|
+
} from "drizzle-orm/pg-core";
|
|
184
|
+
var interactionTypeEnum, maps, mapElements, mapObjects, mapElementsRelations, mapsRelations, mapObjectsRelations;
|
|
185
|
+
var init_table3 = __esm(() => {
|
|
186
|
+
init_table();
|
|
187
|
+
init_table2();
|
|
188
|
+
init_table4();
|
|
189
|
+
interactionTypeEnum = pgEnum3("interaction_type", [
|
|
190
|
+
"game_entry",
|
|
191
|
+
"game_registry",
|
|
192
|
+
"info",
|
|
193
|
+
"teleport",
|
|
194
|
+
"door_in",
|
|
195
|
+
"door_out",
|
|
196
|
+
"npc_interaction",
|
|
197
|
+
"quest_trigger"
|
|
198
|
+
]);
|
|
199
|
+
maps = pgTable3("maps", {
|
|
200
|
+
id: uuid3("id").primaryKey().defaultRandom(),
|
|
201
|
+
identifier: varchar2("identifier", { length: 255 }).notNull().unique(),
|
|
202
|
+
displayName: varchar2("display_name", { length: 255 }).notNull(),
|
|
203
|
+
filePath: varchar2("file_path", { length: 255 }).notNull(),
|
|
204
|
+
tilesetBasePath: varchar2("tileset_base_path", { length: 255 }).notNull().default("/tilesets"),
|
|
205
|
+
defaultSpawnTileX: doublePrecision("default_spawn_tile_x").notNull().default(0),
|
|
206
|
+
defaultSpawnTileY: doublePrecision("default_spawn_tile_y").notNull().default(0),
|
|
207
|
+
description: text3("description")
|
|
208
|
+
});
|
|
209
|
+
mapElements = pgTable3("map_elements", {
|
|
210
|
+
id: uuid3("id").primaryKey().defaultRandom(),
|
|
211
|
+
mapId: uuid3("map_id").references(() => maps.id, {
|
|
212
|
+
onDelete: "cascade"
|
|
213
|
+
}),
|
|
214
|
+
elementSlug: varchar2("element_slug", { length: 255 }).notNull(),
|
|
215
|
+
interactionType: interactionTypeEnum("interaction_type").notNull(),
|
|
216
|
+
gameId: uuid3("game_id").references(() => games.id, {
|
|
217
|
+
onDelete: "set null"
|
|
218
|
+
}),
|
|
219
|
+
metadata: jsonb3("metadata").$type().default({})
|
|
220
|
+
}, (table) => [uniqueIndex3("map_id_element_slug_unique_idx").on(table.mapId, table.elementSlug)]);
|
|
221
|
+
mapObjects = pgTable3("map_objects", {
|
|
222
|
+
id: uuid3("id").primaryKey().defaultRandom(),
|
|
223
|
+
userId: text3("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
224
|
+
mapId: uuid3("map_id").notNull().references(() => maps.id, { onDelete: "cascade" }),
|
|
225
|
+
itemId: uuid3("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
|
|
226
|
+
worldX: doublePrecision("world_x").notNull(),
|
|
227
|
+
worldY: doublePrecision("world_y").notNull(),
|
|
228
|
+
rotation: integer2("rotation").default(0).notNull(),
|
|
229
|
+
scale: doublePrecision("scale").default(1).notNull(),
|
|
230
|
+
createdAt: timestamp3("created_at").defaultNow().notNull()
|
|
231
|
+
}, (table) => [
|
|
232
|
+
index("map_objects_map_idx").on(table.mapId),
|
|
233
|
+
index("map_objects_spatial_idx").on(table.mapId, table.worldX, table.worldY)
|
|
234
|
+
]);
|
|
235
|
+
mapElementsRelations = relations2(mapElements, ({ one }) => ({
|
|
236
|
+
game: one(games, {
|
|
237
|
+
fields: [mapElements.gameId],
|
|
238
|
+
references: [games.id]
|
|
239
|
+
}),
|
|
240
|
+
map: one(maps, {
|
|
241
|
+
fields: [mapElements.mapId],
|
|
242
|
+
references: [maps.id]
|
|
243
|
+
})
|
|
244
|
+
}));
|
|
245
|
+
mapsRelations = relations2(maps, ({ many }) => ({
|
|
246
|
+
elements: many(mapElements),
|
|
247
|
+
objects: many(mapObjects)
|
|
248
|
+
}));
|
|
249
|
+
mapObjectsRelations = relations2(mapObjects, ({ one }) => ({
|
|
250
|
+
user: one(users, {
|
|
251
|
+
fields: [mapObjects.userId],
|
|
252
|
+
references: [users.id]
|
|
253
|
+
}),
|
|
254
|
+
map: one(maps, {
|
|
255
|
+
fields: [mapObjects.mapId],
|
|
256
|
+
references: [maps.id]
|
|
257
|
+
}),
|
|
258
|
+
item: one(items, {
|
|
259
|
+
fields: [mapObjects.itemId],
|
|
260
|
+
references: [items.id]
|
|
261
|
+
})
|
|
262
|
+
}));
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// ../data/src/domains/user/table.ts
|
|
266
|
+
import { relations as relations3 } from "drizzle-orm";
|
|
267
|
+
import { boolean as boolean2, pgEnum as pgEnum4, pgTable as pgTable4, text as text4, timestamp as timestamp4, uniqueIndex as uniqueIndex4 } from "drizzle-orm/pg-core";
|
|
268
|
+
var userRoleEnum, developerStatusEnum, users, accounts, sessions, verification, ssoProvider, usersRelations;
|
|
269
|
+
var init_table4 = __esm(() => {
|
|
270
|
+
init_table3();
|
|
271
|
+
userRoleEnum = pgEnum4("user_role", ["admin", "player", "developer"]);
|
|
272
|
+
developerStatusEnum = pgEnum4("developer_status", ["none", "pending", "approved"]);
|
|
273
|
+
users = pgTable4("user", {
|
|
274
|
+
id: text4("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
275
|
+
name: text4("name").notNull(),
|
|
276
|
+
username: text4("username").unique(),
|
|
277
|
+
email: text4("email").notNull().unique(),
|
|
278
|
+
timebackId: text4("timeback_id").unique(),
|
|
279
|
+
emailVerified: boolean2("email_verified").notNull().default(false),
|
|
280
|
+
image: text4("image"),
|
|
281
|
+
role: userRoleEnum("role").notNull().default("player"),
|
|
282
|
+
developerStatus: developerStatusEnum("developer_status").notNull().default("none"),
|
|
283
|
+
characterCreated: boolean2("character_created").notNull().default(false),
|
|
284
|
+
createdAt: timestamp4("created_at", {
|
|
285
|
+
mode: "date",
|
|
286
|
+
withTimezone: true
|
|
287
|
+
}).notNull(),
|
|
288
|
+
updatedAt: timestamp4("updated_at", {
|
|
289
|
+
mode: "date",
|
|
290
|
+
withTimezone: true
|
|
291
|
+
}).notNull()
|
|
292
|
+
});
|
|
293
|
+
accounts = pgTable4("account", {
|
|
294
|
+
id: text4("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
295
|
+
userId: text4("userId").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
296
|
+
accountId: text4("account_id").notNull(),
|
|
297
|
+
providerId: text4("provider_id").notNull(),
|
|
298
|
+
accessToken: text4("access_token"),
|
|
299
|
+
refreshToken: text4("refresh_token"),
|
|
300
|
+
idToken: text4("id_token"),
|
|
301
|
+
accessTokenExpiresAt: timestamp4("access_token_expires_at", {
|
|
302
|
+
mode: "date",
|
|
303
|
+
withTimezone: true
|
|
304
|
+
}),
|
|
305
|
+
refreshTokenExpiresAt: timestamp4("refresh_token_expires_at", {
|
|
306
|
+
mode: "date",
|
|
307
|
+
withTimezone: true
|
|
308
|
+
}),
|
|
309
|
+
scope: text4("scope"),
|
|
310
|
+
password: text4("password"),
|
|
311
|
+
createdAt: timestamp4("created_at", {
|
|
312
|
+
mode: "date",
|
|
313
|
+
withTimezone: true
|
|
314
|
+
}).notNull(),
|
|
315
|
+
updatedAt: timestamp4("updated_at", {
|
|
316
|
+
mode: "date",
|
|
317
|
+
withTimezone: true
|
|
318
|
+
}).notNull()
|
|
319
|
+
}, (table) => [uniqueIndex4("account_provider_providerId_idx").on(table.accountId, table.providerId)]);
|
|
320
|
+
sessions = pgTable4("session", {
|
|
321
|
+
id: text4("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
322
|
+
userId: text4("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
323
|
+
expiresAt: timestamp4("expires_at", {
|
|
324
|
+
mode: "date",
|
|
325
|
+
withTimezone: true
|
|
326
|
+
}).notNull(),
|
|
327
|
+
token: text4("token").notNull().unique(),
|
|
328
|
+
ipAddress: text4("ip_address"),
|
|
329
|
+
userAgent: text4("user_agent"),
|
|
330
|
+
createdAt: timestamp4("created_at", {
|
|
331
|
+
mode: "date",
|
|
332
|
+
withTimezone: true
|
|
333
|
+
}).notNull(),
|
|
334
|
+
updatedAt: timestamp4("updated_at", {
|
|
335
|
+
mode: "date",
|
|
336
|
+
withTimezone: true
|
|
337
|
+
}).notNull()
|
|
338
|
+
});
|
|
339
|
+
verification = pgTable4("verification", {
|
|
340
|
+
id: text4("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
341
|
+
identifier: text4("identifier").notNull(),
|
|
342
|
+
value: text4("value").notNull(),
|
|
343
|
+
expiresAt: timestamp4("expires_at", {
|
|
344
|
+
mode: "date",
|
|
345
|
+
withTimezone: true
|
|
346
|
+
}).notNull(),
|
|
347
|
+
createdAt: timestamp4("created_at", {
|
|
348
|
+
mode: "date",
|
|
349
|
+
withTimezone: true
|
|
350
|
+
}).notNull(),
|
|
351
|
+
updatedAt: timestamp4("updated_at", {
|
|
352
|
+
mode: "date",
|
|
353
|
+
withTimezone: true
|
|
354
|
+
}).notNull()
|
|
355
|
+
});
|
|
356
|
+
ssoProvider = pgTable4("sso_provider", {
|
|
357
|
+
id: text4("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
358
|
+
issuer: text4("issuer").notNull(),
|
|
359
|
+
oidcConfig: text4("oidc_config"),
|
|
360
|
+
samlConfig: text4("saml_config"),
|
|
361
|
+
userId: text4("user_id").references(() => users.id, { onDelete: "cascade" }),
|
|
362
|
+
providerId: text4("provider_id").notNull().unique(),
|
|
363
|
+
organizationId: text4("organization_id"),
|
|
364
|
+
domain: text4("domain").notNull()
|
|
365
|
+
});
|
|
366
|
+
usersRelations = relations3(users, ({ many }) => ({
|
|
367
|
+
mapObjects: many(mapObjects)
|
|
368
|
+
}));
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// ../data/src/domains/developer/table.ts
|
|
372
|
+
import { pgTable as pgTable5, text as text5, timestamp as timestamp5, uuid as uuid4, varchar as varchar3 } from "drizzle-orm/pg-core";
|
|
373
|
+
var developerKeys;
|
|
374
|
+
var init_table5 = __esm(() => {
|
|
375
|
+
init_table4();
|
|
376
|
+
developerKeys = pgTable5("developer_keys", {
|
|
377
|
+
id: uuid4("id").primaryKey().defaultRandom(),
|
|
378
|
+
userId: text5("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
379
|
+
label: varchar3("label", { length: 255 }),
|
|
380
|
+
keyHash: text5("key_hash").notNull().unique(),
|
|
381
|
+
createdAt: timestamp5("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// ../data/src/domains/level/table.ts
|
|
386
|
+
import { relations as relations4 } from "drizzle-orm";
|
|
387
|
+
import {
|
|
388
|
+
doublePrecision as doublePrecision2,
|
|
389
|
+
integer as integer3,
|
|
390
|
+
pgTable as pgTable6,
|
|
391
|
+
text as text6,
|
|
392
|
+
timestamp as timestamp6,
|
|
393
|
+
uniqueIndex as uniqueIndex5,
|
|
394
|
+
uuid as uuid5
|
|
395
|
+
} from "drizzle-orm/pg-core";
|
|
396
|
+
var userLevels, levelConfigs, userLevelsRelations;
|
|
397
|
+
var init_table6 = __esm(() => {
|
|
398
|
+
init_table4();
|
|
399
|
+
userLevels = pgTable6("user_levels", {
|
|
400
|
+
userId: text6("user_id").primaryKey().references(() => users.id, { onDelete: "cascade" }),
|
|
401
|
+
currentLevel: integer3("current_level").notNull().default(1),
|
|
402
|
+
currentXp: doublePrecision2("current_xp").notNull().default(0),
|
|
403
|
+
totalXP: doublePrecision2("total_xp").notNull().default(0),
|
|
404
|
+
lastLevelUpAt: timestamp6("last_level_up_at", { withTimezone: true }),
|
|
405
|
+
createdAt: timestamp6("created_at").defaultNow().notNull(),
|
|
406
|
+
updatedAt: timestamp6("updated_at", { withTimezone: true }).defaultNow().$onUpdate(() => new Date)
|
|
407
|
+
});
|
|
408
|
+
levelConfigs = pgTable6("level_configs", {
|
|
409
|
+
id: uuid5("id").primaryKey().defaultRandom(),
|
|
410
|
+
level: integer3("level").notNull().unique(),
|
|
411
|
+
xpRequired: integer3("xp_required").notNull(),
|
|
412
|
+
creditsReward: integer3("credits_reward").notNull().default(0),
|
|
413
|
+
createdAt: timestamp6("created_at").defaultNow().notNull()
|
|
414
|
+
}, (table) => [uniqueIndex5("unique_level_config_idx").on(table.level)]);
|
|
415
|
+
userLevelsRelations = relations4(userLevels, ({ one }) => ({
|
|
416
|
+
user: one(users, {
|
|
417
|
+
fields: [userLevels.userId],
|
|
418
|
+
references: [users.id]
|
|
419
|
+
})
|
|
420
|
+
}));
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// ../data/src/domains/leaderboard/table.ts
|
|
424
|
+
import { relations as relations5 } from "drizzle-orm";
|
|
425
|
+
import { index as index2, integer as integer4, jsonb as jsonb4, pgTable as pgTable7, text as text7, timestamp as timestamp7, uuid as uuid6 } from "drizzle-orm/pg-core";
|
|
426
|
+
var gameScores, gameScoresRelations;
|
|
427
|
+
var init_table7 = __esm(() => {
|
|
428
|
+
init_table();
|
|
429
|
+
init_table4();
|
|
430
|
+
gameScores = pgTable7("game_scores", {
|
|
431
|
+
id: uuid6("id").primaryKey().defaultRandom(),
|
|
432
|
+
userId: text7("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
433
|
+
gameId: uuid6("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
|
|
434
|
+
score: integer4("score").notNull(),
|
|
435
|
+
metadata: jsonb4("metadata").default("{}"),
|
|
436
|
+
achievedAt: timestamp7("achieved_at", { withTimezone: true }).defaultNow().notNull(),
|
|
437
|
+
sessionId: uuid6("session_id").references(() => gameSessions.id, { onDelete: "set null" })
|
|
438
|
+
}, (table) => [
|
|
439
|
+
index2("game_scores_user_game_idx").on(table.userId, table.gameId),
|
|
440
|
+
index2("game_scores_game_score_idx").on(table.gameId, table.score),
|
|
441
|
+
index2("game_scores_achieved_at_idx").on(table.achievedAt)
|
|
442
|
+
]);
|
|
443
|
+
gameScoresRelations = relations5(gameScores, ({ one }) => ({
|
|
444
|
+
user: one(users, {
|
|
445
|
+
fields: [gameScores.userId],
|
|
446
|
+
references: [users.id]
|
|
447
|
+
}),
|
|
448
|
+
game: one(games, {
|
|
449
|
+
fields: [gameScores.gameId],
|
|
450
|
+
references: [games.id]
|
|
451
|
+
}),
|
|
452
|
+
session: one(gameSessions, {
|
|
453
|
+
fields: [gameScores.sessionId],
|
|
454
|
+
references: [gameSessions.id]
|
|
455
|
+
})
|
|
456
|
+
}));
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// ../data/src/domains/sprite/table.ts
|
|
460
|
+
import { relations as relations6 } from "drizzle-orm";
|
|
461
|
+
import { integer as integer5, pgTable as pgTable8, timestamp as timestamp8, uuid as uuid7, varchar as varchar4 } from "drizzle-orm/pg-core";
|
|
462
|
+
var spriteTemplates, spriteSheets, spriteTemplatesRelations, spriteSheetsRelations;
|
|
463
|
+
var init_table8 = __esm(() => {
|
|
464
|
+
spriteTemplates = pgTable8("sprite_templates", {
|
|
465
|
+
id: uuid7("id").primaryKey().defaultRandom(),
|
|
466
|
+
slug: varchar4("slug", { length: 64 }).notNull().unique(),
|
|
467
|
+
url: varchar4("url", { length: 255 }).notNull(),
|
|
468
|
+
createdAt: timestamp8("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
469
|
+
updatedAt: timestamp8("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
470
|
+
});
|
|
471
|
+
spriteSheets = pgTable8("sprite_sheets", {
|
|
472
|
+
id: uuid7("id").primaryKey().defaultRandom(),
|
|
473
|
+
templateId: uuid7("template_id").notNull().references(() => spriteTemplates.id, { onDelete: "cascade" }),
|
|
474
|
+
width: integer5("width").notNull(),
|
|
475
|
+
height: integer5("height").notNull(),
|
|
476
|
+
url: varchar4("url", { length: 255 }).notNull(),
|
|
477
|
+
createdAt: timestamp8("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
478
|
+
updatedAt: timestamp8("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
479
|
+
});
|
|
480
|
+
spriteTemplatesRelations = relations6(spriteTemplates, ({ many }) => ({
|
|
481
|
+
sheets: many(spriteSheets)
|
|
482
|
+
}));
|
|
483
|
+
spriteSheetsRelations = relations6(spriteSheets, ({ one }) => ({
|
|
484
|
+
template: one(spriteTemplates, {
|
|
485
|
+
fields: [spriteSheets.templateId],
|
|
486
|
+
references: [spriteTemplates.id]
|
|
487
|
+
})
|
|
488
|
+
}));
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// ../data/src/domains/character/table.ts
|
|
492
|
+
import { relations as relations7 } from "drizzle-orm";
|
|
493
|
+
import {
|
|
494
|
+
integer as integer6,
|
|
495
|
+
pgEnum as pgEnum5,
|
|
496
|
+
pgTable as pgTable9,
|
|
497
|
+
text as text8,
|
|
498
|
+
timestamp as timestamp9,
|
|
499
|
+
uniqueIndex as uniqueIndex6,
|
|
500
|
+
uuid as uuid8,
|
|
501
|
+
varchar as varchar5
|
|
502
|
+
} from "drizzle-orm/pg-core";
|
|
503
|
+
var characterComponentTypeEnum, characterComponents, playerCharacters, playerCharacterAccessories, characterComponentsRelations, playerCharactersRelations, playerCharacterAccessoriesRelations;
|
|
504
|
+
var init_table9 = __esm(() => {
|
|
505
|
+
init_table8();
|
|
506
|
+
init_table4();
|
|
507
|
+
characterComponentTypeEnum = pgEnum5("character_component_type", [
|
|
508
|
+
"body",
|
|
509
|
+
"outfit",
|
|
510
|
+
"hairstyle",
|
|
511
|
+
"eyes",
|
|
512
|
+
"accessory"
|
|
513
|
+
]);
|
|
514
|
+
characterComponents = pgTable9("character_components", {
|
|
515
|
+
id: uuid8("id").primaryKey().defaultRandom(),
|
|
516
|
+
componentType: characterComponentTypeEnum("component_type").notNull(),
|
|
517
|
+
slug: varchar5("slug", { length: 128 }).notNull().unique(),
|
|
518
|
+
displayName: varchar5("display_name", { length: 128 }).notNull(),
|
|
519
|
+
slot: varchar5("slot", { length: 64 }).notNull(),
|
|
520
|
+
spriteSheetId: uuid8("sprite_sheet_id").notNull().references(() => spriteSheets.id, { onDelete: "cascade" }),
|
|
521
|
+
unlockLevel: integer6("unlock_level").notNull().default(0),
|
|
522
|
+
variant: integer6("variant").notNull().default(0),
|
|
523
|
+
createdAt: timestamp9("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
524
|
+
updatedAt: timestamp9("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
525
|
+
});
|
|
526
|
+
playerCharacters = pgTable9("player_characters", {
|
|
527
|
+
id: uuid8("id").primaryKey().defaultRandom(),
|
|
528
|
+
userId: text8("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
529
|
+
bodyComponentId: uuid8("body_component_id").notNull().references(() => characterComponents.id, { onDelete: "restrict" }),
|
|
530
|
+
eyesComponentId: uuid8("eyes_component_id").notNull().references(() => characterComponents.id, { onDelete: "restrict" }),
|
|
531
|
+
hairstyleComponentId: uuid8("hairstyle_component_id").notNull().references(() => characterComponents.id, { onDelete: "restrict" }),
|
|
532
|
+
outfitComponentId: uuid8("outfit_component_id").notNull().references(() => characterComponents.id, { onDelete: "restrict" }),
|
|
533
|
+
createdAt: timestamp9("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
534
|
+
updatedAt: timestamp9("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
535
|
+
});
|
|
536
|
+
playerCharacterAccessories = pgTable9("player_character_accessories", {
|
|
537
|
+
id: uuid8("id").primaryKey().defaultRandom(),
|
|
538
|
+
playerCharacterId: uuid8("player_character_id").notNull().references(() => playerCharacters.id, { onDelete: "cascade" }),
|
|
539
|
+
accessoryComponentId: uuid8("accessory_component_id").notNull().references(() => characterComponents.id, { onDelete: "cascade" }),
|
|
540
|
+
slot: varchar5("slot", { length: 64 }).notNull(),
|
|
541
|
+
equippedAt: timestamp9("equipped_at", { withTimezone: true }).notNull().defaultNow(),
|
|
542
|
+
updatedAt: timestamp9("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
543
|
+
}, (table) => [
|
|
544
|
+
uniqueIndex6("unique_player_character_slot_idx").on(table.playerCharacterId, table.slot),
|
|
545
|
+
uniqueIndex6("player_character_accessory_idx").on(table.playerCharacterId, table.accessoryComponentId)
|
|
546
|
+
]);
|
|
547
|
+
characterComponentsRelations = relations7(characterComponents, ({ one }) => ({
|
|
548
|
+
sheet: one(spriteSheets, {
|
|
549
|
+
fields: [characterComponents.spriteSheetId],
|
|
550
|
+
references: [spriteSheets.id]
|
|
551
|
+
})
|
|
552
|
+
}));
|
|
553
|
+
playerCharactersRelations = relations7(playerCharacters, ({ one, many }) => ({
|
|
554
|
+
user: one(users, {
|
|
555
|
+
fields: [playerCharacters.userId],
|
|
556
|
+
references: [users.id]
|
|
557
|
+
}),
|
|
558
|
+
body: one(characterComponents, {
|
|
559
|
+
fields: [playerCharacters.bodyComponentId],
|
|
560
|
+
references: [characterComponents.id]
|
|
561
|
+
}),
|
|
562
|
+
eyes: one(characterComponents, {
|
|
563
|
+
fields: [playerCharacters.eyesComponentId],
|
|
564
|
+
references: [characterComponents.id]
|
|
565
|
+
}),
|
|
566
|
+
hair: one(characterComponents, {
|
|
567
|
+
fields: [playerCharacters.hairstyleComponentId],
|
|
568
|
+
references: [characterComponents.id]
|
|
569
|
+
}),
|
|
570
|
+
outfit: one(characterComponents, {
|
|
571
|
+
fields: [playerCharacters.outfitComponentId],
|
|
572
|
+
references: [characterComponents.id]
|
|
573
|
+
}),
|
|
574
|
+
accessories: many(playerCharacterAccessories)
|
|
575
|
+
}));
|
|
576
|
+
playerCharacterAccessoriesRelations = relations7(playerCharacterAccessories, ({ one }) => ({
|
|
577
|
+
playerCharacter: one(playerCharacters, {
|
|
578
|
+
fields: [playerCharacterAccessories.playerCharacterId],
|
|
579
|
+
references: [playerCharacters.id]
|
|
580
|
+
}),
|
|
581
|
+
accessoryComponent: one(characterComponents, {
|
|
582
|
+
fields: [playerCharacterAccessories.accessoryComponentId],
|
|
583
|
+
references: [characterComponents.id]
|
|
584
|
+
})
|
|
585
|
+
}));
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// ../data/src/domains/timeback/table.ts
|
|
589
|
+
import { doublePrecision as doublePrecision3, pgTable as pgTable10, text as text9, timestamp as timestamp10, uniqueIndex as uniqueIndex7, uuid as uuid9 } from "drizzle-orm/pg-core";
|
|
590
|
+
var timebackDailyXp, timebackXpEvents;
|
|
591
|
+
var init_table10 = __esm(() => {
|
|
592
|
+
init_table4();
|
|
593
|
+
timebackDailyXp = pgTable10("timeback_daily_xp", {
|
|
594
|
+
userId: text9("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
595
|
+
date: timestamp10("date", { mode: "date", withTimezone: true }).notNull(),
|
|
596
|
+
xp: doublePrecision3("xp").notNull().default(0),
|
|
597
|
+
createdAt: timestamp10("created_at", { mode: "date", withTimezone: true }).notNull().defaultNow(),
|
|
598
|
+
updatedAt: timestamp10("updated_at", { mode: "date", withTimezone: true }).notNull().defaultNow()
|
|
599
|
+
}, (table) => [uniqueIndex7("timeback_daily_xp_user_date_idx").on(table.userId, table.date)]);
|
|
600
|
+
timebackXpEvents = pgTable10("timeback_xp_event", {
|
|
601
|
+
id: uuid9("id").primaryKey().defaultRandom(),
|
|
602
|
+
userId: text9("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
603
|
+
occurredAt: timestamp10("occurred_at", { withTimezone: true }).notNull(),
|
|
604
|
+
xpDelta: doublePrecision3("xp_delta").notNull(),
|
|
605
|
+
source: text9("source").notNull(),
|
|
606
|
+
sourceId: text9("source_id"),
|
|
607
|
+
sensor: text9("sensor"),
|
|
608
|
+
appName: text9("app_name"),
|
|
609
|
+
createdAt: timestamp10("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
610
|
+
updatedAt: timestamp10("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
611
|
+
}, (table) => [uniqueIndex7("timeback_xp_events_source_id_idx").on(table.source, table.sourceId)]);
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// ../data/src/domains/achievement/table.ts
|
|
615
|
+
import { relations as relations8 } from "drizzle-orm";
|
|
616
|
+
import {
|
|
617
|
+
boolean as boolean3,
|
|
618
|
+
index as index3,
|
|
619
|
+
integer as integer7,
|
|
620
|
+
jsonb as jsonb5,
|
|
621
|
+
pgEnum as pgEnum6,
|
|
622
|
+
pgTable as pgTable11,
|
|
623
|
+
text as text10,
|
|
624
|
+
timestamp as timestamp11,
|
|
625
|
+
uniqueIndex as uniqueIndex8,
|
|
626
|
+
uuid as uuid10,
|
|
627
|
+
varchar as varchar6
|
|
628
|
+
} from "drizzle-orm/pg-core";
|
|
629
|
+
var achievementIntervalEnum, achievements, userAchievementProgress, userAchievementClaims, userAchievementProgressRelations, userAchievementClaimsRelations;
|
|
630
|
+
var init_table11 = __esm(() => {
|
|
631
|
+
init_table4();
|
|
632
|
+
achievementIntervalEnum = pgEnum6("achievement_interval", ["daily", "weekly"]);
|
|
633
|
+
achievements = pgTable11("achievements", {
|
|
634
|
+
id: varchar6("id", { length: 255 }).primaryKey(),
|
|
635
|
+
title: varchar6("title", { length: 255 }).notNull(),
|
|
636
|
+
description: text10("description"),
|
|
637
|
+
intervalType: achievementIntervalEnum("interval_type").notNull(),
|
|
638
|
+
rewardCredits: integer7("reward_credits").notNull().default(0),
|
|
639
|
+
limitPerInterval: integer7("limit_per_interval").notNull().default(1),
|
|
640
|
+
completionType: varchar6("completion_type", { length: 50 }).notNull(),
|
|
641
|
+
completionConfig: jsonb5("completion_config").notNull().default({}),
|
|
642
|
+
scope: jsonb5("scope").notNull().default({}),
|
|
643
|
+
active: boolean3("active").notNull().default(true),
|
|
644
|
+
createdAt: timestamp11("created_at", { withTimezone: true }).defaultNow(),
|
|
645
|
+
updatedAt: timestamp11("updated_at", { withTimezone: true }).defaultNow()
|
|
646
|
+
});
|
|
647
|
+
userAchievementProgress = pgTable11("user_achievement_progress", {
|
|
648
|
+
id: uuid10("id").primaryKey().defaultRandom(),
|
|
649
|
+
userId: text10("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
650
|
+
achievementId: varchar6("achievement_id", { length: 255 }).notNull().references(() => achievements.id, { onDelete: "cascade" }),
|
|
651
|
+
intervalKey: text10("interval_key").notNull(),
|
|
652
|
+
progress: jsonb5("progress").notNull().default({}),
|
|
653
|
+
updatedAt: timestamp11("updated_at", { withTimezone: true }).defaultNow().notNull()
|
|
654
|
+
}, (table) => [
|
|
655
|
+
index3("user_achievement_progress_idx").on(table.userId, table.achievementId, table.intervalKey)
|
|
656
|
+
]);
|
|
657
|
+
userAchievementClaims = pgTable11("user_achievement_claims", {
|
|
658
|
+
id: uuid10("id").primaryKey().defaultRandom(),
|
|
659
|
+
userId: text10("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
660
|
+
achievementId: varchar6("achievement_id", { length: 255 }).notNull().references(() => achievements.id, { onDelete: "cascade" }),
|
|
661
|
+
intervalKey: text10("interval_key").notNull(),
|
|
662
|
+
rewardCredits: integer7("reward_credits").notNull(),
|
|
663
|
+
createdAt: timestamp11("created_at", { withTimezone: true }).defaultNow().notNull()
|
|
664
|
+
}, (table) => [
|
|
665
|
+
uniqueIndex8("user_achievement_claims_unique").on(table.userId, table.achievementId, table.intervalKey)
|
|
666
|
+
]);
|
|
667
|
+
userAchievementProgressRelations = relations8(userAchievementProgress, ({ one }) => ({
|
|
668
|
+
user: one(users, {
|
|
669
|
+
fields: [userAchievementProgress.userId],
|
|
670
|
+
references: [users.id]
|
|
671
|
+
}),
|
|
672
|
+
achievement: one(achievements, {
|
|
673
|
+
fields: [userAchievementProgress.achievementId],
|
|
674
|
+
references: [achievements.id]
|
|
675
|
+
})
|
|
676
|
+
}));
|
|
677
|
+
userAchievementClaimsRelations = relations8(userAchievementClaims, ({ one }) => ({
|
|
678
|
+
user: one(users, {
|
|
679
|
+
fields: [userAchievementClaims.userId],
|
|
680
|
+
references: [users.id]
|
|
681
|
+
}),
|
|
682
|
+
achievement: one(achievements, {
|
|
683
|
+
fields: [userAchievementClaims.achievementId],
|
|
684
|
+
references: [achievements.id]
|
|
685
|
+
})
|
|
686
|
+
}));
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// ../data/src/tables.index.ts
|
|
690
|
+
var init_tables_index = __esm(() => {
|
|
691
|
+
init_table4();
|
|
692
|
+
init_table5();
|
|
693
|
+
init_table();
|
|
694
|
+
init_table2();
|
|
695
|
+
init_table3();
|
|
696
|
+
init_table6();
|
|
697
|
+
init_table7();
|
|
698
|
+
init_table8();
|
|
699
|
+
init_table9();
|
|
700
|
+
init_table10();
|
|
701
|
+
init_table11();
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
// ../data/src/constants.ts
|
|
705
|
+
var ITEM_SLUGS, CURRENCIES, BADGES, ACHIEVEMENT_COMPLETION_TYPES, ACHIEVEMENT_COMPLETION_TYPE, INTERACTION_TYPE;
|
|
706
|
+
var init_constants = __esm(() => {
|
|
707
|
+
init_tables_index();
|
|
708
|
+
ITEM_SLUGS = {
|
|
709
|
+
PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
|
|
710
|
+
PLAYCADEMY_XP: "PLAYCADEMY_XP",
|
|
711
|
+
FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
|
|
712
|
+
EARLY_ADOPTER_BADGE: "EARLY_ADOPTER_BADGE",
|
|
713
|
+
FIRST_GAME_BADGE: "FIRST_GAME_BADGE",
|
|
714
|
+
COMMON_SWORD: "COMMON_SWORD",
|
|
715
|
+
SMALL_HEALTH_POTION: "SMALL_HEALTH_POTION",
|
|
716
|
+
SMALL_BACKPACK: "SMALL_BACKPACK",
|
|
717
|
+
LAVA_LAMP: "LAVA_LAMP",
|
|
718
|
+
BOOMBOX: "BOOMBOX",
|
|
719
|
+
CABIN_BED: "CABIN_BED"
|
|
720
|
+
};
|
|
721
|
+
CURRENCIES = {
|
|
722
|
+
PRIMARY: ITEM_SLUGS.PLAYCADEMY_CREDITS,
|
|
723
|
+
XP: ITEM_SLUGS.PLAYCADEMY_XP
|
|
724
|
+
};
|
|
725
|
+
BADGES = {
|
|
726
|
+
FOUNDING_MEMBER: ITEM_SLUGS.FOUNDING_MEMBER_BADGE,
|
|
727
|
+
EARLY_ADOPTER: ITEM_SLUGS.EARLY_ADOPTER_BADGE,
|
|
728
|
+
FIRST_GAME: ITEM_SLUGS.FIRST_GAME_BADGE
|
|
729
|
+
};
|
|
730
|
+
ACHIEVEMENT_COMPLETION_TYPES = [
|
|
731
|
+
"time_played_session",
|
|
732
|
+
"interaction",
|
|
733
|
+
"leaderboard_rank"
|
|
734
|
+
];
|
|
735
|
+
ACHIEVEMENT_COMPLETION_TYPE = Object.fromEntries(ACHIEVEMENT_COMPLETION_TYPES.map((value) => [value, value]));
|
|
736
|
+
INTERACTION_TYPE = Object.fromEntries(interactionTypeEnum.enumValues.map((value) => [value, value]));
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
// ../logger/src/index.ts
|
|
740
|
+
var isBrowser = () => {
|
|
741
|
+
const g = globalThis;
|
|
742
|
+
return typeof g.window !== "undefined" && typeof g.document !== "undefined";
|
|
743
|
+
}, colors, getLevelColor = (level) => {
|
|
744
|
+
switch (level) {
|
|
745
|
+
case "debug":
|
|
746
|
+
return colors.blue;
|
|
747
|
+
case "info":
|
|
748
|
+
return colors.cyan;
|
|
749
|
+
case "warn":
|
|
750
|
+
return colors.yellow;
|
|
751
|
+
case "error":
|
|
752
|
+
return colors.red;
|
|
753
|
+
default:
|
|
754
|
+
return colors.reset;
|
|
755
|
+
}
|
|
756
|
+
}, logInBrowser = (level, message, context) => {
|
|
757
|
+
const timestamp12 = new Date().toISOString();
|
|
758
|
+
const levelUpper = level.toUpperCase();
|
|
759
|
+
const consoleMethod = getConsoleMethod(level);
|
|
760
|
+
if (context && Object.keys(context).length > 0) {
|
|
761
|
+
consoleMethod(`[${timestamp12}] ${levelUpper}`, message, context);
|
|
762
|
+
} else {
|
|
763
|
+
consoleMethod(`[${timestamp12}] ${levelUpper}`, message);
|
|
764
|
+
}
|
|
765
|
+
}, logOnServer = (level, message, context) => {
|
|
766
|
+
const consoleMethod = getConsoleMethod(level);
|
|
767
|
+
if (true) {
|
|
768
|
+
const timestamp12 = new Date().toISOString();
|
|
769
|
+
const levelColor = getLevelColor(level);
|
|
770
|
+
const levelUpper = level.toUpperCase().padEnd(5);
|
|
771
|
+
const coloredPrefix = `${colors.dim}[${timestamp12}]${colors.reset} ${levelColor}${levelUpper}${colors.reset}`;
|
|
772
|
+
if (context && Object.keys(context).length > 0) {
|
|
773
|
+
consoleMethod(`${coloredPrefix} ${message}`, context);
|
|
774
|
+
} else {
|
|
775
|
+
consoleMethod(`${coloredPrefix} ${message}`);
|
|
776
|
+
}
|
|
777
|
+
} else {}
|
|
778
|
+
}, getConsoleMethod = (level) => {
|
|
779
|
+
switch (level) {
|
|
780
|
+
case "debug":
|
|
781
|
+
return console.debug;
|
|
782
|
+
case "info":
|
|
783
|
+
return console.info;
|
|
784
|
+
case "warn":
|
|
785
|
+
return console.warn;
|
|
786
|
+
case "error":
|
|
787
|
+
return console.error;
|
|
788
|
+
default:
|
|
789
|
+
return console.log;
|
|
790
|
+
}
|
|
791
|
+
}, performLog = (level, message, context) => {
|
|
792
|
+
if (level === "debug" && false) {}
|
|
793
|
+
if (isBrowser()) {
|
|
794
|
+
logInBrowser(level, message, context);
|
|
795
|
+
} else {
|
|
796
|
+
logOnServer(level, message, context);
|
|
797
|
+
}
|
|
798
|
+
}, createLogger = () => {
|
|
799
|
+
return {
|
|
800
|
+
debug: (message, context) => performLog("debug", message, context),
|
|
801
|
+
info: (message, context) => performLog("info", message, context),
|
|
802
|
+
warn: (message, context) => performLog("warn", message, context),
|
|
803
|
+
error: (message, context) => performLog("error", message, context),
|
|
804
|
+
log: performLog
|
|
805
|
+
};
|
|
806
|
+
}, log;
|
|
807
|
+
var init_src = __esm(() => {
|
|
808
|
+
colors = {
|
|
809
|
+
reset: "\x1B[0m",
|
|
810
|
+
dim: "\x1B[2m",
|
|
811
|
+
red: "\x1B[31m",
|
|
812
|
+
yellow: "\x1B[33m",
|
|
813
|
+
blue: "\x1B[34m",
|
|
814
|
+
cyan: "\x1B[36m",
|
|
815
|
+
gray: "\x1B[90m"
|
|
816
|
+
};
|
|
817
|
+
log = createLogger();
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
// src/core/auth/utils.ts
|
|
821
|
+
function openPopupWindow(url, name = "auth-popup", width = 500, height = 600) {
|
|
822
|
+
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
823
|
+
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
824
|
+
const features = [
|
|
825
|
+
`width=${width}`,
|
|
826
|
+
`height=${height}`,
|
|
827
|
+
`left=${left}`,
|
|
828
|
+
`top=${top}`,
|
|
829
|
+
"toolbar=no",
|
|
830
|
+
"menubar=no",
|
|
831
|
+
"location=yes",
|
|
832
|
+
"status=yes",
|
|
833
|
+
"scrollbars=yes",
|
|
834
|
+
"resizable=yes"
|
|
835
|
+
].join(",");
|
|
836
|
+
return window.open(url, name, features);
|
|
837
|
+
}
|
|
838
|
+
function isInIframe() {
|
|
839
|
+
if (typeof window === "undefined") {
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
try {
|
|
843
|
+
return window.self !== window.top;
|
|
844
|
+
} catch {
|
|
845
|
+
return true;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/core/errors.ts
|
|
850
|
+
var PlaycademyError, ApiError;
|
|
851
|
+
var init_errors = __esm(() => {
|
|
852
|
+
PlaycademyError = class PlaycademyError extends Error {
|
|
853
|
+
constructor(message) {
|
|
854
|
+
super(message);
|
|
855
|
+
this.name = "PlaycademyError";
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
ApiError = class ApiError extends Error {
|
|
859
|
+
status;
|
|
860
|
+
details;
|
|
861
|
+
constructor(status, message, details) {
|
|
862
|
+
super(`${status} ${message}`);
|
|
863
|
+
this.status = status;
|
|
864
|
+
this.details = details;
|
|
865
|
+
Object.setPrototypeOf(this, ApiError.prototype);
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
// src/core/namespaces/auth.ts
|
|
871
|
+
function createAuthNamespace(client) {
|
|
872
|
+
return {
|
|
873
|
+
login: async (credentials) => {
|
|
874
|
+
try {
|
|
875
|
+
const response = await client["request"]("/auth/login", "POST", credentials);
|
|
876
|
+
client.setToken(response.token);
|
|
877
|
+
return { success: true, token: response.token };
|
|
878
|
+
} catch (error) {
|
|
879
|
+
return {
|
|
880
|
+
success: false,
|
|
881
|
+
error: error instanceof Error ? error.message : "Authentication failed"
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
},
|
|
885
|
+
logout: async () => {
|
|
886
|
+
await client["request"]("/auth/logout", "POST");
|
|
887
|
+
client.setToken(null);
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// ../utils/src/random.ts
|
|
893
|
+
async function generateSecureRandomString(length) {
|
|
894
|
+
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
895
|
+
const randomValues = new Uint8Array(length);
|
|
896
|
+
globalThis.crypto.getRandomValues(randomValues);
|
|
897
|
+
return Array.from(randomValues).map((byte) => charset[byte % charset.length]).join("");
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// src/core/auth/oauth.ts
|
|
901
|
+
function getTimebackConfig() {
|
|
902
|
+
return {
|
|
903
|
+
authorizationEndpoint: "https://alpha-auth-production-idp.auth.us-west-2.amazoncognito.com/oauth2/authorize",
|
|
904
|
+
tokenEndpoint: "https://alpha-auth-production-idp.auth.us-west-2.amazoncognito.com/oauth2/token",
|
|
905
|
+
scope: "openid email phone"
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
async function generateOAuthState(data) {
|
|
909
|
+
const csrfToken = await generateSecureRandomString(32);
|
|
910
|
+
if (data && Object.keys(data).length > 0) {
|
|
911
|
+
const jsonStr = JSON.stringify(data);
|
|
912
|
+
const base64 = btoa(jsonStr).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
913
|
+
return `${csrfToken}.${base64}`;
|
|
914
|
+
}
|
|
915
|
+
return csrfToken;
|
|
916
|
+
}
|
|
917
|
+
function parseOAuthState(state) {
|
|
918
|
+
const lastDotIndex = state.lastIndexOf(".");
|
|
919
|
+
if (lastDotIndex > 0 && lastDotIndex < state.length - 1) {
|
|
920
|
+
try {
|
|
921
|
+
const csrfToken = state.substring(0, lastDotIndex);
|
|
922
|
+
const base64 = state.substring(lastDotIndex + 1);
|
|
923
|
+
const base64WithPadding = base64.replace(/-/g, "+").replace(/_/g, "/");
|
|
924
|
+
const paddedBase64 = base64WithPadding + "=".repeat((4 - base64WithPadding.length % 4) % 4);
|
|
925
|
+
const jsonStr = atob(paddedBase64);
|
|
926
|
+
const data = JSON.parse(jsonStr);
|
|
927
|
+
return { csrfToken, data };
|
|
928
|
+
} catch {}
|
|
929
|
+
}
|
|
930
|
+
return { csrfToken: state };
|
|
931
|
+
}
|
|
932
|
+
function getOAuthConfig(provider) {
|
|
933
|
+
const configGetter = OAUTH_CONFIGS[provider];
|
|
934
|
+
if (!configGetter)
|
|
935
|
+
throw new Error(`Unsupported auth provider: ${provider}`);
|
|
936
|
+
return configGetter();
|
|
937
|
+
}
|
|
938
|
+
var OAUTH_CONFIGS;
|
|
939
|
+
var init_oauth = __esm(() => {
|
|
940
|
+
OAUTH_CONFIGS = {
|
|
941
|
+
get TIMEBACK() {
|
|
942
|
+
return getTimebackConfig;
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
// src/core/auth/flows/popup.ts
|
|
948
|
+
async function initiatePopupFlow(options) {
|
|
949
|
+
const { provider, callbackUrl, onStateChange, oauth } = options;
|
|
950
|
+
try {
|
|
951
|
+
onStateChange?.({
|
|
952
|
+
status: "opening_popup",
|
|
953
|
+
message: "Opening authentication window..."
|
|
954
|
+
});
|
|
955
|
+
const defaults = getOAuthConfig(provider);
|
|
956
|
+
const config = oauth ? { ...defaults, ...oauth } : defaults;
|
|
957
|
+
if (!config.clientId) {
|
|
958
|
+
throw new Error(`clientId is required for ${provider} authentication. ` + "Please provide it in the oauth parameter.");
|
|
959
|
+
}
|
|
960
|
+
const stateData = options.stateData;
|
|
961
|
+
const state = await generateOAuthState(stateData);
|
|
962
|
+
const params = new URLSearchParams({
|
|
963
|
+
response_type: "code",
|
|
964
|
+
client_id: config.clientId,
|
|
965
|
+
redirect_uri: callbackUrl,
|
|
966
|
+
state
|
|
967
|
+
});
|
|
968
|
+
if (config.scope) {
|
|
969
|
+
params.set("scope", config.scope);
|
|
970
|
+
}
|
|
971
|
+
const authUrl = `${config.authorizationEndpoint}?${params.toString()}`;
|
|
972
|
+
const popup = openPopupWindow(authUrl, "playcademy-auth");
|
|
973
|
+
if (!popup || popup.closed) {
|
|
974
|
+
throw new Error("Popup blocked. Please enable popups and try again.");
|
|
975
|
+
}
|
|
976
|
+
onStateChange?.({
|
|
977
|
+
status: "exchanging_token",
|
|
978
|
+
message: "Waiting for authentication..."
|
|
979
|
+
});
|
|
980
|
+
return await waitForServerMessage(popup, onStateChange);
|
|
981
|
+
} catch (error) {
|
|
982
|
+
const errorMessage = error instanceof Error ? error.message : "Authentication failed";
|
|
983
|
+
onStateChange?.({
|
|
984
|
+
status: "error",
|
|
985
|
+
message: errorMessage,
|
|
986
|
+
error: error instanceof Error ? error : new Error(errorMessage)
|
|
987
|
+
});
|
|
988
|
+
throw error;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
async function waitForServerMessage(popup, onStateChange) {
|
|
992
|
+
return new Promise((resolve) => {
|
|
993
|
+
let resolved = false;
|
|
994
|
+
const handleMessage = (event) => {
|
|
995
|
+
if (event.origin !== window.location.origin)
|
|
996
|
+
return;
|
|
997
|
+
const data = event.data;
|
|
998
|
+
if (data?.type === "PLAYCADEMY_AUTH_STATE_CHANGE") {
|
|
999
|
+
resolved = true;
|
|
1000
|
+
window.removeEventListener("message", handleMessage);
|
|
1001
|
+
if (data.authenticated && data.user) {
|
|
1002
|
+
onStateChange?.({
|
|
1003
|
+
status: "complete",
|
|
1004
|
+
message: "Authentication successful"
|
|
1005
|
+
});
|
|
1006
|
+
resolve({
|
|
1007
|
+
success: true,
|
|
1008
|
+
user: data.user
|
|
1009
|
+
});
|
|
1010
|
+
} else {
|
|
1011
|
+
const error = new Error(data.error || "Authentication failed");
|
|
1012
|
+
onStateChange?.({
|
|
1013
|
+
status: "error",
|
|
1014
|
+
message: error.message,
|
|
1015
|
+
error
|
|
1016
|
+
});
|
|
1017
|
+
resolve({
|
|
1018
|
+
success: false,
|
|
1019
|
+
error
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
window.addEventListener("message", handleMessage);
|
|
1025
|
+
const checkClosed = setInterval(() => {
|
|
1026
|
+
if (popup.closed && !resolved) {
|
|
1027
|
+
clearInterval(checkClosed);
|
|
1028
|
+
window.removeEventListener("message", handleMessage);
|
|
1029
|
+
const error = new Error("Authentication cancelled");
|
|
1030
|
+
onStateChange?.({
|
|
1031
|
+
status: "error",
|
|
1032
|
+
message: error.message,
|
|
1033
|
+
error
|
|
1034
|
+
});
|
|
1035
|
+
resolve({
|
|
1036
|
+
success: false,
|
|
1037
|
+
error
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
}, 500);
|
|
1041
|
+
setTimeout(() => {
|
|
1042
|
+
if (!resolved) {
|
|
1043
|
+
window.removeEventListener("message", handleMessage);
|
|
1044
|
+
clearInterval(checkClosed);
|
|
1045
|
+
const error = new Error("Authentication timeout");
|
|
1046
|
+
onStateChange?.({
|
|
1047
|
+
status: "error",
|
|
1048
|
+
message: error.message,
|
|
1049
|
+
error
|
|
1050
|
+
});
|
|
1051
|
+
resolve({
|
|
1052
|
+
success: false,
|
|
1053
|
+
error
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
}, 5 * 60 * 1000);
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
var init_popup = __esm(() => {
|
|
1060
|
+
init_oauth();
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
// src/core/auth/flows/redirect.ts
|
|
1064
|
+
async function initiateRedirectFlow(options) {
|
|
1065
|
+
const { provider, callbackUrl, onStateChange, oauth } = options;
|
|
1066
|
+
try {
|
|
1067
|
+
onStateChange?.({
|
|
1068
|
+
status: "opening_popup",
|
|
1069
|
+
message: "Redirecting to authentication provider..."
|
|
1070
|
+
});
|
|
1071
|
+
const defaults = getOAuthConfig(provider);
|
|
1072
|
+
const config = oauth ? { ...defaults, ...oauth } : defaults;
|
|
1073
|
+
if (!config.clientId) {
|
|
1074
|
+
throw new Error(`clientId is required for ${provider} authentication. ` + "Please provide it in the oauth parameter.");
|
|
1075
|
+
}
|
|
1076
|
+
const stateData = options.stateData;
|
|
1077
|
+
const state = await generateOAuthState(stateData);
|
|
1078
|
+
const params = new URLSearchParams({
|
|
1079
|
+
response_type: "code",
|
|
1080
|
+
client_id: config.clientId,
|
|
1081
|
+
redirect_uri: callbackUrl,
|
|
1082
|
+
state
|
|
1083
|
+
});
|
|
1084
|
+
if (config.scope) {
|
|
1085
|
+
params.set("scope", config.scope);
|
|
1086
|
+
}
|
|
1087
|
+
const authUrl = `${config.authorizationEndpoint}?${params.toString()}`;
|
|
1088
|
+
window.location.href = authUrl;
|
|
1089
|
+
return new Promise(() => {});
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
const errorMessage = error instanceof Error ? error.message : "Authentication failed";
|
|
1092
|
+
onStateChange?.({
|
|
1093
|
+
status: "error",
|
|
1094
|
+
message: errorMessage,
|
|
1095
|
+
error: error instanceof Error ? error : new Error(errorMessage)
|
|
1096
|
+
});
|
|
1097
|
+
throw error;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
var init_redirect = __esm(() => {
|
|
1101
|
+
init_oauth();
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
// src/core/auth/flows/unified.ts
|
|
1105
|
+
async function initiateUnifiedFlow(options) {
|
|
1106
|
+
const { mode = "auto" } = options;
|
|
1107
|
+
const effectiveMode = mode === "auto" ? isInIframe() ? "popup" : "redirect" : mode;
|
|
1108
|
+
switch (effectiveMode) {
|
|
1109
|
+
case "popup":
|
|
1110
|
+
return initiatePopupFlow(options);
|
|
1111
|
+
case "redirect":
|
|
1112
|
+
return initiateRedirectFlow(options);
|
|
1113
|
+
default:
|
|
1114
|
+
throw new Error(`Unsupported authentication mode: ${effectiveMode}`);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
var init_unified = __esm(() => {
|
|
1118
|
+
init_popup();
|
|
1119
|
+
init_redirect();
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
// src/core/auth/login.ts
|
|
1123
|
+
async function login(client, options) {
|
|
1124
|
+
try {
|
|
1125
|
+
let stateData = options.stateData;
|
|
1126
|
+
if (!stateData) {
|
|
1127
|
+
const storedUserId = client["userId"];
|
|
1128
|
+
if (storedUserId) {
|
|
1129
|
+
stateData = {
|
|
1130
|
+
playcademy_user_id: storedUserId,
|
|
1131
|
+
game_id: client["gameId"] || ""
|
|
1132
|
+
};
|
|
1133
|
+
} else {
|
|
1134
|
+
try {
|
|
1135
|
+
const currentUser = await client.users.me();
|
|
1136
|
+
if (currentUser?.id) {
|
|
1137
|
+
stateData = {
|
|
1138
|
+
playcademy_user_id: currentUser.id,
|
|
1139
|
+
game_id: client["gameId"] || ""
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
} catch {
|
|
1143
|
+
log.debug("[Playcademy SDK] No current user available for state data");
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
log.debug("[Playcademy SDK] Starting OAuth login", {
|
|
1148
|
+
provider: options.provider,
|
|
1149
|
+
mode: options.mode || "auto",
|
|
1150
|
+
callbackUrl: options.callbackUrl,
|
|
1151
|
+
hasStateData: !!stateData
|
|
1152
|
+
});
|
|
1153
|
+
const enrichedOptions = {
|
|
1154
|
+
...options,
|
|
1155
|
+
stateData
|
|
1156
|
+
};
|
|
1157
|
+
const result = await initiateUnifiedFlow(enrichedOptions);
|
|
1158
|
+
if (result.success && result.user) {
|
|
1159
|
+
log.debug("[Playcademy SDK] OAuth login successful", {
|
|
1160
|
+
userId: result.user.sub
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
return result;
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
log.error("[Playcademy SDK] OAuth login failed", { error });
|
|
1166
|
+
const authError = error instanceof Error ? error : new Error("Authentication failed");
|
|
1167
|
+
return {
|
|
1168
|
+
success: false,
|
|
1169
|
+
error: authError
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
var init_login = __esm(() => {
|
|
1174
|
+
init_src();
|
|
1175
|
+
init_unified();
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
// src/core/namespaces/identity.ts
|
|
1179
|
+
function createIdentityNamespace(client) {
|
|
1180
|
+
return {
|
|
1181
|
+
connect: (options) => login(client, options),
|
|
1182
|
+
_getContext: () => ({
|
|
1183
|
+
isInIframe: client["authContext"]?.isInIframe ?? false
|
|
1184
|
+
})
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
var init_identity = __esm(() => {
|
|
1188
|
+
init_login();
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
// src/messaging.ts
|
|
1192
|
+
class PlaycademyMessaging {
|
|
1193
|
+
listeners = new Map;
|
|
1194
|
+
send(type, payload, options) {
|
|
1195
|
+
if (options?.target) {
|
|
1196
|
+
this.sendViaPostMessage(type, payload, options.target, options.origin || "*");
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
const context = this.getMessagingContext(type);
|
|
1200
|
+
if (context.shouldUsePostMessage) {
|
|
1201
|
+
this.sendViaPostMessage(type, payload, context.target, context.origin);
|
|
1202
|
+
} else {
|
|
1203
|
+
this.sendViaCustomEvent(type, payload);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
listen(type, handler) {
|
|
1207
|
+
const postMessageListener = (event) => {
|
|
1208
|
+
const messageEvent = event;
|
|
1209
|
+
if (messageEvent.data?.type === type) {
|
|
1210
|
+
handler(messageEvent.data.payload || messageEvent.data);
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
const customEventListener = (event) => {
|
|
1214
|
+
handler(event.detail);
|
|
1215
|
+
};
|
|
1216
|
+
if (!this.listeners.has(type)) {
|
|
1217
|
+
this.listeners.set(type, new Map);
|
|
1218
|
+
}
|
|
1219
|
+
const listenerMap = this.listeners.get(type);
|
|
1220
|
+
listenerMap.set(handler, {
|
|
1221
|
+
postMessage: postMessageListener,
|
|
1222
|
+
customEvent: customEventListener
|
|
1223
|
+
});
|
|
1224
|
+
window.addEventListener("message", postMessageListener);
|
|
1225
|
+
window.addEventListener(type, customEventListener);
|
|
1226
|
+
}
|
|
1227
|
+
unlisten(type, handler) {
|
|
1228
|
+
const typeListeners = this.listeners.get(type);
|
|
1229
|
+
if (!typeListeners || !typeListeners.has(handler)) {
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
const listeners = typeListeners.get(handler);
|
|
1233
|
+
window.removeEventListener("message", listeners.postMessage);
|
|
1234
|
+
window.removeEventListener(type, listeners.customEvent);
|
|
1235
|
+
typeListeners.delete(handler);
|
|
1236
|
+
if (typeListeners.size === 0) {
|
|
1237
|
+
this.listeners.delete(type);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
getMessagingContext(eventType) {
|
|
1241
|
+
const isIframe = typeof window !== "undefined" && window.self !== window.top;
|
|
1242
|
+
const iframeToParentEvents = [
|
|
1243
|
+
"PLAYCADEMY_READY" /* READY */,
|
|
1244
|
+
"PLAYCADEMY_EXIT" /* EXIT */,
|
|
1245
|
+
"PLAYCADEMY_TELEMETRY" /* TELEMETRY */,
|
|
1246
|
+
"PLAYCADEMY_KEY_EVENT" /* KEY_EVENT */
|
|
1247
|
+
];
|
|
1248
|
+
const shouldUsePostMessage = isIframe && iframeToParentEvents.includes(eventType);
|
|
1249
|
+
return {
|
|
1250
|
+
shouldUsePostMessage,
|
|
1251
|
+
target: shouldUsePostMessage ? window.parent : undefined,
|
|
1252
|
+
origin: "*"
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
sendViaPostMessage(type, payload, target = window.parent, origin = "*") {
|
|
1256
|
+
const messageData = { type };
|
|
1257
|
+
if (payload !== undefined) {
|
|
1258
|
+
messageData.payload = payload;
|
|
1259
|
+
}
|
|
1260
|
+
target.postMessage(messageData, origin);
|
|
1261
|
+
}
|
|
1262
|
+
sendViaCustomEvent(type, payload) {
|
|
1263
|
+
window.dispatchEvent(new CustomEvent(type, { detail: payload }));
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
var MessageEvents, messaging;
|
|
1267
|
+
var init_messaging = __esm(() => {
|
|
1268
|
+
((MessageEvents2) => {
|
|
1269
|
+
MessageEvents2["INIT"] = "PLAYCADEMY_INIT";
|
|
1270
|
+
MessageEvents2["TOKEN_REFRESH"] = "PLAYCADEMY_TOKEN_REFRESH";
|
|
1271
|
+
MessageEvents2["PAUSE"] = "PLAYCADEMY_PAUSE";
|
|
1272
|
+
MessageEvents2["RESUME"] = "PLAYCADEMY_RESUME";
|
|
1273
|
+
MessageEvents2["FORCE_EXIT"] = "PLAYCADEMY_FORCE_EXIT";
|
|
1274
|
+
MessageEvents2["OVERLAY"] = "PLAYCADEMY_OVERLAY";
|
|
1275
|
+
MessageEvents2["READY"] = "PLAYCADEMY_READY";
|
|
1276
|
+
MessageEvents2["EXIT"] = "PLAYCADEMY_EXIT";
|
|
1277
|
+
MessageEvents2["TELEMETRY"] = "PLAYCADEMY_TELEMETRY";
|
|
1278
|
+
MessageEvents2["KEY_EVENT"] = "PLAYCADEMY_KEY_EVENT";
|
|
1279
|
+
MessageEvents2["AUTH_STATE_CHANGE"] = "PLAYCADEMY_AUTH_STATE_CHANGE";
|
|
1280
|
+
MessageEvents2["AUTH_CALLBACK"] = "PLAYCADEMY_AUTH_CALLBACK";
|
|
1281
|
+
})(MessageEvents ||= {});
|
|
1282
|
+
messaging = new PlaycademyMessaging;
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
// src/core/namespaces/runtime.ts
|
|
1286
|
+
function createRuntimeNamespace(client) {
|
|
1287
|
+
const eventListeners = new Map;
|
|
1288
|
+
const trackListener = (eventType, handler) => {
|
|
1289
|
+
if (!eventListeners.has(eventType)) {
|
|
1290
|
+
eventListeners.set(eventType, new Set);
|
|
1291
|
+
}
|
|
1292
|
+
eventListeners.get(eventType).add(handler);
|
|
1293
|
+
};
|
|
1294
|
+
const untrackListener = (eventType, handler) => {
|
|
1295
|
+
const listeners = eventListeners.get(eventType);
|
|
1296
|
+
if (listeners) {
|
|
1297
|
+
listeners.delete(handler);
|
|
1298
|
+
if (listeners.size === 0) {
|
|
1299
|
+
eventListeners.delete(eventType);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
};
|
|
1303
|
+
if (typeof window !== "undefined" && window.self !== window.top) {
|
|
1304
|
+
const playcademyConfig = window.PLAYCADEMY;
|
|
1305
|
+
const forwardKeys = Array.isArray(playcademyConfig?.forwardKeys) ? playcademyConfig.forwardKeys : ["Escape"];
|
|
1306
|
+
const keySet = new Set(forwardKeys.map((k) => k.toLowerCase()));
|
|
1307
|
+
const keyListener = (event) => {
|
|
1308
|
+
if (keySet.has(event.key.toLowerCase()) || keySet.has(event.code.toLowerCase())) {
|
|
1309
|
+
messaging.send("PLAYCADEMY_KEY_EVENT" /* KEY_EVENT */, {
|
|
1310
|
+
key: event.key,
|
|
1311
|
+
code: event.code,
|
|
1312
|
+
type: event.type
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1316
|
+
window.addEventListener("keydown", keyListener);
|
|
1317
|
+
window.addEventListener("keyup", keyListener);
|
|
1318
|
+
trackListener("PLAYCADEMY_FORCE_EXIT" /* FORCE_EXIT */, () => {
|
|
1319
|
+
window.removeEventListener("keydown", keyListener);
|
|
1320
|
+
window.removeEventListener("keyup", keyListener);
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
return {
|
|
1324
|
+
getGameToken: async (gameId, options) => {
|
|
1325
|
+
const res = await client["request"](`/games/${gameId}/token`, "POST");
|
|
1326
|
+
if (options?.apply) {
|
|
1327
|
+
client.setToken(res.token);
|
|
1328
|
+
}
|
|
1329
|
+
return res;
|
|
1330
|
+
},
|
|
1331
|
+
exit: async () => {
|
|
1332
|
+
if (client["internalClientSessionId"] && client["gameId"]) {
|
|
1333
|
+
try {
|
|
1334
|
+
await client.games.endSession(client["internalClientSessionId"], client["gameId"]);
|
|
1335
|
+
} catch (error) {
|
|
1336
|
+
log.error("[Playcademy SDK] Failed to auto-end session:", {
|
|
1337
|
+
sessionId: client["internalClientSessionId"],
|
|
1338
|
+
error
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
messaging.send("PLAYCADEMY_EXIT" /* EXIT */, undefined);
|
|
1343
|
+
},
|
|
1344
|
+
onInit: (handler) => {
|
|
1345
|
+
messaging.listen("PLAYCADEMY_INIT" /* INIT */, handler);
|
|
1346
|
+
trackListener("PLAYCADEMY_INIT" /* INIT */, handler);
|
|
1347
|
+
},
|
|
1348
|
+
onTokenRefresh: (handler) => {
|
|
1349
|
+
messaging.listen("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, handler);
|
|
1350
|
+
trackListener("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, handler);
|
|
1351
|
+
},
|
|
1352
|
+
onPause: (handler) => {
|
|
1353
|
+
messaging.listen("PLAYCADEMY_PAUSE" /* PAUSE */, handler);
|
|
1354
|
+
trackListener("PLAYCADEMY_PAUSE" /* PAUSE */, handler);
|
|
1355
|
+
},
|
|
1356
|
+
onResume: (handler) => {
|
|
1357
|
+
messaging.listen("PLAYCADEMY_RESUME" /* RESUME */, handler);
|
|
1358
|
+
trackListener("PLAYCADEMY_RESUME" /* RESUME */, handler);
|
|
1359
|
+
},
|
|
1360
|
+
onForceExit: (handler) => {
|
|
1361
|
+
messaging.listen("PLAYCADEMY_FORCE_EXIT" /* FORCE_EXIT */, handler);
|
|
1362
|
+
trackListener("PLAYCADEMY_FORCE_EXIT" /* FORCE_EXIT */, handler);
|
|
1363
|
+
},
|
|
1364
|
+
onOverlay: (handler) => {
|
|
1365
|
+
messaging.listen("PLAYCADEMY_OVERLAY" /* OVERLAY */, handler);
|
|
1366
|
+
trackListener("PLAYCADEMY_OVERLAY" /* OVERLAY */, handler);
|
|
1367
|
+
},
|
|
1368
|
+
ready: () => {
|
|
1369
|
+
messaging.send("PLAYCADEMY_READY" /* READY */, undefined);
|
|
1370
|
+
},
|
|
1371
|
+
sendTelemetry: (data) => {
|
|
1372
|
+
messaging.send("PLAYCADEMY_TELEMETRY" /* TELEMETRY */, data);
|
|
1373
|
+
},
|
|
1374
|
+
removeListener: (eventType, handler) => {
|
|
1375
|
+
messaging.unlisten(eventType, handler);
|
|
1376
|
+
untrackListener(eventType, handler);
|
|
1377
|
+
},
|
|
1378
|
+
removeAllListeners: () => {
|
|
1379
|
+
for (const [eventType, handlers] of eventListeners.entries()) {
|
|
1380
|
+
for (const handler of handlers) {
|
|
1381
|
+
messaging.unlisten(eventType, handler);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
eventListeners.clear();
|
|
1385
|
+
},
|
|
1386
|
+
getListenerCounts: () => {
|
|
1387
|
+
const counts = {};
|
|
1388
|
+
for (const [eventType, handlers] of eventListeners.entries()) {
|
|
1389
|
+
counts[eventType] = handlers.size;
|
|
1390
|
+
}
|
|
1391
|
+
return counts;
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
var init_runtime = __esm(() => {
|
|
1396
|
+
init_src();
|
|
1397
|
+
init_messaging();
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
// src/core/cache/ttl-cache.ts
|
|
1401
|
+
function createTTLCache(options) {
|
|
1402
|
+
const cache = new Map;
|
|
1403
|
+
const { ttl: defaultTTL, keyPrefix = "", onClear } = options;
|
|
1404
|
+
async function get(key, loader, config) {
|
|
1405
|
+
const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
|
|
1406
|
+
const now = Date.now();
|
|
1407
|
+
const effectiveTTL = config?.ttl !== undefined ? config.ttl : defaultTTL;
|
|
1408
|
+
const force = config?.force || false;
|
|
1409
|
+
const skipCache = config?.skipCache || false;
|
|
1410
|
+
if (effectiveTTL === 0 || skipCache) {
|
|
1411
|
+
return loader();
|
|
1412
|
+
}
|
|
1413
|
+
if (!force) {
|
|
1414
|
+
const cached = cache.get(fullKey);
|
|
1415
|
+
if (cached && cached.expiresAt > now) {
|
|
1416
|
+
return cached.value;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
const promise = loader().catch((error) => {
|
|
1420
|
+
cache.delete(fullKey);
|
|
1421
|
+
throw error;
|
|
1422
|
+
});
|
|
1423
|
+
cache.set(fullKey, {
|
|
1424
|
+
value: promise,
|
|
1425
|
+
expiresAt: now + effectiveTTL
|
|
1426
|
+
});
|
|
1427
|
+
return promise;
|
|
1428
|
+
}
|
|
1429
|
+
function clear(key) {
|
|
1430
|
+
if (key === undefined) {
|
|
1431
|
+
cache.clear();
|
|
1432
|
+
onClear?.();
|
|
1433
|
+
} else {
|
|
1434
|
+
const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
|
|
1435
|
+
cache.delete(fullKey);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
function size() {
|
|
1439
|
+
return cache.size;
|
|
1440
|
+
}
|
|
1441
|
+
function prune() {
|
|
1442
|
+
const now = Date.now();
|
|
1443
|
+
for (const [key, entry] of cache.entries()) {
|
|
1444
|
+
if (entry.expiresAt <= now) {
|
|
1445
|
+
cache.delete(key);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
function getKeys() {
|
|
1450
|
+
const keys = [];
|
|
1451
|
+
const prefixLen = keyPrefix ? keyPrefix.length + 1 : 0;
|
|
1452
|
+
for (const fullKey of cache.keys()) {
|
|
1453
|
+
keys.push(fullKey.substring(prefixLen));
|
|
1454
|
+
}
|
|
1455
|
+
return keys;
|
|
1456
|
+
}
|
|
1457
|
+
function has(key) {
|
|
1458
|
+
const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
|
|
1459
|
+
const cached = cache.get(fullKey);
|
|
1460
|
+
if (!cached)
|
|
1461
|
+
return false;
|
|
1462
|
+
const now = Date.now();
|
|
1463
|
+
if (cached.expiresAt <= now) {
|
|
1464
|
+
cache.delete(fullKey);
|
|
1465
|
+
return false;
|
|
1466
|
+
}
|
|
1467
|
+
return true;
|
|
1468
|
+
}
|
|
1469
|
+
return { get, clear, size, prune, getKeys, has };
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// src/core/request.ts
|
|
1473
|
+
async function request({
|
|
1474
|
+
path,
|
|
1475
|
+
baseUrl,
|
|
1476
|
+
token,
|
|
1477
|
+
method = "GET",
|
|
1478
|
+
body,
|
|
1479
|
+
extraHeaders = {}
|
|
1480
|
+
}) {
|
|
1481
|
+
const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
|
|
1482
|
+
const headers = { ...extraHeaders };
|
|
1483
|
+
let payload;
|
|
1484
|
+
if (body instanceof FormData) {
|
|
1485
|
+
payload = body;
|
|
1486
|
+
} else if (body !== undefined && body !== null) {
|
|
1487
|
+
payload = JSON.stringify(body);
|
|
1488
|
+
headers["Content-Type"] = "application/json";
|
|
1489
|
+
}
|
|
1490
|
+
if (token)
|
|
1491
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
1492
|
+
const res = await fetch(url, {
|
|
1493
|
+
method,
|
|
1494
|
+
headers,
|
|
1495
|
+
body: payload,
|
|
1496
|
+
credentials: "omit"
|
|
1497
|
+
});
|
|
1498
|
+
if (!res.ok) {
|
|
1499
|
+
const clonedRes = res.clone();
|
|
1500
|
+
const errorBody = await clonedRes.json().catch(() => clonedRes.text().catch(() => {
|
|
1501
|
+
return;
|
|
1502
|
+
})) ?? undefined;
|
|
1503
|
+
throw new ApiError(res.status, res.statusText, errorBody);
|
|
1504
|
+
}
|
|
1505
|
+
if (res.status === 204)
|
|
1506
|
+
return;
|
|
1507
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
1508
|
+
if (contentType.includes("application/json")) {
|
|
1509
|
+
try {
|
|
1510
|
+
return await res.json();
|
|
1511
|
+
} catch (err) {
|
|
1512
|
+
if (err instanceof SyntaxError)
|
|
1513
|
+
return;
|
|
1514
|
+
throw err;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
const raw = await res.text().catch(() => "");
|
|
1518
|
+
return raw && raw.length > 0 ? raw : undefined;
|
|
1519
|
+
}
|
|
1520
|
+
async function fetchManifest(assetBundleBase) {
|
|
1521
|
+
const manifestUrl = `${assetBundleBase.replace(/\/$/, "")}/playcademy.manifest.json`;
|
|
1522
|
+
try {
|
|
1523
|
+
const response = await fetch(manifestUrl);
|
|
1524
|
+
if (!response.ok) {
|
|
1525
|
+
log.error(`[fetchManifest] Failed to fetch manifest from ${manifestUrl}. Status: ${response.status}`);
|
|
1526
|
+
throw new PlaycademyError(`Failed to fetch manifest: ${response.status} ${response.statusText}`);
|
|
1527
|
+
}
|
|
1528
|
+
return await response.json();
|
|
1529
|
+
} catch (error) {
|
|
1530
|
+
if (error instanceof PlaycademyError) {
|
|
1531
|
+
throw error;
|
|
1532
|
+
}
|
|
1533
|
+
log.error(`[Playcademy SDK] Error fetching or parsing manifest from ${manifestUrl}:`, {
|
|
1534
|
+
error
|
|
1535
|
+
});
|
|
1536
|
+
throw new PlaycademyError("Failed to load or parse game manifest");
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
var init_request = __esm(() => {
|
|
1540
|
+
init_src();
|
|
1541
|
+
init_errors();
|
|
1542
|
+
});
|
|
1543
|
+
|
|
1544
|
+
// src/core/namespaces/games.ts
|
|
1545
|
+
function createGamesNamespace(client) {
|
|
1546
|
+
const gamesListCache = createTTLCache({
|
|
1547
|
+
ttl: 60 * 1000,
|
|
1548
|
+
keyPrefix: "games.list"
|
|
1549
|
+
});
|
|
1550
|
+
const gameFetchCache = createTTLCache({
|
|
1551
|
+
ttl: 60 * 1000,
|
|
1552
|
+
keyPrefix: "games.fetch"
|
|
1553
|
+
});
|
|
1554
|
+
return {
|
|
1555
|
+
fetch: async (gameIdOrSlug, options) => {
|
|
1556
|
+
const promise = client["request"](`/games/${gameIdOrSlug}`, "GET");
|
|
1557
|
+
return gameFetchCache.get(gameIdOrSlug, async () => {
|
|
1558
|
+
const baseGameData = await promise;
|
|
1559
|
+
if (baseGameData.gameType === "hosted" && baseGameData.assetBundleBase !== null) {
|
|
1560
|
+
const manifestData = await fetchManifest(baseGameData.assetBundleBase);
|
|
1561
|
+
return { ...baseGameData, manifest: manifestData };
|
|
1562
|
+
}
|
|
1563
|
+
return baseGameData;
|
|
1564
|
+
}, options);
|
|
1565
|
+
},
|
|
1566
|
+
list: (options) => {
|
|
1567
|
+
return gamesListCache.get("all", () => client["request"]("/games", "GET"), options);
|
|
1568
|
+
},
|
|
1569
|
+
saveState: async (state) => {
|
|
1570
|
+
const gameId = client["_ensureGameId"]();
|
|
1571
|
+
await client["request"](`/games/${gameId}/state`, "POST", state);
|
|
1572
|
+
},
|
|
1573
|
+
loadState: async () => {
|
|
1574
|
+
const gameId = client["_ensureGameId"]();
|
|
1575
|
+
return client["request"](`/games/${gameId}/state`, "GET");
|
|
1576
|
+
},
|
|
1577
|
+
startSession: async (gameId) => {
|
|
1578
|
+
const idToUse = gameId ?? client["_ensureGameId"]();
|
|
1579
|
+
return client["request"](`/games/${idToUse}/sessions`, "POST", {});
|
|
1580
|
+
},
|
|
1581
|
+
endSession: async (sessionId, gameId) => {
|
|
1582
|
+
const effectiveGameIdToEnd = gameId ?? client["_ensureGameId"]();
|
|
1583
|
+
if (client["internalClientSessionId"] && sessionId === client["internalClientSessionId"] && effectiveGameIdToEnd === client["gameId"]) {
|
|
1584
|
+
client["internalClientSessionId"] = undefined;
|
|
1585
|
+
}
|
|
1586
|
+
await client["request"](`/games/${effectiveGameIdToEnd}/sessions/${sessionId}/end`, "POST");
|
|
1587
|
+
},
|
|
1588
|
+
token: {
|
|
1589
|
+
create: async (gameId, options) => {
|
|
1590
|
+
const res = await client["request"](`/games/${gameId}/token`, "POST");
|
|
1591
|
+
if (options?.apply) {
|
|
1592
|
+
client.setToken(res.token);
|
|
1593
|
+
}
|
|
1594
|
+
return res;
|
|
1595
|
+
}
|
|
1596
|
+
},
|
|
1597
|
+
leaderboard: {
|
|
1598
|
+
get: async (gameId, options) => {
|
|
1599
|
+
const params = new URLSearchParams;
|
|
1600
|
+
if (options?.limit)
|
|
1601
|
+
params.append("limit", String(options.limit));
|
|
1602
|
+
if (options?.offset)
|
|
1603
|
+
params.append("offset", String(options.offset));
|
|
1604
|
+
const queryString = params.toString();
|
|
1605
|
+
const path = queryString ? `/games/${gameId}/leaderboard?${queryString}` : `/games/${gameId}/leaderboard`;
|
|
1606
|
+
return client["request"](path, "GET");
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
var init_games = __esm(() => {
|
|
1612
|
+
init_request();
|
|
1613
|
+
});
|
|
1614
|
+
|
|
1615
|
+
// src/core/cache/permanent-cache.ts
|
|
1616
|
+
function createPermanentCache(keyPrefix) {
|
|
1617
|
+
const cache = new Map;
|
|
1618
|
+
async function get(key, loader) {
|
|
1619
|
+
const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
|
|
1620
|
+
const existing = cache.get(fullKey);
|
|
1621
|
+
if (existing)
|
|
1622
|
+
return existing;
|
|
1623
|
+
const promise = loader().catch((error) => {
|
|
1624
|
+
cache.delete(fullKey);
|
|
1625
|
+
throw error;
|
|
1626
|
+
});
|
|
1627
|
+
cache.set(fullKey, promise);
|
|
1628
|
+
return promise;
|
|
1629
|
+
}
|
|
1630
|
+
function clear(key) {
|
|
1631
|
+
if (key === undefined) {
|
|
1632
|
+
cache.clear();
|
|
1633
|
+
} else {
|
|
1634
|
+
const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
|
|
1635
|
+
cache.delete(fullKey);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
function has(key) {
|
|
1639
|
+
const fullKey = keyPrefix ? `${keyPrefix}:${key}` : key;
|
|
1640
|
+
return cache.has(fullKey);
|
|
1641
|
+
}
|
|
1642
|
+
function size() {
|
|
1643
|
+
return cache.size;
|
|
1644
|
+
}
|
|
1645
|
+
function keys() {
|
|
1646
|
+
const result = [];
|
|
1647
|
+
const prefixLen = keyPrefix ? keyPrefix.length + 1 : 0;
|
|
1648
|
+
for (const fullKey of cache.keys()) {
|
|
1649
|
+
result.push(fullKey.substring(prefixLen));
|
|
1650
|
+
}
|
|
1651
|
+
return result;
|
|
1652
|
+
}
|
|
1653
|
+
return { get, clear, has, size, keys };
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// src/core/namespaces/users.ts
|
|
1657
|
+
function createUsersNamespace(client) {
|
|
1658
|
+
const itemIdCache = createPermanentCache("items");
|
|
1659
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1660
|
+
const resolveItemId = async (identifier) => {
|
|
1661
|
+
if (UUID_REGEX.test(identifier))
|
|
1662
|
+
return identifier;
|
|
1663
|
+
const gameId = client["gameId"];
|
|
1664
|
+
const cacheKey = gameId ? `${identifier}:${gameId}` : identifier;
|
|
1665
|
+
return itemIdCache.get(cacheKey, async () => {
|
|
1666
|
+
const queryParams = new URLSearchParams({ slug: identifier });
|
|
1667
|
+
if (gameId)
|
|
1668
|
+
queryParams.append("gameId", gameId);
|
|
1669
|
+
const item = await client["request"](`/items/resolve?${queryParams.toString()}`, "GET");
|
|
1670
|
+
return item.id;
|
|
1671
|
+
});
|
|
1672
|
+
};
|
|
1673
|
+
return {
|
|
1674
|
+
me: async () => {
|
|
1675
|
+
return client["request"]("/users/me", "GET");
|
|
1676
|
+
},
|
|
1677
|
+
inventory: {
|
|
1678
|
+
get: async () => client["request"](`/inventory`, "GET"),
|
|
1679
|
+
add: async (identifier, qty) => {
|
|
1680
|
+
const itemId = await resolveItemId(identifier);
|
|
1681
|
+
const res = await client["request"](`/inventory/add`, "POST", { itemId, qty });
|
|
1682
|
+
client["emit"]("inventoryChange", {
|
|
1683
|
+
itemId,
|
|
1684
|
+
delta: qty,
|
|
1685
|
+
newTotal: res.newTotal
|
|
1686
|
+
});
|
|
1687
|
+
return res;
|
|
1688
|
+
},
|
|
1689
|
+
remove: async (identifier, qty) => {
|
|
1690
|
+
const itemId = await resolveItemId(identifier);
|
|
1691
|
+
const res = await client["request"](`/inventory/remove`, "POST", { itemId, qty });
|
|
1692
|
+
client["emit"]("inventoryChange", {
|
|
1693
|
+
itemId,
|
|
1694
|
+
delta: -qty,
|
|
1695
|
+
newTotal: res.newTotal
|
|
1696
|
+
});
|
|
1697
|
+
return res;
|
|
1698
|
+
},
|
|
1699
|
+
quantity: async (identifier) => {
|
|
1700
|
+
const itemId = await resolveItemId(identifier);
|
|
1701
|
+
const inventory = await client["request"](`/inventory`, "GET");
|
|
1702
|
+
const item = inventory.find((inv) => inv.item?.id === itemId);
|
|
1703
|
+
return item?.quantity ?? 0;
|
|
1704
|
+
},
|
|
1705
|
+
has: async (identifier, minQuantity = 1) => {
|
|
1706
|
+
const itemId = await resolveItemId(identifier);
|
|
1707
|
+
const inventory = await client["request"](`/inventory`, "GET");
|
|
1708
|
+
const item = inventory.find((inv) => inv.item?.id === itemId);
|
|
1709
|
+
const qty = item?.quantity ?? 0;
|
|
1710
|
+
return qty >= minQuantity;
|
|
1711
|
+
}
|
|
1712
|
+
},
|
|
1713
|
+
scores: {
|
|
1714
|
+
get: async (userIdOrOptions, options) => {
|
|
1715
|
+
let userId;
|
|
1716
|
+
let queryOptions;
|
|
1717
|
+
if (typeof userIdOrOptions === "string") {
|
|
1718
|
+
userId = userIdOrOptions;
|
|
1719
|
+
queryOptions = options || {};
|
|
1720
|
+
} else {
|
|
1721
|
+
queryOptions = userIdOrOptions || {};
|
|
1722
|
+
const user = await client["request"]("/users/me", "GET");
|
|
1723
|
+
userId = user.id;
|
|
1724
|
+
}
|
|
1725
|
+
const params = new URLSearchParams({
|
|
1726
|
+
limit: String(queryOptions.limit || 50)
|
|
1727
|
+
});
|
|
1728
|
+
if (queryOptions.gameId) {
|
|
1729
|
+
params.append("gameId", queryOptions.gameId);
|
|
1730
|
+
}
|
|
1731
|
+
return client["request"](`/users/${userId}/scores?${params}`, "GET");
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
var init_users = () => {};
|
|
1737
|
+
|
|
1738
|
+
// src/core/namespaces/dev.ts
|
|
1739
|
+
function createDevNamespace(client) {
|
|
1740
|
+
return {
|
|
1741
|
+
status: {
|
|
1742
|
+
apply: () => client["request"]("/dev/apply", "POST"),
|
|
1743
|
+
get: async () => {
|
|
1744
|
+
const response = await client["request"]("/dev/status", "GET");
|
|
1745
|
+
return response.status;
|
|
1746
|
+
}
|
|
1747
|
+
},
|
|
1748
|
+
games: {
|
|
1749
|
+
upsert: async (slug, metadata, file, hooks) => {
|
|
1750
|
+
hooks?.onEvent?.({ type: "init" });
|
|
1751
|
+
const game = await client["request"](`/games/${slug}`, "PUT", metadata);
|
|
1752
|
+
if (metadata.gameType === "external" || file === null) {
|
|
1753
|
+
return game;
|
|
1754
|
+
}
|
|
1755
|
+
const fileName = file instanceof File ? file.name : "game.zip";
|
|
1756
|
+
const initiateResponse = await client["request"]("/games/uploads/initiate/", "POST", {
|
|
1757
|
+
fileName,
|
|
1758
|
+
gameId: game.id
|
|
1759
|
+
});
|
|
1760
|
+
if (hooks?.onEvent) {
|
|
1761
|
+
await new Promise((resolve, reject) => {
|
|
1762
|
+
const xhr = new XMLHttpRequest;
|
|
1763
|
+
xhr.open("PUT", initiateResponse.presignedUrl, true);
|
|
1764
|
+
const contentType = file.type || "application/octet-stream";
|
|
1765
|
+
try {
|
|
1766
|
+
xhr.setRequestHeader("Content-Type", contentType);
|
|
1767
|
+
} catch {}
|
|
1768
|
+
xhr.upload.onprogress = (event) => {
|
|
1769
|
+
if (event.lengthComputable) {
|
|
1770
|
+
const percent = event.loaded / event.total;
|
|
1771
|
+
hooks.onEvent?.({
|
|
1772
|
+
type: "s3Progress",
|
|
1773
|
+
loaded: event.loaded,
|
|
1774
|
+
total: event.total,
|
|
1775
|
+
percent
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
xhr.onload = () => {
|
|
1780
|
+
if (xhr.status >= 200 && xhr.status < 300)
|
|
1781
|
+
resolve();
|
|
1782
|
+
else
|
|
1783
|
+
reject(new Error(`File upload failed: ${xhr.status} ${xhr.statusText}`));
|
|
1784
|
+
};
|
|
1785
|
+
xhr.onerror = () => reject(new Error("File upload failed: network error"));
|
|
1786
|
+
xhr.send(file);
|
|
1787
|
+
});
|
|
1788
|
+
} else {
|
|
1789
|
+
const uploadResponse = await fetch(initiateResponse.presignedUrl, {
|
|
1790
|
+
method: "PUT",
|
|
1791
|
+
body: file,
|
|
1792
|
+
headers: {
|
|
1793
|
+
"Content-Type": file.type || "application/octet-stream"
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
if (!uploadResponse.ok) {
|
|
1797
|
+
throw new Error(`File upload failed: ${uploadResponse.status} ${uploadResponse.statusText}`);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
const baseUrl = (() => {
|
|
1801
|
+
const anyClient = client;
|
|
1802
|
+
try {
|
|
1803
|
+
return typeof anyClient.getBaseUrl === "function" ? anyClient.getBaseUrl() : "/api";
|
|
1804
|
+
} catch {
|
|
1805
|
+
return "/api";
|
|
1806
|
+
}
|
|
1807
|
+
})();
|
|
1808
|
+
const finalizeUrl = baseUrl.replace(/\/$/, "") + "/games/uploads/finalize/";
|
|
1809
|
+
const authToken = client.token;
|
|
1810
|
+
const finalizeResponse = await fetch(finalizeUrl, {
|
|
1811
|
+
method: "POST",
|
|
1812
|
+
headers: {
|
|
1813
|
+
"Content-Type": "application/json",
|
|
1814
|
+
...authToken ? { Authorization: `Bearer ${authToken}` } : {}
|
|
1815
|
+
},
|
|
1816
|
+
body: JSON.stringify({
|
|
1817
|
+
tempS3Key: initiateResponse.tempS3Key,
|
|
1818
|
+
gameId: initiateResponse.gameId,
|
|
1819
|
+
version: initiateResponse.version,
|
|
1820
|
+
slug,
|
|
1821
|
+
metadata,
|
|
1822
|
+
originalFileName: fileName
|
|
1823
|
+
}),
|
|
1824
|
+
credentials: "omit"
|
|
1825
|
+
});
|
|
1826
|
+
if (!finalizeResponse.ok) {
|
|
1827
|
+
const errText = await finalizeResponse.text().catch(() => "");
|
|
1828
|
+
throw new Error(`Finalize request failed: ${finalizeResponse.status} ${finalizeResponse.statusText}${errText ? ` - ${errText}` : ""}`);
|
|
1829
|
+
}
|
|
1830
|
+
if (!finalizeResponse.body) {
|
|
1831
|
+
throw new Error("Finalize response body missing");
|
|
1832
|
+
}
|
|
1833
|
+
hooks?.onEvent?.({ type: "finalizeStart" });
|
|
1834
|
+
let sawAnyServerEvent = false;
|
|
1835
|
+
const reader = finalizeResponse.body.pipeThrough(new TextDecoderStream).getReader();
|
|
1836
|
+
let buffer = "";
|
|
1837
|
+
while (true) {
|
|
1838
|
+
const { done, value } = await reader.read();
|
|
1839
|
+
if (done) {
|
|
1840
|
+
if (!sawAnyServerEvent) {
|
|
1841
|
+
hooks?.onClose?.();
|
|
1842
|
+
hooks?.onEvent?.({ type: "close" });
|
|
1843
|
+
}
|
|
1844
|
+
break;
|
|
1845
|
+
}
|
|
1846
|
+
buffer += value;
|
|
1847
|
+
let eolIndex;
|
|
1848
|
+
while ((eolIndex = buffer.indexOf(`
|
|
1849
|
+
|
|
1850
|
+
`)) >= 0) {
|
|
1851
|
+
const message = buffer.slice(0, eolIndex);
|
|
1852
|
+
buffer = buffer.slice(eolIndex + 2);
|
|
1853
|
+
const eventLine = message.match(/^event: (.*)$/m);
|
|
1854
|
+
const dataLine = message.match(/^data: (.*)$/m);
|
|
1855
|
+
if (eventLine && dataLine) {
|
|
1856
|
+
const eventType = eventLine[1];
|
|
1857
|
+
const eventData = JSON.parse(dataLine[1]);
|
|
1858
|
+
if (eventType === "uploadProgress") {
|
|
1859
|
+
sawAnyServerEvent = true;
|
|
1860
|
+
const percent = (eventData.value ?? 0) / 100;
|
|
1861
|
+
hooks?.onEvent?.({
|
|
1862
|
+
type: "finalizeProgress",
|
|
1863
|
+
percent,
|
|
1864
|
+
currentFileLabel: eventData.currentFileLabel || ""
|
|
1865
|
+
});
|
|
1866
|
+
} else if (eventType === "status") {
|
|
1867
|
+
sawAnyServerEvent = true;
|
|
1868
|
+
if (eventData.message) {
|
|
1869
|
+
hooks?.onEvent?.({
|
|
1870
|
+
type: "finalizeStatus",
|
|
1871
|
+
message: eventData.message
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
} else if (eventType === "complete") {
|
|
1875
|
+
sawAnyServerEvent = true;
|
|
1876
|
+
reader.cancel();
|
|
1877
|
+
return eventData;
|
|
1878
|
+
} else if (eventType === "error") {
|
|
1879
|
+
sawAnyServerEvent = true;
|
|
1880
|
+
reader.cancel();
|
|
1881
|
+
throw new Error(eventData.message);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
throw new Error("Upload completed but no final game data received");
|
|
1887
|
+
},
|
|
1888
|
+
update: (gameId, props) => client["request"](`/games/${gameId}`, "PATCH", props),
|
|
1889
|
+
delete: (gameId) => client["request"](`/games/${gameId}`, "DELETE")
|
|
1890
|
+
},
|
|
1891
|
+
keys: {
|
|
1892
|
+
create: (label) => client["request"](`/dev/keys`, "POST", { label }),
|
|
1893
|
+
list: () => client["request"](`/dev/keys`, "GET"),
|
|
1894
|
+
revoke: (keyId) => client["request"](`/dev/keys/${keyId}`, "DELETE")
|
|
1895
|
+
},
|
|
1896
|
+
items: {
|
|
1897
|
+
create: (gameId, slug, itemData) => client["request"](`/games/${gameId}/items`, "POST", {
|
|
1898
|
+
slug,
|
|
1899
|
+
...itemData
|
|
1900
|
+
}),
|
|
1901
|
+
update: (gameId, itemId, updates) => client["request"](`/games/${gameId}/items/${itemId}`, "PATCH", updates),
|
|
1902
|
+
list: (gameId) => client["request"](`/games/${gameId}/items`, "GET"),
|
|
1903
|
+
get: (gameId, slug) => {
|
|
1904
|
+
const queryParams = new URLSearchParams({ slug, gameId });
|
|
1905
|
+
return client["request"](`/items/resolve?${queryParams.toString()}`, "GET");
|
|
1906
|
+
},
|
|
1907
|
+
delete: (gameId, itemId) => client["request"](`/games/${gameId}/items/${itemId}`, "DELETE"),
|
|
1908
|
+
shop: {
|
|
1909
|
+
create: (gameId, itemId, listingData) => {
|
|
1910
|
+
return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "POST", listingData);
|
|
1911
|
+
},
|
|
1912
|
+
get: (gameId, itemId) => {
|
|
1913
|
+
return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "GET");
|
|
1914
|
+
},
|
|
1915
|
+
update: (gameId, itemId, updates) => {
|
|
1916
|
+
return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "PATCH", updates);
|
|
1917
|
+
},
|
|
1918
|
+
delete: (gameId, itemId) => {
|
|
1919
|
+
return client["request"](`/games/${gameId}/items/${itemId}/shop-listing`, "DELETE");
|
|
1920
|
+
},
|
|
1921
|
+
list: (gameId) => {
|
|
1922
|
+
return client["request"](`/games/${gameId}/shop-listings`, "GET");
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
// src/core/namespaces/maps.ts
|
|
1930
|
+
function createMapsNamespace(client) {
|
|
1931
|
+
return {
|
|
1932
|
+
get: (identifier) => client["request"](`/maps/${identifier}`, "GET"),
|
|
1933
|
+
elements: (mapId) => client["request"](`/map/elements?mapId=${mapId}`, "GET"),
|
|
1934
|
+
objects: {
|
|
1935
|
+
list: (mapId) => client["request"](`/maps/${mapId}/objects`, "GET"),
|
|
1936
|
+
create: (mapId, objectData) => client["request"](`/maps/${mapId}/objects`, "POST", objectData),
|
|
1937
|
+
delete: (mapId, objectId) => client["request"](`/maps/${mapId}/objects/${objectId}`, "DELETE")
|
|
1938
|
+
}
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
// src/core/namespaces/admin.ts
|
|
1943
|
+
function createAdminNamespace(client) {
|
|
1944
|
+
return {
|
|
1945
|
+
games: {
|
|
1946
|
+
pauseGame: (gameId) => client["request"](`/admin/games/${gameId}/pause`, "POST"),
|
|
1947
|
+
resumeGame: (gameId) => client["request"](`/admin/games/${gameId}/resume`, "POST")
|
|
1948
|
+
},
|
|
1949
|
+
items: {
|
|
1950
|
+
create: (props) => client["request"]("/items", "POST", props),
|
|
1951
|
+
get: (itemId) => client["request"](`/items/${itemId}`, "GET"),
|
|
1952
|
+
list: () => client["request"]("/items", "GET"),
|
|
1953
|
+
update: (itemId, props) => client["request"](`/items/${itemId}`, "PATCH", props),
|
|
1954
|
+
delete: (itemId) => client["request"](`/items/${itemId}`, "DELETE")
|
|
1955
|
+
},
|
|
1956
|
+
currencies: {
|
|
1957
|
+
create: (props) => client["request"]("/currencies", "POST", props),
|
|
1958
|
+
get: (currencyId) => client["request"](`/currencies/${currencyId}`, "GET"),
|
|
1959
|
+
list: () => client["request"]("/currencies", "GET"),
|
|
1960
|
+
update: (currencyId, props) => client["request"](`/currencies/${currencyId}`, "PATCH", props),
|
|
1961
|
+
delete: (currencyId) => client["request"](`/currencies/${currencyId}`, "DELETE")
|
|
1962
|
+
},
|
|
1963
|
+
shopListings: {
|
|
1964
|
+
create: (props) => client["request"]("/shop-listings", "POST", props),
|
|
1965
|
+
get: (listingId) => client["request"](`/shop-listings/${listingId}`, "GET"),
|
|
1966
|
+
list: () => client["request"]("/shop-listings", "GET"),
|
|
1967
|
+
update: (listingId, props) => client["request"](`/shop-listings/${listingId}`, "PATCH", props),
|
|
1968
|
+
delete: (listingId) => client["request"](`/shop-listings/${listingId}`, "DELETE")
|
|
1969
|
+
}
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
// src/core/namespaces/shop.ts
|
|
1974
|
+
function createShopNamespace(client) {
|
|
1975
|
+
return {
|
|
1976
|
+
view: () => {
|
|
1977
|
+
return client["request"]("/shop/view", "GET");
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
// src/core/namespaces/telemetry.ts
|
|
1983
|
+
function createTelemetryNamespace(client) {
|
|
1984
|
+
return {
|
|
1985
|
+
pushMetrics: (metrics) => client["request"](`/telemetry/metrics`, "POST", metrics)
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
// src/core/cache/cooldown-cache.ts
|
|
1990
|
+
function createCooldownCache(defaultCooldownMs) {
|
|
1991
|
+
const lastFetchTime = new Map;
|
|
1992
|
+
const pendingRequests = new Map;
|
|
1993
|
+
const lastResults = new Map;
|
|
1994
|
+
async function get(key, loader, config) {
|
|
1995
|
+
const now = Date.now();
|
|
1996
|
+
const lastFetch = lastFetchTime.get(key) || 0;
|
|
1997
|
+
const timeSinceLastFetch = now - lastFetch;
|
|
1998
|
+
const effectiveCooldown = config?.cooldown !== undefined ? config.cooldown : defaultCooldownMs;
|
|
1999
|
+
const force = config?.force || false;
|
|
2000
|
+
const pending = pendingRequests.get(key);
|
|
2001
|
+
if (pending) {
|
|
2002
|
+
return pending;
|
|
2003
|
+
}
|
|
2004
|
+
if (!force && timeSinceLastFetch < effectiveCooldown) {
|
|
2005
|
+
const cachedResult = lastResults.get(key);
|
|
2006
|
+
if (cachedResult !== undefined) {
|
|
2007
|
+
return Promise.resolve(cachedResult);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
const promise = loader().then((result) => {
|
|
2011
|
+
pendingRequests.delete(key);
|
|
2012
|
+
lastFetchTime.set(key, Date.now());
|
|
2013
|
+
lastResults.set(key, result);
|
|
2014
|
+
return result;
|
|
2015
|
+
}).catch((error) => {
|
|
2016
|
+
pendingRequests.delete(key);
|
|
2017
|
+
throw error;
|
|
2018
|
+
});
|
|
2019
|
+
pendingRequests.set(key, promise);
|
|
2020
|
+
return promise;
|
|
2021
|
+
}
|
|
2022
|
+
function clear(key) {
|
|
2023
|
+
if (key === undefined) {
|
|
2024
|
+
lastFetchTime.clear();
|
|
2025
|
+
pendingRequests.clear();
|
|
2026
|
+
lastResults.clear();
|
|
2027
|
+
} else {
|
|
2028
|
+
lastFetchTime.delete(key);
|
|
2029
|
+
pendingRequests.delete(key);
|
|
2030
|
+
lastResults.delete(key);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
return { get, clear };
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
// src/core/namespaces/levels.ts
|
|
2037
|
+
function createLevelsNamespace(client) {
|
|
2038
|
+
const progressCache = createCooldownCache(5000);
|
|
2039
|
+
return {
|
|
2040
|
+
get: async () => {
|
|
2041
|
+
return client["request"]("/users/level", "GET");
|
|
2042
|
+
},
|
|
2043
|
+
progress: async (options) => {
|
|
2044
|
+
return progressCache.get("user-progress", () => client["request"]("/users/level/progress", "GET"), options);
|
|
2045
|
+
},
|
|
2046
|
+
config: {
|
|
2047
|
+
list: async () => {
|
|
2048
|
+
return client["request"]("/levels/config", "GET");
|
|
2049
|
+
},
|
|
2050
|
+
get: async (level) => {
|
|
2051
|
+
return client["request"](`/levels/config/${level}`, "GET");
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
var init_levels = () => {};
|
|
2057
|
+
|
|
2058
|
+
// src/core/cache/singleton-cache.ts
|
|
2059
|
+
function createSingletonCache() {
|
|
2060
|
+
let cachedValue;
|
|
2061
|
+
let hasValue = false;
|
|
2062
|
+
async function get(loader) {
|
|
2063
|
+
if (hasValue) {
|
|
2064
|
+
return cachedValue;
|
|
2065
|
+
}
|
|
2066
|
+
const value = await loader();
|
|
2067
|
+
cachedValue = value;
|
|
2068
|
+
hasValue = true;
|
|
2069
|
+
return value;
|
|
2070
|
+
}
|
|
2071
|
+
function clear() {
|
|
2072
|
+
cachedValue = undefined;
|
|
2073
|
+
hasValue = false;
|
|
2074
|
+
}
|
|
2075
|
+
function has() {
|
|
2076
|
+
return hasValue;
|
|
2077
|
+
}
|
|
2078
|
+
return { get, clear, has };
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
// src/core/namespaces/credits.ts
|
|
2082
|
+
function createCreditsNamespace(client) {
|
|
2083
|
+
const creditsIdCache = createSingletonCache();
|
|
2084
|
+
const getCreditsItemId = async () => {
|
|
2085
|
+
return creditsIdCache.get(async () => {
|
|
2086
|
+
const queryParams = new URLSearchParams({ slug: CURRENCIES.PRIMARY });
|
|
2087
|
+
const creditsItem = await client["request"](`/items/resolve?${queryParams.toString()}`, "GET");
|
|
2088
|
+
if (!creditsItem || !creditsItem.id) {
|
|
2089
|
+
throw new Error("Playcademy Credits item not found in catalog");
|
|
2090
|
+
}
|
|
2091
|
+
return creditsItem.id;
|
|
2092
|
+
});
|
|
2093
|
+
};
|
|
2094
|
+
return {
|
|
2095
|
+
balance: async () => {
|
|
2096
|
+
const inventory = await client["request"]("/inventory", "GET");
|
|
2097
|
+
const primaryCurrencyInventoryItem = inventory.find((item) => item.item?.slug === CURRENCIES.PRIMARY);
|
|
2098
|
+
return primaryCurrencyInventoryItem?.quantity ?? 0;
|
|
2099
|
+
},
|
|
2100
|
+
add: async (amount) => {
|
|
2101
|
+
if (amount <= 0) {
|
|
2102
|
+
throw new Error("Amount must be positive");
|
|
2103
|
+
}
|
|
2104
|
+
const creditsItemId = await getCreditsItemId();
|
|
2105
|
+
const result = await client["request"]("/inventory/add", "POST", {
|
|
2106
|
+
itemId: creditsItemId,
|
|
2107
|
+
qty: amount
|
|
2108
|
+
});
|
|
2109
|
+
client["emit"]("inventoryChange", {
|
|
2110
|
+
itemId: creditsItemId,
|
|
2111
|
+
delta: amount,
|
|
2112
|
+
newTotal: result.newTotal
|
|
2113
|
+
});
|
|
2114
|
+
return result.newTotal;
|
|
2115
|
+
},
|
|
2116
|
+
spend: async (amount) => {
|
|
2117
|
+
if (amount <= 0) {
|
|
2118
|
+
throw new Error("Amount must be positive");
|
|
2119
|
+
}
|
|
2120
|
+
const creditsItemId = await getCreditsItemId();
|
|
2121
|
+
const result = await client["request"]("/inventory/remove", "POST", {
|
|
2122
|
+
itemId: creditsItemId,
|
|
2123
|
+
qty: amount
|
|
2124
|
+
});
|
|
2125
|
+
client["emit"]("inventoryChange", {
|
|
2126
|
+
itemId: creditsItemId,
|
|
2127
|
+
delta: -amount,
|
|
2128
|
+
newTotal: result.newTotal
|
|
2129
|
+
});
|
|
2130
|
+
return result.newTotal;
|
|
2131
|
+
}
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
var init_credits = __esm(() => {
|
|
2135
|
+
init_constants();
|
|
2136
|
+
});
|
|
2137
|
+
|
|
2138
|
+
// src/core/namespaces/leaderboard.ts
|
|
2139
|
+
function createLeaderboardNamespace(client) {
|
|
2140
|
+
return {
|
|
2141
|
+
fetch: async (options) => {
|
|
2142
|
+
const params = new URLSearchParams({
|
|
2143
|
+
timeframe: options?.timeframe || "all_time",
|
|
2144
|
+
limit: String(options?.limit || 10),
|
|
2145
|
+
offset: String(options?.offset || 0)
|
|
2146
|
+
});
|
|
2147
|
+
if (options?.gameId) {
|
|
2148
|
+
params.append("gameId", options.gameId);
|
|
2149
|
+
}
|
|
2150
|
+
return client["request"](`/leaderboard?${params}`, "GET");
|
|
2151
|
+
},
|
|
2152
|
+
getUserRank: async (gameId, userId) => {
|
|
2153
|
+
return client["request"](`/games/${gameId}/users/${userId}/rank`, "GET");
|
|
2154
|
+
}
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
// src/core/namespaces/scores.ts
|
|
2159
|
+
function createScoresNamespace(client) {
|
|
2160
|
+
return {
|
|
2161
|
+
submit: async (gameId, score, metadata) => {
|
|
2162
|
+
return client["request"](`/games/${gameId}/scores`, "POST", {
|
|
2163
|
+
score,
|
|
2164
|
+
metadata
|
|
2165
|
+
});
|
|
2166
|
+
},
|
|
2167
|
+
getByUser: async (gameId, userId, options) => {
|
|
2168
|
+
const params = new URLSearchParams;
|
|
2169
|
+
if (options?.limit) {
|
|
2170
|
+
params.append("limit", String(options.limit));
|
|
2171
|
+
}
|
|
2172
|
+
const queryString = params.toString();
|
|
2173
|
+
const path = queryString ? `/games/${gameId}/users/${userId}/scores?${queryString}` : `/games/${gameId}/users/${userId}/scores`;
|
|
2174
|
+
return client["request"](path, "GET");
|
|
2175
|
+
}
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
// src/core/namespaces/character.ts
|
|
2180
|
+
function createCharacterNamespace(client) {
|
|
2181
|
+
const componentCache = createTTLCache({
|
|
2182
|
+
ttl: 5 * 60 * 1000,
|
|
2183
|
+
keyPrefix: "character.components"
|
|
2184
|
+
});
|
|
2185
|
+
return {
|
|
2186
|
+
get: async (userId) => {
|
|
2187
|
+
try {
|
|
2188
|
+
const path = userId ? `/character/${userId}` : "/character";
|
|
2189
|
+
return await client["request"](path, "GET");
|
|
2190
|
+
} catch (error) {
|
|
2191
|
+
if (error instanceof Error) {
|
|
2192
|
+
if (error.message.includes("404")) {
|
|
2193
|
+
return null;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
throw error;
|
|
2197
|
+
}
|
|
2198
|
+
},
|
|
2199
|
+
create: async (characterData) => {
|
|
2200
|
+
return client["request"]("/character", "POST", characterData);
|
|
2201
|
+
},
|
|
2202
|
+
update: async (updates) => {
|
|
2203
|
+
return client["request"]("/character", "PATCH", updates);
|
|
2204
|
+
},
|
|
2205
|
+
components: {
|
|
2206
|
+
list: async (options) => {
|
|
2207
|
+
const cacheKey = options?.level === undefined ? "all" : String(options.level);
|
|
2208
|
+
return componentCache.get(cacheKey, async () => {
|
|
2209
|
+
const path = options?.level !== undefined ? `/character/components?level=${options.level}` : "/character/components";
|
|
2210
|
+
const components = await client["request"](path, "GET");
|
|
2211
|
+
return components || [];
|
|
2212
|
+
}, options);
|
|
2213
|
+
},
|
|
2214
|
+
clearCache: (key) => componentCache.clear(key),
|
|
2215
|
+
getCacheKeys: () => componentCache.getKeys()
|
|
2216
|
+
},
|
|
2217
|
+
accessories: {
|
|
2218
|
+
equip: async (slot, componentId) => {
|
|
2219
|
+
return client["request"]("/character/accessories/equip", "POST", { slot, accessoryComponentId: componentId });
|
|
2220
|
+
},
|
|
2221
|
+
remove: async (slot) => {
|
|
2222
|
+
return client["request"](`/character/accessories/${slot}`, "DELETE");
|
|
2223
|
+
},
|
|
2224
|
+
list: async () => {
|
|
2225
|
+
const character = await client.character.get();
|
|
2226
|
+
return character?.accessories || [];
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
var init_character = () => {};
|
|
2232
|
+
|
|
2233
|
+
// src/core/namespaces/sprites.ts
|
|
2234
|
+
function createSpritesNamespace(client) {
|
|
2235
|
+
const templateUrlCache = createPermanentCache("sprite-template-urls");
|
|
2236
|
+
return {
|
|
2237
|
+
templates: {
|
|
2238
|
+
get: async (slug) => {
|
|
2239
|
+
if (!slug)
|
|
2240
|
+
throw new Error("Sprite template slug is required");
|
|
2241
|
+
const templateMeta = await templateUrlCache.get(slug, async () => {
|
|
2242
|
+
return client["request"](`/sprites/templates/${slug}`, "GET");
|
|
2243
|
+
});
|
|
2244
|
+
if (!templateMeta.url) {
|
|
2245
|
+
throw new Error(`Template ${slug} has no URL in database`);
|
|
2246
|
+
}
|
|
2247
|
+
const response = await fetch(templateMeta.url);
|
|
2248
|
+
if (!response.ok) {
|
|
2249
|
+
throw new Error(`Failed to fetch template JSON from ${templateMeta.url}: ${response.statusText}`);
|
|
2250
|
+
}
|
|
2251
|
+
return await response.json();
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
var init_sprites = () => {};
|
|
2257
|
+
|
|
2258
|
+
// src/core/namespaces/realtime.client.ts
|
|
2259
|
+
class RealtimeChannelClient {
|
|
2260
|
+
gameId;
|
|
2261
|
+
_channelName;
|
|
2262
|
+
getToken;
|
|
2263
|
+
baseUrl;
|
|
2264
|
+
ws;
|
|
2265
|
+
listeners = new Set;
|
|
2266
|
+
isClosing = false;
|
|
2267
|
+
tokenRefreshUnsubscribe;
|
|
2268
|
+
constructor(gameId, _channelName, getToken, baseUrl) {
|
|
2269
|
+
this.gameId = gameId;
|
|
2270
|
+
this._channelName = _channelName;
|
|
2271
|
+
this.getToken = getToken;
|
|
2272
|
+
this.baseUrl = baseUrl;
|
|
2273
|
+
}
|
|
2274
|
+
async connect() {
|
|
2275
|
+
try {
|
|
2276
|
+
const token = await this.getToken();
|
|
2277
|
+
let wsBase;
|
|
2278
|
+
if (/^ws(s)?:\/\//.test(this.baseUrl)) {
|
|
2279
|
+
wsBase = this.baseUrl;
|
|
2280
|
+
} else if (/^http(s)?:\/\//.test(this.baseUrl)) {
|
|
2281
|
+
wsBase = this.baseUrl.replace(/^http/, "ws");
|
|
2282
|
+
} else {
|
|
2283
|
+
const isBrowser2 = typeof window !== "undefined";
|
|
2284
|
+
if (isBrowser2) {
|
|
2285
|
+
const proto = window.location.protocol === "https:" ? "wss" : "ws";
|
|
2286
|
+
wsBase = `${proto}://${window.location.host}${this.baseUrl}`;
|
|
2287
|
+
} else {
|
|
2288
|
+
wsBase = `ws://${this.baseUrl.replace(/^\//, "")}`;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
const url = new URL(wsBase);
|
|
2292
|
+
url.searchParams.set("token", token);
|
|
2293
|
+
url.searchParams.set("c", this._channelName);
|
|
2294
|
+
this.ws = new WebSocket(url);
|
|
2295
|
+
this.setupEventHandlers();
|
|
2296
|
+
this.setupTokenRefreshListener();
|
|
2297
|
+
await new Promise((resolve, reject) => {
|
|
2298
|
+
if (!this.ws) {
|
|
2299
|
+
reject(new Error("WebSocket creation failed"));
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2302
|
+
const onOpen = () => {
|
|
2303
|
+
this.ws?.removeEventListener("open", onOpen);
|
|
2304
|
+
this.ws?.removeEventListener("error", onError);
|
|
2305
|
+
resolve();
|
|
2306
|
+
};
|
|
2307
|
+
const onError = (event) => {
|
|
2308
|
+
this.ws?.removeEventListener("open", onOpen);
|
|
2309
|
+
this.ws?.removeEventListener("error", onError);
|
|
2310
|
+
reject(new Error(`WebSocket connection failed: ${event}`));
|
|
2311
|
+
};
|
|
2312
|
+
this.ws.addEventListener("open", onOpen);
|
|
2313
|
+
this.ws.addEventListener("error", onError);
|
|
2314
|
+
});
|
|
2315
|
+
log.debug("[RealtimeChannelClient] Connected to channel", {
|
|
2316
|
+
gameId: this.gameId,
|
|
2317
|
+
channel: this._channelName
|
|
2318
|
+
});
|
|
2319
|
+
return this;
|
|
2320
|
+
} catch (error) {
|
|
2321
|
+
log.error("[RealtimeChannelClient] Connection failed", {
|
|
2322
|
+
gameId: this.gameId,
|
|
2323
|
+
channel: this._channelName,
|
|
2324
|
+
error
|
|
2325
|
+
});
|
|
2326
|
+
throw error;
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
setupEventHandlers() {
|
|
2330
|
+
if (!this.ws)
|
|
2331
|
+
return;
|
|
2332
|
+
this.ws.onmessage = (event) => {
|
|
2333
|
+
try {
|
|
2334
|
+
const data = JSON.parse(event.data);
|
|
2335
|
+
this.listeners.forEach((callback) => {
|
|
2336
|
+
try {
|
|
2337
|
+
callback(data);
|
|
2338
|
+
} catch (error) {
|
|
2339
|
+
log.warn("[RealtimeChannelClient] Message listener error", {
|
|
2340
|
+
channel: this._channelName,
|
|
2341
|
+
error
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
});
|
|
2345
|
+
} catch (error) {
|
|
2346
|
+
log.warn("[RealtimeChannelClient] Failed to parse message", {
|
|
2347
|
+
channel: this._channelName,
|
|
2348
|
+
message: event.data,
|
|
2349
|
+
error
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
};
|
|
2353
|
+
this.ws.onclose = (event) => {
|
|
2354
|
+
log.debug("[RealtimeChannelClient] Connection closed", {
|
|
2355
|
+
channel: this._channelName,
|
|
2356
|
+
code: event.code,
|
|
2357
|
+
reason: event.reason,
|
|
2358
|
+
wasClean: event.wasClean
|
|
2359
|
+
});
|
|
2360
|
+
if (!this.isClosing && event.code !== CLOSE_CODES.TOKEN_REFRESH) {
|
|
2361
|
+
log.warn("[RealtimeChannelClient] Unexpected disconnection", {
|
|
2362
|
+
channel: this._channelName,
|
|
2363
|
+
code: event.code,
|
|
2364
|
+
reason: event.reason
|
|
2365
|
+
});
|
|
2366
|
+
}
|
|
2367
|
+
};
|
|
2368
|
+
this.ws.onerror = (event) => {
|
|
2369
|
+
log.error("[RealtimeChannelClient] WebSocket error", {
|
|
2370
|
+
channel: this._channelName,
|
|
2371
|
+
event
|
|
2372
|
+
});
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
setupTokenRefreshListener() {
|
|
2376
|
+
const tokenRefreshHandler = async ({ token }) => {
|
|
2377
|
+
log.debug("[RealtimeChannelClient] Token refresh received, reconnecting", {
|
|
2378
|
+
channel: this._channelName
|
|
2379
|
+
});
|
|
2380
|
+
try {
|
|
2381
|
+
await this.reconnectWithNewToken(token);
|
|
2382
|
+
} catch (error) {
|
|
2383
|
+
log.error("[RealtimeChannelClient] Token refresh reconnection failed", {
|
|
2384
|
+
channel: this._channelName,
|
|
2385
|
+
error
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
};
|
|
2389
|
+
messaging.listen("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, tokenRefreshHandler);
|
|
2390
|
+
this.tokenRefreshUnsubscribe = () => {
|
|
2391
|
+
messaging.unlisten("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, tokenRefreshHandler);
|
|
2392
|
+
};
|
|
2393
|
+
}
|
|
2394
|
+
async reconnectWithNewToken(_token) {
|
|
2395
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
2396
|
+
this.ws.close(CLOSE_CODES.TOKEN_REFRESH, "token_refresh");
|
|
2397
|
+
await new Promise((resolve) => {
|
|
2398
|
+
const checkClosed = () => {
|
|
2399
|
+
if (!this.ws || this.ws.readyState === WebSocket.CLOSED) {
|
|
2400
|
+
resolve();
|
|
2401
|
+
} else {
|
|
2402
|
+
setTimeout(checkClosed, 10);
|
|
2403
|
+
}
|
|
2404
|
+
};
|
|
2405
|
+
checkClosed();
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2408
|
+
await this.connect();
|
|
2409
|
+
}
|
|
2410
|
+
send(data) {
|
|
2411
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
2412
|
+
try {
|
|
2413
|
+
const message = JSON.stringify(data);
|
|
2414
|
+
this.ws.send(message);
|
|
2415
|
+
} catch (error) {
|
|
2416
|
+
log.error("[RealtimeChannelClient] Failed to send message", {
|
|
2417
|
+
channel: this._channelName,
|
|
2418
|
+
error,
|
|
2419
|
+
data
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2422
|
+
} else {
|
|
2423
|
+
log.warn("[RealtimeChannelClient] Cannot send message - connection not open", {
|
|
2424
|
+
channel: this._channelName,
|
|
2425
|
+
readyState: this.ws?.readyState
|
|
2426
|
+
});
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
onMessage(callback) {
|
|
2430
|
+
this.listeners.add(callback);
|
|
2431
|
+
return () => this.listeners.delete(callback);
|
|
2432
|
+
}
|
|
2433
|
+
close() {
|
|
2434
|
+
this.isClosing = true;
|
|
2435
|
+
if (this.tokenRefreshUnsubscribe) {
|
|
2436
|
+
this.tokenRefreshUnsubscribe();
|
|
2437
|
+
this.tokenRefreshUnsubscribe = undefined;
|
|
2438
|
+
}
|
|
2439
|
+
if (this.ws) {
|
|
2440
|
+
this.ws.close(CLOSE_CODES.NORMAL_CLOSURE, "client_close");
|
|
2441
|
+
this.ws = undefined;
|
|
2442
|
+
}
|
|
2443
|
+
this.listeners.clear();
|
|
2444
|
+
log.debug("[RealtimeChannelClient] Channel closed", {
|
|
2445
|
+
channel: this._channelName
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
get channelName() {
|
|
2449
|
+
return this._channelName;
|
|
2450
|
+
}
|
|
2451
|
+
get isConnected() {
|
|
2452
|
+
return this.ws?.readyState === WebSocket.OPEN;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
var CLOSE_CODES;
|
|
2456
|
+
var init_realtime_client = __esm(() => {
|
|
2457
|
+
init_src();
|
|
2458
|
+
init_messaging();
|
|
2459
|
+
CLOSE_CODES = {
|
|
2460
|
+
NORMAL_CLOSURE: 1000,
|
|
2461
|
+
TOKEN_REFRESH: 4000
|
|
2462
|
+
};
|
|
2463
|
+
});
|
|
2464
|
+
|
|
2465
|
+
// src/core/namespaces/realtime.ts
|
|
2466
|
+
function createRealtimeNamespace(client) {
|
|
2467
|
+
return {
|
|
2468
|
+
token: {
|
|
2469
|
+
get: async () => {
|
|
2470
|
+
const endpoint = client["gameId"] ? `/games/${client["gameId"]}/realtime/token` : "/realtime/token";
|
|
2471
|
+
return client["request"](endpoint, "POST");
|
|
2472
|
+
}
|
|
2473
|
+
},
|
|
2474
|
+
async open(channel = "default", url) {
|
|
2475
|
+
if (!client["gameId"]) {
|
|
2476
|
+
throw new Error("gameId is required for realtime channels");
|
|
2477
|
+
}
|
|
2478
|
+
let wsBaseUrl = url;
|
|
2479
|
+
if (!wsBaseUrl && typeof window !== "undefined") {
|
|
2480
|
+
const ctx = window.PLAYCADEMY;
|
|
2481
|
+
if (ctx?.realtimeUrl) {
|
|
2482
|
+
wsBaseUrl = ctx.realtimeUrl;
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
const realtimeClient = new RealtimeChannelClient(client["gameId"], channel, () => client.realtime.token.get().then((r) => r.token), wsBaseUrl ?? client.getBaseUrl());
|
|
2486
|
+
return realtimeClient.connect();
|
|
2487
|
+
}
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
var init_realtime = __esm(() => {
|
|
2491
|
+
init_realtime_client();
|
|
2492
|
+
});
|
|
2493
|
+
|
|
2494
|
+
// src/core/namespaces/achievements.ts
|
|
2495
|
+
function createAchievementsNamespace(client) {
|
|
2496
|
+
const achievementsListCache = createTTLCache({
|
|
2497
|
+
ttl: 5 * 1000,
|
|
2498
|
+
keyPrefix: "achievements.list"
|
|
2499
|
+
});
|
|
2500
|
+
const achievementsHistoryCache = createTTLCache({
|
|
2501
|
+
ttl: 5 * 1000,
|
|
2502
|
+
keyPrefix: "achievements.history"
|
|
2503
|
+
});
|
|
2504
|
+
return {
|
|
2505
|
+
list: (options) => {
|
|
2506
|
+
return achievementsListCache.get("current", () => client["request"]("/achievements/current", "GET"), options);
|
|
2507
|
+
},
|
|
2508
|
+
history: {
|
|
2509
|
+
list: async (queryOptions, cacheOptions) => {
|
|
2510
|
+
const params = new URLSearchParams;
|
|
2511
|
+
if (queryOptions?.limit)
|
|
2512
|
+
params.append("limit", String(queryOptions.limit));
|
|
2513
|
+
const qs = params.toString();
|
|
2514
|
+
const path = qs ? `/achievements/history?${qs}` : "/achievements/history";
|
|
2515
|
+
const cacheKey = qs ? `history-${qs}` : "history";
|
|
2516
|
+
return achievementsHistoryCache.get(cacheKey, () => client["request"](path, "GET"), cacheOptions);
|
|
2517
|
+
}
|
|
2518
|
+
},
|
|
2519
|
+
progress: {
|
|
2520
|
+
submit: async (achievementId) => client["request"]("/achievements/progress", "POST", {
|
|
2521
|
+
achievementId
|
|
2522
|
+
})
|
|
2523
|
+
}
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
var init_achievements = () => {};
|
|
2527
|
+
|
|
2528
|
+
// src/core/namespaces/timeback.ts
|
|
2529
|
+
function createTimebackNamespace(client) {
|
|
2530
|
+
return {
|
|
2531
|
+
xp: {
|
|
2532
|
+
today: async (options) => {
|
|
2533
|
+
const params = new URLSearchParams;
|
|
2534
|
+
if (options?.date)
|
|
2535
|
+
params.set("date", options.date);
|
|
2536
|
+
if (options?.timezone)
|
|
2537
|
+
params.set("tz", options.timezone);
|
|
2538
|
+
const query = params.toString();
|
|
2539
|
+
const endpoint = query ? `/timeback/xp/today?${query}` : "/timeback/xp/today";
|
|
2540
|
+
return client["request"](endpoint, "GET");
|
|
2541
|
+
},
|
|
2542
|
+
total: async () => {
|
|
2543
|
+
return client["request"]("/timeback/xp/total", "GET");
|
|
2544
|
+
},
|
|
2545
|
+
history: async (options) => {
|
|
2546
|
+
const params = new URLSearchParams;
|
|
2547
|
+
if (options?.startDate)
|
|
2548
|
+
params.set("startDate", options.startDate);
|
|
2549
|
+
if (options?.endDate)
|
|
2550
|
+
params.set("endDate", options.endDate);
|
|
2551
|
+
const query = params.toString();
|
|
2552
|
+
const endpoint = query ? `/timeback/xp/history?${query}` : "/timeback/xp/history";
|
|
2553
|
+
return client["request"](endpoint, "GET");
|
|
2554
|
+
},
|
|
2555
|
+
summary: async (options) => {
|
|
2556
|
+
const [today, total] = await Promise.all([
|
|
2557
|
+
client["request"]((() => {
|
|
2558
|
+
const params = new URLSearchParams;
|
|
2559
|
+
if (options?.date)
|
|
2560
|
+
params.set("date", options.date);
|
|
2561
|
+
if (options?.timezone)
|
|
2562
|
+
params.set("tz", options.timezone);
|
|
2563
|
+
const query = params.toString();
|
|
2564
|
+
return query ? `/timeback/xp/today?${query}` : "/timeback/xp/today";
|
|
2565
|
+
})(), "GET"),
|
|
2566
|
+
client["request"]("/timeback/xp/total", "GET")
|
|
2567
|
+
]);
|
|
2568
|
+
return { today, total };
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
// src/core/namespaces/index.ts
|
|
2575
|
+
var init_namespaces = __esm(() => {
|
|
2576
|
+
init_identity();
|
|
2577
|
+
init_runtime();
|
|
2578
|
+
init_games();
|
|
2579
|
+
init_users();
|
|
2580
|
+
init_levels();
|
|
2581
|
+
init_credits();
|
|
2582
|
+
init_character();
|
|
2583
|
+
init_sprites();
|
|
2584
|
+
init_realtime();
|
|
2585
|
+
init_achievements();
|
|
2586
|
+
});
|
|
2587
|
+
|
|
2588
|
+
// src/core/static/init.ts
|
|
2589
|
+
async function getPlaycademyConfig() {
|
|
2590
|
+
const preloaded = window.PLAYCADEMY;
|
|
2591
|
+
if (preloaded?.token) {
|
|
2592
|
+
return preloaded;
|
|
2593
|
+
}
|
|
2594
|
+
if (window.self !== window.top) {
|
|
2595
|
+
return await waitForPlaycademyInit();
|
|
2596
|
+
} else {
|
|
2597
|
+
return createStandaloneConfig();
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
async function waitForPlaycademyInit() {
|
|
2601
|
+
return new Promise((resolve, reject) => {
|
|
2602
|
+
let contextReceived = false;
|
|
2603
|
+
const timeoutDuration = 5000;
|
|
2604
|
+
const handleMessage = (event) => {
|
|
2605
|
+
if (event.data?.type === "PLAYCADEMY_INIT" /* INIT */) {
|
|
2606
|
+
contextReceived = true;
|
|
2607
|
+
window.removeEventListener("message", handleMessage);
|
|
2608
|
+
clearTimeout(timeoutId);
|
|
2609
|
+
window.PLAYCADEMY = event.data.payload;
|
|
2610
|
+
resolve(event.data.payload);
|
|
2611
|
+
}
|
|
2612
|
+
};
|
|
2613
|
+
window.addEventListener("message", handleMessage);
|
|
2614
|
+
const timeoutId = setTimeout(() => {
|
|
2615
|
+
if (!contextReceived) {
|
|
2616
|
+
window.removeEventListener("message", handleMessage);
|
|
2617
|
+
reject(new Error(`${"PLAYCADEMY_INIT" /* INIT */} not received within ${timeoutDuration}ms`));
|
|
2618
|
+
}
|
|
2619
|
+
}, timeoutDuration);
|
|
2620
|
+
});
|
|
2621
|
+
}
|
|
2622
|
+
function createStandaloneConfig() {
|
|
2623
|
+
const mockConfig = {
|
|
2624
|
+
baseUrl: "/api",
|
|
2625
|
+
token: "mock-game-token-for-local-dev",
|
|
2626
|
+
gameId: "mock-game-id-from-template",
|
|
2627
|
+
realtimeUrl: undefined
|
|
2628
|
+
};
|
|
2629
|
+
window.PLAYCADEMY = mockConfig;
|
|
2630
|
+
return mockConfig;
|
|
2631
|
+
}
|
|
2632
|
+
async function init() {
|
|
2633
|
+
const { PlaycademyClient } = await Promise.resolve().then(() => (init_client(), exports_client));
|
|
2634
|
+
if (typeof window === "undefined") {
|
|
2635
|
+
throw new Error("Playcademy SDK must run in a browser context");
|
|
2636
|
+
}
|
|
2637
|
+
const config = await getPlaycademyConfig();
|
|
2638
|
+
const client = new PlaycademyClient({
|
|
2639
|
+
baseUrl: config.baseUrl,
|
|
2640
|
+
token: config.token,
|
|
2641
|
+
gameId: config.gameId
|
|
2642
|
+
});
|
|
2643
|
+
if (config.userId) {
|
|
2644
|
+
client["userId"] = config.userId;
|
|
2645
|
+
}
|
|
2646
|
+
messaging.listen("PLAYCADEMY_TOKEN_REFRESH" /* TOKEN_REFRESH */, ({ token }) => client.setToken(token));
|
|
2647
|
+
messaging.send("PLAYCADEMY_READY" /* READY */, undefined);
|
|
2648
|
+
if (import.meta.env?.MODE === "development") {
|
|
2649
|
+
window.PLAYCADEMY_CLIENT = client;
|
|
2650
|
+
}
|
|
2651
|
+
return client;
|
|
2652
|
+
}
|
|
2653
|
+
var init_init = __esm(() => {
|
|
2654
|
+
init_messaging();
|
|
2655
|
+
});
|
|
2656
|
+
|
|
2657
|
+
// src/core/static/login.ts
|
|
2658
|
+
async function login2(baseUrl, email, password) {
|
|
2659
|
+
let url = baseUrl;
|
|
2660
|
+
if (baseUrl.startsWith("/") && typeof window !== "undefined") {
|
|
2661
|
+
url = window.location.origin + baseUrl;
|
|
2662
|
+
}
|
|
2663
|
+
url = url + "/auth/login";
|
|
2664
|
+
const response = await fetch(url, {
|
|
2665
|
+
method: "POST",
|
|
2666
|
+
headers: {
|
|
2667
|
+
"Content-Type": "application/json"
|
|
2668
|
+
},
|
|
2669
|
+
body: JSON.stringify({ email, password })
|
|
2670
|
+
});
|
|
2671
|
+
if (!response.ok) {
|
|
2672
|
+
try {
|
|
2673
|
+
const errorData = await response.json();
|
|
2674
|
+
const errorMessage = errorData && errorData.message ? String(errorData.message) : response.statusText;
|
|
2675
|
+
throw new PlaycademyError(errorMessage);
|
|
2676
|
+
} catch (error) {
|
|
2677
|
+
log.error("[Playcademy SDK] Failed to parse error response JSON, using status text instead:", { error });
|
|
2678
|
+
throw new PlaycademyError(response.statusText);
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
return response.json();
|
|
2682
|
+
}
|
|
2683
|
+
var init_login2 = __esm(() => {
|
|
2684
|
+
init_src();
|
|
2685
|
+
init_errors();
|
|
2686
|
+
});
|
|
2687
|
+
|
|
2688
|
+
// src/core/static/identity.ts
|
|
2689
|
+
var identity;
|
|
2690
|
+
var init_identity2 = __esm(() => {
|
|
2691
|
+
init_oauth();
|
|
2692
|
+
identity = {
|
|
2693
|
+
parseOAuthState
|
|
2694
|
+
};
|
|
2695
|
+
});
|
|
2696
|
+
|
|
2697
|
+
// src/core/static/index.ts
|
|
2698
|
+
var init_static = __esm(() => {
|
|
2699
|
+
init_init();
|
|
2700
|
+
init_login2();
|
|
2701
|
+
init_identity2();
|
|
2702
|
+
});
|
|
2703
|
+
|
|
2704
|
+
// src/core/client.ts
|
|
2705
|
+
var exports_client = {};
|
|
2706
|
+
__export(exports_client, {
|
|
2707
|
+
PlaycademyClient: () => PlaycademyClient
|
|
2708
|
+
});
|
|
2709
|
+
var PlaycademyClient;
|
|
2710
|
+
var init_client = __esm(() => {
|
|
2711
|
+
init_src();
|
|
2712
|
+
init_errors();
|
|
2713
|
+
init_namespaces();
|
|
2714
|
+
init_request();
|
|
2715
|
+
init_static();
|
|
2716
|
+
PlaycademyClient = class PlaycademyClient {
|
|
2717
|
+
baseUrl;
|
|
2718
|
+
token;
|
|
2719
|
+
gameId;
|
|
2720
|
+
listeners = {};
|
|
2721
|
+
internalClientSessionId;
|
|
2722
|
+
authContext;
|
|
2723
|
+
userId;
|
|
2724
|
+
constructor(config) {
|
|
2725
|
+
if (!config) {
|
|
2726
|
+
this.baseUrl = "/api";
|
|
2727
|
+
this.token = undefined;
|
|
2728
|
+
this.gameId = undefined;
|
|
2729
|
+
} else {
|
|
2730
|
+
this.baseUrl = config.baseUrl || "/api";
|
|
2731
|
+
this.token = config.token;
|
|
2732
|
+
this.gameId = config.gameId;
|
|
2733
|
+
}
|
|
2734
|
+
this.detectAuthContext();
|
|
2735
|
+
if (this.gameId) {
|
|
2736
|
+
this._initializeInternalSession().catch((error) => {
|
|
2737
|
+
log.error("[Playcademy SDK] Background initialization of auto-session failed:", {
|
|
2738
|
+
error
|
|
2739
|
+
});
|
|
2740
|
+
});
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
getBaseUrl() {
|
|
2744
|
+
const isRelative = this.baseUrl.startsWith("/");
|
|
2745
|
+
const isBrowser2 = typeof window !== "undefined";
|
|
2746
|
+
return isRelative && isBrowser2 ? `${window.location.origin}${this.baseUrl}` : this.baseUrl;
|
|
2747
|
+
}
|
|
2748
|
+
ping() {
|
|
2749
|
+
return "pong";
|
|
2750
|
+
}
|
|
2751
|
+
setToken(token) {
|
|
2752
|
+
this.token = token ?? undefined;
|
|
2753
|
+
this.emit("authChange", { token: this.token ?? null });
|
|
2754
|
+
}
|
|
2755
|
+
isAuthenticated() {
|
|
2756
|
+
return !!this.token;
|
|
2757
|
+
}
|
|
2758
|
+
onAuthChange(callback) {
|
|
2759
|
+
this.on("authChange", (payload) => callback(payload.token));
|
|
2760
|
+
}
|
|
2761
|
+
_setAuthContext(context) {
|
|
2762
|
+
this.authContext = context;
|
|
2763
|
+
}
|
|
2764
|
+
on(event, callback) {
|
|
2765
|
+
this.listeners[event] = this.listeners[event] ?? [];
|
|
2766
|
+
this.listeners[event].push(callback);
|
|
2767
|
+
}
|
|
2768
|
+
emit(event, payload) {
|
|
2769
|
+
(this.listeners[event] ?? []).forEach((listener) => {
|
|
2770
|
+
listener(payload);
|
|
2771
|
+
});
|
|
2772
|
+
}
|
|
2773
|
+
async request(path, method, body, headers) {
|
|
2774
|
+
const effectiveHeaders = { ...headers };
|
|
2775
|
+
return request({
|
|
2776
|
+
path,
|
|
2777
|
+
method,
|
|
2778
|
+
body,
|
|
2779
|
+
baseUrl: this.baseUrl,
|
|
2780
|
+
token: this.token,
|
|
2781
|
+
extraHeaders: effectiveHeaders
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
_ensureGameId() {
|
|
2785
|
+
if (!this.gameId) {
|
|
2786
|
+
throw new PlaycademyError("This operation requires a gameId, but none was provided when initializing the client.");
|
|
2787
|
+
}
|
|
2788
|
+
return this.gameId;
|
|
2789
|
+
}
|
|
2790
|
+
detectAuthContext() {
|
|
2791
|
+
this.authContext = { isInIframe: isInIframe() };
|
|
2792
|
+
}
|
|
2793
|
+
async _initializeInternalSession() {
|
|
2794
|
+
if (!this.gameId || this.internalClientSessionId) {
|
|
2795
|
+
return;
|
|
2796
|
+
}
|
|
2797
|
+
try {
|
|
2798
|
+
const response = await this.games.startSession(this.gameId);
|
|
2799
|
+
this.internalClientSessionId = response.sessionId;
|
|
2800
|
+
} catch (error) {
|
|
2801
|
+
log.error("[Playcademy SDK] Auto-starting session failed for game", {
|
|
2802
|
+
gameId: this.gameId,
|
|
2803
|
+
error
|
|
2804
|
+
});
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
auth = createAuthNamespace(this);
|
|
2808
|
+
identity = createIdentityNamespace(this);
|
|
2809
|
+
runtime = createRuntimeNamespace(this);
|
|
2810
|
+
games = createGamesNamespace(this);
|
|
2811
|
+
users = createUsersNamespace(this);
|
|
2812
|
+
dev = createDevNamespace(this);
|
|
2813
|
+
maps = createMapsNamespace(this);
|
|
2814
|
+
admin = createAdminNamespace(this);
|
|
2815
|
+
shop = createShopNamespace(this);
|
|
2816
|
+
levels = createLevelsNamespace(this);
|
|
2817
|
+
timeback = createTimebackNamespace(this);
|
|
2818
|
+
telemetry = createTelemetryNamespace(this);
|
|
2819
|
+
credits = createCreditsNamespace(this);
|
|
2820
|
+
leaderboard = createLeaderboardNamespace(this);
|
|
2821
|
+
scores = createScoresNamespace(this);
|
|
2822
|
+
character = createCharacterNamespace(this);
|
|
2823
|
+
sprites = createSpritesNamespace(this);
|
|
2824
|
+
realtime = createRealtimeNamespace(this);
|
|
2825
|
+
achievements = createAchievementsNamespace(this);
|
|
2826
|
+
static init = init;
|
|
2827
|
+
static login = login2;
|
|
2828
|
+
static identity = identity;
|
|
2829
|
+
};
|
|
2830
|
+
});
|
|
2831
|
+
|
|
2832
|
+
// src/types.ts
|
|
2833
|
+
init_constants();
|
|
2834
|
+
var AuthProvider = {
|
|
2835
|
+
TIMEBACK: "TIMEBACK"
|
|
2836
|
+
};
|
|
2837
|
+
|
|
2838
|
+
// src/index.ts
|
|
2839
|
+
init_client();
|
|
2840
|
+
init_messaging();
|
|
2841
|
+
export {
|
|
2842
|
+
messaging,
|
|
2843
|
+
PlaycademyClient,
|
|
2844
|
+
MessageEvents,
|
|
2845
|
+
AuthProvider
|
|
2846
|
+
};
|