@playruo/embed 1.2.9 → 1.3.0
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.cjs +108 -21
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +108 -21
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -43,11 +43,18 @@ var import_react = require("react");
|
|
|
43
43
|
|
|
44
44
|
// src/url.ts
|
|
45
45
|
var PLAYRUO_ORIGIN = "https://playruo.live";
|
|
46
|
-
var
|
|
46
|
+
var normalizeOperation = (operation) => {
|
|
47
|
+
const trimmedOperation = operation.trim();
|
|
48
|
+
if (trimmedOperation === "") {
|
|
49
|
+
throw new Error("`operation` is required to build a Playruo embed URL.");
|
|
50
|
+
}
|
|
51
|
+
return trimmedOperation.startsWith("/") ? trimmedOperation.slice(1) : trimmedOperation;
|
|
52
|
+
};
|
|
53
|
+
var buildEmbedUrl = ({ operation, storeUrl }) => {
|
|
47
54
|
return buildEmbedUrlWithOrigin(PLAYRUO_ORIGIN, { operation, storeUrl });
|
|
48
55
|
};
|
|
49
|
-
var buildEmbedUrlWithOrigin = (origin, { operation
|
|
50
|
-
const operationPath =
|
|
56
|
+
var buildEmbedUrlWithOrigin = (origin, { operation, storeUrl }) => {
|
|
57
|
+
const operationPath = normalizeOperation(operation);
|
|
51
58
|
const url = new URL(`/${operationPath}`, origin);
|
|
52
59
|
url.searchParams.set("iframe", "true");
|
|
53
60
|
if (storeUrl) {
|
|
@@ -60,7 +67,7 @@ var buildEmbedUrlWithOrigin = (origin, { operation = "iframe", storeUrl }) => {
|
|
|
60
67
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
61
68
|
var PlayruoEmbed = ({
|
|
62
69
|
baseUrl,
|
|
63
|
-
operation
|
|
70
|
+
operation,
|
|
64
71
|
storeUrl,
|
|
65
72
|
showIframe = false,
|
|
66
73
|
className,
|
|
@@ -75,6 +82,7 @@ var PlayruoEmbed = ({
|
|
|
75
82
|
const internalIframeRef = (0, import_react.useRef)(null);
|
|
76
83
|
const iframeRef = externalIframeRef || internalIframeRef;
|
|
77
84
|
const wasEmbedFullscreenRef = (0, import_react.useRef)(false);
|
|
85
|
+
const wasEmbedPointerLockedRef = (0, import_react.useRef)(false);
|
|
78
86
|
const [isIframeLoaded, setIsIframeLoaded] = (0, import_react.useState)(false);
|
|
79
87
|
const [hasPlayedOnce, setHasPlayedOnce] = (0, import_react.useState)(false);
|
|
80
88
|
const [showGameIframe, setShowGameIframe] = (0, import_react.useState)(false);
|
|
@@ -144,30 +152,79 @@ var PlayruoEmbed = ({
|
|
|
144
152
|
console.warn("[PlayruoEmbed] Failed to release Escape key lock:", error);
|
|
145
153
|
}
|
|
146
154
|
}, [getKeyboardApi]);
|
|
147
|
-
const
|
|
155
|
+
const lockEscapeKey = (0, import_react.useCallback)(async () => {
|
|
156
|
+
const keyboard = getKeyboardApi();
|
|
157
|
+
if (!keyboard?.lock) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
await keyboard.lock(["Escape"]);
|
|
162
|
+
console.log("[PlayruoEmbed] Escape key locked during fullscreen");
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.warn("[PlayruoEmbed] Failed to lock Escape key:", error);
|
|
165
|
+
}
|
|
166
|
+
}, [getKeyboardApi]);
|
|
167
|
+
const getPointerLockElement = (0, import_react.useCallback)(() => {
|
|
168
|
+
const doc = document;
|
|
169
|
+
return doc.pointerLockElement || doc.mozPointerLockElement || doc.webkitPointerLockElement || null;
|
|
170
|
+
}, []);
|
|
171
|
+
const releasePointerLock = (0, import_react.useCallback)(() => {
|
|
172
|
+
const doc = document;
|
|
173
|
+
const exitPointerLock = doc.exitPointerLock?.bind(doc) || doc.mozExitPointerLock?.bind(doc) || doc.webkitExitPointerLock?.bind(doc);
|
|
174
|
+
try {
|
|
175
|
+
exitPointerLock?.();
|
|
176
|
+
wasEmbedPointerLockedRef.current = false;
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.warn("[PlayruoEmbed] Failed to release pointer lock:", error);
|
|
179
|
+
}
|
|
180
|
+
}, []);
|
|
181
|
+
const requestImmersiveMode = (0, import_react.useCallback)(
|
|
148
182
|
async (iframe) => {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
183
|
+
const pointerTarget = iframe;
|
|
184
|
+
if (pointerTarget.requestPointerLock) {
|
|
185
|
+
try {
|
|
186
|
+
const pointerLockResult = pointerTarget.requestPointerLock({ unadjustedMovement: true });
|
|
187
|
+
if (pointerLockResult && typeof pointerLockResult.then === "function") {
|
|
188
|
+
void pointerLockResult.then(() => {
|
|
189
|
+
wasEmbedPointerLockedRef.current = true;
|
|
190
|
+
console.log("[PlayruoEmbed] Pointer locked on iframe");
|
|
191
|
+
}).catch(async (error) => {
|
|
192
|
+
if (error instanceof DOMException && error.name === "NotSupportedError") {
|
|
193
|
+
try {
|
|
194
|
+
const fallbackResult = pointerTarget.requestPointerLock?.();
|
|
195
|
+
if (fallbackResult && typeof fallbackResult.then === "function") {
|
|
196
|
+
await fallbackResult;
|
|
197
|
+
}
|
|
198
|
+
wasEmbedPointerLockedRef.current = true;
|
|
199
|
+
console.log("[PlayruoEmbed] Pointer locked on iframe without raw input");
|
|
200
|
+
return;
|
|
201
|
+
} catch (fallbackError) {
|
|
202
|
+
console.warn("[PlayruoEmbed] Failed to lock pointer without raw input:", fallbackError);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
console.warn("[PlayruoEmbed] Failed to lock pointer:", error);
|
|
207
|
+
});
|
|
208
|
+
} else {
|
|
209
|
+
wasEmbedPointerLockedRef.current = true;
|
|
210
|
+
console.log("[PlayruoEmbed] Pointer lock requested on iframe");
|
|
158
211
|
}
|
|
159
|
-
})
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.warn("[PlayruoEmbed] Failed to request pointer lock:", error);
|
|
214
|
+
}
|
|
160
215
|
}
|
|
216
|
+
const fullscreenPromise = iframe.requestFullscreen();
|
|
161
217
|
try {
|
|
162
218
|
await fullscreenPromise;
|
|
163
219
|
wasEmbedFullscreenRef.current = true;
|
|
164
220
|
console.log("[PlayruoEmbed] Fullscreen granted");
|
|
165
221
|
} catch (error) {
|
|
166
222
|
releaseEscapeKeyLock();
|
|
223
|
+
releasePointerLock();
|
|
167
224
|
throw error;
|
|
168
225
|
}
|
|
169
226
|
},
|
|
170
|
-
[
|
|
227
|
+
[releaseEscapeKeyLock, releasePointerLock]
|
|
171
228
|
);
|
|
172
229
|
const handleTryNow = (0, import_react.useCallback)(async () => {
|
|
173
230
|
if (!isCompatible) {
|
|
@@ -182,7 +239,7 @@ var PlayruoEmbed = ({
|
|
|
182
239
|
setShowGameIframe(true);
|
|
183
240
|
try {
|
|
184
241
|
try {
|
|
185
|
-
await
|
|
242
|
+
await requestImmersiveMode(iframe);
|
|
186
243
|
} catch (fsError) {
|
|
187
244
|
console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
|
|
188
245
|
}
|
|
@@ -190,6 +247,8 @@ var PlayruoEmbed = ({
|
|
|
190
247
|
type: "playruo",
|
|
191
248
|
action: "tryNowClick",
|
|
192
249
|
fromEmbed: true,
|
|
250
|
+
isFullscreen: true,
|
|
251
|
+
parentWillHandleFullscreen: true,
|
|
193
252
|
isReplay: hasPlayedOnce
|
|
194
253
|
}, "*");
|
|
195
254
|
console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
|
|
@@ -203,7 +262,7 @@ var PlayruoEmbed = ({
|
|
|
203
262
|
setErrorMessage("Failed to start game");
|
|
204
263
|
onError?.("Failed to start game");
|
|
205
264
|
}
|
|
206
|
-
}, [isCompatible, hasPlayedOnce, onError, iframeRef,
|
|
265
|
+
}, [isCompatible, hasPlayedOnce, onError, iframeRef, requestImmersiveMode]);
|
|
207
266
|
const handleBuyNow = (0, import_react.useCallback)(() => {
|
|
208
267
|
if (!storeUrl) {
|
|
209
268
|
console.warn("[PlayruoEmbed] No store URL provided");
|
|
@@ -337,6 +396,7 @@ var PlayruoEmbed = ({
|
|
|
337
396
|
const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
|
|
338
397
|
if (isEmbedFullscreen) {
|
|
339
398
|
wasEmbedFullscreenRef.current = true;
|
|
399
|
+
void lockEscapeKey();
|
|
340
400
|
if (iframe) {
|
|
341
401
|
setTimeout(() => {
|
|
342
402
|
iframe.focus();
|
|
@@ -351,12 +411,18 @@ var PlayruoEmbed = ({
|
|
|
351
411
|
releaseEscapeKeyLock();
|
|
352
412
|
if (wasEmbedFullscreenRef.current) {
|
|
353
413
|
wasEmbedFullscreenRef.current = false;
|
|
414
|
+
const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
|
|
415
|
+
if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
|
|
416
|
+
releasePointerLock();
|
|
417
|
+
}
|
|
354
418
|
if (iframe) {
|
|
355
419
|
try {
|
|
356
420
|
iframe.contentWindow?.postMessage({
|
|
357
421
|
type: "playruo",
|
|
358
422
|
action: "exitFullscreen",
|
|
359
|
-
fromEmbed: true
|
|
423
|
+
fromEmbed: true,
|
|
424
|
+
isFullscreen: false,
|
|
425
|
+
parentWillHandleFullscreen: true
|
|
360
426
|
}, "*");
|
|
361
427
|
} catch {
|
|
362
428
|
console.error("[PlayruoEmbed] Failed to exit fullscreen");
|
|
@@ -380,12 +446,33 @@ var PlayruoEmbed = ({
|
|
|
380
446
|
document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
|
|
381
447
|
document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
|
|
382
448
|
};
|
|
383
|
-
}, [storeUrl, onDemoCompleted, iframeRef, releaseEscapeKeyLock]);
|
|
449
|
+
}, [storeUrl, onDemoCompleted, iframeRef, getPointerLockElement, lockEscapeKey, releaseEscapeKeyLock, releasePointerLock]);
|
|
450
|
+
(0, import_react.useEffect)(() => {
|
|
451
|
+
const handlePointerLockChange = () => {
|
|
452
|
+
const iframe = iframeRef.current;
|
|
453
|
+
wasEmbedPointerLockedRef.current = Boolean(iframe && getPointerLockElement() === iframe);
|
|
454
|
+
};
|
|
455
|
+
const handlePointerLockError = () => {
|
|
456
|
+
wasEmbedPointerLockedRef.current = false;
|
|
457
|
+
console.warn("[PlayruoEmbed] Pointer lock request was rejected");
|
|
458
|
+
};
|
|
459
|
+
document.addEventListener("pointerlockchange", handlePointerLockChange);
|
|
460
|
+
document.addEventListener("pointerlockerror", handlePointerLockError);
|
|
461
|
+
return () => {
|
|
462
|
+
document.removeEventListener("pointerlockchange", handlePointerLockChange);
|
|
463
|
+
document.removeEventListener("pointerlockerror", handlePointerLockError);
|
|
464
|
+
};
|
|
465
|
+
}, [getPointerLockElement, iframeRef]);
|
|
384
466
|
(0, import_react.useEffect)(() => {
|
|
385
467
|
return () => {
|
|
386
468
|
releaseEscapeKeyLock();
|
|
469
|
+
const iframe = iframeRef.current;
|
|
470
|
+
const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
|
|
471
|
+
if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
|
|
472
|
+
releasePointerLock();
|
|
473
|
+
}
|
|
387
474
|
};
|
|
388
|
-
}, [releaseEscapeKeyLock]);
|
|
475
|
+
}, [getPointerLockElement, iframeRef, releaseEscapeKeyLock, releasePointerLock]);
|
|
389
476
|
(0, import_react.useEffect)(() => {
|
|
390
477
|
const logCrossOriginStatus = () => {
|
|
391
478
|
const isIsolated = self.crossOriginIsolated;
|
package/dist/index.d.cts
CHANGED
|
@@ -36,7 +36,8 @@ interface PlayruoEmbedRenderState {
|
|
|
36
36
|
interface PlayruoEmbedProps {
|
|
37
37
|
/** Game origin (scheme + host). Omit to use production (`https://playruo.live`). */
|
|
38
38
|
baseUrl?: string;
|
|
39
|
-
operation
|
|
39
|
+
/** Game operation slug used to build the iframe URL. */
|
|
40
|
+
operation: string;
|
|
40
41
|
storeUrl?: string;
|
|
41
42
|
showIframe?: boolean;
|
|
42
43
|
className?: string;
|
|
@@ -108,6 +109,7 @@ interface PlayruoInternalMessage {
|
|
|
108
109
|
action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
|
|
109
110
|
fromEmbed: true;
|
|
110
111
|
isReplay?: boolean;
|
|
112
|
+
isFullscreen?: boolean;
|
|
111
113
|
parentWillHandleFullscreen?: boolean;
|
|
112
114
|
}
|
|
113
115
|
/**
|
|
@@ -123,7 +125,7 @@ declare const isPlayruoEmbedMessage: (data: unknown) => data is PlayruoEmbedMess
|
|
|
123
125
|
declare const isPlayruoInternalMessage: (data: unknown) => data is PlayruoInternalMessage;
|
|
124
126
|
|
|
125
127
|
interface BuildUrlParams {
|
|
126
|
-
operation
|
|
128
|
+
operation: string;
|
|
127
129
|
storeUrl?: string;
|
|
128
130
|
}
|
|
129
131
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -36,7 +36,8 @@ interface PlayruoEmbedRenderState {
|
|
|
36
36
|
interface PlayruoEmbedProps {
|
|
37
37
|
/** Game origin (scheme + host). Omit to use production (`https://playruo.live`). */
|
|
38
38
|
baseUrl?: string;
|
|
39
|
-
operation
|
|
39
|
+
/** Game operation slug used to build the iframe URL. */
|
|
40
|
+
operation: string;
|
|
40
41
|
storeUrl?: string;
|
|
41
42
|
showIframe?: boolean;
|
|
42
43
|
className?: string;
|
|
@@ -108,6 +109,7 @@ interface PlayruoInternalMessage {
|
|
|
108
109
|
action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
|
|
109
110
|
fromEmbed: true;
|
|
110
111
|
isReplay?: boolean;
|
|
112
|
+
isFullscreen?: boolean;
|
|
111
113
|
parentWillHandleFullscreen?: boolean;
|
|
112
114
|
}
|
|
113
115
|
/**
|
|
@@ -123,7 +125,7 @@ declare const isPlayruoEmbedMessage: (data: unknown) => data is PlayruoEmbedMess
|
|
|
123
125
|
declare const isPlayruoInternalMessage: (data: unknown) => data is PlayruoInternalMessage;
|
|
124
126
|
|
|
125
127
|
interface BuildUrlParams {
|
|
126
|
-
operation
|
|
128
|
+
operation: string;
|
|
127
129
|
storeUrl?: string;
|
|
128
130
|
}
|
|
129
131
|
/**
|
package/dist/index.js
CHANGED
|
@@ -3,11 +3,18 @@ import { useEffect, useMemo, useRef, useState, useCallback } from "react";
|
|
|
3
3
|
|
|
4
4
|
// src/url.ts
|
|
5
5
|
var PLAYRUO_ORIGIN = "https://playruo.live";
|
|
6
|
-
var
|
|
6
|
+
var normalizeOperation = (operation) => {
|
|
7
|
+
const trimmedOperation = operation.trim();
|
|
8
|
+
if (trimmedOperation === "") {
|
|
9
|
+
throw new Error("`operation` is required to build a Playruo embed URL.");
|
|
10
|
+
}
|
|
11
|
+
return trimmedOperation.startsWith("/") ? trimmedOperation.slice(1) : trimmedOperation;
|
|
12
|
+
};
|
|
13
|
+
var buildEmbedUrl = ({ operation, storeUrl }) => {
|
|
7
14
|
return buildEmbedUrlWithOrigin(PLAYRUO_ORIGIN, { operation, storeUrl });
|
|
8
15
|
};
|
|
9
|
-
var buildEmbedUrlWithOrigin = (origin, { operation
|
|
10
|
-
const operationPath =
|
|
16
|
+
var buildEmbedUrlWithOrigin = (origin, { operation, storeUrl }) => {
|
|
17
|
+
const operationPath = normalizeOperation(operation);
|
|
11
18
|
const url = new URL(`/${operationPath}`, origin);
|
|
12
19
|
url.searchParams.set("iframe", "true");
|
|
13
20
|
if (storeUrl) {
|
|
@@ -20,7 +27,7 @@ var buildEmbedUrlWithOrigin = (origin, { operation = "iframe", storeUrl }) => {
|
|
|
20
27
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
21
28
|
var PlayruoEmbed = ({
|
|
22
29
|
baseUrl,
|
|
23
|
-
operation
|
|
30
|
+
operation,
|
|
24
31
|
storeUrl,
|
|
25
32
|
showIframe = false,
|
|
26
33
|
className,
|
|
@@ -35,6 +42,7 @@ var PlayruoEmbed = ({
|
|
|
35
42
|
const internalIframeRef = useRef(null);
|
|
36
43
|
const iframeRef = externalIframeRef || internalIframeRef;
|
|
37
44
|
const wasEmbedFullscreenRef = useRef(false);
|
|
45
|
+
const wasEmbedPointerLockedRef = useRef(false);
|
|
38
46
|
const [isIframeLoaded, setIsIframeLoaded] = useState(false);
|
|
39
47
|
const [hasPlayedOnce, setHasPlayedOnce] = useState(false);
|
|
40
48
|
const [showGameIframe, setShowGameIframe] = useState(false);
|
|
@@ -104,30 +112,79 @@ var PlayruoEmbed = ({
|
|
|
104
112
|
console.warn("[PlayruoEmbed] Failed to release Escape key lock:", error);
|
|
105
113
|
}
|
|
106
114
|
}, [getKeyboardApi]);
|
|
107
|
-
const
|
|
115
|
+
const lockEscapeKey = useCallback(async () => {
|
|
116
|
+
const keyboard = getKeyboardApi();
|
|
117
|
+
if (!keyboard?.lock) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
await keyboard.lock(["Escape"]);
|
|
122
|
+
console.log("[PlayruoEmbed] Escape key locked during fullscreen");
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.warn("[PlayruoEmbed] Failed to lock Escape key:", error);
|
|
125
|
+
}
|
|
126
|
+
}, [getKeyboardApi]);
|
|
127
|
+
const getPointerLockElement = useCallback(() => {
|
|
128
|
+
const doc = document;
|
|
129
|
+
return doc.pointerLockElement || doc.mozPointerLockElement || doc.webkitPointerLockElement || null;
|
|
130
|
+
}, []);
|
|
131
|
+
const releasePointerLock = useCallback(() => {
|
|
132
|
+
const doc = document;
|
|
133
|
+
const exitPointerLock = doc.exitPointerLock?.bind(doc) || doc.mozExitPointerLock?.bind(doc) || doc.webkitExitPointerLock?.bind(doc);
|
|
134
|
+
try {
|
|
135
|
+
exitPointerLock?.();
|
|
136
|
+
wasEmbedPointerLockedRef.current = false;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.warn("[PlayruoEmbed] Failed to release pointer lock:", error);
|
|
139
|
+
}
|
|
140
|
+
}, []);
|
|
141
|
+
const requestImmersiveMode = useCallback(
|
|
108
142
|
async (iframe) => {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
143
|
+
const pointerTarget = iframe;
|
|
144
|
+
if (pointerTarget.requestPointerLock) {
|
|
145
|
+
try {
|
|
146
|
+
const pointerLockResult = pointerTarget.requestPointerLock({ unadjustedMovement: true });
|
|
147
|
+
if (pointerLockResult && typeof pointerLockResult.then === "function") {
|
|
148
|
+
void pointerLockResult.then(() => {
|
|
149
|
+
wasEmbedPointerLockedRef.current = true;
|
|
150
|
+
console.log("[PlayruoEmbed] Pointer locked on iframe");
|
|
151
|
+
}).catch(async (error) => {
|
|
152
|
+
if (error instanceof DOMException && error.name === "NotSupportedError") {
|
|
153
|
+
try {
|
|
154
|
+
const fallbackResult = pointerTarget.requestPointerLock?.();
|
|
155
|
+
if (fallbackResult && typeof fallbackResult.then === "function") {
|
|
156
|
+
await fallbackResult;
|
|
157
|
+
}
|
|
158
|
+
wasEmbedPointerLockedRef.current = true;
|
|
159
|
+
console.log("[PlayruoEmbed] Pointer locked on iframe without raw input");
|
|
160
|
+
return;
|
|
161
|
+
} catch (fallbackError) {
|
|
162
|
+
console.warn("[PlayruoEmbed] Failed to lock pointer without raw input:", fallbackError);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
console.warn("[PlayruoEmbed] Failed to lock pointer:", error);
|
|
167
|
+
});
|
|
168
|
+
} else {
|
|
169
|
+
wasEmbedPointerLockedRef.current = true;
|
|
170
|
+
console.log("[PlayruoEmbed] Pointer lock requested on iframe");
|
|
118
171
|
}
|
|
119
|
-
})
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.warn("[PlayruoEmbed] Failed to request pointer lock:", error);
|
|
174
|
+
}
|
|
120
175
|
}
|
|
176
|
+
const fullscreenPromise = iframe.requestFullscreen();
|
|
121
177
|
try {
|
|
122
178
|
await fullscreenPromise;
|
|
123
179
|
wasEmbedFullscreenRef.current = true;
|
|
124
180
|
console.log("[PlayruoEmbed] Fullscreen granted");
|
|
125
181
|
} catch (error) {
|
|
126
182
|
releaseEscapeKeyLock();
|
|
183
|
+
releasePointerLock();
|
|
127
184
|
throw error;
|
|
128
185
|
}
|
|
129
186
|
},
|
|
130
|
-
[
|
|
187
|
+
[releaseEscapeKeyLock, releasePointerLock]
|
|
131
188
|
);
|
|
132
189
|
const handleTryNow = useCallback(async () => {
|
|
133
190
|
if (!isCompatible) {
|
|
@@ -142,7 +199,7 @@ var PlayruoEmbed = ({
|
|
|
142
199
|
setShowGameIframe(true);
|
|
143
200
|
try {
|
|
144
201
|
try {
|
|
145
|
-
await
|
|
202
|
+
await requestImmersiveMode(iframe);
|
|
146
203
|
} catch (fsError) {
|
|
147
204
|
console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
|
|
148
205
|
}
|
|
@@ -150,6 +207,8 @@ var PlayruoEmbed = ({
|
|
|
150
207
|
type: "playruo",
|
|
151
208
|
action: "tryNowClick",
|
|
152
209
|
fromEmbed: true,
|
|
210
|
+
isFullscreen: true,
|
|
211
|
+
parentWillHandleFullscreen: true,
|
|
153
212
|
isReplay: hasPlayedOnce
|
|
154
213
|
}, "*");
|
|
155
214
|
console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
|
|
@@ -163,7 +222,7 @@ var PlayruoEmbed = ({
|
|
|
163
222
|
setErrorMessage("Failed to start game");
|
|
164
223
|
onError?.("Failed to start game");
|
|
165
224
|
}
|
|
166
|
-
}, [isCompatible, hasPlayedOnce, onError, iframeRef,
|
|
225
|
+
}, [isCompatible, hasPlayedOnce, onError, iframeRef, requestImmersiveMode]);
|
|
167
226
|
const handleBuyNow = useCallback(() => {
|
|
168
227
|
if (!storeUrl) {
|
|
169
228
|
console.warn("[PlayruoEmbed] No store URL provided");
|
|
@@ -297,6 +356,7 @@ var PlayruoEmbed = ({
|
|
|
297
356
|
const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
|
|
298
357
|
if (isEmbedFullscreen) {
|
|
299
358
|
wasEmbedFullscreenRef.current = true;
|
|
359
|
+
void lockEscapeKey();
|
|
300
360
|
if (iframe) {
|
|
301
361
|
setTimeout(() => {
|
|
302
362
|
iframe.focus();
|
|
@@ -311,12 +371,18 @@ var PlayruoEmbed = ({
|
|
|
311
371
|
releaseEscapeKeyLock();
|
|
312
372
|
if (wasEmbedFullscreenRef.current) {
|
|
313
373
|
wasEmbedFullscreenRef.current = false;
|
|
374
|
+
const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
|
|
375
|
+
if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
|
|
376
|
+
releasePointerLock();
|
|
377
|
+
}
|
|
314
378
|
if (iframe) {
|
|
315
379
|
try {
|
|
316
380
|
iframe.contentWindow?.postMessage({
|
|
317
381
|
type: "playruo",
|
|
318
382
|
action: "exitFullscreen",
|
|
319
|
-
fromEmbed: true
|
|
383
|
+
fromEmbed: true,
|
|
384
|
+
isFullscreen: false,
|
|
385
|
+
parentWillHandleFullscreen: true
|
|
320
386
|
}, "*");
|
|
321
387
|
} catch {
|
|
322
388
|
console.error("[PlayruoEmbed] Failed to exit fullscreen");
|
|
@@ -340,12 +406,33 @@ var PlayruoEmbed = ({
|
|
|
340
406
|
document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
|
|
341
407
|
document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
|
|
342
408
|
};
|
|
343
|
-
}, [storeUrl, onDemoCompleted, iframeRef, releaseEscapeKeyLock]);
|
|
409
|
+
}, [storeUrl, onDemoCompleted, iframeRef, getPointerLockElement, lockEscapeKey, releaseEscapeKeyLock, releasePointerLock]);
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
const handlePointerLockChange = () => {
|
|
412
|
+
const iframe = iframeRef.current;
|
|
413
|
+
wasEmbedPointerLockedRef.current = Boolean(iframe && getPointerLockElement() === iframe);
|
|
414
|
+
};
|
|
415
|
+
const handlePointerLockError = () => {
|
|
416
|
+
wasEmbedPointerLockedRef.current = false;
|
|
417
|
+
console.warn("[PlayruoEmbed] Pointer lock request was rejected");
|
|
418
|
+
};
|
|
419
|
+
document.addEventListener("pointerlockchange", handlePointerLockChange);
|
|
420
|
+
document.addEventListener("pointerlockerror", handlePointerLockError);
|
|
421
|
+
return () => {
|
|
422
|
+
document.removeEventListener("pointerlockchange", handlePointerLockChange);
|
|
423
|
+
document.removeEventListener("pointerlockerror", handlePointerLockError);
|
|
424
|
+
};
|
|
425
|
+
}, [getPointerLockElement, iframeRef]);
|
|
344
426
|
useEffect(() => {
|
|
345
427
|
return () => {
|
|
346
428
|
releaseEscapeKeyLock();
|
|
429
|
+
const iframe = iframeRef.current;
|
|
430
|
+
const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
|
|
431
|
+
if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
|
|
432
|
+
releasePointerLock();
|
|
433
|
+
}
|
|
347
434
|
};
|
|
348
|
-
}, [releaseEscapeKeyLock]);
|
|
435
|
+
}, [getPointerLockElement, iframeRef, releaseEscapeKeyLock, releasePointerLock]);
|
|
349
436
|
useEffect(() => {
|
|
350
437
|
const logCrossOriginStatus = () => {
|
|
351
438
|
const isIsolated = self.crossOriginIsolated;
|