@playruo/embed 1.2.9 → 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
@@ -75,6 +75,7 @@ var PlayruoEmbed = ({
75
75
  const internalIframeRef = (0, import_react.useRef)(null);
76
76
  const iframeRef = externalIframeRef || internalIframeRef;
77
77
  const wasEmbedFullscreenRef = (0, import_react.useRef)(false);
78
+ const wasEmbedPointerLockedRef = (0, import_react.useRef)(false);
78
79
  const [isIframeLoaded, setIsIframeLoaded] = (0, import_react.useState)(false);
79
80
  const [hasPlayedOnce, setHasPlayedOnce] = (0, import_react.useState)(false);
80
81
  const [showGameIframe, setShowGameIframe] = (0, import_react.useState)(false);
@@ -144,30 +145,79 @@ var PlayruoEmbed = ({
144
145
  console.warn("[PlayruoEmbed] Failed to release Escape key lock:", error);
145
146
  }
146
147
  }, [getKeyboardApi]);
147
- const requestFullscreenWithEscapeLock = (0, import_react.useCallback)(
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)(
148
175
  async (iframe) => {
149
- const fullscreenPromise = iframe.requestFullscreen();
150
- const keyboard = getKeyboardApi();
151
- if (keyboard?.lock && typeof window !== "undefined" && window.self === window.top) {
152
- void keyboard.lock(["Escape"]).then(() => {
153
- console.log("[PlayruoEmbed] Escape key locked during fullscreen");
154
- }).catch((error) => {
155
- const isTopLevelContextError = error instanceof DOMException && error.name === "InvalidStateError" && error.message.includes("top-level browsing context");
156
- if (!isTopLevelContextError) {
157
- console.warn("[PlayruoEmbed] Failed to lock Escape key:", error);
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");
158
204
  }
159
- });
205
+ } catch (error) {
206
+ console.warn("[PlayruoEmbed] Failed to request pointer lock:", error);
207
+ }
160
208
  }
209
+ const fullscreenPromise = iframe.requestFullscreen();
161
210
  try {
162
211
  await fullscreenPromise;
163
212
  wasEmbedFullscreenRef.current = true;
164
213
  console.log("[PlayruoEmbed] Fullscreen granted");
165
214
  } catch (error) {
166
215
  releaseEscapeKeyLock();
216
+ releasePointerLock();
167
217
  throw error;
168
218
  }
169
219
  },
170
- [getKeyboardApi, releaseEscapeKeyLock]
220
+ [releaseEscapeKeyLock, releasePointerLock]
171
221
  );
