@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 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 = ({ operation, storeUrl }) => {
54
- return buildEmbedUrlWithOrigin(PLAYRUO_ORIGIN, { operation, storeUrl });
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
- status: currentState.tryNow.status ?? (hasIframeConfirmed ? "Ready" : isCompatible ? "Checking availability\u2026" : "Browser not compatible")
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, isCompatible]
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 || typeof window === "undefined" || window.parent === window) {
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: storedUUID
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
- if (iframe) {
556
- setTimeout(() => {
557
- iframe.focus();
558
- iframe.contentWindow?.focus();
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:", hasHighResTime ? "Available" : "Not available");
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:", hasHighResTime ? "Available" : "Not available");
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: hasHighResTime,
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
  });