@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 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 = ({ operation, storeUrl }) => {
54
- return buildEmbedUrlWithOrigin(PLAYRUO_ORIGIN, { operation, storeUrl });
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
- status: currentState.tryNow.status ?? (hasIframeConfirmed ? "Ready" : isCompatible ? "Checking availability\u2026" : "Browser not compatible")
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, isCompatible]
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 || typeof window === "undefined" || window.parent === window) {
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
- try {
341
- await requestImmersiveMode(iframe);
342
- } catch (fsError) {
343
- console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
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: true,
350
- parentWillHandleFullscreen: true,
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
- }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestImmersiveMode]);
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
- if (iframe) {
540
- setTimeout(() => {
541
- iframe.focus();
542
- iframe.contentWindow?.focus();
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:", hasHighResTime ? "Available" : "Not available");
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:", hasHighResTime ? "Available" : "Not available");
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: hasHighResTime,
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 hasPendingConfigChanges = isOperationPending || isStoreUrlPending;
804
- const pendingChangeCount = Number(isOperationPending) + Number(isStoreUrlPending);
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
- }, [hasPendingConfigChanges, operationDraft, resetEmbedSession, storeUrlDraft]);
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 = {