@playruo/embed 1.3.2 → 1.4.0
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 +69 -0
- package/dist/index.cjs +217 -56
- package/dist/index.d.cts +157 -63
- package/dist/index.d.ts +157 -63
- package/dist/index.js +217 -56
- package/package.json +32 -9
package/README.md
CHANGED
|
@@ -2,6 +2,75 @@
|
|
|
2
2
|
|
|
3
3
|
React SDK to embed Playruo via iframe + postMessage.
|
|
4
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
|
+
## Campaigns with several operations
|
|
21
|
+
|
|
22
|
+
`operation` is the campaign path. When the campaign holds more than one
|
|
23
|
+
operation, name the one to open with `operationKey`, sent as `?op=`:
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
<PlayruoEmbed
|
|
27
|
+
operation="crypticalpath"
|
|
28
|
+
operationKey="crypticalpath-fr"
|
|
29
|
+
/>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Passing it inside `operation` (`"crypticalpath?op=..."`) still works and warns:
|
|
33
|
+
it was the only way before this prop existed. `operationKey` wins over it. Other
|
|
34
|
+
query parameters in `operation` are kept as they are.
|
|
35
|
+
|
|
36
|
+
## Password protected operations
|
|
37
|
+
|
|
38
|
+
A protected operation renders no form of its own: it announces the gate and the
|
|
39
|
+
host collects the password. Read the gate from `renderState` and answer with the
|
|
40
|
+
actions:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
<PlayruoEmbed
|
|
44
|
+
operation="crypticalpath"
|
|
45
|
+
onPasswordRequired={() => setAsking(true)}
|
|
46
|
+
onPasswordResult={({ success }) => setAsking(!success)}
|
|
47
|
+
renderState={({ password, actions }) =>
|
|
48
|
+
password.required && !password.unlocked ? (
|
|
49
|
+
<PasswordForm error={password.error} onSubmit={actions.submitPassword} />
|
|
50
|
+
) : null
|
|
51
|
+
}
|
|
52
|
+
/>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The announce is emitted once, when the operation's configuration is loaded, so a
|
|
56
|
+
host that mounts later misses it: call `actions.requestPassword()` to have it
|
|
57
|
+
sent again. `password.checked` tells the two silences apart — an operation that
|
|
58
|
+
has not answered yet, and one that answered it is not protected.
|
|
59
|
+
|
|
60
|
+
`password.required` stays true after a successful unlock, so read
|
|
61
|
+
`password.unlocked` for the gate itself. A refusal carries a code in
|
|
62
|
+
`password.error`, typed as `PlayruoPasswordErrorCode`.
|
|
63
|
+
|
|
64
|
+
## Message origin
|
|
65
|
+
|
|
66
|
+
Messages are only accepted from the iframe's own window **and** from the origin
|
|
67
|
+
`operation`/`baseUrl` point at, and every message sent to the iframe is
|
|
68
|
+
addressed to that same origin rather than to `"*"`. A playable served from an
|
|
69
|
+
origin other than the one the embed URL was built with is therefore ignored.
|
|
70
|
+
|
|
71
|
+
Events forwarded one level up, to the host page's own parent, still go to `"*"`:
|
|
72
|
+
that origin is not something this package can know.
|
|
73
|
+
|
|
5
74
|
## Event forwarding
|
|
6
75
|
|
|
7
76
|
When the embedded Playruo iframe sends `{ type: "playruo_event", data }`, the
|
package/dist/index.cjs
CHANGED
|
@@ -48,15 +48,33 @@ var normalizeOperation = (operation) => {
|
|
|
48
48
|
if (trimmedOperation === "") {
|
|
49
49
|
throw new Error("`operation` is required to build a Playruo embed URL.");
|
|
50
50
|
}
|
|
51
|
+
const operationQuery = trimmedOperation.split("?")[1];
|
|
52
|
+
if (operationQuery != null && new URLSearchParams(operationQuery).has("op")) {
|
|
53
|
+
console.warn(
|
|
54
|
+
"[PlayruoEmbed] `?op=` inside `operation` is deprecated: pass it as the `operationKey` prop."
|
|
55
|
+
);
|
|
56
|
+
}
|
|
51
57
|
return trimmedOperation.startsWith("/") ? trimmedOperation.slice(1) : trimmedOperation;
|
|
52
58
|
};
|
|
53
|
-
var buildEmbedUrl = ({
|
|
54
|
-
|
|
59
|
+
var buildEmbedUrl = ({
|
|
60
|
+
operation,
|
|
61
|
+
operationKey,
|
|
62
|
+
storeUrl
|
|
63
|
+
}) => {
|
|
64
|
+
return buildEmbedUrlWithOrigin(PLAYRUO_ORIGIN, {
|
|
65
|
+
operation,
|
|
66
|
+
operationKey,
|
|
67
|
+
storeUrl
|
|
68
|
+
});
|
|
55
69
|
};
|
|
56
|
-
var buildEmbedUrlWithOrigin = (origin, { operation, storeUrl }) => {
|
|
70
|
+
var buildEmbedUrlWithOrigin = (origin, { operation, operationKey, storeUrl }) => {
|
|
57
71
|
const operationPath = normalizeOperation(operation);
|
|
58
72
|
const url = new URL(`/${operationPath}`, origin);
|
|
59
73
|
url.searchParams.set("iframe", "true");
|
|
74
|
+
const trimmedOperationKey = operationKey?.trim();
|
|
75
|
+
if (trimmedOperationKey) {
|
|
76
|
+
url.searchParams.set("op", trimmedOperationKey);
|
|
77
|
+
}
|
|
60
78
|
if (storeUrl) {
|
|
61
79
|
url.searchParams.set("storeUrl", storeUrl);
|
|
62
80
|
}
|
|
@@ -68,6 +86,7 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
68
86
|
var PlayruoEmbed = ({
|
|
69
87
|
baseUrl,
|
|
70
88
|
operation,
|
|
89
|
+
operationKey,
|
|
71
90
|
storeUrl,
|
|
72
91
|
showIframe = false,
|
|
73
92
|
className,
|
|
@@ -77,6 +96,9 @@ var PlayruoEmbed = ({
|
|
|
77
96
|
onEvent,
|
|
78
97
|
onPurchaseRequest,
|
|
79
98
|
onError,
|
|
99
|
+
onPasswordRequired,
|
|
100
|
+
onPasswordResult,
|
|
101
|
+
enableWindowedLaunch = false,
|
|
80
102
|
forwardEventsToParent = true,
|
|
81
103
|
renderState,
|
|
82
104
|
iframeRef: externalIframeRef
|
|
@@ -93,12 +115,24 @@ var PlayruoEmbed = ({
|
|
|
93
115
|
const [isValidChromium, setIsValidChromium] = (0, import_react.useState)(true);
|
|
94
116
|
const [errorMessage, setErrorMessage] = (0, import_react.useState)(null);
|
|
95
117
|
const [iframeState, setIframeState] = (0, import_react.useState)(null);
|
|
118
|
+
const [passwordRequired, setPasswordRequired] = (0, import_react.useState)(false);
|
|
119
|
+
const [passwordChecked, setPasswordChecked] = (0, import_react.useState)(false);
|
|
120
|
+
const [passwordUnlocked, setPasswordUnlocked] = (0, import_react.useState)(false);
|
|
121
|
+
const [passwordError, setPasswordError] = (0, import_react.useState)(
|
|
122
|
+
void 0
|
|
123
|
+
);
|
|
96
124
|
const src = (0, import_react.useMemo)(
|
|
97
|
-
() => baseUrl != null && baseUrl !== "" ? buildEmbedUrlWithOrigin(baseUrl, { operation, storeUrl }) : buildEmbedUrl({ operation, storeUrl }),
|
|
98
|
-
[baseUrl, operation, storeUrl]
|
|
125
|
+
() => baseUrl != null && baseUrl !== "" ? buildEmbedUrlWithOrigin(baseUrl, { operation, operationKey, storeUrl }) : buildEmbedUrl({ operation, operationKey, storeUrl }),
|
|
126
|
+
[baseUrl, operation, operationKey, storeUrl]
|
|
99
127
|
);
|
|
128
|
+
const embedOrigin = (0, import_react.useMemo)(() => new URL(src).origin, [src]);
|
|
129
|
+
(0, import_react.useEffect)(() => {
|
|
130
|
+
setPasswordRequired(false);
|
|
131
|
+
setPasswordChecked(false);
|
|
132
|
+
setPasswordUnlocked(false);
|
|
133
|
+
setPasswordError(void 0);
|
|
134
|
+
}, [src]);
|
|
100
135
|
(0, import_react.useEffect)(() => {
|
|
101
|
-
if (typeof window === "undefined") return;
|
|
102
136
|
const detectMobile = () => {
|
|
103
137
|
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
|
104
138
|
return /android|iPad|iPhone|iPod/i.test(userAgent);
|
|
@@ -136,15 +170,14 @@ var PlayruoEmbed = ({
|
|
|
136
170
|
tryNow: {
|
|
137
171
|
...currentState.tryNow,
|
|
138
172
|
available: hasIframeConfirmed,
|
|
139
|
-
|
|
173
|
+
// A missing status means the state came from the iframe, which only loads on
|
|
174
|
+
// compatible browsers — the incompatible copy is supplied by currentState.
|
|
175
|
+
status: currentState.tryNow.status ?? (hasIframeConfirmed ? "Ready" : "Checking availability\u2026")
|
|
140
176
|
}
|
|
141
177
|
}),
|
|
142
|
-
[currentState, hasIframeConfirmed
|
|
178
|
+
[currentState, hasIframeConfirmed]
|
|
143
179
|
);
|
|
144
180
|
const getKeyboardApi = (0, import_react.useCallback)(() => {
|
|
145
|
-
if (typeof navigator === "undefined") {
|
|
146
|
-
return void 0;
|
|
147
|
-
}
|
|
148
181
|
return navigator.keyboard;
|
|
149
182
|
}, []);
|
|
150
183
|
const releaseEscapeKeyLock = (0, import_react.useCallback)(() => {
|
|
@@ -174,6 +207,23 @@ var PlayruoEmbed = ({
|
|
|
174
207
|
},
|
|
175
208
|
[onDemoCompleted, storeUrl]
|
|
176
209
|
);
|
|
210
|
+
const postPasswordAction = (0, import_react.useCallback)(
|
|
211
|
+
(message) => {
|
|
212
|
+
const contentWindow = iframeRef.current?.contentWindow;
|
|
213
|
+
if (!contentWindow) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
contentWindow.postMessage(
|
|
218
|
+
{ type: "playruo", fromEmbed: true, ...message },
|
|
219
|
+
embedOrigin
|
|
220
|
+
);
|
|
221
|
+
} catch {
|
|
222
|
+
console.error("[PlayruoEmbed] Failed to send the password action");
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
[embedOrigin, iframeRef]
|
|
226
|
+
);
|
|
177
227
|
const postExitFullscreenToIframe = (0, import_react.useCallback)(() => {
|
|
178
228
|
const iframe = iframeRef.current;
|
|
179
229
|
if (!iframe) {
|
|
@@ -186,22 +236,22 @@ var PlayruoEmbed = ({
|
|
|
186
236
|
fromEmbed: true,
|
|
187
237
|
isFullscreen: false,
|
|
188
238
|
parentWillHandleFullscreen: true
|
|
189
|
-
},
|
|
239
|
+
}, embedOrigin);
|
|
190
240
|
} catch {
|
|
191
241
|
console.error("[PlayruoEmbed] Failed to exit fullscreen");
|
|
192
242
|
}
|
|
193
|
-
}, [iframeRef]);
|
|
243
|
+
}, [embedOrigin, iframeRef]);
|
|
194
244
|
const postExitFullscreenAckToIframe = (0, import_react.useCallback)(() => {
|
|
195
245
|
const iframe = iframeRef.current;
|
|
196
246
|
if (!iframe) {
|
|
197
247
|
return;
|
|
198
248
|
}
|
|
199
249
|
try {
|
|
200
|
-
iframe.contentWindow?.postMessage({ type: "ACK_EXIT_FULLSCREEN" },
|
|
250
|
+
iframe.contentWindow?.postMessage({ type: "ACK_EXIT_FULLSCREEN" }, embedOrigin);
|
|
201
251
|
} catch {
|
|
202
252
|
console.error("[PlayruoEmbed] Failed to acknowledge fullscreen exit");
|
|
203
253
|
}
|
|
204
|
-
}, [iframeRef]);
|
|
254
|
+
}, [embedOrigin, iframeRef]);
|
|
205
255
|
const exitEmbedFullscreen = (0, import_react.useCallback)(
|
|
206
256
|
async ({
|
|
207
257
|
reason = "cleanup",
|
|
@@ -287,7 +337,7 @@ var PlayruoEmbed = ({
|
|
|
287
337
|
}, []);
|
|
288
338
|
const forwardEventToParent = (0, import_react.useCallback)(
|
|
289
339
|
(message) => {
|
|
290
|
-
if (!forwardEventsToParent ||
|
|
340
|
+
if (!forwardEventsToParent || window.parent === window) {
|
|
291
341
|
return;
|
|
292
342
|
}
|
|
293
343
|
try {
|
|
@@ -337,19 +387,26 @@ var PlayruoEmbed = ({
|
|
|
337
387
|
hasNotifiedCompletionRef.current = false;
|
|
338
388
|
setShowGameIframe(true);
|
|
339
389
|
try {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
console.
|
|
390
|
+
let isParentFullscreen = false;
|
|
391
|
+
if (enableWindowedLaunch) {
|
|
392
|
+
releaseEscapeKeyLock();
|
|
393
|
+
console.log("[PlayruoEmbed] Windowed launch enabled, skipping fullscreen request");
|
|
394
|
+
} else {
|
|
395
|
+
try {
|
|
396
|
+
await requestImmersiveMode(iframe);
|
|
397
|
+
isParentFullscreen = true;
|
|
398
|
+
} catch (fsError) {
|
|
399
|
+
console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
|
|
400
|
+
}
|
|
344
401
|
}
|
|
345
402
|
iframe.contentWindow?.postMessage({
|
|
346
403
|
type: "playruo",
|
|
347
404
|
action: "tryNowClick",
|
|
348
405
|
fromEmbed: true,
|
|
349
|
-
isFullscreen:
|
|
350
|
-
parentWillHandleFullscreen:
|
|
406
|
+
isFullscreen: isParentFullscreen,
|
|
407
|
+
parentWillHandleFullscreen: isParentFullscreen,
|
|
351
408
|
isReplay: hasPlayedOnce
|
|
352
|
-
},
|
|
409
|
+
}, embedOrigin);
|
|
353
410
|
console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
|
|
354
411
|
iframe.focus();
|
|
355
412
|
requestAnimationFrame(() => {
|
|
@@ -362,7 +419,15 @@ var PlayruoEmbed = ({
|
|
|
362
419
|
setErrorMessage("Failed to start game");
|
|
363
420
|
onError?.("Failed to start game");
|
|
364
421
|
}
|
|
365
|
-
}, [
|
|
422
|
+
}, [
|
|
423
|
+
enableWindowedLaunch,
|
|
424
|
+
hasPlayedOnce,
|
|
425
|
+
iframeRef,
|
|
426
|
+
isCompatible,
|
|
427
|
+
onError,
|
|
428
|
+
releaseEscapeKeyLock,
|
|
429
|
+
requestImmersiveMode
|
|
430
|
+
]);
|
|
366
431
|
const handleBuyNow = (0, import_react.useCallback)(() => {
|
|
367
432
|
if (!storeUrl) {
|
|
368
433
|
console.warn("[PlayruoEmbed] No store URL provided");
|
|
@@ -388,6 +453,9 @@ var PlayruoEmbed = ({
|
|
|
388
453
|
if (!iframe || event.source !== iframe.contentWindow) {
|
|
389
454
|
return;
|
|
390
455
|
}
|
|
456
|
+
if (event.origin !== embedOrigin) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
391
459
|
console.log("\u{1F537} [Embed] Message received from iframe:", { type, data });
|
|
392
460
|
if (type === "playruo_ready") {
|
|
393
461
|
console.log("\u{1F537} [Embed] playruo_ready received");
|
|
@@ -456,6 +524,24 @@ var PlayruoEmbed = ({
|
|
|
456
524
|
const eventPayload = message.payload;
|
|
457
525
|
onEvent?.(eventPayload);
|
|
458
526
|
forwardEventToParent({ type: "playruo_event", data: eventPayload });
|
|
527
|
+
} else if (type === "playruo_password_required") {
|
|
528
|
+
console.log("\u{1F537} [Embed] playruo_password_required received");
|
|
529
|
+
setPasswordRequired(true);
|
|
530
|
+
setPasswordChecked(true);
|
|
531
|
+
setPasswordUnlocked(false);
|
|
532
|
+
setPasswordError(void 0);
|
|
533
|
+
onPasswordRequired?.();
|
|
534
|
+
} else if (type === "playruo_password_not_required") {
|
|
535
|
+
console.log("\u{1F537} [Embed] playruo_password_not_required received");
|
|
536
|
+
setPasswordRequired(false);
|
|
537
|
+
setPasswordChecked(true);
|
|
538
|
+
} else if (type === "playruo_password_result" && data) {
|
|
539
|
+
const result = data;
|
|
540
|
+
const success = result.success === true;
|
|
541
|
+
console.log("\u{1F537} [Embed] playruo_password_result received:", result);
|
|
542
|
+
setPasswordUnlocked(success);
|
|
543
|
+
setPasswordError(success ? void 0 : result.error ?? "unknown");
|
|
544
|
+
onPasswordResult?.({ success, error: result.error });
|
|
459
545
|
}
|
|
460
546
|
if (type === "error" || type === "closeForBuy" || type === "goToStore") {
|
|
461
547
|
console.log("\u{1F537} [Embed] Legacy message received:", type);
|
|
@@ -471,6 +557,7 @@ var PlayruoEmbed = ({
|
|
|
471
557
|
return () => window.removeEventListener("message", handleGameMessage);
|
|
472
558
|
}, [
|
|
473
559
|
cleanupEmbedSession,
|
|
560
|
+
embedOrigin,
|
|
474
561
|
exitEmbedFullscreen,
|
|
475
562
|
forwardEventToParent,
|
|
476
563
|
hasPlayedOnce,
|
|
@@ -478,6 +565,8 @@ var PlayruoEmbed = ({
|
|
|
478
565
|
markStreamActive,
|
|
479
566
|
onEvent,
|
|
480
567
|
onError,
|
|
568
|
+
onPasswordRequired,
|
|
569
|
+
onPasswordResult,
|
|
481
570
|
onPurchaseRequest,
|
|
482
571
|
onReady,
|
|
483
572
|
onStateChange,
|
|
@@ -500,6 +589,9 @@ var PlayruoEmbed = ({
|
|
|
500
589
|
if (!iframe || event.source !== iframe.contentWindow) {
|
|
501
590
|
return;
|
|
502
591
|
}
|
|
592
|
+
if (event.origin !== embedOrigin) {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
503
595
|
const { type, requestId } = event.data || {};
|
|
504
596
|
if (type === "playruo_uuid_request") {
|
|
505
597
|
try {
|
|
@@ -514,34 +606,27 @@ var PlayruoEmbed = ({
|
|
|
514
606
|
requestId,
|
|
515
607
|
uuid: storedUUID
|
|
516
608
|
},
|
|
517
|
-
|
|
609
|
+
embedOrigin
|
|
518
610
|
);
|
|
519
611
|
} catch (e) {
|
|
520
612
|
}
|
|
521
|
-
} else if (type === "playruo_uuid_remove") {
|
|
522
|
-
try {
|
|
523
|
-
localStorage.removeItem("playruo_uuid");
|
|
524
|
-
} catch (e) {
|
|
525
|
-
}
|
|
526
613
|
}
|
|
527
614
|
};
|
|
528
615
|
window.addEventListener("message", handleUUIDMessage);
|
|
529
616
|
return () => window.removeEventListener("message", handleUUIDMessage);
|
|
530
|
-
}, [iframeRef]);
|
|
617
|
+
}, [embedOrigin, iframeRef]);
|
|
531
618
|
(0, import_react.useEffect)(() => {
|
|
532
619
|
const handleFullscreenChange = () => {
|
|
533
620
|
const iframe = iframeRef.current;
|
|
534
621
|
const fullscreenElement = getFullscreenElement();
|
|
535
622
|
const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
|
|
536
|
-
if (isEmbedFullscreen) {
|
|
623
|
+
if (isEmbedFullscreen && iframe) {
|
|
537
624
|
wasEmbedFullscreenRef.current = true;
|
|
538
625
|
void lockEscapeKey();
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
}, 200);
|
|
544
|
-
}
|
|
626
|
+
setTimeout(() => {
|
|
627
|
+
iframe.focus();
|
|
628
|
+
iframe.contentWindow?.focus();
|
|
629
|
+
}, 200);
|
|
545
630
|
return;
|
|
546
631
|
}
|
|
547
632
|
if (fullscreenElement) {
|
|
@@ -586,17 +671,16 @@ var PlayruoEmbed = ({
|
|
|
586
671
|
const logCrossOriginStatus = () => {
|
|
587
672
|
const isIsolated = self.crossOriginIsolated;
|
|
588
673
|
const hasSharedArrayBuffer = typeof SharedArrayBuffer !== "undefined";
|
|
589
|
-
const hasHighResTime = typeof performance !== "undefined" && typeof performance.now === "function";
|
|
590
674
|
console.group("[PlayruoEmbed] \u{1F512} Cross-Origin Isolation Status");
|
|
591
675
|
if (isIsolated) {
|
|
592
676
|
console.log("\u2705 Cross-origin isolation: ENABLED");
|
|
593
677
|
console.log("\u2705 SharedArrayBuffer support:", hasSharedArrayBuffer ? "Available" : "Not available");
|
|
594
|
-
console.log("\u2705 High-resolution timers:
|
|
678
|
+
console.log("\u2705 High-resolution timers: Full precision");
|
|
595
679
|
console.log("\u{1F3AE} Game performance: Optimal");
|
|
596
680
|
} else {
|
|
597
681
|
console.warn("\u26A0\uFE0F Cross-origin isolation: DISABLED");
|
|
598
682
|
console.warn("\u26A0\uFE0F SharedArrayBuffer support:", hasSharedArrayBuffer ? "Available" : "Not available");
|
|
599
|
-
console.warn("\u26A0\uFE0F High-resolution timers:
|
|
683
|
+
console.warn("\u26A0\uFE0F High-resolution timers: Reduced precision");
|
|
600
684
|
console.warn("\u{1F3AE} Game performance: May be degraded");
|
|
601
685
|
console.group("\u{1F4CB} Setup Instructions:");
|
|
602
686
|
console.log("Add these headers to your server:");
|
|
@@ -640,7 +724,7 @@ var PlayruoEmbed = ({
|
|
|
640
724
|
console.log("User agent:", navigator.userAgent);
|
|
641
725
|
console.log("Available APIs:", {
|
|
642
726
|
SharedArrayBuffer: hasSharedArrayBuffer,
|
|
643
|
-
Performance:
|
|
727
|
+
Performance: true,
|
|
644
728
|
WebGL: !!document.createElement("canvas").getContext("webgl"),
|
|
645
729
|
WebGL2: !!document.createElement("canvas").getContext("webgl2"),
|
|
646
730
|
WebAssembly: typeof WebAssembly !== "undefined"
|
|
@@ -672,22 +756,44 @@ var PlayruoEmbed = ({
|
|
|
672
756
|
tryNow: handleTryNow,
|
|
673
757
|
buyNow: handleBuyNow,
|
|
674
758
|
canTryNow,
|
|
675
|
-
canBuyNow: Boolean(isReady)
|
|
759
|
+
canBuyNow: Boolean(isReady),
|
|
760
|
+
submitPassword: (password2) => {
|
|
761
|
+
if (!password2) {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
postPasswordAction({ action: "submitPassword", password: password2 });
|
|
765
|
+
},
|
|
766
|
+
requestPassword: () => postPasswordAction({ action: "requestPassword" })
|
|
767
|
+
};
|
|
768
|
+
const password = {
|
|
769
|
+
required: passwordRequired,
|
|
770
|
+
checked: passwordChecked,
|
|
771
|
+
unlocked: passwordUnlocked,
|
|
772
|
+
error: passwordError
|
|
676
773
|
};
|
|
677
774
|
if (errorMessage) {
|
|
678
|
-
return renderState({ status: "error", message: errorMessage, actions });
|
|
775
|
+
return renderState({ status: "error", message: errorMessage, actions, password });
|
|
776
|
+
}
|
|
777
|
+
if (password.required && !password.unlocked) {
|
|
778
|
+
return renderState({
|
|
779
|
+
status: "loading",
|
|
780
|
+
message: "Password required",
|
|
781
|
+
actions,
|
|
782
|
+
password
|
|
783
|
+
});
|
|
679
784
|
}
|
|
680
785
|
if (!isReady) {
|
|
681
|
-
return renderState({ status: "loading", message: "Loading\u2026", actions });
|
|
786
|
+
return renderState({ status: "loading", message: "Loading\u2026", actions, password });
|
|
682
787
|
}
|
|
683
788
|
if (isCompatible && !iframeState) {
|
|
684
|
-
return renderState({ status: "loading", message: "Checking availability\u2026", actions });
|
|
789
|
+
return renderState({ status: "loading", message: "Checking availability\u2026", actions, password });
|
|
685
790
|
}
|
|
686
791
|
const message = !isAvailable ? currentState.tryNow.status ?? exposedState.tryNow.status : void 0;
|
|
687
792
|
return renderState({
|
|
688
793
|
status: isAvailable ? "available" : "unavailable",
|
|
689
794
|
message,
|
|
690
|
-
actions
|
|
795
|
+
actions,
|
|
796
|
+
password
|
|
691
797
|
});
|
|
692
798
|
};
|
|
693
799
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, children: [
|
|
@@ -782,10 +888,16 @@ var EmbedTester = (props = {}) => {
|
|
|
782
888
|
const [storeUrlDraft, setStoreUrlDraft] = (0, import_react2.useState)(
|
|
783
889
|
props.storeUrl ?? "https://store.steampowered.com/app/123456"
|
|
784
890
|
);
|
|
891
|
+
const [enableWindowedLaunchDraft, setEnableWindowedLaunchDraft] = (0, import_react2.useState)(
|
|
892
|
+
props.enableWindowedLaunch ?? false
|
|
893
|
+
);
|
|
785
894
|
const [appliedOperation, setAppliedOperation] = (0, import_react2.useState)(props.operation ?? "iframe");
|
|
786
895
|
const [appliedStoreUrl, setAppliedStoreUrl] = (0, import_react2.useState)(
|
|
787
896
|
props.storeUrl ?? "https://store.steampowered.com/app/123456"
|
|
788
897
|
);
|
|
898
|
+
const [appliedEnableWindowedLaunch, setAppliedEnableWindowedLaunch] = (0, import_react2.useState)(
|
|
899
|
+
props.enableWindowedLaunch ?? false
|
|
900
|
+
);
|
|
789
901
|
const [key, setKey] = (0, import_react2.useState)(0);
|
|
790
902
|
const [hasUserClicked, setHasUserClicked] = (0, import_react2.useState)(false);
|
|
791
903
|
const [showIframe, setShowIframe] = (0, import_react2.useState)(false);
|
|
@@ -800,8 +912,9 @@ var EmbedTester = (props = {}) => {
|
|
|
800
912
|
const iframeRef = (0, import_react2.useRef)(null);
|
|
801
913
|
const isOperationPending = operationDraft !== appliedOperation;
|
|
802
914
|
const isStoreUrlPending = storeUrlDraft !== appliedStoreUrl;
|
|
803
|
-
const
|
|
804
|
-
const
|
|
915
|
+
const isWindowedLaunchPending = enableWindowedLaunchDraft !== appliedEnableWindowedLaunch;
|
|
916
|
+
const hasPendingConfigChanges = isOperationPending || isStoreUrlPending || isWindowedLaunchPending;
|
|
917
|
+
const pendingChangeCount = Number(isOperationPending) + Number(isStoreUrlPending) + Number(isWindowedLaunchPending);
|
|
805
918
|
const addEvent = (0, import_react2.useCallback)(
|
|
806
919
|
(message) => {
|
|
807
920
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
@@ -907,21 +1020,20 @@ var EmbedTester = (props = {}) => {
|
|
|
907
1020
|
addEvent(message);
|
|
908
1021
|
}, [addEvent]);
|
|
909
1022
|
const handleApply = (0, import_react2.useCallback)(() => {
|
|
910
|
-
if (!hasPendingConfigChanges) {
|
|
911
|
-
return;
|
|
912
|
-
}
|
|
913
1023
|
setAppliedOperation(operationDraft);
|
|
914
1024
|
setAppliedStoreUrl(storeUrlDraft);
|
|
1025
|
+
setAppliedEnableWindowedLaunch(enableWindowedLaunchDraft);
|
|
915
1026
|
resetEmbedSession("Applied updated embed settings");
|
|
916
|
-
}, [
|
|
1027
|
+
}, [enableWindowedLaunchDraft, operationDraft, resetEmbedSession, storeUrlDraft]);
|
|
917
1028
|
const handleRestartSession = (0, import_react2.useCallback)(() => {
|
|
918
1029
|
resetEmbedSession("Embed restarted with applied settings");
|
|
919
1030
|
}, [resetEmbedSession]);
|
|
920
1031
|
const handleResetDrafts = (0, import_react2.useCallback)(() => {
|
|
921
1032
|
setOperationDraft(appliedOperation);
|
|
922
1033
|
setStoreUrlDraft(appliedStoreUrl);
|
|
1034
|
+
setEnableWindowedLaunchDraft(appliedEnableWindowedLaunch);
|
|
923
1035
|
addEvent("Reverted draft changes to the applied configuration");
|
|
924
|
-
}, [addEvent, appliedOperation, appliedStoreUrl]);
|
|
1036
|
+
}, [addEvent, appliedEnableWindowedLaunch, appliedOperation, appliedStoreUrl]);
|
|
925
1037
|
const panelStyle = {
|
|
926
1038
|
position: "relative",
|
|
927
1039
|
border: "1px solid rgba(148, 163, 184, 0.18)",
|
|
@@ -1489,6 +1601,50 @@ var EmbedTester = (props = {}) => {
|
|
|
1489
1601
|
"Applied value: ",
|
|
1490
1602
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { style: codeStyle, children: appliedStoreUrl })
|
|
1491
1603
|
] })
|
|
1604
|
+
] }),
|
|
1605
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
1606
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: labelStyle, children: [
|
|
1607
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Launch mode" }),
|
|
1608
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: buildIndicatorStyle(isWindowedLaunchPending), children: [
|
|
1609
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: buildIndicatorDotStyle(isWindowedLaunchPending) }),
|
|
1610
|
+
isWindowedLaunchPending ? "Pending" : "Live"
|
|
1611
|
+
] })
|
|
1612
|
+
] }),
|
|
1613
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1614
|
+
"label",
|
|
1615
|
+
{
|
|
1616
|
+
htmlFor: "enableWindowedLaunch",
|
|
1617
|
+
style: {
|
|
1618
|
+
...buildInputShellStyle(isWindowedLaunchPending),
|
|
1619
|
+
justifyContent: "space-between",
|
|
1620
|
+
minHeight: "48px",
|
|
1621
|
+
cursor: "pointer"
|
|
1622
|
+
},
|
|
1623
|
+
children: [
|
|
1624
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#f8fafc", fontSize: "15px", fontWeight: 600 }, children: "Windowed launch" }),
|
|
1625
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1626
|
+
"input",
|
|
1627
|
+
{
|
|
1628
|
+
id: "enableWindowedLaunch",
|
|
1629
|
+
type: "checkbox",
|
|
1630
|
+
checked: enableWindowedLaunchDraft,
|
|
1631
|
+
onChange: (event) => setEnableWindowedLaunchDraft(event.target.checked),
|
|
1632
|
+
style: {
|
|
1633
|
+
width: "18px",
|
|
1634
|
+
height: "18px",
|
|
1635
|
+
accentColor: "#38bdf8",
|
|
1636
|
+
cursor: "pointer"
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
)
|
|
1640
|
+
]
|
|
1641
|
+
}
|
|
1642
|
+
),
|
|
1643
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { style: helperTextStyle, children: [
|
|
1644
|
+
"Applied value:",
|
|
1645
|
+
" ",
|
|
1646
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { style: codeStyle, children: appliedEnableWindowedLaunch ? "windowed" : "fullscreen request" })
|
|
1647
|
+
] })
|
|
1492
1648
|
] })
|
|
1493
1649
|
] }),
|
|
1494
1650
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
@@ -1768,6 +1924,10 @@ var EmbedTester = (props = {}) => {
|
|
|
1768
1924
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { ...codeStyle, padding: "6px 10px" }, children: [
|
|
1769
1925
|
"Operation: ",
|
|
1770
1926
|
appliedOperation
|
|
1927
|
+
] }),
|
|
1928
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { ...codeStyle, padding: "6px 10px" }, children: [
|
|
1929
|
+
"Launch: ",
|
|
1930
|
+
appliedEnableWindowedLaunch ? "windowed" : "fullscreen request"
|
|
1771
1931
|
] })
|
|
1772
1932
|
] })
|
|
1773
1933
|
]
|
|
@@ -1843,6 +2003,7 @@ var EmbedTester = (props = {}) => {
|
|
|
1843
2003
|
onPurchaseRequest: handlePurchaseRequest,
|
|
1844
2004
|
onReady: handleReady,
|
|
1845
2005
|
onStateChange: handleStateChange,
|
|
2006
|
+
enableWindowedLaunch: appliedEnableWindowedLaunch,
|
|
1846
2007
|
operation: appliedOperation,
|
|
1847
2008
|
renderState: ({ actions, message, status }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: !hasUserClicked && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1848
2009
|
"div",
|
|
@@ -1967,12 +2128,12 @@ var isPlayruoEmbedMessage = (data) => {
|
|
|
1967
2128
|
if (!data || typeof data !== "object") return false;
|
|
1968
2129
|
const msg = data;
|
|
1969
2130
|
if (typeof msg.type !== "string") return false;
|
|
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";
|
|
2131
|
+
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" || msg.type === "playruo_password_required" || msg.type === "playruo_password_not_required" || msg.type === "playruo_password_result";
|
|
1971
2132
|
};
|
|
1972
2133
|
var isPlayruoInternalMessage = (data) => {
|
|
1973
2134
|
if (!data || typeof data !== "object") return false;
|
|
1974
2135
|
const msg = data;
|
|
1975
|
-
return msg.type === "playruo" && typeof msg.action === "string" && (msg.action === "tryNowClick" || msg.action === "exitFullscreen" || msg.action === "mute" || msg.action === "unmute") && msg.fromEmbed === true;
|
|
2136
|
+
return msg.type === "playruo" && typeof msg.action === "string" && (msg.action === "tryNowClick" || msg.action === "exitFullscreen" || msg.action === "mute" || msg.action === "unmute" || msg.action === "submitPassword" || msg.action === "requestPassword") && msg.fromEmbed === true;
|
|
1976
2137
|
};
|
|
1977
2138
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1978
2139
|
0 && (module.exports = {
|