@playruo/embed 1.3.1 → 1.3.3
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 +24 -0
- package/dist/index.cjs +340 -47
- package/dist/index.d.cts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +340 -47
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
1
|
# @playruo/embed
|
|
2
2
|
|
|
3
3
|
React SDK to embed Playruo via iframe + postMessage.
|
|
4
|
+
|
|
5
|
+
## Windowed launch
|
|
6
|
+
|
|
7
|
+
`PlayruoEmbed` requests browser fullscreen on the iframe when the user starts a
|
|
8
|
+
demo. This remains the default behavior.
|
|
9
|
+
|
|
10
|
+
Set `enableWindowedLaunch` to `true` to skip the parent fullscreen request and
|
|
11
|
+
start the experience inside the current iframe bounds:
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<PlayruoEmbed
|
|
15
|
+
operation="iframe"
|
|
16
|
+
enableWindowedLaunch
|
|
17
|
+
/>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Event forwarding
|
|
21
|
+
|
|
22
|
+
When the embedded Playruo iframe sends `{ type: "playruo_event", data }`, the
|
|
23
|
+
React package forwards the payload through `onEvent(data)`.
|
|
24
|
+
|
|
25
|
+
If the package host is itself inside another iframe, the same message is also
|
|
26
|
+
forwarded one level up with `window.parent.postMessage({ type:
|
|
27
|
+
"playruo_event", data }, "*")`.
|
package/dist/index.cjs
CHANGED
|
@@ -74,14 +74,19 @@ var PlayruoEmbed = ({
|
|
|
74
74
|
onReady,
|
|
75
75
|
onStateChange,
|
|
76
76
|
onDemoCompleted,
|
|
77
|
+
onEvent,
|
|
77
78
|
onPurchaseRequest,
|
|
78
79
|
onError,
|
|
80
|
+
enableWindowedLaunch = false,
|
|
81
|
+
forwardEventsToParent = true,
|
|
79
82
|
renderState,
|
|
80
83
|
iframeRef: externalIframeRef
|
|
81
84
|
}) => {
|
|
82
85
|
const internalIframeRef = (0, import_react.useRef)(null);
|
|
83
86
|
const iframeRef = externalIframeRef || internalIframeRef;
|
|
84
87
|
const wasEmbedFullscreenRef = (0, import_react.useRef)(false);
|
|
88
|
+
const hasNotifiedCompletionRef = (0, import_react.useRef)(false);
|
|
89
|
+
const suppressNextFullscreenExitCleanupRef = (0, import_react.useRef)(false);
|
|
85
90
|
const [isIframeLoaded, setIsIframeLoaded] = (0, import_react.useState)(false);
|
|
86
91
|
const [hasPlayedOnce, setHasPlayedOnce] = (0, import_react.useState)(false);
|
|
87
92
|
const [showGameIframe, setShowGameIframe] = (0, import_react.useState)(false);
|
|
@@ -151,6 +156,149 @@ var PlayruoEmbed = ({
|
|
|
151
156
|
console.warn("[PlayruoEmbed] Failed to release Escape key lock:", error);
|
|
152
157
|
}
|
|
153
158
|
}, [getKeyboardApi]);
|
|
159
|
+
const getFullscreenElement = (0, import_react.useCallback)(() => {
|
|
160
|
+
const doc = document;
|
|
161
|
+
return doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement || null;
|
|
162
|
+
}, []);
|
|
163
|
+
const notifyDemoCompletedOnce = (0, import_react.useCallback)(
|
|
164
|
+
(payload) => {
|
|
165
|
+
if (hasNotifiedCompletionRef.current) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
hasNotifiedCompletionRef.current = true;
|
|
169
|
+
onDemoCompleted?.(
|
|
170
|
+
payload ?? {
|
|
171
|
+
reason: "Demo session ended",
|
|
172
|
+
storeUrl
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
},
|
|
176
|
+
[onDemoCompleted, storeUrl]
|
|
177
|
+
);
|
|
178
|
+
const postExitFullscreenToIframe = (0, import_react.useCallback)(() => {
|
|
179
|
+
const iframe = iframeRef.current;
|
|
180
|
+
if (!iframe) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
iframe.contentWindow?.postMessage({
|
|
185
|
+
type: "playruo",
|
|
186
|
+
action: "exitFullscreen",
|
|
187
|
+
fromEmbed: true,
|
|
188
|
+
isFullscreen: false,
|
|
189
|
+
parentWillHandleFullscreen: true
|
|
190
|
+
}, "*");
|
|
191
|
+
} catch {
|
|
192
|
+
console.error("[PlayruoEmbed] Failed to exit fullscreen");
|
|
193
|
+
}
|
|
194
|
+
}, [iframeRef]);
|
|
195
|
+
const postExitFullscreenAckToIframe = (0, import_react.useCallback)(() => {
|
|
196
|
+
const iframe = iframeRef.current;
|
|
197
|
+
if (!iframe) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
iframe.contentWindow?.postMessage({ type: "ACK_EXIT_FULLSCREEN" }, "*");
|
|
202
|
+
} catch {
|
|
203
|
+
console.error("[PlayruoEmbed] Failed to acknowledge fullscreen exit");
|
|
204
|
+
}
|
|
205
|
+
}, [iframeRef]);
|
|
206
|
+
const exitEmbedFullscreen = (0, import_react.useCallback)(
|
|
207
|
+
async ({
|
|
208
|
+
reason = "cleanup",
|
|
209
|
+
suppressFullscreenExitCleanup = true
|
|
210
|
+
} = {}) => {
|
|
211
|
+
const fullscreenElement = getFullscreenElement();
|
|
212
|
+
const doc = document;
|
|
213
|
+
const exitFullscreen = doc.exitFullscreen?.bind(doc) ?? doc.webkitExitFullscreen?.bind(doc) ?? doc.mozCancelFullScreen?.bind(doc) ?? doc.msExitFullscreen?.bind(doc);
|
|
214
|
+
const shouldAttemptExit = Boolean(fullscreenElement || wasEmbedFullscreenRef.current);
|
|
215
|
+
if (!shouldAttemptExit || !exitFullscreen) {
|
|
216
|
+
console.log("[PlayruoEmbed] Parent fullscreen exit skipped", {
|
|
217
|
+
hasFullscreenElement: Boolean(fullscreenElement),
|
|
218
|
+
hasExitFullscreen: Boolean(exitFullscreen),
|
|
219
|
+
reason,
|
|
220
|
+
wasEmbedFullscreen: wasEmbedFullscreenRef.current
|
|
221
|
+
});
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
console.log("[PlayruoEmbed] Parent fullscreen exit requested", {
|
|
225
|
+
fullscreenElement: fullscreenElement?.tagName,
|
|
226
|
+
reason,
|
|
227
|
+
wasEmbedFullscreen: wasEmbedFullscreenRef.current
|
|
228
|
+
});
|
|
229
|
+
const shouldSuppressFullscreenExitCleanup = Boolean(
|
|
230
|
+
suppressFullscreenExitCleanup && fullscreenElement
|
|
231
|
+
);
|
|
232
|
+
if (shouldSuppressFullscreenExitCleanup) {
|
|
233
|
+
suppressNextFullscreenExitCleanupRef.current = true;
|
|
234
|
+
wasEmbedFullscreenRef.current = false;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
await exitFullscreen();
|
|
238
|
+
if (shouldSuppressFullscreenExitCleanup) {
|
|
239
|
+
window.setTimeout(() => {
|
|
240
|
+
suppressNextFullscreenExitCleanupRef.current = false;
|
|
241
|
+
}, 1e3);
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
244
|
+
} catch (error) {
|
|
245
|
+
if (shouldSuppressFullscreenExitCleanup) {
|
|
246
|
+
suppressNextFullscreenExitCleanupRef.current = false;
|
|
247
|
+
wasEmbedFullscreenRef.current = Boolean(getFullscreenElement());
|
|
248
|
+
}
|
|
249
|
+
console.warn("[PlayruoEmbed] Failed to leave fullscreen:", error);
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
[getFullscreenElement]
|
|
254
|
+
);
|
|
255
|
+
const cleanupEmbedSession = (0, import_react.useCallback)(
|
|
256
|
+
({
|
|
257
|
+
payload,
|
|
258
|
+
notifyCompletion = true,
|
|
259
|
+
requestFullscreenExit = true,
|
|
260
|
+
notifyIframe = false,
|
|
261
|
+
markPlayed = true
|
|
262
|
+
} = {}) => {
|
|
263
|
+
setShowGameIframe(false);
|
|
264
|
+
if (markPlayed) {
|
|
265
|
+
setHasPlayedOnce(true);
|
|
266
|
+
}
|
|
267
|
+
releaseEscapeKeyLock();
|
|
268
|
+
if (notifyIframe) {
|
|
269
|
+
postExitFullscreenToIframe();
|
|
270
|
+
}
|
|
271
|
+
if (requestFullscreenExit) {
|
|
272
|
+
void exitEmbedFullscreen();
|
|
273
|
+
}
|
|
274
|
+
if (notifyCompletion) {
|
|
275
|
+
notifyDemoCompletedOnce(payload);
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
[
|
|
279
|
+
exitEmbedFullscreen,
|
|
280
|
+
notifyDemoCompletedOnce,
|
|
281
|
+
postExitFullscreenToIframe,
|
|
282
|
+
releaseEscapeKeyLock
|
|
283
|
+
]
|
|
284
|
+
);
|
|
285
|
+
const markStreamActive = (0, import_react.useCallback)(() => {
|
|
286
|
+
hasNotifiedCompletionRef.current = false;
|
|
287
|
+
setShowGameIframe(true);
|
|
288
|
+
}, []);
|
|
289
|
+
const forwardEventToParent = (0, import_react.useCallback)(
|
|
290
|
+
(message) => {
|
|
291
|
+
if (!forwardEventsToParent || typeof window === "undefined" || window.parent === window) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
window.parent.postMessage(message, "*");
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.warn("[PlayruoEmbed] Failed to forward event to parent:", error);
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
[forwardEventsToParent]
|
|
301
|
+
);
|
|
154
302
|
const lockEscapeKey = (0, import_react.useCallback)(async () => {
|
|
155
303
|
const keyboard = getKeyboardApi();
|
|
156
304
|
if (!keyboard?.lock) {
|
|
@@ -187,19 +335,27 @@ var PlayruoEmbed = ({
|
|
|
187
335
|
console.warn("[PlayruoEmbed] Iframe not ready");
|
|
188
336
|
return;
|
|
189
337
|
}
|
|
338
|
+
hasNotifiedCompletionRef.current = false;
|
|
190
339
|
setShowGameIframe(true);
|
|
191
340
|
try {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
console.
|
|
341
|
+
let isParentFullscreen = false;
|
|
342
|
+
if (enableWindowedLaunch) {
|
|
343
|
+
releaseEscapeKeyLock();
|
|
344
|
+
console.log("[PlayruoEmbed] Windowed launch enabled, skipping fullscreen request");
|
|
345
|
+
} else {
|
|
346
|
+
try {
|
|
347
|
+
await requestImmersiveMode(iframe);
|
|
348
|
+
isParentFullscreen = true;
|
|
349
|
+
} catch (fsError) {
|
|
350
|
+
console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
|
|
351
|
+
}
|
|
196
352
|
}
|
|
197
353
|
iframe.contentWindow?.postMessage({
|
|
198
354
|
type: "playruo",
|
|
199
355
|
action: "tryNowClick",
|
|
200
356
|
fromEmbed: true,
|
|
201
|
-
isFullscreen:
|
|
202
|
-
parentWillHandleFullscreen:
|
|
357
|
+
isFullscreen: isParentFullscreen,
|
|
358
|
+
parentWillHandleFullscreen: isParentFullscreen,
|
|
203
359
|
isReplay: hasPlayedOnce
|
|
204
360
|
}, "*");
|
|
205
361
|
console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
|
|
@@ -210,10 +366,19 @@ var PlayruoEmbed = ({
|
|
|
210
366
|
setHasPlayedOnce(true);
|
|
211
367
|
} catch (error) {
|
|
212
368
|
console.error("[PlayruoEmbed] Failed to start game:", error);
|
|
369
|
+
setShowGameIframe(false);
|
|
213
370
|
setErrorMessage("Failed to start game");
|
|
214
371
|
onError?.("Failed to start game");
|
|
215
372
|
}
|
|
216
|
-
}, [
|
|
373
|
+
}, [
|
|
374
|
+
enableWindowedLaunch,
|
|
375
|
+
hasPlayedOnce,
|
|
376
|
+
iframeRef,
|
|
377
|
+
isCompatible,
|
|
378
|
+
onError,
|
|
379
|
+
releaseEscapeKeyLock,
|
|
380
|
+
requestImmersiveMode
|
|
381
|
+
]);
|
|
217
382
|
const handleBuyNow = (0, import_react.useCallback)(() => {
|
|
218
383
|
if (!storeUrl) {
|
|
219
384
|
console.warn("[PlayruoEmbed] No store URL provided");
|
|
@@ -233,7 +398,8 @@ var PlayruoEmbed = ({
|
|
|
233
398
|
}, [isReady, onReady]);
|
|
234
399
|
(0, import_react.useEffect)(() => {
|
|
235
400
|
const handleGameMessage = (event) => {
|
|
236
|
-
const
|
|
401
|
+
const message = event.data || {};
|
|
402
|
+
const { type, data } = message;
|
|
237
403
|
const iframe = iframeRef.current;
|
|
238
404
|
if (!iframe || event.source !== iframe.contentWindow) {
|
|
239
405
|
return;
|
|
@@ -254,8 +420,7 @@ var PlayruoEmbed = ({
|
|
|
254
420
|
onPurchaseRequest?.(data);
|
|
255
421
|
} else if (type === "playruo_demo_completed" && data) {
|
|
256
422
|
console.log("\u{1F537} [Embed] playruo_demo_completed received:", data);
|
|
257
|
-
|
|
258
|
-
onDemoCompleted?.(data);
|
|
423
|
+
cleanupEmbedSession({ payload: data });
|
|
259
424
|
} else if (type === "playruo_error") {
|
|
260
425
|
const errorMessage2 = typeof data === "string" ? data : data?.message || "Unknown error";
|
|
261
426
|
console.log("\u{1F537} [Embed] playruo_error received:", errorMessage2);
|
|
@@ -276,25 +441,65 @@ var PlayruoEmbed = ({
|
|
|
276
441
|
onError?.(errorMessage2);
|
|
277
442
|
} else if (type === "playruo_session_over" && data) {
|
|
278
443
|
console.log("\u{1F537} [Embed] playruo_session_over received:", data);
|
|
279
|
-
|
|
444
|
+
void exitEmbedFullscreen({ reason: "playruo_session_over" });
|
|
280
445
|
setIframeState(data);
|
|
281
446
|
onStateChange?.(data);
|
|
447
|
+
cleanupEmbedSession({ requestFullscreenExit: false });
|
|
282
448
|
} else if (type === "playruo_stream_start") {
|
|
283
449
|
console.log("\u{1F537} [Embed] playruo_stream_start received");
|
|
450
|
+
markStreamActive();
|
|
451
|
+
} else if (type === "playruo_stream_stop") {
|
|
452
|
+
console.log("\u{1F537} [Embed] playruo_stream_stop received");
|
|
453
|
+
void exitEmbedFullscreen({ reason: "playruo_stream_stop" });
|
|
454
|
+
cleanupEmbedSession({
|
|
455
|
+
markPlayed: false,
|
|
456
|
+
notifyCompletion: false,
|
|
457
|
+
requestFullscreenExit: false
|
|
458
|
+
});
|
|
459
|
+
} else if (type === "REQUEST_EXIT_FULLSCREEN") {
|
|
460
|
+
console.log("\u{1F537} [Embed] REQUEST_EXIT_FULLSCREEN received");
|
|
461
|
+
void exitEmbedFullscreen({
|
|
462
|
+
reason: "REQUEST_EXIT_FULLSCREEN",
|
|
463
|
+
suppressFullscreenExitCleanup: false
|
|
464
|
+
}).finally(postExitFullscreenAckToIframe);
|
|
465
|
+
} else if (type === "playruo_event" && data && typeof data === "object") {
|
|
466
|
+
console.log("\u{1F537} [Embed] playruo_event received:", data);
|
|
467
|
+
const eventPayload = data;
|
|
468
|
+
onEvent?.(eventPayload);
|
|
469
|
+
forwardEventToParent({ type: "playruo_event", data: eventPayload });
|
|
470
|
+
} else if (type === "playruo" && message.action === "event" && message.payload && typeof message.payload === "object") {
|
|
471
|
+
console.log("\u{1F537} [Embed] legacy playruo event received:", message.payload);
|
|
472
|
+
const eventPayload = message.payload;
|
|
473
|
+
onEvent?.(eventPayload);
|
|
474
|
+
forwardEventToParent({ type: "playruo_event", data: eventPayload });
|
|
284
475
|
}
|
|
285
476
|
if (type === "error" || type === "closeForBuy" || type === "goToStore") {
|
|
286
477
|
console.log("\u{1F537} [Embed] Legacy message received:", type);
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
478
|
+
cleanupEmbedSession({
|
|
479
|
+
payload: {
|
|
480
|
+
reason: "Demo session ended",
|
|
481
|
+
storeUrl
|
|
482
|
+
}
|
|
483
|
+
});
|
|
293
484
|
}
|
|
294
485
|
};
|
|
295
486
|
window.addEventListener("message", handleGameMessage);
|
|
296
487
|
return () => window.removeEventListener("message", handleGameMessage);
|
|
297
|
-
}, [
|
|
488
|
+
}, [
|
|
489
|
+
cleanupEmbedSession,
|
|
490
|
+
exitEmbedFullscreen,
|
|
491
|
+
forwardEventToParent,
|
|
492
|
+
hasPlayedOnce,
|
|
493
|
+
iframeRef,
|
|
494
|
+
markStreamActive,
|
|
495
|
+
onEvent,
|
|
496
|
+
onError,
|
|
497
|
+
onPurchaseRequest,
|
|
498
|
+
onReady,
|
|
499
|
+
onStateChange,
|
|
500
|
+
postExitFullscreenAckToIframe,
|
|
501
|
+
storeUrl
|
|
502
|
+
]);
|
|
298
503
|
const generateUUID = () => {
|
|
299
504
|
if (self.crypto.randomUUID) return self.crypto.randomUUID();
|
|
300
505
|
const bytes = new Uint8Array(16);
|
|
@@ -341,9 +546,8 @@ var PlayruoEmbed = ({
|
|
|
341
546
|
}, [iframeRef]);
|
|
342
547
|
(0, import_react.useEffect)(() => {
|
|
343
548
|
const handleFullscreenChange = () => {
|
|
344
|
-
const doc = document;
|
|
345
549
|
const iframe = iframeRef.current;
|
|
346
|
-
const fullscreenElement =
|
|
550
|
+
const fullscreenElement = getFullscreenElement();
|
|
347
551
|
const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
|
|
348
552
|
if (isEmbedFullscreen) {
|
|
349
553
|
wasEmbedFullscreenRef.current = true;
|
|
@@ -360,26 +564,15 @@ var PlayruoEmbed = ({
|
|
|
360
564
|
return;
|
|
361
565
|
}
|
|
362
566
|
releaseEscapeKeyLock();
|
|
567
|
+
if (suppressNextFullscreenExitCleanupRef.current) {
|
|
568
|
+
suppressNextFullscreenExitCleanupRef.current = false;
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
363
571
|
if (wasEmbedFullscreenRef.current) {
|
|
364
572
|
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
|
|
573
|
+
cleanupEmbedSession({
|
|
574
|
+
notifyIframe: true,
|
|
575
|
+
requestFullscreenExit: false
|
|
383
576
|
});
|
|
384
577
|
}
|
|
385
578
|
};
|
|
@@ -393,7 +586,13 @@ var PlayruoEmbed = ({
|
|
|
393
586
|
document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
|
|
394
587
|
document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
|
|
395
588
|
};
|
|
396
|
-
}, [
|
|
589
|
+
}, [
|
|
590
|
+
cleanupEmbedSession,
|
|
591
|
+
getFullscreenElement,
|
|
592
|
+
iframeRef,
|
|
593
|
+
lockEscapeKey,
|
|
594
|
+
releaseEscapeKeyLock
|
|
595
|
+
]);
|
|
397
596
|
(0, import_react.useEffect)(() => {
|
|
398
597
|
return () => {
|
|
399
598
|
releaseEscapeKeyLock();
|
|
@@ -478,6 +677,8 @@ var PlayruoEmbed = ({
|
|
|
478
677
|
}
|
|
479
678
|
}, [isReady]);
|
|
480
679
|
const isAvailable = Boolean(isReady && isCompatible && hasIframeConfirmed);
|
|
680
|
+
const shouldDisplayGameIframe = showGameIframe;
|
|
681
|
+
const shouldShowIframeMisuseMessage = Boolean(showIframe && !showGameIframe);
|
|
481
682
|
const renderContent = () => {
|
|
482
683
|
if (!renderState) return null;
|
|
483
684
|
const canTryNow = Boolean(
|
|
@@ -507,6 +708,26 @@ var PlayruoEmbed = ({
|
|
|
507
708
|
};
|
|
508
709
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, children: [
|
|
509
710
|
renderContent(),
|
|
711
|
+
isCompatible && shouldShowIframeMisuseMessage && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
712
|
+
"div",
|
|
713
|
+
{
|
|
714
|
+
role: "status",
|
|
715
|
+
style: {
|
|
716
|
+
alignItems: "center",
|
|
717
|
+
background: "#000",
|
|
718
|
+
color: "#fff",
|
|
719
|
+
display: "flex",
|
|
720
|
+
fontFamily: "system-ui, sans-serif",
|
|
721
|
+
fontSize: 14,
|
|
722
|
+
height: "100%",
|
|
723
|
+
justifyContent: "center",
|
|
724
|
+
padding: 24,
|
|
725
|
+
textAlign: "center",
|
|
726
|
+
width: "100%"
|
|
727
|
+
},
|
|
728
|
+
children: "This iframe is only meant to be displayed through PlayruoEmbed during an active stream."
|
|
729
|
+
}
|
|
730
|
+
),
|
|
510
731
|
isCompatible && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
511
732
|
"iframe",
|
|
512
733
|
{
|
|
@@ -519,9 +740,9 @@ var PlayruoEmbed = ({
|
|
|
519
740
|
title: "Playruo",
|
|
520
741
|
referrerPolicy: "strict-origin-when-cross-origin",
|
|
521
742
|
style: {
|
|
522
|
-
//
|
|
523
|
-
opacity:
|
|
524
|
-
pointerEvents:
|
|
743
|
+
// The iframe stays mounted for readiness checks, visible only during launch/play, and hidden after terminal events.
|
|
744
|
+
opacity: shouldDisplayGameIframe ? 1 : 0,
|
|
745
|
+
pointerEvents: shouldDisplayGameIframe ? "auto" : "none",
|
|
525
746
|
width: "100%",
|
|
526
747
|
height: "100%",
|
|
527
748
|
border: 0
|
|
@@ -577,10 +798,16 @@ var EmbedTester = (props = {}) => {
|
|
|
577
798
|
const [storeUrlDraft, setStoreUrlDraft] = (0, import_react2.useState)(
|
|
578
799
|
props.storeUrl ?? "https://store.steampowered.com/app/123456"
|
|
579
800
|
);
|
|
801
|
+
const [enableWindowedLaunchDraft, setEnableWindowedLaunchDraft] = (0, import_react2.useState)(
|
|
802
|
+
props.enableWindowedLaunch ?? false
|
|
803
|
+
);
|
|
580
804
|
const [appliedOperation, setAppliedOperation] = (0, import_react2.useState)(props.operation ?? "iframe");
|
|
581
805
|
const [appliedStoreUrl, setAppliedStoreUrl] = (0, import_react2.useState)(
|
|
582
806
|
props.storeUrl ?? "https://store.steampowered.com/app/123456"
|
|
583
807
|
);
|
|
808
|
+
const [appliedEnableWindowedLaunch, setAppliedEnableWindowedLaunch] = (0, import_react2.useState)(
|
|
809
|
+
props.enableWindowedLaunch ?? false
|
|
810
|
+
);
|
|
584
811
|
const [key, setKey] = (0, import_react2.useState)(0);
|
|
585
812
|
const [hasUserClicked, setHasUserClicked] = (0, import_react2.useState)(false);
|
|
586
813
|
const [showIframe, setShowIframe] = (0, import_react2.useState)(false);
|
|
@@ -595,8 +822,9 @@ var EmbedTester = (props = {}) => {
|
|
|
595
822
|
const iframeRef = (0, import_react2.useRef)(null);
|
|
596
823
|
const isOperationPending = operationDraft !== appliedOperation;
|
|
597
824
|
const isStoreUrlPending = storeUrlDraft !== appliedStoreUrl;
|
|
598
|
-
const
|
|
599
|
-
const
|
|
825
|
+
const isWindowedLaunchPending = enableWindowedLaunchDraft !== appliedEnableWindowedLaunch;
|
|
826
|
+
const hasPendingConfigChanges = isOperationPending || isStoreUrlPending || isWindowedLaunchPending;
|
|
827
|
+
const pendingChangeCount = Number(isOperationPending) + Number(isStoreUrlPending) + Number(isWindowedLaunchPending);
|
|
600
828
|
const addEvent = (0, import_react2.useCallback)(
|
|
601
829
|
(message) => {
|
|
602
830
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
@@ -680,6 +908,13 @@ var EmbedTester = (props = {}) => {
|
|
|
680
908
|
console.error("Embed error:", error);
|
|
681
909
|
}
|
|
682
910
|
}, [addEvent, enableConsoleLog]);
|
|
911
|
+
const handleEvent = (0, import_react2.useCallback)((data) => {
|
|
912
|
+
const eventName = typeof data.event === "string" ? data.event : typeof data.name === "string" ? data.name : "unknown";
|
|
913
|
+
addEvent(`Event received: ${eventName}`);
|
|
914
|
+
if (enableConsoleLog) {
|
|
915
|
+
console.log("Embed event:", data);
|
|
916
|
+
}
|
|
917
|
+
}, [addEvent, enableConsoleLog]);
|
|
683
918
|
const handlePurchaseRequest = (0, import_react2.useCallback)((data) => {
|
|
684
919
|
addEvent(`\u{1F4B3} Explicit purchase request from Buy Now button`);
|
|
685
920
|
addEvent(`Opening store: ${data.storeUrl}`);
|
|
@@ -700,16 +935,24 @@ var EmbedTester = (props = {}) => {
|
|
|
700
935
|
}
|
|
701
936
|
setAppliedOperation(operationDraft);
|
|
702
937
|
setAppliedStoreUrl(storeUrlDraft);
|
|
938
|
+
setAppliedEnableWindowedLaunch(enableWindowedLaunchDraft);
|
|
703
939
|
resetEmbedSession("Applied updated embed settings");
|
|
704
|
-
}, [
|
|
940
|
+
}, [
|
|
941
|
+
enableWindowedLaunchDraft,
|
|
942
|
+
hasPendingConfigChanges,
|
|
943
|
+
operationDraft,
|
|
944
|
+
resetEmbedSession,
|
|
945
|
+
storeUrlDraft
|
|
946
|
+
]);
|
|
705
947
|
const handleRestartSession = (0, import_react2.useCallback)(() => {
|
|
706
948
|
resetEmbedSession("Embed restarted with applied settings");
|
|
707
949
|
}, [resetEmbedSession]);
|
|
708
950
|
const handleResetDrafts = (0, import_react2.useCallback)(() => {
|
|
709
951
|
setOperationDraft(appliedOperation);
|
|
710
952
|
setStoreUrlDraft(appliedStoreUrl);
|
|
953
|
+
setEnableWindowedLaunchDraft(appliedEnableWindowedLaunch);
|
|
711
954
|
addEvent("Reverted draft changes to the applied configuration");
|
|
712
|
-
}, [addEvent, appliedOperation, appliedStoreUrl]);
|
|
955
|
+
}, [addEvent, appliedEnableWindowedLaunch, appliedOperation, appliedStoreUrl]);
|
|
713
956
|
const panelStyle = {
|
|
714
957
|
position: "relative",
|
|
715
958
|
border: "1px solid rgba(148, 163, 184, 0.18)",
|
|
@@ -1277,6 +1520,50 @@ var EmbedTester = (props = {}) => {
|
|
|
1277
1520
|
"Applied value: ",
|
|
1278
1521
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { style: codeStyle, children: appliedStoreUrl })
|
|
1279
1522
|
] })
|
|
1523
|
+
] }),
|
|
1524
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
1525
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: labelStyle, children: [
|
|
1526
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Launch mode" }),
|
|
1527
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: buildIndicatorStyle(isWindowedLaunchPending), children: [
|
|
1528
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: buildIndicatorDotStyle(isWindowedLaunchPending) }),
|
|
1529
|
+
isWindowedLaunchPending ? "Pending" : "Live"
|
|
1530
|
+
] })
|
|
1531
|
+
] }),
|
|
1532
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1533
|
+
"label",
|
|
1534
|
+
{
|
|
1535
|
+
htmlFor: "enableWindowedLaunch",
|
|
1536
|
+
style: {
|
|
1537
|
+
...buildInputShellStyle(isWindowedLaunchPending),
|
|
1538
|
+
justifyContent: "space-between",
|
|
1539
|
+
minHeight: "48px",
|
|
1540
|
+
cursor: "pointer"
|
|
1541
|
+
},
|
|
1542
|
+
children: [
|
|
1543
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#f8fafc", fontSize: "15px", fontWeight: 600 }, children: "Windowed launch" }),
|
|
1544
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1545
|
+
"input",
|
|
1546
|
+
{
|
|
1547
|
+
id: "enableWindowedLaunch",
|
|
1548
|
+
type: "checkbox",
|
|
1549
|
+
checked: enableWindowedLaunchDraft,
|
|
1550
|
+
onChange: (event) => setEnableWindowedLaunchDraft(event.target.checked),
|
|
1551
|
+
style: {
|
|
1552
|
+
width: "18px",
|
|
1553
|
+
height: "18px",
|
|
1554
|
+
accentColor: "#38bdf8",
|
|
1555
|
+
cursor: "pointer"
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
)
|
|
1559
|
+
]
|
|
1560
|
+
}
|
|
1561
|
+
),
|
|
1562
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { style: helperTextStyle, children: [
|
|
1563
|
+
"Applied value:",
|
|
1564
|
+
" ",
|
|
1565
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { style: codeStyle, children: appliedEnableWindowedLaunch ? "windowed" : "fullscreen request" })
|
|
1566
|
+
] })
|
|
1280
1567
|
] })
|
|
1281
1568
|
] }),
|
|
1282
1569
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
@@ -1556,6 +1843,10 @@ var EmbedTester = (props = {}) => {
|
|
|
1556
1843
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { ...codeStyle, padding: "6px 10px" }, children: [
|
|
1557
1844
|
"Operation: ",
|
|
1558
1845
|
appliedOperation
|
|
1846
|
+
] }),
|
|
1847
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { ...codeStyle, padding: "6px 10px" }, children: [
|
|
1848
|
+
"Launch: ",
|
|
1849
|
+
appliedEnableWindowedLaunch ? "windowed" : "fullscreen request"
|
|
1559
1850
|
] })
|
|
1560
1851
|
] })
|
|
1561
1852
|
]
|
|
@@ -1627,9 +1918,11 @@ var EmbedTester = (props = {}) => {
|
|
|
1627
1918
|
iframeRef,
|
|
1628
1919
|
onDemoCompleted: handleDemoCompleted,
|
|
1629
1920
|
onError: handleError,
|
|
1921
|
+
onEvent: handleEvent,
|
|
1630
1922
|
onPurchaseRequest: handlePurchaseRequest,
|
|
1631
1923
|
onReady: handleReady,
|
|
1632
1924
|
onStateChange: handleStateChange,
|
|
1925
|
+
enableWindowedLaunch: appliedEnableWindowedLaunch,
|
|
1633
1926
|
operation: appliedOperation,
|
|
1634
1927
|
renderState: ({ actions, message, status }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: !hasUserClicked && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1635
1928
|
"div",
|
|
@@ -1754,7 +2047,7 @@ var isPlayruoEmbedMessage = (data) => {
|
|
|
1754
2047
|
if (!data || typeof data !== "object") return false;
|
|
1755
2048
|
const msg = data;
|
|
1756
2049
|
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";
|
|
2050
|
+
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
2051
|
};
|
|
1759
2052
|
var isPlayruoInternalMessage = (data) => {
|
|
1760
2053
|
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,22 @@ 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
|
+
* Skip the parent fullscreen request and start the experience inside the
|
|
60
|
+
* current iframe bounds. Defaults to false to preserve the current behavior.
|
|
61
|
+
*/
|
|
62
|
+
enableWindowedLaunch?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Forward playruo_event messages to the parent window when the package host
|
|
65
|
+
* itself is embedded in another iframe. Direct host pages already receive the
|
|
66
|
+
* original iframe postMessage.
|
|
67
|
+
*/
|
|
68
|
+
forwardEventsToParent?: boolean;
|
|
51
69
|
/**
|
|
52
70
|
* Single render entry point representing the current status.
|
|
53
71
|
* Provides status, message, and available actions based on current state.
|
|
@@ -66,6 +84,8 @@ interface EmbedTesterProps {
|
|
|
66
84
|
enableConsoleLog?: boolean;
|
|
67
85
|
operation?: string;
|
|
68
86
|
storeUrl?: string;
|
|
87
|
+
/** Start the experience inside the iframe without requesting parent fullscreen. */
|
|
88
|
+
enableWindowedLaunch?: boolean;
|
|
69
89
|
className?: string;
|
|
70
90
|
}
|
|
71
91
|
declare const EmbedTester: (props?: EmbedTesterProps) => react_jsx_runtime.JSX.Element;
|
|
@@ -103,7 +123,14 @@ interface PlayruoStreamStartMessage {
|
|
|
103
123
|
interface PlayruoStreamStopMessage {
|
|
104
124
|
type: "playruo_stream_stop";
|
|
105
125
|
}
|
|
106
|
-
|
|
126
|
+
interface PlayruoRequestExitFullscreenMessage {
|
|
127
|
+
type: "REQUEST_EXIT_FULLSCREEN";
|
|
128
|
+
}
|
|
129
|
+
interface PlayruoEventMessage {
|
|
130
|
+
type: "playruo_event";
|
|
131
|
+
data: PlayruoEventPayload;
|
|
132
|
+
}
|
|
133
|
+
type PlayruoEmbedMessage = PlayruoReadyMessage | PlayruoStateUpdateMessage | PlayruoPurchaseRequestMessage | PlayruoDemoCompletedMessage | PlayruoErrorMessage | PlayruoSessionOverMessage | PlayruoStreamStartMessage | PlayruoStreamStopMessage | PlayruoRequestExitFullscreenMessage | PlayruoEventMessage;
|
|
107
134
|
interface PlayruoInternalMessage {
|
|
108
135
|
type: "playruo";
|
|
109
136
|
action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
|
|
@@ -136,4 +163,4 @@ interface BuildUrlParams {
|
|
|
136
163
|
*/
|
|
137
164
|
declare const buildEmbedUrl: ({ operation, storeUrl }: BuildUrlParams) => string;
|
|
138
165
|
|
|
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 };
|
|
166
|
+
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,22 @@ 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
|
+
* Skip the parent fullscreen request and start the experience inside the
|
|
60
|
+
* current iframe bounds. Defaults to false to preserve the current behavior.
|
|
61
|
+
*/
|
|
62
|
+
enableWindowedLaunch?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Forward playruo_event messages to the parent window when the package host
|
|
65
|
+
* itself is embedded in another iframe. Direct host pages already receive the
|
|
66
|
+
* original iframe postMessage.
|
|
67
|
+
*/
|
|
68
|
+
forwardEventsToParent?: boolean;
|
|
51
69
|
/**
|
|
52
70
|
* Single render entry point representing the current status.
|
|
53
71
|
* Provides status, message, and available actions based on current state.
|
|
@@ -66,6 +84,8 @@ interface EmbedTesterProps {
|
|
|
66
84
|
enableConsoleLog?: boolean;
|
|
67
85
|
operation?: string;
|
|
68
86
|
storeUrl?: string;
|
|
87
|
+
/** Start the experience inside the iframe without requesting parent fullscreen. */
|
|
88
|
+
enableWindowedLaunch?: boolean;
|
|
69
89
|
className?: string;
|
|
70
90
|
}
|
|
71
91
|
declare const EmbedTester: (props?: EmbedTesterProps) => react_jsx_runtime.JSX.Element;
|
|
@@ -103,7 +123,14 @@ interface PlayruoStreamStartMessage {
|
|
|
103
123
|
interface PlayruoStreamStopMessage {
|
|
104
124
|
type: "playruo_stream_stop";
|
|
105
125
|
}
|
|
106
|
-
|
|
126
|
+
interface PlayruoRequestExitFullscreenMessage {
|
|
127
|
+
type: "REQUEST_EXIT_FULLSCREEN";
|
|
128
|
+
}
|
|
129
|
+
interface PlayruoEventMessage {
|
|
130
|
+
type: "playruo_event";
|
|
131
|
+
data: PlayruoEventPayload;
|
|
132
|
+
}
|
|
133
|
+
type PlayruoEmbedMessage = PlayruoReadyMessage | PlayruoStateUpdateMessage | PlayruoPurchaseRequestMessage | PlayruoDemoCompletedMessage | PlayruoErrorMessage | PlayruoSessionOverMessage | PlayruoStreamStartMessage | PlayruoStreamStopMessage | PlayruoRequestExitFullscreenMessage | PlayruoEventMessage;
|
|
107
134
|
interface PlayruoInternalMessage {
|
|
108
135
|
type: "playruo";
|
|
109
136
|
action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
|
|
@@ -136,4 +163,4 @@ interface BuildUrlParams {
|
|
|
136
163
|
*/
|
|
137
164
|
declare const buildEmbedUrl: ({ operation, storeUrl }: BuildUrlParams) => string;
|
|
138
165
|
|
|
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 };
|
|
166
|
+
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,19 @@ var PlayruoEmbed = ({
|
|
|
34
34
|
onReady,
|
|
35
35
|
onStateChange,
|
|
36
36
|
onDemoCompleted,
|
|
37
|
+
onEvent,
|
|
37
38
|
onPurchaseRequest,
|
|
38
39
|
onError,
|
|
40
|
+
enableWindowedLaunch = false,
|
|
41
|
+
forwardEventsToParent = true,
|
|
39
42
|
renderState,
|
|
40
43
|
iframeRef: externalIframeRef
|
|
41
44
|
}) => {
|
|
42
45
|
const internalIframeRef = useRef(null);
|
|
43
46
|
const iframeRef = externalIframeRef || internalIframeRef;
|
|
44
47
|
const wasEmbedFullscreenRef = useRef(false);
|
|
48
|
+
const hasNotifiedCompletionRef = useRef(false);
|
|
49
|
+
const suppressNextFullscreenExitCleanupRef = useRef(false);
|
|
45
50
|
const [isIframeLoaded, setIsIframeLoaded] = useState(false);
|
|
46
51
|
const [hasPlayedOnce, setHasPlayedOnce] = useState(false);
|
|
47
52
|
const [showGameIframe, setShowGameIframe] = useState(false);
|
|
@@ -111,6 +116,149 @@ var PlayruoEmbed = ({
|
|
|
111
116
|
console.warn("[PlayruoEmbed] Failed to release Escape key lock:", error);
|
|
112
117
|
}
|
|
113
118
|
}, [getKeyboardApi]);
|
|
119
|
+
const getFullscreenElement = useCallback(() => {
|
|
120
|
+
const doc = document;
|
|
121
|
+
return doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement || null;
|
|
122
|
+
}, []);
|
|
123
|
+
const notifyDemoCompletedOnce = useCallback(
|
|
124
|
+
(payload) => {
|
|
125
|
+
if (hasNotifiedCompletionRef.current) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
hasNotifiedCompletionRef.current = true;
|
|
129
|
+
onDemoCompleted?.(
|
|
130
|
+
payload ?? {
|
|
131
|
+
reason: "Demo session ended",
|
|
132
|
+
storeUrl
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
},
|
|
136
|
+
[onDemoCompleted, storeUrl]
|
|
137
|
+
);
|
|
138
|
+
const postExitFullscreenToIframe = useCallback(() => {
|
|
139
|
+
const iframe = iframeRef.current;
|
|
140
|
+
if (!iframe) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
iframe.contentWindow?.postMessage({
|
|
145
|
+
type: "playruo",
|
|
146
|
+
action: "exitFullscreen",
|
|
147
|
+
fromEmbed: true,
|
|
148
|
+
isFullscreen: false,
|
|
149
|
+
parentWillHandleFullscreen: true
|
|
150
|
+
}, "*");
|
|
151
|
+
} catch {
|
|
152
|
+
console.error("[PlayruoEmbed] Failed to exit fullscreen");
|
|
153
|
+
}
|
|
154
|
+
}, [iframeRef]);
|
|
155
|
+
const postExitFullscreenAckToIframe = useCallback(() => {
|
|
156
|
+
const iframe = iframeRef.current;
|
|
157
|
+
if (!iframe) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
iframe.contentWindow?.postMessage({ type: "ACK_EXIT_FULLSCREEN" }, "*");
|
|
162
|
+
} catch {
|
|
163
|
+
console.error("[PlayruoEmbed] Failed to acknowledge fullscreen exit");
|
|
164
|
+
}
|
|
165
|
+
}, [iframeRef]);
|
|
166
|
+
const exitEmbedFullscreen = useCallback(
|
|
167
|
+
async ({
|
|
168
|
+
reason = "cleanup",
|
|
169
|
+
suppressFullscreenExitCleanup = true
|
|
170
|
+
} = {}) => {
|
|
171
|
+
const fullscreenElement = getFullscreenElement();
|
|
172
|
+
const doc = document;
|
|
173
|
+
const exitFullscreen = doc.exitFullscreen?.bind(doc) ?? doc.webkitExitFullscreen?.bind(doc) ?? doc.mozCancelFullScreen?.bind(doc) ?? doc.msExitFullscreen?.bind(doc);
|
|
174
|
+
const shouldAttemptExit = Boolean(fullscreenElement || wasEmbedFullscreenRef.current);
|
|
175
|
+
if (!shouldAttemptExit || !exitFullscreen) {
|
|
176
|
+
console.log("[PlayruoEmbed] Parent fullscreen exit skipped", {
|
|
177
|
+
hasFullscreenElement: Boolean(fullscreenElement),
|
|
178
|
+
hasExitFullscreen: Boolean(exitFullscreen),
|
|
179
|
+
reason,
|
|
180
|
+
wasEmbedFullscreen: wasEmbedFullscreenRef.current
|
|
181
|
+
});
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
console.log("[PlayruoEmbed] Parent fullscreen exit requested", {
|
|
185
|
+
fullscreenElement: fullscreenElement?.tagName,
|
|
186
|
+
reason,
|
|
187
|
+
wasEmbedFullscreen: wasEmbedFullscreenRef.current
|
|
188
|
+
});
|
|
189
|
+
const shouldSuppressFullscreenExitCleanup = Boolean(
|
|
190
|
+
suppressFullscreenExitCleanup && fullscreenElement
|
|
191
|
+
);
|
|
192
|
+
if (shouldSuppressFullscreenExitCleanup) {
|
|
193
|
+
suppressNextFullscreenExitCleanupRef.current = true;
|
|
194
|
+
wasEmbedFullscreenRef.current = false;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
await exitFullscreen();
|
|
198
|
+
if (shouldSuppressFullscreenExitCleanup) {
|
|
199
|
+
window.setTimeout(() => {
|
|
200
|
+
suppressNextFullscreenExitCleanupRef.current = false;
|
|
201
|
+
}, 1e3);
|
|
202
|
+
}
|
|
203
|
+
return true;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (shouldSuppressFullscreenExitCleanup) {
|
|
206
|
+
suppressNextFullscreenExitCleanupRef.current = false;
|
|
207
|
+
wasEmbedFullscreenRef.current = Boolean(getFullscreenElement());
|
|
208
|
+
}
|
|
209
|
+
console.warn("[PlayruoEmbed] Failed to leave fullscreen:", error);
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
[getFullscreenElement]
|
|
214
|
+
);
|
|
215
|
+
const cleanupEmbedSession = useCallback(
|
|
216
|
+
({
|
|
217
|
+
payload,
|
|
218
|
+
notifyCompletion = true,
|
|
219
|
+
requestFullscreenExit = true,
|
|
220
|
+
notifyIframe = false,
|
|
221
|
+
markPlayed = true
|
|
222
|
+
} = {}) => {
|
|
223
|
+
setShowGameIframe(false);
|
|
224
|
+
if (markPlayed) {
|
|
225
|
+
setHasPlayedOnce(true);
|
|
226
|
+
}
|
|
227
|
+
releaseEscapeKeyLock();
|
|
228
|
+
if (notifyIframe) {
|
|
229
|
+
postExitFullscreenToIframe();
|
|
230
|
+
}
|
|
231
|
+
if (requestFullscreenExit) {
|
|
232
|
+
void exitEmbedFullscreen();
|
|
233
|
+
}
|
|
234
|
+
if (notifyCompletion) {
|
|
235
|
+
notifyDemoCompletedOnce(payload);
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
[
|
|
239
|
+
exitEmbedFullscreen,
|
|
240
|
+
notifyDemoCompletedOnce,
|
|
241
|
+
postExitFullscreenToIframe,
|
|
242
|
+
releaseEscapeKeyLock
|
|
243
|
+
]
|
|
244
|
+
);
|
|
245
|
+
const markStreamActive = useCallback(() => {
|
|
246
|
+
hasNotifiedCompletionRef.current = false;
|
|
247
|
+
setShowGameIframe(true);
|
|
248
|
+
}, []);
|
|
249
|
+
const forwardEventToParent = useCallback(
|
|
250
|
+
(message) => {
|
|
251
|
+
if (!forwardEventsToParent || typeof window === "undefined" || window.parent === window) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
window.parent.postMessage(message, "*");
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.warn("[PlayruoEmbed] Failed to forward event to parent:", error);
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
[forwardEventsToParent]
|
|
261
|
+
);
|
|
114
262
|
const lockEscapeKey = useCallback(async () => {
|
|
115
263
|
const keyboard = getKeyboardApi();
|
|
116
264
|
if (!keyboard?.lock) {
|
|
@@ -147,19 +295,27 @@ var PlayruoEmbed = ({
|
|
|
147
295
|
console.warn("[PlayruoEmbed] Iframe not ready");
|
|
148
296
|
return;
|
|
149
297
|
}
|
|
298
|
+
hasNotifiedCompletionRef.current = false;
|
|
150
299
|
setShowGameIframe(true);
|
|
151
300
|
try {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
console.
|
|
301
|
+
let isParentFullscreen = false;
|
|
302
|
+
if (enableWindowedLaunch) {
|
|
303
|
+
releaseEscapeKeyLock();
|
|
304
|
+
console.log("[PlayruoEmbed] Windowed launch enabled, skipping fullscreen request");
|
|
305
|
+
} else {
|
|
306
|
+
try {
|
|
307
|
+
await requestImmersiveMode(iframe);
|
|
308
|
+
isParentFullscreen = true;
|
|
309
|
+
} catch (fsError) {
|
|
310
|
+
console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
|
|
311
|
+
}
|
|
156
312
|
}
|
|
157
313
|
iframe.contentWindow?.postMessage({
|
|
158
314
|
type: "playruo",
|
|
159
315
|
action: "tryNowClick",
|
|
160
316
|
fromEmbed: true,
|
|
161
|
-
isFullscreen:
|
|
162
|
-
parentWillHandleFullscreen:
|
|
317
|
+
isFullscreen: isParentFullscreen,
|
|
318
|
+
parentWillHandleFullscreen: isParentFullscreen,
|
|
163
319
|
isReplay: hasPlayedOnce
|
|
164
320
|
}, "*");
|
|
165
321
|
console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
|
|
@@ -170,10 +326,19 @@ var PlayruoEmbed = ({
|
|
|
170
326
|
setHasPlayedOnce(true);
|
|
171
327
|
} catch (error) {
|
|
172
328
|
console.error("[PlayruoEmbed] Failed to start game:", error);
|
|
329
|
+
setShowGameIframe(false);
|
|
173
330
|
setErrorMessage("Failed to start game");
|
|
174
331
|
onError?.("Failed to start game");
|
|
175
332
|
}
|
|
176
|
-
}, [
|
|
333
|
+
}, [
|
|
334
|
+
enableWindowedLaunch,
|
|
335
|
+
hasPlayedOnce,
|
|
336
|
+
iframeRef,
|
|
337
|
+
isCompatible,
|
|
338
|
+
onError,
|
|
339
|
+
releaseEscapeKeyLock,
|
|
340
|
+
requestImmersiveMode
|
|
341
|
+
]);
|
|
177
342
|
const handleBuyNow = useCallback(() => {
|
|
178
343
|
if (!storeUrl) {
|
|
179
344
|
console.warn("[PlayruoEmbed] No store URL provided");
|
|
@@ -193,7 +358,8 @@ var PlayruoEmbed = ({
|
|
|
193
358
|
}, [isReady, onReady]);
|
|
194
359
|
useEffect(() => {
|
|
195
360
|
const handleGameMessage = (event) => {
|
|
196
|
-
const
|
|
361
|
+
const message = event.data || {};
|
|
362
|
+
const { type, data } = message;
|
|
197
363
|
const iframe = iframeRef.current;
|
|
198
364
|
if (!iframe || event.source !== iframe.contentWindow) {
|
|
199
365
|
return;
|
|
@@ -214,8 +380,7 @@ var PlayruoEmbed = ({
|
|
|
214
380
|
onPurchaseRequest?.(data);
|
|
215
381
|
} else if (type === "playruo_demo_completed" && data) {
|
|
216
382
|
console.log("\u{1F537} [Embed] playruo_demo_completed received:", data);
|
|
217
|
-
|
|
218
|
-
onDemoCompleted?.(data);
|
|
383
|
+
cleanupEmbedSession({ payload: data });
|
|
219
384
|
} else if (type === "playruo_error") {
|
|
220
385
|
const errorMessage2 = typeof data === "string" ? data : data?.message || "Unknown error";
|
|
221
386
|
console.log("\u{1F537} [Embed] playruo_error received:", errorMessage2);
|
|
@@ -236,25 +401,65 @@ var PlayruoEmbed = ({
|
|
|
236
401
|
onError?.(errorMessage2);
|
|
237
402
|
} else if (type === "playruo_session_over" && data) {
|
|
238
403
|
console.log("\u{1F537} [Embed] playruo_session_over received:", data);
|
|
239
|
-
|
|
404
|
+
void exitEmbedFullscreen({ reason: "playruo_session_over" });
|
|
240
405
|
setIframeState(data);
|
|
241
406
|
onStateChange?.(data);
|
|
407
|
+
cleanupEmbedSession({ requestFullscreenExit: false });
|
|
242
408
|
} else if (type === "playruo_stream_start") {
|
|
243
409
|
console.log("\u{1F537} [Embed] playruo_stream_start received");
|
|
410
|
+
markStreamActive();
|
|
411
|
+
} else if (type === "playruo_stream_stop") {
|
|
412
|
+
console.log("\u{1F537} [Embed] playruo_stream_stop received");
|
|
413
|
+
void exitEmbedFullscreen({ reason: "playruo_stream_stop" });
|
|
414
|
+
cleanupEmbedSession({
|
|
415
|
+
markPlayed: false,
|
|
416
|
+
notifyCompletion: false,
|
|
417
|
+
requestFullscreenExit: false
|
|
418
|
+
});
|
|
419
|
+
} else if (type === "REQUEST_EXIT_FULLSCREEN") {
|
|
420
|
+
console.log("\u{1F537} [Embed] REQUEST_EXIT_FULLSCREEN received");
|
|
421
|
+
void exitEmbedFullscreen({
|
|
422
|
+
reason: "REQUEST_EXIT_FULLSCREEN",
|
|
423
|
+
suppressFullscreenExitCleanup: false
|
|
424
|
+
}).finally(postExitFullscreenAckToIframe);
|
|
425
|
+
} else if (type === "playruo_event" && data && typeof data === "object") {
|
|
426
|
+
console.log("\u{1F537} [Embed] playruo_event received:", data);
|
|
427
|
+
const eventPayload = data;
|
|
428
|
+
onEvent?.(eventPayload);
|
|
429
|
+
forwardEventToParent({ type: "playruo_event", data: eventPayload });
|
|
430
|
+
} else if (type === "playruo" && message.action === "event" && message.payload && typeof message.payload === "object") {
|
|
431
|
+
console.log("\u{1F537} [Embed] legacy playruo event received:", message.payload);
|
|
432
|
+
const eventPayload = message.payload;
|
|
433
|
+
onEvent?.(eventPayload);
|
|
434
|
+
forwardEventToParent({ type: "playruo_event", data: eventPayload });
|
|
244
435
|
}
|
|
245
436
|
if (type === "error" || type === "closeForBuy" || type === "goToStore") {
|
|
246
437
|
console.log("\u{1F537} [Embed] Legacy message received:", type);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
438
|
+
cleanupEmbedSession({
|
|
439
|
+
payload: {
|
|
440
|
+
reason: "Demo session ended",
|
|
441
|
+
storeUrl
|
|
442
|
+
}
|
|
443
|
+
});
|
|
253
444
|
}
|
|
254
445
|
};
|
|
255
446
|
window.addEventListener("message", handleGameMessage);
|
|
256
447
|
return () => window.removeEventListener("message", handleGameMessage);
|
|
257
|
-
}, [
|
|
448
|
+
}, [
|
|
449
|
+
cleanupEmbedSession,
|
|
450
|
+
exitEmbedFullscreen,
|
|
451
|
+
forwardEventToParent,
|
|
452
|
+
hasPlayedOnce,
|
|
453
|
+
iframeRef,
|
|
454
|
+
markStreamActive,
|
|
455
|
+
onEvent,
|
|
456
|
+
onError,
|
|
457
|
+
onPurchaseRequest,
|
|
458
|
+
onReady,
|
|
459
|
+
onStateChange,
|
|
460
|
+
postExitFullscreenAckToIframe,
|
|
461
|
+
storeUrl
|
|
462
|
+
]);
|
|
258
463
|
const generateUUID = () => {
|
|
259
464
|
if (self.crypto.randomUUID) return self.crypto.randomUUID();
|
|
260
465
|
const bytes = new Uint8Array(16);
|
|
@@ -301,9 +506,8 @@ var PlayruoEmbed = ({
|
|
|
301
506
|
}, [iframeRef]);
|
|
302
507
|
useEffect(() => {
|
|
303
508
|
const handleFullscreenChange = () => {
|
|
304
|
-
const doc = document;
|
|
305
509
|
const iframe = iframeRef.current;
|
|
306
|
-
const fullscreenElement =
|
|
510
|
+
const fullscreenElement = getFullscreenElement();
|
|
307
511
|
const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
|
|
308
512
|
if (isEmbedFullscreen) {
|
|
309
513
|
wasEmbedFullscreenRef.current = true;
|
|
@@ -320,26 +524,15 @@ var PlayruoEmbed = ({
|
|
|
320
524
|
return;
|
|
321
525
|
}
|
|
322
526
|
releaseEscapeKeyLock();
|
|
527
|
+
if (suppressNextFullscreenExitCleanupRef.current) {
|
|
528
|
+
suppressNextFullscreenExitCleanupRef.current = false;
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
323
531
|
if (wasEmbedFullscreenRef.current) {
|
|
324
532
|
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
|
|
533
|
+
cleanupEmbedSession({
|
|
534
|
+
notifyIframe: true,
|
|
535
|
+
requestFullscreenExit: false
|
|
343
536
|
});
|
|
344
537
|
}
|
|
345
538
|
};
|
|
@@ -353,7 +546,13 @@ var PlayruoEmbed = ({
|
|
|
353
546
|
document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
|
|
354
547
|
document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
|
|
355
548
|
};
|
|
356
|
-
}, [
|
|
549
|
+
}, [
|
|
550
|
+
cleanupEmbedSession,
|
|
551
|
+
getFullscreenElement,
|
|
552
|
+
iframeRef,
|
|
553
|
+
lockEscapeKey,
|
|
554
|
+
releaseEscapeKeyLock
|
|
555
|
+
]);
|
|
357
556
|
useEffect(() => {
|
|
358
557
|
return () => {
|
|
359
558
|
releaseEscapeKeyLock();
|
|
@@ -438,6 +637,8 @@ var PlayruoEmbed = ({
|
|
|
438
637
|
}
|
|
439
638
|
}, [isReady]);
|
|
440
639
|
const isAvailable = Boolean(isReady && isCompatible && hasIframeConfirmed);
|
|
640
|
+
const shouldDisplayGameIframe = showGameIframe;
|
|
641
|
+
const shouldShowIframeMisuseMessage = Boolean(showIframe && !showGameIframe);
|
|
441
642
|
const renderContent = () => {
|
|
442
643
|
if (!renderState) return null;
|
|
443
644
|
const canTryNow = Boolean(
|
|
@@ -467,6 +668,26 @@ var PlayruoEmbed = ({
|
|
|
467
668
|
};
|
|
468
669
|
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
469
670
|
renderContent(),
|
|
671
|
+
isCompatible && shouldShowIframeMisuseMessage && /* @__PURE__ */ jsx(
|
|
672
|
+
"div",
|
|
673
|
+
{
|
|
674
|
+
role: "status",
|
|
675
|
+
style: {
|
|
676
|
+
alignItems: "center",
|
|
677
|
+
background: "#000",
|
|
678
|
+
color: "#fff",
|
|
679
|
+
display: "flex",
|
|
680
|
+
fontFamily: "system-ui, sans-serif",
|
|
681
|
+
fontSize: 14,
|
|
682
|
+
height: "100%",
|
|
683
|
+
justifyContent: "center",
|
|
684
|
+
padding: 24,
|
|
685
|
+
textAlign: "center",
|
|
686
|
+
width: "100%"
|
|
687
|
+
},
|
|
688
|
+
children: "This iframe is only meant to be displayed through PlayruoEmbed during an active stream."
|
|
689
|
+
}
|
|
690
|
+
),
|
|
470
691
|
isCompatible && /* @__PURE__ */ jsx(
|
|
471
692
|
"iframe",
|
|
472
693
|
{
|
|
@@ -479,9 +700,9 @@ var PlayruoEmbed = ({
|
|
|
479
700
|
title: "Playruo",
|
|
480
701
|
referrerPolicy: "strict-origin-when-cross-origin",
|
|
481
702
|
style: {
|
|
482
|
-
//
|
|
483
|
-
opacity:
|
|
484
|
-
pointerEvents:
|
|
703
|
+
// The iframe stays mounted for readiness checks, visible only during launch/play, and hidden after terminal events.
|
|
704
|
+
opacity: shouldDisplayGameIframe ? 1 : 0,
|
|
705
|
+
pointerEvents: shouldDisplayGameIframe ? "auto" : "none",
|
|
485
706
|
width: "100%",
|
|
486
707
|
height: "100%",
|
|
487
708
|
border: 0
|
|
@@ -537,10 +758,16 @@ var EmbedTester = (props = {}) => {
|
|
|
537
758
|
const [storeUrlDraft, setStoreUrlDraft] = useState2(
|
|
538
759
|
props.storeUrl ?? "https://store.steampowered.com/app/123456"
|
|
539
760
|
);
|
|
761
|
+
const [enableWindowedLaunchDraft, setEnableWindowedLaunchDraft] = useState2(
|
|
762
|
+
props.enableWindowedLaunch ?? false
|
|
763
|
+
);
|
|
540
764
|
const [appliedOperation, setAppliedOperation] = useState2(props.operation ?? "iframe");
|
|
541
765
|
const [appliedStoreUrl, setAppliedStoreUrl] = useState2(
|
|
542
766
|
props.storeUrl ?? "https://store.steampowered.com/app/123456"
|
|
543
767
|
);
|
|
768
|
+
const [appliedEnableWindowedLaunch, setAppliedEnableWindowedLaunch] = useState2(
|
|
769
|
+
props.enableWindowedLaunch ?? false
|
|
770
|
+
);
|
|
544
771
|
const [key, setKey] = useState2(0);
|
|
545
772
|
const [hasUserClicked, setHasUserClicked] = useState2(false);
|
|
546
773
|
const [showIframe, setShowIframe] = useState2(false);
|
|
@@ -555,8 +782,9 @@ var EmbedTester = (props = {}) => {
|
|
|
555
782
|
const iframeRef = useRef2(null);
|
|
556
783
|
const isOperationPending = operationDraft !== appliedOperation;
|
|
557
784
|
const isStoreUrlPending = storeUrlDraft !== appliedStoreUrl;
|
|
558
|
-
const
|
|
559
|
-
const
|
|
785
|
+
const isWindowedLaunchPending = enableWindowedLaunchDraft !== appliedEnableWindowedLaunch;
|
|
786
|
+
const hasPendingConfigChanges = isOperationPending || isStoreUrlPending || isWindowedLaunchPending;
|
|
787
|
+
const pendingChangeCount = Number(isOperationPending) + Number(isStoreUrlPending) + Number(isWindowedLaunchPending);
|
|
560
788
|
const addEvent = useCallback2(
|
|
561
789
|
(message) => {
|
|
562
790
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
@@ -640,6 +868,13 @@ var EmbedTester = (props = {}) => {
|
|
|
640
868
|
console.error("Embed error:", error);
|
|
641
869
|
}
|
|
642
870
|
}, [addEvent, enableConsoleLog]);
|
|
871
|
+
const handleEvent = useCallback2((data) => {
|
|
872
|
+
const eventName = typeof data.event === "string" ? data.event : typeof data.name === "string" ? data.name : "unknown";
|
|
873
|
+
addEvent(`Event received: ${eventName}`);
|
|
874
|
+
if (enableConsoleLog) {
|
|
875
|
+
console.log("Embed event:", data);
|
|
876
|
+
}
|
|
877
|
+
}, [addEvent, enableConsoleLog]);
|
|
643
878
|
const handlePurchaseRequest = useCallback2((data) => {
|
|
644
879
|
addEvent(`\u{1F4B3} Explicit purchase request from Buy Now button`);
|
|
645
880
|
addEvent(`Opening store: ${data.storeUrl}`);
|
|
@@ -660,16 +895,24 @@ var EmbedTester = (props = {}) => {
|
|
|
660
895
|
}
|
|
661
896
|
setAppliedOperation(operationDraft);
|
|
662
897
|
setAppliedStoreUrl(storeUrlDraft);
|
|
898
|
+
setAppliedEnableWindowedLaunch(enableWindowedLaunchDraft);
|
|
663
899
|
resetEmbedSession("Applied updated embed settings");
|
|
664
|
-
}, [
|
|
900
|
+
}, [
|
|
901
|
+
enableWindowedLaunchDraft,
|
|
902
|
+
hasPendingConfigChanges,
|
|
903
|
+
operationDraft,
|
|
904
|
+
resetEmbedSession,
|
|
905
|
+
storeUrlDraft
|
|
906
|
+
]);
|
|
665
907
|
const handleRestartSession = useCallback2(() => {
|
|
666
908
|
resetEmbedSession("Embed restarted with applied settings");
|
|
667
909
|
}, [resetEmbedSession]);
|
|
668
910
|
const handleResetDrafts = useCallback2(() => {
|
|
669
911
|
setOperationDraft(appliedOperation);
|
|
670
912
|
setStoreUrlDraft(appliedStoreUrl);
|
|
913
|
+
setEnableWindowedLaunchDraft(appliedEnableWindowedLaunch);
|
|
671
914
|
addEvent("Reverted draft changes to the applied configuration");
|
|
672
|
-
}, [addEvent, appliedOperation, appliedStoreUrl]);
|
|
915
|
+
}, [addEvent, appliedEnableWindowedLaunch, appliedOperation, appliedStoreUrl]);
|
|
673
916
|
const panelStyle = {
|
|
674
917
|
position: "relative",
|
|
675
918
|
border: "1px solid rgba(148, 163, 184, 0.18)",
|
|
@@ -1237,6 +1480,50 @@ var EmbedTester = (props = {}) => {
|
|
|
1237
1480
|
"Applied value: ",
|
|
1238
1481
|
/* @__PURE__ */ jsx2("code", { style: codeStyle, children: appliedStoreUrl })
|
|
1239
1482
|
] })
|
|
1483
|
+
] }),
|
|
1484
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
1485
|
+
/* @__PURE__ */ jsxs2("div", { style: labelStyle, children: [
|
|
1486
|
+
/* @__PURE__ */ jsx2("span", { children: "Launch mode" }),
|
|
1487
|
+
/* @__PURE__ */ jsxs2("span", { style: buildIndicatorStyle(isWindowedLaunchPending), children: [
|
|
1488
|
+
/* @__PURE__ */ jsx2("span", { style: buildIndicatorDotStyle(isWindowedLaunchPending) }),
|
|
1489
|
+
isWindowedLaunchPending ? "Pending" : "Live"
|
|
1490
|
+
] })
|
|
1491
|
+
] }),
|
|
1492
|
+
/* @__PURE__ */ jsxs2(
|
|
1493
|
+
"label",
|
|
1494
|
+
{
|
|
1495
|
+
htmlFor: "enableWindowedLaunch",
|
|
1496
|
+
style: {
|
|
1497
|
+
...buildInputShellStyle(isWindowedLaunchPending),
|
|
1498
|
+
justifyContent: "space-between",
|
|
1499
|
+
minHeight: "48px",
|
|
1500
|
+
cursor: "pointer"
|
|
1501
|
+
},
|
|
1502
|
+
children: [
|
|
1503
|
+
/* @__PURE__ */ jsx2("span", { style: { color: "#f8fafc", fontSize: "15px", fontWeight: 600 }, children: "Windowed launch" }),
|
|
1504
|
+
/* @__PURE__ */ jsx2(
|
|
1505
|
+
"input",
|
|
1506
|
+
{
|
|
1507
|
+
id: "enableWindowedLaunch",
|
|
1508
|
+
type: "checkbox",
|
|
1509
|
+
checked: enableWindowedLaunchDraft,
|
|
1510
|
+
onChange: (event) => setEnableWindowedLaunchDraft(event.target.checked),
|
|
1511
|
+
style: {
|
|
1512
|
+
width: "18px",
|
|
1513
|
+
height: "18px",
|
|
1514
|
+
accentColor: "#38bdf8",
|
|
1515
|
+
cursor: "pointer"
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
)
|
|
1519
|
+
]
|
|
1520
|
+
}
|
|
1521
|
+
),
|
|
1522
|
+
/* @__PURE__ */ jsxs2("p", { style: helperTextStyle, children: [
|
|
1523
|
+
"Applied value:",
|
|
1524
|
+
" ",
|
|
1525
|
+
/* @__PURE__ */ jsx2("code", { style: codeStyle, children: appliedEnableWindowedLaunch ? "windowed" : "fullscreen request" })
|
|
1526
|
+
] })
|
|
1240
1527
|
] })
|
|
1241
1528
|
] }),
|
|
1242
1529
|
/* @__PURE__ */ jsxs2(
|
|
@@ -1516,6 +1803,10 @@ var EmbedTester = (props = {}) => {
|
|
|
1516
1803
|
/* @__PURE__ */ jsxs2("span", { style: { ...codeStyle, padding: "6px 10px" }, children: [
|
|
1517
1804
|
"Operation: ",
|
|
1518
1805
|
appliedOperation
|
|
1806
|
+
] }),
|
|
1807
|
+
/* @__PURE__ */ jsxs2("span", { style: { ...codeStyle, padding: "6px 10px" }, children: [
|
|
1808
|
+
"Launch: ",
|
|
1809
|
+
appliedEnableWindowedLaunch ? "windowed" : "fullscreen request"
|
|
1519
1810
|
] })
|
|
1520
1811
|
] })
|
|
1521
1812
|
]
|
|
@@ -1587,9 +1878,11 @@ var EmbedTester = (props = {}) => {
|
|
|
1587
1878
|
iframeRef,
|
|
1588
1879
|
onDemoCompleted: handleDemoCompleted,
|
|
1589
1880
|
onError: handleError,
|
|
1881
|
+
onEvent: handleEvent,
|
|
1590
1882
|
onPurchaseRequest: handlePurchaseRequest,
|
|
1591
1883
|
onReady: handleReady,
|
|
1592
1884
|
onStateChange: handleStateChange,
|
|
1885
|
+
enableWindowedLaunch: appliedEnableWindowedLaunch,
|
|
1593
1886
|
operation: appliedOperation,
|
|
1594
1887
|
renderState: ({ actions, message, status }) => /* @__PURE__ */ jsx2(Fragment, { children: !hasUserClicked && /* @__PURE__ */ jsx2(
|
|
1595
1888
|
"div",
|
|
@@ -1714,7 +2007,7 @@ var isPlayruoEmbedMessage = (data) => {
|
|
|
1714
2007
|
if (!data || typeof data !== "object") return false;
|
|
1715
2008
|
const msg = data;
|
|
1716
2009
|
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";
|
|
2010
|
+
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
2011
|
};
|
|
1719
2012
|
var isPlayruoInternalMessage = (data) => {
|
|
1720
2013
|
if (!data || typeof data !== "object") return false;
|