@playruo/embed 1.3.1 → 1.3.2
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/README.md +9 -0
- package/dist/index.cjs +249 -36
- package/dist/index.d.cts +22 -2
- package/dist/index.d.ts +22 -2
- package/dist/index.js +249 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
# @playruo/embed
|
|
2
2
|
|
|
3
3
|
React SDK to embed Playruo via iframe + postMessage.
|
|
4
|
+
|
|
5
|
+
## Event forwarding
|
|
6
|
+
|
|
7
|
+
When the embedded Playruo iframe sends `{ type: "playruo_event", data }`, the
|
|
8
|
+
React package forwards the payload through `onEvent(data)`.
|
|
9
|
+
|
|
10
|
+
If the package host is itself inside another iframe, the same message is also
|
|
11
|
+
forwarded one level up with `window.parent.postMessage({ type:
|
|
12
|
+
"playruo_event", data }, "*")`.
|
package/dist/index.cjs
CHANGED
|
@@ -74,14 +74,18 @@ var PlayruoEmbed = ({
|
|
|
74
74
|
onReady,
|
|
75
75
|
onStateChange,
|
|
76
76
|
onDemoCompleted,
|
|
77
|
+
onEvent,
|
|
77
78
|
onPurchaseRequest,
|
|
78
79
|
onError,
|
|
80
|
+
forwardEventsToParent = true,
|
|
79
81
|
renderState,
|
|
80
82
|
iframeRef: externalIframeRef
|
|
81
83
|
}) => {
|
|
82
84
|
const internalIframeRef = (0, import_react.useRef)(null);
|
|
83
85
|
const iframeRef = externalIframeRef || internalIframeRef;
|
|
84
86
|
const wasEmbedFullscreenRef = (0, import_react.useRef)(false);
|
|
87
|
+
const hasNotifiedCompletionRef = (0, import_react.useRef)(false);
|
|
88
|
+
const suppressNextFullscreenExitCleanupRef = (0, import_react.useRef)(false);
|
|
85
89
|
const [isIframeLoaded, setIsIframeLoaded] = (0, import_react.useState)(false);
|
|
86
90
|
const [hasPlayedOnce, setHasPlayedOnce] = (0, import_react.useState)(false);
|
|
87
91
|
const [showGameIframe, setShowGameIframe] = (0, import_react.useState)(false);
|
|
@@ -151,6 +155,149 @@ var PlayruoEmbed = ({
|
|
|
151
155
|
console.warn("[PlayruoEmbed] Failed to release Escape key lock:", error);
|
|
152
156
|
}
|
|
153
157
|
}, [getKeyboardApi]);
|
|
158
|
+
const getFullscreenElement = (0, import_react.useCallback)(() => {
|
|
159
|
+
const doc = document;
|
|
160
|
+
return doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement || null;
|
|
161
|
+
}, []);
|
|
162
|
+
const notifyDemoCompletedOnce = (0, import_react.useCallback)(
|
|
163
|
+
(payload) => {
|
|
164
|
+
if (hasNotifiedCompletionRef.current) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
hasNotifiedCompletionRef.current = true;
|
|
168
|
+
onDemoCompleted?.(
|
|
169
|
+
payload ?? {
|
|
170
|
+
reason: "Demo session ended",
|
|
171
|
+
storeUrl
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
[onDemoCompleted, storeUrl]
|
|
176
|
+
);
|
|
177
|
+
const postExitFullscreenToIframe = (0, import_react.useCallback)(() => {
|
|
178
|
+
const iframe = iframeRef.current;
|
|
179
|
+
if (!iframe) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
iframe.contentWindow?.postMessage({
|
|
184
|
+
type: "playruo",
|
|
185
|
+
action: "exitFullscreen",
|
|
186
|
+
fromEmbed: true,
|
|
187
|
+
isFullscreen: false,
|
|
188
|
+
parentWillHandleFullscreen: true
|
|
189
|
+
}, "*");
|
|
190
|
+
} catch {
|
|
191
|
+
console.error("[PlayruoEmbed] Failed to exit fullscreen");
|
|
192
|
+
}
|
|
193
|
+
}, [iframeRef]);
|
|
194
|
+
const postExitFullscreenAckToIframe = (0, import_react.useCallback)(() => {
|
|
195
|
+
const iframe = iframeRef.current;
|
|
196
|
+
if (!iframe) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
iframe.contentWindow?.postMessage({ type: "ACK_EXIT_FULLSCREEN" }, "*");
|
|
201
|
+
} catch {
|
|
202
|
+
console.error("[PlayruoEmbed] Failed to acknowledge fullscreen exit");
|
|
203
|
+
}
|
|
204
|
+
}, [iframeRef]);
|
|
205
|
+
const exitEmbedFullscreen = (0, import_react.useCallback)(
|
|
206
|
+
async ({
|
|
207
|
+
reason = "cleanup",
|
|
208
|
+
suppressFullscreenExitCleanup = true
|
|
209
|
+
} = {}) => {
|
|
210
|
+
const fullscreenElement = getFullscreenElement();
|
|
211
|
+
const doc = document;
|
|
212
|
+
const exitFullscreen = doc.exitFullscreen?.bind(doc) ?? doc.webkitExitFullscreen?.bind(doc) ?? doc.mozCancelFullScreen?.bind(doc) ?? doc.msExitFullscreen?.bind(doc);
|
|
213
|
+
const shouldAttemptExit = Boolean(fullscreenElement || wasEmbedFullscreenRef.current);
|
|
214
|
+
if (!shouldAttemptExit || !exitFullscreen) {
|
|
215
|
+
console.log("[PlayruoEmbed] Parent fullscreen exit skipped", {
|
|
216
|
+
hasFullscreenElement: Boolean(fullscreenElement),
|
|
217
|
+
hasExitFullscreen: Boolean(exitFullscreen),
|
|
218
|
+
reason,
|
|
219
|
+
wasEmbedFullscreen: wasEmbedFullscreenRef.current
|
|
220
|
+
});
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
console.log("[PlayruoEmbed] Parent fullscreen exit requested", {
|
|
224
|
+
fullscreenElement: fullscreenElement?.tagName,
|
|
225
|
+
reason,
|
|
226
|
+
wasEmbedFullscreen: wasEmbedFullscreenRef.current
|
|
227
|
+
});
|
|
228
|
+
const shouldSuppressFullscreenExitCleanup = Boolean(
|
|
229
|
+
suppressFullscreenExitCleanup && fullscreenElement
|
|
230
|
+
);
|
|
231
|
+
if (shouldSuppressFullscreenExitCleanup) {
|
|
232
|
+
suppressNextFullscreenExitCleanupRef.current = true;
|
|
233
|
+
wasEmbedFullscreenRef.current = false;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
await exitFullscreen();
|
|
237
|
+
if (shouldSuppressFullscreenExitCleanup) {
|
|
238
|
+
window.setTimeout(() => {
|
|
239
|
+
suppressNextFullscreenExitCleanupRef.current = false;
|
|
240
|
+
}, 1e3);
|
|
241
|
+
}
|
|
242
|
+
return true;
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if (shouldSuppressFullscreenExitCleanup) {
|
|
245
|
+
suppressNextFullscreenExitCleanupRef.current = false;
|
|
246
|
+
wasEmbedFullscreenRef.current = Boolean(getFullscreenElement());
|
|
247
|
+
}
|
|
248
|
+
console.warn("[PlayruoEmbed] Failed to leave fullscreen:", error);
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
[getFullscreenElement]
|
|
253
|
+
);
|
|
254
|
+
const cleanupEmbedSession = (0, import_react.useCallback)(
|
|
255
|
+
({
|
|
256
|
+
payload,
|
|
257
|
+
notifyCompletion = true,
|
|
258
|
+
requestFullscreenExit = true,
|
|
259
|
+
notifyIframe = false,
|
|
260
|
+
markPlayed = true
|
|
261
|
+
} = {}) => {
|
|
262
|
+
setShowGameIframe(false);
|
|
263
|
+
if (markPlayed) {
|
|
264
|
+
setHasPlayedOnce(true);
|
|
265
|
+
}
|
|
266
|
+
releaseEscapeKeyLock();
|
|
267
|
+
if (notifyIframe) {
|
|
268
|
+
postExitFullscreenToIframe();
|
|
269
|
+
}
|
|
270
|
+
if (requestFullscreenExit) {
|
|
271
|
+
void exitEmbedFullscreen();
|
|
272
|
+
}
|
|
273
|
+
if (notifyCompletion) {
|
|
274
|
+
notifyDemoCompletedOnce(payload);
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
[
|
|
278
|
+
exitEmbedFullscreen,
|
|
279
|
+
notifyDemoCompletedOnce,
|
|
280
|
+
postExitFullscreenToIframe,
|
|
281
|
+
releaseEscapeKeyLock
|
|
282
|
+
]
|
|
283
|
+
);
|
|
284
|
+
const markStreamActive = (0, import_react.useCallback)(() => {
|
|
285
|
+
hasNotifiedCompletionRef.current = false;
|
|
286
|
+
setShowGameIframe(true);
|
|
287
|
+
}, []);
|
|
288
|
+
const forwardEventToParent = (0, import_react.useCallback)(
|
|
289
|
+
(message) => {
|
|
290
|
+
if (!forwardEventsToParent || typeof window === "undefined" || window.parent === window) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
window.parent.postMessage(message, "*");
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.warn("[PlayruoEmbed] Failed to forward event to parent:", error);
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
[forwardEventsToParent]
|
|
300
|
+
);
|
|
154
301
|
const lockEscapeKey = (0, import_react.useCallback)(async () => {
|
|
155
302
|
const keyboard = getKeyboardApi();
|
|
156
303
|
if (!keyboard?.lock) {
|
|
@@ -187,6 +334,7 @@ var PlayruoEmbed = ({
|
|
|
187
334
|
console.warn("[PlayruoEmbed] Iframe not ready");
|
|
188
335
|
return;
|
|
189
336
|
}
|
|
337
|
+
hasNotifiedCompletionRef.current = false;
|
|
190
338
|
setShowGameIframe(true);
|
|
191
339
|
try {
|
|
192
340
|
try {
|
|
@@ -210,6 +358,7 @@ var PlayruoEmbed = ({
|
|
|
210
358
|
setHasPlayedOnce(true);
|
|
211
359
|
} catch (error) {
|
|
212
360
|
console.error("[PlayruoEmbed] Failed to start game:", error);
|
|
361
|
+
setShowGameIframe(false);
|
|
213
362
|
setErrorMessage("Failed to start game");
|
|
214
363
|
onError?.("Failed to start game");
|
|
215
364
|
}
|
|
@@ -233,7 +382,8 @@ var PlayruoEmbed = ({
|
|
|
233
382
|
}, [isReady, onReady]);
|
|
234
383
|
(0, import_react.useEffect)(() => {
|
|
235
384
|
const handleGameMessage = (event) => {
|
|
236
|
-
const
|
|
385
|
+
const message = event.data || {};
|
|
386
|
+
const { type, data } = message;
|
|
237
387
|
const iframe = iframeRef.current;
|
|
238
388
|
if (!iframe || event.source !== iframe.contentWindow) {
|
|
239
389
|
return;
|
|
@@ -254,8 +404,7 @@ var PlayruoEmbed = ({
|
|
|
254
404
|
onPurchaseRequest?.(data);
|
|
255
405
|
} else if (type === "playruo_demo_completed" && data) {
|
|
256
406
|
console.log("\u{1F537} [Embed] playruo_demo_completed received:", data);
|
|
257
|
-
|
|
258
|
-
onDemoCompleted?.(data);
|
|
407
|
+
cleanupEmbedSession({ payload: data });
|
|
259
408
|
} else if (type === "playruo_error") {
|
|
260
409
|
const errorMessage2 = typeof data === "string" ? data : data?.message || "Unknown error";
|
|
261
410
|
console.log("\u{1F537} [Embed] playruo_error received:", errorMessage2);
|
|
@@ -276,25 +425,65 @@ var PlayruoEmbed = ({
|
|
|
276
425
|
onError?.(errorMessage2);
|
|
277
426
|
} else if (type === "playruo_session_over" && data) {
|
|
278
427
|
console.log("\u{1F537} [Embed] playruo_session_over received:", data);
|
|
279
|
-
|
|
428
|
+
void exitEmbedFullscreen({ reason: "playruo_session_over" });
|
|
280
429
|
setIframeState(data);
|
|
281
430
|
onStateChange?.(data);
|
|
431
|
+
cleanupEmbedSession({ requestFullscreenExit: false });
|
|
282
432
|
} else if (type === "playruo_stream_start") {
|
|
283
433
|
console.log("\u{1F537} [Embed] playruo_stream_start received");
|
|
434
|
+
markStreamActive();
|
|
435
|
+
} else if (type === "playruo_stream_stop") {
|
|
436
|
+
console.log("\u{1F537} [Embed] playruo_stream_stop received");
|
|
437
|
+
void exitEmbedFullscreen({ reason: "playruo_stream_stop" });
|
|
438
|
+
cleanupEmbedSession({
|
|
439
|
+
markPlayed: false,
|
|
440
|
+
notifyCompletion: false,
|
|
441
|
+
requestFullscreenExit: false
|
|
442
|
+
});
|
|
443
|
+
} else if (type === "REQUEST_EXIT_FULLSCREEN") {
|
|
444
|
+
console.log("\u{1F537} [Embed] REQUEST_EXIT_FULLSCREEN received");
|
|
445
|
+
void exitEmbedFullscreen({
|
|
446
|
+
reason: "REQUEST_EXIT_FULLSCREEN",
|
|
447
|
+
suppressFullscreenExitCleanup: false
|
|
448
|
+
}).finally(postExitFullscreenAckToIframe);
|
|
449
|
+
} else if (type === "playruo_event" && data && typeof data === "object") {
|
|
450
|
+
console.log("\u{1F537} [Embed] playruo_event received:", data);
|
|
451
|
+
const eventPayload = data;
|
|
452
|
+
onEvent?.(eventPayload);
|
|
453
|
+
forwardEventToParent({ type: "playruo_event", data: eventPayload });
|
|
454
|
+
} else if (type === "playruo" && message.action === "event" && message.payload && typeof message.payload === "object") {
|
|
455
|
+
console.log("\u{1F537} [Embed] legacy playruo event received:", message.payload);
|
|
456
|
+
const eventPayload = message.payload;
|
|
457
|
+
onEvent?.(eventPayload);
|
|
458
|
+
forwardEventToParent({ type: "playruo_event", data: eventPayload });
|
|
284
459
|
}
|
|
285
460
|
if (type === "error" || type === "closeForBuy" || type === "goToStore") {
|
|
286
461
|
console.log("\u{1F537} [Embed] Legacy message received:", type);
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
462
|
+
cleanupEmbedSession({
|
|
463
|
+
payload: {
|
|
464
|
+
reason: "Demo session ended",
|
|
465
|
+
storeUrl
|
|
466
|
+
}
|
|
467
|
+
});
|
|
293
468
|
}
|
|
294
469
|
};
|
|
295
470
|
window.addEventListener("message", handleGameMessage);
|
|
296
471
|
return () => window.removeEventListener("message", handleGameMessage);
|
|
297
|
-
}, [
|
|
472
|
+
}, [
|
|
473
|
+
cleanupEmbedSession,
|
|
474
|
+
exitEmbedFullscreen,
|
|
475
|
+
forwardEventToParent,
|
|
476
|
+
hasPlayedOnce,
|
|
477
|
+
iframeRef,
|
|
478
|
+
markStreamActive,
|
|
479
|
+
onEvent,
|
|
480
|
+
onError,
|
|
481
|
+
onPurchaseRequest,
|
|
482
|
+
onReady,
|
|
483
|
+
onStateChange,
|
|
484
|
+
postExitFullscreenAckToIframe,
|
|
485
|
+
storeUrl
|
|
486
|
+
]);
|
|
298
487
|
const generateUUID = () => {
|
|
299
488
|
if (self.crypto.randomUUID) return self.crypto.randomUUID();
|
|
300
489
|
const bytes = new Uint8Array(16);
|
|
@@ -341,9 +530,8 @@ var PlayruoEmbed = ({
|
|
|
341
530
|
}, [iframeRef]);
|
|
342
531
|
(0, import_react.useEffect)(() => {
|
|
343
532
|
const handleFullscreenChange = () => {
|
|
344
|
-
const doc = document;
|
|
345
533
|
const iframe = iframeRef.current;
|
|
346
|
-
const fullscreenElement =
|
|
534
|
+
const fullscreenElement = getFullscreenElement();
|
|
347
535
|
const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
|
|
348
536
|
if (isEmbedFullscreen) {
|
|
349
537
|
wasEmbedFullscreenRef.current = true;
|
|
@@ -360,26 +548,15 @@ var PlayruoEmbed = ({
|
|
|
360
548
|
return;
|
|
361
549
|
}
|
|
362
550
|
releaseEscapeKeyLock();
|
|
551
|
+
if (suppressNextFullscreenExitCleanupRef.current) {
|
|
552
|
+
suppressNextFullscreenExitCleanupRef.current = false;
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
363
555
|
if (wasEmbedFullscreenRef.current) {
|
|
364
556
|
wasEmbedFullscreenRef.current = false;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
type: "playruo",
|
|
369
|
-
action: "exitFullscreen",
|
|
370
|
-
fromEmbed: true,
|
|
371
|
-
isFullscreen: false,
|
|
372
|
-
parentWillHandleFullscreen: true
|
|
373
|
-
}, "*");
|
|
374
|
-
} catch {
|
|
375
|
-
console.error("[PlayruoEmbed] Failed to exit fullscreen");
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
setShowGameIframe(false);
|
|
379
|
-
setHasPlayedOnce(true);
|
|
380
|
-
onDemoCompleted?.({
|
|
381
|
-
reason: "Demo session ended",
|
|
382
|
-
storeUrl
|
|
557
|
+
cleanupEmbedSession({
|
|
558
|
+
notifyIframe: true,
|
|
559
|
+
requestFullscreenExit: false
|
|
383
560
|
});
|
|
384
561
|
}
|
|
385
562
|
};
|
|
@@ -393,7 +570,13 @@ var PlayruoEmbed = ({
|
|
|
393
570
|
document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
|
|
394
571
|
document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
|
|
395
572
|
};
|
|
396
|
-
}, [
|
|
573
|
+
}, [
|
|
574
|
+
cleanupEmbedSession,
|
|
575
|
+
getFullscreenElement,
|
|
576
|
+
iframeRef,
|
|
577
|
+
lockEscapeKey,
|
|
578
|
+
releaseEscapeKeyLock
|
|
579
|
+
]);
|
|
397
580
|
(0, import_react.useEffect)(() => {
|
|
398
581
|
return () => {
|
|
399
582
|
releaseEscapeKeyLock();
|
|
@@ -478,6 +661,8 @@ var PlayruoEmbed = ({
|
|
|
478
661
|
}
|
|
479
662
|
}, [isReady]);
|
|
480
663
|
const isAvailable = Boolean(isReady && isCompatible && hasIframeConfirmed);
|
|
664
|
+
const shouldDisplayGameIframe = showGameIframe;
|
|
665
|
+
const shouldShowIframeMisuseMessage = Boolean(showIframe && !showGameIframe);
|
|
481
666
|
const renderContent = () => {
|
|
482
667
|
if (!renderState) return null;
|
|
483
668
|
const canTryNow = Boolean(
|
|
@@ -507,6 +692,26 @@ var PlayruoEmbed = ({
|
|
|
507
692
|
};
|
|
508
693
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, children: [
|
|
509
694
|
renderContent(),
|
|
695
|
+
isCompatible && shouldShowIframeMisuseMessage && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
696
|
+
"div",
|
|
697
|
+
{
|
|
698
|
+
role: "status",
|
|
699
|
+
style: {
|
|
700
|
+
alignItems: "center",
|
|
701
|
+
background: "#000",
|
|
702
|
+
color: "#fff",
|
|
703
|
+
display: "flex",
|
|
704
|
+
fontFamily: "system-ui, sans-serif",
|
|
705
|
+
fontSize: 14,
|
|
706
|
+
height: "100%",
|
|
707
|
+
justifyContent: "center",
|
|
708
|
+
padding: 24,
|
|
709
|
+
textAlign: "center",
|
|
710
|
+
width: "100%"
|
|
711
|
+
},
|
|
712
|
+
children: "This iframe is only meant to be displayed through PlayruoEmbed during an active stream."
|
|
713
|
+
}
|
|
714
|
+
),
|
|
510
715
|
isCompatible && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
511
716
|
"iframe",
|
|
512
717
|
{
|
|
@@ -519,9 +724,9 @@ var PlayruoEmbed = ({
|
|
|
519
724
|
title: "Playruo",
|
|
520
725
|
referrerPolicy: "strict-origin-when-cross-origin",
|
|
521
726
|
style: {
|
|
522
|
-
//
|
|
523
|
-
opacity:
|
|
524
|
-
pointerEvents:
|
|
727
|
+
// The iframe stays mounted for readiness checks, visible only during launch/play, and hidden after terminal events.
|
|
728
|
+
opacity: shouldDisplayGameIframe ? 1 : 0,
|
|
729
|
+
pointerEvents: shouldDisplayGameIframe ? "auto" : "none",
|
|
525
730
|
width: "100%",
|
|
526
731
|
height: "100%",
|
|
527
732
|
border: 0
|
|
@@ -680,6 +885,13 @@ var EmbedTester = (props = {}) => {
|
|
|
680
885
|
console.error("Embed error:", error);
|
|
681
886
|
}
|
|
682
887
|
}, [addEvent, enableConsoleLog]);
|
|
888
|
+
const handleEvent = (0, import_react2.useCallback)((data) => {
|
|
889
|
+
const eventName = typeof data.event === "string" ? data.event : typeof data.name === "string" ? data.name : "unknown";
|
|
890
|
+
addEvent(`Event received: ${eventName}`);
|
|
891
|
+
if (enableConsoleLog) {
|
|
892
|
+
console.log("Embed event:", data);
|
|
893
|
+
}
|
|
894
|
+
}, [addEvent, enableConsoleLog]);
|
|
683
895
|
const handlePurchaseRequest = (0, import_react2.useCallback)((data) => {
|
|
684
896
|
addEvent(`\u{1F4B3} Explicit purchase request from Buy Now button`);
|
|
685
897
|
addEvent(`Opening store: ${data.storeUrl}`);
|
|
@@ -1627,6 +1839,7 @@ var EmbedTester = (props = {}) => {
|
|
|
1627
1839
|
iframeRef,
|
|
1628
1840
|
onDemoCompleted: handleDemoCompleted,
|
|
1629
1841
|
onError: handleError,
|
|
1842
|
+
onEvent: handleEvent,
|
|
1630
1843
|
onPurchaseRequest: handlePurchaseRequest,
|
|
1631
1844
|
onReady: handleReady,
|
|
1632
1845
|
onStateChange: handleStateChange,
|
|
@@ -1754,7 +1967,7 @@ var isPlayruoEmbedMessage = (data) => {
|
|
|
1754
1967
|
if (!data || typeof data !== "object") return false;
|
|
1755
1968
|
const msg = data;
|
|
1756
1969
|
if (typeof msg.type !== "string") return false;
|
|
1757
|
-
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";
|
|
1970
|
+
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" || msg.type === "REQUEST_EXIT_FULLSCREEN" || msg.type === "playruo_event";
|
|
1758
1971
|
};
|
|
1759
1972
|
var isPlayruoInternalMessage = (data) => {
|
|
1760
1973
|
if (!data || typeof data !== "object") return false;
|
package/dist/index.d.cts
CHANGED
|
@@ -17,6 +17,12 @@ interface DemoCompletedPayload {
|
|
|
17
17
|
reason?: string;
|
|
18
18
|
storeUrl?: string;
|
|
19
19
|
}
|
|
20
|
+
type PlayruoEventPayload = Record<string, unknown> & {
|
|
21
|
+
action?: string;
|
|
22
|
+
category?: string;
|
|
23
|
+
event?: string;
|
|
24
|
+
name?: string;
|
|
25
|
+
};
|
|
20
26
|
|
|
21
27
|
interface PlayruoEmbedActions {
|
|
22
28
|
/** Start the demo session */
|
|
@@ -44,10 +50,17 @@ interface PlayruoEmbedProps {
|
|
|
44
50
|
onReady?: () => void;
|
|
45
51
|
onStateChange?: (state: PlayruoEmbedState) => void;
|
|
46
52
|
onDemoCompleted?: (data: DemoCompletedPayload) => void;
|
|
53
|
+
onEvent?: (data: PlayruoEventPayload) => void;
|
|
47
54
|
onPurchaseRequest?: (data: {
|
|
48
55
|
storeUrl: string;
|
|
49
56
|
}) => void;
|
|
50
57
|
onError?: (error: string) => void;
|
|
58
|
+
/**
|
|
59
|
+
* Forward playruo_event messages to the parent window when the package host
|
|
60
|
+
* itself is embedded in another iframe. Direct host pages already receive the
|
|
61
|
+
* original iframe postMessage.
|
|
62
|
+
*/
|
|
63
|
+
forwardEventsToParent?: boolean;
|
|
51
64
|
/**
|
|
52
65
|
* Single render entry point representing the current status.
|
|
53
66
|
* Provides status, message, and available actions based on current state.
|
|
@@ -103,7 +116,14 @@ interface PlayruoStreamStartMessage {
|
|
|
103
116
|
interface PlayruoStreamStopMessage {
|
|
104
117
|
type: "playruo_stream_stop";
|
|
105
118
|
}
|
|
106
|
-
|
|
119
|
+
interface PlayruoRequestExitFullscreenMessage {
|
|
120
|
+
type: "REQUEST_EXIT_FULLSCREEN";
|
|
121
|
+
}
|
|
122
|
+
interface PlayruoEventMessage {
|
|
123
|
+
type: "playruo_event";
|
|
124
|
+
data: PlayruoEventPayload;
|
|
125
|
+
}
|
|
126
|
+
type PlayruoEmbedMessage = PlayruoReadyMessage | PlayruoStateUpdateMessage | PlayruoPurchaseRequestMessage | PlayruoDemoCompletedMessage | PlayruoErrorMessage | PlayruoSessionOverMessage | PlayruoStreamStartMessage | PlayruoStreamStopMessage | PlayruoRequestExitFullscreenMessage | PlayruoEventMessage;
|
|
107
127
|
interface PlayruoInternalMessage {
|
|
108
128
|
type: "playruo";
|
|
109
129
|
action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
|
|
@@ -136,4 +156,4 @@ interface BuildUrlParams {
|
|
|
136
156
|
*/
|
|
137
157
|
declare const buildEmbedUrl: ({ operation, storeUrl }: BuildUrlParams) => string;
|
|
138
158
|
|
|
139
|
-
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 };
|
|
159
|
+
export { type DemoCompletedPayload, EmbedTester, type EmbedTesterProps, type PlayruoDemoCompletedMessage, PlayruoEmbed, type PlayruoEmbedActions, type PlayruoEmbedMessage, type PlayruoEmbedProps, type PlayruoEmbedRenderState, type PlayruoEmbedState, type PlayruoErrorMessage, type PlayruoEventMessage, type PlayruoEventPayload, type PlayruoInternalMessage, type PlayruoPurchaseRequestMessage, type PlayruoReadyMessage, type PlayruoRequestExitFullscreenMessage, type PlayruoSessionOverMessage, type PlayruoStateUpdateMessage, type PlayruoStreamStartMessage, type PlayruoStreamStopMessage, buildEmbedUrl, isPlayruoEmbedMessage, isPlayruoInternalMessage };
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,12 @@ interface DemoCompletedPayload {
|
|
|
17
17
|
reason?: string;
|
|
18
18
|
storeUrl?: string;
|
|
19
19
|
}
|
|
20
|
+
type PlayruoEventPayload = Record<string, unknown> & {
|
|
21
|
+
action?: string;
|
|
22
|
+
category?: string;
|
|
23
|
+
event?: string;
|
|
24
|
+
name?: string;
|
|
25
|
+
};
|
|
20
26
|
|
|
21
27
|
interface PlayruoEmbedActions {
|
|
22
28
|
/** Start the demo session */
|
|
@@ -44,10 +50,17 @@ interface PlayruoEmbedProps {
|
|
|
44
50
|
onReady?: () => void;
|
|
45
51
|
onStateChange?: (state: PlayruoEmbedState) => void;
|
|
46
52
|
onDemoCompleted?: (data: DemoCompletedPayload) => void;
|
|
53
|
+
onEvent?: (data: PlayruoEventPayload) => void;
|
|
47
54
|
onPurchaseRequest?: (data: {
|
|
48
55
|
storeUrl: string;
|
|
49
56
|
}) => void;
|
|
50
57
|
onError?: (error: string) => void;
|
|
58
|
+
/**
|
|
59
|
+
* Forward playruo_event messages to the parent window when the package host
|
|
60
|
+
* itself is embedded in another iframe. Direct host pages already receive the
|
|
61
|
+
* original iframe postMessage.
|
|
62
|
+
*/
|
|
63
|
+
forwardEventsToParent?: boolean;
|
|
51
64
|
/**
|
|
52
65
|
* Single render entry point representing the current status.
|
|
53
66
|
* Provides status, message, and available actions based on current state.
|
|
@@ -103,7 +116,14 @@ interface PlayruoStreamStartMessage {
|
|
|
103
116
|
interface PlayruoStreamStopMessage {
|
|
104
117
|
type: "playruo_stream_stop";
|
|
105
118
|
}
|
|
106
|
-
|
|
119
|
+
interface PlayruoRequestExitFullscreenMessage {
|
|
120
|
+
type: "REQUEST_EXIT_FULLSCREEN";
|
|
121
|
+
}
|
|
122
|
+
interface PlayruoEventMessage {
|
|
123
|
+
type: "playruo_event";
|
|
124
|
+
data: PlayruoEventPayload;
|
|
125
|
+
}
|
|
126
|
+
type PlayruoEmbedMessage = PlayruoReadyMessage | PlayruoStateUpdateMessage | PlayruoPurchaseRequestMessage | PlayruoDemoCompletedMessage | PlayruoErrorMessage | PlayruoSessionOverMessage | PlayruoStreamStartMessage | PlayruoStreamStopMessage | PlayruoRequestExitFullscreenMessage | PlayruoEventMessage;
|
|
107
127
|
interface PlayruoInternalMessage {
|
|
108
128
|
type: "playruo";
|
|
109
129
|
action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
|
|
@@ -136,4 +156,4 @@ interface BuildUrlParams {
|
|
|
136
156
|
*/
|
|
137
157
|
declare const buildEmbedUrl: ({ operation, storeUrl }: BuildUrlParams) => string;
|
|
138
158
|
|
|
139
|
-
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 };
|
|
159
|
+
export { type DemoCompletedPayload, EmbedTester, type EmbedTesterProps, type PlayruoDemoCompletedMessage, PlayruoEmbed, type PlayruoEmbedActions, type PlayruoEmbedMessage, type PlayruoEmbedProps, type PlayruoEmbedRenderState, type PlayruoEmbedState, type PlayruoErrorMessage, type PlayruoEventMessage, type PlayruoEventPayload, type PlayruoInternalMessage, type PlayruoPurchaseRequestMessage, type PlayruoReadyMessage, type PlayruoRequestExitFullscreenMessage, type PlayruoSessionOverMessage, type PlayruoStateUpdateMessage, type PlayruoStreamStartMessage, type PlayruoStreamStopMessage, buildEmbedUrl, isPlayruoEmbedMessage, isPlayruoInternalMessage };
|
package/dist/index.js
CHANGED
|
@@ -34,14 +34,18 @@ var PlayruoEmbed = ({
|
|
|
34
34
|
onReady,
|
|
35
35
|
onStateChange,
|
|
36
36
|
onDemoCompleted,
|
|
37
|
+
onEvent,
|
|
37
38
|
onPurchaseRequest,
|
|
38
39
|
onError,
|
|
40
|
+
forwardEventsToParent = true,
|
|
39
41
|
renderState,
|
|
40
42
|
iframeRef: externalIframeRef
|
|
41
43
|
}) => {
|
|
42
44
|
const internalIframeRef = useRef(null);
|
|
43
45
|
const iframeRef = externalIframeRef || internalIframeRef;
|
|
44
46
|
const wasEmbedFullscreenRef = useRef(false);
|
|
47
|
+
const hasNotifiedCompletionRef = useRef(false);
|
|
48
|
+
const suppressNextFullscreenExitCleanupRef = useRef(false);
|
|
45
49
|
const [isIframeLoaded, setIsIframeLoaded] = useState(false);
|
|
46
50
|
const [hasPlayedOnce, setHasPlayedOnce] = useState(false);
|
|
47
51
|
const [showGameIframe, setShowGameIframe] = useState(false);
|
|
@@ -111,6 +115,149 @@ var PlayruoEmbed = ({
|
|
|
111
115
|
console.warn("[PlayruoEmbed] Failed to release Escape key lock:", error);
|
|
112
116
|
}
|
|
113
117
|
}, [getKeyboardApi]);
|
|
118
|
+
const getFullscreenElement = useCallback(() => {
|
|
119
|
+
const doc = document;
|
|
120
|
+
return doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement || null;
|
|
121
|
+
}, []);
|
|
122
|
+
const notifyDemoCompletedOnce = useCallback(
|
|
123
|
+
(payload) => {
|
|
124
|
+
if (hasNotifiedCompletionRef.current) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
hasNotifiedCompletionRef.current = true;
|
|
128
|
+
onDemoCompleted?.(
|
|
129
|
+
payload ?? {
|
|
130
|
+
reason: "Demo session ended",
|
|
131
|
+
storeUrl
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
},
|
|
135
|
+
[onDemoCompleted, storeUrl]
|
|
136
|
+
);
|
|
137
|
+
const postExitFullscreenToIframe = useCallback(() => {
|
|
138
|
+
const iframe = iframeRef.current;
|
|
139
|
+
if (!iframe) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
iframe.contentWindow?.postMessage({
|
|
144
|
+
type: "playruo",
|
|
145
|
+
action: "exitFullscreen",
|
|
146
|
+
fromEmbed: true,
|
|
147
|
+
isFullscreen: false,
|
|
148
|
+
parentWillHandleFullscreen: true
|
|
149
|
+
}, "*");
|
|
150
|
+
} catch {
|
|
151
|
+
console.error("[PlayruoEmbed] Failed to exit fullscreen");
|
|
152
|
+
}
|
|
153
|
+
}, [iframeRef]);
|
|
154
|
+
const postExitFullscreenAckToIframe = useCallback(() => {
|
|
155
|
+
const iframe = iframeRef.current;
|
|
156
|
+
if (!iframe) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
iframe.contentWindow?.postMessage({ type: "ACK_EXIT_FULLSCREEN" }, "*");
|
|
161
|
+
} catch {
|
|
162
|
+
console.error("[PlayruoEmbed] Failed to acknowledge fullscreen exit");
|
|
163
|
+
}
|
|
164
|
+
}, [iframeRef]);
|
|
165
|
+
const exitEmbedFullscreen = useCallback(
|
|
166
|
+
async ({
|
|
167
|
+
reason = "cleanup",
|
|
168
|
+
suppressFullscreenExitCleanup = true
|
|
169
|
+
} = {}) => {
|
|
170
|
+
const fullscreenElement = getFullscreenElement();
|
|
171
|
+
const doc = document;
|
|
172
|
+
const exitFullscreen = doc.exitFullscreen?.bind(doc) ?? doc.webkitExitFullscreen?.bind(doc) ?? doc.mozCancelFullScreen?.bind(doc) ?? doc.msExitFullscreen?.bind(doc);
|
|
173
|
+
const shouldAttemptExit = Boolean(fullscreenElement || wasEmbedFullscreenRef.current);
|
|
174
|
+
if (!shouldAttemptExit || !exitFullscreen) {
|
|
175
|
+
console.log("[PlayruoEmbed] Parent fullscreen exit skipped", {
|
|
176
|
+
hasFullscreenElement: Boolean(fullscreenElement),
|
|
177
|
+
hasExitFullscreen: Boolean(exitFullscreen),
|
|
178
|
+
reason,
|
|
179
|
+
wasEmbedFullscreen: wasEmbedFullscreenRef.current
|
|
180
|
+
});
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
console.log("[PlayruoEmbed] Parent fullscreen exit requested", {
|
|
184
|
+
fullscreenElement: fullscreenElement?.tagName,
|
|
185
|
+
reason,
|
|
186
|
+
wasEmbedFullscreen: wasEmbedFullscreenRef.current
|
|
187
|
+
});
|
|
188
|
+
const shouldSuppressFullscreenExitCleanup = Boolean(
|
|
189
|
+
suppressFullscreenExitCleanup && fullscreenElement
|
|
190
|
+
);
|
|
191
|
+
if (shouldSuppressFullscreenExitCleanup) {
|
|
192
|
+
suppressNextFullscreenExitCleanupRef.current = true;
|
|
193
|
+
wasEmbedFullscreenRef.current = false;
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
await exitFullscreen();
|
|
197
|
+
if (shouldSuppressFullscreenExitCleanup) {
|
|
198
|
+
window.setTimeout(() => {
|
|
199
|
+
suppressNextFullscreenExitCleanupRef.current = false;
|
|
200
|
+
}, 1e3);
|
|
201
|
+
}
|
|
202
|
+
return true;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (shouldSuppressFullscreenExitCleanup) {
|
|
205
|
+
suppressNextFullscreenExitCleanupRef.current = false;
|
|
206
|
+
wasEmbedFullscreenRef.current = Boolean(getFullscreenElement());
|
|
207
|
+
}
|
|
208
|
+
console.warn("[PlayruoEmbed] Failed to leave fullscreen:", error);
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
[getFullscreenElement]
|
|
213
|
+
);
|
|
214
|
+
const cleanupEmbedSession = useCallback(
|
|
215
|
+
({
|
|
216
|
+
payload,
|
|
217
|
+
notifyCompletion = true,
|
|
218
|
+
requestFullscreenExit = true,
|
|
219
|
+
notifyIframe = false,
|
|
220
|
+
markPlayed = true
|
|
221
|
+
} = {}) => {
|
|
222
|
+
setShowGameIframe(false);
|
|
223
|
+
if (markPlayed) {
|
|
224
|
+
setHasPlayedOnce(true);
|
|
225
|
+
}
|
|
226
|
+
releaseEscapeKeyLock();
|
|
227
|
+
if (notifyIframe) {
|
|
228
|
+
postExitFullscreenToIframe();
|
|
229
|
+
}
|
|
230
|
+
if (requestFullscreenExit) {
|
|
231
|
+
void exitEmbedFullscreen();
|
|
232
|
+
}
|
|
233
|
+
if (notifyCompletion) {
|
|
234
|
+
notifyDemoCompletedOnce(payload);
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
[
|
|
238
|
+
exitEmbedFullscreen,
|
|
239
|
+
notifyDemoCompletedOnce,
|
|
240
|
+
postExitFullscreenToIframe,
|
|
241
|
+
releaseEscapeKeyLock
|
|
242
|
+
]
|
|
243
|
+
);
|
|
244
|
+
const markStreamActive = useCallback(() => {
|
|
245
|
+
hasNotifiedCompletionRef.current = false;
|
|
246
|
+
setShowGameIframe(true);
|
|
247
|
+
}, []);
|
|
248
|
+
const forwardEventToParent = useCallback(
|
|
249
|
+
(message) => {
|
|
250
|
+
if (!forwardEventsToParent || typeof window === "undefined" || window.parent === window) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
window.parent.postMessage(message, "*");
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.warn("[PlayruoEmbed] Failed to forward event to parent:", error);
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
[forwardEventsToParent]
|
|
260
|
+
);
|
|
114
261
|
const lockEscapeKey = useCallback(async () => {
|
|
115
262
|
const keyboard = getKeyboardApi();
|
|
116
263
|
if (!keyboard?.lock) {
|
|
@@ -147,6 +294,7 @@ var PlayruoEmbed = ({
|
|
|
147
294
|
console.warn("[PlayruoEmbed] Iframe not ready");
|
|
148
295
|
return;
|
|
149
296
|
}
|
|
297
|
+
hasNotifiedCompletionRef.current = false;
|
|
150
298
|
setShowGameIframe(true);
|
|
151
299
|
try {
|
|
152
300
|
try {
|
|
@@ -170,6 +318,7 @@ var PlayruoEmbed = ({
|
|
|
170
318
|
setHasPlayedOnce(true);
|
|
171
319
|
} catch (error) {
|
|
172
320
|
console.error("[PlayruoEmbed] Failed to start game:", error);
|
|
321
|
+
setShowGameIframe(false);
|
|
173
322
|
setErrorMessage("Failed to start game");
|
|
174
323
|
onError?.("Failed to start game");
|
|
175
324
|
}
|
|
@@ -193,7 +342,8 @@ var PlayruoEmbed = ({
|
|
|
193
342
|
}, [isReady, onReady]);
|
|
194
343
|
useEffect(() => {
|
|
195
344
|
const handleGameMessage = (event) => {
|
|
196
|
-
const
|
|
345
|
+
const message = event.data || {};
|
|
346
|
+
const { type, data } = message;
|
|
197
347
|
const iframe = iframeRef.current;
|
|
198
348
|
if (!iframe || event.source !== iframe.contentWindow) {
|
|
199
349
|
return;
|
|
@@ -214,8 +364,7 @@ var PlayruoEmbed = ({
|
|
|
214
364
|
onPurchaseRequest?.(data);
|
|
215
365
|
} else if (type === "playruo_demo_completed" && data) {
|
|
216
366
|
console.log("\u{1F537} [Embed] playruo_demo_completed received:", data);
|
|
217
|
-
|
|
218
|
-
onDemoCompleted?.(data);
|
|
367
|
+
cleanupEmbedSession({ payload: data });
|
|
219
368
|
} else if (type === "playruo_error") {
|
|
220
369
|
const errorMessage2 = typeof data === "string" ? data : data?.message || "Unknown error";
|
|
221
370
|
console.log("\u{1F537} [Embed] playruo_error received:", errorMessage2);
|
|
@@ -236,25 +385,65 @@ var PlayruoEmbed = ({
|
|
|
236
385
|
onError?.(errorMessage2);
|
|
237
386
|
} else if (type === "playruo_session_over" && data) {
|
|
238
387
|
console.log("\u{1F537} [Embed] playruo_session_over received:", data);
|
|
239
|
-
|
|
388
|
+
void exitEmbedFullscreen({ reason: "playruo_session_over" });
|
|
240
389
|
setIframeState(data);
|
|
241
390
|
onStateChange?.(data);
|
|
391
|
+
cleanupEmbedSession({ requestFullscreenExit: false });
|
|
242
392
|
} else if (type === "playruo_stream_start") {
|
|
243
393
|
console.log("\u{1F537} [Embed] playruo_stream_start received");
|
|
394
|
+
markStreamActive();
|
|
395
|
+
} else if (type === "playruo_stream_stop") {
|
|
396
|
+
console.log("\u{1F537} [Embed] playruo_stream_stop received");
|
|
397
|
+
void exitEmbedFullscreen({ reason: "playruo_stream_stop" });
|
|
398
|
+
cleanupEmbedSession({
|
|
399
|
+
markPlayed: false,
|
|
400
|
+
notifyCompletion: false,
|
|
401
|
+
requestFullscreenExit: false
|
|
402
|
+
});
|
|
403
|
+
} else if (type === "REQUEST_EXIT_FULLSCREEN") {
|
|
404
|
+
console.log("\u{1F537} [Embed] REQUEST_EXIT_FULLSCREEN received");
|
|
405
|
+
void exitEmbedFullscreen({
|
|
406
|
+
reason: "REQUEST_EXIT_FULLSCREEN",
|
|
407
|
+
suppressFullscreenExitCleanup: false
|
|
408
|
+
}).finally(postExitFullscreenAckToIframe);
|
|
409
|
+
} else if (type === "playruo_event" && data && typeof data === "object") {
|
|
410
|
+
console.log("\u{1F537} [Embed] playruo_event received:", data);
|
|
411
|
+
const eventPayload = data;
|
|
412
|
+
onEvent?.(eventPayload);
|
|
413
|
+
forwardEventToParent({ type: "playruo_event", data: eventPayload });
|
|
414
|
+
} else if (type === "playruo" && message.action === "event" && message.payload && typeof message.payload === "object") {
|
|
415
|
+
console.log("\u{1F537} [Embed] legacy playruo event received:", message.payload);
|
|
416
|
+
const eventPayload = message.payload;
|
|
417
|
+
onEvent?.(eventPayload);
|
|
418
|
+
forwardEventToParent({ type: "playruo_event", data: eventPayload });
|
|
244
419
|
}
|
|
245
420
|
if (type === "error" || type === "closeForBuy" || type === "goToStore") {
|
|
246
421
|
console.log("\u{1F537} [Embed] Legacy message received:", type);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
422
|
+
cleanupEmbedSession({
|
|
423
|
+
payload: {
|
|
424
|
+
reason: "Demo session ended",
|
|
425
|
+
storeUrl
|
|
426
|
+
}
|
|
427
|
+
});
|
|
253
428
|
}
|
|
254
429
|
};
|
|
255
430
|
window.addEventListener("message", handleGameMessage);
|
|
256
431
|
return () => window.removeEventListener("message", handleGameMessage);
|
|
257
|
-
}, [
|
|
432
|
+
}, [
|
|
433
|
+
cleanupEmbedSession,
|
|
434
|
+
exitEmbedFullscreen,
|
|
435
|
+
forwardEventToParent,
|
|
436
|
+
hasPlayedOnce,
|
|
437
|
+
iframeRef,
|
|
438
|
+
markStreamActive,
|
|
439
|
+
onEvent,
|
|
440
|
+
onError,
|
|
441
|
+
onPurchaseRequest,
|
|
442
|
+
onReady,
|
|
443
|
+
onStateChange,
|
|
444
|
+
postExitFullscreenAckToIframe,
|
|
445
|
+
storeUrl
|
|
446
|
+
]);
|
|
258
447
|
const generateUUID = () => {
|
|
259
448
|
if (self.crypto.randomUUID) return self.crypto.randomUUID();
|
|
260
449
|
const bytes = new Uint8Array(16);
|
|
@@ -301,9 +490,8 @@ var PlayruoEmbed = ({
|
|
|
301
490
|
}, [iframeRef]);
|
|
302
491
|
useEffect(() => {
|
|
303
492
|
const handleFullscreenChange = () => {
|
|
304
|
-
const doc = document;
|
|
305
493
|
const iframe = iframeRef.current;
|
|
306
|
-
const fullscreenElement =
|
|
494
|
+
const fullscreenElement = getFullscreenElement();
|
|
307
495
|
const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
|
|
308
496
|
if (isEmbedFullscreen) {
|
|
309
497
|
wasEmbedFullscreenRef.current = true;
|
|
@@ -320,26 +508,15 @@ var PlayruoEmbed = ({
|
|
|
320
508
|
return;
|
|
321
509
|
}
|
|
322
510
|
releaseEscapeKeyLock();
|
|
511
|
+
if (suppressNextFullscreenExitCleanupRef.current) {
|
|
512
|
+
suppressNextFullscreenExitCleanupRef.current = false;
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
323
515
|
if (wasEmbedFullscreenRef.current) {
|
|
324
516
|
wasEmbedFullscreenRef.current = false;
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
type: "playruo",
|
|
329
|
-
action: "exitFullscreen",
|
|
330
|
-
fromEmbed: true,
|
|
331
|
-
isFullscreen: false,
|
|
332
|
-
parentWillHandleFullscreen: true
|
|
333
|
-
}, "*");
|
|
334
|
-
} catch {
|
|
335
|
-
console.error("[PlayruoEmbed] Failed to exit fullscreen");
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
setShowGameIframe(false);
|
|
339
|
-
setHasPlayedOnce(true);
|
|
340
|
-
onDemoCompleted?.({
|
|
341
|
-
reason: "Demo session ended",
|
|
342
|
-
storeUrl
|
|
517
|
+
cleanupEmbedSession({
|
|
518
|
+
notifyIframe: true,
|
|
519
|
+
requestFullscreenExit: false
|
|
343
520
|
});
|
|
344
521
|
}
|
|
345
522
|
};
|
|
@@ -353,7 +530,13 @@ var PlayruoEmbed = ({
|
|
|
353
530
|
document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
|
|
354
531
|
document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
|
|
355
532
|
};
|
|
356
|
-
}, [
|
|
533
|
+
}, [
|
|
534
|
+
cleanupEmbedSession,
|
|
535
|
+
getFullscreenElement,
|
|
536
|
+
iframeRef,
|
|
537
|
+
lockEscapeKey,
|
|
538
|
+
releaseEscapeKeyLock
|
|
539
|
+
]);
|
|
357
540
|
useEffect(() => {
|
|
358
541
|
return () => {
|
|
359
542
|
releaseEscapeKeyLock();
|
|
@@ -438,6 +621,8 @@ var PlayruoEmbed = ({
|
|
|
438
621
|
}
|
|
439
622
|
}, [isReady]);
|
|
440
623
|
const isAvailable = Boolean(isReady && isCompatible && hasIframeConfirmed);
|
|
624
|
+
const shouldDisplayGameIframe = showGameIframe;
|
|
625
|
+
const shouldShowIframeMisuseMessage = Boolean(showIframe && !showGameIframe);
|
|
441
626
|
const renderContent = () => {
|
|
442
627
|
if (!renderState) return null;
|
|
443
628
|
const canTryNow = Boolean(
|
|
@@ -467,6 +652,26 @@ var PlayruoEmbed = ({
|
|
|
467
652
|
};
|
|
468
653
|
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
469
654
|
renderContent(),
|
|
655
|
+
isCompatible && shouldShowIframeMisuseMessage && /* @__PURE__ */ jsx(
|
|
656
|
+
"div",
|
|
657
|
+
{
|
|
658
|
+
role: "status",
|
|
659
|
+
style: {
|
|
660
|
+
alignItems: "center",
|
|
661
|
+
background: "#000",
|
|
662
|
+
color: "#fff",
|
|
663
|
+
display: "flex",
|
|
664
|
+
fontFamily: "system-ui, sans-serif",
|
|
665
|
+
fontSize: 14,
|
|
666
|
+
height: "100%",
|
|
667
|
+
justifyContent: "center",
|
|
668
|
+
padding: 24,
|
|
669
|
+
textAlign: "center",
|
|
670
|
+
width: "100%"
|
|
671
|
+
},
|
|
672
|
+
children: "This iframe is only meant to be displayed through PlayruoEmbed during an active stream."
|
|
673
|
+
}
|
|
674
|
+
),
|
|
470
675
|
isCompatible && /* @__PURE__ */ jsx(
|
|
471
676
|
"iframe",
|
|
472
677
|
{
|
|
@@ -479,9 +684,9 @@ var PlayruoEmbed = ({
|
|
|
479
684
|
title: "Playruo",
|
|
480
685
|
referrerPolicy: "strict-origin-when-cross-origin",
|
|
481
686
|
style: {
|
|
482
|
-
//
|
|
483
|
-
opacity:
|
|
484
|
-
pointerEvents:
|
|
687
|
+
// The iframe stays mounted for readiness checks, visible only during launch/play, and hidden after terminal events.
|
|
688
|
+
opacity: shouldDisplayGameIframe ? 1 : 0,
|
|
689
|
+
pointerEvents: shouldDisplayGameIframe ? "auto" : "none",
|
|
485
690
|
width: "100%",
|
|
486
691
|
height: "100%",
|
|
487
692
|
border: 0
|
|
@@ -640,6 +845,13 @@ var EmbedTester = (props = {}) => {
|
|
|
640
845
|
console.error("Embed error:", error);
|
|
641
846
|
}
|
|
642
847
|
}, [addEvent, enableConsoleLog]);
|
|
848
|
+
const handleEvent = useCallback2((data) => {
|
|
849
|
+
const eventName = typeof data.event === "string" ? data.event : typeof data.name === "string" ? data.name : "unknown";
|
|
850
|
+
addEvent(`Event received: ${eventName}`);
|
|
851
|
+
if (enableConsoleLog) {
|
|
852
|
+
console.log("Embed event:", data);
|
|
853
|
+
}
|
|
854
|
+
}, [addEvent, enableConsoleLog]);
|
|
643
855
|
const handlePurchaseRequest = useCallback2((data) => {
|
|
644
856
|
addEvent(`\u{1F4B3} Explicit purchase request from Buy Now button`);
|
|
645
857
|
addEvent(`Opening store: ${data.storeUrl}`);
|
|
@@ -1587,6 +1799,7 @@ var EmbedTester = (props = {}) => {
|
|
|
1587
1799
|
iframeRef,
|
|
1588
1800
|
onDemoCompleted: handleDemoCompleted,
|
|
1589
1801
|
onError: handleError,
|
|
1802
|
+
onEvent: handleEvent,
|
|
1590
1803
|
onPurchaseRequest: handlePurchaseRequest,
|
|
1591
1804
|
onReady: handleReady,
|
|
1592
1805
|
onStateChange: handleStateChange,
|
|
@@ -1714,7 +1927,7 @@ var isPlayruoEmbedMessage = (data) => {
|
|
|
1714
1927
|
if (!data || typeof data !== "object") return false;
|
|
1715
1928
|
const msg = data;
|
|
1716
1929
|
if (typeof msg.type !== "string") return false;
|
|
1717
|
-
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";
|
|
1930
|
+
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" || msg.type === "REQUEST_EXIT_FULLSCREEN" || msg.type === "playruo_event";
|
|
1718
1931
|
};
|
|
1719
1932
|
var isPlayruoInternalMessage = (data) => {
|
|
1720
1933
|
if (!data || typeof data !== "object") return false;
|