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