@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/dist/index.js CHANGED
@@ -8,15 +8,33 @@ var normalizeOperation = (operation) => {
8
8
  if (trimmedOperation === "") {
9
9
  throw new Error("`operation` is required to build a Playruo embed URL.");
10
10
  }
11
+ const operationQuery = trimmedOperation.split("?")[1];
12
+ if (operationQuery != null && new URLSearchParams(operationQuery).has("op")) {
13
+ console.warn(
14
+ "[PlayruoEmbed] `?op=` inside `operation` is deprecated: pass it as the `operationKey` prop."
15
+ );
16
+ }
11
17
  return trimmedOperation.startsWith("/") ? trimmedOperation.slice(1) : trimmedOperation;
12
18
  };
13
- var buildEmbedUrl = ({ operation, storeUrl }) => {
14
- return buildEmbedUrlWithOrigin(PLAYRUO_ORIGIN, { operation, storeUrl });
19
+ var buildEmbedUrl = ({
20
+ operation,
21
+ operationKey,
22
+ storeUrl
23
+ }) => {
24
+ return buildEmbedUrlWithOrigin(PLAYRUO_ORIGIN, {
25
+ operation,
26
+ operationKey,
27
+ storeUrl
28
+ });
15
29
  };
16
- var buildEmbedUrlWithOrigin = (origin, { operation, storeUrl }) => {
30
+ var buildEmbedUrlWithOrigin = (origin, { operation, operationKey, storeUrl }) => {
17
31
  const operationPath = normalizeOperation(operation);
18
32
  const url = new URL(`/${operationPath}`, origin);
19
33
  url.searchParams.set("iframe", "true");
34
+ const trimmedOperationKey = operationKey?.trim();
35
+ if (trimmedOperationKey) {
36
+ url.searchParams.set("op", trimmedOperationKey);
37
+ }
20
38
  if (storeUrl) {
21
39
  url.searchParams.set("storeUrl", storeUrl);
22
40
  }
@@ -28,6 +46,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
28
46
  var PlayruoEmbed = ({
29
47
  baseUrl,
30
48
  operation,
49
+ operationKey,
31
50
  storeUrl,
32
51
  showIframe = false,
33
52
  className,
@@ -37,6 +56,9 @@ var PlayruoEmbed = ({
37
56
  onEvent,
38
57
  onPurchaseRequest,
39
58
  onError,
59
+ onPasswordRequired,
60
+ onPasswordResult,
61
+ enableWindowedLaunch = false,
40
62
  forwardEventsToParent = true,
41
63
  renderState,
42
64
  iframeRef: externalIframeRef
@@ -53,12 +75,24 @@ var PlayruoEmbed = ({
53
75
  const [isValidChromium, setIsValidChromium] = useState(true);
54
76
  const [errorMessage, setErrorMessage] = useState(null);
55
77
  const [iframeState, setIframeState] = useState(null);
78
+ const [passwordRequired, setPasswordRequired] = useState(false);
79
+ const [passwordChecked, setPasswordChecked] = useState(false);
80
+ const [passwordUnlocked, setPasswordUnlocked] = useState(false);
81
+ const [passwordError, setPasswordError] = useState(
82
+ void 0
83
+ );
56
84
  const src = useMemo(
57
- () => baseUrl != null && baseUrl !== "" ? buildEmbedUrlWithOrigin(baseUrl, { operation, storeUrl }) : buildEmbedUrl({ operation, storeUrl }),
58
- [baseUrl, operation, storeUrl]
85
+ () => baseUrl != null && baseUrl !== "" ? buildEmbedUrlWithOrigin(baseUrl, { operation, operationKey, storeUrl }) : buildEmbedUrl({ operation, operationKey, storeUrl }),
86
+ [baseUrl, operation, operationKey, storeUrl]
59
87
  );
88
+ const embedOrigin = useMemo(() => new URL(src).origin, [src]);
89
+ useEffect(() => {
90
+ setPasswordRequired(false);
91
+ setPasswordChecked(false);
92
+ setPasswordUnlocked(false);
93
+ setPasswordError(void 0);
94
+ }, [src]);
60
95
  useEffect(() => {
61
- if (typeof window === "undefined") return;
62
96
  const detectMobile = () => {
63
97
  const userAgent = navigator.userAgent || navigator.vendor || window.opera;
64
98
  return /android|iPad|iPhone|iPod/i.test(userAgent);
@@ -96,15 +130,14 @@ var PlayruoEmbed = ({
96
130
  tryNow: {
97
131
  ...currentState.tryNow,
98
132
  available: hasIframeConfirmed,
99
- status: currentState.tryNow.status ?? (hasIframeConfirmed ? "Ready" : isCompatible ? "Checking availability\u2026" : "Browser not compatible")
133
+ // A missing status means the state came from the iframe, which only loads on
134
+ // compatible browsers — the incompatible copy is supplied by currentState.
135
+ status: currentState.tryNow.status ?? (hasIframeConfirmed ? "Ready" : "Checking availability\u2026")
100
136
  }
101
137
  }),
102
- [currentState, hasIframeConfirmed, isCompatible]
138
+ [currentState, hasIframeConfirmed]
103
139
  );
104
140
  const getKeyboardApi = useCallback(() => {
105
- if (typeof navigator === "undefined") {
106
- return void 0;
107
- }
108
141
  return navigator.keyboard;
109
142
  }, []);
110
143
  const releaseEscapeKeyLock = useCallback(() => {
@@ -134,6 +167,23 @@ var PlayruoEmbed = ({
134
167
  },
135
168
  [onDemoCompleted, storeUrl]
136
169
  );
170
+ const postPasswordAction = useCallback(
171
+ (message) => {
172
+ const contentWindow = iframeRef.current?.contentWindow;
173
+ if (!contentWindow) {
174
+ return;
175
+ }
176
+ try {
177
+ contentWindow.postMessage(
178
+ { type: "playruo", fromEmbed: true, ...message },
179
+ embedOrigin
180
+ );
181
+ } catch {
182
+ console.error("[PlayruoEmbed] Failed to send the password action");
183
+ }
184
+ },
185
+ [embedOrigin, iframeRef]
186
+ );
137
187
  const postExitFullscreenToIframe = useCallback(() => {
138
188
  const iframe = iframeRef.current;
139
189
  if (!iframe) {
@@ -146,22 +196,22 @@ var PlayruoEmbed = ({
146
196
  fromEmbed: true,
147
197
  isFullscreen: false,
148
198
  parentWillHandleFullscreen: true
149
- }, "*");
199
+ }, embedOrigin);
150
200
  } catch {
151
201
  console.error("[PlayruoEmbed] Failed to exit fullscreen");
152
202
  }
153
- }, [iframeRef]);
203
+ }, [embedOrigin, iframeRef]);
154
204
  const postExitFullscreenAckToIframe = useCallback(() => {
155
205
  const iframe = iframeRef.current;
156
206
  if (!iframe) {
157
207
  return;
158
208
  }
159
209
  try {
160
- iframe.contentWindow?.postMessage({ type: "ACK_EXIT_FULLSCREEN" }, "*");
210
+ iframe.contentWindow?.postMessage({ type: "ACK_EXIT_FULLSCREEN" }, embedOrigin);
161
211
  } catch {
162
212
  console.error("[PlayruoEmbed] Failed to acknowledge fullscreen exit");
163
213
  }
164
- }, [iframeRef]);
214
+ }, [embedOrigin, iframeRef]);
165
215
  const exitEmbedFullscreen = useCallback(
166
216
  async ({
167
217
  reason = "cleanup",
@@ -247,7 +297,7 @@ var PlayruoEmbed = ({
247
297
  }, []);
248
298
  const forwardEventToParent = useCallback(
249
299
  (message) => {
250
- if (!forwardEventsToParent || typeof window === "undefined" || window.parent === window) {
300
+ if (!forwardEventsToParent || window.parent === window) {
251
301
  return;
252
302
  }
253
303
  try {
@@ -297,19 +347,26 @@ var PlayruoEmbed = ({
297
347
  hasNotifiedCompletionRef.current = false;
298
348
  setShowGameIframe(true);
299
349
  try {
300
- try {
301
- await requestImmersiveMode(iframe);
302
- } catch (fsError) {
303
- console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
350
+ let isParentFullscreen = false;
351
+ if (enableWindowedLaunch) {
352
+ releaseEscapeKeyLock();
353
+ console.log("[PlayruoEmbed] Windowed launch enabled, skipping fullscreen request");
354
+ } else {
355
+ try {
356
+ await requestImmersiveMode(iframe);
357
+ isParentFullscreen = true;
358
+ } catch (fsError) {
359
+ console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
360
+ }
304
361
  }
305
362
  iframe.contentWindow?.postMessage({
306
363
  type: "playruo",
307
364
  action: "tryNowClick",
308
365
  fromEmbed: true,
309
- isFullscreen: true,
310
- parentWillHandleFullscreen: true,
366
+ isFullscreen: isParentFullscreen,
367
+ parentWillHandleFullscreen: isParentFullscreen,
311
368
  isReplay: hasPlayedOnce
312
- }, "*");
369
+ }, embedOrigin);
313
370
  console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
314
371
  iframe.focus();
315
372
  requestAnimationFrame(() => {
@@ -322,7 +379,15 @@ var PlayruoEmbed = ({
322
379
  setErrorMessage("Failed to start game");
323
380
  onError?.("Failed to start game");
324
381
  }
325
- }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestImmersiveMode]);
382
+ }, [
383
+ enableWindowedLaunch,
384
+ hasPlayedOnce,
385
+ iframeRef,
386
+ isCompatible,
387
+ onError,
388
+ releaseEscapeKeyLock,
389
+ requestImmersiveMode
390
+ ]);
326
391
  const handleBuyNow = useCallback(() => {
327
392
  if (!storeUrl) {
328
393
  console.warn("[PlayruoEmbed] No store URL provided");
@@ -348,6 +413,9 @@ var PlayruoEmbed = ({
348
413
  if (!iframe || event.source !== iframe.contentWindow) {
349
414
  return;
350
415
  }
416
+ if (event.origin !== embedOrigin) {
417
+ return;
418
+ }
351
419
  console.log("\u{1F537} [Embed] Message received from iframe:", { type, data });
352
420
  if (type === "playruo_ready") {
353
421
  console.log("\u{1F537} [Embed] playruo_ready received");
@@ -416,6 +484,24 @@ var PlayruoEmbed = ({
416
484
  const eventPayload = message.payload;
417
485
  onEvent?.(eventPayload);
418
486
  forwardEventToParent({ type: "playruo_event", data: eventPayload });
487
+ } else if (type === "playruo_password_required") {
488
+ console.log("\u{1F537} [Embed] playruo_password_required received");
489
+ setPasswordRequired(true);
490
+ setPasswordChecked(true);
491
+ setPasswordUnlocked(false);
492
+ setPasswordError(void 0);
493
+ onPasswordRequired?.();
494
+ } else if (type === "playruo_password_not_required") {
495
+ console.log("\u{1F537} [Embed] playruo_password_not_required received");
496
+ setPasswordRequired(false);
497
+ setPasswordChecked(true);
498
+ } else if (type === "playruo_password_result" && data) {
499
+ const result = data;
500
+ const success = result.success === true;
501
+ console.log("\u{1F537} [Embed] playruo_password_result received:", result);
502
+ setPasswordUnlocked(success);
503
+ setPasswordError(success ? void 0 : result.error ?? "unknown");
504
+ onPasswordResult?.({ success, error: result.error });
419
505
  }
420
506
  if (type === "error" || type === "closeForBuy" || type === "goToStore") {
421
507
  console.log("\u{1F537} [Embed] Legacy message received:", type);
@@ -431,6 +517,7 @@ var PlayruoEmbed = ({
431
517
  return () => window.removeEventListener("message", handleGameMessage);
432
518
  }, [
433
519
  cleanupEmbedSession,
520
+ embedOrigin,
434
521
  exitEmbedFullscreen,
435
522
  forwardEventToParent,
436
523
  hasPlayedOnce,
@@ -438,6 +525,8 @@ var PlayruoEmbed = ({
438
525
  markStreamActive,
439
526
  onEvent,
440
527
  onError,
528
+ onPasswordRequired,
529
+ onPasswordResult,
441
530
  onPurchaseRequest,
442
531
  onReady,
443
532
  onStateChange,
@@ -460,6 +549,9 @@ var PlayruoEmbed = ({
460
549
  if (!iframe || event.source !== iframe.contentWindow) {
461
550
  return;
462
551
  }
552
+ if (event.origin !== embedOrigin) {
553
+ return;
554
+ }
463
555
  const { type, requestId } = event.data || {};
464
556
  if (type === "playruo_uuid_request") {
465
557
  try {
@@ -474,34 +566,27 @@ var PlayruoEmbed = ({
474
566
  requestId,
475
567
  uuid: storedUUID
476
568
  },
477
- "*"
569
+ embedOrigin
478
570
  );
479
571
  } catch (e) {
480
572
  }
481
- } else if (type === "playruo_uuid_remove") {
482
- try {
483
- localStorage.removeItem("playruo_uuid");
484
- } catch (e) {
485
- }
486
573
  }
487
574
  };
488
575
  window.addEventListener("message", handleUUIDMessage);
489
576
  return () => window.removeEventListener("message", handleUUIDMessage);
490
- }, [iframeRef]);
577
+ }, [embedOrigin, iframeRef]);
491
578
  useEffect(() => {
492
579
  const handleFullscreenChange = () => {
493
580
  const iframe = iframeRef.current;
494
581
  const fullscreenElement = getFullscreenElement();
495
582
  const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
496
- if (isEmbedFullscreen) {
583
+ if (isEmbedFullscreen && iframe) {
497
584
  wasEmbedFullscreenRef.current = true;
498
585
  void lockEscapeKey();
499
- if (iframe) {
500
- setTimeout(() => {
501
- iframe.focus();
502
- iframe.contentWindow?.focus();
503
- }, 200);
504
- }
586
+ setTimeout(() => {
587
+ iframe.focus();
588
+ iframe.contentWindow?.focus();
589
+ }, 200);
505
590
  return;
506
591
  }
507
592
  if (fullscreenElement) {
@@ -546,17 +631,16 @@ var PlayruoEmbed = ({
546
631
  const logCrossOriginStatus = () => {
547
632
  const isIsolated = self.crossOriginIsolated;
548
633
  const hasSharedArrayBuffer = typeof SharedArrayBuffer !== "undefined";
549
- const hasHighResTime = typeof performance !== "undefined" && typeof performance.now === "function";
550
634
  console.group("[PlayruoEmbed] \u{1F512} Cross-Origin Isolation Status");
551
635
  if (isIsolated) {
552
636
  console.log("\u2705 Cross-origin isolation: ENABLED");
553
637
  console.log("\u2705 SharedArrayBuffer support:", hasSharedArrayBuffer ? "Available" : "Not available");
554
- console.log("\u2705 High-resolution timers:", hasHighResTime ? "Available" : "Not available");
638
+ console.log("\u2705 High-resolution timers: Full precision");
555
639
  console.log("\u{1F3AE} Game performance: Optimal");
556
640
  } else {
557
641
  console.warn("\u26A0\uFE0F Cross-origin isolation: DISABLED");
558
642
  console.warn("\u26A0\uFE0F SharedArrayBuffer support:", hasSharedArrayBuffer ? "Available" : "Not available");
559
- console.warn("\u26A0\uFE0F High-resolution timers:", hasHighResTime ? "Available" : "Not available");
643
+ console.warn("\u26A0\uFE0F High-resolution timers: Reduced precision");
560
644
  console.warn("\u{1F3AE} Game performance: May be degraded");
561
645
  console.group("\u{1F4CB} Setup Instructions:");
562
646
  console.log("Add these headers to your server:");
@@ -600,7 +684,7 @@ var PlayruoEmbed = ({
600
684
  console.log("User agent:", navigator.userAgent);
601
685
  console.log("Available APIs:", {
602
686
  SharedArrayBuffer: hasSharedArrayBuffer,
603
- Performance: hasHighResTime,
687
+ Performance: true,
604
688
  WebGL: !!document.createElement("canvas").getContext("webgl"),
605
689
  WebGL2: !!document.createElement("canvas").getContext("webgl2"),
606
690
  WebAssembly: typeof WebAssembly !== "undefined"
@@ -632,22 +716,44 @@ var PlayruoEmbed = ({
632
716
  tryNow: handleTryNow,
633
717
  buyNow: handleBuyNow,
634
718
  canTryNow,
635
- canBuyNow: Boolean(isReady)
719
+ canBuyNow: Boolean(isReady),
720
+ submitPassword: (password2) => {
721
+ if (!password2) {
722
+ return;
723
+ }
724
+ postPasswordAction({ action: "submitPassword", password: password2 });
725
+ },
726
+ requestPassword: () => postPasswordAction({ action: "requestPassword" })
727
+ };
728
+ const password = {
729
+ required: passwordRequired,
730
+ checked: passwordChecked,
731
+ unlocked: passwordUnlocked,
732
+ error: passwordError
636
733
  };
637
734
  if (errorMessage) {
638
- return renderState({ status: "error", message: errorMessage, actions });
735
+ return renderState({ status: "error", message: errorMessage, actions, password });
736
+ }
737
+ if (password.required && !password.unlocked) {
738
+ return renderState({
739
+ status: "loading",
740
+ message: "Password required",
741
+ actions,
742
+ password
743
+ });
639
744
  }
640
745
  if (!isReady) {
641
- return renderState({ status: "loading", message: "Loading\u2026", actions });
746
+ return renderState({ status: "loading", message: "Loading\u2026", actions, password });
642
747
  }
643
748
  if (isCompatible && !iframeState) {
644
- return renderState({ status: "loading", message: "Checking availability\u2026", actions });
749
+ return renderState({ status: "loading", message: "Checking availability\u2026", actions, password });
645
750
  }
646
751
  const message = !isAvailable ? currentState.tryNow.status ?? exposedState.tryNow.status : void 0;
647
752
  return renderState({
648
753
  status: isAvailable ? "available" : "unavailable",
649
754
  message,
650
- actions
755
+ actions,
756
+ password
651
757
  });
652
758
  };
653
759
  return /* @__PURE__ */ jsxs("div", { className, children: [
@@ -742,10 +848,16 @@ var EmbedTester = (props = {}) => {
742
848
  const [storeUrlDraft, setStoreUrlDraft] = useState2(
743
849
  props.storeUrl ?? "https://store.steampowered.com/app/123456"
744
850
  );
851
+ const [enableWindowedLaunchDraft, setEnableWindowedLaunchDraft] = useState2(
852
+ props.enableWindowedLaunch ?? false
853
+ );
745
854
  const [appliedOperation, setAppliedOperation] = useState2(props.operation ?? "iframe");
746
855
  const [appliedStoreUrl, setAppliedStoreUrl] = useState2(
747
856
  props.storeUrl ?? "https://store.steampowered.com/app/123456"
748
857
  );
858
+ const [appliedEnableWindowedLaunch, setAppliedEnableWindowedLaunch] = useState2(
859
+ props.enableWindowedLaunch ?? false
860
+ );
749
861
  const [key, setKey] = useState2(0);
750
862
  const [hasUserClicked, setHasUserClicked] = useState2(false);
751
863
  const [showIframe, setShowIframe] = useState2(false);
@@ -760,8 +872,9 @@ var EmbedTester = (props = {}) => {
760
872
  const iframeRef = useRef2(null);
761
873
  const isOperationPending = operationDraft !== appliedOperation;
762
874
  const isStoreUrlPending = storeUrlDraft !== appliedStoreUrl;
763
- const hasPendingConfigChanges = isOperationPending || isStoreUrlPending;
764
- const pendingChangeCount = Number(isOperationPending) + Number(isStoreUrlPending);
875
+ const isWindowedLaunchPending = enableWindowedLaunchDraft !== appliedEnableWindowedLaunch;
876
+ const hasPendingConfigChanges = isOperationPending || isStoreUrlPending || isWindowedLaunchPending;
877
+ const pendingChangeCount = Number(isOperationPending) + Number(isStoreUrlPending) + Number(isWindowedLaunchPending);
765
878
  const addEvent = useCallback2(
766
879
  (message) => {
767
880
  const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
@@ -867,21 +980,20 @@ var EmbedTester = (props = {}) => {
867
980
  addEvent(message);
868
981
  }, [addEvent]);
869
982
  const handleApply = useCallback2(() => {
870
- if (!hasPendingConfigChanges) {
871
- return;
872
- }
873
983
  setAppliedOperation(operationDraft);
874
984
  setAppliedStoreUrl(storeUrlDraft);
985
+ setAppliedEnableWindowedLaunch(enableWindowedLaunchDraft);
875
986
  resetEmbedSession("Applied updated embed settings");
876
- }, [hasPendingConfigChanges, operationDraft, resetEmbedSession, storeUrlDraft]);
987
+ }, [enableWindowedLaunchDraft, operationDraft, resetEmbedSession, storeUrlDraft]);
877
988
  const handleRestartSession = useCallback2(() => {
878
989
  resetEmbedSession("Embed restarted with applied settings");
879
990
  }, [resetEmbedSession]);
880
991
  const handleResetDrafts = useCallback2(() => {
881
992
  setOperationDraft(appliedOperation);
882
993
  setStoreUrlDraft(appliedStoreUrl);
994
+ setEnableWindowedLaunchDraft(appliedEnableWindowedLaunch);
883
995
  addEvent("Reverted draft changes to the applied configuration");
884
- }, [addEvent, appliedOperation, appliedStoreUrl]);
996
+ }, [addEvent, appliedEnableWindowedLaunch, appliedOperation, appliedStoreUrl]);
885
997
  const panelStyle = {
886
998
  position: "relative",
887
999
  border: "1px solid rgba(148, 163, 184, 0.18)",
@@ -1449,6 +1561,50 @@ var EmbedTester = (props = {}) => {
1449
1561
  "Applied value: ",
1450
1562
  /* @__PURE__ */ jsx2("code", { style: codeStyle, children: appliedStoreUrl })
1451
1563
  ] })
1564
+ ] }),
1565
+ /* @__PURE__ */ jsxs2("div", { children: [
1566
+ /* @__PURE__ */ jsxs2("div", { style: labelStyle, children: [
1567
+ /* @__PURE__ */ jsx2("span", { children: "Launch mode" }),
1568
+ /* @__PURE__ */ jsxs2("span", { style: buildIndicatorStyle(isWindowedLaunchPending), children: [
1569
+ /* @__PURE__ */ jsx2("span", { style: buildIndicatorDotStyle(isWindowedLaunchPending) }),
1570
+ isWindowedLaunchPending ? "Pending" : "Live"
1571
+ ] })
1572
+ ] }),
1573
+ /* @__PURE__ */ jsxs2(
1574
+ "label",
1575
+ {
1576
+ htmlFor: "enableWindowedLaunch",
1577
+ style: {
1578
+ ...buildInputShellStyle(isWindowedLaunchPending),
1579
+ justifyContent: "space-between",
1580
+ minHeight: "48px",
1581
+ cursor: "pointer"
1582
+ },
1583
+ children: [
1584
+ /* @__PURE__ */ jsx2("span", { style: { color: "#f8fafc", fontSize: "15px", fontWeight: 600 }, children: "Windowed launch" }),
1585
+ /* @__PURE__ */ jsx2(
1586
+ "input",
1587
+ {
1588
+ id: "enableWindowedLaunch",
1589
+ type: "checkbox",
1590
+ checked: enableWindowedLaunchDraft,
1591
+ onChange: (event) => setEnableWindowedLaunchDraft(event.target.checked),
1592
+ style: {
1593
+ width: "18px",
1594
+ height: "18px",
1595
+ accentColor: "#38bdf8",
1596
+ cursor: "pointer"
1597
+ }
1598
+ }
1599
+ )
1600
+ ]
1601
+ }
1602
+ ),
1603
+ /* @__PURE__ */ jsxs2("p", { style: helperTextStyle, children: [
1604
+ "Applied value:",
1605
+ " ",
1606
+ /* @__PURE__ */ jsx2("code", { style: codeStyle, children: appliedEnableWindowedLaunch ? "windowed" : "fullscreen request" })
1607
+ ] })
1452
1608
  ] })
1453
1609
  ] }),
1454
1610
  /* @__PURE__ */ jsxs2(
@@ -1728,6 +1884,10 @@ var EmbedTester = (props = {}) => {
1728
1884
  /* @__PURE__ */ jsxs2("span", { style: { ...codeStyle, padding: "6px 10px" }, children: [
1729
1885
  "Operation: ",
1730
1886
  appliedOperation
1887
+ ] }),
1888
+ /* @__PURE__ */ jsxs2("span", { style: { ...codeStyle, padding: "6px 10px" }, children: [
1889
+ "Launch: ",
1890
+ appliedEnableWindowedLaunch ? "windowed" : "fullscreen request"
1731
1891
  ] })
1732
1892
  ] })
1733
1893
  ]
@@ -1803,6 +1963,7 @@ var EmbedTester = (props = {}) => {
1803
1963
  onPurchaseRequest: handlePurchaseRequest,
1804
1964
  onReady: handleReady,
1805
1965
  onStateChange: handleStateChange,
1966
+ enableWindowedLaunch: appliedEnableWindowedLaunch,
1806
1967
  operation: appliedOperation,
1807
1968
  renderState: ({ actions, message, status }) => /* @__PURE__ */ jsx2(Fragment, { children: !hasUserClicked && /* @__PURE__ */ jsx2(
1808
1969
  "div",
@@ -1927,12 +2088,12 @@ var isPlayruoEmbedMessage = (data) => {
1927
2088
  if (!data || typeof data !== "object") return false;
1928
2089
  const msg = data;
1929
2090
  if (typeof msg.type !== "string") return false;
1930
- 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";
2091
+ 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";
1931
2092
  };
1932
2093
  var isPlayruoInternalMessage = (data) => {
1933
2094
  if (!data || typeof data !== "object") return false;
1934
2095
  const msg = data;
1935
- return msg.type === "playruo" && typeof msg.action === "string" && (msg.action === "tryNowClick" || msg.action === "exitFullscreen" || msg.action === "mute" || msg.action === "unmute") && msg.fromEmbed === true;
2096
+ 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;
1936
2097
  };
1937
2098
  export {
1938
2099
  EmbedTester,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playruo/embed",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "React SDK to embed Playruo with postMessage API.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,27 +9,50 @@
9
9
  "types": "dist/index.d.ts",
10
10
  "exports": {
11
11
  ".": {
12
- "types": "./dist/index.d.ts",
13
- "import": "./dist/index.js",
14
- "require": "./dist/index.cjs",
15
- "default": "./dist/index.js"
12
+ "import": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/index.d.cts",
18
+ "default": "./dist/index.cjs"
19
+ }
16
20
  }
17
21
  },
18
- "files": ["dist"],
22
+ "files": [
23
+ "dist"
24
+ ],
19
25
  "sideEffects": false,
20
26
  "peerDependencies": {
21
27
  "react": ">=18",
22
28
  "react-dom": ">=18"
23
29
  },
24
30
  "devDependencies": {
31
+ "@arethetypeswrong/cli": "^0.18.5",
32
+ "@testing-library/jest-dom": "^6.6.3",
33
+ "@testing-library/react": "^16.3.0",
34
+ "@testing-library/user-event": "^14.6.1",
35
+ "@types/node": "^26.2.0",
36
+ "@types/react": "^19",
37
+ "@types/react-dom": "^19",
38
+ "@vitest/browser": "^3.2.7",
39
+ "@vitest/coverage-v8": "^3.2.4",
40
+ "jsdom": "^26.1.0",
41
+ "playwright": "^1.62.1",
42
+ "react": "^19.1.0",
43
+ "react-dom": "^19.1.0",
25
44
  "tsup": "^8.0.0",
26
45
  "typescript": "^5.4.0",
27
- "@types/react": "^19",
28
- "@types/react-dom": "^19"
46
+ "vitest": "^3.2.4"
29
47
  },
30
48
  "scripts": {
31
49
  "build": "tsup src/index.ts --format esm,cjs --dts --clean",
32
50
  "dev": "tsup src/index.ts --format esm --dts --watch",
33
- "prepare": "npm run build"
51
+ "prepare": "npm run build",
52
+ "typecheck": "tsc -p tsconfig.test.json",
53
+ "check:exports": "attw --pack .",
54
+ "test": "npm run build && vitest run --coverage",
55
+ "test:browser": "vitest run --config vitest.browser.config.ts",
56
+ "test:watch": "npm run build && vitest"
34
57
  }
35
58
  }