@playruo/embed 1.2.7 → 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,6 +130,45 @@ var PlayruoEmbed = ({
122
130
  }),
123
131
  [currentState, hasIframeConfirmed, isCompatible]
124
132
  );
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
+ );
125
172
  const handleTryNow = (0, import_react.useCallback)(async () => {
126
173
  if (!isCompatible) {
127
174
  console.warn("[PlayruoEmbed] Browser not compatible");
@@ -135,8 +182,7 @@ var PlayruoEmbed = ({
135
182
  setShowGameIframe(true);
136
183
  try {
137
184
  try {
138
- await iframe.requestFullscreen();
139
- console.log("[PlayruoEmbed] Fullscreen granted");
185
+ await requestFullscreenWithEscapeLock(iframe);
140
186
  } catch (fsError) {
141
187
  console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
142
188
  }
@@ -157,7 +203,7 @@ var PlayruoEmbed = ({
157
203
  setErrorMessage("Failed to start game");
158
204
  onError?.("Failed to start game");
159
205
  }
160
- }, [isCompatible, hasPlayedOnce, onError, iframeRef]);
206
+ }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestFullscreenWithEscapeLock]);
161
207
  const handleBuyNow = (0, import_react.useCallback)(() => {
162
208
  if (!storeUrl) {
163
209
  console.warn("[PlayruoEmbed] No store URL provided");
@@ -286,17 +332,25 @@ var PlayruoEmbed = ({
286
332
  (0, import_react.useEffect)(() => {
287
333
  const handleFullscreenChange = () => {
288
334
  const doc = document;
289
- const isFullscreen = !!(doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement);
290
- if (isFullscreen) {
291
- 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;
292
340
  if (iframe) {
293
341
  setTimeout(() => {
294
342
  iframe.focus();
295
343
  iframe.contentWindow?.focus();
296
344
  }, 200);
297
345
  }
298
- } else {
299
- const iframe = iframeRef.current;
346
+ return;
347
+ }
348
+ if (fullscreenElement) {
349
+ return;
350
+ }
351
+ releaseEscapeKeyLock();
352
+ if (wasEmbedFullscreenRef.current) {
353
+ wasEmbedFullscreenRef.current = false;
300
354
  if (iframe) {
301
355
  try {
302
356
  iframe.contentWindow?.postMessage({
@@ -326,7 +380,12 @@ var PlayruoEmbed = ({
326
380
  document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
327
381
  document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
328
382
  };
329
- }, [storeUrl, onDemoCompleted, iframeRef]);
383
+ }, [storeUrl, onDemoCompleted, iframeRef, releaseEscapeKeyLock]);
384
+ (0, import_react.useEffect)(() => {
385
+ return () => {
386
+ releaseEscapeKeyLock();
387
+ };
388
+ }, [releaseEscapeKeyLock]);
330
389
  (0, import_react.useEffect)(() => {
331
390
  const logCrossOriginStatus = () => {
332
391
  const isIsolated = self.crossOriginIsolated;
@@ -497,7 +556,7 @@ var EmbedTester = (props = {}) => {
497
556
  fullscreenSupported: false,
498
557
  keyboardLockSupported: false
499
558
  });
500
- const baseUrl = props.baseUrl;
559
+ const displayHost = props.baseUrl ? new URL(props.baseUrl).host : "playruo.live";
501
560
  const enableConsoleLog = props.enableConsoleLog ?? false;
502
561
  const hasReadyBeenCalled = (0, import_react2.useRef)(false);
503
562
  const previousStateRef = (0, import_react2.useRef)("");
@@ -722,7 +781,7 @@ var EmbedTester = (props = {}) => {
722
781
  border: "1px solid #404040"
723
782
  }, children: [
724
783
  "\u{1F9EA} ",
725
- baseUrl ? new URL(baseUrl).host : "localhost:3000"
784
+ displayHost
726
785
  ] })
727
786
  ] }),
728
787
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
@@ -735,7 +794,7 @@ var EmbedTester = (props = {}) => {
735
794
  }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
736
795
  PlayruoEmbed,
737
796
  {
738
- baseUrl: baseUrl || "",
797
+ baseUrl: props.baseUrl,
739
798
  className: "w-full h-full absolute inset-0",
740
799
  iframeRef,
741
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,6 +90,45 @@ var PlayruoEmbed = ({
82
90
  }),
83
91
  [currentState, hasIframeConfirmed, isCompatible]
84
92
  );
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
+ );
85
132
  const handleTryNow = useCallback(async () => {
86
133
  if (!isCompatible) {
87
134
  console.warn("[PlayruoEmbed] Browser not compatible");
@@ -95,8 +142,7 @@ var PlayruoEmbed = ({
95
142
  setShowGameIframe(true);
96
143
  try {
97
144
  try {
98
- await iframe.requestFullscreen();
99
- console.log("[PlayruoEmbed] Fullscreen granted");
145
+ await requestFullscreenWithEscapeLock(iframe);
100
146
  } catch (fsError) {
101
147
  console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
102
148
  }
@@ -117,7 +163,7 @@ var PlayruoEmbed = ({
117
163
  setErrorMessage("Failed to start game");
118
164
  onError?.("Failed to start game");
119
165
  }
120
- }, [isCompatible, hasPlayedOnce, onError, iframeRef]);
166
+ }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestFullscreenWithEscapeLock]);
121
167
  const handleBuyNow = useCallback(() => {
122
168
  if (!storeUrl) {
123
169
  console.warn("[PlayruoEmbed] No store URL provided");
@@ -246,17 +292,25 @@ var PlayruoEmbed = ({
246
292
  useEffect(() => {
247
293
  const handleFullscreenChange = () => {
248
294
  const doc = document;
249
- const isFullscreen = !!(doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement);
250
- if (isFullscreen) {
251
- 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;
252
300
  if (iframe) {
253
301
  setTimeout(() => {
254
302
  iframe.focus();
255
303
  iframe.contentWindow?.focus();
256
304
  }, 200);
257
305
  }
258
- } else {
259
- const iframe = iframeRef.current;
306
+ return;
307
+ }
308
+ if (fullscreenElement) {
309
+ return;
310
+ }
311
+ releaseEscapeKeyLock();
312
+ if (wasEmbedFullscreenRef.current) {
313
+ wasEmbedFullscreenRef.current = false;
260
314
  if (iframe) {
261
315
  try {
262
316
  iframe.contentWindow?.postMessage({
@@ -286,7 +340,12 @@ var PlayruoEmbed = ({
286
340
  document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
287
341
  document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
288
342
  };
289
- }, [storeUrl, onDemoCompleted, iframeRef]);
343
+ }, [storeUrl, onDemoCompleted, iframeRef, releaseEscapeKeyLock]);
344
+ useEffect(() => {
345
+ return () => {
346
+ releaseEscapeKeyLock();
347
+ };
348
+ }, [releaseEscapeKeyLock]);
290
349
  useEffect(() => {
291
350
  const logCrossOriginStatus = () => {
292
351
  const isIsolated = self.crossOriginIsolated;
@@ -457,7 +516,7 @@ var EmbedTester = (props = {}) => {
457
516
  fullscreenSupported: false,
458
517
  keyboardLockSupported: false
459
518
  });
460
- const baseUrl = props.baseUrl;
519
+ const displayHost = props.baseUrl ? new URL(props.baseUrl).host : "playruo.live";
461
520
  const enableConsoleLog = props.enableConsoleLog ?? false;
462
521
  const hasReadyBeenCalled = useRef2(false);
463
522
  const previousStateRef = useRef2("");
@@ -682,7 +741,7 @@ var EmbedTester = (props = {}) => {
682
741
  border: "1px solid #404040"
683
742
  }, children: [
684
743
  "\u{1F9EA} ",
685
- baseUrl ? new URL(baseUrl).host : "localhost:3000"
744
+ displayHost
686
745
  ] })
687
746
  ] }),
688
747
  /* @__PURE__ */ jsx2("div", { style: {
@@ -695,7 +754,7 @@ var EmbedTester = (props = {}) => {
695
754
  }, children: /* @__PURE__ */ jsx2(
696
755
  PlayruoEmbed,
697
756
  {
698
- baseUrl: baseUrl || "",
757
+ baseUrl: props.baseUrl,
699
758
  className: "w-full h-full absolute inset-0",
700
759
  iframeRef,
701
760
  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.9",
4
4
  "description": "React SDK to embed Playruo with postMessage API.",
5
5
  "license": "MIT",
6
6
  "type": "module",