@wvdsh/sdk-js 1.3.14 → 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/index.d.ts CHANGED
@@ -625,36 +625,53 @@ declare class HeartbeatManager extends WavedashManager {
625
625
  private deviceFingerprintReady;
626
626
  private testConnectionInterval;
627
627
  private heartbeatInterval;
628
+ private gamepadPollInterval;
629
+ private inactivityTimeout;
628
630
  private isConnected;
629
631
  private sentDisconnectedEvent;
630
632
  private disconnectedAt;
631
633
  private lastHeartbeatTime;
634
+ private lastInputResetAt;
632
635
  private heartbeatInFlight;
633
636
  private isFirstTick;
634
637
  private readonly TEST_CONNECTION_INTERVAL_MS;
635
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;
636
643
  private cachedPresenceData;
637
644
  constructor(sdk: WavedashSDK);
638
- /** 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
+ */
639
650
  start(): void;
640
- /** Stop heartbeat and connection-check intervals */
651
+ /** Stop the heartbeat and clear the inactivity timer. Idempotent. */
641
652
  stop(): void;
642
- /** Full teardown — stops intervals and removes all listeners */
643
- destroy(): void;
644
- private handleVisibilityChange;
645
- private tickHeartbeat;
646
- private sendHeartbeat;
647
653
  /**
648
654
  * Updates user presence in the backend.
649
655
  * @param data - Data to send to the backend
650
656
  * @returns true if the presence was updated successfully
651
657
  */
652
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;
653
671
  /**
654
672
  * Tests the connection to the backend
655
673
  */
656
674
  private testConnection;
657
- isCurrentlyConnected(): boolean;
658
675
  }
659
676
 
