@wvdsh/sdk-js 1.2.2 → 1.2.4

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
@@ -2,7 +2,7 @@ import { ConvexClient } from 'convex/browser';
2
2
  import { GenericId } from 'convex/values';
3
3
  export { GenericId as Id } from 'convex/values';
4
4
  import { FunctionReturnType } from 'convex/server';
5
- import { PublicApiType, api, GAME_ENGINE, SDKUser, IFRAME_MESSAGE_TYPE, IFrameEventPayloadMap, SDKConfig, GameLaunchParams } from '@wvdsh/api';
5
+ import { PublicApiType, api, GAME_ENGINE, SDKUser, IFrameEventPayloadMap, IFRAME_MESSAGE_TYPE, SDKConfig, GameLaunchParams } from '@wvdsh/api';
6
6
  export { GameLaunchParams } from '@wvdsh/api';
7
7
 
8
8
  /**
@@ -30,6 +30,7 @@ declare const WavedashEvents: {
30
30
  readonly BACKEND_CONNECTED: "BackendConnected";
31
31
  readonly BACKEND_DISCONNECTED: "BackendDisconnected";
32
32
  readonly BACKEND_RECONNECTING: "BackendReconnecting";
33
+ readonly FULLSCREEN_CHANGED: "FullscreenChanged";
33
34
  };
34
35
 
35
36
  type LobbyVisibility = PublicApiType["sdk"]["gameLobby"]["createAndJoinLobby"]["_args"]["visibility"];
@@ -72,7 +73,7 @@ interface RemoteFileMetadata {
72
73
  }
73
74
  interface EngineInstance {
74
75
  type: (typeof GAME_ENGINE)[keyof typeof GAME_ENGINE];
75
- SendMessage(objectName: string, methodName: WavedashEvent, value?: string | number | boolean): void;
76
+ SendMessage(objectName: string, methodName: WavedashEvent, value?: string | number): void;
76
77
  FS: {
77
78
  readFile(path: string, opts?: Record<string, unknown>): string | Uint8Array;
78
79
  writeFile(path: string, data: string | ArrayBufferView, opts?: Record<string, unknown>): void;
@@ -184,6 +185,10 @@ interface BackendConnectionPayload {
184
185
  connectionCount: number;
185
186
  connectionRetries: number;
186
187
  }
188
+ /** Payload for FullscreenChanged event - emitted when fullscreen state flips */
189
+ interface FullscreenChangedPayload {
190
+ isFullscreen: boolean;
191
+ }
187
192
  interface P2PPeer {
188
193
  userId: GenericId<"users">;
189
194
  username: string;
@@ -585,11 +590,68 @@ declare class GameEventManager {
585
590
  private sdk;
586
591
  private eventQueue;
587
592
  constructor(sdk: WavedashSDK);
588
- notifyGame(event: WavedashEvent, payload: string | number | boolean | object): void;
593
+ notifyGame(event: WavedashEvent, payload: string | number | object): void;
589
594
  private sendGameEvent;
590
595
  flushEventQueue(): void;
591
596
  }
592
597
 
598
+ /**
599
+ * FullscreenManager
600
+ *
601
+ * Wavedash owns the fullscreen target (a wrapper DIV on the host page that
602
+ * contains both the game iframe and our overlay UI). The SDK inside the iframe
603
+ * therefore can't call `requestFullscreen` directly — it asks the parent to
604
+ * do it via postMessage, and the parent broadcasts state changes back through
605
+ * FULLSCREEN_CHANGED so we can keep a local mirror of `isFullscreen`.
606
+ *
607
+ * User activation: browsers require a fresh user gesture to enter fullscreen.
608
+ * The click happens in the iframe, User Activation v2 propagates transient
609
+ * activation to ancestor frames, and the parent's message handler runs within
610
+ * the ~5s window — so the parent's requestFullscreen call stays activated.
611
+ *
612
+ * Legacy compat: games that call `element.requestFullscreen()` or listen for
613
+ * `fullscreenchange` directly are monkey-patched in the constructor so those
614
+ * calls route through us. The iframe isn't granted the fullscreen feature
615
+ * policy anymore, so without these shims those calls would silently reject.
616
+ */
617
+ declare class FullscreenManager {
618
+ private _isFullscreen;
619
+ private listeners;
620
+ private sdk;
621
+ constructor(sdk: WavedashSDK);
622
+ isFullscreen(): boolean;
623
+ /**
624
+ * Ask the host to enter (true) or exit (false) fullscreen. Resolves to
625
+ * `true` if the host reports the operation succeeded, `false` otherwise
626
+ * (e.g. browser rejected for lack of user activation).
627
+ */
628
+ requestFullscreen(fullscreen: boolean): Promise<boolean>;
629
+ toggleFullscreen(): Promise<boolean>;
630
+ /** Subscribe to state flips. Returns an unsubscribe fn. */
631
+ subscribe(listener: (isFullscreen: boolean) => void): () => void;
632
+ private setState;
633
+ private installCompatShims;
634
+ }
635
+
636
+ /**
637
+ * OverlayManager
638
+ *
639
+ * Owns the iframe ↔ parent interactions for the Wavedash overlay UI:
640
+ * - Shift+Tab inside the iframe toggles the overlay on the host page
641
+ * (the host owns the overlay, so we postMessage up).
642
+ * - When the parent closes the overlay it sends TAKE_FOCUS so keyboard
643
+ * input goes back to the game; we walk the DOM for a focusable target.
644
+ * - `takeFocus()` is also called after load completes so the game starts
645
+ * with keyboard focus without the player clicking first.
646
+ */
647
+ declare class OverlayManager {
648
+ private sdk;
649
+ constructor(sdk: WavedashSDK);
650
+ toggleOverlay(): void;
651
+ takeFocus(): void;
652
+ private handleKeyDown;
653
+ }
654
+
593
655
  /**
594
656
  * Friends service
595
657
  *
@@ -659,18 +721,22 @@ declare class WavedashLogger implements Logger {
659
721
  * TODO: Look into Vercel's BIDC for this https://github.com/vercel/bidc
660
722
  */
661
723
 
724
+ type PushType = keyof IFrameEventPayloadMap;
725
+ type PushListener<T extends PushType> = (data: IFrameEventPayloadMap[T]) => void;
662
726
  declare class IFrameMessenger {
663
727
  private pendingRequests;
664
728
  private requestIdCounter;
729
+ private listeners;
665
730
  constructor();
666
- private handleMessage;
667
- postToParent(requestType: (typeof IFRAME_MESSAGE_TYPE)[keyof typeof IFRAME_MESSAGE_TYPE], data: Record<string, string | number | boolean>): boolean;
668
731
  /**
669
- * Register global keyboard/mouse handlers for iframe communication.
670
- * Handles F3 prevention, initial interaction signaling, and Tab+Shift overlay toggle.
671
- * Called once during SDK setup.
732
+ * Register a handler for a one-way (no requestId) push from the parent —
733
+ * e.g. FULLSCREEN_CHANGED or TAKE_FOCUS. Multiple handlers per type are
734
+ * supported; `data` is typed from IFramePushMap.
672
735
  */
673
- registerEventHandlers(): void;
736
+ addEventListener<T extends PushType>(type: T, listener: PushListener<T>): void;
737
+ removeEventListener<T extends PushType>(type: T, listener: PushListener<T>): void;
738
+ private handleMessage;
739
+ postToParent(requestType: (typeof IFRAME_MESSAGE_TYPE)[keyof typeof IFRAME_MESSAGE_TYPE], data: Record<string, string | number | boolean>): boolean;
674
740
  requestFromParent<T extends keyof IFrameEventPayloadMap>(requestType: T, data?: Record<string, unknown>): Promise<IFrameEventPayloadMap[T]>;
675
741
  }
676
742
 
@@ -700,6 +766,7 @@ declare class WavedashSDK extends EventTarget {
700
766
  readonly BACKEND_CONNECTED: "BackendConnected";
701
767
  readonly BACKEND_DISCONNECTED: "BackendDisconnected";
702
768
  readonly BACKEND_RECONNECTING: "BackendReconnecting";
769
+ readonly FULLSCREEN_CHANGED: "FullscreenChanged";
703
770
  };
704
771
  protected lobbyManager: LobbyManager;
705
772
  protected statsManager: StatsManager;
@@ -718,6 +785,8 @@ declare class WavedashSDK extends EventTarget {
718
785
  logger: WavedashLogger;
719
786
  iframeMessenger: IFrameMessenger;
720
787
  p2pManager: P2PManager;
788
+ fullscreenManager: FullscreenManager;
789
+ overlayManager: OverlayManager;
721
790
  private gameplayJwt;
722
791
  private gameplayJwtPromise;
723
792
  ugcHost: string;
@@ -735,6 +804,23 @@ declare class WavedashSDK extends EventTarget {
735
804
  loadComplete(): void;
736
805
  get gameLoaded(): boolean;
737
806
  toggleOverlay(): void;
807
+ /**
808
+ * Whether the game is currently presented in fullscreen. Mirrored from the
809
+ * Wavedash host page, which owns the real fullscreen target so our overlay
810
+ * UI stays on top of the game.
811
+ */
812
+ isFullscreen(): boolean;
813
+ /**
814
+ * Ask the host page to enter (true) or exit (false) fullscreen. Entering
815
+ * must happen inside a user gesture handler (click / keydown / pointerdown)
816
+ * for the browser to permit it.
817
+ */
818
+ requestFullscreen(fullscreen: boolean): Promise<boolean>;
819
+ /**
820
+ * Toggle fullscreen. Like `requestFullscreen(true)`, this must run inside
821
+ * a user gesture handler when entering fullscreen.
822
+ */
823
+ toggleFullscreen(): Promise<boolean>;
738
824
  getUser(): SDKUser;
739
825
  /**
740
826
  * Get a username. Returns the logged in user's username if no ID is passed.
@@ -969,4 +1055,4 @@ declare class WavedashSDK extends EventTarget {
969
1055
 
970
1056
  declare function setupWavedashSDK(): WavedashSDK;
971
1057
 
972
- export { AVATAR_SIZE_LARGE, AVATAR_SIZE_MEDIUM, AVATAR_SIZE_SMALL, type BackendConnectionPayload, type EngineInstance, type Friend, type Leaderboard, type LeaderboardDisplayType, type LeaderboardEntries, type LeaderboardSortOrder, type Lobby, type LobbyDataUpdatedPayload, type LobbyInvite, type LobbyInvitePayload, type LobbyJoinResponse, type LobbyJoinedPayload, type LobbyKickedPayload, LobbyKickedReason, type LobbyMessage, type LobbyMessagePayload, type LobbyUser, 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 P2PSignalingMessage, type P2PTurnCredentials, type RemoteFileMetadata, type StatsStoredPayload, type UGCType, type UGCVisibility, type UpsertedLeaderboardEntry, type WavedashConfig, type WavedashEvent, WavedashEvents, type WavedashResponse, WavedashSDK, setupWavedashSDK };
1058
+ export { AVATAR_SIZE_LARGE, AVATAR_SIZE_MEDIUM, AVATAR_SIZE_SMALL, type BackendConnectionPayload, type EngineInstance, type Friend, type FullscreenChangedPayload, type Leaderboard, type LeaderboardDisplayType, type LeaderboardEntries, type LeaderboardSortOrder, type Lobby, type LobbyDataUpdatedPayload, type LobbyInvite, type LobbyInvitePayload, type LobbyJoinResponse, type LobbyJoinedPayload, type LobbyKickedPayload, LobbyKickedReason, type LobbyMessage, type LobbyMessagePayload, type LobbyUser, 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 P2PSignalingMessage, type P2PTurnCredentials, type RemoteFileMetadata, type StatsStoredPayload, type UGCType, type UGCVisibility, type UpsertedLeaderboardEntry, type WavedashConfig, type WavedashEvent, WavedashEvents, type WavedashResponse, WavedashSDK, setupWavedashSDK };
package/dist/index.js CHANGED
@@ -4218,8 +4218,11 @@ var WavedashEvents = {
4218
4218
  // connected to Convex backend
4219
4219
  BACKEND_DISCONNECTED: "BackendDisconnected",
4220
4220
  // disconnected from Convex backend
4221
- BACKEND_RECONNECTING: "BackendReconnecting"
4221
+ BACKEND_RECONNECTING: "BackendReconnecting",
4222
4222
  // attempting to reconnect to backend
4223
+ // Fullscreen events
4224
+ FULLSCREEN_CHANGED: "FullscreenChanged"
4225
+ // fullscreen state changed
4223
4226
  // TODO: Future events to implement
4224
4227
  // P2P_CONNECTION_REQUESTED: 'P2PConnectionRequested', // for now we always connect all lobby members
4225
4228
  };
@@ -6294,16 +6297,13 @@ var _P2PManager = class _P2PManager {
6294
6297
  }
6295
6298
  }
6296
6299
  emitPacketDropped(tracker, droppedCount) {
6297
- this.sdk.gameEventManager.notifyGame(
6298
- WavedashEvents.P2P_PACKET_DROPPED,
6299
- {
6300
- channel: tracker.channel,
6301
- direction: tracker.direction,
6302
- reason: tracker.reason,
6303
- droppedCount,
6304
- droppedTotal: tracker.droppedTotal
6305
- }
6306
- );
6300
+ this.sdk.gameEventManager.notifyGame(WavedashEvents.P2P_PACKET_DROPPED, {
6301
+ channel: tracker.channel,
6302
+ direction: tracker.direction,
6303
+ reason: tracker.reason,
6304
+ droppedCount,
6305
+ droppedTotal: tracker.droppedTotal
6306
+ });
6307
6307
  }
6308
6308
  clearPacketDropTrackers() {
6309
6309
  for (const tracker of this.packetDropTrackers.values()) {
@@ -6585,11 +6585,10 @@ var StatsManager = class {
6585
6585
  // Debounced persist — used by storeNow in setters to batch rapid calls.
6586
6586
  // Leading+trailing: first call fires immediately, subsequent calls within
6587
6587
  // the window are batched into one trailing call.
6588
- this.debouncedPersist = (0, import_lodash2.default)(
6589
- () => this.persist(),
6590
- STORE_DEBOUNCE_MS,
6591
- { leading: true, trailing: true }
6592
- );
6588
+ this.debouncedPersist = (0, import_lodash2.default)(() => this.persist(), STORE_DEBOUNCE_MS, {
6589
+ leading: true,
6590
+ trailing: true
6591
+ });
6593
6592
  this.sdk = sdk;
6594
6593
  this.subscribe();
6595
6594
  this.requestStats().catch((error) => {
@@ -6956,6 +6955,135 @@ var GameEventManager = class {
6956
6955
  }
6957
6956
  };
6958
6957
 
6958
+ // src/services/fullscreen.ts
6959
+ import { IFRAME_MESSAGE_TYPE as IFRAME_MESSAGE_TYPE3 } from "@wvdsh/api";
6960
+ var FullscreenManager = class {
6961
+ constructor(sdk) {
6962
+ this._isFullscreen = false;
6963
+ this.listeners = /* @__PURE__ */ new Set();
6964
+ this.sdk = sdk;
6965
+ this.sdk.iframeMessenger.addEventListener(
6966
+ IFRAME_MESSAGE_TYPE3.FULLSCREEN_CHANGED,
6967
+ (data) => {
6968
+ this.sdk.gameEventManager.notifyGame(
6969
+ WavedashEvents.FULLSCREEN_CHANGED,
6970
+ { isFullscreen: data.isFullscreen }
6971
+ );
6972
+ this.setState(data.isFullscreen);
6973
+ }
6974
+ );
6975
+ this.installCompatShims();
6976
+ }
6977
+ isFullscreen() {
6978
+ return this._isFullscreen;
6979
+ }
6980
+ /**
6981
+ * Ask the host to enter (true) or exit (false) fullscreen. Resolves to
6982
+ * `true` if the host reports the operation succeeded, `false` otherwise
6983
+ * (e.g. browser rejected for lack of user activation).
6984
+ */
6985
+ async requestFullscreen(fullscreen) {
6986
+ const response = await this.sdk.iframeMessenger.requestFromParent(
6987
+ IFRAME_MESSAGE_TYPE3.SET_FULLSCREEN,
6988
+ { fullscreen }
6989
+ );
6990
+ return response.success;
6991
+ }
6992
+ async toggleFullscreen() {
6993
+ const response = await this.sdk.iframeMessenger.requestFromParent(
6994
+ IFRAME_MESSAGE_TYPE3.TOGGLE_FULLSCREEN
6995
+ );
6996
+ return response.success;
6997
+ }
6998
+ /** Subscribe to state flips. Returns an unsubscribe fn. */
6999
+ subscribe(listener) {
7000
+ this.listeners.add(listener);
7001
+ return () => this.listeners.delete(listener);
7002
+ }
7003
+ setState(isFullscreen) {
7004
+ if (this._isFullscreen === isFullscreen) return;
7005
+ this._isFullscreen = isFullscreen;
7006
+ for (const listener of this.listeners) listener(isFullscreen);
7007
+ }
7008
+ installCompatShims() {
7009
+ if (typeof document === "undefined") return;
7010
+ const fullscreenElementGetter = () => this._isFullscreen ? document.body : null;
7011
+ Object.defineProperty(document, "fullscreenElement", {
7012
+ configurable: true,
7013
+ get: fullscreenElementGetter
7014
+ });
7015
+ Object.defineProperty(document, "webkitFullscreenElement", {
7016
+ configurable: true,
7017
+ get: fullscreenElementGetter
7018
+ });
7019
+ const enter = async () => {
7020
+ if (this._isFullscreen) return;
7021
+ const ok = await this.requestFullscreen(true);
7022
+ if (!ok) throw new Error("Fullscreen request was denied");
7023
+ };
7024
+ const exit = async () => {
7025
+ if (!this._isFullscreen) return;
7026
+ const ok = await this.requestFullscreen(false);
7027
+ if (!ok) throw new Error("Exit fullscreen request was denied");
7028
+ };
7029
+ Element.prototype.requestFullscreen = function() {
7030
+ return enter();
7031
+ };
7032
+ Element.prototype.webkitRequestFullscreen = function() {
7033
+ return enter();
7034
+ };
7035
+ Document.prototype.exitFullscreen = function() {
7036
+ return exit();
7037
+ };
7038
+ Document.prototype.webkitExitFullscreen = function() {
7039
+ return exit();
7040
+ };
7041
+ this.subscribe(() => {
7042
+ document.dispatchEvent(new Event("fullscreenchange", { bubbles: true }));
7043
+ });
7044
+ }
7045
+ };
7046
+
7047
+ // src/services/overlay.ts
7048
+ import { IFRAME_MESSAGE_TYPE as IFRAME_MESSAGE_TYPE4 } from "@wvdsh/api";
7049
+ var OverlayManager = class {
7050
+ constructor(sdk) {
7051
+ this.handleKeyDown = (event) => {
7052
+ if (event.defaultPrevented) return;
7053
+ if (event.key === "Tab" && event.shiftKey) {
7054
+ event.preventDefault();
7055
+ this.toggleOverlay();
7056
+ }
7057
+ };
7058
+ this.sdk = sdk;
7059
+ this.sdk.iframeMessenger.addEventListener(
7060
+ IFRAME_MESSAGE_TYPE4.TAKE_FOCUS,
7061
+ () => this.takeFocus()
7062
+ );
7063
+ if (typeof window !== "undefined") {
7064
+ window.addEventListener("keydown", this.handleKeyDown);
7065
+ }
7066
+ }
7067
+ toggleOverlay() {
7068
+ this.sdk.iframeMessenger.postToParent(
7069
+ IFRAME_MESSAGE_TYPE4.TOGGLE_OVERLAY,
7070
+ {}
7071
+ );
7072
+ }
7073
+ takeFocus() {
7074
+ if (typeof document === "undefined") return;
7075
+ const gameFocusTargets = document.getElementsByClassName("game-focus-target");
7076
+ if (gameFocusTargets.length > 0) {
7077
+ gameFocusTargets[0].focus();
7078
+ return;
7079
+ }
7080
+ const focusableElement = document.querySelector(
7081
+ "canvas, input, button, [tabindex]:not([tabindex='-1'])"
7082
+ );
7083
+ focusableElement?.focus();
7084
+ }
7085
+ };
7086
+
6959
7087
  // src/services/friends.ts
6960
7088
  import { api as api8 } from "@wvdsh/api";
6961
7089
 
@@ -7069,26 +7197,6 @@ var WavedashLogger = class {
7069
7197
  }
7070
7198
  };
