@playruo/embed 1.2.1 → 1.2.4

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.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,69 +54,22 @@ 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 = async () => {
71
- if (!navigator.permissions) {
72
- console.log("[PlayruoEmbed] Permissions API not supported");
73
- return false;
74
- }
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;
82
- }
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 = async () => {
91
- const iframe = iframeRef.current;
92
- if (!iframe?.requestFullscreen) {
93
- console.log("[PlayruoEmbed] Fullscreen not supported on this browser");
94
- return false;
57
+ const currentState = useMemo(() => {
58
+ if (iframeState) {
59
+ return iframeState;
95
60
  }
96
- try {
97
- const fullscreenPromise = iframe.requestFullscreen();
98
- if (navigator.keyboard) {
99
- const permissionPromise = checkKeyboardLockPermission();
100
- const keyboardLockPromise = navigator.keyboard.lock(["Escape"]);
101
- void Promise.all([permissionPromise, keyboardLockPromise]).then(([hasPermission]) => {
102
- if (hasPermission) {
103
- console.log("[PlayruoEmbed] Keyboard locked successfully");
104
- }
105
- }).catch((err) => {
106
- console.warn("[PlayruoEmbed] Keyboard lock failed:", err);
107
- });
108
- } else {
109
- console.log("[PlayruoEmbed] Keyboard API not supported");
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"
110
70
  }
111
- void fullscreenPromise.then(() => {
112
- console.log("[PlayruoEmbed] Fullscreen activated successfully");
113
- }).catch((err) => {
114
- console.warn("[PlayruoEmbed] Fullscreen failed:", err);
115
- });
116
- return true;
117
- } catch (error) {
118
- console.error("[PlayruoEmbed] Error requesting fullscreen:", error);
119
- return false;
120
- }
121
- };
71
+ };
72
+ }, [hasPlayedOnce, isCompatible, iframeState]);
122
73
  const handleTryNow = useCallback(() => {
123
74
  if (!isCompatible) {
124
75
  console.warn("[PlayruoEmbed] Browser not compatible");
@@ -131,12 +82,17 @@ var PlayruoEmbed = ({
131
82
  }
132
83
  setShowGameIframe(true);
133
84
  try {
134
- requestFullscreenWithKeyboardLock();
85
+ const el = iframeRef.current;
86
+ if (el) {
87
+ try {
88
+ el.focus();
89
+ } catch {
90
+ }
91
+ }
135
92
  iframe.contentWindow?.postMessage({
136
93
  type: "playruo",
137
94
  action: "tryNowClick",
138
95
  fromEmbed: true,
139
- isFullscreen: true,
140
96
  isReplay: hasPlayedOnce
141
97
  }, "*");
142
98
  console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
@@ -170,18 +126,112 @@ var PlayruoEmbed = ({
170
126
  }, [isReady, onReady]);
171
127
  useEffect(() => {
172
128
  const handleGameMessage = (event) => {
173
- const { type } = event.data || {};
129
+ const { type, data } = event.data || {};
130
+ const iframe = iframeRef.current;
131
+ if (!iframe || event.source !== iframe.contentWindow) {
132
+ return;
133
+ }
134
+ console.log("\u{1F537} [Embed] Message received from iframe:", { type, data });
135
+ if (type === "playruo_ready") {
136
+ console.log("\u{1F537} [Embed] playruo_ready received");
137
+ onReady?.();
138
+ } else if (type === "playruo_state_update" && data) {
139
+ console.log("\u{1F537} [Embed] playruo_state_update received:", data);
140
+ setIframeState(data);
141
+ if (data.hasPlayedOnce !== void 0) {
142
+ setHasPlayedOnce(data.hasPlayedOnce);
143
+ }
144
+ onStateChange?.(data);
145
+ } else if (type === "playruo_purchase_request" && data?.storeUrl) {
146
+ console.log("\u{1F537} [Embed] playruo_purchase_request received:", data);
147
+ onPurchaseRequest?.(data);
148
+ } else if (type === "playruo_demo_completed" && data) {
149
+ console.log("\u{1F537} [Embed] playruo_demo_completed received:", data);
150
+ setHasPlayedOnce(true);
151
+ onDemoCompleted?.(data);
152
+ } else if (type === "playruo_error") {
153
+ const errorMessage2 = typeof data === "string" ? data : data?.message || "Unknown error";
154
+ console.log("\u{1F537} [Embed] playruo_error received:", errorMessage2);
155
+ setErrorMessage(errorMessage2);
156
+ const errorState = {
157
+ hasPlayedOnce,
158
+ tryNow: {
159
+ available: false,
160
+ status: `Error: ${errorMessage2}`
161
+ },
162
+ buyNow: {
163
+ available: true,
164
+ status: "Ready"
165
+ }
166
+ };
167
+ setIframeState(errorState);
168
+ onStateChange?.(errorState);
169
+ onError?.(errorMessage2);
170
+ } else if (type === "playruo_session_over" && data) {
171
+ console.log("\u{1F537} [Embed] playruo_session_over received:", data);
172
+ setHasPlayedOnce(true);
173
+ setIframeState(data);
174
+ onStateChange?.(data);
175
+ } else if (type === "playruo_stream_start") {
176
+ console.log("\u{1F537} [Embed] playruo_stream_start received");
177
+ }
174
178
  if (type === "error" || type === "closeForBuy" || type === "goToStore") {
179
+ console.log("\u{1F537} [Embed] Legacy message received:", type);
175
180
  setHasPlayedOnce(true);
176
- onDemoCompleted?.({
181
+ const payload = {
177
182
  reason: "Demo session ended",
178
183
  storeUrl
179
- });
184
+ };
185
+ onDemoCompleted?.(payload);
180
186
  }
181
187
  };
182
188
  window.addEventListener("message", handleGameMessage);
183
189
  return () => window.removeEventListener("message", handleGameMessage);
184
- }, [storeUrl, onDemoCompleted]);
190
+ }, [storeUrl, onDemoCompleted, onReady, onStateChange, onPurchaseRequest, onError, iframeRef]);
191
+ const generateUUID = () => {
192
+ if (self.crypto.randomUUID) return self.crypto.randomUUID();
193
+ const bytes = new Uint8Array(16);
194
+ self.crypto.getRandomValues(bytes);
195
+ bytes[6] = bytes[6] & 15 | 64;
196
+ bytes[8] = bytes[8] & 63 | 128;
197
+ return [...bytes].map(
198
+ (b, i) => ([4, 6, 8, 10].includes(i) ? "-" : "") + b.toString(16).padStart(2, "0")
199
+ ).join("");
200
+ };
201
+ useEffect(() => {
202
+ const handleUUIDMessage = (event) => {
203
+ const iframe = iframeRef.current;
204
+ if (!iframe || event.source !== iframe.contentWindow) {
205
+ return;
206
+ }
207
+ const { type, requestId } = event.data || {};
208
+ if (type === "playruo_uuid_request") {
209
+ try {
210
+ let storedUUID = localStorage.getItem("playruo_uuid");
211
+ if (!storedUUID) {
212
+ storedUUID = generateUUID();
213
+ localStorage.setItem("playruo_uuid", storedUUID);
214
+ }
215
+ iframe.contentWindow?.postMessage(
216
+ {
217
+ type: "playruo_uuid_response",
218
+ requestId,
219
+ uuid: storedUUID
220
+ },
221
+ "*"
222
+ );
223
+ } catch (e) {
224
+ }
225
+ } else if (type === "playruo_uuid_remove") {
226
+ try {
227
+ localStorage.removeItem("playruo_uuid");
228
+ } catch (e) {
229
+ }
230
+ }
231
+ };
232
+ window.addEventListener("message", handleUUIDMessage);
233
+ return () => window.removeEventListener("message", handleUUIDMessage);
234
+ }, [iframeRef]);
185
235
  useEffect(() => {
186
236
  const handleFullscreenChange = () => {
187
237
  const doc = document;
@@ -195,11 +245,19 @@ var PlayruoEmbed = ({
195
245
  }, 200);
196
246
  }
197
247
  } else {
198
- escapeKeyPressed.current = false;
199
- if (escapeKeyTimer.current) {
200
- clearTimeout(escapeKeyTimer.current);
201
- escapeKeyTimer.current = null;
248
+ const iframe = iframeRef.current;
249
+ if (iframe) {
250
+ try {
251
+ iframe.contentWindow?.postMessage({
252
+ type: "playruo",
253
+ action: "exitFullscreen",
254
+ fromEmbed: true
255
+ }, "*");
256
+ } catch {
257
+ console.error("[PlayruoEmbed] Failed to exit fullscreen");
258
+ }
202
259
  }
260
+ setShowGameIframe(false);
203
261
  setHasPlayedOnce(true);
204
262
  onDemoCompleted?.({
205
263
  reason: "Demo session ended",
@@ -219,49 +277,83 @@ var PlayruoEmbed = ({
219
277
  };
220
278
  }, [storeUrl, onDemoCompleted, iframeRef]);
221
279
  useEffect(() => {
222
- const handleKeyDown = (event) => {
223
- if (event.key === "Escape" && !escapeKeyPressed.current) {
224
- escapeKeyPressed.current = true;
225
- event.preventDefault();
226
- escapeKeyTimer.current = setTimeout(() => {
227
- if (document.fullscreenElement) {
228
- document.exitFullscreen();
229
- }
230
- }, ESCAPE_HOLD_DURATION);
280
+ const logCrossOriginStatus = () => {
281
+ const isIsolated = self.crossOriginIsolated;
282
+ const hasSharedArrayBuffer = typeof SharedArrayBuffer !== "undefined";
283
+ const hasHighResTime = typeof performance !== "undefined" && typeof performance.now === "function";
284
+ console.group("[PlayruoEmbed] \u{1F512} Cross-Origin Isolation Status");
285
+ if (isIsolated) {
286
+ console.log("\u2705 Cross-origin isolation: ENABLED");
287
+ console.log("\u2705 SharedArrayBuffer support:", hasSharedArrayBuffer ? "Available" : "Not available");
288
+ console.log("\u2705 High-resolution timers:", hasHighResTime ? "Available" : "Not available");
289
+ console.log("\u{1F3AE} Game performance: Optimal");
290
+ } else {
291
+ console.warn("\u26A0\uFE0F Cross-origin isolation: DISABLED");
292
+ console.warn("\u26A0\uFE0F SharedArrayBuffer support:", hasSharedArrayBuffer ? "Available" : "Not available");
293
+ console.warn("\u26A0\uFE0F High-resolution timers:", hasHighResTime ? "Available" : "Not available");
294
+ console.warn("\u{1F3AE} Game performance: May be degraded");
295
+ console.group("\u{1F4CB} Setup Instructions:");
296
+ console.log("Add these headers to your server:");
297
+ console.log(" Cross-Origin-Opener-Policy: same-origin");
298
+ console.log(" Cross-Origin-Embedder-Policy: credentialless");
299
+ console.groupEnd();
300
+ console.group("\u{1F527} Framework-specific setup:");
301
+ console.log("Next.js (next.config.js):");
302
+ console.log(`const nextConfig = {
303
+ async headers() {
304
+ return [
305
+ {
306
+ source: '/:path*',
307
+ headers: [
308
+ { key: 'Cross-Origin-Opener-Policy', value: 'same-origin' },
309
+ { key: 'Cross-Origin-Embedder-Policy', value: 'credentialless' }
310
+ ]
231
311
  }
232
- if (event.ctrlKey && event.shiftKey && event.key === "R") {
233
- event.preventDefault();
234
- if (document.fullscreenElement) {
235
- document.exitFullscreen().catch(console.warn);
236
- }
237
- setTimeout(() => {
238
- window.location.reload();
239
- }, 500);
312
+ ];
313
+ }
314
+ };`);
315
+ console.log("Vite (vite.config.ts):");
316
+ console.log(`export default defineConfig({
317
+ server: {
318
+ headers: {
319
+ 'Cross-Origin-Opener-Policy': 'same-origin',
320
+ 'Cross-Origin-Embedder-Policy': 'credentialless'
321
+ }
322
+ }
323
+ });`);
324
+ console.log("Express.js:");
325
+ console.log(`app.use((req, res, next) => {
326
+ res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
327
+ res.setHeader('Cross-Origin-Embedder-Policy', 'credentialless');
328
+ next();
329
+ });`);
330
+ console.groupEnd();
331
+ console.group("\u{1F50D} Debug Information:");
332
+ console.log("Current origin:", window.location.origin);
333
+ console.log("Protocol:", window.location.protocol);
334
+ console.log("User agent:", navigator.userAgent);
335
+ console.log("Available APIs:", {
336
+ SharedArrayBuffer: hasSharedArrayBuffer,
337
+ Performance: hasHighResTime,
338
+ WebGL: !!document.createElement("canvas").getContext("webgl"),
339
+ WebGL2: !!document.createElement("canvas").getContext("webgl2"),
340
+ WebAssembly: typeof WebAssembly !== "undefined"
341
+ });
342
+ console.groupEnd();
240
343
  }
241
- };
242
- const handleKeyUp = (event) => {
243
- if (event.key === "Escape" && escapeKeyPressed.current) {
244
- escapeKeyPressed.current = false;
245
- if (escapeKeyTimer.current) {
246
- clearTimeout(escapeKeyTimer.current);
247
- escapeKeyTimer.current = null;
248
- }
344
+ console.groupEnd();
345
+ if (isIsolated) {
346
+ console.log("[PlayruoEmbed] \u{1F680} All performance features available - optimal gaming experience!");
347
+ } else {
348
+ console.warn("[PlayruoEmbed] \u26A0\uFE0F Some features may not work optimally without cross-origin isolation");
249
349
  }
250
350
  };
251
- document.addEventListener("keydown", handleKeyDown);
252
- document.addEventListener("keyup", handleKeyUp);
253
- return () => {
254
- document.removeEventListener("keydown", handleKeyDown);
255
- document.removeEventListener("keyup", handleKeyUp);
256
- };
257
- }, []);
258
- useEffect(() => {
259
- if (!self.crossOriginIsolated) {
260
- console.warn(
261
- '[PlayruoEmbed] \u26A0\uFE0F Not in a cross-origin isolated context.\nSharedArrayBuffer and high-precision timers may not work.\n\nAdd these headers to your server:\n Cross-Origin-Opener-Policy: same-origin\n Cross-Origin-Embedder-Policy: credentialless\n\nFor Next.js, add to next.config.js:\n async headers() {\n return [{\n source: "/:path*",\n headers: [\n { key: "Cross-Origin-Opener-Policy", value: "same-origin" },\n { key: "Cross-Origin-Embedder-Policy", value: "credentialless" }\n ]\n }];\n }'
262
- );
351
+ logCrossOriginStatus();
352
+ if (isReady) {
353
+ console.log("[PlayruoEmbed] \u{1F4CA} Final status check:");
354
+ logCrossOriginStatus();
263
355
  }
264
- }, []);
356
+ }, [isReady]);
265
357
  const isAvailable = Boolean(isReady && isCompatible);
266
358
  const renderContent = () => {
267
359
  if (!renderState) return null;
@@ -292,20 +384,18 @@ var PlayruoEmbed = ({
292
384
  ref: iframeRef,
293
385
  src,
294
386
  allow: "cross-origin-isolated; fullscreen *; autoplay *; gamepad *; clipboard-write *; web-share *; encrypted-media *; accelerometer *; gyroscope *",
295
- allowFullScreen: true,
296
387
  className: "w-full h-[70vh] rounded-lg border",
297
388
  ...{ credentialless: "true" },
298
389
  sandbox: "allow-same-origin allow-scripts allow-popups allow-forms allow-presentation allow-top-navigation allow-modals allow-pointer-lock",
299
390
  title: "Playruo",
300
391
  referrerPolicy: "strict-origin-when-cross-origin",
301
392
  style: {
302
- position: showIframe ? void 0 : "absolute",
303
- top: showIframe ? void 0 : -9999,
304
- left: showIframe ? void 0 : -9999,
305
- zIndex: showIframe ? void 0 : -10,
306
- opacity: showIframe && showGameIframe ? 1 : 0,
307
- visibility: showIframe && showGameIframe ? "visible" : "hidden",
308
- pointerEvents: showIframe ? "auto" : "none"
393
+ // Only mounted when compatible; visible after click (internal) or if parent explicitly asks
394
+ opacity: showGameIframe || showIframe ? 1 : 0,
395
+ pointerEvents: showGameIframe || showIframe ? "auto" : "none",
396
+ width: "100%",
397
+ height: "100%",
398
+ border: 0
309
399
  },
310
400
  onLoad: () => {
311
401
  setIsIframeLoaded(true);
@@ -326,22 +416,377 @@ var PlayruoEmbed = ({
326
416
  ] });
327
417
  };
328
418
 
419
+ // src/react/embed-tester.tsx
420
+ import React, { useCallback as useCallback2, useRef as useRef2, useState as useState2 } from "react";
421
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
422
+ var EmbedTester = (props = {}) => {
423
+ const [operation, setOperation] = useState2(props.operation ?? "iframe");
424
+ const [storeUrl, setStoreUrl] = useState2(
425
+ props.storeUrl ?? "https://store.steampowered.com/app/123456"
426
+ );
427
+ const [key, setKey] = useState2(0);
428
+ const [hasUserClicked, setHasUserClicked] = useState2(false);
429
+ const [showIframe, setShowIframe] = useState2(false);
430
+ const [embedState, setEmbedState] = useState2({
431
+ buyNow: { available: false, status: "Not ready" },
432
+ hasPlayedOnce: false,
433
+ isReady: false,
434
+ tryNow: { available: false, status: "Not ready" }
435
+ });
436
+ const [eventLog, setEventLog] = useState2([]);
437
+ const [diagnostics, setDiagnostics] = useState2({
438
+ crossOriginIsolation: false,
439
+ gamepadSupported: false,
440
+ fullscreenSupported: false,
441
+ keyboardLockSupported: false
442
+ });
443
+ const baseUrl = props.baseUrl;
444
+ const enableConsoleLog = props.enableConsoleLog ?? false;
445
+ const hasReadyBeenCalled = useRef2(false);
446
+ const previousStateRef = useRef2("");
447
+ const iframeRef = useRef2(null);
448
+ const addEvent = useCallback2(
449
+ (message) => {
450
+ const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
451
+ setEventLog((prev) => [...prev.slice(-10), { message, timestamp }]);
452
+ if (enableConsoleLog) {
453
+ console.log(`[EmbedTester] ${timestamp}: ${message}`);
454
+ }
455
+ },
456
+ [enableConsoleLog]
457
+ );
458
+ const runDiagnostics = useCallback2(() => {
459
+ const newDiagnostics = {
460
+ crossOriginIsolation: !!(window.crossOriginIsolated || window.crossOriginIsolated),
461
+ gamepadSupported: "getGamepads" in navigator,
462
+ fullscreenSupported: !!(document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled),
463
+ keyboardLockSupported: "keyboard" in navigator && "lock" in navigator.keyboard
464
+ };
465
+ setDiagnostics(newDiagnostics);
466
+ addEvent(`\u{1F50D} Diagnostics updated - COI: ${newDiagnostics.crossOriginIsolation}, Gamepad: ${newDiagnostics.gamepadSupported}`);
467
+ }, [addEvent]);
468
+ React.useEffect(() => {
469
+ runDiagnostics();
470
+ }, [runDiagnostics]);
471
+ const handleReady = useCallback2(() => {
472
+ if (!hasReadyBeenCalled.current) {
473
+ hasReadyBeenCalled.current = true;
474
+ addEvent("Embed ready");
475
+ setEmbedState((prev) => ({ ...prev, isReady: true }));
476
+ }
477
+ }, [addEvent]);
478
+ const handleStateChange = useCallback2((state) => {
479
+ const stateString = JSON.stringify(state);
480
+ if (stateString !== previousStateRef.current) {
481
+ previousStateRef.current = stateString;
482
+ setEmbedState(state);
483
+ addEvent(`State changed: ${stateString}`);
484
+ }
485
+ }, [addEvent]);
486
+ const handleDemoCompleted = useCallback2((data) => {
487
+ const payload = data;
488
+ addEvent(`\u{1F3AF} Demo completed! Reason: ${payload.reason}`);
489
+ addEvent(`Store URL available for purchase: ${payload.storeUrl}`);
490
+ setShowIframe(false);
491
+ setHasUserClicked(false);
492
+ }, [addEvent]);
493
+ const handleError = useCallback2((error) => {
494
+ addEvent(`Error: ${error}`);
495
+ if (enableConsoleLog) {
496
+ console.error("Embed error:", error);
497
+ }
498
+ }, [addEvent, enableConsoleLog]);
499
+ const handlePurchaseRequest = useCallback2((data) => {
500
+ addEvent(`\u{1F4B3} Explicit purchase request from Buy Now button`);
501
+ addEvent(`Opening store: ${data.storeUrl}`);
502
+ window.open(data.storeUrl, "_blank");
503
+ }, [addEvent]);
504
+ const handleRestart = useCallback2(() => {
505
+ setKey((prev) => prev + 1);
506
+ setHasUserClicked(false);
507
+ setShowIframe(false);
508
+ hasReadyBeenCalled.current = false;
509
+ previousStateRef.current = "";
510
+ addEvent("Embed restarted");
511
+ }, [addEvent]);
512
+ const buttonStyle = {
513
+ padding: "8px 16px",
514
+ margin: "4px",
515
+ border: "1px solid #404040",
516
+ borderRadius: "4px",
517
+ backgroundColor: "#2a2a2a",
518
+ color: "#e5e5e5",
519
+ cursor: "pointer",
520
+ fontSize: "14px",
521
+ transition: "all 0.2s ease"
522
+ };
523
+ const inputStyle = {
524
+ width: "100%",
525
+ padding: "8px",
526
+ margin: "4px 0",
527
+ border: "1px solid #404040",
528
+ borderRadius: "4px",
529
+ backgroundColor: "#1a1a1a",
530
+ color: "#e5e5e5",
531
+ fontSize: "14px"
532
+ };
533
+ const cardStyle = {
534
+ border: "1px solid #333333",
535
+ borderRadius: "8px",
536
+ padding: "16px",
537
+ margin: "8px 0",
538
+ backgroundColor: "#1a1a1a"
539
+ };
540
+ const statusStyle = {
541
+ display: "inline-block",
542
+ padding: "4px 8px",
543
+ margin: "2px",
544
+ borderRadius: "12px",
545
+ fontSize: "12px",
546
+ fontWeight: "500"
547
+ };
548
+ const successStatus = { ...statusStyle, backgroundColor: "#0d5d1f", color: "#4ade80" };
549
+ const errorStatus = { ...statusStyle, backgroundColor: "#5d1a1a", color: "#f87171" };
550
+ return /* @__PURE__ */ jsx2("div", { className: props.className, style: { minHeight: "100vh", padding: "20px", backgroundColor: "#000000", color: "#e5e5e5" }, children: /* @__PURE__ */ jsxs2("div", { style: { maxWidth: "1200px", margin: "0 auto" }, children: [
551
+ /* @__PURE__ */ jsx2("h1", { style: { marginBottom: "20px", fontSize: "24px", fontWeight: "bold", color: "#ffffff" }, children: "\u{1F9EA} Playruo Embed Tester" }),
552
+ /* @__PURE__ */ jsxs2("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "20px" }, children: [
553
+ /* @__PURE__ */ jsxs2("div", { children: [
554
+ /* @__PURE__ */ jsxs2("div", { style: cardStyle, children: [
555
+ /* @__PURE__ */ jsx2("h3", { style: { margin: "0 0 16px 0", fontSize: "18px", fontWeight: "600", color: "#ffffff" }, children: "Test Controls" }),
556
+ /* @__PURE__ */ jsxs2("div", { style: { marginBottom: "12px" }, children: [
557
+ /* @__PURE__ */ jsx2("label", { htmlFor: "operation", style: { display: "block", marginBottom: "4px", fontWeight: "500", color: "#d4d4d4" }, children: "Operation" }),
558
+ /* @__PURE__ */ jsx2(
559
+ "input",
560
+ {
561
+ style: inputStyle,
562
+ onChange: (e) => setOperation(e.target.value),
563
+ placeholder: "e.g., iframe",
564
+ value: operation
565
+ }
566
+ )
567
+ ] }),
568
+ /* @__PURE__ */ jsxs2("div", { style: { marginBottom: "12px" }, children: [
569
+ /* @__PURE__ */ jsx2("label", { htmlFor: "storeUrl", style: { display: "block", marginBottom: "4px", fontWeight: "500", color: "#d4d4d4" }, children: "Store URL" }),
570
+ /* @__PURE__ */ jsx2(
571
+ "input",
572
+ {
573
+ style: inputStyle,
574
+ onChange: (e) => setStoreUrl(e.target.value),
575
+ placeholder: "https://store.example.com",
576
+ value: storeUrl
577
+ }
578
+ )
579
+ ] }),
580
+ /* @__PURE__ */ jsx2(
581
+ "button",
582
+ {
583
+ type: "button",
584
+ style: {
585
+ ...buttonStyle,
586
+ backgroundColor: "#0066cc",
587
+ color: "#ffffff",
588
+ border: "none",
589
+ fontWeight: "600"
590
+ },
591
+ onClick: handleRestart,
592
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "#0052a3",
593
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "#0066cc",
594
+ children: "\u{1F504} Restart Embed"
595
+ }
596
+ )
597
+ ] }),
598
+ /* @__PURE__ */ jsxs2("div", { style: cardStyle, children: [
599
+ /* @__PURE__ */ jsx2("h3", { style: { margin: "0 0 16px 0", fontSize: "18px", fontWeight: "600", color: "#ffffff" }, children: "Status" }),
600
+ /* @__PURE__ */ jsxs2("div", { children: [
601
+ /* @__PURE__ */ jsx2("span", { style: embedState.isReady ? successStatus : errorStatus, children: embedState.isReady ? "\u2705 Ready" : "\u274C Not Ready" }),
602
+ /* @__PURE__ */ jsx2("span", { style: embedState.hasPlayedOnce ? successStatus : errorStatus, children: embedState.hasPlayedOnce ? "\u2705 Played" : "\u274C Not Played" }),
603
+ /* @__PURE__ */ jsx2("span", { style: embedState.tryNow?.available ? successStatus : errorStatus, children: embedState.tryNow?.available ? "\u2705 Try Now" : "\u274C Try Now" }),
604
+ /* @__PURE__ */ jsx2("span", { style: embedState.buyNow?.available ? successStatus : errorStatus, children: embedState.buyNow?.available ? "\u2705 Buy Now" : "\u274C Buy Now" })
605
+ ] })
606
+ ] }),
607
+ /* @__PURE__ */ jsxs2("div", { style: cardStyle, children: [
608
+ /* @__PURE__ */ jsx2("h3", { style: { margin: "0 0 16px 0", fontSize: "18px", fontWeight: "600", color: "#ffffff" }, children: "\u{1F50D} Browser Diagnostics" }),
609
+ /* @__PURE__ */ jsxs2("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
610
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
611
+ /* @__PURE__ */ jsx2("span", { style: diagnostics.crossOriginIsolation ? successStatus : errorStatus, children: diagnostics.crossOriginIsolation ? "\u2705" : "\u274C" }),
612
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: "12px", color: "#d4d4d4" }, children: "Cross-Origin Isolation" })
613
+ ] }),
614
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
615
+ /* @__PURE__ */ jsx2("span", { style: diagnostics.gamepadSupported ? successStatus : errorStatus, children: diagnostics.gamepadSupported ? "\u2705" : "\u274C" }),
616
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: "12px", color: "#d4d4d4" }, children: "Gamepad API" })
617
+ ] }),
618
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
619
+ /* @__PURE__ */ jsx2("span", { style: diagnostics.fullscreenSupported ? successStatus : errorStatus, children: diagnostics.fullscreenSupported ? "\u2705" : "\u274C" }),
620
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: "12px", color: "#d4d4d4" }, children: "Fullscreen API" })
621
+ ] }),
622
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
623
+ /* @__PURE__ */ jsx2("span", { style: diagnostics.keyboardLockSupported ? successStatus : errorStatus, children: diagnostics.keyboardLockSupported ? "\u2705" : "\u274C" }),
624
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: "12px", color: "#d4d4d4" }, children: "Keyboard Lock" })
625
+ ] })
626
+ ] })
627
+ ] }),
628
+ /* @__PURE__ */ jsxs2("div", { style: cardStyle, children: [
629
+ /* @__PURE__ */ jsx2("h3", { style: { margin: "0 0 16px 0", fontSize: "18px", fontWeight: "600", color: "#ffffff" }, children: "Event Log" }),
630
+ /* @__PURE__ */ jsx2("div", { style: {
631
+ backgroundColor: "#0a0a0a",
632
+ padding: "12px",
633
+ borderRadius: "4px",
634
+ height: "200px",
635
+ overflowY: "auto",
636
+ fontFamily: "monospace",
637
+ fontSize: "12px",
638
+ border: "1px solid #333333"
639
+ }, children: eventLog.length ? /* @__PURE__ */ jsx2("div", { children: eventLog.map((event) => /* @__PURE__ */ jsxs2("div", { style: { marginBottom: "4px" }, children: [
640
+ /* @__PURE__ */ jsxs2("span", { style: { color: "#737373" }, children: [
641
+ "[",
642
+ event.timestamp,
643
+ "]"
644
+ ] }),
645
+ " ",
646
+ /* @__PURE__ */ jsx2("span", { style: { color: "#e5e5e5" }, children: event.message })
647
+ ] }, event.timestamp)) }) : /* @__PURE__ */ jsx2("div", { style: { color: "#737373" }, children: "No events yet..." }) })
648
+ ] })
649
+ ] }),
650
+ /* @__PURE__ */ jsx2("div", { children: /* @__PURE__ */ jsxs2("div", { style: cardStyle, children: [
651
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "16px" }, children: [
652
+ /* @__PURE__ */ jsx2("h3", { style: { margin: "0", fontSize: "18px", fontWeight: "600", color: "#ffffff" }, children: "Embed Container" }),
653
+ /* @__PURE__ */ jsxs2("span", { style: {
654
+ backgroundColor: "#2a2a2a",
655
+ color: "#e5e5e5",
656
+ padding: "4px 8px",
657
+ borderRadius: "12px",
658
+ fontSize: "12px",
659
+ fontWeight: "500",
660
+ border: "1px solid #404040"
661
+ }, children: [
662
+ "\u{1F9EA} ",
663
+ baseUrl ? new URL(baseUrl).host : "localhost:3000"
664
+ ] })
665
+ ] }),
666
+ /* @__PURE__ */ jsx2("div", { style: {
667
+ position: "relative",
668
+ minHeight: "500px",
669
+ overflow: "hidden",
670
+ borderRadius: "8px",
671
+ border: "1px solid #333333",
672
+ backgroundColor: "#0a0a0a"
673
+ }, children: /* @__PURE__ */ jsx2(
674
+ PlayruoEmbed,
675
+ {
676
+ baseUrl: baseUrl || "",
677
+ className: "w-full h-full absolute inset-0",
678
+ iframeRef,
679
+ onDemoCompleted: handleDemoCompleted,
680
+ onError: handleError,
681
+ onPurchaseRequest: handlePurchaseRequest,
682
+ onReady: handleReady,
683
+ onStateChange: handleStateChange,
684
+ operation,
685
+ renderState: ({ actions, message, status }) => /* @__PURE__ */ jsx2(Fragment, { children: !hasUserClicked && /* @__PURE__ */ jsxs2("div", { style: {
686
+ position: "absolute",
687
+ inset: "0",
688
+ display: "flex",
689
+ flexDirection: "column",
690
+ alignItems: "center",
691
+ justifyContent: "center",
692
+ gap: "16px",
693
+ backgroundColor: "rgba(0, 0, 0, 0.85)",
694
+ backdropFilter: "blur(8px)",
695
+ zIndex: 50,
696
+ padding: "20px"
697
+ }, children: [
698
+ /* @__PURE__ */ jsxs2("p", { style: { margin: "0", fontSize: "14px", color: "#d4d4d4" }, children: [
699
+ "Status: ",
700
+ /* @__PURE__ */ jsx2("span", { style: { color: "#ffffff", fontWeight: "600" }, children: status })
701
+ ] }),
702
+ message && /* @__PURE__ */ jsx2("p", { style: { margin: "0", fontSize: "14px", color: "#d4d4d4" }, children: message }),
703
+ /* @__PURE__ */ jsxs2(
704
+ "button",
705
+ {
706
+ type: "button",
707
+ style: {
708
+ ...buttonStyle,
709
+ backgroundColor: "#16a34a",
710
+ color: "#ffffff",
711
+ border: "none",
712
+ padding: "12px 24px",
713
+ fontSize: "16px",
714
+ fontWeight: "600",
715
+ cursor: actions.canTryNow ? "pointer" : "not-allowed",
716
+ opacity: actions.canTryNow ? 1 : 0.5
717
+ },
718
+ disabled: !actions.canTryNow,
719
+ onMouseEnter: (e) => {
720
+ if (actions.canTryNow) {
721
+ e.currentTarget.style.backgroundColor = "#15803d";
722
+ }
723
+ },
724
+ onMouseLeave: (e) => {
725
+ if (actions.canTryNow) {
726
+ e.currentTarget.style.backgroundColor = "#16a34a";
727
+ }
728
+ },
729
+ onClick: () => {
730
+ setHasUserClicked(true);
731
+ setShowIframe(true);
732
+ addEvent(embedState.hasPlayedOnce ? "User clicked - Resuming game" : "User clicked - Starting game with tryNow");
733
+ actions.tryNow();
734
+ },
735
+ children: [
736
+ "\u{1F3AE} ",
737
+ embedState.hasPlayedOnce ? "Resume Game" : "Try Demo Now"
738
+ ]
739
+ }
740
+ ),
741
+ actions.canBuyNow && /* @__PURE__ */ jsx2(
742
+ "button",
743
+ {
744
+ type: "button",
745
+ style: {
746
+ ...buttonStyle,
747
+ backgroundColor: "#525252",
748
+ color: "#ffffff",
749
+ border: "none",
750
+ padding: "8px 16px",
751
+ fontSize: "14px",
752
+ fontWeight: "500"
753
+ },
754
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "#404040",
755
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "#525252",
756
+ onClick: () => {
757
+ addEvent("Buy Now clicked");
758
+ actions.buyNow();
759
+ },
760
+ children: "Buy Now"
761
+ }
762
+ )
763
+ ] }) }),
764
+ showIframe,
765
+ storeUrl
766
+ },
767
+ key
768
+ ) })
769
+ ] }) })
770
+ ] })
771
+ ] }) });
772
+ };
773
+
329
774
  // src/post-message-types.ts
