@wvdsh/sdk-js 1.2.1 → 1.2.3

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"];
@@ -590,6 +591,63 @@ declare class GameEventManager {
590
591
  flushEventQueue(): void;
591
592
  }
592
593
 
594
+ /**
595
+ * FullscreenManager
596
+ *
597
+ * Wavedash owns the fullscreen target (a wrapper DIV on the host page that
598
+ * contains both the game iframe and our overlay UI). The SDK inside the iframe
599
+ * therefore can't call `requestFullscreen` directly — it asks the parent to
600
+ * do it via postMessage, and the parent broadcasts state changes back through
601
+ * FULLSCREEN_CHANGED so we can keep a local mirror of `isFullscreen`.
602
+ *
603
+ * User activation: browsers require a fresh user gesture to enter fullscreen.
604
+ * The click happens in the iframe, User Activation v2 propagates transient
605
+ * activation to ancestor frames, and the parent's message handler runs within
606
+ * the ~5s window — so the parent's requestFullscreen call stays activated.
607
+ *
608
+ * Legacy compat: games that call `element.requestFullscreen()` or listen for
609
+ * `fullscreenchange` directly are monkey-patched in the constructor so those
610
+ * calls route through us. The iframe isn't granted the fullscreen feature
611
+ * policy anymore, so without these shims those calls would silently reject.
612
+ */
613
+ declare class FullscreenManager {
614
+ private _isFullscreen;
615
+ private listeners;
616
+ private sdk;
617
+ constructor(sdk: WavedashSDK);
618
+ isFullscreen(): boolean;
619
+ /**
620
+ * Ask the host to enter (true) or exit (false) fullscreen. Resolves to
621
+ * `true` if the host reports the operation succeeded, `false` otherwise
622
+ * (e.g. browser rejected for lack of user activation).
623
+ */
624
+ requestFullscreen(fullscreen: boolean): Promise<boolean>;
625
+ toggleFullscreen(): Promise<boolean>;
626
+ /** Subscribe to state flips. Returns an unsubscribe fn. */
627
+ subscribe(listener: (isFullscreen: boolean) => void): () => void;
628
+ private setState;
629
+ private installCompatShims;
630
+ }
631
+
632
+ /**
633
+ * OverlayManager
634
+ *
635
+ * Owns the iframe ↔ parent interactions for the Wavedash overlay UI:
636
+ * - Shift+Tab inside the iframe toggles the overlay on the host page
637
+ * (the host owns the overlay, so we postMessage up).
638
+ * - When the parent closes the overlay it sends TAKE_FOCUS so keyboard
639
+ * input goes back to the game; we walk the DOM for a focusable target.
640
+ * - `takeFocus()` is also called after load completes so the game starts
641
+ * with keyboard focus without the player clicking first.
642
+ */
643
+ declare class OverlayManager {
644
+ private sdk;
645
+ constructor(sdk: WavedashSDK);
646
+ toggleOverlay(): void;
647
+ takeFocus(): void;
648
+ private handleKeyDown;
649
+ }
650
+
593
651
  /**
594
652
  * Friends service
595
653
  *
@@ -659,18 +717,22 @@ declare class WavedashLogger implements Logger {
659
717
  * TODO: Look into Vercel's BIDC for this https://github.com/vercel/bidc
660
718
  */
661
719
 
720
+ type PushType = keyof IFrameEventPayloadMap;
721
+ type PushListener<T extends PushType> = (data: IFrameEventPayloadMap[T]) => void;
662
722
  declare class IFrameMessenger {
663
723
  private pendingRequests;
664
724
  private requestIdCounter;
725
+ private listeners;
665
726
  constructor();
666
- private handleMessage;
667
- postToParent(requestType: (typeof IFRAME_MESSAGE_TYPE)[keyof typeof IFRAME_MESSAGE_TYPE], data: Record<string, string | number | boolean>): boolean;
668
727
  /**
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.
728
+ * Register a handler for a one-way (no requestId) push from the parent —
729
+ * e.g. FULLSCREEN_CHANGED or TAKE_FOCUS. Multiple handlers per type are
730
+ * supported; `data` is typed from IFramePushMap.
672
731
  */
673
- registerEventHandlers(): void;
732
+ addEventListener<T extends PushType>(type: T, listener: PushListener<T>): void;
733
+ removeEventListener<T extends PushType>(type: T, listener: PushListener<T>): void;
734
+ private handleMessage;
735
+ postToParent(requestType: (typeof IFRAME_MESSAGE_TYPE)[keyof typeof IFRAME_MESSAGE_TYPE], data: Record<string, string | number | boolean>): boolean;
674
736
  requestFromParent<T extends keyof IFrameEventPayloadMap>(requestType: T, data?: Record<string, unknown>): Promise<IFrameEventPayloadMap[T]>;
675
737
  }