172
222
  const handleTryNow = (0, import_react.useCallback)(async () => {
173
223
  if (!isCompatible) {
@@ -182,7 +232,7 @@ var PlayruoEmbed = ({
182
232
  setShowGameIframe(true);
183
233
  try {
184
234
  try {
185
- await requestFullscreenWithEscapeLock(iframe);
235
+ await requestImmersiveMode(iframe);
186
236
  } catch (fsError) {
187
237
  console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
188
238
  }
@@ -190,6 +240,8 @@ var PlayruoEmbed = ({
190
240
  type: "playruo",
191
241
  action: "tryNowClick",
192
242
  fromEmbed: true,
243
+ isFullscreen: true,
244
+ parentWillHandleFullscreen: true,
193
245
  isReplay: hasPlayedOnce
194
246
  }, "*");
195
247
  console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
@@ -203,7 +255,7 @@ var PlayruoEmbed = ({
203
255
  setErrorMessage("Failed to start game");
204
256
  onError?.("Failed to start game");
205
257
  }
206
- }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestFullscreenWithEscapeLock]);
258
+ }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestImmersiveMode]);
207
259
  const handleBuyNow = (0, import_react.useCallback)(() => {
208
260
  if (!storeUrl) {
209
261
  console.warn("[PlayruoEmbed] No store URL provided");
@@ -337,6 +389,7 @@ var PlayruoEmbed = ({
337
389
  const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
338
390
  if (isEmbedFullscreen) {
339
391
  wasEmbedFullscreenRef.current = true;
392
+ void lockEscapeKey();
340
393
  if (iframe) {
341
394
  setTimeout(() => {
342
395
  iframe.focus();
@@ -351,12 +404,18 @@ var PlayruoEmbed = ({
351
404
  releaseEscapeKeyLock();
352
405
  if (wasEmbedFullscreenRef.current) {
353
406
  wasEmbedFullscreenRef.current = false;
407
+ const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
408
+ if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
409
+ releasePointerLock();
410
+ }
354
411
  if (iframe) {
355
412
  try {
356
413
  iframe.contentWindow?.postMessage({
357
414
  type: "playruo",
358
415
  action: "exitFullscreen",
359
- fromEmbed: true
416
+ fromEmbed: true,
417
+ isFullscreen: false,
418
+ parentWillHandleFullscreen: true
360
419
  }, "*");
361
420
  } catch {
362
421
  console.error("[PlayruoEmbed] Failed to exit fullscreen");
@@ -380,12 +439,33 @@ var PlayruoEmbed = ({
380
439
  document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
381
440
  document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
382
441
  };
383
- }, [storeUrl, onDemoCompleted, iframeRef, releaseEscapeKeyLock]);
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]);
384
459
  (0, import_react.useEffect)(() => {
385
460
  return () => {
386
461
  releaseEscapeKeyLock();
462
+ const iframe = iframeRef.current;
463
+ const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
464
+ if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
465
+ releasePointerLock();
466
+ }
387
467
  };
388
- }, [releaseEscapeKeyLock]);
468
+ }, [getPointerLockElement, iframeRef, releaseEscapeKeyLock, releasePointerLock]);
389
469
  (0, import_react.useEffect)(() => {
390
470
  const logCrossOriginStatus = () => {
391
471
  const isIsolated = self.crossOriginIsolated;
package/dist/index.d.cts CHANGED
@@ -108,6 +108,7 @@ interface PlayruoInternalMessage {
108
108
  action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
109
109
  fromEmbed: true;
110
110
  isReplay?: boolean;
111
+ isFullscreen?: boolean;
111
112
  parentWillHandleFullscreen?: boolean;
112
113
  }
113
114
  /**
package/dist/index.d.ts CHANGED
@@ -108,6 +108,7 @@ interface PlayruoInternalMessage {
108
108
  action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
109
109
  fromEmbed: true;
110
110
  isReplay?: boolean;
111
+ isFullscreen?: boolean;
111
112
  parentWillHandleFullscreen?: boolean;
112
113
  }
113
114
  /**
package/dist/index.js CHANGED
@@ -35,6 +35,7 @@ var PlayruoEmbed = ({
35
35
  const internalIframeRef = useRef(null);
36
36
  const iframeRef = externalIframeRef || internalIframeRef;
37
37
  const wasEmbedFullscreenRef = useRef(false);
38
+ const wasEmbedPointerLockedRef = useRef(false);
38
39
  const [isIframeLoaded, setIsIframeLoaded] = useState(false);
39
40
  const [hasPlayedOnce, setHasPlayedOnce] = useState(false);
40
41
  const [showGameIframe, setShowGameIframe] = useState(false);
@@ -104,30 +105,79 @@ var PlayruoEmbed = ({
104
105
  console.warn("[PlayruoEmbed] Failed to release Escape key lock:", error);
105
106
  }
106
107
  }, [getKeyboardApi]);
107
- const requestFullscreenWithEscapeLock = useCallback(
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(
108
135
  async (iframe) => {
109
- const fullscreenPromise = iframe.requestFullscreen();
110
- const keyboard = getKeyboardApi();
111
- if (keyboard?.lock && typeof window !== "undefined" && window.self === window.top) {
112
- void keyboard.lock(["Escape"]).then(() => {
113
- console.log("[PlayruoEmbed] Escape key locked during fullscreen");
114
- }).catch((error) => {
115
- const isTopLevelContextError = error instanceof DOMException && error.name === "InvalidStateError" && error.message.includes("top-level browsing context");
116
- if (!isTopLevelContextError) {
117
- console.warn("[PlayruoEmbed] Failed to lock Escape key:", error);
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");
118
164
  }
119
- });
165
+ } catch (error) {
166
+ console.warn("[PlayruoEmbed] Failed to request pointer lock:", error);
167
+ }
120
168
  }
169
+ const fullscreenPromise = iframe.requestFullscreen();
121
170
  try {
122
171
  await fullscreenPromise;
123
172
  wasEmbedFullscreenRef.current = true;
124
173
  console.log("[PlayruoEmbed] Fullscreen granted");
125
174
  } catch (error) {
126
175
  releaseEscapeKeyLock();
176
+ releasePointerLock();
127
177
  throw error;
128
178
  }
129
179
  },
130
- [getKeyboardApi, releaseEscapeKeyLock]
180
+ [releaseEscapeKeyLock, releasePointerLock]
131
181
  );
132
182
  const handleTryNow = useCallback(async () => {
133
183
  if (!isCompatible) {
@@ -142,7 +192,7 @@ var PlayruoEmbed = ({
142
192
  setShowGameIframe(true);
143
193
  try {
144
194
  try {
145
- await requestFullscreenWithEscapeLock(iframe);
195
+ await requestImmersiveMode(iframe);
146
196
  } catch (fsError) {
147
197
  console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
148
198
  }
@@ -150,6 +200,8 @@ var PlayruoEmbed = ({
150
200
  type: "playruo",
151
201
  action: "tryNowClick",
152
202
  fromEmbed: true,
203
+ isFullscreen: true,
204
+ parentWillHandleFullscreen: true,
153
205
  isReplay: hasPlayedOnce
154
206
  }, "*");
155
207
  console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
@@ -163,7 +215,7 @@ var PlayruoEmbed = ({
163
215
  setErrorMessage("Failed to start game");
164
216
  onError?.("Failed to start game");
165
217
  }
166
- }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestFullscreenWithEscapeLock]);
218
+ }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestImmersiveMode]);
167
219
  const handleBuyNow = useCallback(() => {
168
220
  if (!storeUrl) {
169
221
  console.warn("[PlayruoEmbed] No store URL provided");
@@ -297,6 +349,7 @@ var PlayruoEmbed = ({
297
349
  const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
298
350
  if (isEmbedFullscreen) {
299
351
  wasEmbedFullscreenRef.current = true;
352
+ void lockEscapeKey();
300
353
  if (iframe) {
301
354
  setTimeout(() => {
302
355
  iframe.focus();
@@ -311,12 +364,18 @@ var PlayruoEmbed = ({
311
364
  releaseEscapeKeyLock();
312
365
  if (wasEmbedFullscreenRef.current) {
313
366
  wasEmbedFullscreenRef.current = false;
367
+ const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
368
+ if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
369
+ releasePointerLock();
370
+ }
314
371
  if (iframe) {
315
372
  try {
316
373
  iframe.contentWindow?.postMessage({
317
374
  type: "playruo",
318
375
  action: "exitFullscreen",
319
- fromEmbed: true
376
+ fromEmbed: true,
377
+ isFullscreen: false,
378
+ parentWillHandleFullscreen: true
320
379
  }, "*");
321
380
  } catch {
322
381
  console.error("[PlayruoEmbed] Failed to exit fullscreen");
@@ -340,12 +399,33 @@ var PlayruoEmbed = ({
340
399
  document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
341
400
  document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
342
401
  };
343
- }, [storeUrl, onDemoCompleted, iframeRef, releaseEscapeKeyLock]);
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]);
344
419
  useEffect(() => {
345
420
  return () => {
346
421
  releaseEscapeKeyLock();
422
+ const iframe = iframeRef.current;
423
+ const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
424
+ if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
425
+ releasePointerLock();
426
+ }
347
427
  };
348
- }, [releaseEscapeKeyLock]);
428
+ }, [getPointerLockElement, iframeRef, releaseEscapeKeyLock, releasePointerLock]);
349
429
  useEffect(() => {
350
430
  const logCrossOriginStatus = () => {
351
431
  const isIsolated = self.crossOriginIsolated;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playruo/embed",
3
- "version": "1.2.9",
3
+ "version": "1.2.10",
4
4
  "description": "React SDK to embed Playruo with postMessage API.",
5
5
  "license": "MIT",
6
6
  "type": "module",