@wvdsh/sdk-js 1.3.13 → 1.3.15

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/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { WavedashSDK } from './index.js';
2
- export { BackendConnectionPayload, EngineInstance, Friend, FullscreenChangedPayload, Leaderboard, LeaderboardDisplayType, LeaderboardEntries, LeaderboardSortOrder, ListUGCItemsArgs, Lobby, LobbyDataUpdatedPayload, LobbyInvite, LobbyInvitePayload, LobbyJoinResponse, LobbyJoinedPayload, LobbyKickedPayload, LobbyKickedReason, LobbyMessage, LobbyMessagePayload, LobbyUser, LobbyUserChangeType, LobbyUsersUpdatedPayload, LobbyVisibility, P2PConfig, P2PConnection, P2PConnectionEstablishedPayload, P2PConnectionFailedPayload, P2PMessage, P2PPacketDropReason, P2PPacketDroppedPayload, P2PPeer, P2PPeerDisconnectedPayload, P2PPeerReconnectedPayload, P2PPeerReconnectingPayload, PaginatedUGCItems, RemoteFileMetadata, StatsStoredPayload, UGCItem, UGCType, UGCVisibility, UpdateUGCItemArgs, UpsertedLeaderboardEntry, WavedashConfig, WavedashEvent, WavedashEventMap, WavedashResponse } from './index.js';
2
+ export { BackendConnectionPayload, EngineInstance, Friend, FullscreenChangedPayload, Leaderboard, LeaderboardDisplayType, LeaderboardEntries, LeaderboardSortOrder, ListUGCItemsArgs, Lobby, LobbyDataUpdatedPayload, LobbyInvite, LobbyInvitePayload, LobbyJoinResponse, LobbyJoinedPayload, LobbyKickedPayload, LobbyKickedReason, LobbyMessage, LobbyMessagePayload, LobbyUser, LobbyUserChangeType, LobbyUsersUpdatedPayload, LobbyVisibility, MuteChangedPayload, P2PConfig, P2PConnection, P2PConnectionEstablishedPayload, P2PConnectionFailedPayload, P2PMessage, P2PPacketDropReason, P2PPacketDroppedPayload, P2PPeer, P2PPeerDisconnectedPayload, P2PPeerReconnectedPayload, P2PPeerReconnectingPayload, PaginatedUGCItems, RemoteFileMetadata, StatsStoredPayload, UGCItem, UGCType, UGCVisibility, UpdateUGCItemArgs, UpsertedLeaderboardEntry, WavedashConfig, WavedashEvent, WavedashEventMap, WavedashResponse } from './index.js';
3
3
  export { GameLaunchParams } from '@wvdsh/api';
4
4
  export { GenericId as Id } from 'convex/values';
5
5
  import 'convex/browser';
package/dist/index.d.ts CHANGED
@@ -31,6 +31,7 @@ declare const WavedashEvents: {
31
31
  readonly BACKEND_DISCONNECTED: "BackendDisconnected";
32
32
  readonly BACKEND_RECONNECTING: "BackendReconnecting";
33
33
  readonly FULLSCREEN_CHANGED: "FullscreenChanged";
34
+ readonly MUTE_CHANGED: "MuteChanged";
34
35
  };
35
36
 
36
37
  /** Reasons why a user was kicked from a lobby */