330
775
  var isPlayruoEmbedMessage = (data) => {
331
776
  if (!data || typeof data !== "object") return false;
332
777
  const msg = data;
333
778
  if (typeof msg.type !== "string") return false;
334
- return msg.type === "playruo_ready" || msg.type === "playruo_state_update" || msg.type === "playruo_purchase_request" || msg.type === "playruo_demo_completed" || msg.type === "playruo_error";
779
+ 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";
335
780
  };
336
- var isPlayruoEmbedCommand = (data) => {
781
+ var isPlayruoInternalMessage = (data) => {
337
782
  if (!data || typeof data !== "object") return false;
338
- const cmd = data;
339
- if (typeof cmd.type !== "string") return false;
340
- return cmd.type === "try_now" || cmd.type === "buy_now" || cmd.type === "get_state";
783
+ const msg = data;
784
+ return msg.type === "playruo" && typeof msg.action === "string" && (msg.action === "tryNowClick" || msg.action === "exitFullscreen" || msg.action === "mute" || msg.action === "unmute") && msg.fromEmbed === true;
341
785
  };
342
786
  export {
787
+ EmbedTester,
343
788
  PlayruoEmbed,
344
789
  buildEmbedUrl,
345
- isPlayruoEmbedCommand,
346
- isPlayruoEmbedMessage
790
+ isPlayruoEmbedMessage,
791
+ isPlayruoInternalMessage
347
792
  };