@playruo/embed 1.2.6 → 1.2.9

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,7 @@ 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);
73
78
  const [isIframeLoaded, setIsIframeLoaded] = (0, import_react.useState)(false);
74
79
  const [hasPlayedOnce, setHasPlayedOnce] = (0, import_react.useState)(false);
75
80
  const [showGameIframe, setShowGameIframe] = (0, import_react.useState)(false);
@@ -77,7 +82,10 @@ var PlayruoEmbed = ({
77
82
  const [isValidChromium, setIsValidChromium] = (0, import_react.useState)(true);
78
83
  const [errorMessage, setErrorMessage] = (0, import_react.useState)(null);
79
84
  const [iframeState, setIframeState] = (0, import_react.useState)(null);
80
- const src = (0, import_react.useMemo)(() => buildEmbedUrl({ baseUrl, operation, storeUrl }), [baseUrl, operation, storeUrl]);
85
+ const src = (0, import_react.useMemo)(
86
+ () => baseUrl != null && baseUrl !== "" ? buildEmbedUrlWithOrigin(baseUrl, { operation, storeUrl }) : buildEmbedUrl({ operation, storeUrl }),
87
+ [baseUrl, operation, storeUrl]
88
+ );
81
89
  (0, import_react.useEffect)(() => {
82
90
  if (typeof window === "undefined") return;
83
91
  const detectMobile = () => {
@@ -122,7 +130,46 @@ var PlayruoEmbed = ({
122
130
  }),
123
131
  [currentState, hasIframeConfirmed, isCompatible]
124
132
  );
125
- const handleTryNow = (0, import_react.useCallback)(() => {
133
+ const getKeyboardApi = (0, import_react.useCallback)(() => {
134
+ if (typeof navigator === "undefined") {
135
+ return void 0;
136
+ }
137
+ return navigator.keyboard;
138
+ }, []);
139
+ const releaseEscapeKeyLock = (0, import_react.useCallback)(() => {
140
+ const keyboard = getKeyboardApi();
141
+ try {
142
+ keyboard?.unlock?.();
143
+ } catch (error) {
144
+ console.warn("[PlayruoEmbed] Failed to release Escape key lock:", error);
145
+ }
146
+ }, [getKeyboardApi]);
147
+ const requestFullscreenWithEscapeLock = (0, import_react.useCallback)(
148
+ 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);
158
+ }
159
+ });
160
+ }
161
+ try {
162
+ await fullscreenPromise;
163
+ wasEmbedFullscreenRef.current = true;
164
+ console.log("[PlayruoEmbed] Fullscreen granted");
165
+ } catch (error) {
166
+ releaseEscapeKeyLock();
167
+ throw error;
168
+ }
169
+ },
170
+ [getKeyboardApi, releaseEscapeKeyLock]
171
+ );
172
+ const handleTryNow = (0, import_react.useCallback)(async () => {
126
173
  if (!isCompatible) {
127
174
  console.warn("[PlayruoEmbed] Browser not compatible");
128
175
  return;
@@ -134,12 +181,10 @@ var PlayruoEmbed = ({
134
181
  }
135
182
  setShowGameIframe(true);
136
183
  try {
137
- const el = iframeRef.current;
138
- if (el) {
139
- try {
140
- el.focus();
141
- } catch {
142
- }
184
+ try {
185
+ await requestFullscreenWithEscapeLock(iframe);
186
+ } catch (fsError) {
187
+ console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
143
188
  }
144
189
  iframe.contentWindow?.postMessage({
145
190
  type: "playruo",
@@ -158,7 +203,7 @@ var PlayruoEmbed = ({
158
203
  setErrorMessage("Failed to start game");
159
204
  onError?.("Failed to start game");
160
205
  }
161
- }, [isCompatible, hasPlayedOnce, onError, iframeRef]);
206
+ }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestFullscreenWithEscapeLock]);
162
207
  const handleBuyNow = (0, import_react.useCallback)(() => {
163
208
  if (!storeUrl) {
164
209
  console.warn("[PlayruoEmbed] No store URL provided");
@@ -287,17 +332,25 @@ var PlayruoEmbed = ({
287
332
  (0, import_react.useEffect)(() => {
288
333
  const handleFullscreenChange = () => {
289
334
  const doc = document;
290
- const isFullscreen = !!(doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement);
291
- if (isFullscreen) {
292
- const iframe = iframeRef.current;
335
+ const iframe = iframeRef.current;
336
+ const fullscreenElement = doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement || null;
337
+ const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
338
+ if (isEmbedFullscreen) {
339
+ wasEmbedFullscreenRef.current = true;
293
340
  if (iframe) {
294
341
  setTimeout(() => {
295
342
  iframe.focus();
296
343
  iframe.contentWindow?.focus();
297
344
  }, 200);
298
345
  }
299
- } else {
300
- const iframe = iframeRef.current;
346
+ return;
347
+ }
348
+ if (fullscreenElement) {
349
+ return;
350
+ }
351
+ releaseEscapeKeyLock();
352
+ if (wasEmbedFullscreenRef.current) {
353
+ wasEmbedFullscreenRef.current = false;
301
354
  if (iframe) {
302
355
  try {
303
356
  iframe.contentWindow?.postMessage({
@@ -327,7 +380,12 @@ var PlayruoEmbed = ({
327
380
  document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
328
381
  document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
329
382
  };
330
- }, [storeUrl, onDemoCompleted, iframeRef]);
383
+ }, [storeUrl, onDemoCompleted, iframeRef, releaseEscapeKeyLock]);
384
+ (0, import_react.useEffect)(() => {
385
+ return () => {
386
+ releaseEscapeKeyLock();
387
+ };
388
+ }, [releaseEscapeKeyLock]);
331
389
  (0, import_react.useEffect)(() => {
332
390
  const logCrossOriginStatus = () => {
333
391
  const isIsolated = self.crossOriginIsolated;
@@ -498,7 +556,7 @@ var EmbedTester = (props = {}) => {
498
556
  fullscreenSupported: false,
499
557
  keyboardLockSupported: false
500
558
  });
501
- const baseUrl = props.baseUrl;
559
+ const displayHost = props.baseUrl ? new URL(props.baseUrl).host : "playruo.live";
502
560
  const enableConsoleLog = props.enableConsoleLog ?? false;
503
561
  const hasReadyBeenCalled = (0, import_react2.useRef)(false);
504
562
  const previousStateRef = (0, import_react2.useRef)("");
@@ -723,7 +781,7 @@ var EmbedTester = (props = {}) => {
723
781
  border: "1px solid #404040"
724
782
  }, children: [
725
783
  "\u{1F9EA} ",
726
- baseUrl ? new URL(baseUrl).host : "localhost:3000"
784
+ displayHost
727
785
  ] })
728
786
  ] }),
729
787
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
@@ -736,7 +794,7 @@ var EmbedTester = (props = {}) => {
736
794
  }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
737
795
  PlayruoEmbed,
738
796
  {
739
- baseUrl: baseUrl || "",
797
+ baseUrl: props.baseUrl,
740
798
  className: "w-full h-full absolute inset-0",
741
799
  iframeRef,
742
800
  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;
@@ -121,16 +123,15 @@ declare const isPlayruoEmbedMessage: (data: unknown) => data is PlayruoEmbedMess
121
123
  declare const isPlayruoInternalMessage: (data: unknown) => data is PlayruoInternalMessage;
122
124
 
123
125
  interface BuildUrlParams {
124
- baseUrl: string;
125
126
  operation?: string;
126
127
  storeUrl?: string;
127
128
  }
128
129
  /**
129
- * Build direct game URL (no intermediate /embed page)
130
+ * Build direct game URL (no intermediate /embed page). Always targets production.
130
131
  * @example
131
- * buildEmbedUrl({ baseUrl: "https://playruo.live", operation: "my-game" })
132
+ * buildEmbedUrl({ operation: "my-game" })
132
133
  * // => "https://playruo.live/my-game?iframe=true"
133
134
  */
134
- declare const buildEmbedUrl: ({ baseUrl, operation, storeUrl }: BuildUrlParams) => string;
135
+ declare const buildEmbedUrl: ({ operation, storeUrl }: BuildUrlParams) => string;
135
136
 
136
137
  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;
@@ -121,16 +123,15 @@ declare const isPlayruoEmbedMessage: (data: unknown) => data is PlayruoEmbedMess
121
123
  declare const isPlayruoInternalMessage: (data: unknown) => data is PlayruoInternalMessage;
122
124
 
123
125
  interface BuildUrlParams {
124
- baseUrl: string;
125
126
  operation?: string;
126
127
  storeUrl?: string;
127
128
  }
128
129
  /**
129
- * Build direct game URL (no intermediate /embed page)
130
+ * Build direct game URL (no intermediate /embed page). Always targets production.
130
131
  * @example
131
- * buildEmbedUrl({ baseUrl: "https://playruo.live", operation: "my-game" })
132
+ * buildEmbedUrl({ operation: "my-game" })
132
133
  * // => "https://playruo.live/my-game?iframe=true"
133
134
  */
134
- declare const buildEmbedUrl: ({ baseUrl, operation, storeUrl }: BuildUrlParams) => string;
135
+ declare const buildEmbedUrl: ({ operation, storeUrl }: BuildUrlParams) => string;
135
136
 
136
137
  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,7 @@ var PlayruoEmbed = ({
30
34
  }) => {
31
35
  const internalIframeRef = useRef(null);
32
36
  const iframeRef = externalIframeRef || internalIframeRef;
37
+ const wasEmbedFullscreenRef = useRef(false);
33
38
  const [isIframeLoaded, setIsIframeLoaded] = useState(false);
34
39
  const [hasPlayedOnce, setHasPlayedOnce] = useState(false);
35
40
  const [showGameIframe, setShowGameIframe] = useState(false);
@@ -37,7 +42,10 @@ var PlayruoEmbed = ({
37
42
  const [isValidChromium, setIsValidChromium] = useState(true);
38
43
  const [errorMessage, setErrorMessage] = useState(null);
39
44
  const [iframeState, setIframeState] = useState(null);
40
- const src = useMemo(() => buildEmbedUrl({ baseUrl, operation, storeUrl }), [baseUrl, operation, storeUrl]);
45
+ const src = useMemo(
46
+ () => baseUrl != null && baseUrl !== "" ? buildEmbedUrlWithOrigin(baseUrl, { operation, storeUrl }) : buildEmbedUrl({ operation, storeUrl }),
47
+ [baseUrl, operation, storeUrl]
48
+ );
41
49
  useEffect(() => {
42
50
  if (typeof window === "undefined") return;
43
51
  const detectMobile = () => {
@@ -82,7 +90,46 @@ var PlayruoEmbed = ({
82
90
  }),
83
91
  [currentState, hasIframeConfirmed, isCompatible]
84
92
  );
85
- const handleTryNow = useCallback(() => {
93
+ const getKeyboardApi = useCallback(() => {
94
+ if (typeof navigator === "undefined") {
95
+ return void 0;
96
+ }
97
+ return navigator.keyboard;
98
+ }, []);
99
+ const releaseEscapeKeyLock = useCallback(() => {
100
+ const keyboard = getKeyboardApi();
101
+ try {
102
+ keyboard?.unlock?.();
103
+ } catch (error) {
104
+ console.warn("[PlayruoEmbed] Failed to release Escape key lock:", error);
105
+ }
106
+ }, [getKeyboardApi]);
107
+ const requestFullscreenWithEscapeLock = useCallback(
108
+ 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);
118
+ }
119
+ });
120
+ }
121
+ try {
122
+ await fullscreenPromise;
123
+ wasEmbedFullscreenRef.current = true;
124
+ console.log("[PlayruoEmbed] Fullscreen granted");
125
+ } catch (error) {
126
+ releaseEscapeKeyLock();
127
+ throw error;
128
+ }
129
+ },
130
+ [getKeyboardApi, releaseEscapeKeyLock]
131
+ );
132
+ const handleTryNow = useCallback(async () => {
86
133
  if (!isCompatible) {
87
134
  console.warn("[PlayruoEmbed] Browser not compatible");
88
135
  return;
@@ -94,12 +141,10 @@ var PlayruoEmbed = ({
94
141
  }
95
142
  setShowGameIframe(true);
96
143
  try {
97
- const el = iframeRef.current;
98
- if (el) {
99
- try {
100
- el.focus();
101
- } catch {
102
- }
144
+ try {
145
+ await requestFullscreenWithEscapeLock(iframe);
146
+ } catch (fsError) {
147
+ console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
103
148
  }
104
149
  iframe.contentWindow?.postMessage({
105
150
  type: "playruo",
@@ -118,7 +163,7 @@ var PlayruoEmbed = ({
118
163
  setErrorMessage("Failed to start game");
119
164
  onError?.("Failed to start game");
120
165
  }
121
- }, [isCompatible, hasPlayedOnce, onError, iframeRef]);
166
+ }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestFullscreenWithEscapeLock]);
122
167
  const handleBuyNow = useCallback(() => {
123
168
  if (!storeUrl) {
124
169
  console.warn("[PlayruoEmbed] No store URL provided");
@@ -247,17 +292,25 @@ var PlayruoEmbed = ({
247
292
  useEffect(() => {
248
293
  const handleFullscreenChange = () => {
249
294
  const doc = document;
250
- const isFullscreen = !!(doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement);
251
- if (isFullscreen) {
252
- const iframe = iframeRef.current;
295
+ const iframe = iframeRef.current;
296
+ const fullscreenElement = doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement || null;
297
+ const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
298
+ if (isEmbedFullscreen) {
299
+ wasEmbedFullscreenRef.current = true;
253
300
  if (iframe) {
254
301
  setTimeout(() => {
255
302
  iframe.focus();
256
303
  iframe.contentWindow?.focus();
257
304
  }, 200);
258
305
  }
259
- } else {
260
- const iframe = iframeRef.current;
306
+ return;
307
+ }
308
+ if (fullscreenElement) {
309
+ return;
310
+ }
311
+ releaseEscapeKeyLock();
312
+ if (wasEmbedFullscreenRef.current) {
313
+ wasEmbedFullscreenRef.current = false;
261
314
  if (iframe) {
262
315
  try {
263
316
  iframe.contentWindow?.postMessage({
@@ -287,7 +340,12 @@ var PlayruoEmbed = ({
287
340
  document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
288
341
  document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
289
342
  };
290
- }, [storeUrl, onDemoCompleted, iframeRef]);
343
+ }, [storeUrl, onDemoCompleted, iframeRef, releaseEscapeKeyLock]);
344
+ useEffect(() => {
345
+ return () => {
346
+ releaseEscapeKeyLock();
347
+ };
348
+ }, [releaseEscapeKeyLock]);
291
349
  useEffect(() => {
292
350
  const logCrossOriginStatus = () => {
293
351
  const isIsolated = self.crossOriginIsolated;
@@ -458,7 +516,7 @@ var EmbedTester = (props = {}) => {
458
516
  fullscreenSupported: false,
459
517
  keyboardLockSupported: false
460
518
  });
461
- const baseUrl = props.baseUrl;
519
+ const displayHost = props.baseUrl ? new URL(props.baseUrl).host : "playruo.live";
462
520
  const enableConsoleLog = props.enableConsoleLog ?? false;
463
521
  const hasReadyBeenCalled = useRef2(false);
464
522
  const previousStateRef = useRef2("");
@@ -683,7 +741,7 @@ var EmbedTester = (props = {}) => {
683
741
  border: "1px solid #404040"
684
742
  }, children: [
685
743
  "\u{1F9EA} ",
686
- baseUrl ? new URL(baseUrl).host : "localhost:3000"
744
+ displayHost
687
745
  ] })
688
746
  ] }),
689
747
  /* @__PURE__ */ jsx2("div", { style: {
@@ -696,7 +754,7 @@ var EmbedTester = (props = {}) => {
696
754
  }, children: /* @__PURE__ */ jsx2(
697
755
  PlayruoEmbed,
698
756
  {
699
- baseUrl: baseUrl || "",
757
+ baseUrl: props.baseUrl,
700
758
  className: "w-full h-full absolute inset-0",
701
759
  iframeRef,
702
760
  onDemoCompleted: handleDemoCompleted,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playruo/embed",
3
- "version": "1.2.6",
3
+ "version": "1.2.9",
4
4
  "description": "React SDK to embed Playruo with postMessage API.",
5
5
  "license": "MIT",
6
6
  "type": "module",