660
677
  declare class GameEventManager extends WavedashManager {
package/dist/index.js CHANGED
@@ -2698,20 +2698,31 @@ import {
2698
2698
  HEARTBEAT,
2699
2699
  IFRAME_MESSAGE_TYPE as IFRAME_MESSAGE_TYPE2
2700
2700
  } from "@wvdsh/api";
2701
+ var INPUT_LISTENER_OPTS = {
2702
+ passive: true,
2703
+ capture: true
2704
+ };
2701
2705
  var HeartbeatManager = class extends WavedashManager {
2702
2706
  constructor(sdk) {
2703
2707
  super(sdk);
2704
2708
  this.deviceFingerprint = void 0;
2705
2709
  this.testConnectionInterval = null;
2706
2710
  this.heartbeatInterval = null;
2711
+ this.gamepadPollInterval = null;
2712
+ this.inactivityTimeout = null;
2707
2713
  this.isConnected = false;
2708
2714
  this.sentDisconnectedEvent = false;
2709
2715
  this.disconnectedAt = null;
2710
2716
  this.lastHeartbeatTime = 0;
2717
+ this.lastInputResetAt = 0;
2711
2718
  this.heartbeatInFlight = false;
2712
2719
  this.isFirstTick = true;
2713
2720
  this.TEST_CONNECTION_INTERVAL_MS = 1e3;
2714
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;
2715
2726
  this.cachedPresenceData = {};
2716
2727
  this.handleVisibilityChange = () => {
2717
2728
  if (document.visibilityState === "visible") {
@@ -2720,19 +2731,53 @@ var HeartbeatManager = class extends WavedashManager {
2720
2731
  this.stop();
2721
2732
  }
2722
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
+ };
2723
2740
  this.isConnected = this.sdk.convexClient.client.connectionState().isWebSocketConnected;
2724
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);
2725
2761
  this.deviceFingerprintReady = this.sdk.iframeMessenger.requestFromParent(IFRAME_MESSAGE_TYPE2.GET_DEVICE_FINGERPRINT).then((fingerprint) => {
2726
2762
  this.deviceFingerprint = fingerprint;
2727
2763
  }).catch(() => {
2728
2764
  });
2729
2765
  }
2730
- /** 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
+ */
2731
2771
  start() {
2732
- if (!this.sdk.gameLoaded) {
2733
- return;
2734
- }
2735
- 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;
2736
2781
  if (this.isFirstTick) {
2737
2782
  void this.deviceFingerprintReady.then(() => {
2738
2783
  if (!this.sdk.gameLoaded || !this.isFirstTick) return;
@@ -2748,8 +2793,12 @@ var HeartbeatManager = class extends WavedashManager {
2748
2793
  this.testConnection();
2749
2794
  }, this.TEST_CONNECTION_INTERVAL_MS);
2750
2795
  }
2751
- /** Stop heartbeat and connection-check intervals */
2796
+ /** Stop the heartbeat and clear the inactivity timer. Idempotent. */
2752
2797
  stop() {
2798
+ if (this.inactivityTimeout !== null) {
2799
+ clearTimeout(this.inactivityTimeout);
2800
+ this.inactivityTimeout = null;
2801
+ }
2753
2802
  if (this.heartbeatInterval !== null) {
2754
2803
  clearInterval(this.heartbeatInterval);
2755
2804
  this.heartbeatInterval = null;
@@ -2759,14 +2808,62 @@ var HeartbeatManager = class extends WavedashManager {
2759
2808
  this.testConnectionInterval = null;
2760
2809
  }
2761
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
+ }
2762
2832
  /** Full teardown — stops intervals and removes all listeners */
2763
2833
  destroy() {
2764
2834
  this.stop();
2835
+ if (this.gamepadPollInterval !== null) {
2836
+ clearInterval(this.gamepadPollInterval);
2837
+ this.gamepadPollInterval = null;
2838
+ }
2765
2839
  document.removeEventListener(
2766
2840
  "visibilitychange",
2767
2841
  this.handleVisibilityChange
2768
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
+ );
2769
2863
  }
2864
+ // =================
2865
+ // Private functions
2866
+ // =================
2770
2867
  tickHeartbeat() {
2771
2868
  const timeSinceLastHeartbeat = Date.now() - this.lastHeartbeatTime;
2772
2869
  const needsReestablish = this.isFirstTick || timeSinceLastHeartbeat >= HEARTBEAT.CLIENT_REESTABLISH_THRESHOLD_MS;
@@ -2796,21 +2893,22 @@ var HeartbeatManager = class extends WavedashManager {
2796
2893
  });
2797
2894
  }
2798
2895
  /**
2799
- * Updates user presence in the backend.
2800
- * @param data - Data to send to the backend
2801
- * @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.
2802
2898
  */
2803
- async updateUserPresence(data) {
2804
- try {
2805
- this.cachedPresenceData = data;
2806
- await this.sdk.convexClient.mutation(api7.sdk.presence.heartbeat, {
2807
- data,
2808
- deviceFingerprint: this.deviceFingerprint
2809
- });
2810
- return true;
2811
- } catch (error) {
2812
- logger.error(`Error updating presence: ${error}`);
2813
- 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
+ }
2814
2912
  }
2815
2913
  }
2816
2914
  /**
@@ -2857,9 +2955,6 @@ var HeartbeatManager = class extends WavedashManager {
2857
2955
  logger.error("Error testing connection:", error);
2858
2956
  }
2859
2957
  }
2860
- isCurrentlyConnected() {
2861
- return this.isConnected;
2862
- }
2863
2958
  };
2864
2959
 
2865
2960
  // src/services/gameEvents.ts
@@ -3673,11 +3768,11 @@ var WavedashSDK = class extends EventTarget {
3673
3768
  expectAuth: true
3674
3769
  });
3675
3770
  this.gameCloudId = sdkConfig.gameCloudId;
3771
+ this.iframeMessenger = iframeMessenger;
3676
3772
  this.convexClient.setAuth(
3677
3773
  ({ forceRefreshToken }) => this.getAuthToken(forceRefreshToken)
3678
3774
  );
3679
3775
  this.wavedashUser = sdkConfig.wavedashUser;
3680
- this.iframeMessenger = iframeMessenger;
3681
3776
  this.ugcHost = sdkConfig.ugcHost;
3682
3777
  this.uploadsHost = sdkConfig.uploadsHost;
3683
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.14",
3
+ "version": "1.3.15",
4
4
  "type": "module",
5
5
  "description": "Wavedash JavaScript SDK",
6
6
  "main": "./dist/client.js",