@playruo/embed 1.4.0 → 1.6.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
@@ -33,6 +39,38 @@ Passing it inside `operation` (`"crypticalpath?op=..."`) still works and warns:
33
39
  it was the only way before this prop existed. `operationKey` wins over it. Other
34
40
  query parameters in `operation` are kept as they are.
35
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
+
36
74
  ## Password protected operations
37
75
 
38
76
  A protected operation renders no form of its own: it announces the gate and the
package/dist/index.cjs CHANGED
@@ -31,13 +31,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  EmbedTester: () => EmbedTester,
34
+ PLAYRUO_IFRAME_ALLOW: () => PLAYRUO_IFRAME_ALLOW,
35
+ PLAYRUO_UUID_STORAGE_KEY: () => PLAYRUO_UUID_STORAGE_KEY,
34
36
  PlayruoEmbed: () => PlayruoEmbed,
35
37
  buildEmbedUrl: () => buildEmbedUrl,
36
38
  isPlayruoEmbedMessage: () => isPlayruoEmbedMessage,
37
- isPlayruoInternalMessage: () => isPlayruoInternalMessage
39
+ isPlayruoInternalMessage: () => isPlayruoInternalMessage,
40
+ resolveVisitorUuid: () => resolveVisitorUuid
38
41
  });
39
42
  module.exports = __toCommonJS(index_exports);
40
43
 
44
+ // src/iframe-allow.ts
45
+ var PLAYRUO_IFRAME_ALLOW = "cross-origin-isolated; fullscreen *; autoplay *; gamepad *; clipboard-write *; web-share *; encrypted-media *; accelerometer *; gyroscope *; screen-wake-lock *";
46
+
41
47
  // src/react/playruo-embed.tsx
42
48
  var import_react = require("react");
43
49
 
@@ -81,6 +87,31 @@ var buildEmbedUrlWithOrigin = (origin, { operation, operationKey, storeUrl }) =>
81
87
  return url.toString();
82
88
  };
83
89
 
90
+ // src/visitor-uuid.ts
91
+ var PLAYRUO_UUID_STORAGE_KEY = "playruo_uuid";
92
+ var generateUUID = () => {
93
+ if (self.crypto.randomUUID) return self.crypto.randomUUID();
94
+ const bytes = new Uint8Array(16);
95
+ self.crypto.getRandomValues(bytes);
96
+ bytes[6] = bytes[6] & 15 | 64;
97
+ bytes[8] = bytes[8] & 63 | 128;
98
+ return [...bytes].map(
99
+ (b, i) => ([4, 6, 8, 10].includes(i) ? "-" : "") + b.toString(16).padStart(2, "0")
100
+ ).join("");
101
+ };
102
+ var resolveVisitorUuid = (clientFingerprint) => {
103
+ const provided = clientFingerprint?.trim();
104
+ if (provided) {
105
+ return provided;
106
+ }
107
+ let uuid = localStorage.getItem(PLAYRUO_UUID_STORAGE_KEY);
108
+ if (!uuid) {
109
+ uuid = generateUUID();
110
+ localStorage.setItem(PLAYRUO_UUID_STORAGE_KEY, uuid);
111
+ }
112
+ return uuid;
113
+ };
114
+
84
115
  // src/react/playruo-embed.tsx
85
116
  var import_jsx_runtime = require("react/jsx-runtime");
