@wvdsh/sdk-js 1.3.5 → 1.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +10 -12
- package/dist/index.js +88 -53
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -82,6 +82,7 @@ type LeaderboardEntries = FunctionReturnType<typeof api.sdk.leaderboards.listEnt
|
|
|
82
82
|
type UpsertedLeaderboardEntry = FunctionReturnType<typeof api.sdk.leaderboards.upsertLeaderboardEntry>["entry"] & {
|
|
83
83
|
userId: GenericId<"users">;
|
|
84
84
|
username: string;
|
|
85
|
+
userAvatarUrl?: string;
|
|
85
86
|
};
|
|
86
87
|
type WavedashEvent = (typeof WavedashEvents)[keyof typeof WavedashEvents];
|
|
87
88
|
interface WavedashConfig {
|
|
@@ -278,6 +279,8 @@ declare class LobbyManager extends WavedashManager {
|
|
|
278
279
|
private recentMessageIds;
|
|
279
280
|
private maybeBeingDeletedLobbyIds;
|
|
280
281
|
private resetMaybeBeingDeletedLobbyIdTimeouts;
|
|
282
|
+
private static readonly METADATA_UPDATE_THROTTLE_MS;
|
|
283
|
+
private inFlightMetadataUpdate;
|
|
281
284
|
private cachedLobbies;
|
|
282
285
|
private unsubscribeLobbyInvites;
|
|
283
286
|
private seenInviteIds;
|
|
@@ -295,7 +298,6 @@ declare class LobbyManager extends WavedashManager {
|
|
|
295
298
|
getHostId(lobbyId: GenericId<"lobbies">): GenericId<"users"> | null;
|
|
296
299
|
getLobbyData(lobbyId: GenericId<"lobbies">, key: string): string | number | null;
|
|
297
300
|
deleteLobbyData(lobbyId: GenericId<"lobbies">, key: string): boolean;
|
|
298
|
-
private debouncedMetadataUpdate;
|
|
299
301
|
setLobbyData(lobbyId: GenericId<"lobbies">, key: string, value: string | number | null): boolean;
|
|
300
302
|
getLobbyMaxPlayers(lobbyId: GenericId<"lobbies">): number;
|
|
301
303
|
getNumLobbyUsers(lobbyId: GenericId<"lobbies">): number;
|
|
@@ -333,7 +335,8 @@ declare class LobbyManager extends WavedashManager {
|
|
|
333
335
|
* Called during session end to ensure no lingering listeners.
|
|
334
336
|
*/
|
|
335
337
|
destroy(): void;
|
|
336
|
-
private
|
|
338
|
+
private throttledSetMetadata;
|
|
339
|
+
private setMetadata;
|
|
337
340
|
/**
|
|
338
341
|
* Process user updates and emit individual user events
|
|
339
342
|
* @param newUsers - The updated list of lobby users
|
|
@@ -556,10 +559,6 @@ declare class P2PManager extends WavedashManager {
|
|
|
556
559
|
private decodeBinaryMessage;
|
|
557
560
|
}
|
|
558
561
|
|
|
559
|
-
type StatEntry = {
|
|
560
|
-
identifier: string;
|
|
561
|
-
value: number;
|
|
562
|
-
};
|
|
563
562
|
declare class StatsManager extends WavedashManager {
|
|
564
563
|
private stats;
|
|
565
564
|
private unlockedAchievements;
|
|
@@ -569,24 +568,23 @@ declare class StatsManager extends WavedashManager {
|
|
|
569
568
|
private knownAchievementIds;
|
|
570
569
|
private loaded;
|
|
571
570
|
private subscriptions;
|
|
572
|
-
private
|
|
571
|
+
private inFlightPersist;
|
|
572
|
+
private flushRequested;
|
|
573
573
|
constructor(sdk: WavedashSDK);
|
|
574
574
|
destroy(): void;
|
|
575
575
|
private isReady;
|
|
576
576
|
private subscribe;
|
|
577
577
|
requestStats(): Promise<boolean>;
|
|
578
|
-
private
|
|
578
|
+
private throttledPersist;
|
|
579
579
|
storeStats(): boolean;
|
|
580
|
+
private requestPersistFlush;
|
|
580
581
|
private persist;
|
|
581
582
|
getStat(identifier: string): number;
|
|
582
583
|
setStat(identifier: string, value: number, storeNow?: boolean): boolean;
|
|
583
584
|
getAchievement(identifier: string): boolean;
|
|
584
585
|
setAchievement(identifier: string, storeNow?: boolean): boolean;
|
|
585
586
|
/** @destructive - Returns the pending stats and achievements and resets the dirty collections */
|
|
586
|
-
getPendingData
|
|
587
|
-
stats: StatEntry[];
|
|
588
|
-
achievements: string[];
|
|
589
|
-
} | null;
|
|
587
|
+
private getPendingData;
|
|
590
588
|
}
|
|
591
589
|
|
|
592
590
|
/**
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { ConvexClient } from "convex/browser";
|
|
3
3
|
|
|
4
4
|
// src/services/lobby.ts
|
|
5
|
-
import
|
|
5
|
+
import throttle from "lodash.throttle";
|
|
6
6
|
|
|
7
7
|
// src/constants.ts
|
|
8
8
|
import {
|
|
@@ -94,7 +94,7 @@ var WavedashManager = class {
|
|
|
94
94
|
};
|
|
95
95
|
|
|
96
96
|
// src/services/lobby.ts
|
|
97
|
-
var
|
|
97
|
+
var _LobbyManager = class _LobbyManager extends WavedashManager {
|
|
98
98
|
constructor(sdk) {
|
|
99
99
|
super(sdk);
|
|
100
100
|
// Track current lobby state
|
|
@@ -109,6 +109,7 @@ var LobbyManager = class extends WavedashManager {
|
|
|
109
109
|
this.recentMessageIds = [];
|
|
110
110
|
this.maybeBeingDeletedLobbyIds = /* @__PURE__ */ new Set();
|
|
111
111
|
this.resetMaybeBeingDeletedLobbyIdTimeouts = /* @__PURE__ */ new Map();
|
|
112
|
+
this.inFlightMetadataUpdate = null;
|
|
112
113
|
// Cache results of queries for a list of lobbies
|
|
113
114
|
// We'll cache metadata and num users for each lobby and return that info synchronously when requested by the game
|
|
114
115
|
this.cachedLobbies = {};
|
|
@@ -117,9 +118,13 @@ var LobbyManager = class extends WavedashManager {
|
|
|
117
118
|
this.seenInviteIds = /* @__PURE__ */ new Set();
|
|
118
119
|
// Queue for serializing P2P connection updates to prevent race conditions
|
|
119
120
|
this.p2pUpdateQueue = Promise.resolve();
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
// leading: false so the first call doesn't fire synchronously inside setLobbyData
|
|
122
|
+
// (which is called from a tight loop); trailing: true to flush coalesced updates
|
|
123
|
+
// at the end of the window.
|
|
124
|
+
this.throttledSetMetadata = throttle(
|
|
125
|
+
() => this.setMetadata(),
|
|
126
|
+
_LobbyManager.METADATA_UPDATE_THROTTLE_MS,
|
|
127
|
+
{ leading: false, trailing: true }
|
|
123
128
|
);
|
|
124
129
|
/**
|
|
125
130
|
* Process user updates and emit individual user events
|
|
@@ -266,7 +271,7 @@ var LobbyManager = class extends WavedashManager {
|
|
|
266
271
|
this.lobbyMetadata[key] = value;
|
|
267
272
|
}
|
|
268
273
|
this.pendingMetadataUpdates[key] = value;
|
|
269
|
-
this.
|
|
274
|
+
this.throttledSetMetadata();
|
|
270
275
|
return true;
|
|
271
276
|
}
|
|
272
277
|
getLobbyMaxPlayers(lobbyId) {
|
|
@@ -443,7 +448,7 @@ var LobbyManager = class extends WavedashManager {
|
|
|
443
448
|
cleanupLobbyState() {
|
|
444
449
|
const currentLobbyId = this.lobbyId;
|
|
445
450
|
this.lobbyId = null;
|
|
446
|
-
this.
|
|
451
|
+
this.throttledSetMetadata.cancel();
|
|
447
452
|
this.pendingMetadataUpdates = {};
|
|
448
453
|
if (this.unsubscribeLobbyMessages) {
|
|
449
454
|
this.unsubscribeLobbyMessages();
|
|
@@ -501,14 +506,22 @@ var LobbyManager = class extends WavedashManager {
|
|
|
501
506
|
this.seenInviteIds.clear();
|
|
502
507
|
this.cachedLobbies = {};
|
|
503
508
|
}
|
|
504
|
-
|
|
509
|
+
setMetadata() {
|
|
510
|
+
if (this.inFlightMetadataUpdate !== null) return;
|
|
511
|
+
if (this.lobbyId === null) return;
|
|
512
|
+
if (Object.keys(this.pendingMetadataUpdates).length === 0) return;
|
|
505
513
|
const updates = this.pendingMetadataUpdates;
|
|
506
514
|
this.pendingMetadataUpdates = {};
|
|
507
|
-
this.sdk.convexClient.mutation(api.sdk.gameLobby.setLobbyMetadata, {
|
|
515
|
+
this.inFlightMetadataUpdate = this.sdk.convexClient.mutation(api.sdk.gameLobby.setLobbyMetadata, {
|
|
508
516
|
lobbyId: this.lobbyId,
|
|
509
517
|
updates
|
|
510
518
|
}).catch((error) => {
|
|
511
519
|
this.sdk.logger.error("Error updating lobby metadata:", error);
|
|
520
|
+
}).finally(() => {
|
|
521
|
+
this.inFlightMetadataUpdate = null;
|
|
522
|
+
if (Object.keys(this.pendingMetadataUpdates).length > 0) {
|
|
523
|
+
this.throttledSetMetadata();
|
|
524
|
+
}
|
|
512
525
|
});
|
|
513
526
|
}
|
|
514
527
|
// ================
|
|
@@ -547,6 +560,10 @@ var LobbyManager = class extends WavedashManager {
|
|
|
547
560
|
}
|
|
548
561
|
}
|
|
549
562
|
};
|
|
563
|
+
// Throttle (not debounce) batches rapid setLobbyData calls; the in-flight
|
|
564
|
+
// gate in setMetadata prevents OCC self-conflicts.
|
|
565
|
+
_LobbyManager.METADATA_UPDATE_THROTTLE_MS = 150;
|
|
566
|
+
var LobbyManager = _LobbyManager;
|
|
550
567
|
|
|
551
568
|
// src/utils/indexedDB.ts
|
|
552
569
|
var LOCAL_STORAGE_DB_NAME = "/userfs";
|
|
@@ -1081,7 +1098,8 @@ var LeaderboardManager = class extends WavedashManager {
|
|
|
1081
1098
|
return {
|
|
1082
1099
|
...result.entry,
|
|
1083
1100
|
userId: this.sdk.wavedashUser.id,
|
|
1084
|
-
username: this.sdk.wavedashUser.username
|
|
1101
|
+
username: this.sdk.wavedashUser.username,
|
|
1102
|
+
userAvatarUrl: this.sdk.wavedashUser.avatarUrl
|
|
1085
1103
|
};
|
|
1086
1104
|
}
|
|
1087
1105
|
// ================
|
|
@@ -2424,9 +2442,8 @@ var P2PManager = _P2PManager;
|
|
|
2424
2442
|
|
|
2425
2443
|
// src/services/stats.ts
|
|
2426
2444
|
import { api as api6 } from "@wvdsh/api";
|
|
2427
|
-
import
|
|
2428
|
-
var
|
|
2429
|
-
var PERIODIC_PERSIST_MS = 1e4;
|
|
2445
|
+
import throttle2 from "lodash.throttle";
|
|
2446
|
+
var STORE_THROTTLE_MS = 1e3;
|
|
2430
2447
|
var StatsManager = class extends WavedashManager {
|
|
2431
2448
|
constructor(sdk) {
|
|
2432
2449
|
super(sdk);
|
|
@@ -2444,32 +2461,33 @@ var StatsManager = class extends WavedashManager {
|
|
|
2444
2461
|
this.loaded = { stats: false, achievements: false };
|
|
2445
2462
|
// Subscription cleanup
|
|
2446
2463
|
this.subscriptions = [];
|
|
2447
|
-
//
|
|
2448
|
-
this.
|
|
2464
|
+
// Single in-flight persist mutation; prevents OCC self-conflicts.
|
|
2465
|
+
this.inFlightPersist = null;
|
|
2466
|
+
// Set when a storeNow flush hits the in-flight gate; persist's .finally()
|
|
2467
|
+
// checks this and fires immediately on the next cycle instead of waiting
|
|
2468
|
+
// out the throttle window. (lodash treats the gated flush as a successful
|
|
2469
|
+
// invocation, so without this flag a contended storeNow waits ~THROTTLE_MS.)
|
|
2470
|
+
this.flushRequested = false;
|
|
2449
2471
|
// ================
|
|
2450
2472
|
// Store / Persist
|
|
2451
2473
|
// ================
|
|
2452
|
-
//
|
|
2453
|
-
//
|
|
2454
|
-
//
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2474
|
+
// leading: false so a single setStat doesn't fire synchronously inside the
|
|
2475
|
+
// setter; trailing: true to flush coalesced edits at the end of the window.
|
|
2476
|
+
// storeNow=true (and storeStats()) call .flush() to fire the pending invocation
|
|
2477
|
+
// immediately. The in-flight gate in persist() covers mutations that outlast
|
|
2478
|
+
// the throttle window, which would otherwise overlap and cause OCC conflicts.
|
|
2479
|
+
this.throttledPersist = throttle2(
|
|
2480
|
+
() => this.persist(),
|
|
2481
|
+
STORE_THROTTLE_MS,
|
|
2482
|
+
{ leading: false, trailing: true }
|
|
2483
|
+
);
|
|
2459
2484
|
this.subscribe();
|
|
2460
2485
|
this.requestStats().catch((error) => {
|
|
2461
2486
|
this.sdk.logger.error("Initial stats fetch failed:", error);
|
|
2462
2487
|
});
|
|
2463
|
-
this.periodicPersistInterval = setInterval(() => {
|
|
2464
|
-
void this.persist();
|
|
2465
|
-
}, PERIODIC_PERSIST_MS);
|
|
2466
2488
|
}
|
|
2467
2489
|
destroy() {
|
|
2468
|
-
this.
|
|
2469
|
-
if (this.periodicPersistInterval !== null) {
|
|
2470
|
-
clearInterval(this.periodicPersistInterval);
|
|
2471
|
-
this.periodicPersistInterval = null;
|
|
2472
|
-
}
|
|
2490
|
+
this.throttledPersist.cancel();
|
|
2473
2491
|
for (const unsub of this.subscriptions) unsub();
|
|
2474
2492
|
this.subscriptions = [];
|
|
2475
2493
|
}
|
|
@@ -2534,35 +2552,51 @@ var StatsManager = class extends WavedashManager {
|
|
|
2534
2552
|
}
|
|
2535
2553
|
storeStats() {
|
|
2536
2554
|
if (!this.isReady()) return false;
|
|
2537
|
-
this.
|
|
2538
|
-
this.
|
|
2555
|
+
this.throttledPersist();
|
|
2556
|
+
this.requestPersistFlush();
|
|
2539
2557
|
return true;
|
|
2540
2558
|
}
|
|
2541
|
-
|
|
2559
|
+
// Force-fire the throttled persist now. If a mutation is already in flight,
|
|
2560
|
+
// the gate will swallow the flush(), so we also flag flushRequested so the
|
|
2561
|
+
// next .finally() flushes again instead of waiting a full throttle window.
|
|
2562
|
+
requestPersistFlush() {
|
|
2563
|
+
if (this.inFlightPersist !== null) this.flushRequested = true;
|
|
2564
|
+
this.throttledPersist.flush();
|
|
2565
|
+
}
|
|
2566
|
+
persist() {
|
|
2567
|
+
if (this.inFlightPersist !== null) return;
|
|
2568
|
+
if (this.dirtyStats.size === 0 && this.dirtyAchievements.size === 0) return;
|
|
2542
2569
|
const pending = this.getPendingData();
|
|
2543
2570
|
if (!pending) return;
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
]);
|
|
2571
|
+
this.inFlightPersist = Promise.all([
|
|
2572
|
+
pending.stats.length > 0 ? this.sdk.convexClient.mutation(
|
|
2573
|
+
api6.sdk.gameAchievements.setUserGameStats,
|
|
2574
|
+
{ stats: pending.stats }
|
|
2575
|
+
) : Promise.resolve(),
|
|
2576
|
+
pending.achievements.length > 0 ? this.sdk.convexClient.mutation(
|
|
2577
|
+
api6.sdk.gameAchievements.setUserGameAchievements,
|
|
2578
|
+
{ achievements: pending.achievements }
|
|
2579
|
+
) : Promise.resolve()
|
|
2580
|
+
]).then(() => {
|
|
2555
2581
|
this.sdk.gameEventManager.notifyGame(WavedashEvents.STATS_STORED, {
|
|
2556
2582
|
success: true
|
|
2557
2583
|
});
|
|
2558
|
-
}
|
|
2584
|
+
}).catch((error) => {
|
|
2559
2585
|
const message = error instanceof Error ? error.message : `Error storing stats: ${error}`;
|
|
2560
2586
|
this.sdk.logger.error(message);
|
|
2561
2587
|
this.sdk.gameEventManager.notifyGame(WavedashEvents.STATS_STORED, {
|
|
2562
2588
|
success: false,
|
|
2563
2589
|
message
|
|
2564
2590
|
});
|
|
2565
|
-
}
|
|
2591
|
+
}).finally(() => {
|
|
2592
|
+
this.inFlightPersist = null;
|
|
2593
|
+
const shouldFlushNow = this.flushRequested;
|
|
2594
|
+
this.flushRequested = false;
|
|
2595
|
+
if (this.dirtyStats.size > 0 || this.dirtyAchievements.size > 0) {
|
|
2596
|
+
this.throttledPersist();
|
|
2597
|
+
if (shouldFlushNow) this.throttledPersist.flush();
|
|
2598
|
+
}
|
|
2599
|
+
});
|
|
2566
2600
|
}
|
|
2567
2601
|
// ================
|
|
2568
2602
|
// Stats
|
|
@@ -2576,8 +2610,9 @@ var StatsManager = class extends WavedashManager {
|
|
|
2576
2610
|
if (this.stats.get(identifier) !== value) {
|
|
2577
2611
|
this.stats.set(identifier, value);
|
|
2578
2612
|
this.dirtyStats.add(identifier);
|
|
2613
|
+
this.throttledPersist();
|
|
2579
2614
|
}
|
|
2580
|
-
if (storeNow) this.
|
|
2615
|
+
if (storeNow) this.requestPersistFlush();
|
|
2581
2616
|
return true;
|
|
2582
2617
|
}
|
|
2583
2618
|
// ================
|
|
@@ -2594,13 +2629,11 @@ var StatsManager = class extends WavedashManager {
|
|
|
2594
2629
|
if (!this.unlockedAchievements.has(identifier)) {
|
|
2595
2630
|
this.unlockedAchievements.add(identifier);
|
|
2596
2631
|
this.dirtyAchievements.add(identifier);
|
|
2632
|
+
this.throttledPersist();
|
|
2597
2633
|
}
|
|
2598
|
-
if (storeNow) this.
|
|
2634
|
+
if (storeNow) this.requestPersistFlush();
|
|
2599
2635
|
return true;
|
|
2600
2636
|
}
|
|
2601
|
-
// ================
|
|
2602
|
-
// Session End
|
|
2603
|
-
// ================
|
|
2604
2637
|
/** @destructive - Returns the pending stats and achievements and resets the dirty collections */
|
|
2605
2638
|
getPendingData() {
|
|
2606
2639
|
if (this.dirtyStats.size === 0 && this.dirtyAchievements.size === 0) {
|
|
@@ -2705,8 +2738,10 @@ var HeartbeatManager = class extends WavedashManager {
|
|
|
2705
2738
|
if (!reestablish && this.heartbeatInFlight) return;
|
|
2706
2739
|
this.heartbeatInFlight = true;
|
|
2707
2740
|
this.sdk.convexClient.mutation(api7.sdk.presence.heartbeat, {
|
|
2708
|
-
...reestablish ? {
|
|
2709
|
-
|
|
2741
|
+
...reestablish ? {
|
|
2742
|
+
data: { forceUpdate: true },
|
|
2743
|
+
deviceFingerprint: this.deviceFingerprint
|
|
2744
|
+
} : {}
|
|
2710
2745
|
}).then((accepted) => {
|
|
2711
2746
|
if (accepted) {
|
|
2712
2747
|
this.lastHeartbeatTime = Date.now();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wvdsh/sdk-js",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Wavedash JavaScript SDK",
|
|
6
6
|
"main": "./dist/client.js",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@eslint/js": "^9.39.2",
|
|
43
|
-
"@types/lodash.
|
|
43
|
+
"@types/lodash.throttle": "^4.1.9",
|
|
44
44
|
"eslint": "^9.39.2",
|
|
45
45
|
"eslint-config-prettier": "^10.1.8",
|
|
46
46
|
"prettier": "^3.7.4",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"typescript-eslint": "^8.52.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@wvdsh/api": "^0.1.
|
|
52
|
+
"@wvdsh/api": "^0.1.16",
|
|
53
53
|
"convex": "^1.34.0",
|
|
54
|
-
"lodash.
|
|
54
|
+
"lodash.throttle": "^4.1.1"
|
|
55
55
|
}
|
|
56
56
|
}
|