@playruo/embed 1.2.2 → 1.2.5

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
@@ -33,8 +33,8 @@ __export(index_exports, {
33
33
  EmbedTester: () => EmbedTester,
34
34
  PlayruoEmbed: () => PlayruoEmbed,
35
35
  buildEmbedUrl: () => buildEmbedUrl,
36
- isPlayruoEmbedCommand: () => isPlayruoEmbedCommand,
37
- isPlayruoEmbedMessage: () => isPlayruoEmbedMessage
36
+ isPlayruoEmbedMessage: () => isPlayruoEmbedMessage,
37
+ isPlayruoInternalMessage: () => isPlayruoInternalMessage
38
38
  });
39
39
  module.exports = __toCommonJS(index_exports);
40
40
 
@@ -76,9 +76,7 @@ var PlayruoEmbed = ({
76
76
  const [isMobile, setIsMobile] = (0, import_react.useState)(false);
77
77
  const [isValidChromium, setIsValidChromium] = (0, import_react.useState)(true);
78
78
  const [errorMessage, setErrorMessage] = (0, import_react.useState)(null);
79
- const escapeKeyTimer = (0, import_react.useRef)(null);
80
- const escapeKeyPressed = (0, import_react.useRef)(false);
81
- const ESCAPE_HOLD_DURATION = 1500;
79
+ const [iframeState, setIframeState] = (0, import_react.useState)(null);
82
80
  const src = (0, import_react.useMemo)(() => buildEmbedUrl({ baseUrl, operation, storeUrl }), [baseUrl, operation, storeUrl]);
83
81
  (0, import_react.useEffect)(() => {
84
82
  if (typeof window === "undefined") return;
@@ -96,81 +94,34 @@ var PlayruoEmbed = ({
96
94
  }, []);
97
95
  const isCompatible = !isMobile && isValidChromium;
98
96
  const isReady = isCompatible ? isIframeLoaded : true;
99
- const currentState = (0, import_react.useMemo)(() => ({
100
- hasPlayedOnce,
101
- tryNow: {
102
- available: isCompatible,
103
- status: !isCompatible ? "Browser not compatible" : hasPlayedOnce ? "Click to replay" : "Ready"
104
- },
105
- buyNow: {
106
- available: true,
107
- status: "Ready"
108
- }
109
- }), [hasPlayedOnce, isCompatible]);
110
- const checkKeyboardLockPermission = (0, import_react.useCallback)(async () => {
111
- if (!navigator.permissions) {
112
- console.log("[PlayruoEmbed] Permissions API not supported");
113
- return false;
97
+ const currentState = (0, import_react.useMemo)(() => {
98
+ if (iframeState) {
99
+ return iframeState;
114
100
  }
115
- try {
116
- const { state } = await navigator.permissions.query({
117
- name: "keyboard-lock"
118
- });
119
- if (state === "granted" || state === "prompt") {
120
- console.log(`[PlayruoEmbed] Keyboard lock permission: ${state}`);
121
- return true;
101
+ return {
102
+ hasPlayedOnce,
103
+ tryNow: {
104
+ available: isCompatible,
105
+ status: !isCompatible ? "Browser not compatible" : hasPlayedOnce ? "Click to resume" : "Ready"
106
+ },
107
+ buyNow: {
108
+ available: true,
109
+ status: "Ready"
122
110
  }
123
- console.log("[PlayruoEmbed] Keyboard lock permission denied");
124
- return false;
125
- } catch (error) {
126
- console.warn("[PlayruoEmbed] Error checking keyboard lock permission:", error);
127
- return false;
128
- }
129
- }, []);
130
- const requestFullscreenWithKeyboardLock = (0, import_react.useCallback)(async () => {
131
- const iframe = iframeRef.current;
132
- if (!iframe?.requestFullscreen) {
133
- console.log("[PlayruoEmbed] Fullscreen not supported on this browser");
134
- return false;
135
- }
136
- try {
137
- const fullscreenPromise = iframe.requestFullscreen();
138
- if (navigator.keyboard && window.self === window.top) {
139
- try {
140
- const permissionPromise = checkKeyboardLockPermission();
141
- const keyboardLockPromise = navigator.keyboard.lock(["Escape"]);
142
- void Promise.all([permissionPromise, keyboardLockPromise]).then(([hasPermission]) => {
143
- if (hasPermission) {
144
- console.log("[PlayruoEmbed] Keyboard locked successfully");
145
- }
146
- }).catch((err) => {
147
- const isTopLevelContextError = err?.name === "InvalidStateError" && err?.message?.includes("primary top-level browsing context");
148
- if (!isTopLevelContextError) {
149
- console.warn("[PlayruoEmbed] Keyboard lock failed:", err);
150
- }
151
- });
152
- } catch (err) {
153
- const isTopLevelContextError = err?.name === "InvalidStateError" && err?.message?.includes("primary top-level browsing context");
154
- if (!isTopLevelContextError) {
155
- console.warn("[PlayruoEmbed] Keyboard lock error:", err);
156
- }
157
- }
158
- } else if (!navigator.keyboard) {
159
- console.log("[PlayruoEmbed] Keyboard API not supported");
160
- } else {
161
- console.log("[PlayruoEmbed] Keyboard lock not available (not in top-level context)");
111
+ };
112
+ }, [hasPlayedOnce, isCompatible, iframeState]);
113
+ const hasIframeConfirmed = Boolean(iframeState?.tryNow?.available === true);
114
+ const exposedState = (0, import_react.useMemo)(
115
+ () => ({
116
+ ...currentState,
117
+ tryNow: {
118
+ ...currentState.tryNow,
119
+ available: hasIframeConfirmed,
120
+ status: currentState.tryNow.status ?? (hasIframeConfirmed ? "Ready" : isCompatible ? "Checking availability\u2026" : "Browser not compatible")
162
121
  }
163
- void fullscreenPromise.then(() => {
164
- console.log("[PlayruoEmbed] Fullscreen activated successfully");
165
- }).catch((err) => {
166
- console.warn("[PlayruoEmbed] Fullscreen failed:", err);
167
- });
168
- return true;
169
- } catch (error) {
170
- console.error("[PlayruoEmbed] Error requesting fullscreen:", error);
171
- return false;
172
- }
173
- }, [iframeRef, checkKeyboardLockPermission]);
122
+ }),
123
+ [currentState, hasIframeConfirmed, isCompatible]
124
+ );
174
125
  const handleTryNow = (0, import_react.useCallback)(() => {
175
126
  if (!isCompatible) {
176
127
  console.warn("[PlayruoEmbed] Browser not compatible");
@@ -190,12 +141,10 @@ var PlayruoEmbed = ({
190
141
  } catch {
191
142
  }
192
143
  }
193
- requestFullscreenWithKeyboardLock();
194
144
  iframe.contentWindow?.postMessage({
195
145
  type: "playruo",
196
146
  action: "tryNowClick",
197
147
  fromEmbed: true,
198
- isFullscreen: true,
199
148
  isReplay: hasPlayedOnce
200
149
  }, "*");
201
150
  console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
@@ -209,7 +158,7 @@ var PlayruoEmbed = ({
209
158
  setErrorMessage("Failed to start game");
210
159
  onError?.("Failed to start game");
211
160
  }
212
- }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestFullscreenWithKeyboardLock]);
161
+ }, [isCompatible, hasPlayedOnce, onError, iframeRef]);
213
162
  const handleBuyNow = (0, import_react.useCallback)(() => {
214
163
  if (!storeUrl) {
215
164
  console.warn("[PlayruoEmbed] No store URL provided");
@@ -219,9 +168,9 @@ var PlayruoEmbed = ({
219
168
  }, [storeUrl, onPurchaseRequest]);
220
169
  (0, import_react.useEffect)(() => {
221
170
  if (isReady) {
222
- onStateChange?.(currentState);
171
+ onStateChange?.(exposedState);
223
172
  }
224
- }, [isReady, currentState, onStateChange]);
173
+ }, [isReady, exposedState, onStateChange]);
225
174
  (0, import_react.useEffect)(() => {
226
175
  if (isReady) {
227
176
  onReady?.();
@@ -229,18 +178,112 @@ var PlayruoEmbed = ({
229
178
  }, [isReady, onReady]);
230
179
  (0, import_react.useEffect)(() => {
231
180
  const handleGameMessage = (event) => {
232
- const { type } = event.data || {};
181
+ const { type, data } = event.data || {};
182
+ const iframe = iframeRef.current;
183
+ if (!iframe || event.source !== iframe.contentWindow) {
184
+ return;
185
+ }
186
+ console.log("\u{1F537} [Embed] Message received from iframe:", { type, data });
187
+ if (type === "playruo_ready") {
188
+ console.log("\u{1F537} [Embed] playruo_ready received");
189
+ onReady?.();
190
+ } else if (type === "playruo_state_update" && data) {
191
+ console.log("\u{1F537} [Embed] playruo_state_update received:", data);
192
+ setIframeState(data);
193
+ if (data.hasPlayedOnce !== void 0) {
194
+ setHasPlayedOnce(data.hasPlayedOnce);
195
+ }
196
+ onStateChange?.(data);
197
+ } else if (type === "playruo_purchase_request" && data?.storeUrl) {
198
+ console.log("\u{1F537} [Embed] playruo_purchase_request received:", data);
199
+ onPurchaseRequest?.(data);
200
+ } else if (type === "playruo_demo_completed" && data) {
201
+ console.log("\u{1F537} [Embed] playruo_demo_completed received:", data);
202
+ setHasPlayedOnce(true);
203
+ onDemoCompleted?.(data);
204
+ } else if (type === "playruo_error") {
205
+ const errorMessage2 = typeof data === "string" ? data : data?.message || "Unknown error";
206
+ console.log("\u{1F537} [Embed] playruo_error received:", errorMessage2);
207
+ setErrorMessage(errorMessage2);
208
+ const errorState = {
209
+ hasPlayedOnce,
210
+ tryNow: {
211
+ available: false,
212
+ status: `Error: ${errorMessage2}`
213
+ },
214
+ buyNow: {
215
+ available: true,
216
+ status: "Ready"
217
+ }
218
+ };
219
+ setIframeState(errorState);
220
+ onStateChange?.(errorState);
221
+ onError?.(errorMessage2);
222
+ } else if (type === "playruo_session_over" && data) {
223
+ console.log("\u{1F537} [Embed] playruo_session_over received:", data);
224
+ setHasPlayedOnce(true);
225
+ setIframeState(data);
226
+ onStateChange?.(data);
227
+ } else if (type === "playruo_stream_start") {
228
+ console.log("\u{1F537} [Embed] playruo_stream_start received");
229
+ }
233
230
  if (type === "error" || type === "closeForBuy" || type === "goToStore") {
231
+ console.log("\u{1F537} [Embed] Legacy message received:", type);
234
232
  setHasPlayedOnce(true);
235
- onDemoCompleted?.({
233
+ const payload = {
236
234
  reason: "Demo session ended",
237
235
  storeUrl
238
- });
236
+ };
237
+ onDemoCompleted?.(payload);
239
238
  }
240
239
  };
241
240
  window.addEventListener("message", handleGameMessage);
242
241
  return () => window.removeEventListener("message", handleGameMessage);
243
- }, [storeUrl, onDemoCompleted]);
242
+ }, [storeUrl, onDemoCompleted, onReady, onStateChange, onPurchaseRequest, onError, iframeRef]);
243
+ const generateUUID = () => {
244
+ if (self.crypto.randomUUID) return self.crypto.randomUUID();
245
+ const bytes = new Uint8Array(16);
246
+ self.crypto.getRandomValues(bytes);
247
+ bytes[6] = bytes[6] & 15 | 64;
248
+ bytes[8] = bytes[8] & 63 | 128;
249
+ return [...bytes].map(
250
+ (b, i) => ([4, 6, 8, 10].includes(i) ? "-" : "") + b.toString(16).padStart(2, "0")
251
+ ).join("");
252
+ };
253
+ (0, import_react.useEffect)(() => {
254
+ const handleUUIDMessage = (event) => {
255
+ const iframe = iframeRef.current;
256
+ if (!iframe || event.source !== iframe.contentWindow) {
257
+ return;
258
+ }
259
+ const { type, requestId } = event.data || {};
260
+ if (type === "playruo_uuid_request") {
261
+ try {
262
+ let storedUUID = localStorage.getItem("playruo_uuid");
263
+ if (!storedUUID) {
264
+ storedUUID = generateUUID();
265
+ localStorage.setItem("playruo_uuid", storedUUID);
266
+ }
267
+ iframe.contentWindow?.postMessage(
268
+ {
269
+ type: "playruo_uuid_response",
270
+ requestId,
271
+ uuid: storedUUID
272
+ },
273
+ "*"
274
+ );
275
+ } catch (e) {
276
+ }
277
+ } else if (type === "playruo_uuid_remove") {
278
+ try {
279
+ localStorage.removeItem("playruo_uuid");
280
+ } catch (e) {
281
+ }
282
+ }
283
+ };
284
+ window.addEventListener("message", handleUUIDMessage);
285
+ return () => window.removeEventListener("message", handleUUIDMessage);
286
+ }, [iframeRef]);
244
287
  (0, import_react.useEffect)(() => {
245
288
  const handleFullscreenChange = () => {
246
289
  const doc = document;
@@ -254,10 +297,17 @@ var PlayruoEmbed = ({
254
297
  }, 200);
255
298
  }
256
299
  } else {
257
- escapeKeyPressed.current = false;
258
- if (escapeKeyTimer.current) {
259
- clearTimeout(escapeKeyTimer.current);
260
- escapeKeyTimer.current = null;
300
+ const iframe = iframeRef.current;
301
+ if (iframe) {
302
+ try {
303
+ iframe.contentWindow?.postMessage({
304
+ type: "playruo",
305
+ action: "exitFullscreen",
306
+ fromEmbed: true
307
+ }, "*");
308
+ } catch {
309
+ console.error("[PlayruoEmbed] Failed to exit fullscreen");
310
+ }
261
311
  }
262
312
  setShowGameIframe(false);
263
313
  setHasPlayedOnce(true);
@@ -278,56 +328,6 @@ var PlayruoEmbed = ({
278
328
  document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
279
329
  };
280
330
  }, [storeUrl, onDemoCompleted, iframeRef]);
281
- (0, import_react.useEffect)(() => {
282
- const handleKeyDown = (event) => {
283
- if (event.key === "Escape" && !escapeKeyPressed.current) {
284
- escapeKeyPressed.current = true;
285
- event.preventDefault();
286
- escapeKeyTimer.current = setTimeout(() => {
287
- if (document.fullscreenElement) {
288
- document.exitFullscreen();
289
- }
290
- }, ESCAPE_HOLD_DURATION);
291
- }
292
- if (event.ctrlKey && event.shiftKey && event.key === "R") {
293
- event.preventDefault();
294
- if (document.fullscreenElement) {
295
- document.exitFullscreen().catch(console.warn);
296
- }
297
- setTimeout(() => {
298
- window.location.reload();
299
- }, 500);
300
- }
301
- };
302
- const handleKeyUp = (event) => {
303
- if (event.key === "Escape" && escapeKeyPressed.current) {
304
- escapeKeyPressed.current = false;
305
- if (escapeKeyTimer.current) {
306
- clearTimeout(escapeKeyTimer.current);
307
- escapeKeyTimer.current = null;
308
- }
309
- }
310
- };
311
- document.addEventListener("keydown", handleKeyDown);
312
- document.addEventListener("keyup", handleKeyUp);
313
- return () => {
314
- document.removeEventListener("keydown", handleKeyDown);
315
- document.removeEventListener("keyup", handleKeyUp);
316
- };
317
- }, []);
318
- (0, import_react.useEffect)(() => {
319
- const isVisible = showGameIframe || showIframe;
320
- const iframe = iframeRef.current;
321
- if (!iframe || !isIframeLoaded) return;
322
- try {
323
- iframe.contentWindow?.postMessage({
324
- type: "playruo",
325
- action: isVisible ? "unmute" : "mute",
326
- fromEmbed: true
327
- }, "*");
328
- } catch {
329
- }
330
- }, [showGameIframe, showIframe, isIframeLoaded, iframeRef]);
331
331
  (0, import_react.useEffect)(() => {
332
332
  const logCrossOriginStatus = () => {
333
333
  const isIsolated = self.crossOriginIsolated;
@@ -406,22 +406,28 @@ var PlayruoEmbed = ({
406
406
  logCrossOriginStatus();
407
407
  }
408
408
  }, [isReady]);
409
- const isAvailable = Boolean(isReady && isCompatible);
409
+ const isAvailable = Boolean(isReady && isCompatible && hasIframeConfirmed);
410
410
  const renderContent = () => {
411
411
  if (!renderState) return null;
412
+ const canTryNow = Boolean(
413
+ !errorMessage && isReady && isCompatible && hasIframeConfirmed
414
+ );
412
415
  const actions = {
413
416
  tryNow: handleTryNow,
414
417
  buyNow: handleBuyNow,
415
- canTryNow: Boolean(isReady && isCompatible),
418
+ canTryNow,
416
419
  canBuyNow: Boolean(isReady)
417
420
  };
418
421
  if (errorMessage) {
419
422
  return renderState({ status: "error", message: errorMessage, actions });
420
423
  }
421
424
  if (!isReady) {
422
- return renderState({ status: "loading", actions });
425
+ return renderState({ status: "loading", message: "Loading\u2026", actions });
426
+ }
427
+ if (isCompatible && !iframeState) {
428
+ return renderState({ status: "loading", message: "Checking availability\u2026", actions });
423
429
  }
424
- const message = !isAvailable ? currentState.tryNow.status : void 0;
430
+ const message = !isAvailable ? currentState.tryNow.status ?? exposedState.tryNow.status : void 0;
425
431
  return renderState({
426
432
  status: isAvailable ? "available" : "unavailable",
427
433
  message,
@@ -436,7 +442,6 @@ var PlayruoEmbed = ({
436
442
  ref: iframeRef,
437
443
  src,
438
444
  allow: "cross-origin-isolated; fullscreen *; autoplay *; gamepad *; clipboard-write *; web-share *; encrypted-media *; accelerometer *; gyroscope *",
439
- allowFullScreen: true,
440
445
  className: "w-full h-[70vh] rounded-lg border",
441
446
  ...{ credentialless: "true" },
442
447
  sandbox: "allow-same-origin allow-scripts allow-popups allow-forms allow-presentation allow-top-navigation allow-modals allow-pointer-lock",
@@ -655,6 +660,10 @@ var EmbedTester = (props = {}) => {
655
660
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: embedState.hasPlayedOnce ? successStatus : errorStatus, children: embedState.hasPlayedOnce ? "\u2705 Played" : "\u274C Not Played" }),
656
661
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: embedState.tryNow?.available ? successStatus : errorStatus, children: embedState.tryNow?.available ? "\u2705 Try Now" : "\u274C Try Now" }),
657
662
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: embedState.buyNow?.available ? successStatus : errorStatus, children: embedState.buyNow?.available ? "\u2705 Buy Now" : "\u274C Buy Now" })
663
+ ] }),
664
+ !embedState.tryNow?.available && embedState.tryNow?.status && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { style: { margin: "8px 0 0 0", fontSize: "12px", color: "#a3a3a3" }, children: [
665
+ "Try Now reason: ",
666
+ embedState.tryNow.status
658
667
  ] })
659
668
  ] }),
660
669
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: cardStyle, children: [
@@ -684,7 +693,8 @@ var EmbedTester = (props = {}) => {
684
693
  backgroundColor: "#0a0a0a",
685
694
  padding: "12px",
686
695
  borderRadius: "4px",
687
- height: "200px",
696
+ minHeight: "200px",
697
+ height: "min(50vh, 480px)",
688
698
  overflowY: "auto",
689
699
  fontFamily: "monospace",
690
700
  fontSize: "12px",
@@ -829,19 +839,18 @@ var isPlayruoEmbedMessage = (data) => {
829
839
  if (!data || typeof data !== "object") return false;
830
840
  const msg = data;
831
841
  if (typeof msg.type !== "string") return false;
832
- return msg.type === "playruo_ready" || msg.type === "playruo_state_update" || msg.type === "playruo_purchase_request" || msg.type === "playruo_demo_completed" || msg.type === "playruo_error";
842
+ return msg.type === "playruo_ready" || msg.type === "playruo_state_update" || msg.type === "playruo_purchase_request" || msg.type === "playruo_demo_completed" || msg.type === "playruo_error" || msg.type === "playruo_session_over" || msg.type === "playruo_stream_start" || msg.type === "playruo_stream_stop";
833
843
  };
834
- var isPlayruoEmbedCommand = (data) => {
844
+ var isPlayruoInternalMessage = (data) => {
835
845
  if (!data || typeof data !== "object") return false;
836
- const cmd = data;
837
- if (typeof cmd.type !== "string") return false;
838
- return cmd.type === "try_now" || cmd.type === "buy_now" || cmd.type === "get_state";
846
+ const msg = data;
847
+ return msg.type === "playruo" && typeof msg.action === "string" && (msg.action === "tryNowClick" || msg.action === "exitFullscreen" || msg.action === "mute" || msg.action === "unmute") && msg.fromEmbed === true;
839
848
  };
840
849
  // Annotate the CommonJS export names for ESM import in node:
841
850
  0 && (module.exports = {
842
851
  EmbedTester,
843
852
  PlayruoEmbed,
844
853
  buildEmbedUrl,
845
- isPlayruoEmbedCommand,
846
- isPlayruoEmbedMessage
854
+ isPlayruoEmbedMessage,
855
+ isPlayruoInternalMessage
847
856
  });
package/dist/index.d.cts CHANGED
@@ -10,6 +10,7 @@ interface PlayruoEmbedState {
10
10
  buyNow: {
11
11
  available: boolean;
12
12
  status?: string;
13
+ storeUrl?: string;
13
14
  };
14
15
  }
15
16
  interface DemoCompletedPayload {
@@ -17,15 +18,6 @@ interface DemoCompletedPayload {
17
18
  storeUrl?: string;
18
19
  }
19
20
 
20
- interface Keyboard {
21
- lock: (keys: string[]) => Promise<void>;
22
- unlock: () => void;
23
- }
24
- declare global {
25
- interface Navigator {
26
- keyboard?: Keyboard;
27
- }
28
- }
29
21
  interface PlayruoEmbedActions {
30
22
  /** Start the demo session */
31
23
  tryNow: () => void;
@@ -98,25 +90,35 @@ interface PlayruoErrorMessage {
98
90
  message: string;
99
91
  } | string;
100
92
  }
101
- type PlayruoEmbedMessage = PlayruoReadyMessage | PlayruoStateUpdateMessage | PlayruoPurchaseRequestMessage | PlayruoDemoCompletedMessage | PlayruoErrorMessage;
102
- interface TryNowCommand {
103
- type: "try_now";
93
+ interface PlayruoSessionOverMessage {
94
+ type: "playruo_session_over";
95
+ data: PlayruoEmbedState;
96
+ }
97
+ interface PlayruoStreamStartMessage {
98
+ type: "playruo_stream_start";
104
99
  }
105
- interface BuyNowCommand {
106
- type: "buy_now";
100
+ interface PlayruoStreamStopMessage {
101
+ type: "playruo_stream_stop";
107
102
  }
108
- interface GetStateCommand {
109
- type: "get_state";
103
+ type PlayruoEmbedMessage = PlayruoReadyMessage | PlayruoStateUpdateMessage | PlayruoPurchaseRequestMessage | PlayruoDemoCompletedMessage | PlayruoErrorMessage | PlayruoSessionOverMessage | PlayruoStreamStartMessage | PlayruoStreamStopMessage;
104
+ interface PlayruoInternalMessage {
105
+ type: "playruo";
106
+ action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
107
+ fromEmbed: true;
108
+ isReplay?: boolean;
109
+ parentWillHandleFullscreen?: boolean;
110
110
  }
111
- type PlayruoEmbedCommand = TryNowCommand | BuyNowCommand | GetStateCommand;
112
111
  /**
113
112
  * Type guard to check if a message event is a Playruo embed message
113
+ * Messages FROM Embed → Host via postMessage
114
114
  */
115
115
  declare const isPlayruoEmbedMessage: (data: unknown) => data is PlayruoEmbedMessage;
116
116
  /**
117
- * Type guard to check if a command is a valid Playruo embed command
117
+ * Type guard to check if a message is a Playruo internal message
118
+ * Format: { type: 'playruo', action: '...', fromEmbed: true }
119
+ * These are messages sent TO the iframe (game), not to the parent
118
120
  */
119
- declare const isPlayruoEmbedCommand: (data: unknown) => data is PlayruoEmbedCommand;
121
+ declare const isPlayruoInternalMessage: (data: unknown) => data is PlayruoInternalMessage;
120
122
 
121
123
  interface BuildUrlParams {
122
124
  baseUrl: string;
@@ -131,4 +133,4 @@ interface BuildUrlParams {
131
133
  */
132
134
  declare const buildEmbedUrl: ({ baseUrl, operation, storeUrl }: BuildUrlParams) => string;
133
135
 
134
- export { type BuyNowCommand, type DemoCompletedPayload, EmbedTester, type EmbedTesterProps, type GetStateCommand, type PlayruoDemoCompletedMessage, PlayruoEmbed, type PlayruoEmbedActions, type PlayruoEmbedCommand, type PlayruoEmbedMessage, type PlayruoEmbedProps, type PlayruoEmbedRenderState, type PlayruoEmbedState, type PlayruoErrorMessage, type PlayruoPurchaseRequestMessage, type PlayruoReadyMessage, type PlayruoStateUpdateMessage, type TryNowCommand, buildEmbedUrl, isPlayruoEmbedCommand, isPlayruoEmbedMessage };
136
+ 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
@@ -10,6 +10,7 @@ interface PlayruoEmbedState {
10
10
  buyNow: {
11
11
  available: boolean;
12
12
  status?: string;
13
+ storeUrl?: string;
13
14
  };
14
15
  }
15
16
  interface DemoCompletedPayload {
@@ -17,15 +18,6 @@ interface DemoCompletedPayload {
17
18
  storeUrl?: string;
18
19
  }
19
20
 
20
- interface Keyboard {
21
- lock: (keys: string[]) => Promise<void>;
22
- unlock: () => void;
23
- }
24
- declare global {
25
- interface Navigator {
26
- keyboard?: Keyboard;
27
- }
28
- }
29
21
  interface PlayruoEmbedActions {
30
22
  /** Start the demo session */
31
23
  tryNow: () => void;
@@ -98,25 +90,35 @@ interface PlayruoErrorMessage {
98
90
  message: string;
99
91
  } | string;
100
92
  }
101
- type PlayruoEmbedMessage = PlayruoReadyMessage | PlayruoStateUpdateMessage | PlayruoPurchaseRequestMessage | PlayruoDemoCompletedMessage | PlayruoErrorMessage;
102
- interface TryNowCommand {
103
- type: "try_now";
93
+ interface PlayruoSessionOverMessage {
94
+ type: "playruo_session_over";
95
+ data: PlayruoEmbedState;
96
+ }
97
+ interface PlayruoStreamStartMessage {
98
+ type: "playruo_stream_start";
104
99
  }
105
- interface BuyNowCommand {
106
- type: "buy_now";
100
+ interface PlayruoStreamStopMessage {
101
+ type: "playruo_stream_stop";
107
102
  }
108
- interface GetStateCommand {
109
- type: "get_state";
103
+ type PlayruoEmbedMessage = PlayruoReadyMessage | PlayruoStateUpdateMessage | PlayruoPurchaseRequestMessage | PlayruoDemoCompletedMessage | PlayruoErrorMessage | PlayruoSessionOverMessage | PlayruoStreamStartMessage | PlayruoStreamStopMessage;
104
+ interface PlayruoInternalMessage {
105
+ type: "playruo";
106
+ action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
107
+ fromEmbed: true;
108
+ isReplay?: boolean;
109
+ parentWillHandleFullscreen?: boolean;
110
110
  }
111
- type PlayruoEmbedCommand = TryNowCommand | BuyNowCommand | GetStateCommand;
112
111
  /**
113
112
  * Type guard to check if a message event is a Playruo embed message
113
+ * Messages FROM Embed → Host via postMessage
114
114
  */
115
115
  declare const isPlayruoEmbedMessage: (data: unknown) => data is PlayruoEmbedMessage;
116
116
  /**
117
- * Type guard to check if a command is a valid Playruo embed command
117
+ * Type guard to check if a message is a Playruo internal message
118
+ * Format: { type: 'playruo', action: '...', fromEmbed: true }
119
+ * These are messages sent TO the iframe (game), not to the parent
118
120
  */
119
- declare const isPlayruoEmbedCommand: (data: unknown) => data is PlayruoEmbedCommand;
121
+ declare const isPlayruoInternalMessage: (data: unknown) => data is PlayruoInternalMessage;
120
122
 
121
123
  interface BuildUrlParams {
122
124
  baseUrl: string;
@@ -131,4 +133,4 @@ interface BuildUrlParams {
131
133
  */
132
134
  declare const buildEmbedUrl: ({ baseUrl, operation, storeUrl }: BuildUrlParams) => string;
133
135
 
134
- export { type BuyNowCommand, type DemoCompletedPayload, EmbedTester, type EmbedTesterProps, type GetStateCommand, type PlayruoDemoCompletedMessage, PlayruoEmbed, type PlayruoEmbedActions, type PlayruoEmbedCommand, type PlayruoEmbedMessage, type PlayruoEmbedProps, type PlayruoEmbedRenderState, type PlayruoEmbedState, type PlayruoErrorMessage, type PlayruoPurchaseRequestMessage, type PlayruoReadyMessage, type PlayruoStateUpdateMessage, type TryNowCommand, buildEmbedUrl, isPlayruoEmbedCommand, isPlayruoEmbedMessage };
136
+ 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
@@ -36,9 +36,7 @@ var PlayruoEmbed = ({
36
36
  const [isMobile, setIsMobile] = useState(false);
37
37
  const [isValidChromium, setIsValidChromium] = useState(true);
38
38
  const [errorMessage, setErrorMessage] = useState(null);
39
- const escapeKeyTimer = useRef(null);
40
- const escapeKeyPressed = useRef(false);
41
- const ESCAPE_HOLD_DURATION = 1500;
39
+ const [iframeState, setIframeState] = useState(null);
42
40
  const src = useMemo(() => buildEmbedUrl({ baseUrl, operation, storeUrl }), [baseUrl, operation, storeUrl]);
43
41
  useEffect(() => {
44
42
  if (typeof window === "undefined") return;
@@ -56,81 +54,34 @@ var PlayruoEmbed = ({
56
54
  }, []);
57
55
  const isCompatible = !isMobile && isValidChromium;
58
56
  const isReady = isCompatible ? isIframeLoaded : true;
59
- const currentState = useMemo(() => ({
60
- hasPlayedOnce,
61
- tryNow: {
62
- available: isCompatible,
63
- status: !isCompatible ? "Browser not compatible" : hasPlayedOnce ? "Click to replay" : "Ready"
64
- },
65
- buyNow: {
66
- available: true,
67
- status: "Ready"
68
- }
69
- }), [hasPlayedOnce, isCompatible]);
70
- const checkKeyboardLockPermission = useCallback(async () => {
71
- if (!navigator.permissions) {
72
- console.log("[PlayruoEmbed] Permissions API not supported");
73
- return false;
57
+ const currentState = useMemo(() => {
58
+ if (iframeState) {
59
+ return iframeState;
74
60
  }
75
- try {
76
- const { state } = await navigator.permissions.query({
77
- name: "keyboard-lock"
78
- });
79
- if (state === "granted" || state === "prompt") {
80
- console.log(`[PlayruoEmbed] Keyboard lock permission: ${state}`);
81
- return true;
61
+ return {
62
+ hasPlayedOnce,
63
+ tryNow: {
64
+ available: isCompatible,
65
+ status: !isCompatible ? "Browser not compatible" : hasPlayedOnce ? "Click to resume" : "Ready"
66
+ },
67
+ buyNow: {
68
+ available: true,
69
+ status: "Ready"
82
70
  }
83
- console.log("[PlayruoEmbed] Keyboard lock permission denied");
84
- return false;
85
- } catch (error) {
86
- console.warn("[PlayruoEmbed] Error checking keyboard lock permission:", error);
87
- return false;
88
- }
89
- }, []);
90
- const requestFullscreenWithKeyboardLock = useCallback(async () => {
91
- const iframe = iframeRef.current;
92
- if (!iframe?.requestFullscreen) {
93
- console.log("[PlayruoEmbed] Fullscreen not supported on this browser");
94
- return false;
95
- }
96
- try {
97
- const fullscreenPromise = iframe.requestFullscreen();
98
- if (navigator.keyboard && window.self === window.top) {
99
- try {
100
- const permissionPromise = checkKeyboardLockPermission();
101
- const keyboardLockPromise = navigator.keyboard.lock(["Escape"]);
102
- void Promise.all([permissionPromise, keyboardLockPromise]).then(([hasPermission]) => {
103
- if (hasPermission) {
104
- console.log("[PlayruoEmbed] Keyboard locked successfully");
105
- }
106
- }).catch((err) => {
107
- const isTopLevelContextError = err?.name === "InvalidStateError" && err?.message?.includes("primary top-level browsing context");
108
- if (!isTopLevelContextError) {
109
- console.warn("[PlayruoEmbed] Keyboard lock failed:", err);
110
- }
111
- });
112
- } catch (err) {
113
- const isTopLevelContextError = err?.name === "InvalidStateError" && err?.message?.includes("primary top-level browsing context");
114
- if (!isTopLevelContextError) {
115
- console.warn("[PlayruoEmbed] Keyboard lock error:", err);
116
- }
117
- }
118
- } else if (!navigator.keyboard) {
119
- console.log("[PlayruoEmbed] Keyboard API not supported");
120
- } else {
121
- console.log("[PlayruoEmbed] Keyboard lock not available (not in top-level context)");
71
+ };
72
+ }, [hasPlayedOnce, isCompatible, iframeState]);
73
+ const hasIframeConfirmed = Boolean(iframeState?.tryNow?.available === true);
74
+ const exposedState = useMemo(
75
+ () => ({
76
+ ...currentState,
77
+ tryNow: {
78
+ ...currentState.tryNow,
79
+ available: hasIframeConfirmed,
80
+ status: currentState.tryNow.status ?? (hasIframeConfirmed ? "Ready" : isCompatible ? "Checking availability\u2026" : "Browser not compatible")
122
81
  }
123
- void fullscreenPromise.then(() => {
124
- console.log("[PlayruoEmbed] Fullscreen activated successfully");
125
- }).catch((err) => {
126
- console.warn("[PlayruoEmbed] Fullscreen failed:", err);
127
- });
128
- return true;
129
- } catch (error) {
130
- console.error("[PlayruoEmbed] Error requesting fullscreen:", error);
131
- return false;
132
- }
133
- }, [iframeRef, checkKeyboardLockPermission]);
82
+ }),
83
+ [currentState, hasIframeConfirmed, isCompatible]
84
+ );
134
85
  const handleTryNow = useCallback(() => {
135
86
  if (!isCompatible) {
136
87
  console.warn("[PlayruoEmbed] Browser not compatible");
@@ -150,12 +101,10 @@ var PlayruoEmbed = ({
150
101
  } catch {
151
102
  }
152
103
  }
153
- requestFullscreenWithKeyboardLock();
154
104
  iframe.contentWindow?.postMessage({
155
105
  type: "playruo",
156
106
  action: "tryNowClick",
157
107
  fromEmbed: true,
158
- isFullscreen: true,
159
108
  isReplay: hasPlayedOnce
160
109
  }, "*");
161
110
  console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
@@ -169,7 +118,7 @@ var PlayruoEmbed = ({
169
118
  setErrorMessage("Failed to start game");
170
119
  onError?.("Failed to start game");
171
120
  }
172
- }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestFullscreenWithKeyboardLock]);
121
+ }, [isCompatible, hasPlayedOnce, onError, iframeRef]);
173
122
  const handleBuyNow = useCallback(() => {
174
123
  if (!storeUrl) {
175
124
  console.warn("[PlayruoEmbed] No store URL provided");
@@ -179,9 +128,9 @@ var PlayruoEmbed = ({
179
128
  }, [storeUrl, onPurchaseRequest]);
180
129
  useEffect(() => {
181
130
  if (isReady) {
182
- onStateChange?.(currentState);
131
+ onStateChange?.(exposedState);
183
132
  }
184
- }, [isReady, currentState, onStateChange]);
133
+ }, [isReady, exposedState, onStateChange]);
185
134
  useEffect(() => {
186
135
  if (isReady) {
187
136
  onReady?.();
@@ -189,18 +138,112 @@ var PlayruoEmbed = ({
189
138
  }, [isReady, onReady]);
190
139
  useEffect(() => {
191
140
  const handleGameMessage = (event) => {
192
- const { type } = event.data || {};
141
+ const { type, data } = event.data || {};
142
+ const iframe = iframeRef.current;
143
+ if (!iframe || event.source !== iframe.contentWindow) {
144
+ return;
145
+ }
146
+ console.log("\u{1F537} [Embed] Message received from iframe:", { type, data });
147
+ if (type === "playruo_ready") {
148
+ console.log("\u{1F537} [Embed] playruo_ready received");
149
+ onReady?.();
150
+ } else if (type === "playruo_state_update" && data) {
151
+ console.log("\u{1F537} [Embed] playruo_state_update received:", data);
152
+ setIframeState(data);
153
+ if (data.hasPlayedOnce !== void 0) {
154
+ setHasPlayedOnce(data.hasPlayedOnce);
155
+ }
156
+ onStateChange?.(data);
157
+ } else if (type === "playruo_purchase_request" && data?.storeUrl) {
158
+ console.log("\u{1F537} [Embed] playruo_purchase_request received:", data);
159
+ onPurchaseRequest?.(data);
160
+ } else if (type === "playruo_demo_completed" && data) {
161
+ console.log("\u{1F537} [Embed] playruo_demo_completed received:", data);
162
+ setHasPlayedOnce(true);
163
+ onDemoCompleted?.(data);
164
+ } else if (type === "playruo_error") {
165
+ const errorMessage2 = typeof data === "string" ? data : data?.message || "Unknown error";
166
+ console.log("\u{1F537} [Embed] playruo_error received:", errorMessage2);
167
+ setErrorMessage(errorMessage2);
168
+ const errorState = {
169
+ hasPlayedOnce,
170
+ tryNow: {
171
+ available: false,
172
+ status: `Error: ${errorMessage2}`
173
+ },
174
+ buyNow: {
175
+ available: true,
176
+ status: "Ready"
177
+ }
178
+ };
179
+ setIframeState(errorState);
180
+ onStateChange?.(errorState);
181
+ onError?.(errorMessage2);
182
+ } else if (type === "playruo_session_over" && data) {
183
+ console.log("\u{1F537} [Embed] playruo_session_over received:", data);
184
+ setHasPlayedOnce(true);
185
+ setIframeState(data);
186
+ onStateChange?.(data);
187
+ } else if (type === "playruo_stream_start") {
188
+ console.log("\u{1F537} [Embed] playruo_stream_start received");
189
+ }
193
190
  if (type === "error" || type === "closeForBuy" || type === "goToStore") {
191
+ console.log("\u{1F537} [Embed] Legacy message received:", type);
194
192
  setHasPlayedOnce(true);
195
- onDemoCompleted?.({
193
+ const payload = {
196
194
  reason: "Demo session ended",
197
195
  storeUrl
198
- });
196
+ };
197
+ onDemoCompleted?.(payload);
199
198
  }
200
199
  };
201
200
  window.addEventListener("message", handleGameMessage);
202
201
  return () => window.removeEventListener("message", handleGameMessage);
203
- }, [storeUrl, onDemoCompleted]);
202
+ }, [storeUrl, onDemoCompleted, onReady, onStateChange, onPurchaseRequest, onError, iframeRef]);
203
+ const generateUUID = () => {
204
+ if (self.crypto.randomUUID) return self.crypto.randomUUID();
205
+ const bytes = new Uint8Array(16);
206
+ self.crypto.getRandomValues(bytes);
207
+ bytes[6] = bytes[6] & 15 | 64;
208
+ bytes[8] = bytes[8] & 63 | 128;
209
+ return [...bytes].map(
210
+ (b, i) => ([4, 6, 8, 10].includes(i) ? "-" : "") + b.toString(16).padStart(2, "0")
211
+ ).join("");
212
+ };
213
+ useEffect(() => {
214
+ const handleUUIDMessage = (event) => {
215
+ const iframe = iframeRef.current;
216
+ if (!iframe || event.source !== iframe.contentWindow) {
217
+ return;
218
+ }
219
+ const { type, requestId } = event.data || {};
220
+ if (type === "playruo_uuid_request") {
221
+ try {
222
+ let storedUUID = localStorage.getItem("playruo_uuid");
223
+ if (!storedUUID) {
224
+ storedUUID = generateUUID();
225
+ localStorage.setItem("playruo_uuid", storedUUID);
226
+ }
227
+ iframe.contentWindow?.postMessage(
228
+ {
229
+ type: "playruo_uuid_response",
230
+ requestId,
231
+ uuid: storedUUID
232
+ },
233
+ "*"
234
+ );
235
+ } catch (e) {
236
+ }
237
+ } else if (type === "playruo_uuid_remove") {
238
+ try {
239
+ localStorage.removeItem("playruo_uuid");
240
+ } catch (e) {
241
+ }
242
+ }
243
+ };
244
+ window.addEventListener("message", handleUUIDMessage);
245
+ return () => window.removeEventListener("message", handleUUIDMessage);
246
+ }, [iframeRef]);
204
247
  useEffect(() => {
205
248
  const handleFullscreenChange = () => {
206
249
  const doc = document;
@@ -214,10 +257,17 @@ var PlayruoEmbed = ({
214
257
  }, 200);
215
258
  }
216
259
  } else {
217
- escapeKeyPressed.current = false;
218
- if (escapeKeyTimer.current) {
219
- clearTimeout(escapeKeyTimer.current);
220
- escapeKeyTimer.current = null;
260
+ const iframe = iframeRef.current;
261
+ if (iframe) {
262
+ try {
263
+ iframe.contentWindow?.postMessage({
264
+ type: "playruo",
265
+ action: "exitFullscreen",
266
+ fromEmbed: true
267
+ }, "*");
268
+ } catch {
269
+ console.error("[PlayruoEmbed] Failed to exit fullscreen");
270
+ }
221
271
  }
222
272
  setShowGameIframe(false);
223
273
  setHasPlayedOnce(true);
@@ -238,56 +288,6 @@ var PlayruoEmbed = ({
238
288
  document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
239
289
  };
240
290
  }, [storeUrl, onDemoCompleted, iframeRef]);
241
- useEffect(() => {
242
- const handleKeyDown = (event) => {
243
- if (event.key === "Escape" && !escapeKeyPressed.current) {
244
- escapeKeyPressed.current = true;
245
- event.preventDefault();
246
- escapeKeyTimer.current = setTimeout(() => {
247
- if (document.fullscreenElement) {
248
- document.exitFullscreen();
249
- }
250
- }, ESCAPE_HOLD_DURATION);
251
- }
252
- if (event.ctrlKey && event.shiftKey && event.key === "R") {
253
- event.preventDefault();
254
- if (document.fullscreenElement) {
255
- document.exitFullscreen().catch(console.warn);
256
- }
257
- setTimeout(() => {
258
- window.location.reload();
259
- }, 500);
260
- }
261
- };
262
- const handleKeyUp = (event) => {
263
- if (event.key === "Escape" && escapeKeyPressed.current) {
264
- escapeKeyPressed.current = false;
265
- if (escapeKeyTimer.current) {
266
- clearTimeout(escapeKeyTimer.current);
267
- escapeKeyTimer.current = null;
268
- }
269
- }
270
- };
271
- document.addEventListener("keydown", handleKeyDown);
272
- document.addEventListener("keyup", handleKeyUp);
273
- return () => {
274
- document.removeEventListener("keydown", handleKeyDown);
275
- document.removeEventListener("keyup", handleKeyUp);
276
- };
277
- }, []);
278
- useEffect(() => {
279
- const isVisible = showGameIframe || showIframe;
280
- const iframe = iframeRef.current;
281
- if (!iframe || !isIframeLoaded) return;
282
- try {
283
- iframe.contentWindow?.postMessage({
284
- type: "playruo",
285
- action: isVisible ? "unmute" : "mute",
286
- fromEmbed: true
287
- }, "*");
288
- } catch {
289
- }
290
- }, [showGameIframe, showIframe, isIframeLoaded, iframeRef]);
291
291
  useEffect(() => {
292
292
  const logCrossOriginStatus = () => {
293
293
  const isIsolated = self.crossOriginIsolated;
@@ -366,22 +366,28 @@ var PlayruoEmbed = ({
366
366
  logCrossOriginStatus();
367
367
  }
368
368
  }, [isReady]);
369
- const isAvailable = Boolean(isReady && isCompatible);
369
+ const isAvailable = Boolean(isReady && isCompatible && hasIframeConfirmed);
370
370
  const renderContent = () => {
371
371
  if (!renderState) return null;
372
+ const canTryNow = Boolean(
373
+ !errorMessage && isReady && isCompatible && hasIframeConfirmed
374
+ );
372
375
  const actions = {
373
376
  tryNow: handleTryNow,
374
377
  buyNow: handleBuyNow,
375
- canTryNow: Boolean(isReady && isCompatible),
378
+ canTryNow,
376
379
  canBuyNow: Boolean(isReady)
377
380
  };
378
381
  if (errorMessage) {
379
382
  return renderState({ status: "error", message: errorMessage, actions });
380
383
  }
381
384
  if (!isReady) {
382
- return renderState({ status: "loading", actions });
385
+ return renderState({ status: "loading", message: "Loading\u2026", actions });
386
+ }
387
+ if (isCompatible && !iframeState) {
388
+ return renderState({ status: "loading", message: "Checking availability\u2026", actions });
383
389
  }
384
- const message = !isAvailable ? currentState.tryNow.status : void 0;
390
+ const message = !isAvailable ? currentState.tryNow.status ?? exposedState.tryNow.status : void 0;
385
391
  return renderState({
386
392
  status: isAvailable ? "available" : "unavailable",
387
393
  message,
@@ -396,7 +402,6 @@ var PlayruoEmbed = ({
396
402
  ref: iframeRef,
397
403
  src,
398
404
  allow: "cross-origin-isolated; fullscreen *; autoplay *; gamepad *; clipboard-write *; web-share *; encrypted-media *; accelerometer *; gyroscope *",
399
- allowFullScreen: true,
400
405
  className: "w-full h-[70vh] rounded-lg border",
401
406
  ...{ credentialless: "true" },
402
407
  sandbox: "allow-same-origin allow-scripts allow-popups allow-forms allow-presentation allow-top-navigation allow-modals allow-pointer-lock",
@@ -615,6 +620,10 @@ var EmbedTester = (props = {}) => {
615
620
  /* @__PURE__ */ jsx2("span", { style: embedState.hasPlayedOnce ? successStatus : errorStatus, children: embedState.hasPlayedOnce ? "\u2705 Played" : "\u274C Not Played" }),
616
621
  /* @__PURE__ */ jsx2("span", { style: embedState.tryNow?.available ? successStatus : errorStatus, children: embedState.tryNow?.available ? "\u2705 Try Now" : "\u274C Try Now" }),
617
622
  /* @__PURE__ */ jsx2("span", { style: embedState.buyNow?.available ? successStatus : errorStatus, children: embedState.buyNow?.available ? "\u2705 Buy Now" : "\u274C Buy Now" })
623
+ ] }),
624
+ !embedState.tryNow?.available && embedState.tryNow?.status && /* @__PURE__ */ jsxs2("p", { style: { margin: "8px 0 0 0", fontSize: "12px", color: "#a3a3a3" }, children: [
625
+ "Try Now reason: ",
626
+ embedState.tryNow.status
618
627
  ] })
619
628
  ] }),
620
629
  /* @__PURE__ */ jsxs2("div", { style: cardStyle, children: [
@@ -644,7 +653,8 @@ var EmbedTester = (props = {}) => {
644
653
  backgroundColor: "#0a0a0a",
645
654
  padding: "12px",
646
655
  borderRadius: "4px",
647
- height: "200px",
656
+ minHeight: "200px",
657
+ height: "min(50vh, 480px)",
648
658
  overflowY: "auto",
649
659
  fontFamily: "monospace",
650
660
  fontSize: "12px",
@@ -789,18 +799,17 @@ var isPlayruoEmbedMessage = (data) => {
789
799
  if (!data || typeof data !== "object") return false;
790
800
  const msg = data;
791
801
  if (typeof msg.type !== "string") return false;
792
- return msg.type === "playruo_ready" || msg.type === "playruo_state_update" || msg.type === "playruo_purchase_request" || msg.type === "playruo_demo_completed" || msg.type === "playruo_error";
802
+ return msg.type === "playruo_ready" || msg.type === "playruo_state_update" || msg.type === "playruo_purchase_request" || msg.type === "playruo_demo_completed" || msg.type === "playruo_error" || msg.type === "playruo_session_over" || msg.type === "playruo_stream_start" || msg.type === "playruo_stream_stop";
793
803
  };
794
- var isPlayruoEmbedCommand = (data) => {
804
+ var isPlayruoInternalMessage = (data) => {
795
805
  if (!data || typeof data !== "object") return false;
796
- const cmd = data;
797
- if (typeof cmd.type !== "string") return false;
798
- return cmd.type === "try_now" || cmd.type === "buy_now" || cmd.type === "get_state";
806
+ const msg = data;
807
+ return msg.type === "playruo" && typeof msg.action === "string" && (msg.action === "tryNowClick" || msg.action === "exitFullscreen" || msg.action === "mute" || msg.action === "unmute") && msg.fromEmbed === true;
799
808
  };
800
809
  export {
801
810
  EmbedTester,
802
811
  PlayruoEmbed,
803
812
  buildEmbedUrl,
804
- isPlayruoEmbedCommand,
805
- isPlayruoEmbedMessage
813
+ isPlayruoEmbedMessage,
814
+ isPlayruoInternalMessage
806
815
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playruo/embed",
3
- "version": "1.2.2",
3
+ "version": "1.2.5",
4
4
  "description": "React SDK to embed Playruo with postMessage API.",
5
5
  "license": "MIT",
6
6
  "type": "module",