@playruo/embed 1.3.3 → 1.5.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 +92 -0
- package/dist/index.cjs +166 -76
- package/dist/index.d.cts +166 -64
- package/dist/index.d.ts +166 -64
- package/dist/index.js +163 -75
- package/package.json +34 -9
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
React SDK to embed Playruo via iframe + postMessage.
|
|
4
4
|
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
- Integrator overview and React/vanilla guides: [`docs/`](./docs/)
|
|
8
|
+
- Protocol reference for webapp ↔ SDK changes: [`DOCUMENTATION.md`](./DOCUMENTATION.md)
|
|
9
|
+
- Customer Notion pages are generated from `docs/` via `npm run docs:notion:dry-run` / `npm run docs:notion:publish`
|
|
10
|
+
|
|
5
11
|
## Windowed launch
|
|
6
12
|
|
|
7
13
|
`PlayruoEmbed` requests browser fullscreen on the iframe when the user starts a
|
|
@@ -17,6 +23,92 @@ start the experience inside the current iframe bounds:
|
|
|
17
23
|
/>
|
|
18
24
|
```
|
|
19
25
|
|
|
26
|
+
## Campaigns with several operations
|
|
27
|
+
|
|
28
|
+
`operation` is the campaign path. When the campaign holds more than one
|
|
29
|
+
operation, name the one to open with `operationKey`, sent as `?op=`:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
<PlayruoEmbed
|
|
33
|
+
operation="crypticalpath"
|
|
34
|
+
operationKey="crypticalpath-fr"
|
|
35
|
+
/>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Passing it inside `operation` (`"crypticalpath?op=..."`) still works and warns:
|
|
39
|
+
it was the only way before this prop existed. `operationKey` wins over it. Other
|
|
40
|
+
query parameters in `operation` are kept as they are.
|
|
41
|
+
|
|
42
|
+
## Host-owned visitor identity
|
|
43
|
+
|
|
44
|
+
By default the SDK stores a UUID in `localStorage["playruo_uuid"]` and answers
|
|
45
|
+
`playruo_uuid_request` with it. Pass `clientFingerprint` when the host already
|
|
46
|
+
has a stable visitor id (account id, CRM id, …): that value is answered as
|
|
47
|
+
`uuid` instead, and localStorage is left alone. Playruo derives the session
|
|
48
|
+
fingerprint from it.
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
<PlayruoEmbed
|
|
52
|
+
operation="crypticalpath"
|
|
53
|
+
clientFingerprint={visitorId}
|
|
54
|
+
/>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
A vanilla host answers the same way, with no React:
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
iframe.contentWindow.postMessage(
|
|
61
|
+
{
|
|
62
|
+
type: "playruo_uuid_response",
|
|
63
|
+
requestId,
|
|
64
|
+
uuid: visitorId,
|
|
65
|
+
},
|
|
66
|
+
"https://playruo.live",
|
|
67
|
+
);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`resolveVisitorUuid(visitorId)` from this package is the shared helper both
|
|
71
|
+
paths use. A blank or whitespace-only value is ignored and the SDK-managed UUID
|
|
72
|
+
is used.
|
|
73
|
+
|
|
74
|
+
## Password protected operations
|
|
75
|
+
|
|
76
|
+
A protected operation renders no form of its own: it announces the gate and the
|
|
77
|
+
host collects the password. Read the gate from `renderState` and answer with the
|
|
78
|
+
actions:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<PlayruoEmbed
|
|
82
|
+
operation="crypticalpath"
|
|
83
|
+
onPasswordRequired={() => setAsking(true)}
|
|
84
|
+
onPasswordResult={({ success }) => setAsking(!success)}
|
|
85
|
+
renderState={({ password, actions }) =>
|
|
86
|
+
password.required && !password.unlocked ? (
|
|
87
|
+
<PasswordForm error={password.error} onSubmit={actions.submitPassword} />
|
|
88
|
+
) : null
|
|
89
|
+
}
|
|
90
|
+
/>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The announce is emitted once, when the operation's configuration is loaded, so a
|
|
94
|
+
host that mounts later misses it: call `actions.requestPassword()` to have it
|
|
95
|
+
sent again. `password.checked` tells the two silences apart — an operation that
|
|
96
|
+
has not answered yet, and one that answered it is not protected.
|
|
97
|
+
|
|
98
|
+
`password.required` stays true after a successful unlock, so read
|
|
99
|
+
`password.unlocked` for the gate itself. A refusal carries a code in
|
|
100
|
+
`password.error`, typed as `PlayruoPasswordErrorCode`.
|
|
101
|
+
|
|
102
|
+
## Message origin
|
|
103
|
+
|
|
104
|
+
Messages are only accepted from the iframe's own window **and** from the origin
|
|
105
|
+
`operation`/`baseUrl` point at, and every message sent to the iframe is
|
|
106
|
+
addressed to that same origin rather than to `"*"`. A playable served from an
|
|
107
|
+
origin other than the one the embed URL was built with is therefore ignored.
|
|
108
|
+
|
|
109
|
+
Events forwarded one level up, to the host page's own parent, still go to `"*"`:
|
|
110
|
+
that origin is not something this package can know.
|
|
111
|
+
|
|
20
112
|
## Event forwarding
|
|
21
113
|
|
|
22
114
|
When the embedded Playruo iframe sends `{ type: "playruo_event", data }`, the
|
package/dist/index.cjs
CHANGED
|
@@ -31,10 +31,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
EmbedTester: () => EmbedTester,
|
|
34
|
+
PLAYRUO_UUID_STORAGE_KEY: () => PLAYRUO_UUID_STORAGE_KEY,
|
|
34
35
|
PlayruoEmbed: () => PlayruoEmbed,
|
|
35
36
|
buildEmbedUrl: () => buildEmbedUrl,
|
|
36
37
|
isPlayruoEmbedMessage: () => isPlayruoEmbedMessage,
|
|
37
|
-
isPlayruoInternalMessage: () => isPlayruoInternalMessage
|
|
38
|
+
isPlayruoInternalMessage: () => isPlayruoInternalMessage,
|
|
39
|
+
resolveVisitorUuid: () => resolveVisitorUuid
|
|
38
40
|
});
|
|
39
41
|
module.exports = __toCommonJS(index_exports);
|
|
40
42
|
|
|
@@ -48,27 +50,72 @@ var normalizeOperation = (operation) => {
|
|
|
48
50
|
if (trimmedOperation === "") {
|
|
49
51
|
throw new Error("`operation` is required to build a Playruo embed URL.");
|
|
50
52
|
}
|
|
53
|
+
const operationQuery = trimmedOperation.split("?")[1];
|
|
54
|
+
if (operationQuery != null && new URLSearchParams(operationQuery).has("op")) {
|
|
55
|
+
console.warn(
|
|
56
|
+
"[PlayruoEmbed] `?op=` inside `operation` is deprecated: pass it as the `operationKey` prop."
|
|
57
|
+
);
|
|
58
|
+
}
|
|
51
59
|
return trimmedOperation.startsWith("/") ? trimmedOperation.slice(1) : trimmedOperation;
|
|
52
60
|
};
|
|
53
|
-
var buildEmbedUrl = ({
|
|
54
|
-
|
|
61
|
+
var buildEmbedUrl = ({
|
|
62
|
+
operation,
|
|
63
|
+
operationKey,
|
|
64
|
+
storeUrl
|
|
65
|
+
}) => {
|
|
66
|
+
return buildEmbedUrlWithOrigin(PLAYRUO_ORIGIN, {
|
|
67
|
+
operation,
|
|
68
|
+
operationKey,
|
|
69
|
+
storeUrl
|
|
70
|
+
});
|
|
55
71
|
};
|
|
56
|
-
var buildEmbedUrlWithOrigin = (origin, { operation, storeUrl }) => {
|
|
72
|
+
var buildEmbedUrlWithOrigin = (origin, { operation, operationKey, storeUrl }) => {
|
|
57
73
|
const operationPath = normalizeOperation(operation);
|
|
58
74
|
const url = new URL(`/${operationPath}`, origin);
|
|
59
75
|
url.searchParams.set("iframe", "true");
|
|
76
|
+
const trimmedOperationKey = operationKey?.trim();
|
|
77
|
+
if (trimmedOperationKey) {
|
|
78
|
+
url.searchParams.set("op", trimmedOperationKey);
|
|
79
|
+
}
|
|
60
80
|
if (storeUrl) {
|
|
61
81
|
url.searchParams.set("storeUrl", storeUrl);
|
|
62
82
|
}
|
|
63
83
|
return url.toString();
|
|
64
84
|
};
|
|
65
85
|
|
|
86
|
+
// src/visitor-uuid.ts
|
|
87
|
+
var PLAYRUO_UUID_STORAGE_KEY = "playruo_uuid";
|
|
88
|
+
var generateUUID = () => {
|
|
89
|
+
if (self.crypto.randomUUID) return self.crypto.randomUUID();
|
|
90
|
+
const bytes = new Uint8Array(16);
|
|
91
|
+
self.crypto.getRandomValues(bytes);
|
|
92
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
93
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
94
|
+
return [...bytes].map(
|
|
95
|
+
(b, i) => ([4, 6, 8, 10].includes(i) ? "-" : "") + b.toString(16).padStart(2, "0")
|
|
96
|
+
).join("");
|
|
97
|
+
};
|
|
98
|
+
var resolveVisitorUuid = (clientFingerprint) => {
|
|
99
|
+
const provided = clientFingerprint?.trim();
|
|
100
|
+
if (provided) {
|
|
101
|
+
return provided;
|
|
102
|
+
}
|
|
103
|
+
let uuid = localStorage.getItem(PLAYRUO_UUID_STORAGE_KEY);
|
|
104
|
+
if (!uuid) {
|
|
105
|
+
uuid = generateUUID();
|
|
106
|
+
localStorage.setItem(PLAYRUO_UUID_STORAGE_KEY, uuid);
|
|
107
|
+
}
|
|
108
|
+
return uuid;
|
|
109
|
+
};
|
|
110
|
+
|
|
66
111
|
// src/react/playruo-embed.tsx
|
|
67
112
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
68
113
|
var PlayruoEmbed = ({
|
|
69
114
|
baseUrl,
|
|
70
115
|
operation,
|
|
116
|
+
operationKey,
|
|
71
117
|
storeUrl,
|
|
118
|
+
clientFingerprint,
|
|
72
119
|
showIframe = false,
|
|
73
120
|
className,
|
|
74
121
|
onReady,
|
|
@@ -77,6 +124,8 @@ var PlayruoEmbed = ({
|
|
|
77
124
|
onEvent,
|
|
78
125
|
onPurchaseRequest,
|
|
79
126
|
onError,
|
|
127
|
+
onPasswordRequired,
|
|
128
|
+
onPasswordResult,
|
|
80
129
|
enableWindowedLaunch = false,
|
|
81
130
|
forwardEventsToParent = true,
|
|
82
131
|
renderState,
|
|
@@ -94,12 +143,24 @@ var PlayruoEmbed = ({
|
|
|
94
143
|
const [isValidChromium, setIsValidChromium] = (0, import_react.useState)(true);
|
|
95
144
|
const [errorMessage, setErrorMessage] = (0, import_react.useState)(null);
|
|
96
145
|
const [iframeState, setIframeState] = (0, import_react.useState)(null);
|
|
146
|
+
const [passwordRequired, setPasswordRequired] = (0, import_react.useState)(false);
|
|
147
|
+
const [passwordChecked, setPasswordChecked] = (0, import_react.useState)(false);
|
|
148
|
+
const [passwordUnlocked, setPasswordUnlocked] = (0, import_react.useState)(false);
|
|
149
|
+
const [passwordError, setPasswordError] = (0, import_react.useState)(
|
|
150
|
+
void 0
|
|
151
|
+
);
|
|
97
152
|
const src = (0, import_react.useMemo)(
|
|
98
|
-
() => baseUrl != null && baseUrl !== "" ? buildEmbedUrlWithOrigin(baseUrl, { operation, storeUrl }) : buildEmbedUrl({ operation, storeUrl }),
|
|
99
|
-
[baseUrl, operation, storeUrl]
|
|
153
|
+
() => baseUrl != null && baseUrl !== "" ? buildEmbedUrlWithOrigin(baseUrl, { operation, operationKey, storeUrl }) : buildEmbedUrl({ operation, operationKey, storeUrl }),
|
|
154
|
+
[baseUrl, operation, operationKey, storeUrl]
|
|
100
155
|
);
|
|
156
|
+
const embedOrigin = (0, import_react.useMemo)(() => new URL(src).origin, [src]);
|
|
157
|
+
(0, import_react.useEffect)(() => {
|
|
158
|
+
setPasswordRequired(false);
|
|
159
|
+
setPasswordChecked(false);
|
|
160
|
+
setPasswordUnlocked(false);
|
|
161
|
+
setPasswordError(void 0);
|
|
162
|
+
}, [src]);
|
|
101
163
|
(0, import_react.useEffect)(() => {
|
|
102
|
-
if (typeof window === "undefined") return;
|
|
103
164
|
const detectMobile = () => {
|
|
104
165
|
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
|
105
166
|
return /android|iPad|iPhone|iPod/i.test(userAgent);
|
|
@@ -137,15 +198,14 @@ var PlayruoEmbed = ({
|
|
|
137
198
|
tryNow: {
|
|
138
199
|
...currentState.tryNow,
|
|
139
200
|
available: hasIframeConfirmed,
|
|
140
|
-
|
|
201
|
+
// A missing status means the state came from the iframe, which only loads on
|
|
202
|
+
// compatible browsers — the incompatible copy is supplied by currentState.
|
|
203
|
+
status: currentState.tryNow.status ?? (hasIframeConfirmed ? "Ready" : "Checking availability\u2026")
|
|
141
204
|
}
|
|
142
205
|
}),
|
|
143
|
-
[currentState, hasIframeConfirmed
|
|
206
|
+
[currentState, hasIframeConfirmed]
|
|
144
207
|
);
|
|
145
208
|
const getKeyboardApi = (0, import_react.useCallback)(() => {
|
|
146
|
-
if (typeof navigator === "undefined") {
|
|
147
|
-
return void 0;
|
|
148
|
-
}
|
|
149
209
|
return navigator.keyboard;
|
|
150
210
|
}, []);
|
|
151
211
|
const releaseEscapeKeyLock = (0, import_react.useCallback)(() => {
|
|
@@ -175,6 +235,23 @@ var PlayruoEmbed = ({
|
|
|
175
235
|
},
|
|
176
236
|
[onDemoCompleted, storeUrl]
|
|
177
237
|
);
|
|
238
|
+
const postPasswordAction = (0, import_react.useCallback)(
|
|
239
|
+
(message) => {
|
|
240
|
+
const contentWindow = iframeRef.current?.contentWindow;
|
|
241
|
+
if (!contentWindow) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
contentWindow.postMessage(
|
|
246
|
+
{ type: "playruo", fromEmbed: true, ...message },
|
|
247
|
+
embedOrigin
|
|
248
|
+
);
|
|
249
|
+
} catch {
|
|
250
|
+
console.error("[PlayruoEmbed] Failed to send the password action");
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
[embedOrigin, iframeRef]
|
|
254
|
+
);
|
|
178
255
|
const postExitFullscreenToIframe = (0, import_react.useCallback)(() => {
|
|
179
256
|
const iframe = iframeRef.current;
|
|
180
257
|
if (!iframe) {
|
|
@@ -187,22 +264,22 @@ var PlayruoEmbed = ({
|
|
|
187
264
|
fromEmbed: true,
|
|
188
265
|
isFullscreen: false,
|
|
189
266
|
parentWillHandleFullscreen: true
|
|
190
|
-
},
|
|
267
|
+
}, embedOrigin);
|
|
191
268
|
} catch {
|
|
192
269
|
console.error("[PlayruoEmbed] Failed to exit fullscreen");
|
|
193
270
|
}
|
|
194
|
-
}, [iframeRef]);
|
|
271
|
+
}, [embedOrigin, iframeRef]);
|
|
195
272
|
const postExitFullscreenAckToIframe = (0, import_react.useCallback)(() => {
|
|
196
273
|
const iframe = iframeRef.current;
|
|
197
274
|
if (!iframe) {
|
|
198
275
|
return;
|
|
199
276
|
}
|
|
200
277
|
try {
|
|
201
|
-
iframe.contentWindow?.postMessage({ type: "ACK_EXIT_FULLSCREEN" },
|
|
278
|
+
iframe.contentWindow?.postMessage({ type: "ACK_EXIT_FULLSCREEN" }, embedOrigin);
|
|
202
279
|
} catch {
|
|
203
280
|
console.error("[PlayruoEmbed] Failed to acknowledge fullscreen exit");
|
|
204
281
|
}
|
|
205
|
-
}, [iframeRef]);
|
|
282
|
+
}, [embedOrigin, iframeRef]);
|
|
206
283
|
const exitEmbedFullscreen = (0, import_react.useCallback)(
|
|
207
284
|
async ({
|
|
208
285
|
reason = "cleanup",
|
|
@@ -288,7 +365,7 @@ var PlayruoEmbed = ({
|
|
|
288
365
|
}, []);
|
|
289
366
|
const forwardEventToParent = (0, import_react.useCallback)(
|
|
290
367
|
(message) => {
|
|
291
|
-
if (!forwardEventsToParent ||
|
|
368
|
+
if (!forwardEventsToParent || window.parent === window) {
|
|
292
369
|
return;
|
|
293
370
|
}
|
|
294
371
|
try {
|
|
@@ -357,7 +434,7 @@ var PlayruoEmbed = ({
|
|
|
357
434
|
isFullscreen: isParentFullscreen,
|
|
358
435
|
parentWillHandleFullscreen: isParentFullscreen,
|
|
359
436
|
isReplay: hasPlayedOnce
|
|
360
|
-
},
|
|
437
|
+
}, embedOrigin);
|
|
361
438
|
console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
|
|
362
439
|
iframe.focus();
|
|
363
440
|
requestAnimationFrame(() => {
|
|
@@ -404,6 +481,9 @@ var PlayruoEmbed = ({
|
|
|
404
481
|
if (!iframe || event.source !== iframe.contentWindow) {
|
|
405
482
|
return;
|
|
406
483
|
}
|
|
484
|
+
if (event.origin !== embedOrigin) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
407
487
|
console.log("\u{1F537} [Embed] Message received from iframe:", { type, data });
|
|
408
488
|
if (type === "playruo_ready") {
|
|
409
489
|
console.log("\u{1F537} [Embed] playruo_ready received");
|
|
@@ -472,6 +552,24 @@ var PlayruoEmbed = ({
|
|
|
472
552
|
const eventPayload = message.payload;
|
|
473
553
|
onEvent?.(eventPayload);
|
|
474
554
|
forwardEventToParent({ type: "playruo_event", data: eventPayload });
|
|
555
|
+
} else if (type === "playruo_password_required") {
|
|
556
|
+
console.log("\u{1F537} [Embed] playruo_password_required received");
|
|
557
|
+
setPasswordRequired(true);
|
|
558
|
+
setPasswordChecked(true);
|
|
559
|
+
setPasswordUnlocked(false);
|
|
560
|
+
setPasswordError(void 0);
|
|
561
|
+
onPasswordRequired?.();
|
|
562
|
+
} else if (type === "playruo_password_not_required") {
|
|
563
|
+
console.log("\u{1F537} [Embed] playruo_password_not_required received");
|
|
564
|
+
setPasswordRequired(false);
|
|
565
|
+
setPasswordChecked(true);
|
|
566
|
+
} else if (type === "playruo_password_result" && data) {
|
|
567
|
+
const result = data;
|
|
568
|
+
const success = result.success === true;
|
|
569
|
+
console.log("\u{1F537} [Embed] playruo_password_result received:", result);
|
|
570
|
+
setPasswordUnlocked(success);
|
|
571
|
+
setPasswordError(success ? void 0 : result.error ?? "unknown");
|
|
572
|
+
onPasswordResult?.({ success, error: result.error });
|
|
475
573
|
}
|
|
476
574
|
if (type === "error" || type === "closeForBuy" || type === "goToStore") {
|
|
477
575
|
console.log("\u{1F537} [Embed] Legacy message received:", type);
|
|
@@ -487,6 +585,7 @@ var PlayruoEmbed = ({
|
|
|
487
585
|
return () => window.removeEventListener("message", handleGameMessage);
|
|
488
586
|
}, [
|
|
489
587
|
cleanupEmbedSession,
|
|
588
|
+
embedOrigin,
|
|
490
589
|
exitEmbedFullscreen,
|
|
491
590
|
forwardEventToParent,
|
|
492
591
|
hasPlayedOnce,
|
|
@@ -494,70 +593,53 @@ var PlayruoEmbed = ({
|
|
|
494
593
|
markStreamActive,
|
|
495
594
|
onEvent,
|
|
496
595
|
onError,
|
|
596
|
+
onPasswordRequired,
|
|
597
|
+
onPasswordResult,
|
|
497
598
|
onPurchaseRequest,
|
|
498
599
|
onReady,
|
|
499
600
|
onStateChange,
|
|
500
601
|
postExitFullscreenAckToIframe,
|
|
501
602
|
storeUrl
|
|
502
603
|
]);
|
|
503
|
-
const generateUUID = () => {
|
|
504
|
-
if (self.crypto.randomUUID) return self.crypto.randomUUID();
|
|
505
|
-
const bytes = new Uint8Array(16);
|
|
506
|
-
self.crypto.getRandomValues(bytes);
|
|
507
|
-
bytes[6] = bytes[6] & 15 | 64;
|
|
508
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
509
|
-
return [...bytes].map(
|
|
510
|
-
(b, i) => ([4, 6, 8, 10].includes(i) ? "-" : "") + b.toString(16).padStart(2, "0")
|
|
511
|
-
).join("");
|
|
512
|
-
};
|
|
513
604
|
(0, import_react.useEffect)(() => {
|
|
514
605
|
const handleUUIDMessage = (event) => {
|
|
515
606
|
const iframe = iframeRef.current;
|
|
516
607
|
if (!iframe || event.source !== iframe.contentWindow) {
|
|
517
608
|
return;
|
|
518
609
|
}
|
|
610
|
+
if (event.origin !== embedOrigin) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
519
613
|
const { type, requestId } = event.data || {};
|
|
520
614
|
if (type === "playruo_uuid_request") {
|
|
521
615
|
try {
|
|
522
|
-
let storedUUID = localStorage.getItem("playruo_uuid");
|
|
523
|
-
if (!storedUUID) {
|
|
524
|
-
storedUUID = generateUUID();
|
|
525
|
-
localStorage.setItem("playruo_uuid", storedUUID);
|
|
526
|
-
}
|
|
527
616
|
iframe.contentWindow?.postMessage(
|
|
528
617
|
{
|
|
529
618
|
type: "playruo_uuid_response",
|
|
530
619
|
requestId,
|
|
531
|
-
uuid:
|
|
620
|
+
uuid: resolveVisitorUuid(clientFingerprint)
|
|
532
621
|
},
|
|
533
|
-
|
|
622
|
+
embedOrigin
|
|
534
623
|
);
|
|
535
624
|
} catch (e) {
|
|
536
625
|
}
|
|
537
|
-
} else if (type === "playruo_uuid_remove") {
|
|
538
|
-
try {
|
|
539
|
-
localStorage.removeItem("playruo_uuid");
|
|
540
|
-
} catch (e) {
|
|
541
|
-
}
|
|
542
626
|
}
|
|
543
627
|
};
|
|
544
628
|
window.addEventListener("message", handleUUIDMessage);
|
|
545
629
|
return () => window.removeEventListener("message", handleUUIDMessage);
|
|
546
|
-
}, [iframeRef]);
|
|
630
|
+
}, [clientFingerprint, embedOrigin, iframeRef]);
|
|
547
631
|
(0, import_react.useEffect)(() => {
|
|
548
632
|
const handleFullscreenChange = () => {
|
|
549
633
|
const iframe = iframeRef.current;
|
|
550
634
|
const fullscreenElement = getFullscreenElement();
|
|
551
635
|
const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
|
|
552
|
-
if (isEmbedFullscreen) {
|
|
636
|
+
if (isEmbedFullscreen && iframe) {
|
|
553
637
|
wasEmbedFullscreenRef.current = true;
|
|
554
638
|
void lockEscapeKey();
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}, 200);
|
|
560
|
-
}
|
|
639
|
+
setTimeout(() => {
|
|
640
|
+
iframe.focus();
|
|
641
|
+
iframe.contentWindow?.focus();
|
|
642
|
+
}, 200);
|
|
561
643
|
return;
|
|
562
644
|
}
|
|
563
645
|
if (fullscreenElement) {
|
|
@@ -602,17 +684,16 @@ var PlayruoEmbed = ({
|
|
|
602
684
|
const logCrossOriginStatus = () => {
|
|
603
685
|
const isIsolated = self.crossOriginIsolated;
|
|
604
686
|
const hasSharedArrayBuffer = typeof SharedArrayBuffer !== "undefined";
|
|
605
|
-
const hasHighResTime = typeof performance !== "undefined" && typeof performance.now === "function";
|
|
606
687
|
console.group("[PlayruoEmbed] \u{1F512} Cross-Origin Isolation Status");
|
|
607
688
|
if (isIsolated) {
|
|
608
689
|
console.log("\u2705 Cross-origin isolation: ENABLED");
|
|
609
690
|
console.log("\u2705 SharedArrayBuffer support:", hasSharedArrayBuffer ? "Available" : "Not available");
|
|
610
|
-
console.log("\u2705 High-resolution timers:
|
|
691
|
+
console.log("\u2705 High-resolution timers: Full precision");
|
|
611
692
|
console.log("\u{1F3AE} Game performance: Optimal");
|
|
612
693
|
} else {
|
|
613
694
|
console.warn("\u26A0\uFE0F Cross-origin isolation: DISABLED");
|
|
614
695
|
console.warn("\u26A0\uFE0F SharedArrayBuffer support:", hasSharedArrayBuffer ? "Available" : "Not available");
|
|
615
|
-
console.warn("\u26A0\uFE0F High-resolution timers:
|
|
696
|
+
console.warn("\u26A0\uFE0F High-resolution timers: Reduced precision");
|
|
616
697
|
console.warn("\u{1F3AE} Game performance: May be degraded");
|
|
617
698
|
console.group("\u{1F4CB} Setup Instructions:");
|
|
618
699
|
console.log("Add these headers to your server:");
|
|
@@ -656,7 +737,7 @@ var PlayruoEmbed = ({
|
|
|
656
737
|
console.log("User agent:", navigator.userAgent);
|
|
657
738
|
console.log("Available APIs:", {
|
|
658
739
|
SharedArrayBuffer: hasSharedArrayBuffer,
|
|
659
|
-
Performance:
|
|
740
|
+
Performance: true,
|
|
660
741
|
WebGL: !!document.createElement("canvas").getContext("webgl"),
|
|
661
742
|
WebGL2: !!document.createElement("canvas").getContext("webgl2"),
|
|
662
743
|
WebAssembly: typeof WebAssembly !== "undefined"
|
|
@@ -688,22 +769,44 @@ var PlayruoEmbed = ({
|
|
|
688
769
|
tryNow: handleTryNow,
|
|
689
770
|
buyNow: handleBuyNow,
|
|
690
771
|
canTryNow,
|
|
691
|
-
canBuyNow: Boolean(isReady)
|
|
772
|
+
canBuyNow: Boolean(isReady),
|
|
773
|
+
submitPassword: (password2) => {
|
|
774
|
+
if (!password2) {
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
postPasswordAction({ action: "submitPassword", password: password2 });
|
|
778
|
+
},
|
|
779
|
+
requestPassword: () => postPasswordAction({ action: "requestPassword" })
|
|
780
|
+
};
|
|
781
|
+
const password = {
|
|
782
|
+
required: passwordRequired,
|
|
783
|
+
checked: passwordChecked,
|
|
784
|
+
unlocked: passwordUnlocked,
|
|
785
|
+
error: passwordError
|
|
692
786
|
};
|
|
693
787
|
if (errorMessage) {
|
|
694
|
-
return renderState({ status: "error", message: errorMessage, actions });
|
|
788
|
+
return renderState({ status: "error", message: errorMessage, actions, password });
|
|
789
|
+
}
|
|
790
|
+
if (password.required && !password.unlocked) {
|
|
791
|
+
return renderState({
|
|
792
|
+
status: "loading",
|
|
793
|
+
message: "Password required",
|
|
794
|
+
actions,
|
|
795
|
+
password
|
|
796
|
+
});
|
|
695
797
|
}
|
|
696
798
|
if (!isReady) {
|
|
697
|
-
return renderState({ status: "loading", message: "Loading\u2026", actions });
|
|
799
|
+
return renderState({ status: "loading", message: "Loading\u2026", actions, password });
|
|
698
800
|
}
|
|
699
801
|
if (isCompatible && !iframeState) {
|
|
700
|
-
return renderState({ status: "loading", message: "Checking availability\u2026", actions });
|
|
802
|
+
return renderState({ status: "loading", message: "Checking availability\u2026", actions, password });
|
|
701
803
|
}
|
|
702
804
|
const message = !isAvailable ? currentState.tryNow.status ?? exposedState.tryNow.status : void 0;
|
|
703
805
|
return renderState({
|
|
704
806
|
status: isAvailable ? "available" : "unavailable",
|
|
705
807
|
message,
|
|
706
|
-
actions
|
|
808
|
+
actions,
|
|
809
|
+
password
|
|
707
810
|
});
|
|
708
811
|
};
|
|
709
812
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, children: [
|
|
@@ -749,12 +852,6 @@ var PlayruoEmbed = ({
|
|
|
749
852
|
},
|
|
750
853
|
onLoad: () => {
|
|
751
854
|
setIsIframeLoaded(true);
|
|
752
|
-
setTimeout(() => {
|
|
753
|
-
if (iframeRef.current) {
|
|
754
|
-
iframeRef.current.focus();
|
|
755
|
-
iframeRef.current.contentWindow?.focus();
|
|
756
|
-
}
|
|
757
|
-
}, 500);
|
|
758
855
|
},
|
|
759
856
|
onError: () => {
|
|
760
857
|
console.error("[PlayruoEmbed] Iframe failed to load");
|
|
@@ -930,20 +1027,11 @@ var EmbedTester = (props = {}) => {
|
|
|
930
1027
|
addEvent(message);
|
|
931
1028
|
}, [addEvent]);
|
|
932
1029
|
const handleApply = (0, import_react2.useCallback)(() => {
|
|
933
|
-
if (!hasPendingConfigChanges) {
|
|
934
|
-
return;
|
|
935
|
-
}
|
|
936
1030
|
setAppliedOperation(operationDraft);
|
|
937
1031
|
setAppliedStoreUrl(storeUrlDraft);
|
|
938
1032
|
setAppliedEnableWindowedLaunch(enableWindowedLaunchDraft);
|
|
939
1033
|
resetEmbedSession("Applied updated embed settings");
|
|
940
|
-
}, [
|
|
941
|
-
enableWindowedLaunchDraft,
|
|
942
|
-
hasPendingConfigChanges,
|
|
943
|
-
operationDraft,
|
|
944
|
-
resetEmbedSession,
|
|
945
|
-
storeUrlDraft
|
|
946
|
-
]);
|
|
1034
|
+
}, [enableWindowedLaunchDraft, operationDraft, resetEmbedSession, storeUrlDraft]);
|
|
947
1035
|
const handleRestartSession = (0, import_react2.useCallback)(() => {
|
|
948
1036
|
resetEmbedSession("Embed restarted with applied settings");
|
|
949
1037
|
}, [resetEmbedSession]);
|
|
@@ -2047,18 +2135,20 @@ var isPlayruoEmbedMessage = (data) => {
|
|
|
2047
2135
|
if (!data || typeof data !== "object") return false;
|
|
2048
2136
|
const msg = data;
|
|
2049
2137
|
if (typeof msg.type !== "string") return false;
|
|
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";
|
|
2138
|
+
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";
|
|
2051
2139
|
};
|
|
2052
2140
|
var isPlayruoInternalMessage = (data) => {
|
|
2053
2141
|
if (!data || typeof data !== "object") return false;
|
|
2054
2142
|
const msg = data;
|
|
2055
|
-
return msg.type === "playruo" && typeof msg.action === "string" && (msg.action === "tryNowClick" || msg.action === "exitFullscreen" || msg.action === "mute" || msg.action === "unmute") && msg.fromEmbed === true;
|
|
2143
|
+
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;
|
|
2056
2144
|
};
|
|
2057
2145
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2058
2146
|
0 && (module.exports = {
|
|
2059
2147
|
EmbedTester,
|
|
2148
|
+
PLAYRUO_UUID_STORAGE_KEY,
|
|
2060
2149
|
PlayruoEmbed,
|
|
2061
2150
|
buildEmbedUrl,
|
|
2062
2151
|
isPlayruoEmbedMessage,
|
|
2063
|
-
isPlayruoInternalMessage
|
|
2152
|
+
isPlayruoInternalMessage,
|
|
2153
|
+
resolveVisitorUuid
|
|
2064
2154
|
});
|