7071
7199
 
7072
- // src/utils/iframeMessenger.ts
7073
- import { IFRAME_MESSAGE_TYPE as IFRAME_MESSAGE_TYPE3 } from "@wvdsh/api";
7074
-
7075
- // src/utils/focusManager.ts
7076
- function takeFocus() {
7077
- if (typeof document !== "undefined") {
7078
- const gameFocusTargets = document.getElementsByClassName("game-focus-target");
7079
- if (gameFocusTargets.length > 0) {
7080
- gameFocusTargets[0].focus();
7081
- } else {
7082
- const focusableElement = document.querySelector(
7083
- "canvas, input, button, [tabindex]:not([tabindex='-1'])"
7084
- );
7085
- if (focusableElement) {
7086
- focusableElement.focus();
7087
- }
7088
- }
7089
- }
7090
- }
7091
-
7092
7200
  // src/utils/parentOrigin.ts
7093
7201
  function deriveParentOrigin() {
7094
7202
  if (typeof window === "undefined") return "";
@@ -7122,46 +7230,42 @@ var IFrameMessenger = class {
7122
7230
  this.pendingRequests.delete(event.data.requestId);
7123
7231
  pending.resolve(event.data.data);
7124
7232
  }
7125
- } else if (event.data?.type === IFRAME_MESSAGE_TYPE3.TAKE_FOCUS) {
7126
- takeFocus();
7233
+ return;
7127
7234
  }
