@wvdsh/sdk-js 1.3.19 → 1.3.21
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 +30 -5
- package/dist/index.js +159 -17
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -43,17 +43,24 @@ declare abstract class WavedashManager {
|
|
|
43
43
|
* e.g. a PIXI/GDevelop intro video), force-muting it before playback begins
|
|
44
44
|
* regardless of how it was created — the one path the DOM-based sources and
|
|
45
45
|
* the `muted` setter all miss.
|
|
46
|
+
*
|
|
47
|
+
* Speech synthesis (`window.speechSynthesis`): bypasses both Web Audio and HTML
|
|
48
|
+
* media entirely, so it gets its own shim — `speak()` forces the utterance's
|
|
49
|
+
* native volume to 0 while muted.
|
|
46
50
|
*/
|
|
47
51
|
declare class AudioManager extends WavedashManager {
|
|
48
52
|
private _isMuted;
|
|
49
53
|
private contexts;
|
|
50
54
|
private elements;
|
|
51
55
|
private intendedMuted;
|
|
56
|
+
private intendedUtteranceVolume;
|
|
52
57
|
private originalAudioContext;
|
|
53
58
|
private originalWebKitAudioContext;
|
|
54
59
|
private originalAudio;
|
|
55
60
|
private originalMutedDescriptor;
|
|
56
61
|
private originalPlay;
|
|
62
|
+
private originalSpeak;
|
|
63
|
+
private originalUtteranceVolumeDescriptor;
|
|
57
64
|
private mutationObserver;
|
|
58
65
|
constructor(sdk: WavedashSDK);
|
|
59
66
|
isMuted(): boolean;
|
|
@@ -78,6 +85,18 @@ declare class AudioManager extends WavedashManager {
|
|
|
78
85
|
*/
|
|
79
86
|
private trackElement;
|
|
80
87
|
private installShims;
|
|
88
|
+
/**
|
|
89
|
+
* Shim `window.speechSynthesis` so speech respects the SDK mute state.
|
|
90
|
+
*
|
|
91
|
+
* Never swallows speak(): utterances have a lifecycle the game may sequence
|
|
92
|
+
* off (onstart/onend, synth.speaking/pending checks), so every call is
|
|
93
|
+
* delegated and silenced via volume instead. Volume is sampled at speak()
|
|
94
|
+
* time, so forcing the native value to 0 right before delegating silences
|
|
95
|
+
* anything spoken while muted; in-flight speech at the mute edge is
|
|
96
|
+
* deliberately left to finish (can't be softened mid-utterance, and
|
|
97
|
+
* cancel() would discard the pending queue).
|
|
98
|
+
*/
|
|
99
|
+
private shimSpeechSynthesis;
|
|
81
100
|
private shimAudioContextClass;
|
|
82
101
|
destroy(): void;
|
|
83
102
|
}
|
|
@@ -665,16 +684,18 @@ declare class LobbyManager extends WavedashManager {
|
|
|
665
684
|
* Owns the iframe ↔ parent interactions for the Wavedash overlay UI:
|
|
666
685
|
* - Shift+Tab inside the iframe toggles the overlay on the host page
|
|
667
686
|
* (the host owns the overlay, so we postMessage up).
|
|
668
|
-
* - When the parent closes the overlay it sends TAKE_FOCUS
|
|
669
|
-
*
|
|
670
|
-
* -
|
|
671
|
-
*
|
|
687
|
+
* - When the parent closes the overlay it sends TAKE_FOCUS, which hands
|
|
688
|
+
* keyboard focus back to the game (see `takeFocus`).
|
|
689
|
+
* - While the overlay is open we suspend pointer lock (the host broadcasts
|
|
690
|
+
* OVERLAY_CHANGED) so a game can't hold/re-grab the cursor behind it.
|
|
672
691
|
*/
|
|
673
692
|
declare class OverlayManager extends WavedashManager {
|
|
693
|
+
private restorePointerLock;
|
|
674
694
|
constructor(sdk: WavedashSDK);
|
|
695
|
+
private setOpen;
|
|
675
696
|
toggleOverlay(): void;
|
|
676
|
-
takeFocus(): void;
|
|
677
697
|
private handleKeyDown;
|
|
698
|
+
destroy(): void;
|
|
678
699
|
}
|
|
679
700
|
|
|
680
701
|
/**
|
|
@@ -795,9 +816,13 @@ declare class P2PManager extends WavedashManager {
|
|
|
795
816
|
}
|
|
796
817
|
|
|
797
818
|
declare class PaidContentManager extends WavedashManager {
|
|
819
|
+
private paywallOpen;
|
|
820
|
+
private restorePointerLock;
|
|
798
821
|
isEntitled(contentId: string): Promise<boolean>;
|
|
799
822
|
getEntitlements(): Promise<string[]>;
|
|
800
823
|
triggerPaywall(contentIdentifier: string): Promise<boolean>;
|
|
824
|
+
isPaywallOpen(): boolean;
|
|
825
|
+
destroy(): void;
|
|
801
826
|
}
|
|
802
827
|
|
|
803
828
|
declare class StatsManager extends WavedashManager {
|
package/dist/index.js
CHANGED
|
@@ -124,12 +124,16 @@ var AudioManager = class extends WavedashManager {
|
|
|
124
124
|
// HTML media elements we know about + their game-intended muted state
|
|
125
125
|
this.elements = new WeakRefSet();
|
|
126
126
|
this.intendedMuted = /* @__PURE__ */ new WeakMap();
|
|
127
|
+
// Speech synthesis utterances + their game-intended volume
|
|
128
|
+
this.intendedUtteranceVolume = /* @__PURE__ */ new WeakMap();
|
|
127
129
|
// Originals (restored on destroy)
|
|
128
130
|
this.originalAudioContext = null;
|
|
129
131
|
this.originalWebKitAudioContext = null;
|
|
130
132
|
this.originalAudio = null;
|
|
131
133
|
this.originalMutedDescriptor = null;
|
|
132
134
|
this.originalPlay = null;
|
|
135
|
+
this.originalSpeak = null;
|
|
136
|
+
this.originalUtteranceVolumeDescriptor = null;
|
|
133
137
|
this.mutationObserver = null;
|
|
134
138
|
this.handleMute = (data) => {
|
|
135
139
|
if (this._isMuted === data.isMuted) return;
|
|
@@ -275,6 +279,66 @@ var AudioManager = class extends WavedashManager {
|
|
|
275
279
|
return originalPlay.call(this);
|
|
276
280
|
};
|
|
277
281
|
})(this);
|
|
282
|
+
this.shimSpeechSynthesis();
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Shim `window.speechSynthesis` so speech respects the SDK mute state.
|
|
286
|
+
*
|
|
287
|
+
* Never swallows speak(): utterances have a lifecycle the game may sequence
|
|
288
|
+
* off (onstart/onend, synth.speaking/pending checks), so every call is
|
|
289
|
+
* delegated and silenced via volume instead. Volume is sampled at speak()
|
|
290
|
+
* time, so forcing the native value to 0 right before delegating silences
|
|
291
|
+
* anything spoken while muted; in-flight speech at the mute edge is
|
|
292
|
+
* deliberately left to finish (can't be softened mid-utterance, and
|
|
293
|
+
* cancel() would discard the pending queue).
|
|
294
|
+
*/
|
|
295
|
+
shimSpeechSynthesis() {
|
|
296
|
+
if (!window.speechSynthesis || typeof SpeechSynthesisUtterance === "undefined") {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
this.originalUtteranceVolumeDescriptor = Object.getOwnPropertyDescriptor(
|
|
300
|
+
SpeechSynthesisUtterance.prototype,
|
|
301
|
+
"volume"
|
|
302
|
+
) ?? null;
|
|
303
|
+
const volDesc = this.originalUtteranceVolumeDescriptor;
|
|
304
|
+
if (volDesc?.get && volDesc?.set) {
|
|
305
|
+
((manager) => {
|
|
306
|
+
Object.defineProperty(SpeechSynthesisUtterance.prototype, "volume", {
|
|
307
|
+
configurable: true,
|
|
308
|
+
get() {
|
|
309
|
+
const intended = manager.intendedUtteranceVolume.get(this);
|
|
310
|
+
return intended !== void 0 ? intended : volDesc.get.call(this);
|
|
311
|
+
},
|
|
312
|
+
set(value) {
|
|
313
|
+
manager.intendedUtteranceVolume.set(this, value);
|
|
314
|
+
volDesc.set.call(this, value);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
})(this);
|
|
318
|
+
}
|
|
319
|
+
const speechSynthesis = window.speechSynthesis;
|
|
320
|
+
const originalSpeak = speechSynthesis.speak;
|
|
321
|
+
this.originalSpeak = originalSpeak;
|
|
322
|
+
((manager) => {
|
|
323
|
+
speechSynthesis.speak = function(utterance) {
|
|
324
|
+
if (manager._isMuted) {
|
|
325
|
+
if (!manager.intendedUtteranceVolume.has(utterance)) {
|
|
326
|
+
const current = volDesc?.get ? volDesc.get.call(utterance) : utterance.volume;
|
|
327
|
+
manager.intendedUtteranceVolume.set(utterance, current);
|
|
328
|
+
}
|
|
329
|
+
if (volDesc?.set) volDesc.set.call(utterance, 0);
|
|
330
|
+
else utterance.volume = 0;
|
|
331
|
+
} else {
|
|
332
|
+
const intended = manager.intendedUtteranceVolume.get(utterance);
|
|
333
|
+
if (intended !== void 0) {
|
|
334
|
+
if (volDesc?.set) volDesc.set.call(utterance, intended);
|
|
335
|
+
else utterance.volume = intended;
|
|
336
|
+
manager.intendedUtteranceVolume.delete(utterance);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return originalSpeak.call(speechSynthesis, utterance);
|
|
340
|
+
};
|
|
341
|
+
})(this);
|
|
278
342
|
}
|
|
279
343
|
shimAudioContextClass(Original) {
|
|
280
344
|
return /* @__PURE__ */ ((manager) => class extends Original {
|
|
@@ -320,6 +384,16 @@ var AudioManager = class extends WavedashManager {
|
|
|
320
384
|
if (this.originalAudio) {
|
|
321
385
|
window.Audio = this.originalAudio;
|
|
322
386
|
}
|
|
387
|
+
if (this.originalSpeak && window.speechSynthesis) {
|
|
388
|
+
window.speechSynthesis.speak = this.originalSpeak;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (this.originalUtteranceVolumeDescriptor && typeof SpeechSynthesisUtterance !== "undefined") {
|
|
392
|
+
Object.defineProperty(
|
|
393
|
+
SpeechSynthesisUtterance.prototype,
|
|
394
|
+
"volume",
|
|
395
|
+
this.originalUtteranceVolumeDescriptor
|
|
396
|
+
);
|
|
323
397
|
}
|
|
324
398
|
if (this.originalPlay) {
|
|
325
399
|
HTMLMediaElement.prototype.play = this.originalPlay;
|
|
@@ -334,6 +408,7 @@ var AudioManager = class extends WavedashManager {
|
|
|
334
408
|
this.contexts.clear();
|
|
335
409
|
this.elements.clear();
|
|
336
410
|
this.intendedMuted = /* @__PURE__ */ new WeakMap();
|
|
411
|
+
this.intendedUtteranceVolume = /* @__PURE__ */ new WeakMap();
|
|
337
412
|
super.destroy();
|
|
338
413
|
}
|
|
339
414
|
};
|
|
@@ -1849,6 +1924,44 @@ var LobbyManager = _LobbyManager;
|
|
|
1849
1924
|
|
|
1850
1925
|
// src/services/overlay.ts
|
|
1851
1926
|
import { IFRAME_MESSAGE_TYPE as IFRAME_MESSAGE_TYPE5 } from "@wvdsh/api";
|
|
1927
|
+
|
|
1928
|
+
// src/utils/focus.ts
|
|
1929
|
+
function takeFocus() {
|
|
1930
|
+
if (typeof document === "undefined") return;
|
|
1931
|
+
const gameFocusTargets = document.getElementsByClassName("game-focus-target");
|
|
1932
|
+
if (gameFocusTargets.length > 0) {
|
|
1933
|
+
gameFocusTargets[0].focus();
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
const focusableElement = document.querySelector(
|
|
1937
|
+
"canvas, input, button, [tabindex]:not([tabindex='-1'])"
|
|
1938
|
+
);
|
|
1939
|
+
focusableElement?.focus();
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
// src/utils/pointerLock.ts
|
|
1943
|
+
var hasDom = typeof Element !== "undefined" && typeof document !== "undefined";
|
|
1944
|
+
var nativeRequestPointerLock = hasDom ? Element.prototype.requestPointerLock : void 0;
|
|
1945
|
+
var depth = 0;
|
|
1946
|
+
function suspendPointerLock() {
|
|
1947
|
+
if (!hasDom || !nativeRequestPointerLock) return () => {
|
|
1948
|
+
};
|
|
1949
|
+
if (++depth === 1) {
|
|
1950
|
+
Element.prototype.requestPointerLock = function() {
|
|
1951
|
+
return Promise.resolve();
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
document.exitPointerLock();
|
|
1955
|
+
let disposed = false;
|
|
1956
|
+
return () => {
|
|
1957
|
+
if (disposed) return;
|
|
1958
|
+
disposed = true;
|
|
1959
|
+
if (--depth === 0)
|
|
1960
|
+
Element.prototype.requestPointerLock = nativeRequestPointerLock;
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
// src/services/overlay.ts
|
|
1852
1965
|
var OverlayManager = class extends WavedashManager {
|
|
1853
1966
|
constructor(sdk) {
|
|
1854
1967
|
super(sdk);
|
|
@@ -1860,29 +1973,36 @@ var OverlayManager = class extends WavedashManager {
|
|
|
1860
1973
|
};
|
|
1861
1974
|
this.sdk.iframeMessenger.addEventListener(
|
|
1862
1975
|
IFRAME_MESSAGE_TYPE5.TAKE_FOCUS,
|
|
1863
|
-
|
|
1976
|
+
takeFocus
|
|
1977
|
+
);
|
|
1978
|
+
this.sdk.iframeMessenger.addEventListener(
|
|
1979
|
+
IFRAME_MESSAGE_TYPE5.OVERLAY_CHANGED,
|
|
1980
|
+
({ isOpen }) => this.setOpen(isOpen)
|
|
1864
1981
|
);
|
|
1865
1982
|
if (typeof window !== "undefined") {
|
|
1866
1983
|
window.addEventListener("keydown", this.handleKeyDown);
|
|
1867
1984
|
}
|
|
1868
1985
|
}
|
|
1986
|
+
setOpen(open) {
|
|
1987
|
+
if (open) {
|
|
1988
|
+
this.restorePointerLock ?? (this.restorePointerLock = suspendPointerLock());
|
|
1989
|
+
} else {
|
|
1990
|
+
this.restorePointerLock?.();
|
|
1991
|
+
this.restorePointerLock = void 0;
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1869
1994
|
toggleOverlay() {
|
|
1870
1995
|
this.sdk.iframeMessenger.postToParent(
|
|
1871
1996
|
IFRAME_MESSAGE_TYPE5.TOGGLE_OVERLAY,
|
|
1872
1997
|
{}
|
|
1873
1998
|
);
|
|
1874
1999
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
if (
|
|
1879
|
-
|
|
1880
|
-
return;
|
|
2000
|
+
destroy() {
|
|
2001
|
+
this.restorePointerLock?.();
|
|
2002
|
+
this.restorePointerLock = void 0;
|
|
2003
|
+
if (typeof window !== "undefined") {
|
|
2004
|
+
window.removeEventListener("keydown", this.handleKeyDown);
|
|
1881
2005
|
}
|
|
1882
|
-
const focusableElement = document.querySelector(
|
|
1883
|
-
"canvas, input, button, [tabindex]:not([tabindex='-1'])"
|
|
1884
|
-
);
|
|
1885
|
-
focusableElement?.focus();
|
|
1886
2006
|
}
|
|
1887
2007
|
};
|
|
1888
2008
|
|
|
@@ -3204,6 +3324,10 @@ function readEntitlementsFromJwt(jwt) {
|
|
|
3204
3324
|
return ents.filter((e) => typeof e === "string");
|
|
3205
3325
|
}
|
|
3206
3326
|
var PaidContentManager = class extends WavedashManager {
|
|
3327
|
+
constructor() {
|
|
3328
|
+
super(...arguments);
|
|
3329
|
+
this.paywallOpen = false;
|
|
3330
|
+
}
|
|
3207
3331
|
async isEntitled(contentId) {
|
|
3208
3332
|
const jwt = await this.sdk.ensureGameplayJwt();
|
|
3209
3333
|
return readEntitlementsFromJwt(jwt).includes(contentId);
|
|
@@ -3214,15 +3338,34 @@ var PaidContentManager = class extends WavedashManager {
|
|
|
3214
3338
|
}
|
|
3215
3339
|
async triggerPaywall(contentIdentifier) {
|
|
3216
3340
|
if (await this.isEntitled(contentIdentifier)) return true;
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
);
|
|
3341
|
+
if (this.paywallOpen) {
|
|
3342
|
+
throw new Error("Paywall already in progress");
|
|
3343
|
+
}
|
|
3344
|
+
this.paywallOpen = true;
|
|
3345
|
+
this.restorePointerLock = suspendPointerLock();
|
|
3346
|
+
let response;
|
|
3347
|
+
try {
|
|
3348
|
+
response = await this.sdk.iframeMessenger.requestFromParent(
|
|
3349
|
+
IFRAME_MESSAGE_TYPE6.TRIGGER_PAYWALL,
|
|
3350
|
+
{ contentIdentifier },
|
|
3351
|
+
PAYWALL_TIMEOUT_MS
|
|
3352
|
+
);
|
|
3353
|
+
} finally {
|
|
3354
|
+
this.restorePointerLock?.();
|
|
3355
|
+
this.restorePointerLock = void 0;
|
|
3356
|
+
this.paywallOpen = false;
|
|
3357
|
+
}
|
|
3222
3358
|
if (!response.purchased) return false;
|
|
3223
3359
|
await this.sdk.ensureGameplayJwt(true);
|
|
3224
3360
|
return true;
|
|
3225
3361
|
}
|
|
3362
|
+
isPaywallOpen() {
|
|
3363
|
+
return this.paywallOpen;
|
|
3364
|
+
}
|
|
3365
|
+
destroy() {
|
|
3366
|
+
this.restorePointerLock?.();
|
|
3367
|
+
this.restorePointerLock = void 0;
|
|
3368
|
+
}
|
|
3226
3369
|
};
|
|
3227
3370
|
|
|
3228
3371
|
// src/services/stats.ts
|
|
@@ -4033,7 +4176,6 @@ var WavedashSDK = class extends EventTarget {
|
|
|
4033
4176
|
this.gameFinishedLoading = true;
|
|
4034
4177
|
this.heartbeatManager.start();
|
|
4035
4178
|
iframeMessenger.postToParent(IFRAME_MESSAGE_TYPE7.LOADING_COMPLETE, {});
|
|
4036
|
-
this.overlayManager.takeFocus();
|
|
4037
4179
|
}
|
|
4038
4180
|
get gameLoaded() {
|
|
4039
4181
|
return this.gameFinishedLoading;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wvdsh/sdk-js",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.21",
|
|
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.
|
|
52
|
+
"@wvdsh/api": "^0.1.36",
|
|
53
53
|
"convex": "^1.39.1",
|
|
54
54
|
"lodash.throttle": "^4.1.1"
|
|
55
55
|
}
|