@@ -214,6 +215,10 @@ interface BackendConnectionPayload {
214
215
  interface FullscreenChangedPayload {
215
216
  isFullscreen: boolean;
216
217
  }
218
+ /** Payload for MuteChanged event - emitted when mute state flips */
219
+ interface MuteChangedPayload {
220
+ isMuted: boolean;
221
+ }
217
222
  type WavedashEventMap = {
218
223
  [WavedashEvents.LOBBY_MESSAGE]: LobbyMessagePayload;
219
224
  [WavedashEvents.LOBBY_JOINED]: LobbyJoinedPayload;
@@ -232,6 +237,7 @@ type WavedashEventMap = {
232
237
  [WavedashEvents.BACKEND_DISCONNECTED]: BackendConnectionPayload;
233
238
  [WavedashEvents.BACKEND_RECONNECTING]: BackendConnectionPayload;
234
239
  [WavedashEvents.FULLSCREEN_CHANGED]: FullscreenChangedPayload;
240
+ [WavedashEvents.MUTE_CHANGED]: MuteChangedPayload;
235
241
  };
236
242
  interface P2PPeer {
237
243
  userId: GenericId<"users">;
@@ -619,36 +625,53 @@ declare class HeartbeatManager extends WavedashManager {
619
625
  private deviceFingerprintReady;
620
626
  private testConnectionInterval;
621
627
  private heartbeatInterval;
628
+ private gamepadPollInterval;
629
+ private inactivityTimeout;
622
630
  private isConnected;
623
631
  private sentDisconnectedEvent;
624
632
  private disconnectedAt;
625
633
  private lastHeartbeatTime;
634
+ private lastInputResetAt;
626
635
  private heartbeatInFlight;
627
636
  private isFirstTick;
628
637
  private readonly TEST_CONNECTION_INTERVAL_MS;
629
638
  private readonly DISCONNECTED_TIMEOUT_MS;
639
+ private readonly INACTIVITY_TIMEOUT_MS;
640
+ private readonly INPUT_THROTTLE_MS;
641
+ private readonly GAMEPAD_POLL_INTERVAL_MS;
642
+ private readonly GAMEPAD_AXIS_DEADZONE;
630
643
  private cachedPresenceData;
631
644
  constructor(sdk: WavedashSDK);
632
- /** Start heartbeat and connection-check intervals */
645
+ /**
646
+ * Start (or refresh) the heartbeat. Idempotent: if intervals are already
647
+ * running this just reschedules the inactivity timer. No-op if the game
648
+ * hasn't loaded yet or the tab is hidden.
649
+ */
633
650
  start(): void;
634
- /** Stop heartbeat and connection-check intervals */
651
+ /** Stop the heartbeat and clear the inactivity timer. Idempotent. */
635
652
  stop(): void;
636
- /** Full teardown — stops intervals and removes all listeners */
637
- destroy(): void;
638
- private handleVisibilityChange;
639
- private tickHeartbeat;
640
- private sendHeartbeat;
641
653
  /**
642
654
  * Updates user presence in the backend.
643
655
  * @param data - Data to send to the backend
644
656
  * @returns true if the presence was updated successfully
645
657
  */
646
658
  updateUserPresence(data: Record<string, string | number | boolean | null>): Promise<boolean>;
659
+ isCurrentlyConnected(): boolean;
660
+ /** Full teardown — stops intervals and removes all listeners */
661
+ destroy(): void;
662
+ private tickHeartbeat;
663
+ private sendHeartbeat;
664
+ private handleVisibilityChange;
665
+ private handleUserInput;
666
+ /**
667
+ * Polls connected gamepads; any pressed button or out-of-deadzone axis
668
+ * counts as user activity and (re)starts the heartbeat.
669
+ */
670
+ private pollGamepads;
647
671
  /**
648
672
  * Tests the connection to the backend
649
673
  */
650
674
  private testConnection;
651
- isCurrentlyConnected(): boolean;
652
675
  }
653
676
 
654
677
  declare class GameEventManager extends WavedashManager {
@@ -873,6 +896,7 @@ declare class WavedashSDK extends EventTarget {
873
896
  readonly BACKEND_DISCONNECTED: "BackendDisconnected";
874
897
  readonly BACKEND_RECONNECTING: "BackendReconnecting";
875
898
  readonly FULLSCREEN_CHANGED: "FullscreenChanged";
899
+ readonly MUTE_CHANGED: "MuteChanged";
876
900
  };
877
901
  LobbyVisibility: {
878
902
  readonly PUBLIC: 0;
@@ -1261,4 +1285,4 @@ declare global {
1261
1285
 
1262
1286
  declare function setupWavedashSDK(): WavedashSDK;
1263
1287
 
1264
- export { type BackendConnectionPayload, type EngineInstance, type Friend, type FullscreenChangedPayload, type Leaderboard, type LeaderboardDisplayType, type LeaderboardEntries, type LeaderboardSortOrder, type ListUGCItemsArgs, type Lobby, type LobbyDataUpdatedPayload, type LobbyInvite, type LobbyInvitePayload, type LobbyJoinResponse, type LobbyJoinedPayload, type LobbyKickedPayload, type LobbyKickedReason, type LobbyMessage, type LobbyMessagePayload, type LobbyUser, type LobbyUserChangeType, type LobbyUsersUpdatedPayload, type LobbyVisibility, type P2PConfig, type P2PConnection, type P2PConnectionEstablishedPayload, type P2PConnectionFailedPayload, type P2PMessage, type P2PPacketDropReason, type P2PPacketDroppedPayload, type P2PPeer, type P2PPeerDisconnectedPayload, type P2PPeerReconnectedPayload, type P2PPeerReconnectingPayload, type PaginatedUGCItems, type RemoteFileMetadata, type StatsStoredPayload, type UGCItem, type UGCType, type UGCVisibility, type UpdateUGCItemArgs, type UpsertedLeaderboardEntry, type WavedashConfig, type WavedashEvent, type WavedashEventMap, type WavedashResponse, WavedashSDK, setupWavedashSDK };
1288
+ export { type BackendConnectionPayload, type EngineInstance, type Friend, type FullscreenChangedPayload, type Leaderboard, type LeaderboardDisplayType, type LeaderboardEntries, type LeaderboardSortOrder, type ListUGCItemsArgs, type Lobby, type LobbyDataUpdatedPayload, type LobbyInvite, type LobbyInvitePayload, type LobbyJoinResponse, type LobbyJoinedPayload, type LobbyKickedPayload, type LobbyKickedReason, type LobbyMessage, type LobbyMessagePayload, type LobbyUser, type LobbyUserChangeType, type LobbyUsersUpdatedPayload, type LobbyVisibility, type MuteChangedPayload, type P2PConfig, type P2PConnection, type P2PConnectionEstablishedPayload, type P2PConnectionFailedPayload, type P2PMessage, type P2PPacketDropReason, type P2PPacketDroppedPayload, type P2PPeer, type P2PPeerDisconnectedPayload, type P2PPeerReconnectedPayload, type P2PPeerReconnectingPayload, type PaginatedUGCItems, type RemoteFileMetadata, type StatsStoredPayload, type UGCItem, type UGCType, type UGCVisibility, type UpdateUGCItemArgs, type UpsertedLeaderboardEntry, type WavedashConfig, type WavedashEvent, type WavedashEventMap, type WavedashResponse, WavedashSDK, setupWavedashSDK };
package/dist/index.js CHANGED
@@ -75,8 +75,11 @@ var WavedashEvents = {
75
75
  BACKEND_RECONNECTING: "BackendReconnecting",
76
76
  // attempting to reconnect to backend
77
77
  // Fullscreen events
78
- FULLSCREEN_CHANGED: "FullscreenChanged"
78
+ FULLSCREEN_CHANGED: "FullscreenChanged",
79
79
  // fullscreen state changed
80
+ // Audio events
81
+ MUTE_CHANGED: "MuteChanged"
82
+ // mute state changed
80
83
  // TODO: Future events to implement
81
84
  // P2P_CONNECTION_REQUESTED: 'P2PConnectionRequested', // for now we always connect all lobby members
82
85
  };
@@ -2695,20 +2698,31 @@ import {
2695
2698
  HEARTBEAT,
2696
2699
  IFRAME_MESSAGE_TYPE as IFRAME_MESSAGE_TYPE2
2697
2700
  } from "@wvdsh/api";
2701
+ var INPUT_LISTENER_OPTS = {
2702
+ passive: true,
2703
+ capture: true
2704
+ };
2698
2705
  var HeartbeatManager = class extends WavedashManager {
2699
2706
  constructor(sdk) {
2700
2707
  super(sdk);
2701
2708
  this.deviceFingerprint = void 0;
2702
2709
  this.testConnectionInterval = null;
2703
2710
  this.heartbeatInterval = null;
2711
+ this.gamepadPollInterval = null;
2712
+ this.inactivityTimeout = null;
2704
2713
  this.isConnected = false;
2705
2714
  this.sentDisconnectedEvent = false;
2706
2715
  this.disconnectedAt = null;
2707
2716
  this.lastHeartbeatTime = 0;
2717
+ this.lastInputResetAt = 0;
2708
2718
  this.heartbeatInFlight = false;
2709
2719
  this.isFirstTick = true;
2710
2720
  this.TEST_CONNECTION_INTERVAL_MS = 1e3;
2711
2721
  this.DISCONNECTED_TIMEOUT_MS = 9e4;
2722
+ this.INACTIVITY_TIMEOUT_MS = 30 * 60 * 1e3;
2723
+ this.INPUT_THROTTLE_MS = 1e3;
2724
+ this.GAMEPAD_POLL_INTERVAL_MS = 1e3;
2725
+ this.GAMEPAD_AXIS_DEADZONE = 0.2;
2712
2726
  this.cachedPresenceData = {};
2713
2727
  this.handleVisibilityChange = () => {
2714
2728
  if (document.visibilityState === "visible") {
@@ -2717,19 +2731,53 @@ var HeartbeatManager = class extends WavedashManager {
2717
2731
  this.stop();
2718
2732
  }
2719
2733
  };
2734
+ this.handleUserInput = () => {
2735
+ const now = Date.now();
2736
+ if (now - this.lastInputResetAt < this.INPUT_THROTTLE_MS) return;
2737
+ this.lastInputResetAt = now;
2738
+ this.start();
2739
+ };
2720
2740
  this.isConnected = this.sdk.convexClient.client.connectionState().isWebSocketConnected;
2721
2741
  document.addEventListener("visibilitychange", this.handleVisibilityChange);
2742
+ window.addEventListener(
2743
+ "keydown",
2744
+ this.handleUserInput,
2745
+ INPUT_LISTENER_OPTS
2746
+ );
2747
+ window.addEventListener(
2748
+ "pointerdown",
2749
+ this.handleUserInput,
2750
+ INPUT_LISTENER_OPTS
2751
+ );
2752
+ window.addEventListener(
2753
+ "pointermove",
2754
+ this.handleUserInput,
2755
+ INPUT_LISTENER_OPTS
2756
+ );
2757
+ window.addEventListener("wheel", this.handleUserInput, INPUT_LISTENER_OPTS);
2758
+ this.gamepadPollInterval = setInterval(() => {
2759
+ this.pollGamepads();
2760
+ }, this.GAMEPAD_POLL_INTERVAL_MS);
2722
2761
  this.deviceFingerprintReady = this.sdk.iframeMessenger.requestFromParent(IFRAME_MESSAGE_TYPE2.GET_DEVICE_FINGERPRINT).then((fingerprint) => {
2723
2762
  this.deviceFingerprint = fingerprint;
2724
2763
  }).catch(() => {
2725
2764
  });
2726
2765
  }
2727
- /** Start heartbeat and connection-check intervals */
2766
+ /**
2767
+ * Start (or refresh) the heartbeat. Idempotent: if intervals are already
2768
+ * running this just reschedules the inactivity timer. No-op if the game
2769
+ * hasn't loaded yet or the tab is hidden.
2770
+ */
2728
2771
  start() {
2729
- if (!this.sdk.gameLoaded) {
2730
- return;
2731
- }
2732
- this.stop();
2772
+ if (!this.sdk.gameLoaded) return;
2773
+ if (document.visibilityState !== "visible") return;
2774
+ if (this.inactivityTimeout !== null) {
2775
+ clearTimeout(this.inactivityTimeout);
2776
+ }
2777
+ this.inactivityTimeout = setTimeout(() => {
2778
+ this.stop();
2779
+ }, this.INACTIVITY_TIMEOUT_MS);
2780
+ if (this.heartbeatInterval !== null) return;
2733
2781
  if (this.isFirstTick) {
2734
2782
  void this.deviceFingerprintReady.then(() => {
2735
2783
  if (!this.sdk.gameLoaded || !this.isFirstTick) return;
@@ -2745,8 +2793,12 @@ var HeartbeatManager = class extends WavedashManager {
2745
2793
  this.testConnection();
2746
2794
  }, this.TEST_CONNECTION_INTERVAL_MS);
2747
2795
  }
2748
- /** Stop heartbeat and connection-check intervals */
2796
+ /** Stop the heartbeat and clear the inactivity timer. Idempotent. */
2749
2797
  stop() {
2798
+ if (this.inactivityTimeout !== null) {
2799
+ clearTimeout(this.inactivityTimeout);
2800
+ this.inactivityTimeout = null;
2801
+ }
2750
2802
  if (this.heartbeatInterval !== null) {
2751
2803
  clearInterval(this.heartbeatInterval);
2752
2804
  this.heartbeatInterval = null;
@@ -2756,14 +2808,62 @@ var HeartbeatManager = class extends WavedashManager {
2756
2808
  this.testConnectionInterval = null;
2757
2809
  }
2758
2810
  }
2811
+ /**
2812
+ * Updates user presence in the backend.
2813
+ * @param data - Data to send to the backend
2814
+ * @returns true if the presence was updated successfully
2815
+ */
2816
+ async updateUserPresence(data) {
2817
+ try {
2818
+ this.cachedPresenceData = data;
2819
+ await this.sdk.convexClient.mutation(api7.sdk.presence.heartbeat, {
2820
+ data,
2821
+ deviceFingerprint: this.deviceFingerprint
2822
+ });
2823
+ return true;
2824
+ } catch (error) {
2825
+ logger.error(`Error updating presence: ${error}`);
2826
+ return false;
2827
+ }
2828
+ }
2829
+ isCurrentlyConnected() {
2830
+ return this.isConnected;
2831
+ }
2759
2832
  /** Full teardown — stops intervals and removes all listeners */
2760
2833
  destroy() {
2761
2834
  this.stop();
2835
+ if (this.gamepadPollInterval !== null) {
2836
+ clearInterval(this.gamepadPollInterval);
2837
+ this.gamepadPollInterval = null;
2838
+ }
2762
2839
  document.removeEventListener(
2763
2840
  "visibilitychange",
2764
2841
  this.handleVisibilityChange
2765
2842
  );
2843
+ window.removeEventListener(
2844
+ "keydown",
2845
+ this.handleUserInput,
2846
+ INPUT_LISTENER_OPTS
2847
+ );
2848
+ window.removeEventListener(
2849
+ "pointerdown",
2850
+ this.handleUserInput,
2851
+ INPUT_LISTENER_OPTS
2852
+ );
2853
+ window.removeEventListener(
2854
+ "pointermove",
2855
+ this.handleUserInput,
2856
+ INPUT_LISTENER_OPTS
2857
+ );
2858
+ window.removeEventListener(
2859
+ "wheel",
2860
+ this.handleUserInput,
2861
+ INPUT_LISTENER_OPTS
2862
+ );
2766
2863
  }
2864
+ // =================
2865
+ // Private functions
2866
+ // =================
2767
2867
  tickHeartbeat() {
2768
2868
  const timeSinceLastHeartbeat = Date.now() - this.lastHeartbeatTime;
2769
2869
  const needsReestablish = this.isFirstTick || timeSinceLastHeartbeat >= HEARTBEAT.CLIENT_REESTABLISH_THRESHOLD_MS;
@@ -2793,21 +2893,22 @@ var HeartbeatManager = class extends WavedashManager {
2793
2893
  });
2794
2894
  }
2795
2895
  /**
2796
- * Updates user presence in the backend.
2797
- * @param data - Data to send to the backend
2798
- * @returns true if the presence was updated successfully
2896
+ * Polls connected gamepads; any pressed button or out-of-deadzone axis
2897
+ * counts as user activity and (re)starts the heartbeat.
2799
2898
  */
2800
- async updateUserPresence(data) {
2801
- try {
2802
- this.cachedPresenceData = data;
2803
- await this.sdk.convexClient.mutation(api7.sdk.presence.heartbeat, {
2804
- data,
2805
- deviceFingerprint: this.deviceFingerprint
2806
- });
2807
- return true;
2808
- } catch (error) {
2809
- logger.error(`Error updating presence: ${error}`);
2810
- return false;
2899
+ pollGamepads() {
2900
+ if (typeof navigator === "undefined" || !navigator.getGamepads) return;
2901
+ const pads = navigator.getGamepads();
2902
+ for (const pad of pads) {
2903
+ if (!pad) continue;
2904
+ if (pad.buttons.some((b) => b.pressed)) {
2905
+ this.start();
2906
+ return;
2907
+ }
2908
+ if (pad.axes.some((a) => Math.abs(a) > this.GAMEPAD_AXIS_DEADZONE)) {
2909
+ this.start();
2910
+ return;
2911
+ }
2811
2912
  }
2812
2913
  }
2813
2914
  /**
@@ -2854,9 +2955,6 @@ var HeartbeatManager = class extends WavedashManager {
2854
2955
  logger.error("Error testing connection:", error);
2855
2956
  }
2856
2957
  }
2857
- isCurrentlyConnected() {
2858
- return this.isConnected;
2859
- }
2860
2958
  };
2861
2959
 
2862
2960
  // src/services/gameEvents.ts
@@ -3070,7 +3168,6 @@ var AudioManager = class extends WavedashManager {
3070
3168
  this.handleMute = (data) => {
3071
3169
  if (this._isMuted === data.isMuted) return;
3072
3170
  this._isMuted = data.isMuted;
3073
- logger.debug(`[AudioManager] muted=${this._isMuted}`);
3074
3171
  const target = this._isMuted ? 0 : 1;
3075
3172
  this.contexts.forEach((gain, ctx) => {
3076
3173
  const now = ctx.currentTime;
@@ -3085,6 +3182,10 @@ var AudioManager = class extends WavedashManager {
3085
3182
  setMutedNative.call(el, this._isMuted ? true : intended);
3086
3183
  });
3087
3184
  }
3185
+ this.sdk.gameEventManager.notifyGame(
3186
+ WavedashEvents.MUTE_CHANGED,
3187
+ { isMuted: this._isMuted }
3188
+ );
3088
3189
  };
3089
3190
  this.installShims();
3090
3191
  this.sdk.iframeMessenger.addEventListener(
@@ -3667,11 +3768,11 @@ var WavedashSDK = class extends EventTarget {
3667
3768
  expectAuth: true
3668
3769
  });
3669
3770
  this.gameCloudId = sdkConfig.gameCloudId;
3771
+ this.iframeMessenger = iframeMessenger;
3670
3772
  this.convexClient.setAuth(
3671
3773
  ({ forceRefreshToken }) => this.getAuthToken(forceRefreshToken)
3672
3774
  );
3673
3775
  this.wavedashUser = sdkConfig.wavedashUser;
3674
- this.iframeMessenger = iframeMessenger;
3675
3776
  this.ugcHost = sdkConfig.ugcHost;
3676
3777
  this.uploadsHost = sdkConfig.uploadsHost;
3677
3778
  this.swMessenger = new SwMessenger();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wvdsh/sdk-js",
3
- "version": "1.3.13",
3
+ "version": "1.3.15",
4
4
  "type": "module",
5
5
  "description": "Wavedash JavaScript SDK",
6
6
  "main": "./dist/client.js",