86
117
  var PlayruoEmbed = ({
@@ -88,6 +119,7 @@ var PlayruoEmbed = ({
88
119
  operation,
89
120
  operationKey,
90
121
  storeUrl,
122
+ clientFingerprint,
91
123
  showIframe = false,
92
124
  className,
93
125
  onReady,
@@ -573,16 +605,6 @@ var PlayruoEmbed = ({
573
605
  postExitFullscreenAckToIframe,
574
606
  storeUrl
575
607
  ]);
576
- const generateUUID = () => {
577
- if (self.crypto.randomUUID) return self.crypto.randomUUID();
578
- const bytes = new Uint8Array(16);
579
- self.crypto.getRandomValues(bytes);
580
- bytes[6] = bytes[6] & 15 | 64;
581
- bytes[8] = bytes[8] & 63 | 128;
582
- return [...bytes].map(
583
- (b, i) => ([4, 6, 8, 10].includes(i) ? "-" : "") + b.toString(16).padStart(2, "0")
584
- ).join("");
585
- };
586
608
  (0, import_react.useEffect)(() => {
587
609
  const handleUUIDMessage = (event) => {
588
610
  const iframe = iframeRef.current;
@@ -595,16 +617,11 @@ var PlayruoEmbed = ({
595
617
  const { type, requestId } = event.data || {};
596
618
  if (type === "playruo_uuid_request") {
597
619
  try {
598
- let storedUUID = localStorage.getItem("playruo_uuid");
599
- if (!storedUUID) {
600
- storedUUID = generateUUID();
601
- localStorage.setItem("playruo_uuid", storedUUID);
602
- }
603
620
  iframe.contentWindow?.postMessage(
604
621
  {
605
622
  type: "playruo_uuid_response",
606
623
  requestId,
607
- uuid: storedUUID
624
+ uuid: resolveVisitorUuid(clientFingerprint)
608
625
  },
609
626
  embedOrigin
610
627
  );
@@ -614,7 +631,7 @@ var PlayruoEmbed = ({
614
631
  };
615
632
  window.addEventListener("message", handleUUIDMessage);
616
633
  return () => window.removeEventListener("message", handleUUIDMessage);
617
- }, [embedOrigin, iframeRef]);
634
+ }, [clientFingerprint, embedOrigin, iframeRef]);
618
635
  (0, import_react.useEffect)(() => {
619
636
  const handleFullscreenChange = () => {
620
637
  const iframe = iframeRef.current;
@@ -823,7 +840,7 @@ var PlayruoEmbed = ({
823
840
  {
824
841
  ref: iframeRef,
825
842
  src,
826
- allow: "cross-origin-isolated; fullscreen *; autoplay *; gamepad *; clipboard-write *; web-share *; encrypted-media *; accelerometer *; gyroscope *",
843
+ allow: PLAYRUO_IFRAME_ALLOW,
827
844
  className: "w-full h-[70vh] rounded-lg border",
828
845
  ...{ credentialless: "true" },
829
846
  sandbox: "allow-same-origin allow-scripts allow-popups allow-forms allow-presentation allow-top-navigation allow-modals allow-pointer-lock",
@@ -839,12 +856,6 @@ var PlayruoEmbed = ({
839
856
  },
840
857
  onLoad: () => {
841
858
  setIsIframeLoaded(true);
842
- setTimeout(() => {
843
- if (iframeRef.current) {
844
- iframeRef.current.focus();
845
- iframeRef.current.contentWindow?.focus();
846
- }
847
- }, 500);
848
859
  },
849
860
  onError: () => {
850
861
  console.error("[PlayruoEmbed] Iframe failed to load");
@@ -2138,8 +2149,11 @@ var isPlayruoInternalMessage = (data) => {
2138
2149
  // Annotate the CommonJS export names for ESM import in node:
2139
2150
  0 && (module.exports = {
2140
2151
  EmbedTester,
2152
+ PLAYRUO_IFRAME_ALLOW,
2153
+ PLAYRUO_UUID_STORAGE_KEY,
2141
2154
  PlayruoEmbed,
2142
2155
  buildEmbedUrl,
2143
2156
  isPlayruoEmbedMessage,
2144
- isPlayruoInternalMessage
2157
+ isPlayruoInternalMessage,
2158
+ resolveVisitorUuid
2145
2159
  });
package/dist/index.d.cts CHANGED
@@ -1,6 +1,14 @@
1
1
  import { FC, ReactNode } from 'react';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
 
4
+ /**
5
+ * iframe `allow` feature policy delegated to the Playruo player.
6
+ * Must stay aligned with the webapp embed harness (`front/scripts/embed-host/host.js`)
7
+ * and `next.config.ts` Permissions-Policy. `screen-wake-lock` is required so the
8
+ * framed player can hold a Wake Lock while streaming.
9
+ */
10
+ declare const PLAYRUO_IFRAME_ALLOW = "cross-origin-isolated; fullscreen *; autoplay *; gamepad *; clipboard-write *; web-share *; encrypted-media *; accelerometer *; gyroscope *; screen-wake-lock *";
11
+
4
12
  interface PlayruoEmbedState {
5
13
  hasPlayedOnce: boolean;
6
14
  tryNow: {
@@ -180,6 +188,13 @@ interface PlayruoEmbedProps {
180
188
  */
181
189
  operationKey?: string;
182
190
  storeUrl?: string;
191
+ /**
192
+ * Stable visitor identity the host already owns (account id, CRM id, …).
193
+ * When set, answers `playruo_uuid_request` with this value and skips
194
+ * `localStorage["playruo_uuid"]`. Omit to keep the SDK-managed UUID.
195
+ * Playruo derives the session fingerprint from the answered `uuid`.
196
+ */
197
+ clientFingerprint?: string;
183
198
  showIframe?: boolean;
184
199
  className?: string;
185
200
  onReady?: () => void;
@@ -250,4 +265,12 @@ interface BuildUrlParams {
250
265
  */
251
266
  declare const buildEmbedUrl: ({ operation, operationKey, storeUrl, }: BuildUrlParams) => string;
252
267
 
253
- export { type DemoCompletedPayload, EmbedTester, type EmbedTesterProps, type PlayruoDemoCompletedMessage, PlayruoEmbed, type PlayruoEmbedActions, type PlayruoEmbedMessage, type PlayruoEmbedPasswordState, type PlayruoEmbedProps, type PlayruoEmbedRenderState, type PlayruoEmbedState, type PlayruoErrorMessage, type PlayruoEventMessage, type PlayruoEventPayload, type PlayruoInternalMessage, type PlayruoPasswordError, type PlayruoPasswordErrorCode, type PlayruoPasswordNotRequiredMessage, type PlayruoPasswordRequiredMessage, type PlayruoPasswordResultMessage, type PlayruoPurchaseRequestMessage, type PlayruoReadyMessage, type PlayruoRequestExitFullscreenMessage, type PlayruoSessionOverMessage, type PlayruoStateUpdateMessage, type PlayruoStreamStartMessage, type PlayruoStreamStopMessage, buildEmbedUrl, isPlayruoEmbedMessage, isPlayruoInternalMessage };
268
+ declare const PLAYRUO_UUID_STORAGE_KEY = "playruo_uuid";
269
+ /**
270
+ * Visitor identity the host answers on `playruo_uuid_request`.
271
+ * Same rule as PlayruoEmbed `clientFingerprint`: a non-empty value is used as-is
272
+ * and is not written to localStorage. Otherwise the SDK-managed UUID is used.
273
+ */
274
+ declare const resolveVisitorUuid: (clientFingerprint?: string) => string;
275
+
276
+ export { type DemoCompletedPayload, EmbedTester, type EmbedTesterProps, PLAYRUO_IFRAME_ALLOW, PLAYRUO_UUID_STORAGE_KEY, type PlayruoDemoCompletedMessage, PlayruoEmbed, type PlayruoEmbedActions, type PlayruoEmbedMessage, type PlayruoEmbedPasswordState, type PlayruoEmbedProps, type PlayruoEmbedRenderState, type PlayruoEmbedState, type PlayruoErrorMessage, type PlayruoEventMessage, type PlayruoEventPayload, type PlayruoInternalMessage, type PlayruoPasswordError, type PlayruoPasswordErrorCode, type PlayruoPasswordNotRequiredMessage, type PlayruoPasswordRequiredMessage, type PlayruoPasswordResultMessage, type PlayruoPurchaseRequestMessage, type PlayruoReadyMessage, type PlayruoRequestExitFullscreenMessage, type PlayruoSessionOverMessage, type PlayruoStateUpdateMessage, type PlayruoStreamStartMessage, type PlayruoStreamStopMessage, buildEmbedUrl, isPlayruoEmbedMessage, isPlayruoInternalMessage, resolveVisitorUuid };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,14 @@
1
1
  import { FC, ReactNode } from 'react';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
 
4
+ /**
5
+ * iframe `allow` feature policy delegated to the Playruo player.
6
+ * Must stay aligned with the webapp embed harness (`front/scripts/embed-host/host.js`)
7
+ * and `next.config.ts` Permissions-Policy. `screen-wake-lock` is required so the
8
+ * framed player can hold a Wake Lock while streaming.
9
+ */
10
+ declare const PLAYRUO_IFRAME_ALLOW = "cross-origin-isolated; fullscreen *; autoplay *; gamepad *; clipboard-write *; web-share *; encrypted-media *; accelerometer *; gyroscope *; screen-wake-lock *";
11
+
4
12
  interface PlayruoEmbedState {
5
13
  hasPlayedOnce: boolean;
6
14
  tryNow: {
@@ -180,6 +188,13 @@ interface PlayruoEmbedProps {
180
188
  */
181
189
  operationKey?: string;
182
190
  storeUrl?: string;
191
+ /**
192
+ * Stable visitor identity the host already owns (account id, CRM id, …).
193
+ * When set, answers `playruo_uuid_request` with this value and skips
194
+ * `localStorage["playruo_uuid"]`. Omit to keep the SDK-managed UUID.
195
+ * Playruo derives the session fingerprint from the answered `uuid`.
196
+ */
197
+ clientFingerprint?: string;
183
198
  showIframe?: boolean;
184
199
  className?: string;
185
200
  onReady?: () => void;
@@ -250,4 +265,12 @@ interface BuildUrlParams {
250
265
  */
251
266
  declare const buildEmbedUrl: ({ operation, operationKey, storeUrl, }: BuildUrlParams) => string;
252
267
 
253
- export { type DemoCompletedPayload, EmbedTester, type EmbedTesterProps, type PlayruoDemoCompletedMessage, PlayruoEmbed, type PlayruoEmbedActions, type PlayruoEmbedMessage, type PlayruoEmbedPasswordState, type PlayruoEmbedProps, type PlayruoEmbedRenderState, type PlayruoEmbedState, type PlayruoErrorMessage, type PlayruoEventMessage, type PlayruoEventPayload, type PlayruoInternalMessage, type PlayruoPasswordError, type PlayruoPasswordErrorCode, type PlayruoPasswordNotRequiredMessage, type PlayruoPasswordRequiredMessage, type PlayruoPasswordResultMessage, type PlayruoPurchaseRequestMessage, type PlayruoReadyMessage, type PlayruoRequestExitFullscreenMessage, type PlayruoSessionOverMessage, type PlayruoStateUpdateMessage, type PlayruoStreamStartMessage, type PlayruoStreamStopMessage, buildEmbedUrl, isPlayruoEmbedMessage, isPlayruoInternalMessage };
268
+ declare const PLAYRUO_UUID_STORAGE_KEY = "playruo_uuid";
269
+ /**
270
+ * Visitor identity the host answers on `playruo_uuid_request`.
271
+ * Same rule as PlayruoEmbed `clientFingerprint`: a non-empty value is used as-is
272
+ * and is not written to localStorage. Otherwise the SDK-managed UUID is used.
273
+ */
274
+ declare const resolveVisitorUuid: (clientFingerprint?: string) => string;
275
+
276
+ export { type DemoCompletedPayload, EmbedTester, type EmbedTesterProps, PLAYRUO_IFRAME_ALLOW, PLAYRUO_UUID_STORAGE_KEY, type PlayruoDemoCompletedMessage, PlayruoEmbed, type PlayruoEmbedActions, type PlayruoEmbedMessage, type PlayruoEmbedPasswordState, type PlayruoEmbedProps, type PlayruoEmbedRenderState, type PlayruoEmbedState, type PlayruoErrorMessage, type PlayruoEventMessage, type PlayruoEventPayload, type PlayruoInternalMessage, type PlayruoPasswordError, type PlayruoPasswordErrorCode, type PlayruoPasswordNotRequiredMessage, type PlayruoPasswordRequiredMessage, type PlayruoPasswordResultMessage, type PlayruoPurchaseRequestMessage, type PlayruoReadyMessage, type PlayruoRequestExitFullscreenMessage, type PlayruoSessionOverMessage, type PlayruoStateUpdateMessage, type PlayruoStreamStartMessage, type PlayruoStreamStopMessage, buildEmbedUrl, isPlayruoEmbedMessage, isPlayruoInternalMessage, resolveVisitorUuid };
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ // src/iframe-allow.ts
2
+ var PLAYRUO_IFRAME_ALLOW = "cross-origin-isolated; fullscreen *; autoplay *; gamepad *; clipboard-write *; web-share *; encrypted-media *; accelerometer *; gyroscope *; screen-wake-lock *";
3
+
1
4
  // src/react/playruo-embed.tsx
2
5
  import { useEffect, useMemo, useRef, useState, useCallback } from "react";
3
6
 
@@ -41,6 +44,31 @@ var buildEmbedUrlWithOrigin = (origin, { operation, operationKey, storeUrl }) =>
41
44
  return url.toString();
42
45
  };
43
46
 
47
+ // src/visitor-uuid.ts
48
+ var PLAYRUO_UUID_STORAGE_KEY = "playruo_uuid";
49
+ var generateUUID = () => {
50
+ if (self.crypto.randomUUID) return self.crypto.randomUUID();
51
+ const bytes = new Uint8Array(16);
52
+ self.crypto.getRandomValues(bytes);
53
+ bytes[6] = bytes[6] & 15 | 64;
54
+ bytes[8] = bytes[8] & 63 | 128;
55
+ return [...bytes].map(
56
+ (b, i) => ([4, 6, 8, 10].includes(i) ? "-" : "") + b.toString(16).padStart(2, "0")
57
+ ).join("");
58
+ };
59
+ var resolveVisitorUuid = (clientFingerprint) => {
60
+ const provided = clientFingerprint?.trim();
61
+ if (provided) {
62
+ return provided;
63
+ }
64
+ let uuid = localStorage.getItem(PLAYRUO_UUID_STORAGE_KEY);
65
+ if (!uuid) {
66
+ uuid = generateUUID();
67
+ localStorage.setItem(PLAYRUO_UUID_STORAGE_KEY, uuid);
68
+ }
69
+ return uuid;
70
+ };
71
+
44
72
  // src/react/playruo-embed.tsx
45
73
  import { jsx, jsxs } from "react/jsx-runtime";
46
74
  var PlayruoEmbed = ({
@@ -48,6 +76,7 @@ var PlayruoEmbed = ({
48
76
  operation,
49
77
  operationKey,
50
78
  storeUrl,
79
+ clientFingerprint,
51
80
  showIframe = false,
52
81
  className,
53
82
  onReady,
@@ -533,16 +562,6 @@ var PlayruoEmbed = ({
533
562
  postExitFullscreenAckToIframe,
534
563
  storeUrl
535
564
  ]);
536
- const generateUUID = () => {
537
- if (self.crypto.randomUUID) return self.crypto.randomUUID();
538
- const bytes = new Uint8Array(16);
539
- self.crypto.getRandomValues(bytes);
540
- bytes[6] = bytes[6] & 15 | 64;
541
- bytes[8] = bytes[8] & 63 | 128;
542
- return [...bytes].map(
543
- (b, i) => ([4, 6, 8, 10].includes(i) ? "-" : "") + b.toString(16).padStart(2, "0")
544
- ).join("");
545
- };
546
565
  useEffect(() => {
547
566
  const handleUUIDMessage = (event) => {
548
567
  const iframe = iframeRef.current;
@@ -555,16 +574,11 @@ var PlayruoEmbed = ({
555
574
  const { type, requestId } = event.data || {};
556
575
  if (type === "playruo_uuid_request") {
557
576
  try {
558
- let storedUUID = localStorage.getItem("playruo_uuid");
559
- if (!storedUUID) {
560
- storedUUID = generateUUID();
561
- localStorage.setItem("playruo_uuid", storedUUID);
562
- }
563
577
  iframe.contentWindow?.postMessage(
564
578
  {
565
579
  type: "playruo_uuid_response",
566
580
  requestId,
567
- uuid: storedUUID
581
+ uuid: resolveVisitorUuid(clientFingerprint)
568
582
  },
569
583
  embedOrigin
570
584
  );
@@ -574,7 +588,7 @@ var PlayruoEmbed = ({
574
588
  };
575
589
  window.addEventListener("message", handleUUIDMessage);
576
590
  return () => window.removeEventListener("message", handleUUIDMessage);
577
- }, [embedOrigin, iframeRef]);
591
+ }, [clientFingerprint, embedOrigin, iframeRef]);
578
592
  useEffect(() => {
579
593
  const handleFullscreenChange = () => {
580
594
  const iframe = iframeRef.current;
@@ -783,7 +797,7 @@ var PlayruoEmbed = ({
783
797
  {
784
798
  ref: iframeRef,
785
799
  src,
786
- allow: "cross-origin-isolated; fullscreen *; autoplay *; gamepad *; clipboard-write *; web-share *; encrypted-media *; accelerometer *; gyroscope *",
800
+ allow: PLAYRUO_IFRAME_ALLOW,
787
801
  className: "w-full h-[70vh] rounded-lg border",
788
802
  ...{ credentialless: "true" },
789
803
  sandbox: "allow-same-origin allow-scripts allow-popups allow-forms allow-presentation allow-top-navigation allow-modals allow-pointer-lock",
@@ -799,12 +813,6 @@ var PlayruoEmbed = ({
799
813
  },
800
814
  onLoad: () => {
801
815
  setIsIframeLoaded(true);
802
- setTimeout(() => {
803
- if (iframeRef.current) {
804
- iframeRef.current.focus();
805
- iframeRef.current.contentWindow?.focus();
806
- }
807
- }, 500);
808
816
  },
809
817
  onError: () => {
810
818
  console.error("[PlayruoEmbed] Iframe failed to load");
@@ -2097,8 +2105,11 @@ var isPlayruoInternalMessage = (data) => {
2097
2105
  };
2098
2106
  export {
2099
2107
  EmbedTester,
2108
+ PLAYRUO_IFRAME_ALLOW,
2109
+ PLAYRUO_UUID_STORAGE_KEY,
2100
2110
  PlayruoEmbed,
2101
2111
  buildEmbedUrl,
2102
2112
  isPlayruoEmbedMessage,
2103
- isPlayruoInternalMessage
2113
+ isPlayruoInternalMessage,
2114
+ resolveVisitorUuid
2104
2115
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playruo/embed",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "React SDK to embed Playruo with postMessage API.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -53,6 +53,8 @@
53
53
  "check:exports": "attw --pack .",
54
54
  "test": "npm run build && vitest run --coverage",
55
55
  "test:browser": "vitest run --config vitest.browser.config.ts",
56
- "test:watch": "npm run build && vitest"
56
+ "test:watch": "npm run build && vitest",
57
+ "docs:notion:dry-run": "node scripts/sync-notion-docs.mjs",
58
+ "docs:notion:publish": "node scripts/sync-notion-docs.mjs --publish"
57
59
  }
58
60
  }