@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 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 buildEmbedUrl = ({ baseUrl, operation = "iframe", storeUrl }) => {
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}`, baseUrl);
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)(() => buildEmbedUrl({ baseUrl, operation, storeUrl }), [baseUrl, operation, storeUrl]);
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.requestFullscreen();
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 isFullscreen = !!(doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement);
290
- if (isFullscreen) {
291
- const iframe = iframeRef.current;
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
- } else {
299
- const iframe = iframeRef.current;
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 baseUrl = props.baseUrl;
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
- baseUrl ? new URL(baseUrl).host : "localhost:3000"
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
- baseUrl: string;
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({ baseUrl: "https://playruo.live", operation: "my-game" })
133
+ * buildEmbedUrl({ operation: "my-game" })
132
134
  * // => "https://playruo.live/my-game?iframe=true"
133
135
  */
134
- declare const buildEmbedUrl: ({ baseUrl, operation, storeUrl }: BuildUrlParams) => string;
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
- baseUrl: string;
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({ baseUrl: "https://playruo.live", operation: "my-game" })
133
+ * buildEmbedUrl({ operation: "my-game" })
132
134
  * // => "https://playruo.live/my-game?iframe=true"
133
135
  */
134
- declare const buildEmbedUrl: ({ baseUrl, operation, storeUrl }: BuildUrlParams) => string;
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 buildEmbedUrl = ({ baseUrl, operation = "iframe", storeUrl }) => {
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}`, baseUrl);
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(() => buildEmbedUrl({ baseUrl, operation, storeUrl }), [baseUrl, operation, storeUrl]);
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.requestFullscreen();
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 isFullscreen = !!(doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement);
250
- if (isFullscreen) {
251
- const iframe = iframeRef.current;
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
- } else {
259
- const iframe = iframeRef.current;
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 baseUrl = props.baseUrl;
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
- baseUrl ? new URL(baseUrl).host : "localhost:3000"
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playruo/embed",
3
- "version": "1.2.7",
3
+ "version": "1.2.10",
4
4
  "description": "React SDK to embed Playruo with postMessage API.",
5
5
  "license": "MIT",
6
6
  "type": "module",