7235
+ const messageType = event.data?.type;
7236
+ if (!messageType) return;
7237
+ const set = this.listeners.get(messageType);
7238
+ if (!set) return;
7239
+ for (const listener of set) listener(event.data);
7128
7240
  };
7129
7241
  this.pendingRequests = /* @__PURE__ */ new Map();
7130
7242
  this.requestIdCounter = 0;
7243
+ this.listeners = /* @__PURE__ */ new Map();
7131
7244
  if (typeof window !== "undefined") {
7132
7245
  window.addEventListener("message", this.handleMessage);
7133
7246
  }
7134
7247
  }
7248
+ /**
7249
+ * Register a handler for a one-way (no requestId) push from the parent —
7250
+ * e.g. FULLSCREEN_CHANGED or TAKE_FOCUS. Multiple handlers per type are
7251
+ * supported; `data` is typed from IFramePushMap.
7252
+ */
7253
+ addEventListener(type, listener) {
7254
+ let set = this.listeners.get(type);
7255
+ if (!set) {
7256
+ set = /* @__PURE__ */ new Set();
7257
+ this.listeners.set(type, set);
7258
+ }
7259
+ set.add(listener);
7260
+ }
7261
+ removeEventListener(type, listener) {
7262
+ this.listeners.get(type)?.delete(listener);
7263
+ }
7135
7264
  postToParent(requestType, data) {
7136
7265
  if (typeof window === "undefined" || !parentOrigin) return false;
7137
7266
  window.parent.postMessage({ type: requestType, ...data }, parentOrigin);
7138
7267
  return true;
7139
7268
  }
