@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/react.mjs
CHANGED
|
@@ -1177,6 +1177,7 @@ var PlaygamaService = class {
|
|
|
1177
1177
|
var CRAZYGAMES_SDK_CDN = "https://sdk.crazygames.com/crazygames-sdk-v2.js";
|
|
1178
1178
|
var CrazyGamesService = class {
|
|
1179
1179
|
initialized = false;
|
|
1180
|
+
environment = null;
|
|
1180
1181
|
/**
|
|
1181
1182
|
* Detects if the game is running on the CrazyGames platform.
|
|
1182
1183
|
* Games on CrazyGames run inside an iframe, so we check document.referrer
|
|
@@ -1218,6 +1219,7 @@ var CrazyGamesService = class {
|
|
|
1218
1219
|
logger.warn("[CrazyGamesService] Unexpected environment:", env);
|
|
1219
1220
|
return false;
|
|
1220
1221
|
}
|
|
1222
|
+
this.environment = env;
|
|
1221
1223
|
this.initialized = true;
|
|
1222
1224
|
return true;
|
|
1223
1225
|
} catch (error) {
|
|
@@ -1328,6 +1330,88 @@ var CrazyGamesService = class {
|
|
|
1328
1330
|
if (!this.initialized) return;
|
|
1329
1331
|
window.CrazyGames?.SDK.game.happytime();
|
|
1330
1332
|
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Returns the resolved CrazyGames environment ('crazygames' | 'local' |
|
|
1335
|
+
* 'disabled'), or null if the SDK has not initialized yet. Used to
|
|
1336
|
+
* auto-select the CrazyGames storage adapter.
|
|
1337
|
+
*/
|
|
1338
|
+
getEnvironment() {
|
|
1339
|
+
return this.environment;
|
|
1340
|
+
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Whether the CrazyGames account system is present (sync). When false,
|
|
1343
|
+
* there is no logged-in user concept and telemetry falls back to guests.
|
|
1344
|
+
*/
|
|
1345
|
+
isUserAccountAvailable() {
|
|
1346
|
+
if (!this.initialized) return false;
|
|
1347
|
+
return window.CrazyGames?.SDK.user.isUserAccountAvailable ?? false;
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Fetches the current CrazyGames user, or null if the player is not logged
|
|
1351
|
+
* in (guest). The returned `__dangerousUserId` is client-asserted and used
|
|
1352
|
+
* only as a telemetry attribution label.
|
|
1353
|
+
*/
|
|
1354
|
+
async getUser() {
|
|
1355
|
+
const sdk = window.CrazyGames?.SDK;
|
|
1356
|
+
if (!this.initialized || !sdk || !this.isUserAccountAvailable()) {
|
|
1357
|
+
return null;
|
|
1358
|
+
}
|
|
1359
|
+
try {
|
|
1360
|
+
return await sdk.user.getUser() ?? null;
|
|
1361
|
+
} catch (error) {
|
|
1362
|
+
logger.warn("[CrazyGamesService] getUser failed:", error);
|
|
1363
|
+
return null;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Registers a listener that fires when a guest logs in mid-session.
|
|
1368
|
+
* No-op if the SDK or account system is unavailable.
|
|
1369
|
+
*/
|
|
1370
|
+
addAuthListener(callback) {
|
|
1371
|
+
const sdk = window.CrazyGames?.SDK;
|
|
1372
|
+
if (!this.initialized || !sdk || !this.isUserAccountAvailable()) return;
|
|
1373
|
+
try {
|
|
1374
|
+
sdk.user.addAuthListener(callback);
|
|
1375
|
+
} catch (error) {
|
|
1376
|
+
logger.warn("[CrazyGamesService] addAuthListener failed:", error);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
/** Removes a previously registered auth listener. */
|
|
1380
|
+
removeAuthListener(callback) {
|
|
1381
|
+
const sdk = window.CrazyGames?.SDK;
|
|
1382
|
+
if (!this.initialized || !sdk) return;
|
|
1383
|
+
try {
|
|
1384
|
+
sdk.user.removeAuthListener(callback);
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
logger.warn("[CrazyGamesService] removeAuthListener failed:", error);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Reads a value from the CrazyGames data store. Returns null when the SDK
|
|
1391
|
+
* is unavailable or the key is absent.
|
|
1392
|
+
*/
|
|
1393
|
+
async dataGetItem(key) {
|
|
1394
|
+
const sdk = window.CrazyGames?.SDK;
|
|
1395
|
+
if (!this.initialized || !sdk) return null;
|
|
1396
|
+
const value = await sdk.data.getItem(key);
|
|
1397
|
+
return value ?? null;
|
|
1398
|
+
}
|
|
1399
|
+
/** Writes a value to the CrazyGames data store. */
|
|
1400
|
+
async dataSetItem(key, value) {
|
|
1401
|
+
const sdk = window.CrazyGames?.SDK;
|
|
1402
|
+
if (!this.initialized || !sdk) {
|
|
1403
|
+
throw new Error("CrazyGames SDK not initialized");
|
|
1404
|
+
}
|
|
1405
|
+
await sdk.data.setItem(key, value);
|
|
1406
|
+
}
|
|
1407
|
+
/** Removes a value from the CrazyGames data store. */
|
|
1408
|
+
async dataRemoveItem(key) {
|
|
1409
|
+
const sdk = window.CrazyGames?.SDK;
|
|
1410
|
+
if (!this.initialized || !sdk) {
|
|
1411
|
+
throw new Error("CrazyGames SDK not initialized");
|
|
1412
|
+
}
|
|
1413
|
+
await sdk.data.removeItem(key);
|
|
1414
|
+
}
|
|
1331
1415
|
loadScript() {
|
|
1332
1416
|
return new Promise((resolve, reject) => {
|
|
1333
1417
|
if (window.CrazyGames?.SDK) {
|
|
@@ -1989,6 +2073,162 @@ var LocalStorageAdapter = class {
|
|
|
1989
2073
|
return count;
|
|
1990
2074
|
}
|
|
1991
2075
|
};
|
|
2076
|
+
function isGameDataObject(value) {
|
|
2077
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2078
|
+
}
|
|
2079
|
+
function toNumber(value) {
|
|
2080
|
+
return typeof value === "number" ? value : 0;
|
|
2081
|
+
}
|
|
2082
|
+
function applyOperation(operation, current, operand) {
|
|
2083
|
+
switch (operation) {
|
|
2084
|
+
case "set":
|
|
2085
|
+
return operand;
|
|
2086
|
+
case "add":
|
|
2087
|
+
return toNumber(current) + toNumber(operand);
|
|
2088
|
+
case "subtract":
|
|
2089
|
+
return toNumber(current) - toNumber(operand);
|
|
2090
|
+
case "multiply":
|
|
2091
|
+
return toNumber(current) * toNumber(operand);
|
|
2092
|
+
case "divide": {
|
|
2093
|
+
const divisor = toNumber(operand);
|
|
2094
|
+
if (divisor === 0) throw new Error("Cannot divide by zero");
|
|
2095
|
+
return toNumber(current) / divisor;
|
|
2096
|
+
}
|
|
2097
|
+
case "modulo": {
|
|
2098
|
+
const divisor = toNumber(operand);
|
|
2099
|
+
if (divisor === 0) throw new Error("Cannot modulo by zero");
|
|
2100
|
+
return toNumber(current) % divisor;
|
|
2101
|
+
}
|
|
2102
|
+
// For min/max, a missing current has nothing to compare against — adopt the operand.
|
|
2103
|
+
case "min":
|
|
2104
|
+
return typeof current === "number" ? Math.min(current, toNumber(operand)) : operand;
|
|
2105
|
+
case "max":
|
|
2106
|
+
return typeof current === "number" ? Math.max(current, toNumber(operand)) : operand;
|
|
2107
|
+
case "append": {
|
|
2108
|
+
const base = Array.isArray(current) ? [...current] : current === void 0 || current === null ? [] : [current];
|
|
2109
|
+
if (Array.isArray(operand)) {
|
|
2110
|
+
base.push(...operand);
|
|
2111
|
+
} else {
|
|
2112
|
+
base.push(operand);
|
|
2113
|
+
}
|
|
2114
|
+
return base;
|
|
2115
|
+
}
|
|
2116
|
+
default:
|
|
2117
|
+
return operand;
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
function getAtPath(value, path) {
|
|
2121
|
+
let current = value;
|
|
2122
|
+
for (const segment of path.split(".")) {
|
|
2123
|
+
if (!isGameDataObject(current)) return void 0;
|
|
2124
|
+
current = current[segment];
|
|
2125
|
+
}
|
|
2126
|
+
return current;
|
|
2127
|
+
}
|
|
2128
|
+
function setAtPath(value, path, newValue) {
|
|
2129
|
+
const segments = path.split(".");
|
|
2130
|
+
const root = isGameDataObject(value) ? { ...value } : {};
|
|
2131
|
+
let cursor = root;
|
|
2132
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
2133
|
+
const segment = segments[i];
|
|
2134
|
+
const existing = cursor[segment];
|
|
2135
|
+
const next = isGameDataObject(existing) ? { ...existing } : {};
|
|
2136
|
+
cursor[segment] = next;
|
|
2137
|
+
cursor = next;
|
|
2138
|
+
}
|
|
2139
|
+
cursor[segments[segments.length - 1]] = newValue;
|
|
2140
|
+
return root;
|
|
2141
|
+
}
|
|
2142
|
+
var CrazyGamesStorageAdapter = class {
|
|
2143
|
+
constructor(service) {
|
|
2144
|
+
this.service = service;
|
|
2145
|
+
}
|
|
2146
|
+
getStorageKey(gameId, key) {
|
|
2147
|
+
return `${gameId}:${key}`;
|
|
2148
|
+
}
|
|
2149
|
+
async saveGameData(gameId, key, value, operation, path) {
|
|
2150
|
+
const storageKey = this.getStorageKey(gameId, key);
|
|
2151
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2152
|
+
const existingRaw = await this.service.dataGetItem(storageKey);
|
|
2153
|
+
const existing = existingRaw ? JSON.parse(existingRaw) : null;
|
|
2154
|
+
const createdAt = existing?.created_at ?? now;
|
|
2155
|
+
const useOperation = operation !== void 0;
|
|
2156
|
+
let storedValue;
|
|
2157
|
+
let opResult;
|
|
2158
|
+
if (path) {
|
|
2159
|
+
const current = getAtPath(existing?.value, path);
|
|
2160
|
+
const next = useOperation ? applyOperation(operation, current, value) : value;
|
|
2161
|
+
storedValue = setAtPath(existing?.value, path, next);
|
|
2162
|
+
opResult = next;
|
|
2163
|
+
} else {
|
|
2164
|
+
const current = existing?.value;
|
|
2165
|
+
const next = useOperation ? applyOperation(operation, current, value) : value;
|
|
2166
|
+
storedValue = next;
|
|
2167
|
+
opResult = next;
|
|
2168
|
+
}
|
|
2169
|
+
const item = {
|
|
2170
|
+
key,
|
|
2171
|
+
value: storedValue,
|
|
2172
|
+
created_at: createdAt,
|
|
2173
|
+
updated_at: now
|
|
2174
|
+
};
|
|
2175
|
+
await this.service.dataSetItem(storageKey, JSON.stringify(item));
|
|
2176
|
+
const response = { success: true, message: "Data saved successfully" };
|
|
2177
|
+
if (useOperation && operation !== "set") {
|
|
2178
|
+
response.result = opResult;
|
|
2179
|
+
}
|
|
2180
|
+
return response;
|
|
2181
|
+
}
|
|
2182
|
+
async batchSaveGameData(gameId, items) {
|
|
2183
|
+
const results = [];
|
|
2184
|
+
let usedOperation = false;
|
|
2185
|
+
for (const item of items) {
|
|
2186
|
+
if (item.operation !== void 0) usedOperation = true;
|
|
2187
|
+
try {
|
|
2188
|
+
const saved = await this.saveGameData(gameId, item.key, item.value, item.operation, item.path);
|
|
2189
|
+
results.push({ key: item.key, success: true, ...saved.result !== void 0 && { result: saved.result } });
|
|
2190
|
+
} catch (error) {
|
|
2191
|
+
results.push({
|
|
2192
|
+
key: item.key,
|
|
2193
|
+
success: false,
|
|
2194
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
const allSucceeded = results.every((r) => r.success);
|
|
2199
|
+
return {
|
|
2200
|
+
success: allSucceeded,
|
|
2201
|
+
message: `${results.filter((r) => r.success).length}/${items.length} items saved`,
|
|
2202
|
+
...usedOperation && { results }
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
async getGameData(gameId, key) {
|
|
2206
|
+
const raw = await this.service.dataGetItem(this.getStorageKey(gameId, key));
|
|
2207
|
+
return raw ? JSON.parse(raw) : null;
|
|
2208
|
+
}
|
|
2209
|
+
async getMultipleGameData(gameId, keys) {
|
|
2210
|
+
const items = [];
|
|
2211
|
+
for (const key of keys) {
|
|
2212
|
+
const item = await this.getGameData(gameId, key);
|
|
2213
|
+
if (item) items.push(item);
|
|
2214
|
+
}
|
|
2215
|
+
return items;
|
|
2216
|
+
}
|
|
2217
|
+
async deleteGameData(gameId, key) {
|
|
2218
|
+
const storageKey = this.getStorageKey(gameId, key);
|
|
2219
|
+
const existing = await this.service.dataGetItem(storageKey);
|
|
2220
|
+
if (existing === null) return false;
|
|
2221
|
+
await this.service.dataRemoveItem(storageKey);
|
|
2222
|
+
return true;
|
|
2223
|
+
}
|
|
2224
|
+
async deleteMultipleGameData(gameId, keys) {
|
|
2225
|
+
let count = 0;
|
|
2226
|
+
for (const key of keys) {
|
|
2227
|
+
if (await this.deleteGameData(gameId, key)) count++;
|
|
2228
|
+
}
|
|
2229
|
+
return count;
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
1992
2232
|
|
|
1993
2233
|
// src/core/client.ts
|
|
1994
2234
|
function determineEnvironmentFromParentUrl() {
|
|
@@ -2035,6 +2275,11 @@ var HyveClient = class {
|
|
|
2035
2275
|
storageMode;
|
|
2036
2276
|
cloudStorageAdapter;
|
|
2037
2277
|
localStorageAdapter;
|
|
2278
|
+
crazyGamesStorageAdapter = null;
|
|
2279
|
+
partnerApiKey = null;
|
|
2280
|
+
partnerApiBaseUrl = null;
|
|
2281
|
+
/** Raw (unprefixed) CrazyGames __dangerousUserId, seeded at init and on login. */
|
|
2282
|
+
crazyGamesUserId = null;
|
|
2038
2283
|
/**
|
|
2039
2284
|
* Creates a new HyveClient instance
|
|
2040
2285
|
* @param config Optional configuration including telemetry and ads
|
|
@@ -2062,10 +2307,17 @@ var HyveClient = class {
|
|
|
2062
2307
|
return success;
|
|
2063
2308
|
});
|
|
2064
2309
|
}
|
|
2310
|
+
this.partnerApiKey = config?.partnerApiKey ?? null;
|
|
2311
|
+
this.partnerApiBaseUrl = config?.partnerApiBaseUrl ?? null;
|
|
2312
|
+
this.gameId = config?.gameId ?? null;
|
|
2065
2313
|
if (typeof window !== "undefined" && CrazyGamesService.isCrazyGamesDomain()) {
|
|
2066
2314
|
this.crazyGamesService = new CrazyGamesService();
|
|
2067
|
-
this.
|
|
2315
|
+
this.crazyGamesStorageAdapter = new CrazyGamesStorageAdapter(this.crazyGamesService);
|
|
2316
|
+
this.crazyGamesInitPromise = this.crazyGamesService.initialize().then(async (success) => {
|
|
2068
2317
|
logger.info("CrazyGames SDK initialized:", success);
|
|
2318
|
+
if (success) {
|
|
2319
|
+
await this.seedCrazyGamesUser();
|
|
2320
|
+
}
|
|
2069
2321
|
return success;
|
|
2070
2322
|
});
|
|
2071
2323
|
}
|
|
@@ -2102,6 +2354,7 @@ var HyveClient = class {
|
|
|
2102
2354
|
!!config?.billing && Object.keys(config.billing).length > 0
|
|
2103
2355
|
);
|
|
2104
2356
|
logger.info("Storage mode:", this.storageMode);
|
|
2357
|
+
logger.info("Game ID:", this.gameId ?? "not set");
|
|
2105
2358
|
logger.info("Authenticated:", this.jwtToken !== null);
|
|
2106
2359
|
logger.debug("Config:", {
|
|
2107
2360
|
isDev: this.telemetryConfig.isDev,
|
|
@@ -2161,6 +2414,16 @@ var HyveClient = class {
|
|
|
2161
2414
|
* @returns Promise resolving to boolean indicating success
|
|
2162
2415
|
*/
|
|
2163
2416
|
async sendTelemetry(eventLocation, eventCategory, eventAction, eventSubCategory, eventSubAction, eventDetails, platformId) {
|
|
2417
|
+
if (this.partnerApiKey) {
|
|
2418
|
+
return this.sendPartnerTelemetry(
|
|
2419
|
+
eventLocation,
|
|
2420
|
+
eventCategory,
|
|
2421
|
+
eventAction,
|
|
2422
|
+
eventSubCategory,
|
|
2423
|
+
eventSubAction,
|
|
2424
|
+
eventDetails
|
|
2425
|
+
);
|
|
2426
|
+
}
|
|
2164
2427
|
if (!this.jwtToken) {
|
|
2165
2428
|
logger.error("JWT token required. Ensure hyve-access and game-id are present in the URL.");
|
|
2166
2429
|
return false;
|
|
@@ -2170,42 +2433,11 @@ var HyveClient = class {
|
|
|
2170
2433
|
return false;
|
|
2171
2434
|
}
|
|
2172
2435
|
try {
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
JSON.parse(eventDetails);
|
|
2177
|
-
} else if (typeof eventDetails === "object") {
|
|
2178
|
-
JSON.stringify(eventDetails);
|
|
2179
|
-
}
|
|
2180
|
-
} catch (validationError) {
|
|
2181
|
-
logger.error("Invalid JSON in eventDetails:", validationError);
|
|
2182
|
-
logger.error("eventDetails value:", eventDetails);
|
|
2183
|
-
return false;
|
|
2184
|
-
}
|
|
2185
|
-
}
|
|
2186
|
-
let parsedEventDetails = {};
|
|
2187
|
-
if (eventDetails) {
|
|
2188
|
-
if (typeof eventDetails === "string") {
|
|
2189
|
-
try {
|
|
2190
|
-
parsedEventDetails = JSON.parse(eventDetails);
|
|
2191
|
-
} catch {
|
|
2192
|
-
}
|
|
2193
|
-
} else {
|
|
2194
|
-
parsedEventDetails = eventDetails;
|
|
2195
|
-
}
|
|
2436
|
+
const enrichedEventDetails = this.enrichEventDetails(eventDetails);
|
|
2437
|
+
if (enrichedEventDetails === null) {
|
|
2438
|
+
return false;
|
|
2196
2439
|
}
|
|
2197
2440
|
const attribution = getAttributionData();
|
|
2198
|
-
const enrichedEventDetails = {
|
|
2199
|
-
// Device info
|
|
2200
|
-
...getEssentialDeviceInfo(),
|
|
2201
|
-
// Attribution data
|
|
2202
|
-
...attribution,
|
|
2203
|
-
// Timestamp and session context
|
|
2204
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2205
|
-
session_id: this.sessionId,
|
|
2206
|
-
// Event-specific details can override any of the above
|
|
2207
|
-
...parsedEventDetails
|
|
2208
|
-
};
|
|
2209
2441
|
const telemetryEvent = {
|
|
2210
2442
|
game_id: this.gameId,
|
|
2211
2443
|
session_id: this.sessionId,
|
|
@@ -2244,6 +2476,122 @@ var HyveClient = class {
|
|
|
2244
2476
|
return false;
|
|
2245
2477
|
}
|
|
2246
2478
|
}
|
|
2479
|
+
/**
|
|
2480
|
+
* Validates and enriches user-provided event details with device info,
|
|
2481
|
+
* attribution data, and session context — matching the enrichment
|
|
2482
|
+
* platform-v2 applies in sendAnalyticsEvent. Shared by the JWT and partner
|
|
2483
|
+
* telemetry paths.
|
|
2484
|
+
* @returns Enriched details object, or null if eventDetails is not valid JSON.
|
|
2485
|
+
*/
|
|
2486
|
+
enrichEventDetails(eventDetails) {
|
|
2487
|
+
let parsedEventDetails = {};
|
|
2488
|
+
if (eventDetails) {
|
|
2489
|
+
try {
|
|
2490
|
+
if (typeof eventDetails === "string") {
|
|
2491
|
+
parsedEventDetails = JSON.parse(eventDetails);
|
|
2492
|
+
} else if (typeof eventDetails === "object") {
|
|
2493
|
+
JSON.stringify(eventDetails);
|
|
2494
|
+
parsedEventDetails = eventDetails;
|
|
2495
|
+
}
|
|
2496
|
+
} catch (validationError) {
|
|
2497
|
+
logger.error("Invalid JSON in eventDetails:", validationError);
|
|
2498
|
+
logger.error("eventDetails value:", eventDetails);
|
|
2499
|
+
return null;
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
const attribution = getAttributionData();
|
|
2503
|
+
return {
|
|
2504
|
+
// Device info
|
|
2505
|
+
...getEssentialDeviceInfo(),
|
|
2506
|
+
// Attribution data
|
|
2507
|
+
...attribution,
|
|
2508
|
+
// Timestamp and session context
|
|
2509
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2510
|
+
session_id: this.sessionId,
|
|
2511
|
+
// Event-specific details can override any of the above
|
|
2512
|
+
...parsedEventDetails
|
|
2513
|
+
};
|
|
2514
|
+
}
|
|
2515
|
+
/**
|
|
2516
|
+
* Sends a telemetry event via the partner analytics endpoint using a partner
|
|
2517
|
+
* API key (x-api-key) instead of a hyve JWT. Used by externally-hosted games
|
|
2518
|
+
* such as CrazyGames. game_id is derived from the API key server-side and is
|
|
2519
|
+
* therefore not sent. hyve_user_id carries the `cg:`-prefixed CrazyGames id,
|
|
2520
|
+
* or a guest sentinel when the player is not logged in.
|
|
2521
|
+
*/
|
|
2522
|
+
async sendPartnerTelemetry(eventLocation, eventCategory, eventAction, eventSubCategory, eventSubAction, eventDetails) {
|
|
2523
|
+
if (!this.partnerApiKey) {
|
|
2524
|
+
logger.error("Partner API key required for partner telemetry path.");
|
|
2525
|
+
return false;
|
|
2526
|
+
}
|
|
2527
|
+
if (this.crazyGamesService && this.crazyGamesInitPromise) {
|
|
2528
|
+
await this.crazyGamesInitPromise;
|
|
2529
|
+
}
|
|
2530
|
+
try {
|
|
2531
|
+
const enrichedEventDetails = this.enrichEventDetails(eventDetails);
|
|
2532
|
+
if (enrichedEventDetails === null) {
|
|
2533
|
+
return false;
|
|
2534
|
+
}
|
|
2535
|
+
const hyveUserId = this.crazyGamesUserId ? `cg:${this.crazyGamesUserId}` : "cg:guest";
|
|
2536
|
+
const partnerEvent = {
|
|
2537
|
+
session_id: this.sessionId,
|
|
2538
|
+
hyve_user_id: hyveUserId,
|
|
2539
|
+
event_location: eventLocation,
|
|
2540
|
+
event_category: eventCategory,
|
|
2541
|
+
event_sub_category: eventSubCategory || null,
|
|
2542
|
+
event_action: eventAction,
|
|
2543
|
+
event_sub_action: eventSubAction || null,
|
|
2544
|
+
event_details: enrichedEventDetails
|
|
2545
|
+
};
|
|
2546
|
+
logger.debug("Sending partner telemetry event:", partnerEvent);
|
|
2547
|
+
const base = this.partnerApiBaseUrl || this.apiBaseUrl;
|
|
2548
|
+
const telemetryUrl = `${base}/api/v1/partners/analytics/events`;
|
|
2549
|
+
const response = await fetch(telemetryUrl, {
|
|
2550
|
+
method: "POST",
|
|
2551
|
+
headers: {
|
|
2552
|
+
"Content-Type": "application/json",
|
|
2553
|
+
"x-api-key": this.partnerApiKey
|
|
2554
|
+
},
|
|
2555
|
+
body: JSON.stringify(partnerEvent)
|
|
2556
|
+
});
|
|
2557
|
+
if (response.ok) {
|
|
2558
|
+
logger.info("Partner telemetry event sent successfully:", response.status);
|
|
2559
|
+
return true;
|
|
2560
|
+
} else {
|
|
2561
|
+
const errorText = await response.text();
|
|
2562
|
+
logger.error(
|
|
2563
|
+
"Failed to send partner telemetry event:",
|
|
2564
|
+
response.status,
|
|
2565
|
+
errorText
|
|
2566
|
+
);
|
|
2567
|
+
return false;
|
|
2568
|
+
}
|
|
2569
|
+
} catch (error) {
|
|
2570
|
+
logger.error("Error sending partner telemetry event:", error);
|
|
2571
|
+
return false;
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
/**
|
|
2575
|
+
* Seeds the CrazyGames user id used for telemetry attribution and registers
|
|
2576
|
+
* an auth listener so the id updates if a guest logs in mid-session.
|
|
2577
|
+
* The id is client-asserted (spoofable) and used only as an attribution label.
|
|
2578
|
+
*/
|
|
2579
|
+
async seedCrazyGamesUser() {
|
|
2580
|
+
if (!this.crazyGamesService) return;
|
|
2581
|
+
if (!this.crazyGamesService.isUserAccountAvailable()) {
|
|
2582
|
+
logger.info("CrazyGames account system unavailable \u2014 telemetry attributed by session only");
|
|
2583
|
+
return;
|
|
2584
|
+
}
|
|
2585
|
+
const user = await this.crazyGamesService.getUser();
|
|
2586
|
+
this.crazyGamesUserId = user?.__dangerousUserId ?? null;
|
|
2587
|
+
if (this.crazyGamesUserId) {
|
|
2588
|
+
logger.info("CrazyGames user id seeded for telemetry attribution");
|
|
2589
|
+
}
|
|
2590
|
+
this.crazyGamesService.addAuthListener((updatedUser) => {
|
|
2591
|
+
this.crazyGamesUserId = updatedUser?.__dangerousUserId ?? null;
|
|
2592
|
+
logger.info("CrazyGames auth state changed; telemetry user id updated");
|
|
2593
|
+
});
|
|
2594
|
+
}
|
|
2247
2595
|
/**
|
|
2248
2596
|
* Required lifecycle telemetry — Session start.
|
|
2249
2597
|
* See https://docs.hyve.gg/docs/telemetry#required-lifecycle-events
|
|
@@ -2464,12 +2812,28 @@ var HyveClient = class {
|
|
|
2464
2812
|
logger.info("Client reset with new sessionId:", this.sessionId);
|
|
2465
2813
|
}
|
|
2466
2814
|
/**
|
|
2467
|
-
* Get the storage adapter based on mode
|
|
2815
|
+
* Get the storage adapter based on mode.
|
|
2816
|
+
*
|
|
2817
|
+
* Selection order:
|
|
2818
|
+
* 1. An explicit `mode` override ('cloud' | 'local') always wins.
|
|
2819
|
+
* 2. On the CrazyGames platform, the CrazyGames data store is auto-selected
|
|
2820
|
+
* (D3 — public storageMode type is preserved; this is chosen internally).
|
|
2821
|
+
* 3. Otherwise the configured storageMode is used.
|
|
2822
|
+
*
|
|
2823
|
+
* Awaits CrazyGames SDK initialization so the data store is ready before use.
|
|
2468
2824
|
* @param mode Storage mode override (cloud or local)
|
|
2469
2825
|
*/
|
|
2470
|
-
getStorageAdapter(mode) {
|
|
2471
|
-
|
|
2472
|
-
|
|
2826
|
+
async getStorageAdapter(mode) {
|
|
2827
|
+
if (mode) {
|
|
2828
|
+
return mode === "local" ? this.localStorageAdapter : this.cloudStorageAdapter;
|
|
2829
|
+
}
|
|
2830
|
+
if (this.crazyGamesService && this.crazyGamesStorageAdapter) {
|
|
2831
|
+
if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
|
|
2832
|
+
if (this.crazyGamesService.getEnvironment() === "crazygames") {
|
|
2833
|
+
return this.crazyGamesStorageAdapter;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
return this.storageMode === "local" ? this.localStorageAdapter : this.cloudStorageAdapter;
|
|
2473
2837
|
}
|
|
2474
2838
|
/**
|
|
2475
2839
|
* Returns the current game ID or throws if not available.
|
|
@@ -2493,7 +2857,7 @@ var HyveClient = class {
|
|
|
2493
2857
|
const gameId = this.requireGameId();
|
|
2494
2858
|
const storageMode = storage || this.storageMode;
|
|
2495
2859
|
logger.debug(`Saving game data to ${storageMode}: ${gameId}/${key}`);
|
|
2496
|
-
const adapter = this.getStorageAdapter(storage);
|
|
2860
|
+
const adapter = await this.getStorageAdapter(storage);
|
|
2497
2861
|
const response = await adapter.saveGameData(gameId, key, value, operation, path);
|
|
2498
2862
|
logger.info(`Game data saved successfully to ${storageMode}: ${gameId}/${key}`);
|
|
2499
2863
|
return response;
|
|
@@ -2508,7 +2872,7 @@ var HyveClient = class {
|
|
|
2508
2872
|
const gameId = this.requireGameId();
|
|
2509
2873
|
const storageMode = storage || this.storageMode;
|
|
2510
2874
|
logger.debug(`Batch saving ${items.length} game data entries to ${storageMode} for game: ${gameId}`);
|
|
2511
|
-
const adapter = this.getStorageAdapter(storage);
|
|
2875
|
+
const adapter = await this.getStorageAdapter(storage);
|
|
2512
2876
|
const response = await adapter.batchSaveGameData(gameId, items);
|
|
2513
2877
|
logger.info(`Batch saved ${items.length} game data entries successfully to ${storageMode}`);
|
|
2514
2878
|
return response;
|
|
@@ -2523,7 +2887,7 @@ var HyveClient = class {
|
|
|
2523
2887
|
const gameId = this.requireGameId();
|
|
2524
2888
|
const storageMode = storage || this.storageMode;
|
|
2525
2889
|
logger.debug(`Getting game data from ${storageMode}: ${gameId}/${key}`);
|
|
2526
|
-
const adapter = this.getStorageAdapter(storage);
|
|
2890
|
+
const adapter = await this.getStorageAdapter(storage);
|
|
2527
2891
|
const data = await adapter.getGameData(gameId, key);
|
|
2528
2892
|
if (data) {
|
|
2529
2893
|
logger.info(`Game data retrieved successfully from ${storageMode}: ${gameId}/${key}`);
|
|
@@ -2542,7 +2906,7 @@ var HyveClient = class {
|
|
|
2542
2906
|
const gameId = this.requireGameId();
|
|
2543
2907
|
const storageMode = storage || this.storageMode;
|
|
2544
2908
|
logger.debug(`Getting ${keys.length} game data entries from ${storageMode} for game: ${gameId}`);
|
|
2545
|
-
const adapter = this.getStorageAdapter(storage);
|
|
2909
|
+
const adapter = await this.getStorageAdapter(storage);
|
|
2546
2910
|
const data = await adapter.getMultipleGameData(gameId, keys);
|
|
2547
2911
|
logger.info(`Retrieved ${data.length} game data entries from ${storageMode}`);
|
|
2548
2912
|
return data;
|
|
@@ -2557,7 +2921,7 @@ var HyveClient = class {
|
|
|
2557
2921
|
const gameId = this.requireGameId();
|
|
2558
2922
|
const storageMode = storage || this.storageMode;
|
|
2559
2923
|
logger.debug(`Deleting game data from ${storageMode}: ${gameId}/${key}`);
|
|
2560
|
-
const adapter = this.getStorageAdapter(storage);
|
|
2924
|
+
const adapter = await this.getStorageAdapter(storage);
|
|
2561
2925
|
const deleted = await adapter.deleteGameData(gameId, key);
|
|
2562
2926
|
if (deleted) {
|
|
2563
2927
|
logger.info(`Game data deleted successfully from ${storageMode}: ${gameId}/${key}`);
|
|
@@ -2576,7 +2940,7 @@ var HyveClient = class {
|
|
|
2576
2940
|
const gameId = this.requireGameId();
|
|
2577
2941
|
const storageMode = storage || this.storageMode;
|
|
2578
2942
|
logger.debug(`Deleting ${keys.length} game data entries from ${storageMode} for game: ${gameId}`);
|
|
2579
|
-
const adapter = this.getStorageAdapter(storage);
|
|
2943
|
+
const adapter = await this.getStorageAdapter(storage);
|
|
2580
2944
|
const deletedCount = await adapter.deleteMultipleGameData(gameId, keys);
|
|
2581
2945
|
logger.info(`Deleted ${deletedCount} game data entries from ${storageMode}`);
|
|
2582
2946
|
return deletedCount;
|