676
738
 
@@ -700,6 +762,7 @@ declare class WavedashSDK extends EventTarget {
700
762
  readonly BACKEND_CONNECTED: "BackendConnected";
701
763
  readonly BACKEND_DISCONNECTED: "BackendDisconnected";
702
764
  readonly BACKEND_RECONNECTING: "BackendReconnecting";
765
+ readonly FULLSCREEN_CHANGED: "FullscreenChanged";
703
766
  };
704
767
  protected lobbyManager: LobbyManager;
705
768
  protected statsManager: StatsManager;
@@ -718,6 +781,8 @@ declare class WavedashSDK extends EventTarget {
718
781
  logger: WavedashLogger;
719
782
  iframeMessenger: IFrameMessenger;
720
783
  p2pManager: P2PManager;
784
+ fullscreenManager: FullscreenManager;
785
+ overlayManager: OverlayManager;
721
786
  private gameplayJwt;
722
787
  private gameplayJwtPromise;
723
788
  ugcHost: string;
@@ -735,6 +800,23 @@ declare class WavedashSDK extends EventTarget {
735
800
  loadComplete(): void;
736
801
  get gameLoaded(): boolean;
737
802
  toggleOverlay(): void;
803
+ /**
804
+ * Whether the game is currently presented in fullscreen. Mirrored from the
805
+ * Wavedash host page, which owns the real fullscreen target so our overlay
806
+ * UI stays on top of the game.
807
+ */
808
+ isFullscreen(): boolean;
809
+ /**
810
+ * Ask the host page to enter (true) or exit (false) fullscreen. Entering
811
+ * must happen inside a user gesture handler (click / keydown / pointerdown)
812
+ * for the browser to permit it.
813
+ */
814
+ requestFullscreen(fullscreen: boolean): Promise<boolean>;
815
+ /**
816
+ * Toggle fullscreen. Like `requestFullscreen(true)`, this must run inside
817
+ * a user gesture handler when entering fullscreen.
818
+ */
819
+ toggleFullscreen(): Promise<boolean>;
738
820
  getUser(): SDKUser;
739
821
  /**
740
822
  * Get a username. Returns the logged in user's username if no ID is passed.
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
+ 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) {
@@ -7190,46 +7294,10 @@ var IFrameMessenger = class {
7190
7294
  }
7191
7295
  };
7192
7296
 
7193
- // src/utils/pageEnhancementManager.ts
7194
- var SCROLL_KEYS = /* @__PURE__ */ new Set([
7195
- " ",
7196
- "ArrowUp",
7197
- "ArrowDown",
7198
- "ArrowLeft",
7199
- "ArrowRight",
7200
- "PageUp",
7201
- "PageDown",
7202
- "Home",
7203
- "End"
7204
- ]);
7205
- function isTypingContext(target) {
7206
- if (!(target instanceof HTMLElement)) return false;
7207
- const tag = target.tagName;
7208
- if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
7209
- return target.isContentEditable;
7210
- }
7211
- var PageEnhancementManager = class {
7212
- constructor() {
7213
- this.registered = false;
7214
- this.handleKeyDown = (event) => {
7215
- if (isTypingContext(event.target)) return;
7216
- if (SCROLL_KEYS.has(event.key)) {
7217
- event.preventDefault();
7218
- }
7219
- };
7220
- }
7221
- register() {
7222
- if (this.registered) return;
7223
- if (typeof window === "undefined") return;
7224
- this.registered = true;
7225
- window.addEventListener("keydown", this.handleKeyDown);
7226
- }
7227
- };
7228
-
7229
7297
  // src/index.ts
7230
7298
  import {
7231
7299
  GAME_ENGINE,
7232
- IFRAME_MESSAGE_TYPE as IFRAME_MESSAGE_TYPE4,
7300
+ IFRAME_MESSAGE_TYPE as IFRAME_MESSAGE_TYPE5,
7233
7301
  LEADERBOARD_DISPLAY_TYPE,
7234
7302
  LEADERBOARD_SORT_ORDER,
7235
7303
  LOBBY_VISIBILITY,
@@ -7242,9 +7310,7 @@ import {
7242
7310
  var CONVEX_ID_REGEX = /^[0-9a-z]{31,37}$/;
7243
7311
  var vString = (value, path) => {
7244
7312
  if (typeof value !== "string") {
7245
- throw new Error(
7246
- `${path}: expected string, got ${describeValue(value)}`
7247
- );
7313
+ throw new Error(`${path}: expected string, got ${describeValue(value)}`);
7248
7314
  }
7249
7315
  return value;
7250
7316
  };
@@ -7258,9 +7324,7 @@ var vNumber = (value, path) => {
7258
7324
  };
7259
7325
  var vBoolean = (value, path) => {
7260
7326
  if (typeof value !== "boolean") {
7261
- throw new Error(
7262
- `${path}: expected boolean, got ${describeValue(value)}`
7263
- );
7327
+ throw new Error(`${path}: expected boolean, got ${describeValue(value)}`);
7264
7328
  }
7265
7329
  return value;
7266
7330
  };
@@ -7323,9 +7387,7 @@ function vUnion(...variants) {
7323
7387
  } catch {
7324
7388
  }
7325
7389
  }
7326
- throw new Error(
7327
- `${path}: no variant matched, got ${describeValue(value)}`
7328
- );
7390
+ throw new Error(`${path}: no variant matched, got ${describeValue(value)}`);
7329
7391
  };
7330
7392
  }
7331
7393
  function validateArgs(methodName, specs, values) {
@@ -7385,6 +7447,8 @@ var WavedashSDK = class extends EventTarget {
7385
7447
  this.leaderboardManager = new LeaderboardManager(this);
7386
7448
  this.friendsManager = new FriendsManager(this);
7387
7449
  this.gameEventManager = new GameEventManager(this);
7450
+ this.fullscreenManager = new FullscreenManager(this);
7451
+ this.overlayManager = new OverlayManager(this);
7388
7452
  this.friendsManager.cacheUsers([
7389
7453
  {
7390
7454
  userId: this.wavedashUser.id,
@@ -7393,7 +7457,6 @@ var WavedashSDK = class extends EventTarget {
7393
7457
  }
7394
7458
  ]);
7395
7459
  this.setupSessionEndListeners();
7396
- new PageEnhancementManager().register();
7397
7460
  this.launchParams = sdkConfig.launchParams ?? {};
7398
7461
  }
7399
7462
  get initialized() {
@@ -7463,7 +7526,7 @@ var WavedashSDK = class extends EventTarget {
7463
7526
  [["progress", vNumber]],
7464
7527
  [progress]
7465
7528
  );
7466
- iframeMessenger.postToParent(IFRAME_MESSAGE_TYPE4.PROGRESS_UPDATE, {
7529
+ iframeMessenger.postToParent(IFRAME_MESSAGE_TYPE5.PROGRESS_UPDATE, {
7467
7530
  progress
7468
7531
  });
7469
7532
  }
@@ -7471,14 +7534,40 @@ var WavedashSDK = class extends EventTarget {
7471
7534
  if (this.gameFinishedLoading) return;
7472
7535
  this.gameFinishedLoading = true;
7473
7536
  this.heartbeatManager.start();
7474
- iframeMessenger.postToParent(IFRAME_MESSAGE_TYPE4.LOADING_COMPLETE, {});
7475
- takeFocus();
7537
+ iframeMessenger.postToParent(IFRAME_MESSAGE_TYPE5.LOADING_COMPLETE, {});
7538
+ this.overlayManager.takeFocus();
7476
7539
  }
7477
7540
  get gameLoaded() {
7478
7541
  return this.gameFinishedLoading;
7479
7542
  }
7480
7543
  toggleOverlay() {
7481
- 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();
7482
7571
  }
7483
7572
  // ============
7484
7573
  // User methods
@@ -8274,7 +8363,6 @@ var WavedashSDK = class extends EventTarget {
8274
8363
  function setupWavedashSDK() {
8275
8364
  const existing = window.WavedashJS;
8276
8365
  if (existing) return existing;
8277
- iframeMessenger.registerEventHandlers();
8278
8366
  const raw = new URLSearchParams(window.location.search).get(
8279
8367
  UrlParams.SdkConfig
8280
8368
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wvdsh/sdk-js",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
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
  }