@wvdsh/sdk-js 1.3.3 → 1.3.5

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 CHANGED
@@ -245,14 +245,28 @@ interface P2PConfig {
245
245
  maxIncomingMessages?: number;
246
246
  }
247
247
 
248
+ /**
249
+ * Base class for SDK managers. Provides the shared `sdk` reference and a
250
+ * default no-op `destroy()` so the SDK can safely iterate every manager
251
+ * during teardown without each one having to define an empty stub.
252
+ *
253
+ * Override `destroy()` in any manager that owns ongoing state — Convex
254
+ * subscriptions, intervals, peer connections, monkey-patched globals, etc.
255
+ * — to make sure that state is released when the SDK is torn down.
256
+ */
257
+ declare abstract class WavedashManager {
258
+ protected sdk: WavedashSDK;
259
+ constructor(sdk: WavedashSDK);
260
+ destroy(): void;
261
+ }
262
+
248
263
  /**
249
264
  * Lobby service
250
265
  *
251
266
  * Implements each of the lobby methods of the Wavedash SDK
252
267
  */
253
268
 
254
- declare class LobbyManager {
255
- private sdk;
269
+ declare class LobbyManager extends WavedashManager {
256
270
  private unsubscribeLobbyMessages;
257
271
  private unsubscribeLobbyUsers;
258
272
  private unsubscribeLobbyData;
@@ -342,8 +356,7 @@ declare class LobbyManager {
342
356
  * TODO: Extend this to game-level assets as well.
343
357
  */
344
358
 
345
- declare class FileSystemManager {
346
- private sdk;
359
+ declare class FileSystemManager extends WavedashManager {
347
360
  private remoteStorageOrigin;
348
361
  constructor(sdk: WavedashSDK);
349
362
  /**
@@ -399,11 +412,11 @@ declare class FileSystemManager {
399
412
  * Implements each of the user generated content methods of the Wavedash SDK
400
413
  */
401
414
 
402
- declare class UGCManager {
403
- private sdk;
415
+ declare class UGCManager extends WavedashManager {
404
416
  constructor(sdk: WavedashSDK);
405
417
  createUGCItem(ugcType: UGCType, title?: string, description?: string, visibility?: UGCVisibility, filePath?: string): Promise<GenericId<"userGeneratedContent">>;
406
418
  updateUGCItem(ugcId: GenericId<"userGeneratedContent">, title?: string, description?: string, visibility?: UGCVisibility, filePath?: string): Promise<GenericId<"userGeneratedContent">>;
419
+ deleteUGCItem(ugcId: GenericId<"userGeneratedContent">): Promise<GenericId<"userGeneratedContent">>;
407
420
  downloadUGCItem(ugcId: GenericId<"userGeneratedContent">, filePath: string): Promise<GenericId<"userGeneratedContent">>;
408
421
  }
409
422
 
@@ -413,8 +426,7 @@ declare class UGCManager {
413
426
  * Implements each of the leaderboard methods of the Wavedash SDK
414
427
  */
415
428
 
416
- declare class LeaderboardManager {
417
- private sdk;
429
+ declare class LeaderboardManager extends WavedashManager {
418
430
  private leaderboardCache;
419
431
  constructor(sdk: WavedashSDK);
420
432
  getLeaderboard(name: string): Promise<Leaderboard>;
@@ -433,8 +445,7 @@ declare class LeaderboardManager {
433
445
  * Handles WebRTC peer-to-peer connections for lobbies
434
446
  */
435
447
 
436
- declare class P2PManager {
437
- private sdk;
448
+ declare class P2PManager extends WavedashManager {
438
449
  private config;
439
450
  private currentConnection;
440
451
  private peerConnections;
@@ -479,6 +490,7 @@ declare class P2PManager {
479
490
  private textDecoder;
480
491
  private initialized;
481
492
  constructor(sdk: WavedashSDK);
493
+ destroy(): void;
482
494
  private ensureInitialized;
483
495
  init(config?: Partial<P2PConfig>): void;
484
496
  initializeP2PForCurrentLobby(lobbyId: GenericId<"lobbies">, members: SDKUser[]): Promise<P2PConnection>;
@@ -548,8 +560,7 @@ type StatEntry = {
548
560
  identifier: string;
549
561
  value: number;
550
562
  };
551
- declare class StatsManager {
552
- private sdk;
563
+ declare class StatsManager extends WavedashManager {
553
564
  private stats;
554
565
  private unlockedAchievements;
555
566
  private dirtyStats;
@@ -558,6 +569,7 @@ declare class StatsManager {
558
569
  private knownAchievementIds;
559
570
  private loaded;
560
571
  private subscriptions;
572
+ private periodicPersistInterval;
561
573
  constructor(sdk: WavedashSDK);
562
574
  destroy(): void;
563
575
  private isReady;
@@ -585,8 +597,7 @@ declare class StatsManager {
585
597
  * Lets the game update userPresence in the backend
586
598
  */
587
599
 
588
- declare class HeartbeatManager {
589
- private sdk;
600
+ declare class HeartbeatManager extends WavedashManager {
590
601
  private deviceFingerprint;
591
602
  private deviceFingerprintReady;
592
603
  private testConnectionInterval;
@@ -622,8 +633,7 @@ declare class HeartbeatManager {
622
633
  isCurrentlyConnected(): boolean;
623
634
  }
624
635
 
625
- declare class GameEventManager {
626
- private sdk;
636
+ declare class GameEventManager extends WavedashManager {
627
637
  private eventQueue;
628
638
  constructor(sdk: WavedashSDK);
629
639
  notifyGame(event: WavedashEvent, payload: string | number | object): void;
@@ -650,10 +660,9 @@ declare class GameEventManager {
650
660
  * calls route through us. The iframe isn't granted the fullscreen feature
651
661
  * policy anymore, so without these shims those calls would silently reject.
652
662
  */
653
- declare class FullscreenManager {
663
+ declare class FullscreenManager extends WavedashManager {
654
664
  private _isFullscreen;
655
665
  private listeners;
656
- private sdk;
657
666
  constructor(sdk: WavedashSDK);
658
667
  isFullscreen(): boolean;
659
668
  /**
@@ -680,8 +689,7 @@ declare class FullscreenManager {
680
689
  * - `takeFocus()` is also called after load completes so the game starts
681
690
  * with keyboard focus without the player clicking first.
682
691
  */
683
- declare class OverlayManager {
684
- private sdk;
692
+ declare class OverlayManager extends WavedashManager {
685
693
  constructor(sdk: WavedashSDK);
686
694
  toggleOverlay(): void;
687
695
  takeFocus(): void;
@@ -694,8 +702,7 @@ declare class OverlayManager {
694
702
  * Implements friend-related methods for the Wavedash SDK
695
703
  */
696
704
 
697
- declare class FriendsManager {
698
- private sdk;
705
+ declare class FriendsManager extends WavedashManager {
699
706
  private userCache;
700
707
  constructor(sdk: WavedashSDK);
701
708
  /**
@@ -780,8 +787,7 @@ declare class WavedashSDK extends EventTarget {
780
787
  private _eventsReady;
781
788
  get eventsReady(): boolean;
782
789
  private launchParams;
783
- private sessionEndSent;
784
- private convexHttpUrl;
790
+ private destroyed;
785
791
  private gameFinishedLoading;
786
792
  Events: {
787
793
  readonly LOBBY_MESSAGE: "LobbyMessage";
@@ -869,6 +875,7 @@ declare class WavedashSDK extends EventTarget {
869
875
  p2pManager: P2PManager;
870
876
  fullscreenManager: FullscreenManager;
871
877
  overlayManager: OverlayManager;
878
+ private managers;
872
879
  private gameplayJwt;
873
880
  private gameplayJwtPromise;
874
881
  private setupWarningTimeout;
@@ -984,6 +991,11 @@ declare class WavedashSDK extends EventTarget {
984
991
  * @returns ugcId
985
992
  */
986
993
  updateUGCItem(ugcId: GenericId<"userGeneratedContent">, title?: string, description?: string, visibility?: UGCVisibility, filePath?: string): Promise<WavedashResponse<GenericId<"userGeneratedContent">>>;
994
+ /**
995
+ * Delete a UGC item: removes the row, the R2 object, and frees up the
996
+ * user's storage quota by the size of the deleted upload.
997
+ */
998
+ deleteUGCItem(ugcId: GenericId<"userGeneratedContent">): Promise<WavedashResponse<GenericId<"userGeneratedContent">>>;
987
999
  downloadUGCItem(ugcId: GenericId<"userGeneratedContent">, filePath: string): Promise<WavedashResponse<GenericId<"userGeneratedContent">>>;
988
1000
  /**
989
1001
  * Deletes a remote file from storage
@@ -1154,12 +1166,9 @@ declare class WavedashSDK extends EventTarget {
1154
1166
  */
1155
1167
  ensureGameplayJwt(): Promise<string>;
1156
1168
  /**
1157
- * Set up listeners that flush the end-of-session request when the iframe
1158
- * is going away. We listen for three signals:
1159
- * - `beforeunload` / `pagehide` on our own window: covers tab close, hard
1160
- * reload, and top-level navigation of the parent.
1161
- * - `END_SESSION` postMessage from the parent: covers parent SPA navigation
1169
+ * Tear down every manager. Called on the parent's `END_SESSION` signal
1162
1170
  */
1171
+ private destroy;
1163
1172
  private setupSessionEndListeners;
1164
1173
  }
1165
1174
  declare global {
package/dist/index.js CHANGED
@@ -83,8 +83,20 @@ var WavedashEvents = {
83
83
 
84
84
  // src/services/lobby.ts
85
85
  import { api, IFRAME_MESSAGE_TYPE } from "@wvdsh/api";
86
- var LobbyManager = class {
86
+
87
+ // src/services/manager.ts
88
+ var WavedashManager = class {
89
+ constructor(sdk) {
90
+ this.sdk = sdk;
91
+ }
92
+ destroy() {
93
+ }
94
+ };
95
+
96
+ // src/services/lobby.ts
97
+ var LobbyManager = class extends WavedashManager {
87
98
  constructor(sdk) {
99
+ super(sdk);
88
100
  // Track current lobby state
89
101
  this.unsubscribeLobbyMessages = null;
90
102
  this.unsubscribeLobbyUsers = null;
@@ -182,7 +194,6 @@ var LobbyManager = class {
182
194
  invites.map((invite) => invite.notificationId)
183
195
  );
184
196
  };
185
- this.sdk = sdk;
186
197
  this.unsubscribeLobbyInvites = this.sdk.convexClient.onUpdate(
187
198
  api.sdk.gameLobby.getLobbyInvites,
188
199
  {},
@@ -611,9 +622,9 @@ function toBlobFromIndexedDBValue(value) {
611
622
  import { api as api2 } from "@wvdsh/api";
612
623
  var REMOTE_STORAGE_FOLDER = "userfs";
613
624
  var WAVEDASH_PERSISTENT_DATA_PATH = "/idbfs/wavedash";
614
- var FileSystemManager = class {
625
+ var FileSystemManager = class extends WavedashManager {
615
626
  constructor(sdk) {
616
- this.sdk = sdk;
627
+ super(sdk);
617
628
  }
618
629
  /**
619
630
  * Converts a local filesystem path into a full R2 object key.
@@ -663,9 +674,19 @@ var FileSystemManager = class {
663
674
  * @returns The path of the remote file that was deleted
664
675
  */
665
676
  async deleteRemoteFile(filePath) {
666
- await this.sdk.convexClient.action(api2.sdk.remoteFileStorage.deleteFile, {
667
- path: this.toRemoteKey(filePath)
677
+ const url = this.getRemoteStorageUrl(filePath);
678
+ const jwt = await this.sdk.ensureGameplayJwt();
679
+ const response = await fetch(url, {
680
+ method: "DELETE",
681
+ headers: {
682
+ Authorization: `Bearer ${jwt}`
683
+ }
668
684
  });
685
+ if (!response.ok) {
686
+ const msg = `Failed to delete remote file ${filePath}: ${response.status} (${response.statusText})`;
687
+ this.sdk.logger.error(msg);
688
+ throw new Error(msg);
689
+ }
669
690
  return filePath;
670
691
  }
671
692
  /**
@@ -907,9 +928,9 @@ var FileSystemManager = class {
907
928
 
908
929
  // src/services/ugc.ts
909
930
  import { api as api3 } from "@wvdsh/api";
910
- var UGCManager = class {
931
+ var UGCManager = class extends WavedashManager {
911
932
  constructor(sdk) {
912
- this.sdk = sdk;
933
+ super(sdk);
913
934
  }
914
935
  async createUGCItem(ugcType, title, description, visibility, filePath) {
915
936
  const { ugcId, uploadUrl } = await this.sdk.convexClient.mutation(
@@ -931,10 +952,6 @@ var UGCManager = class {
931
952
  uploadUrl,
932
953
  filePath
933
954
  );
934
- await this.sdk.convexClient.mutation(
935
- api3.sdk.userGeneratedContent.finishUGCUpload,
936
- { success, ugcId }
937
- );
938
955
  if (!success) {
939
956
  throw new Error(`Failed to upload UGC item: ${filePath}`);
940
957
  }
@@ -961,16 +978,19 @@ var UGCManager = class {
961
978
  uploadUrl,
962
979
  filePath
963
980
  );
964
- await this.sdk.convexClient.mutation(
965
- api3.sdk.userGeneratedContent.finishUGCUpload,
966
- { success, ugcId }
967
- );
968
981
  if (!success) {
969
982
  throw new Error(`Failed to upload UGC item: ${filePath}`);
970
983
  }
971
984
  }
972
985
  return ugcId;
973
986
  }
987
+ async deleteUGCItem(ugcId) {
988
+ await this.sdk.convexClient.mutation(
989
+ api3.sdk.userGeneratedContent.deleteUGCItem,
990
+ { ugcId }
991
+ );
992
+ return ugcId;
993
+ }
974
994
  async downloadUGCItem(ugcId, filePath) {
975
995
  const downloadUrl = await this.sdk.convexClient.query(
976
996
  api3.sdk.userGeneratedContent.getUGCItemDownloadUrl,
@@ -989,11 +1009,11 @@ var UGCManager = class {
989
1009
 
990
1010
  // src/services/leaderboards.ts
991
1011
  import { api as api4 } from "@wvdsh/api";
992
- var LeaderboardManager = class {
1012
+ var LeaderboardManager = class extends WavedashManager {
993
1013
  constructor(sdk) {
1014
+ super(sdk);
994
1015
  // Cache leaderboards to return totalEntries synchronously without a network call
995
1016
  this.leaderboardCache = /* @__PURE__ */ new Map();
996
- this.sdk = sdk;
997
1017
  }
998
1018
  async getLeaderboard(name) {
999
1019
  const leaderboard = await this.sdk.convexClient.query(
@@ -1086,8 +1106,9 @@ var DEFAULT_P2P_CONFIG = {
1086
1106
  messageSize: 2048,
1087
1107
  maxIncomingMessages: 1024
1088
1108
  };
1089
- var _P2PManager = class _P2PManager {
1109
+ var _P2PManager = class _P2PManager extends WavedashManager {
1090
1110
  constructor(sdk) {
1111
+ super(sdk);
1091
1112
  this.currentConnection = null;
1092
1113
  // WebRTC connection state
1093
1114
  this.peerConnections = /* @__PURE__ */ new Map();
@@ -1171,9 +1192,11 @@ var _P2PManager = class _P2PManager {
1171
1192
  this.textEncoder = new TextEncoder();
1172
1193
  this.textDecoder = new TextDecoder();
1173
1194
  this.initialized = false;
1174
- this.sdk = sdk;
1175
1195
  this.config = { ...DEFAULT_P2P_CONFIG };
1176
1196
  }
1197
+ destroy() {
1198
+ this.disconnectP2P();
1199
+ }
1177
1200
  ensureInitialized() {
1178
1201
  if (!this.initialized) {
1179
1202
  this.init();
@@ -2403,8 +2426,10 @@ var P2PManager = _P2PManager;
2403
2426
  import { api as api6 } from "@wvdsh/api";
2404
2427
  import debounce2 from "lodash.debounce";
2405
2428
  var STORE_DEBOUNCE_MS = 1e3;
2406
- var StatsManager = class {
2429
+ var PERIODIC_PERSIST_MS = 1e4;
2430
+ var StatsManager = class extends WavedashManager {
2407
2431
  constructor(sdk) {
2432
+ super(sdk);
2408
2433
  // Current user values
2409
2434
  this.stats = /* @__PURE__ */ new Map();
2410
2435
  this.unlockedAchievements = /* @__PURE__ */ new Set();
@@ -2419,6 +2444,8 @@ var StatsManager = class {
2419
2444
  this.loaded = { stats: false, achievements: false };
2420
2445
  // Subscription cleanup
2421
2446
  this.subscriptions = [];
2447
+ // Background flush timer — see PERIODIC_PERSIST_MS
2448
+ this.periodicPersistInterval = null;
2422
2449
  // ================
2423
2450
  // Store / Persist
2424
2451
  // ================
@@ -2429,14 +2456,20 @@ var StatsManager = class {
2429
2456
  leading: true,
2430
2457
  trailing: true
2431
2458
  });
2432
- this.sdk = sdk;
2433
2459
  this.subscribe();
2434
2460
  this.requestStats().catch((error) => {
2435
2461
  this.sdk.logger.error("Initial stats fetch failed:", error);
2436
2462
  });
2463
+ this.periodicPersistInterval = setInterval(() => {
2464
+ void this.persist();
2465
+ }, PERIODIC_PERSIST_MS);
2437
2466
  }
2438
2467
  destroy() {
2439
2468
  this.debouncedPersist.cancel();
2469
+ if (this.periodicPersistInterval !== null) {
2470
+ clearInterval(this.periodicPersistInterval);
2471
+ this.periodicPersistInterval = null;
2472
+ }
2440
2473
  for (const unsub of this.subscriptions) unsub();
2441
2474
  this.subscriptions = [];
2442
2475
  }
@@ -2590,8 +2623,9 @@ import {
2590
2623
  HEARTBEAT,
2591
2624
  IFRAME_MESSAGE_TYPE as IFRAME_MESSAGE_TYPE2
2592
2625
  } from "@wvdsh/api";
2593
- var HeartbeatManager = class {
2626
+ var HeartbeatManager = class extends WavedashManager {
2594
2627
  constructor(sdk) {
2628
+ super(sdk);
2595
2629
  this.deviceFingerprint = void 0;
2596
2630
  this.testConnectionInterval = null;
2597
2631
  this.heartbeatInterval = null;
@@ -2610,7 +2644,6 @@ var HeartbeatManager = class {
2610
2644
  this.stop();
2611
2645
  }
2612
2646
  };
2613
- this.sdk = sdk;
2614
2647
  this.isConnected = this.sdk.convexClient.client.connectionState().isWebSocketConnected;
2615
2648
  document.addEventListener("visibilitychange", this.handleVisibilityChange);
2616
2649
  this.deviceFingerprintReady = this.sdk.iframeMessenger.requestFromParent(IFRAME_MESSAGE_TYPE2.GET_DEVICE_FINGERPRINT).then((fingerprint) => {
@@ -2754,10 +2787,10 @@ var HeartbeatManager = class {
2754
2787
  };
2755
2788
 
2756
2789
  // src/services/gameEvents.ts
2757
- var GameEventManager = class {
2790
+ var GameEventManager = class extends WavedashManager {
2758
2791
  constructor(sdk) {
2792
+ super(sdk);
2759
2793
  this.eventQueue = [];
2760
- this.sdk = sdk;
2761
2794
  }
2762
2795
  // ==============================
2763
2796
  // JS -> Game Event Broadcasting
@@ -2797,11 +2830,11 @@ var GameEventManager = class {
2797
2830
 
2798
2831
  // src/services/fullscreen.ts
2799
2832
  import { IFRAME_MESSAGE_TYPE as IFRAME_MESSAGE_TYPE3 } from "@wvdsh/api";
2800
- var FullscreenManager = class {
2833
+ var FullscreenManager = class extends WavedashManager {
2801
2834
  constructor(sdk) {
2835
+ super(sdk);
2802
2836
  this._isFullscreen = false;
2803
2837
  this.listeners = /* @__PURE__ */ new Set();
2804
- this.sdk = sdk;
2805
2838
  this.sdk.iframeMessenger.addEventListener(
2806
2839
  IFRAME_MESSAGE_TYPE3.FULLSCREEN_CHANGED,
2807
2840
  (data) => {
@@ -2886,15 +2919,15 @@ var FullscreenManager = class {
2886
2919
 
2887
2920
  // src/services/overlay.ts
2888
2921
  import { IFRAME_MESSAGE_TYPE as IFRAME_MESSAGE_TYPE4 } from "@wvdsh/api";
2889
- var OverlayManager = class {
2922
+ var OverlayManager = class extends WavedashManager {
2890
2923
  constructor(sdk) {
2924
+ super(sdk);
2891
2925
  this.handleKeyDown = (event) => {
2892
2926
  if (event.key === "Tab" && event.shiftKey) {
2893
2927
  event.preventDefault();
2894
2928
  this.toggleOverlay();
2895
2929
  }
2896
2930
  };
2897
- this.sdk = sdk;
2898
2931
  this.sdk.iframeMessenger.addEventListener(
2899
2932
  IFRAME_MESSAGE_TYPE4.TAKE_FOCUS,
2900
2933
  () => this.takeFocus()
@@ -2938,10 +2971,10 @@ function getCdnImageUrl(r2Key, host, options) {
2938
2971
  }
2939
2972
 
2940
2973
  // src/services/friends.ts
2941
- var FriendsManager = class {
2974
+ var FriendsManager = class extends WavedashManager {
2942
2975
  constructor(sdk) {
2976
+ super(sdk);
2943
2977
  this.userCache = /* @__PURE__ */ new Map();
2944
- this.sdk = sdk;
2945
2978
  }
2946
2979
  /**
2947
2980
  * Cache users from any source (friends, lobby users)
@@ -3240,7 +3273,7 @@ var WavedashSDK = class extends EventTarget {
3240
3273
  super();
3241
3274
  this._initialized = false;
3242
3275
  this._eventsReady = false;
3243
- this.sessionEndSent = false;
3276
+ this.destroyed = false;
3244
3277
  this.gameFinishedLoading = false;
3245
3278
  // Expose constants for easy access `Wavedash.LobbyVisibility.PUBLIC` etc.
3246
3279
  this.Events = WavedashEvents;
@@ -3272,7 +3305,6 @@ var WavedashSDK = class extends EventTarget {
3272
3305
  this.convexClient.setAuth(
3273
3306
  ({ forceRefreshToken }) => this.getAuthToken(forceRefreshToken)
3274
3307
  );
3275
- this.convexHttpUrl = sdkConfig.convexHttpUrl;
3276
3308
  this.wavedashUser = sdkConfig.wavedashUser;
3277
3309
  this.iframeMessenger = iframeMessenger;
3278
3310
  this.ugcHost = sdkConfig.ugcHost;
@@ -3289,6 +3321,19 @@ var WavedashSDK = class extends EventTarget {
3289
3321
  this.gameEventManager = new GameEventManager(this);
3290
3322
  this.fullscreenManager = new FullscreenManager(this);
3291
3323
  this.overlayManager = new OverlayManager(this);
3324
+ this.managers = [
3325
+ this.p2pManager,
3326
+ this.lobbyManager,
3327
+ this.statsManager,
3328
+ this.heartbeatManager,
3329
+ this.fileSystemManager,
3330
+ this.ugcManager,
3331
+ this.leaderboardManager,
3332
+ this.friendsManager,
3333
+ this.gameEventManager,
3334
+ this.fullscreenManager,
3335
+ this.overlayManager
3336
+ ];
3292
3337
  this.friendsManager.cacheUsers([
3293
3338
  {
3294
3339
  userId: this.wavedashUser.id,
@@ -3683,6 +3728,18 @@ var WavedashSDK = class extends EventTarget {
3683
3728
  filePath
3684
3729
  );
3685
3730
  }
3731
+ /**
3732
+ * Delete a UGC item: removes the row, the R2 object, and frees up the
3733
+ * user's storage quota by the size of the deleted upload.
3734
+ */
3735
+ async deleteUGCItem(ugcId) {
3736
+ return this.apiCall(
3737
+ this.ugcManager,
3738
+ "deleteUGCItem",
3739
+ [["ugcId", vId("userGeneratedContent")]],
3740
+ ugcId
3741
+ );
3742
+ }
3686
3743
  async downloadUGCItem(ugcId, filePath) {
3687
3744
  return this.apiCall(
3688
3745
  this.ugcManager,
@@ -4195,6 +4252,9 @@ var WavedashSDK = class extends EventTarget {
4195
4252
  throw new Error(`Failed to refresh gameplay token: ${response.status}`);
4196
4253
  }
4197
4254
  this.gameplayJwt = await response.text();
4255
+ iframeMessenger.postToParent(IFRAME_MESSAGE_TYPE5.GAMEPLAY_JWT_READY, {
4256
+ gameplayJwt: this.gameplayJwt
4257
+ });
4198
4258
  return this.gameplayJwt;
4199
4259
  })().finally(() => {
4200
4260
  if (this.gameplayJwtPromise === promise) {
@@ -4213,47 +4273,19 @@ var WavedashSDK = class extends EventTarget {
4213
4273
  return this.getAuthToken();
4214
4274
  }
4215
4275
  /**
4216
- * Set up listeners that flush the end-of-session request when the iframe
4217
- * is going away. We listen for three signals:
4218
- * - `beforeunload` / `pagehide` on our own window: covers tab close, hard
4219
- * reload, and top-level navigation of the parent.
4220
- * - `END_SESSION` postMessage from the parent: covers parent SPA navigation
4276
+ * Tear down every manager. Called on the parent's `END_SESSION` signal
4221
4277
  */
4278
+ destroy() {
4279
+ if (this.destroyed) return;
4280
+ this.destroyed = true;
4281
+ for (const manager of this.managers) {
4282
+ manager.destroy();
4283
+ }
4284
+ }
4222
4285
  setupSessionEndListeners() {
4223
- const endSessionEndpoint = `${this.convexHttpUrl}/gameplay/end-session`;
4224
- const endGameplaySession = () => {
4225
- if (this.sessionEndSent) return;
4226
- if (!this.gameplayJwt) return;
4227
- this.sessionEndSent = true;
4228
- const pendingData = this.statsManager.getPendingData();
4229
- this.lobbyManager.destroy();
4230
- this.heartbeatManager.destroy();
4231
- this.statsManager.destroy();
4232
- const body = {
4233
- gameplayJwt: this.gameplayJwt
4234
- };
4235
- if (pendingData?.stats?.length) {
4236
- body.stats = pendingData.stats;
4237
- }
4238
- if (pendingData?.achievements?.length) {
4239
- body.achievements = pendingData.achievements;
4240
- }
4241
- const payload = JSON.stringify(body);
4242
- const beaconSent = navigator?.sendBeacon?.(endSessionEndpoint, payload);
4243
- if (!beaconSent) {
4244
- fetch(endSessionEndpoint, {
4245
- method: "POST",
4246
- body: payload,
4247
- keepalive: true
4248
- }).catch(() => {
4249
- });
4250
- }
4251
- };
4252
- window.addEventListener("beforeunload", endGameplaySession);
4253
- window.addEventListener("pagehide", endGameplaySession);
4254
4286
  iframeMessenger.addEventListener(
4255
4287
  IFRAME_MESSAGE_TYPE5.END_SESSION,
4256
- endGameplaySession
4288
+ () => this.destroy()
4257
4289
  );
4258
4290
  }
4259
4291
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wvdsh/sdk-js",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "type": "module",
5
5
  "description": "Wavedash JavaScript SDK",
6
6
  "main": "./dist/client.js",
@@ -49,7 +49,7 @@
49
49
  "typescript-eslint": "^8.52.0"
50
50
  },
51
51
  "dependencies": {
52
- "@wvdsh/api": "^0.1.10",
52
+ "@wvdsh/api": "^0.1.14",
53
53
  "convex": "^1.34.0",
54
54
  "lodash.debounce": "^4.0.8"
55
55
  }