@hyve-sdk/js 2.13.0 → 2.14.0-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +144 -2
- package/dist/index.d.ts +144 -2
- package/dist/index.js +411 -45
- package/dist/index.mjs +410 -45
- package/dist/react.d.mts +55 -1
- package/dist/react.d.ts +55 -1
- package/dist/react.js +409 -45
- package/dist/react.mjs +409 -45
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ __export(src_exports, {
|
|
|
25
25
|
BillingService: () => BillingService,
|
|
26
26
|
CloudStorageAdapter: () => CloudStorageAdapter,
|
|
27
27
|
CrazyGamesService: () => CrazyGamesService,
|
|
28
|
+
CrazyGamesStorageAdapter: () => CrazyGamesStorageAdapter,
|
|
28
29
|
HyveClient: () => HyveClient,
|
|
29
30
|
LocalStorageAdapter: () => LocalStorageAdapter,
|
|
30
31
|
Logger: () => Logger,
|
|
@@ -1241,6 +1242,7 @@ var PlaygamaService = class {
|
|
|
1241
1242
|
var CRAZYGAMES_SDK_CDN = "https://sdk.crazygames.com/crazygames-sdk-v2.js";
|
|
1242
1243
|
var CrazyGamesService = class {
|
|
1243
1244
|
initialized = false;
|
|
1245
|
+
environment = null;
|
|
1244
1246
|
/**
|
|
1245
1247
|
* Detects if the game is running on the CrazyGames platform.
|
|
1246
1248
|
* Games on CrazyGames run inside an iframe, so we check document.referrer
|
|
@@ -1282,6 +1284,7 @@ var CrazyGamesService = class {
|
|
|
1282
1284
|
logger.warn("[CrazyGamesService] Unexpected environment:", env);
|
|
1283
1285
|
return false;
|
|
1284
1286
|
}
|
|
1287
|
+
this.environment = env;
|
|
1285
1288
|
this.initialized = true;
|
|
1286
1289
|
return true;
|
|
1287
1290
|
} catch (error) {
|
|
@@ -1392,6 +1395,88 @@ var CrazyGamesService = class {
|
|
|
1392
1395
|
if (!this.initialized) return;
|
|
1393
1396
|
window.CrazyGames?.SDK.game.happytime();
|
|
1394
1397
|
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Returns the resolved CrazyGames environment ('crazygames' | 'local' |
|
|
1400
|
+
* 'disabled'), or null if the SDK has not initialized yet. Used to
|
|
1401
|
+
* auto-select the CrazyGames storage adapter.
|
|
1402
|
+
*/
|
|
1403
|
+
getEnvironment() {
|
|
1404
|
+
return this.environment;
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Whether the CrazyGames account system is present (sync). When false,
|
|
1408
|
+
* there is no logged-in user concept and telemetry falls back to guests.
|
|
1409
|
+
*/
|
|
1410
|
+
isUserAccountAvailable() {
|
|
1411
|
+
if (!this.initialized) return false;
|
|
1412
|
+
return window.CrazyGames?.SDK.user.isUserAccountAvailable ?? false;
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Fetches the current CrazyGames user, or null if the player is not logged
|
|
1416
|
+
* in (guest). The returned `__dangerousUserId` is client-asserted and used
|
|
1417
|
+
* only as a telemetry attribution label.
|
|
1418
|
+
*/
|
|
1419
|
+
async getUser() {
|
|
1420
|
+
const sdk = window.CrazyGames?.SDK;
|
|
1421
|
+
if (!this.initialized || !sdk || !this.isUserAccountAvailable()) {
|
|
1422
|
+
return null;
|
|
1423
|
+
}
|
|
1424
|
+
try {
|
|
1425
|
+
return await sdk.user.getUser() ?? null;
|
|
1426
|
+
} catch (error) {
|
|
1427
|
+
logger.warn("[CrazyGamesService] getUser failed:", error);
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Registers a listener that fires when a guest logs in mid-session.
|
|
1433
|
+
* No-op if the SDK or account system is unavailable.
|
|
1434
|
+
*/
|
|
1435
|
+
addAuthListener(callback) {
|
|
1436
|
+
const sdk = window.CrazyGames?.SDK;
|
|
1437
|
+
if (!this.initialized || !sdk || !this.isUserAccountAvailable()) return;
|
|
1438
|
+
try {
|
|
1439
|
+
sdk.user.addAuthListener(callback);
|
|
1440
|
+
} catch (error) {
|
|
1441
|
+
logger.warn("[CrazyGamesService] addAuthListener failed:", error);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
/** Removes a previously registered auth listener. */
|
|
1445
|
+
removeAuthListener(callback) {
|
|
1446
|
+
const sdk = window.CrazyGames?.SDK;
|
|
1447
|
+
if (!this.initialized || !sdk) return;
|
|
1448
|
+
try {
|
|
1449
|
+
sdk.user.removeAuthListener(callback);
|
|
1450
|
+
} catch (error) {
|
|
1451
|
+
logger.warn("[CrazyGamesService] removeAuthListener failed:", error);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Reads a value from the CrazyGames data store. Returns null when the SDK
|
|
1456
|
+
* is unavailable or the key is absent.
|
|
1457
|
+
*/
|
|
1458
|
+
async dataGetItem(key) {
|
|
1459
|
+
const sdk = window.CrazyGames?.SDK;
|
|
1460
|
+
if (!this.initialized || !sdk) return null;
|
|
1461
|
+
const value = await sdk.data.getItem(key);
|
|
1462
|
+
return value ?? null;
|
|
1463
|
+
}
|
|
1464
|
+
/** Writes a value to the CrazyGames data store. */
|
|
1465
|
+
async dataSetItem(key, value) {
|
|
1466
|
+
const sdk = window.CrazyGames?.SDK;
|
|
1467
|
+
if (!this.initialized || !sdk) {
|
|
1468
|
+
throw new Error("CrazyGames SDK not initialized");
|
|
1469
|
+
}
|
|
1470
|
+
await sdk.data.setItem(key, value);
|
|
1471
|
+
}
|
|
1472
|
+
/** Removes a value from the CrazyGames data store. */
|
|
1473
|
+
async dataRemoveItem(key) {
|
|
1474
|
+
const sdk = window.CrazyGames?.SDK;
|
|
1475
|
+
if (!this.initialized || !sdk) {
|
|
1476
|
+
throw new Error("CrazyGames SDK not initialized");
|
|
1477
|
+
}
|
|
1478
|
+
await sdk.data.removeItem(key);
|
|
1479
|
+
}
|
|
1395
1480
|
loadScript() {
|
|
1396
1481
|
return new Promise((resolve, reject) => {
|
|
1397
1482
|
if (window.CrazyGames?.SDK) {
|
|
@@ -2059,6 +2144,162 @@ var LocalStorageAdapter = class {
|
|
|
2059
2144
|
return count;
|
|
2060
2145
|
}
|
|
2061
2146
|
};
|
|
2147
|
+
function isGameDataObject(value) {
|
|
2148
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2149
|
+
}
|
|
2150
|
+
function toNumber(value) {
|
|
2151
|
+
return typeof value === "number" ? value : 0;
|
|
2152
|
+
}
|
|
2153
|
+
function applyOperation(operation, current, operand) {
|
|
2154
|
+
switch (operation) {
|
|
2155
|
+
case "set":
|
|
2156
|
+
return operand;
|
|
2157
|
+
case "add":
|
|
2158
|
+
return toNumber(current) + toNumber(operand);
|
|
2159
|
+
case "subtract":
|
|
2160
|
+
return toNumber(current) - toNumber(operand);
|
|
2161
|
+
case "multiply":
|
|
2162
|
+
return toNumber(current) * toNumber(operand);
|
|
2163
|
+
case "divide": {
|
|
2164
|
+
const divisor = toNumber(operand);
|
|
2165
|
+
if (divisor === 0) throw new Error("Cannot divide by zero");
|
|
2166
|
+
return toNumber(current) / divisor;
|
|
2167
|
+
}
|
|
2168
|
+
case "modulo": {
|
|
2169
|
+
const divisor = toNumber(operand);
|
|
2170
|
+
if (divisor === 0) throw new Error("Cannot modulo by zero");
|
|
2171
|
+
return toNumber(current) % divisor;
|
|
2172
|
+
}
|
|
2173
|
+
// For min/max, a missing current has nothing to compare against — adopt the operand.
|
|
2174
|
+
case "min":
|
|
2175
|
+
return typeof current === "number" ? Math.min(current, toNumber(operand)) : operand;
|
|
2176
|
+
case "max":
|
|
2177
|
+
return typeof current === "number" ? Math.max(current, toNumber(operand)) : operand;
|
|
2178
|
+
case "append": {
|
|
2179
|
+
const base = Array.isArray(current) ? [...current] : current === void 0 || current === null ? [] : [current];
|
|
2180
|
+
if (Array.isArray(operand)) {
|
|
2181
|
+
base.push(...operand);
|
|
2182
|
+
} else {
|
|
2183
|
+
base.push(operand);
|
|
2184
|
+
}
|
|
2185
|
+
return base;
|
|
2186
|
+
}
|
|
2187
|
+
default:
|
|
2188
|
+
return operand;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
function getAtPath(value, path) {
|
|
2192
|
+
let current = value;
|
|
2193
|
+
for (const segment of path.split(".")) {
|
|
2194
|
+
if (!isGameDataObject(current)) return void 0;
|
|
2195
|
+
current = current[segment];
|
|
2196
|
+
}
|
|
2197
|
+
return current;
|
|
2198
|
+
}
|
|
2199
|
+
function setAtPath(value, path, newValue) {
|
|
2200
|
+
const segments = path.split(".");
|
|
2201
|
+
const root = isGameDataObject(value) ? { ...value } : {};
|
|
2202
|
+
let cursor = root;
|
|
2203
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
2204
|
+
const segment = segments[i];
|
|
2205
|
+
const existing = cursor[segment];
|
|
2206
|
+
const next = isGameDataObject(existing) ? { ...existing } : {};
|
|
2207
|
+
cursor[segment] = next;
|
|
2208
|
+
cursor = next;
|
|
2209
|
+
}
|
|
2210
|
+
cursor[segments[segments.length - 1]] = newValue;
|
|
2211
|
+
return root;
|
|
2212
|
+
}
|
|
2213
|
+
var CrazyGamesStorageAdapter = class {
|
|
2214
|
+
constructor(service) {
|
|
2215
|
+
this.service = service;
|
|
2216
|
+
}
|
|
2217
|
+
getStorageKey(gameId, key) {
|
|
2218
|
+
return `${gameId}:${key}`;
|
|
2219
|
+
}
|
|
2220
|
+
async saveGameData(gameId, key, value, operation, path) {
|
|
2221
|
+
const storageKey = this.getStorageKey(gameId, key);
|
|
2222
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2223
|
+
const existingRaw = await this.service.dataGetItem(storageKey);
|
|
2224
|
+
const existing = existingRaw ? JSON.parse(existingRaw) : null;
|
|
2225
|
+
const createdAt = existing?.created_at ?? now;
|
|
2226
|
+
const useOperation = operation !== void 0;
|
|
2227
|
+
let storedValue;
|
|
2228
|
+
let opResult;
|
|
2229
|
+
if (path) {
|
|
2230
|
+
const current = getAtPath(existing?.value, path);
|
|
2231
|
+
const next = useOperation ? applyOperation(operation, current, value) : value;
|
|
2232
|
+
storedValue = setAtPath(existing?.value, path, next);
|
|
2233
|
+
opResult = next;
|
|
2234
|
+
} else {
|
|
2235
|
+
const current = existing?.value;
|
|
2236
|
+
const next = useOperation ? applyOperation(operation, current, value) : value;
|
|
2237
|
+
storedValue = next;
|
|
2238
|
+
opResult = next;
|
|
2239
|
+
}
|
|
2240
|
+
const item = {
|
|
2241
|
+
key,
|
|
2242
|
+
value: storedValue,
|
|
2243
|
+
created_at: createdAt,
|
|
2244
|
+
updated_at: now
|
|
2245
|
+
};
|
|
2246
|
+
await this.service.dataSetItem(storageKey, JSON.stringify(item));
|
|
2247
|
+
const response = { success: true, message: "Data saved successfully" };
|
|
2248
|
+
if (useOperation && operation !== "set") {
|
|
2249
|
+
response.result = opResult;
|
|
2250
|
+
}
|
|
2251
|
+
return response;
|
|
2252
|
+
}
|
|
2253
|
+
async batchSaveGameData(gameId, items) {
|
|
2254
|
+
const results = [];
|
|
2255
|
+
let usedOperation = false;
|
|
2256
|
+
for (const item of items) {
|
|
2257
|
+
if (item.operation !== void 0) usedOperation = true;
|
|
2258
|
+
try {
|
|
2259
|
+
const saved = await this.saveGameData(gameId, item.key, item.value, item.operation, item.path);
|
|
2260
|
+
results.push({ key: item.key, success: true, ...saved.result !== void 0 && { result: saved.result } });
|
|
2261
|
+
} catch (error) {
|
|
2262
|
+
results.push({
|
|
2263
|
+
key: item.key,
|
|
2264
|
+
success: false,
|
|
2265
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
const allSucceeded = results.every((r) => r.success);
|
|
2270
|
+
return {
|
|
2271
|
+
success: allSucceeded,
|
|
2272
|
+
message: `${results.filter((r) => r.success).length}/${items.length} items saved`,
|
|
2273
|
+
...usedOperation && { results }
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
async getGameData(gameId, key) {
|
|
2277
|
+
const raw = await this.service.dataGetItem(this.getStorageKey(gameId, key));
|
|
2278
|
+
return raw ? JSON.parse(raw) : null;
|
|
2279
|
+
}
|
|
2280
|
+
async getMultipleGameData(gameId, keys) {
|
|
2281
|
+
const items = [];
|
|
2282
|
+
for (const key of keys) {
|
|
2283
|
+
const item = await this.getGameData(gameId, key);
|
|
2284
|
+
if (item) items.push(item);
|
|
2285
|
+
}
|
|
2286
|
+
return items;
|
|
2287
|
+
}
|
|
2288
|
+
async deleteGameData(gameId, key) {
|
|
2289
|
+
const storageKey = this.getStorageKey(gameId, key);
|
|
2290
|
+
const existing = await this.service.dataGetItem(storageKey);
|
|
2291
|
+
if (existing === null) return false;
|
|
2292
|
+
await this.service.dataRemoveItem(storageKey);
|
|
2293
|
+
return true;
|
|
2294
|
+
}
|
|
2295
|
+
async deleteMultipleGameData(gameId, keys) {
|
|
2296
|
+
let count = 0;
|
|
2297
|
+
for (const key of keys) {
|
|
2298
|
+
if (await this.deleteGameData(gameId, key)) count++;
|
|
2299
|
+
}
|
|
2300
|
+
return count;
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2062
2303
|
|
|
2063
2304
|
// src/core/client.ts
|
|
2064
2305
|
function determineEnvironmentFromParentUrl() {
|
|
@@ -2105,6 +2346,11 @@ var HyveClient = class {
|
|
|
2105
2346
|
storageMode;
|
|
2106
2347
|
cloudStorageAdapter;
|
|
2107
2348
|
localStorageAdapter;
|
|
2349
|
+
crazyGamesStorageAdapter = null;
|
|
2350
|
+
partnerApiKey = null;
|
|
2351
|
+
partnerApiBaseUrl = null;
|
|
2352
|
+
/** Raw (unprefixed) CrazyGames __dangerousUserId, seeded at init and on login. */
|
|
2353
|
+
crazyGamesUserId = null;
|
|
2108
2354
|
/**
|
|
2109
2355
|
* Creates a new HyveClient instance
|
|
2110
2356
|
* @param config Optional configuration including telemetry and ads
|
|
@@ -2132,10 +2378,17 @@ var HyveClient = class {
|
|
|
2132
2378
|
return success;
|
|
2133
2379
|
});
|
|
2134
2380
|
}
|
|
2381
|
+
this.partnerApiKey = config?.partnerApiKey ?? null;
|
|
2382
|
+
this.partnerApiBaseUrl = config?.partnerApiBaseUrl ?? null;
|
|
2383
|
+
this.gameId = config?.gameId ?? null;
|
|
2135
2384
|
if (typeof window !== "undefined" && CrazyGamesService.isCrazyGamesDomain()) {
|
|
2136
2385
|
this.crazyGamesService = new CrazyGamesService();
|
|
2137
|
-
this.
|
|
2386
|
+
this.crazyGamesStorageAdapter = new CrazyGamesStorageAdapter(this.crazyGamesService);
|
|
2387
|
+
this.crazyGamesInitPromise = this.crazyGamesService.initialize().then(async (success) => {
|
|
2138
2388
|
logger.info("CrazyGames SDK initialized:", success);
|
|
2389
|
+
if (success) {
|
|
2390
|
+
await this.seedCrazyGamesUser();
|
|
2391
|
+
}
|
|
2139
2392
|
return success;
|
|
2140
2393
|
});
|
|
2141
2394
|
}
|
|
@@ -2172,6 +2425,7 @@ var HyveClient = class {
|
|
|
2172
2425
|
!!config?.billing && Object.keys(config.billing).length > 0
|
|
2173
2426
|
);
|
|
2174
2427
|
logger.info("Storage mode:", this.storageMode);
|
|
2428
|
+
logger.info("Game ID:", this.gameId ?? "not set");
|
|
2175
2429
|
logger.info("Authenticated:", this.jwtToken !== null);
|
|
2176
2430
|
logger.debug("Config:", {
|
|
2177
2431
|
isDev: this.telemetryConfig.isDev,
|
|
@@ -2231,6 +2485,16 @@ var HyveClient = class {
|
|
|
2231
2485
|
* @returns Promise resolving to boolean indicating success
|
|
2232
2486
|
*/
|
|
2233
2487
|
async sendTelemetry(eventLocation, eventCategory, eventAction, eventSubCategory, eventSubAction, eventDetails, platformId) {
|
|
2488
|
+
if (this.partnerApiKey) {
|
|
2489
|
+
return this.sendPartnerTelemetry(
|
|
2490
|
+
eventLocation,
|
|
2491
|
+
eventCategory,
|
|
2492
|
+
eventAction,
|
|
2493
|
+
eventSubCategory,
|
|
2494
|
+
eventSubAction,
|
|
2495
|
+
eventDetails
|
|
2496
|
+
);
|
|
2497
|
+
}
|
|
2234
2498
|
if (!this.jwtToken) {
|
|
2235
2499
|
logger.error("JWT token required. Ensure hyve-access and game-id are present in the URL.");
|
|
2236
2500
|
return false;
|
|
@@ -2240,42 +2504,11 @@ var HyveClient = class {
|
|
|
2240
2504
|
return false;
|
|
2241
2505
|
}
|
|
2242
2506
|
try {
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
JSON.parse(eventDetails);
|
|
2247
|
-
} else if (typeof eventDetails === "object") {
|
|
2248
|
-
JSON.stringify(eventDetails);
|
|
2249
|
-
}
|
|
2250
|
-
} catch (validationError) {
|
|
2251
|
-
logger.error("Invalid JSON in eventDetails:", validationError);
|
|
2252
|
-
logger.error("eventDetails value:", eventDetails);
|
|
2253
|
-
return false;
|
|
2254
|
-
}
|
|
2255
|
-
}
|
|
2256
|
-
let parsedEventDetails = {};
|
|
2257
|
-
if (eventDetails) {
|
|
2258
|
-
if (typeof eventDetails === "string") {
|
|
2259
|
-
try {
|
|
2260
|
-
parsedEventDetails = JSON.parse(eventDetails);
|
|
2261
|
-
} catch {
|
|
2262
|
-
}
|
|
2263
|
-
} else {
|
|
2264
|
-
parsedEventDetails = eventDetails;
|
|
2265
|
-
}
|
|
2507
|
+
const enrichedEventDetails = this.enrichEventDetails(eventDetails);
|
|
2508
|
+
if (enrichedEventDetails === null) {
|
|
2509
|
+
return false;
|
|
2266
2510
|
}
|
|
2267
2511
|
const attribution = getAttributionData();
|
|
2268
|
-
const enrichedEventDetails = {
|
|
2269
|
-
// Device info
|
|
2270
|
-
...getEssentialDeviceInfo(),
|
|
2271
|
-
// Attribution data
|
|
2272
|
-
...attribution,
|
|
2273
|
-
// Timestamp and session context
|
|
2274
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2275
|
-
session_id: this.sessionId,
|
|
2276
|
-
// Event-specific details can override any of the above
|
|
2277
|
-
...parsedEventDetails
|
|
2278
|
-
};
|
|
2279
2512
|
const telemetryEvent = {
|
|
2280
2513
|
game_id: this.gameId,
|
|
2281
2514
|
session_id: this.sessionId,
|
|
@@ -2314,6 +2547,122 @@ var HyveClient = class {
|
|
|
2314
2547
|
return false;
|
|
2315
2548
|
}
|
|
2316
2549
|
}
|
|
2550
|
+
/**
|
|
2551
|
+
* Validates and enriches user-provided event details with device info,
|
|
2552
|
+
* attribution data, and session context — matching the enrichment
|
|
2553
|
+
* platform-v2 applies in sendAnalyticsEvent. Shared by the JWT and partner
|
|
2554
|
+
* telemetry paths.
|
|
2555
|
+
* @returns Enriched details object, or null if eventDetails is not valid JSON.
|
|
2556
|
+
*/
|
|
2557
|
+
enrichEventDetails(eventDetails) {
|
|
2558
|
+
let parsedEventDetails = {};
|
|
2559
|
+
if (eventDetails) {
|
|
2560
|
+
try {
|
|
2561
|
+
if (typeof eventDetails === "string") {
|
|
2562
|
+
parsedEventDetails = JSON.parse(eventDetails);
|
|
2563
|
+
} else if (typeof eventDetails === "object") {
|
|
2564
|
+
JSON.stringify(eventDetails);
|
|
2565
|
+
parsedEventDetails = eventDetails;
|
|
2566
|
+
}
|
|
2567
|
+
} catch (validationError) {
|
|
2568
|
+
logger.error("Invalid JSON in eventDetails:", validationError);
|
|
2569
|
+
logger.error("eventDetails value:", eventDetails);
|
|
2570
|
+
return null;
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
const attribution = getAttributionData();
|
|
2574
|
+
return {
|
|
2575
|
+
// Device info
|
|
2576
|
+
...getEssentialDeviceInfo(),
|
|
2577
|
+
// Attribution data
|
|
2578
|
+
...attribution,
|
|
2579
|
+
// Timestamp and session context
|
|
2580
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2581
|
+
session_id: this.sessionId,
|
|
2582
|
+
// Event-specific details can override any of the above
|
|
2583
|
+
...parsedEventDetails
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
/**
|
|
2587
|
+
* Sends a telemetry event via the partner analytics endpoint using a partner
|
|
2588
|
+
* API key (x-api-key) instead of a hyve JWT. Used by externally-hosted games
|
|
2589
|
+
* such as CrazyGames. game_id is derived from the API key server-side and is
|
|
2590
|
+
* therefore not sent. hyve_user_id carries the `cg:`-prefixed CrazyGames id,
|
|
2591
|
+
* or a guest sentinel when the player is not logged in.
|
|
2592
|
+
*/
|
|
2593
|
+
async sendPartnerTelemetry(eventLocation, eventCategory, eventAction, eventSubCategory, eventSubAction, eventDetails) {
|
|
2594
|
+
if (!this.partnerApiKey) {
|
|
2595
|
+
logger.error("Partner API key required for partner telemetry path.");
|
|
2596
|
+
return false;
|
|
2597
|
+
}
|
|
2598
|
+
if (this.crazyGamesService && this.crazyGamesInitPromise) {
|
|
2599
|
+
await this.crazyGamesInitPromise;
|
|
2600
|
+
}
|
|
2601
|
+
try {
|
|
2602
|
+
const enrichedEventDetails = this.enrichEventDetails(eventDetails);
|
|
2603
|
+
if (enrichedEventDetails === null) {
|
|
2604
|
+
return false;
|
|
2605
|
+
}
|
|
2606
|
+
const hyveUserId = this.crazyGamesUserId ? `cg:${this.crazyGamesUserId}` : "cg:guest";
|
|
2607
|
+
const partnerEvent = {
|
|
2608
|
+
session_id: this.sessionId,
|
|
2609
|
+
hyve_user_id: hyveUserId,
|
|
2610
|
+
event_location: eventLocation,
|
|
2611
|
+
event_category: eventCategory,
|
|
2612
|
+
event_sub_category: eventSubCategory || null,
|
|
2613
|
+
event_action: eventAction,
|
|
2614
|
+
event_sub_action: eventSubAction || null,
|
|
2615
|
+
event_details: enrichedEventDetails
|
|
2616
|
+
};
|
|
2617
|
+
logger.debug("Sending partner telemetry event:", partnerEvent);
|
|
2618
|
+
const base = this.partnerApiBaseUrl || this.apiBaseUrl;
|
|
2619
|
+
const telemetryUrl = `${base}/api/v1/partners/analytics/events`;
|
|
2620
|
+
const response = await fetch(telemetryUrl, {
|
|
2621
|
+
method: "POST",
|
|
2622
|
+
headers: {
|
|
2623
|
+
"Content-Type": "application/json",
|
|
2624
|
+
"x-api-key": this.partnerApiKey
|
|
2625
|
+
},
|
|
2626
|
+
body: JSON.stringify(partnerEvent)
|
|
2627
|
+
});
|
|
2628
|
+
if (response.ok) {
|
|
2629
|
+
logger.info("Partner telemetry event sent successfully:", response.status);
|
|
2630
|
+
return true;
|
|
2631
|
+
} else {
|
|
2632
|
+
const errorText = await response.text();
|
|
2633
|
+
logger.error(
|
|
2634
|
+
"Failed to send partner telemetry event:",
|
|
2635
|
+
response.status,
|
|
2636
|
+
errorText
|
|
2637
|
+
);
|
|
2638
|
+
return false;
|
|
2639
|
+
}
|
|
2640
|
+
} catch (error) {
|
|
2641
|
+
logger.error("Error sending partner telemetry event:", error);
|
|
2642
|
+
return false;
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2646
|
+
* Seeds the CrazyGames user id used for telemetry attribution and registers
|
|
2647
|
+
* an auth listener so the id updates if a guest logs in mid-session.
|
|
2648
|
+
* The id is client-asserted (spoofable) and used only as an attribution label.
|
|
2649
|
+
*/
|
|
2650
|
+
async seedCrazyGamesUser() {
|
|
2651
|
+
if (!this.crazyGamesService) return;
|
|
2652
|
+
if (!this.crazyGamesService.isUserAccountAvailable()) {
|
|
2653
|
+
logger.info("CrazyGames account system unavailable \u2014 telemetry attributed by session only");
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
const user = await this.crazyGamesService.getUser();
|
|
2657
|
+
this.crazyGamesUserId = user?.__dangerousUserId ?? null;
|
|
2658
|
+
if (this.crazyGamesUserId) {
|
|
2659
|
+
logger.info("CrazyGames user id seeded for telemetry attribution");
|
|
2660
|
+
}
|
|
2661
|
+
this.crazyGamesService.addAuthListener((updatedUser) => {
|
|
2662
|
+
this.crazyGamesUserId = updatedUser?.__dangerousUserId ?? null;
|
|
2663
|
+
logger.info("CrazyGames auth state changed; telemetry user id updated");
|
|
2664
|
+
});
|
|
2665
|
+
}
|
|
2317
2666
|
/**
|
|
2318
2667
|
* Required lifecycle telemetry — Session start.
|
|
2319
2668
|
* See https://docs.hyve.gg/docs/telemetry#required-lifecycle-events
|
|
@@ -2534,12 +2883,28 @@ var HyveClient = class {
|
|
|
2534
2883
|
logger.info("Client reset with new sessionId:", this.sessionId);
|
|
2535
2884
|
}
|
|
2536
2885
|
/**
|
|
2537
|
-
* Get the storage adapter based on mode
|
|
2886
|
+
* Get the storage adapter based on mode.
|
|
2887
|
+
*
|
|
2888
|
+
* Selection order:
|
|
2889
|
+
* 1. An explicit `mode` override ('cloud' | 'local') always wins.
|
|
2890
|
+
* 2. On the CrazyGames platform, the CrazyGames data store is auto-selected
|
|
2891
|
+
* (D3 — public storageMode type is preserved; this is chosen internally).
|
|
2892
|
+
* 3. Otherwise the configured storageMode is used.
|
|
2893
|
+
*
|
|
2894
|
+
* Awaits CrazyGames SDK initialization so the data store is ready before use.
|
|
2538
2895
|
* @param mode Storage mode override (cloud or local)
|
|
2539
2896
|
*/
|
|
2540
|
-
getStorageAdapter(mode) {
|
|
2541
|
-
|
|
2542
|
-
|
|
2897
|
+
async getStorageAdapter(mode) {
|
|
2898
|
+
if (mode) {
|
|
2899
|
+
return mode === "local" ? this.localStorageAdapter : this.cloudStorageAdapter;
|
|
2900
|
+
}
|
|
2901
|
+
if (this.crazyGamesService && this.crazyGamesStorageAdapter) {
|
|
2902
|
+
if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
|
|
2903
|
+
if (this.crazyGamesService.getEnvironment() === "crazygames") {
|
|
2904
|
+
return this.crazyGamesStorageAdapter;
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
return this.storageMode === "local" ? this.localStorageAdapter : this.cloudStorageAdapter;
|
|
2543
2908
|
}
|
|
2544
2909
|
/**
|
|
2545
2910
|
* Returns the current game ID or throws if not available.
|
|
@@ -2563,7 +2928,7 @@ var HyveClient = class {
|
|
|
2563
2928
|
const gameId = this.requireGameId();
|
|
2564
2929
|
const storageMode = storage || this.storageMode;
|
|
2565
2930
|
logger.debug(`Saving game data to ${storageMode}: ${gameId}/${key}`);
|
|
2566
|
-
const adapter = this.getStorageAdapter(storage);
|
|
2931
|
+
const adapter = await this.getStorageAdapter(storage);
|
|
2567
2932
|
const response = await adapter.saveGameData(gameId, key, value, operation, path);
|
|
2568
2933
|
logger.info(`Game data saved successfully to ${storageMode}: ${gameId}/${key}`);
|
|
2569
2934
|
return response;
|
|
@@ -2578,7 +2943,7 @@ var HyveClient = class {
|
|
|
2578
2943
|
const gameId = this.requireGameId();
|
|
2579
2944
|
const storageMode = storage || this.storageMode;
|
|
2580
2945
|
logger.debug(`Batch saving ${items.length} game data entries to ${storageMode} for game: ${gameId}`);
|
|
2581
|
-
const adapter = this.getStorageAdapter(storage);
|
|
2946
|
+
const adapter = await this.getStorageAdapter(storage);
|
|
2582
2947
|
const response = await adapter.batchSaveGameData(gameId, items);
|
|
2583
2948
|
logger.info(`Batch saved ${items.length} game data entries successfully to ${storageMode}`);
|
|
2584
2949
|
return response;
|
|
@@ -2593,7 +2958,7 @@ var HyveClient = class {
|
|
|
2593
2958
|
const gameId = this.requireGameId();
|
|
2594
2959
|
const storageMode = storage || this.storageMode;
|
|
2595
2960
|
logger.debug(`Getting game data from ${storageMode}: ${gameId}/${key}`);
|
|
2596
|
-
const adapter = this.getStorageAdapter(storage);
|
|
2961
|
+
const adapter = await this.getStorageAdapter(storage);
|
|
2597
2962
|
const data = await adapter.getGameData(gameId, key);
|
|
2598
2963
|
if (data) {
|
|
2599
2964
|
logger.info(`Game data retrieved successfully from ${storageMode}: ${gameId}/${key}`);
|
|
@@ -2612,7 +2977,7 @@ var HyveClient = class {
|
|
|
2612
2977
|
const gameId = this.requireGameId();
|
|
2613
2978
|
const storageMode = storage || this.storageMode;
|
|
2614
2979
|
logger.debug(`Getting ${keys.length} game data entries from ${storageMode} for game: ${gameId}`);
|
|
2615
|
-
const adapter = this.getStorageAdapter(storage);
|
|
2980
|
+
const adapter = await this.getStorageAdapter(storage);
|
|
2616
2981
|
const data = await adapter.getMultipleGameData(gameId, keys);
|
|
2617
2982
|
logger.info(`Retrieved ${data.length} game data entries from ${storageMode}`);
|
|
2618
2983
|
return data;
|
|
@@ -2627,7 +2992,7 @@ var HyveClient = class {
|
|
|
2627
2992
|
const gameId = this.requireGameId();
|
|
2628
2993
|
const storageMode = storage || this.storageMode;
|
|
2629
2994
|
logger.debug(`Deleting game data from ${storageMode}: ${gameId}/${key}`);
|
|
2630
|
-
const adapter = this.getStorageAdapter(storage);
|
|
2995
|
+
const adapter = await this.getStorageAdapter(storage);
|
|
2631
2996
|
const deleted = await adapter.deleteGameData(gameId, key);
|
|
2632
2997
|
if (deleted) {
|
|
2633
2998
|
logger.info(`Game data deleted successfully from ${storageMode}: ${gameId}/${key}`);
|
|
@@ -2646,7 +3011,7 @@ var HyveClient = class {
|
|
|
2646
3011
|
const gameId = this.requireGameId();
|
|
2647
3012
|
const storageMode = storage || this.storageMode;
|
|
2648
3013
|
logger.debug(`Deleting ${keys.length} game data entries from ${storageMode} for game: ${gameId}`);
|
|
2649
|
-
const adapter = this.getStorageAdapter(storage);
|
|
3014
|
+
const adapter = await this.getStorageAdapter(storage);
|
|
2650
3015
|
const deletedCount = await adapter.deleteMultipleGameData(gameId, keys);
|
|
2651
3016
|
logger.info(`Deleted ${deletedCount} game data entries from ${storageMode}`);
|
|
2652
3017
|
return deletedCount;
|
|
@@ -2865,6 +3230,7 @@ var HyveClient = class {
|
|
|
2865
3230
|
BillingService,
|
|
2866
3231
|
CloudStorageAdapter,
|
|
2867
3232
|
CrazyGamesService,
|
|
3233
|
+
CrazyGamesStorageAdapter,
|
|
2868
3234
|
HyveClient,
|
|
2869
3235
|
LocalStorageAdapter,
|
|
2870
3236
|
Logger,
|