7140
- /**
7141
- * Register global keyboard/mouse handlers for iframe communication.
7142
- * Handles F3 prevention, initial interaction signaling, and Tab+Shift overlay toggle.
7143
- * Called once during SDK setup.
7144
- */
7145
- registerEventHandlers() {
7146
- let sentInitialInteraction = false;
7147
- const handleInteraction = () => {
7148
- if (!sentInitialInteraction) {
7149
- sentInitialInteraction = true;
7150
- this.postToParent(IFRAME_MESSAGE_TYPE3.INITIAL_INTERACTION, {});
7151
- }
7152
- };
7153
- window.addEventListener("keydown", (event) => {
7154
- if (event.key === "F3") {
7155
- event.preventDefault();
7156
- }
7157
- handleInteraction();
7158
- if (event.key === "Tab" && event.shiftKey) {
7159
- event.preventDefault();
7160
- this.postToParent(IFRAME_MESSAGE_TYPE3.TOGGLE_OVERLAY, {});
7161
- }
7162
- });
7163
- window.addEventListener("mousedown", handleInteraction);
7164
- }
7165
7269
  async requestFromParent(requestType, data) {
7166
7270
  return new Promise((resolve, reject) => {
7167
7271
  if (typeof window === "undefined" || !parentOrigin) {
@@ -7193,7 +7297,7 @@ var IFrameMessenger = class {
7193
7297
  // src/index.ts
7194
7298
  import {
7195
7299
  GAME_ENGINE,
7196
- IFRAME_MESSAGE_TYPE as IFRAME_MESSAGE_TYPE4,
7300
+ IFRAME_MESSAGE_TYPE as IFRAME_MESSAGE_TYPE5,
7197
7301
  LEADERBOARD_DISPLAY_TYPE,
7198
7302
  LEADERBOARD_SORT_ORDER,
7199
7303
  LOBBY_VISIBILITY,
@@ -7206,9 +7310,7 @@ import {
7206
7310
  var CONVEX_ID_REGEX = /^[0-9a-z]{31,37}$/;
7207
7311
  var vString = (value, path) => {
7208
7312
  if (typeof value !== "string") {
7209
- throw new Error(
7210
- `${path}: expected string, got ${describeValue(value)}`
7211
- );
7313
+ throw new Error(`${path}: expected string, got ${describeValue(value)}`);
7212
7314
  }
7213
7315
  return value;
7214
7316
  };
@@ -7222,9 +7324,7 @@ var vNumber = (value, path) => {
7222
7324
  };
7223
7325
  var vBoolean = (value, path) => {
7224
7326
  if (typeof value !== "boolean") {
7225
- throw new Error(
7226
- `${path}: expected boolean, got ${describeValue(value)}`
7227
- );
7327
+ throw new Error(`${path}: expected boolean, got ${describeValue(value)}`);
7228
7328
  }
7229
7329
  return value;
7230
7330
  };
@@ -7287,9 +7387,7 @@ function vUnion(...variants) {
7287
7387
  } catch {
7288
7388
  }
7289
7389
  }
7290
- throw new Error(
7291
- `${path}: no variant matched, got ${describeValue(value)}`
7292
- );
7390
+ throw new Error(`${path}: no variant matched, got ${describeValue(value)}`);
7293
7391
  };
7294
7392
  }
7295
7393
  function validateArgs(methodName, specs, values) {
@@ -7349,6 +7447,8 @@ var WavedashSDK = class extends EventTarget {
7349
7447
  this.leaderboardManager = new LeaderboardManager(this);
7350
7448
  this.friendsManager = new FriendsManager(this);
7351
7449
  this.gameEventManager = new GameEventManager(this);
7450
+ this.fullscreenManager = new FullscreenManager(this);
7451
+ this.overlayManager = new OverlayManager(this);
7352
7452
  this.friendsManager.cacheUsers([
7353
7453
  {
7354
7454
  userId: this.wavedashUser.id,
@@ -7426,7 +7526,7 @@ var WavedashSDK = class extends EventTarget {
7426
7526
  [["progress", vNumber]],
7427
7527
  [progress]
7428
7528
  );
7429
- iframeMessenger.postToParent(IFRAME_MESSAGE_TYPE4.PROGRESS_UPDATE, {
7529
+ iframeMessenger.postToParent(IFRAME_MESSAGE_TYPE5.PROGRESS_UPDATE, {
7430
7530
  progress
7431
7531
  });
7432
7532
  }
@@ -7434,14 +7534,40 @@ var WavedashSDK = class extends EventTarget {
7434
7534
  if (this.gameFinishedLoading) return;
7435
7535
  this.gameFinishedLoading = true;
7436
7536
  this.heartbeatManager.start();
7437
- iframeMessenger.postToParent(IFRAME_MESSAGE_TYPE4.LOADING_COMPLETE, {});
7438
- takeFocus();
7537
+ iframeMessenger.postToParent(IFRAME_MESSAGE_TYPE5.LOADING_COMPLETE, {});
7538
+ this.overlayManager.takeFocus();
7439
7539
  }
7440
7540
  get gameLoaded() {
7441
7541
  return this.gameFinishedLoading;
7442
7542
  }
7443
7543
  toggleOverlay() {
7444
- iframeMessenger.postToParent(IFRAME_MESSAGE_TYPE4.TOGGLE_OVERLAY, {});
7544
+ this.overlayManager.toggleOverlay();
7545
+ }
7546
+ // ==========
7547
+ // Fullscreen
7548
+ // ==========
7549
+ /**
7550
+ * Whether the game is currently presented in fullscreen. Mirrored from the
7551
+ * Wavedash host page, which owns the real fullscreen target so our overlay
7552
+ * UI stays on top of the game.
7553
+ */
7554
+ isFullscreen() {
7555
+ return this.fullscreenManager.isFullscreen();
7556
+ }
7557
+ /**
7558
+ * Ask the host page to enter (true) or exit (false) fullscreen. Entering
7559
+ * must happen inside a user gesture handler (click / keydown / pointerdown)
7560
+ * for the browser to permit it.
7561
+ */
7562
+ async requestFullscreen(fullscreen) {
7563
+ return this.fullscreenManager.requestFullscreen(fullscreen);
7564
+ }
7565
+ /**
7566
+ * Toggle fullscreen. Like `requestFullscreen(true)`, this must run inside
7567
+ * a user gesture handler when entering fullscreen.
7568
+ */
7569
+ async toggleFullscreen() {
7570
+ return this.fullscreenManager.toggleFullscreen();
7445
7571
  }
7446
7572
  // ============
7447
7573
  // User methods
@@ -8237,7 +8363,6 @@ var WavedashSDK = class extends EventTarget {
8237
8363
  function setupWavedashSDK() {
8238
8364
  const existing = window.WavedashJS;
8239
8365
  if (existing) return existing;
8240
- iframeMessenger.registerEventHandlers();
8241
8366
  const raw = new URLSearchParams(window.location.search).get(
8242
8367
  UrlParams.SdkConfig
8243
8368
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wvdsh/sdk-js",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "type": "module",
5
5
  "description": "Wavedash JavaScript SDK",
6
6
  "main": "./dist/index.js",
@@ -40,7 +40,7 @@
40
40
  "typescript-eslint": "^8.52.0"
41
41
  },
42
42
  "dependencies": {
43
- "@wvdsh/api": "^0.1.0",
43
+ "@wvdsh/api": "^0.1.1",
44
44
  "convex": "^1.28.0",
45
45
  "lodash.debounce": "^4.0.8"
46
46
  }