@playruo/embed 1.2.7 → 1.2.10
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 +155 -16
- package/dist/index.d.cts +7 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +155 -16
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -42,9 +42,13 @@ module.exports = __toCommonJS(index_exports);
|
|
|
42
42
|
var import_react = require("react");
|
|
43
43
|
|
|
44
44
|
// src/url.ts
|
|
45
|
-
var
|
|
45
|
+
var PLAYRUO_ORIGIN = "https://playruo.live";
|
|
46
|
+
var buildEmbedUrl = ({ operation = "iframe", storeUrl }) => {
|
|
47
|
+
return buildEmbedUrlWithOrigin(PLAYRUO_ORIGIN, { operation, storeUrl });
|
|
48
|
+
};
|
|
49
|
+
var buildEmbedUrlWithOrigin = (origin, { operation = "iframe", storeUrl }) => {
|
|
46
50
|
const operationPath = operation.startsWith("/") ? operation.slice(1) : operation;
|
|
47
|
-
const url = new URL(`/${operationPath}`,
|
|
51
|
+
const url = new URL(`/${operationPath}`, origin);
|
|
48
52
|
url.searchParams.set("iframe", "true");
|
|
49
53
|
if (storeUrl) {
|
|
50
54
|
url.searchParams.set("storeUrl", storeUrl);
|
|
@@ -70,6 +74,8 @@ var PlayruoEmbed = ({
|
|
|
70
74
|
}) => {
|
|
71
75
|
const internalIframeRef = (0, import_react.useRef)(null);
|
|
72
76
|
const iframeRef = externalIframeRef || internalIframeRef;
|
|
77
|
+
const wasEmbedFullscreenRef = (0, import_react.useRef)(false);
|
|
78
|
+
const wasEmbedPointerLockedRef = (0, import_react.useRef)(false);
|
|
73
79
|
const [isIframeLoaded, setIsIframeLoaded] = (0, import_react.useState)(false);
|
|
74
80
|
const [hasPlayedOnce, setHasPlayedOnce] = (0, import_react.useState)(false);
|
|
75
81
|
const [showGameIframe, setShowGameIframe] = (0, import_react.useState)(false);
|
|
@@ -77,7 +83,10 @@ var PlayruoEmbed = ({
|
|
|
77
83
|
const [isValidChromium, setIsValidChromium] = (0, import_react.useState)(true);
|
|
78
84
|
const [errorMessage, setErrorMessage] = (0, import_react.useState)(null);
|
|
79
85
|
const [iframeState, setIframeState] = (0, import_react.useState)(null);
|
|
80
|
-
const src = (0, import_react.useMemo)(
|
|
86
|
+
const src = (0, import_react.useMemo)(
|
|
87
|
+
() => baseUrl != null && baseUrl !== "" ? buildEmbedUrlWithOrigin(baseUrl, { operation, storeUrl }) : buildEmbedUrl({ operation, storeUrl }),
|
|
88
|
+
[baseUrl, operation, storeUrl]
|
|
89
|
+
);
|
|
81
90
|
(0, import_react.useEffect)(() => {
|
|
82
91
|
if (typeof window === "undefined") return;
|
|
83
92
|
const detectMobile = () => {
|
|
@@ -122,6 +131,94 @@ var PlayruoEmbed = ({
|
|
|
122
131
|
}),
|
|
123
132
|
[currentState, hasIframeConfirmed, isCompatible]
|
|
124
133
|
);
|
|
134
|
+
const getKeyboardApi = (0, import_react.useCallback)(() => {
|
|
135
|
+
if (typeof navigator === "undefined") {
|
|
136
|
+
return void 0;
|
|
137
|
+
}
|
|
138
|
+
return navigator.keyboard;
|
|
139
|
+
}, []);
|
|
140
|
+
const releaseEscapeKeyLock = (0, import_react.useCallback)(() => {
|
|
141
|
+
const keyboard = getKeyboardApi();
|
|
142
|
+
try {
|
|
143
|
+
keyboard?.unlock?.();
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.warn("[PlayruoEmbed] Failed to release Escape key lock:", error);
|
|
146
|
+
}
|
|
147
|
+
}, [getKeyboardApi]);
|
|
148
|
+
const lockEscapeKey = (0, import_react.useCallback)(async () => {
|
|
149
|
+
const keyboard = getKeyboardApi();
|
|
150
|
+
if (!keyboard?.lock) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
await keyboard.lock(["Escape"]);
|
|
155
|
+
console.log("[PlayruoEmbed] Escape key locked during fullscreen");
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.warn("[PlayruoEmbed] Failed to lock Escape key:", error);
|
|
158
|
+
}
|
|
159
|
+
}, [getKeyboardApi]);
|
|
160
|
+
const getPointerLockElement = (0, import_react.useCallback)(() => {
|
|
161
|
+
const doc = document;
|
|
162
|
+
return doc.pointerLockElement || doc.mozPointerLockElement || doc.webkitPointerLockElement || null;
|
|
163
|
+
}, []);
|
|
164
|
+
const releasePointerLock = (0, import_react.useCallback)(() => {
|
|
165
|
+
const doc = document;
|
|
166
|
+
const exitPointerLock = doc.exitPointerLock?.bind(doc) || doc.mozExitPointerLock?.bind(doc) || doc.webkitExitPointerLock?.bind(doc);
|
|
167
|
+
try {
|
|
168
|
+
exitPointerLock?.();
|
|
169
|
+
wasEmbedPointerLockedRef.current = false;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.warn("[PlayruoEmbed] Failed to release pointer lock:", error);
|
|
172
|
+
}
|
|
173
|
+
}, []);
|
|
174
|
+
const requestImmersiveMode = (0, import_react.useCallback)(
|
|
175
|
+
async (iframe) => {
|
|
176
|
+
const pointerTarget = iframe;
|
|
177
|
+
if (pointerTarget.requestPointerLock) {
|
|
178
|
+
try {
|
|
179
|
+
const pointerLockResult = pointerTarget.requestPointerLock({ unadjustedMovement: true });
|
|
180
|
+
if (pointerLockResult && typeof pointerLockResult.then === "function") {
|
|
181
|
+
void pointerLockResult.then(() => {
|
|
182
|
+
wasEmbedPointerLockedRef.current = true;
|
|
183
|
+
console.log("[PlayruoEmbed] Pointer locked on iframe");
|
|
184
|
+
}).catch(async (error) => {
|
|
185
|
+
if (error instanceof DOMException && error.name === "NotSupportedError") {
|
|
186
|
+
try {
|
|
187
|
+
const fallbackResult = pointerTarget.requestPointerLock?.();
|
|
188
|
+
if (fallbackResult && typeof fallbackResult.then === "function") {
|
|
189
|
+
await fallbackResult;
|
|
190
|
+
}
|
|
191
|
+
wasEmbedPointerLockedRef.current = true;
|
|
192
|
+
console.log("[PlayruoEmbed] Pointer locked on iframe without raw input");
|
|
193
|
+
return;
|
|
194
|
+
} catch (fallbackError) {
|
|
195
|
+
console.warn("[PlayruoEmbed] Failed to lock pointer without raw input:", fallbackError);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
console.warn("[PlayruoEmbed] Failed to lock pointer:", error);
|
|
200
|
+
});
|
|
201
|
+
} else {
|
|
202
|
+
wasEmbedPointerLockedRef.current = true;
|
|
203
|
+
console.log("[PlayruoEmbed] Pointer lock requested on iframe");
|
|
204
|
+
}
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.warn("[PlayruoEmbed] Failed to request pointer lock:", error);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const fullscreenPromise = iframe.requestFullscreen();
|
|
210
|
+
try {
|
|
211
|
+
await fullscreenPromise;
|
|
212
|
+
wasEmbedFullscreenRef.current = true;
|
|
213
|
+
console.log("[PlayruoEmbed] Fullscreen granted");
|
|
214
|
+
} catch (error) {
|
|
215
|
+
releaseEscapeKeyLock();
|
|
216
|
+
releasePointerLock();
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
[releaseEscapeKeyLock, releasePointerLock]
|
|
221
|
+
);
|
|
125
222
|
const handleTryNow = (0, import_react.useCallback)(async () => {
|
|
126
223
|
if (!isCompatible) {
|
|
127
224
|
console.warn("[PlayruoEmbed] Browser not compatible");
|
|
@@ -135,8 +232,7 @@ var PlayruoEmbed = ({
|
|
|
135
232
|
setShowGameIframe(true);
|
|
136
233
|
try {
|
|
137
234
|
try {
|
|
138
|
-
await iframe
|
|
139
|
-
console.log("[PlayruoEmbed] Fullscreen granted");
|
|
235
|
+
await requestImmersiveMode(iframe);
|
|
140
236
|
} catch (fsError) {
|
|
141
237
|
console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
|
|
142
238
|
}
|
|
@@ -144,6 +240,8 @@ var PlayruoEmbed = ({
|
|
|
144
240
|
type: "playruo",
|
|
145
241
|
action: "tryNowClick",
|
|
146
242
|
fromEmbed: true,
|
|
243
|
+
isFullscreen: true,
|
|
244
|
+
parentWillHandleFullscreen: true,
|
|
147
245
|
isReplay: hasPlayedOnce
|
|
148
246
|
}, "*");
|
|
149
247
|
console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
|
|
@@ -157,7 +255,7 @@ var PlayruoEmbed = ({
|
|
|
157
255
|
setErrorMessage("Failed to start game");
|
|
158
256
|
onError?.("Failed to start game");
|
|
159
257
|
}
|
|
160
|
-
}, [isCompatible, hasPlayedOnce, onError, iframeRef]);
|
|
258
|
+
}, [isCompatible, hasPlayedOnce, onError, iframeRef, requestImmersiveMode]);
|
|
161
259
|
const handleBuyNow = (0, import_react.useCallback)(() => {
|
|
162
260
|
if (!storeUrl) {
|
|
163
261
|
console.warn("[PlayruoEmbed] No store URL provided");
|
|
@@ -286,23 +384,38 @@ var PlayruoEmbed = ({
|
|
|
286
384
|
(0, import_react.useEffect)(() => {
|
|
287
385
|
const handleFullscreenChange = () => {
|
|
288
386
|
const doc = document;
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
387
|
+
const iframe = iframeRef.current;
|
|
388
|
+
const fullscreenElement = doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement || null;
|
|
389
|
+
const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
|
|
390
|
+
if (isEmbedFullscreen) {
|
|
391
|
+
wasEmbedFullscreenRef.current = true;
|
|
392
|
+
void lockEscapeKey();
|
|
292
393
|
if (iframe) {
|
|
293
394
|
setTimeout(() => {
|
|
294
395
|
iframe.focus();
|
|
295
396
|
iframe.contentWindow?.focus();
|
|
296
397
|
}, 200);
|
|
297
398
|
}
|
|
298
|
-
|
|
299
|
-
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (fullscreenElement) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
releaseEscapeKeyLock();
|
|
405
|
+
if (wasEmbedFullscreenRef.current) {
|
|
406
|
+
wasEmbedFullscreenRef.current = false;
|
|
407
|
+
const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
|
|
408
|
+
if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
|
|
409
|
+
releasePointerLock();
|
|
410
|
+
}
|
|
300
411
|
if (iframe) {
|
|
301
412
|
try {
|
|
302
413
|
iframe.contentWindow?.postMessage({
|
|
303
414
|
type: "playruo",
|
|
304
415
|
action: "exitFullscreen",
|
|
305
|
-
fromEmbed: true
|
|
416
|
+
fromEmbed: true,
|
|
417
|
+
isFullscreen: false,
|
|
418
|
+
parentWillHandleFullscreen: true
|
|
306
419
|
}, "*");
|
|
307
420
|
} catch {
|
|
308
421
|
console.error("[PlayruoEmbed] Failed to exit fullscreen");
|
|
@@ -326,7 +439,33 @@ var PlayruoEmbed = ({
|
|
|
326
439
|
document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
|
|
327
440
|
document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
|
|
328
441
|
};
|
|
329
|
-
}, [storeUrl, onDemoCompleted, iframeRef]);
|
|
442
|
+
}, [storeUrl, onDemoCompleted, iframeRef, getPointerLockElement, lockEscapeKey, releaseEscapeKeyLock, releasePointerLock]);
|
|
443
|
+
(0, import_react.useEffect)(() => {
|
|
444
|
+
const handlePointerLockChange = () => {
|
|
445
|
+
const iframe = iframeRef.current;
|
|
446
|
+
wasEmbedPointerLockedRef.current = Boolean(iframe && getPointerLockElement() === iframe);
|
|
447
|
+
};
|
|
448
|
+
const handlePointerLockError = () => {
|
|
449
|
+
wasEmbedPointerLockedRef.current = false;
|
|
450
|
+
console.warn("[PlayruoEmbed] Pointer lock request was rejected");
|
|
451
|
+
};
|
|
452
|
+
document.addEventListener("pointerlockchange", handlePointerLockChange);
|
|
453
|
+
document.addEventListener("pointerlockerror", handlePointerLockError);
|
|
454
|
+
return () => {
|
|
455
|
+
document.removeEventListener("pointerlockchange", handlePointerLockChange);
|
|
456
|
+
document.removeEventListener("pointerlockerror", handlePointerLockError);
|
|
457
|
+
};
|
|
458
|
+
}, [getPointerLockElement, iframeRef]);
|
|
459
|
+
(0, import_react.useEffect)(() => {
|
|
460
|
+
return () => {
|
|
461
|
+
releaseEscapeKeyLock();
|
|
462
|
+
const iframe = iframeRef.current;
|
|
463
|
+
const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
|
|
464
|
+
if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
|
|
465
|
+
releasePointerLock();
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
}, [getPointerLockElement, iframeRef, releaseEscapeKeyLock, releasePointerLock]);
|
|
330
469
|
(0, import_react.useEffect)(() => {
|
|
331
470
|
const logCrossOriginStatus = () => {
|
|
332
471
|
const isIsolated = self.crossOriginIsolated;
|
|
@@ -497,7 +636,7 @@ var EmbedTester = (props = {}) => {
|
|
|
497
636
|
fullscreenSupported: false,
|
|
498
637
|
keyboardLockSupported: false
|
|
499
638
|
});
|
|
500
|
-
const
|
|
639
|
+
const displayHost = props.baseUrl ? new URL(props.baseUrl).host : "playruo.live";
|
|
501
640
|
const enableConsoleLog = props.enableConsoleLog ?? false;
|
|
502
641
|
const hasReadyBeenCalled = (0, import_react2.useRef)(false);
|
|
503
642
|
const previousStateRef = (0, import_react2.useRef)("");
|
|
@@ -722,7 +861,7 @@ var EmbedTester = (props = {}) => {
|
|
|
722
861
|
border: "1px solid #404040"
|
|
723
862
|
}, children: [
|
|
724
863
|
"\u{1F9EA} ",
|
|
725
|
-
|
|
864
|
+
displayHost
|
|
726
865
|
] })
|
|
727
866
|
] }),
|
|
728
867
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
|
|
@@ -735,7 +874,7 @@ var EmbedTester = (props = {}) => {
|
|
|
735
874
|
}, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
736
875
|
PlayruoEmbed,
|
|
737
876
|
{
|
|
738
|
-
baseUrl: baseUrl
|
|
877
|
+
baseUrl: props.baseUrl,
|
|
739
878
|
className: "w-full h-full absolute inset-0",
|
|
740
879
|
iframeRef,
|
|
741
880
|
onDemoCompleted: handleDemoCompleted,
|
package/dist/index.d.cts
CHANGED
|
@@ -34,7 +34,8 @@ interface PlayruoEmbedRenderState {
|
|
|
34
34
|
actions: PlayruoEmbedActions;
|
|
35
35
|
}
|
|
36
36
|
interface PlayruoEmbedProps {
|
|
37
|
-
|
|
37
|
+
/** Game origin (scheme + host). Omit to use production (`https://playruo.live`). */
|
|
38
|
+
baseUrl?: string;
|
|
38
39
|
operation?: string;
|
|
39
40
|
storeUrl?: string;
|
|
40
41
|
showIframe?: boolean;
|
|
@@ -59,6 +60,7 @@ interface PlayruoEmbedProps {
|
|
|
59
60
|
declare const PlayruoEmbed: FC<PlayruoEmbedProps>;
|
|
60
61
|
|
|
61
62
|
interface EmbedTesterProps {
|
|
63
|
+
/** Override game origin (e.g. http://localhost:3000). Omit for production. */
|
|
62
64
|
baseUrl?: string;
|
|
63
65
|
enableConsoleLog?: boolean;
|
|
64
66
|
operation?: string;
|
|
@@ -106,6 +108,7 @@ interface PlayruoInternalMessage {
|
|
|
106
108
|
action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
|
|
107
109
|
fromEmbed: true;
|
|
108
110
|
isReplay?: boolean;
|
|
111
|
+
isFullscreen?: boolean;
|
|
109
112
|
parentWillHandleFullscreen?: boolean;
|
|
110
113
|
}
|
|
111
114
|
/**
|
|
@@ -121,16 +124,15 @@ declare const isPlayruoEmbedMessage: (data: unknown) => data is PlayruoEmbedMess
|
|
|
121
124
|
declare const isPlayruoInternalMessage: (data: unknown) => data is PlayruoInternalMessage;
|
|
122
125
|
|
|
123
126
|
interface BuildUrlParams {
|
|
124
|
-
baseUrl: string;
|
|
125
127
|
operation?: string;
|
|
126
128
|
storeUrl?: string;
|
|
127
129
|
}
|
|
128
130
|
/**
|
|
129
|
-
* Build direct game URL (no intermediate /embed page)
|
|
131
|
+
* Build direct game URL (no intermediate /embed page). Always targets production.
|
|
130
132
|
* @example
|
|
131
|
-
* buildEmbedUrl({
|
|
133
|
+
* buildEmbedUrl({ operation: "my-game" })
|
|
132
134
|
* // => "https://playruo.live/my-game?iframe=true"
|
|
133
135
|
*/
|
|
134
|
-
declare const buildEmbedUrl: ({
|
|
136
|
+
declare const buildEmbedUrl: ({ operation, storeUrl }: BuildUrlParams) => string;
|
|
135
137
|
|
|
136
138
|
export { type DemoCompletedPayload, EmbedTester, type EmbedTesterProps, type PlayruoDemoCompletedMessage, PlayruoEmbed, type PlayruoEmbedActions, type PlayruoEmbedMessage, type PlayruoEmbedProps, type PlayruoEmbedRenderState, type PlayruoEmbedState, type PlayruoErrorMessage, type PlayruoInternalMessage, type PlayruoPurchaseRequestMessage, type PlayruoReadyMessage, type PlayruoSessionOverMessage, type PlayruoStateUpdateMessage, type PlayruoStreamStartMessage, buildEmbedUrl, isPlayruoEmbedMessage, isPlayruoInternalMessage };
|
package/dist/index.d.ts
CHANGED
|
@@ -34,7 +34,8 @@ interface PlayruoEmbedRenderState {
|
|
|
34
34
|
actions: PlayruoEmbedActions;
|
|
35
35
|
}
|
|
36
36
|
interface PlayruoEmbedProps {
|
|
37
|
-
|
|
37
|
+
/** Game origin (scheme + host). Omit to use production (`https://playruo.live`). */
|
|
38
|
+
baseUrl?: string;
|
|
38
39
|
operation?: string;
|
|
39
40
|
storeUrl?: string;
|
|
40
41
|
showIframe?: boolean;
|
|
@@ -59,6 +60,7 @@ interface PlayruoEmbedProps {
|
|
|
59
60
|
declare const PlayruoEmbed: FC<PlayruoEmbedProps>;
|
|
60
61
|
|
|
61
62
|
interface EmbedTesterProps {
|
|
63
|
+
/** Override game origin (e.g. http://localhost:3000). Omit for production. */
|
|
62
64
|
baseUrl?: string;
|
|
63
65
|
enableConsoleLog?: boolean;
|
|
64
66
|
operation?: string;
|
|
@@ -106,6 +108,7 @@ interface PlayruoInternalMessage {
|
|
|
106
108
|
action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
|
|
107
109
|
fromEmbed: true;
|
|
108
110
|
isReplay?: boolean;
|
|
111
|
+
isFullscreen?: boolean;
|
|
109
112
|
parentWillHandleFullscreen?: boolean;
|
|
110
113
|
}
|
|
111
114
|
/**
|
|
@@ -121,16 +124,15 @@ declare const isPlayruoEmbedMessage: (data: unknown) => data is PlayruoEmbedMess
|
|
|
121
124
|
declare const isPlayruoInternalMessage: (data: unknown) => data is PlayruoInternalMessage;
|
|
122
125
|
|
|
123
126
|
interface BuildUrlParams {
|
|
124
|
-
baseUrl: string;
|
|
125
127
|
operation?: string;
|
|
126
128
|
storeUrl?: string;
|
|
127
129
|
}
|
|
128
130
|
/**
|
|
129
|
-
* Build direct game URL (no intermediate /embed page)
|
|
131
|
+
* Build direct game URL (no intermediate /embed page). Always targets production.
|
|
130
132
|
* @example
|
|
131
|
-
* buildEmbedUrl({
|
|
133
|
+
* buildEmbedUrl({ operation: "my-game" })
|
|
132
134
|
* // => "https://playruo.live/my-game?iframe=true"
|
|
133
135
|
*/
|
|
134
|
-
declare const buildEmbedUrl: ({
|
|
136
|
+
declare const buildEmbedUrl: ({ operation, storeUrl }: BuildUrlParams) => string;
|
|
135
137
|
|
|
136
138
|
export { type DemoCompletedPayload, EmbedTester, type EmbedTesterProps, type PlayruoDemoCompletedMessage, PlayruoEmbed, type PlayruoEmbedActions, type PlayruoEmbedMessage, type PlayruoEmbedProps, type PlayruoEmbedRenderState, type PlayruoEmbedState, type PlayruoErrorMessage, type PlayruoInternalMessage, type PlayruoPurchaseRequestMessage, type PlayruoReadyMessage, type PlayruoSessionOverMessage, type PlayruoStateUpdateMessage, type PlayruoStreamStartMessage, buildEmbedUrl, isPlayruoEmbedMessage, isPlayruoInternalMessage };
|
package/dist/index.js
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
import { useEffect, useMemo, useRef, useState, useCallback } from "react";
|
|
3
3
|
|
|
4
4
|
// src/url.ts
|
|
5
|
-
var
|
|
5
|
+
var PLAYRUO_ORIGIN = "https://playruo.live";
|
|
6
|
+
var buildEmbedUrl = ({ operation = "iframe", storeUrl }) => {
|
|
7
|
+
return buildEmbedUrlWithOrigin(PLAYRUO_ORIGIN, { operation, storeUrl });
|
|
8
|
+
};
|
|
9
|
+
var buildEmbedUrlWithOrigin = (origin, { operation = "iframe", storeUrl }) => {
|
|
6
10
|
const operationPath = operation.startsWith("/") ? operation.slice(1) : operation;
|
|
7
|
-
const url = new URL(`/${operationPath}`,
|
|
11
|
+
const url = new URL(`/${operationPath}`, origin);
|
|
8
12
|
url.searchParams.set("iframe", "true");
|
|
9
13
|
if (storeUrl) {
|
|
10
14
|
url.searchParams.set("storeUrl", storeUrl);
|
|
@@ -30,6 +34,8 @@ var PlayruoEmbed = ({
|
|
|
30
34
|
}) => {
|
|
31
35
|
const internalIframeRef = useRef(null);
|
|
32
36
|
const iframeRef = externalIframeRef || internalIframeRef;
|
|
37
|
+
const wasEmbedFullscreenRef = useRef(false);
|
|
38
|
+
const wasEmbedPointerLockedRef = useRef(false);
|
|
33
39
|
const [isIframeLoaded, setIsIframeLoaded] = useState(false);
|
|
34
40
|
const [hasPlayedOnce, setHasPlayedOnce] = useState(false);
|
|
35
41
|
const [showGameIframe, setShowGameIframe] = useState(false);
|
|
@@ -37,7 +43,10 @@ var PlayruoEmbed = ({
|
|
|
37
43
|
const [isValidChromium, setIsValidChromium] = useState(true);
|
|
38
44
|
const [errorMessage, setErrorMessage] = useState(null);
|
|
39
45
|
const [iframeState, setIframeState] = useState(null);
|
|
40
|
-
const src = useMemo(
|
|
46
|
+
const src = useMemo(
|
|
47
|
+
() => baseUrl != null && baseUrl !== "" ? buildEmbedUrlWithOrigin(baseUrl, { operation, storeUrl }) : buildEmbedUrl({ operation, storeUrl }),
|
|
48
|
+
[baseUrl, operation, storeUrl]
|
|
49
|
+
);
|
|
41
50
|
useEffect(() => {
|
|
42
51
|
if (typeof window === "undefined") return;
|
|
43
52
|
const detectMobile = () => {
|
|
@@ -82,6 +91,94 @@ var PlayruoEmbed = ({
|
|
|
82
91
|
}),
|
|
83
92
|
[currentState, hasIframeConfirmed, isCompatible]
|
|
84
93
|
);
|
|
94
|
+
const getKeyboardApi = useCallback(() => {
|
|
95
|
+
if (typeof navigator === "undefined") {
|
|
96
|
+
return void 0;
|
|
97
|
+
}
|
|
98
|
+
return navigator.keyboard;
|
|
99
|
+
}, []);
|
|
100
|
+
const releaseEscapeKeyLock = useCallback(() => {
|
|
101
|
+
const keyboard = getKeyboardApi();
|
|
102
|
+
try {
|
|
103
|
+
keyboard?.unlock?.();
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.warn("[PlayruoEmbed] Failed to release Escape key lock:", error);
|
|
106
|
+
}
|
|
107
|
+
}, [getKeyboardApi]);
|
|
108
|
+
const lockEscapeKey = useCallback(async () => {
|
|
109
|
+
const keyboard = getKeyboardApi();
|
|
110
|
+
if (!keyboard?.lock) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
await keyboard.lock(["Escape"]);
|
|
115
|
+
console.log("[PlayruoEmbed] Escape key locked during fullscreen");
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.warn("[PlayruoEmbed] Failed to lock Escape key:", error);
|
|
118
|
+
}
|
|
119
|
+
}, [getKeyboardApi]);
|
|
120
|
+
const getPointerLockElement = useCallback(() => {
|
|
121
|
+
const doc = document;
|
|
122
|
+
return doc.pointerLockElement || doc.mozPointerLockElement || doc.webkitPointerLockElement || null;
|
|
123
|
+
}, []);
|
|
124
|
+
const releasePointerLock = useCallback(() => {
|
|
125
|
+
const doc = document;
|
|
126
|
+
const exitPointerLock = doc.exitPointerLock?.bind(doc) || doc.mozExitPointerLock?.bind(doc) || doc.webkitExitPointerLock?.bind(doc);
|
|
127
|
+
try {
|
|
128
|
+
exitPointerLock?.();
|
|
129
|
+
wasEmbedPointerLockedRef.current = false;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.warn("[PlayruoEmbed] Failed to release pointer lock:", error);
|
|
132
|
+
}
|
|
133
|
+
}, []);
|
|
134
|
+
const requestImmersiveMode = useCallback(
|
|
135
|
+
async (iframe) => {
|
|
136
|
+
const pointerTarget = iframe;
|
|
137
|
+
if (pointerTarget.requestPointerLock) {
|
|
138
|
+
try {
|
|
139
|
+
const pointerLockResult = pointerTarget.requestPointerLock({ unadjustedMovement: true });
|
|
140
|
+
if (pointerLockResult && typeof pointerLockResult.then === "function") {
|
|
141
|
+
void pointerLockResult.then(() => {
|
|
142
|
+
wasEmbedPointerLockedRef.current = true;
|
|
143
|
+
console.log("[PlayruoEmbed] Pointer locked on iframe");
|
|
144
|
+
}).catch(async (error) => {
|
|
145
|
+
if (error instanceof DOMException && error.name === "NotSupportedError") {
|
|
146
|
+
try {
|
|
147
|
+
const fallbackResult = pointerTarget.requestPointerLock?.();
|
|
148
|
+
if (fallbackResult && typeof fallbackResult.then === "function") {
|
|
149
|
+
await fallbackResult;
|
|
150
|
+
}
|
|
151
|
+
wasEmbedPointerLockedRef.current = true;
|
|
152
|
+
console.log("[PlayruoEmbed] Pointer locked on iframe without raw input");
|
|
153
|
+
return;
|
|
154
|
+
} catch (fallbackError) {
|
|
155
|
+
console.warn("[PlayruoEmbed] Failed to lock pointer without raw input:", fallbackError);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
console.warn("[PlayruoEmbed] Failed to lock pointer:", error);
|
|
160
|
+
});
|
|
161
|
+
} else {
|
|
162
|
+
wasEmbedPointerLockedRef.current = true;
|
|
163
|
+
console.log("[PlayruoEmbed] Pointer lock requested on iframe");
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.warn("[PlayruoEmbed] Failed to request pointer lock:", error);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const fullscreenPromise = iframe.requestFullscreen();
|
|
170
|
+
try {
|
|
171
|
+
await fullscreenPromise;
|
|
172
|
+
wasEmbedFullscreenRef.current = true;
|
|
173
|
+
console.log("[PlayruoEmbed] Fullscreen granted");
|
|
174
|
+
} catch (error) {
|
|
175
|
+
releaseEscapeKeyLock();
|
|
176
|
+
releasePointerLock();
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
[releaseEscapeKeyLock, releasePointerLock]
|
|
181
|
+
);
|
|
85
182
|
const handleTryNow = useCallback(async () => {
|
|
86
183
|
if (!isCompatible) {
|
|
87
184
|
console.warn("[PlayruoEmbed] Browser not compatible");
|
|
@@ -95,8 +192,7 @@ var PlayruoEmbed = ({
|
|
|
95
192
|
setShowGameIframe(true);
|
|
96
193
|
try {
|
|
97
194
|
try {
|
|
98
|
-
await iframe
|
|
99
|
-
console.log("[PlayruoEmbed] Fullscreen granted");
|
|
195
|
+
await requestImmersiveMode(iframe);
|
|
100
196
|
} catch (fsError) {
|
|
101
197
|
console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
|
|
102
198
|
}
|
|
@@ -104,6 +200,8 @@ var PlayruoEmbed = ({
|
|
|
104
200
|
type: "playruo",
|
|
105
201
|
action: "tryNowClick",
|
|
106
202
|
fromEmbed: true,
|
|
203
|
+
isFullscreen: true,
|
|
204
|
+
parentWillHandleFullscreen: true,
|
|
107
205
|
isReplay: hasPlayedOnce
|
|
108
206
|
}, "*");
|
|
109
207
|
console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
|
|
@@ -117,7 +215,7 @@ var PlayruoEmbed = ({
|
|
|
117
215
|
setErrorMessage("Failed to start game");
|
|
118
216
|
onError?.("Failed to start game");
|
|
119
217
|
}
|
|
120
|
-
}, [isCompatible, hasPlayedOnce, onError, iframeRef]);
|
|
218
|
+
}, [isCompatible, hasPlayedOnce, onError, iframeRef, requestImmersiveMode]);
|
|
121
219
|
const handleBuyNow = useCallback(() => {
|
|
122
220
|
if (!storeUrl) {
|
|
123
221
|
console.warn("[PlayruoEmbed] No store URL provided");
|
|
@@ -246,23 +344,38 @@ var PlayruoEmbed = ({
|
|
|
246
344
|
useEffect(() => {
|
|
247
345
|
const handleFullscreenChange = () => {
|
|
248
346
|
const doc = document;
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
347
|
+
const iframe = iframeRef.current;
|
|
348
|
+
const fullscreenElement = doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement || null;
|
|
349
|
+
const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
|
|
350
|
+
if (isEmbedFullscreen) {
|
|
351
|
+
wasEmbedFullscreenRef.current = true;
|
|
352
|
+
void lockEscapeKey();
|
|
252
353
|
if (iframe) {
|
|
253
354
|
setTimeout(() => {
|
|
254
355
|
iframe.focus();
|
|
255
356
|
iframe.contentWindow?.focus();
|
|
256
357
|
}, 200);
|
|
257
358
|
}
|
|
258
|
-
|
|
259
|
-
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (fullscreenElement) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
releaseEscapeKeyLock();
|
|
365
|
+
if (wasEmbedFullscreenRef.current) {
|
|
366
|
+
wasEmbedFullscreenRef.current = false;
|
|
367
|
+
const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
|
|
368
|
+
if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
|
|
369
|
+
releasePointerLock();
|
|
370
|
+
}
|
|
260
371
|
if (iframe) {
|
|
261
372
|
try {
|
|
262
373
|
iframe.contentWindow?.postMessage({
|
|
263
374
|
type: "playruo",
|
|
264
375
|
action: "exitFullscreen",
|
|
265
|
-
fromEmbed: true
|
|
376
|
+
fromEmbed: true,
|
|
377
|
+
isFullscreen: false,
|
|
378
|
+
parentWillHandleFullscreen: true
|
|
266
379
|
}, "*");
|
|
267
380
|
} catch {
|
|
268
381
|
console.error("[PlayruoEmbed] Failed to exit fullscreen");
|
|
@@ -286,7 +399,33 @@ var PlayruoEmbed = ({
|
|
|
286
399
|
document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
|
|
287
400
|
document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
|
|
288
401
|
};
|
|
289
|
-
}, [storeUrl, onDemoCompleted, iframeRef]);
|
|
402
|
+
}, [storeUrl, onDemoCompleted, iframeRef, getPointerLockElement, lockEscapeKey, releaseEscapeKeyLock, releasePointerLock]);
|
|
403
|
+
useEffect(() => {
|
|
404
|
+
const handlePointerLockChange = () => {
|
|
405
|
+
const iframe = iframeRef.current;
|
|
406
|
+
wasEmbedPointerLockedRef.current = Boolean(iframe && getPointerLockElement() === iframe);
|
|
407
|
+
};
|
|
408
|
+
const handlePointerLockError = () => {
|
|
409
|
+
wasEmbedPointerLockedRef.current = false;
|
|
410
|
+
console.warn("[PlayruoEmbed] Pointer lock request was rejected");
|
|
411
|
+
};
|
|
412
|
+
document.addEventListener("pointerlockchange", handlePointerLockChange);
|
|
413
|
+
document.addEventListener("pointerlockerror", handlePointerLockError);
|
|
414
|
+
return () => {
|
|
415
|
+
document.removeEventListener("pointerlockchange", handlePointerLockChange);
|
|
416
|
+
document.removeEventListener("pointerlockerror", handlePointerLockError);
|
|
417
|
+
};
|
|
418
|
+
}, [getPointerLockElement, iframeRef]);
|
|
419
|
+
useEffect(() => {
|
|
420
|
+
return () => {
|
|
421
|
+
releaseEscapeKeyLock();
|
|
422
|
+
const iframe = iframeRef.current;
|
|
423
|
+
const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
|
|
424
|
+
if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
|
|
425
|
+
releasePointerLock();
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
}, [getPointerLockElement, iframeRef, releaseEscapeKeyLock, releasePointerLock]);
|
|
290
429
|
useEffect(() => {
|
|
291
430
|
const logCrossOriginStatus = () => {
|
|
292
431
|
const isIsolated = self.crossOriginIsolated;
|
|
@@ -457,7 +596,7 @@ var EmbedTester = (props = {}) => {
|
|
|
457
596
|
fullscreenSupported: false,
|
|
458
597
|
keyboardLockSupported: false
|
|
459
598
|
});
|
|
460
|
-
const
|
|
599
|
+
const displayHost = props.baseUrl ? new URL(props.baseUrl).host : "playruo.live";
|
|
461
600
|
const enableConsoleLog = props.enableConsoleLog ?? false;
|
|
462
601
|
const hasReadyBeenCalled = useRef2(false);
|
|
463
602
|
const previousStateRef = useRef2("");
|
|
@@ -682,7 +821,7 @@ var EmbedTester = (props = {}) => {
|
|
|
682
821
|
border: "1px solid #404040"
|
|
683
822
|
}, children: [
|
|
684
823
|
"\u{1F9EA} ",
|
|
685
|
-
|
|
824
|
+
displayHost
|
|
686
825
|
] })
|
|
687
826
|
] }),
|
|
688
827
|
/* @__PURE__ */ jsx2("div", { style: {
|
|
@@ -695,7 +834,7 @@ var EmbedTester = (props = {}) => {
|
|
|
695
834
|
}, children: /* @__PURE__ */ jsx2(
|
|
696
835
|
PlayruoEmbed,
|
|
697
836
|
{
|
|
698
|
-
baseUrl: baseUrl
|
|
837
|
+
baseUrl: props.baseUrl,
|
|
699
838
|
className: "w-full h-full absolute inset-0",
|
|
700
839
|
iframeRef,
|
|
701
840
|
onDemoCompleted: handleDemoCompleted,
|