@playruo/embed 1.4.0 → 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
@@ -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,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
 
@@ -81,6 +83,31 @@ var buildEmbedUrlWithOrigin = (origin, { operation, operationKey, storeUrl }) =>
81
83
  return url.toString();
82
84
  };
83
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
+
84
111
  // src/react/playruo-embed.tsx
85
112
  var import_jsx_runtime = require("react/jsx-runtime");
86
113
  var PlayruoEmbed = ({
@@ -88,6 +115,7 @@ var PlayruoEmbed = ({
88
115
  operation,
89
116
  operationKey,
90
117
  storeUrl,
118
+ clientFingerprint,
91
119
  showIframe = false,
92
120
  className,
93
121
  onReady,
@@ -573,16 +601,6 @@ var PlayruoEmbed = ({
573
601
  postExitFullscreenAckToIframe,
574
602
  storeUrl
575
603
  ]);
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
604
  (0, import_react.useEffect)(() => {
587
605
  const handleUUIDMessage = (event) => {
588
606
  const iframe = iframeRef.current;
@@ -595,16 +613,11 @@ var PlayruoEmbed = ({
595
613
  const { type, requestId } = event.data || {};
596
614
  if (type === "playruo_uuid_request") {
597
615
  try {
598
- let storedUUID = localStorage.getItem("playruo_uuid");
599
- if (!storedUUID) {
600
- storedUUID = generateUUID();
601
- localStorage.setItem("playruo_uuid", storedUUID);
602
- }
603
616
  iframe.contentWindow?.postMessage(
604
617
  {
605
618
  type: "playruo_uuid_response",
606
619
  requestId,
607
- uuid: storedUUID
620
+ uuid: resolveVisitorUuid(clientFingerprint)
608
621
  },
609
622
  embedOrigin
610
623
  );
@@ -614,7 +627,7 @@ var PlayruoEmbed = ({
614
627
  };
615
628
  window.addEventListener("message", handleUUIDMessage);
616
629
  return () => window.removeEventListener("message", handleUUIDMessage);
617
- }, [embedOrigin, iframeRef]);
630
+ }, [clientFingerprint, embedOrigin, iframeRef]);
618
631
  (0, import_react.useEffect)(() => {
619
632
  const handleFullscreenChange = () => {
620
633
  const iframe = iframeRef.current;
@@ -839,12 +852,6 @@ var PlayruoEmbed = ({
839
852
  },
840
853
  onLoad: () => {
841
854
  setIsIframeLoaded(true);
842
- setTimeout(() => {
843
- if (iframeRef.current) {
844
- iframeRef.current.focus();
845
- iframeRef.current.contentWindow?.focus();
846
- }
847
- }, 500);
848
855
  },
849
856
  onError: () => {
850
857
  console.error("[PlayruoEmbed] Iframe failed to load");
@@ -2138,8 +2145,10 @@ var isPlayruoInternalMessage = (data) => {
2138
2145
  // Annotate the CommonJS export names for ESM import in node:
2139
2146
  0 && (module.exports = {
2140
2147
  EmbedTester,
2148
+ PLAYRUO_UUID_STORAGE_KEY,
2141
2149
  PlayruoEmbed,
2142
2150
  buildEmbedUrl,
2143
2151
  isPlayruoEmbedMessage,
2144
- isPlayruoInternalMessage
2152
+ isPlayruoInternalMessage,
2153
+ resolveVisitorUuid
2145
2154
  });
package/dist/index.d.cts CHANGED
@@ -180,6 +180,13 @@ interface PlayruoEmbedProps {
180
180
  */
181
181
  operationKey?: string;
182
182
  storeUrl?: string;
183
+ /**
184
+ * Stable visitor identity the host already owns (account id, CRM id, …).
185
+ * When set, answers `playruo_uuid_request` with this value and skips
186
+ * `localStorage["playruo_uuid"]`. Omit to keep the SDK-managed UUID.
187
+ * Playruo derives the session fingerprint from the answered `uuid`.
188
+ */
189
+ clientFingerprint?: string;
183
190
  showIframe?: boolean;
184
191
  className?: string;
185
192
  onReady?: () => void;
@@ -250,4 +257,12 @@ interface BuildUrlParams {
250
257
  */
251
258
  declare const buildEmbedUrl: ({ operation, operationKey, storeUrl, }: BuildUrlParams) => string;
252
259
 
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 };
260
+ declare const PLAYRUO_UUID_STORAGE_KEY = "playruo_uuid";
261
+ /**
262
+ * Visitor identity the host answers on `playruo_uuid_request`.
263
+ * Same rule as PlayruoEmbed `clientFingerprint`: a non-empty value is used as-is
264
+ * and is not written to localStorage. Otherwise the SDK-managed UUID is used.
265
+ */
266
+ declare const resolveVisitorUuid: (clientFingerprint?: string) => string;
267
+
268
+ export { type DemoCompletedPayload, EmbedTester, type EmbedTesterProps, 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
@@ -180,6 +180,13 @@ interface PlayruoEmbedProps {
180
180
  */
181
181
  operationKey?: string;
182
182
  storeUrl?: string;
183
+ /**
184
+ * Stable visitor identity the host already owns (account id, CRM id, …).
185
+ * When set, answers `playruo_uuid_request` with this value and skips
186
+ * `localStorage["playruo_uuid"]`. Omit to keep the SDK-managed UUID.
187
+ * Playruo derives the session fingerprint from the answered `uuid`.
188
+ */
189
+ clientFingerprint?: string;
183
190
  showIframe?: boolean;
184
191
  className?: string;
185
192
  onReady?: () => void;
@@ -250,4 +257,12 @@ interface BuildUrlParams {
250
257
  */
251
258
  declare const buildEmbedUrl: ({ operation, operationKey, storeUrl, }: BuildUrlParams) => string;
252
259
 
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 };
260
+ declare const PLAYRUO_UUID_STORAGE_KEY = "playruo_uuid";
261
+ /**
262
+ * Visitor identity the host answers on `playruo_uuid_request`.
263
+ * Same rule as PlayruoEmbed `clientFingerprint`: a non-empty value is used as-is
264
+ * and is not written to localStorage. Otherwise the SDK-managed UUID is used.
265
+ */
266
+ declare const resolveVisitorUuid: (clientFingerprint?: string) => string;
267
+
268
+ export { type DemoCompletedPayload, EmbedTester, type EmbedTesterProps, 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
@@ -41,6 +41,31 @@ var buildEmbedUrlWithOrigin = (origin, { operation, operationKey, storeUrl }) =>
41
41
  return url.toString();
42
42
  };
43
43
 
44
+ // src/visitor-uuid.ts
45
+ var PLAYRUO_UUID_STORAGE_KEY = "playruo_uuid";
46
+ var generateUUID = () => {
47
+ if (self.crypto.randomUUID) return self.crypto.randomUUID();
48
+ const bytes = new Uint8Array(16);
49
+ self.crypto.getRandomValues(bytes);
50
+ bytes[6] = bytes[6] & 15 | 64;
51
+ bytes[8] = bytes[8] & 63 | 128;
52
+ return [...bytes].map(
53
+ (b, i) => ([4, 6, 8, 10].includes(i) ? "-" : "") + b.toString(16).padStart(2, "0")
54
+ ).join("");
55
+ };
56
+ var resolveVisitorUuid = (clientFingerprint) => {
57
+ const provided = clientFingerprint?.trim();
58
+ if (provided) {
59
+ return provided;
60
+ }
61
+ let uuid = localStorage.getItem(PLAYRUO_UUID_STORAGE_KEY);
62
+ if (!uuid) {
63
+ uuid = generateUUID();
64
+ localStorage.setItem(PLAYRUO_UUID_STORAGE_KEY, uuid);
65
+ }
66
+ return uuid;
67
+ };
68
+
44
69
  // src/react/playruo-embed.tsx
45
70
  import { jsx, jsxs } from "react/jsx-runtime";
46
71
  var PlayruoEmbed = ({
@@ -48,6 +73,7 @@ var PlayruoEmbed = ({
48
73
  operation,
49
74
  operationKey,
50
75
  storeUrl,
76
+ clientFingerprint,
51
77
  showIframe = false,
52
78
  className,
53
79
  onReady,
@@ -533,16 +559,6 @@ var PlayruoEmbed = ({
533
559
  postExitFullscreenAckToIframe,
534
560
  storeUrl
535
561
  ]);
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
562
  useEffect(() => {
547
563
  const handleUUIDMessage = (event) => {
548
564
  const iframe = iframeRef.current;
@@ -555,16 +571,11 @@ var PlayruoEmbed = ({
555
571
  const { type, requestId } = event.data || {};
556
572
  if (type === "playruo_uuid_request") {
557
573
  try {
558
- let storedUUID = localStorage.getItem("playruo_uuid");
559
- if (!storedUUID) {
560
- storedUUID = generateUUID();
561
- localStorage.setItem("playruo_uuid", storedUUID);
562
- }
563
574
  iframe.contentWindow?.postMessage(
564
575
  {
565
576
  type: "playruo_uuid_response",
566
577
  requestId,
567
- uuid: storedUUID
578
+ uuid: resolveVisitorUuid(clientFingerprint)
568
579
  },
569
580
  embedOrigin
570
581
  );
@@ -574,7 +585,7 @@ var PlayruoEmbed = ({
574
585
  };
575
586
  window.addEventListener("message", handleUUIDMessage);
576
587
  return () => window.removeEventListener("message", handleUUIDMessage);
577
- }, [embedOrigin, iframeRef]);
588
+ }, [clientFingerprint, embedOrigin, iframeRef]);
578
589
  useEffect(() => {
579
590
  const handleFullscreenChange = () => {
580
591
  const iframe = iframeRef.current;
@@ -799,12 +810,6 @@ var PlayruoEmbed = ({
799
810
  },
800
811
  onLoad: () => {
801
812
  setIsIframeLoaded(true);
802
- setTimeout(() => {
803
- if (iframeRef.current) {
804
- iframeRef.current.focus();
805
- iframeRef.current.contentWindow?.focus();
806
- }
807
- }, 500);
808
813
  },
809
814
  onError: () => {
810
815
  console.error("[PlayruoEmbed] Iframe failed to load");
@@ -2097,8 +2102,10 @@ var isPlayruoInternalMessage = (data) => {
2097
2102
  };
2098
2103
  export {
2099
2104
  EmbedTester,
2105
+ PLAYRUO_UUID_STORAGE_KEY,
2100
2106
  PlayruoEmbed,
2101
2107
  buildEmbedUrl,
2102
2108
  isPlayruoEmbedMessage,
2103
- isPlayruoInternalMessage
2109
+ isPlayruoInternalMessage,
2110
+ resolveVisitorUuid
2104
2111
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playruo/embed",
3
- "version": "1.4.0",
3
+ "version": "1.5.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
  }