@playruo/embed 1.6.0 → 1.8.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.cjs CHANGED
@@ -37,6 +37,7 @@ __export(index_exports, {
37
37
  buildEmbedUrl: () => buildEmbedUrl,
38
38
  isPlayruoEmbedMessage: () => isPlayruoEmbedMessage,
39
39
  isPlayruoInternalMessage: () => isPlayruoInternalMessage,
40
+ normalizePlayruoError: () => normalizePlayruoError,
40
41
  resolveVisitorUuid: () => resolveVisitorUuid
41
42
  });
42
43
  module.exports = __toCommonJS(index_exports);
@@ -47,6 +48,37 @@ var PLAYRUO_IFRAME_ALLOW = "cross-origin-isolated; fullscreen *; autoplay *; gam
47
48
  // src/react/playruo-embed.tsx
48
49
  var import_react = require("react");
49
50
 
51
+ // src/post-message-types.ts
52
+ var normalizePlayruoError = (data) => {
53
+ if (typeof data === "string") {
54
+ return { message: data || "Unknown error" };
55
+ }
56
+ if (!data || typeof data !== "object") {
57
+ return { message: "Unknown error" };
58
+ }
59
+ const record = data;
60
+ const message = typeof record.message === "string" && record.message ? record.message : "Unknown error";
61
+ const details = { message };
62
+ if (typeof record.code === "string") {
63
+ details.code = record.code;
64
+ }
65
+ if (typeof record.event === "string") {
66
+ details.event = record.event;
67
+ }
68
+ return details;
69
+ };
70
+ var isPlayruoEmbedMessage = (data) => {
71
+ if (!data || typeof data !== "object") return false;
72
+ const msg = data;
73
+ if (typeof msg.type !== "string") return false;
74
+ 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";
75
+ };
76
+ var isPlayruoInternalMessage = (data) => {
77
+ if (!data || typeof data !== "object") return false;
78
+ const msg = data;
79
+ 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;
80
+ };
81
+
50
82
  // src/url.ts
51
83
  var PLAYRUO_ORIGIN = "https://playruo.live";
52
84
  var normalizeOperation = (operation) => {
@@ -179,6 +211,14 @@ var PlayruoEmbed = ({
179
211
  }, []);
180
212
  const isCompatible = !isMobile && isValidChromium;
181
213
  const isReady = isCompatible ? isIframeLoaded : true;
214
+ (0, import_react.useEffect)(() => {
215
+ const iframe = iframeRef.current;
216
+ if (!iframe || iframe.getAttribute("src") === src) {
217
+ return;
218
+ }
219
+ iframe.setAttribute("credentialless", "");
220
+ iframe.src = src;
221
+ }, [iframeRef, isCompatible, src]);
182
222
  const currentState = (0, import_react.useMemo)(() => {
183
223
  if (iframeState) {
184
224
  return iframeState;
@@ -251,7 +291,7 @@ var PlayruoEmbed = ({
251
291
  embedOrigin
252
292
  );
253
293
  } catch {
254
- console.error("[PlayruoEmbed] Failed to send the password action");
294
+ console.error("[PlayruoEmbed] Failed to send iframe action");
255
295
  }
256
296
  },
257
297
  [embedOrigin, iframeRef]
@@ -449,7 +489,7 @@ var PlayruoEmbed = ({
449
489
  console.error("[PlayruoEmbed] Failed to start game:", error);
450
490
  setShowGameIframe(false);
451
491
  setErrorMessage("Failed to start game");
452
- onError?.("Failed to start game");
492
+ onError?.("Failed to start game", { message: "Failed to start game" });
453
493
  }
454
494
  }, [
455
495
  enableWindowedLaunch,
@@ -506,8 +546,9 @@ var PlayruoEmbed = ({
506
546
  console.log("\u{1F537} [Embed] playruo_demo_completed received:", data);
507
547
  cleanupEmbedSession({ payload: data });
508
548
  } else if (type === "playruo_error") {
509
- const errorMessage2 = typeof data === "string" ? data : data?.message || "Unknown error";
510
- console.log("\u{1F537} [Embed] playruo_error received:", errorMessage2);
549
+ const details = normalizePlayruoError(data);
550
+ const errorMessage2 = details.message;
551
+ console.log("\u{1F537} [Embed] playruo_error received:", errorMessage2, details);
511
552
  setErrorMessage(errorMessage2);
512
553
  const errorState = {
513
554
  hasPlayedOnce,
@@ -522,7 +563,7 @@ var PlayruoEmbed = ({
522
563
  };
523
564
  setIframeState(errorState);
524
565
  onStateChange?.(errorState);
525
- onError?.(errorMessage2);
566
+ onError?.(errorMessage2, details);
526
567
  } else if (type === "playruo_session_over" && data) {
527
568
  console.log("\u{1F537} [Embed] playruo_session_over received:", data);
528
569
  void exitEmbedFullscreen({ reason: "playruo_session_over" });
@@ -773,7 +814,9 @@ var PlayruoEmbed = ({
773
814
  tryNow: handleTryNow,
774
815
  buyNow: handleBuyNow,
775
816
  canTryNow,
776
- canBuyNow: Boolean(isReady),
817
+ canBuyNow: Boolean(isReady && storeUrl),
818
+ mute: () => postPasswordAction({ action: "mute" }),
819
+ unmute: () => postPasswordAction({ action: "unmute" }),
777
820
  submitPassword: (password2) => {
778
821
  if (!password2) {
779
822
  return;
@@ -839,10 +882,8 @@ var PlayruoEmbed = ({
839
882
  "iframe",
840
883
  {
841
884
  ref: iframeRef,
842
- src,
843
885
  allow: PLAYRUO_IFRAME_ALLOW,
844
886
  className: "w-full h-[70vh] rounded-lg border",
845
- ...{ credentialless: "true" },
846
887
  sandbox: "allow-same-origin allow-scripts allow-popups allow-forms allow-presentation allow-top-navigation allow-modals allow-pointer-lock",
847
888
  title: "Playruo",
848
889
  referrerPolicy: "strict-origin-when-cross-origin",
@@ -854,13 +895,15 @@ var PlayruoEmbed = ({
854
895
  height: "100%",
855
896
  border: 0
856
897
  },
857
- onLoad: () => {
858
- setIsIframeLoaded(true);
898
+ onLoad: (event) => {
899
+ if (event.currentTarget.getAttribute("src")) {
900
+ setIsIframeLoaded(true);
901
+ }
859
902
  },
860
903
  onError: () => {
861
904
  console.error("[PlayruoEmbed] Iframe failed to load");
862
905
  setErrorMessage("Failed to load game");
863
- onError?.("Failed to load game");
906
+ onError?.("Failed to load game", { message: "Failed to load game" });
864
907
  }
865
908
  }
866
909
  )
@@ -1010,7 +1053,7 @@ var EmbedTester = (props = {}) => {
1010
1053
  }
1011
1054
  }, [addEvent, enableConsoleLog]);
1012
1055
  const handleEvent = (0, import_react2.useCallback)((data) => {
1013
- const eventName = typeof data.event === "string" ? data.event : typeof data.name === "string" ? data.name : "unknown";
1056
+ const eventName = typeof data.name === "string" ? data.name : typeof data.event === "string" ? data.event : "unknown";
1014
1057
  addEvent(`Event received: ${eventName}`);
1015
1058
  if (enableConsoleLog) {
1016
1059
  console.log("Embed event:", data);
@@ -2133,19 +2176,6 @@ var EmbedTester = (props = {}) => {
2133
2176
  }
2134
2177
  );
2135
2178
  };
2136
-
2137
- // src/post-message-types.ts
2138
- var isPlayruoEmbedMessage = (data) => {
2139
- if (!data || typeof data !== "object") return false;
2140
- const msg = data;
2141
- if (typeof msg.type !== "string") return false;
2142
- 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";
2143
- };
2144
- var isPlayruoInternalMessage = (data) => {
2145
- if (!data || typeof data !== "object") return false;
2146
- const msg = data;
2147
- 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;
2148
- };
2149
2179
  // Annotate the CommonJS export names for ESM import in node:
2150
2180
  0 && (module.exports = {
2151
2181
  EmbedTester,
@@ -2155,5 +2185,6 @@ var isPlayruoInternalMessage = (data) => {
2155
2185
  buildEmbedUrl,
2156
2186
  isPlayruoEmbedMessage,
2157
2187
  isPlayruoInternalMessage,
2188
+ normalizePlayruoError,
2158
2189
  resolveVisitorUuid
2159
2190
  });
package/dist/index.d.cts CHANGED
@@ -1,5 +1,4 @@
1
- import { FC, ReactNode } from 'react';
2
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import React$1, { FC, ReactNode } from 'react';
3
2
 
4
3
  /**
5
4
  * iframe `allow` feature policy delegated to the Playruo player.
@@ -28,7 +27,13 @@ interface DemoCompletedPayload {
28
27
  type PlayruoEventPayload = Record<string, unknown> & {
29
28
  action?: string;
30
29
  category?: string;
30
+ /**
31
+ * @deprecated The webapp puts the event identifier on `name`. Nothing it
32
+ * posts carries `event`, so a host switching on it never matches. Kept so
33
+ * older hosts still compile.
34
+ */
31
35
  event?: string;
36
+ /** Event identifier, snake_case (`auth_ok`, `streaming`, `vmnok`). */
32
37
  name?: string;
33
38
  };
34
39
 
@@ -49,12 +54,35 @@ interface PlayruoDemoCompletedMessage {
49
54
  type: "playruo_demo_completed";
50
55
  data: DemoCompletedPayload;
51
56
  }
57
+ /**
58
+ * What React `onError` sees after normalisation. `code` and `event` come from
59
+ * structured hard errors; extras such as `visibility` are dropped on purpose.
60
+ */
61
+ type PlayruoErrorDetails = {
62
+ message: string;
63
+ code?: string;
64
+ event?: string;
65
+ };
52
66
  interface PlayruoErrorMessage {
53
67
  type: "playruo_error";
68
+ /**
69
+ * Production hard errors carry `code` and `event`. Soft capacity blocks and
70
+ * legacy paths may send only `{ message }` or a bare string. Extra keys may
71
+ * appear on the wire; hosts that go through `normalizePlayruoError` never see
72
+ * them.
73
+ */
54
74
  data: {
55
75
  message: string;
76
+ code?: string;
77
+ event?: string;
56
78
  } | string;
57
79
  }
80
+ /**
81
+ * Reduce a `playruo_error.data` value (or a host-local string) to the shape
82
+ * React `onError` receives. Only string `code` / `event` survive; everything
83
+ * else is dropped so a host cannot grow a dependency on accidental extras.
84
+ */
85
+ declare const normalizePlayruoError: (data: unknown) => PlayruoErrorDetails;
58
86
  interface PlayruoSessionOverMessage {
59
87
  type: "playruo_session_over";
60
88
  data: PlayruoEmbedState;
@@ -139,8 +167,16 @@ interface PlayruoEmbedActions {
139
167
  buyNow: () => void;
140
168
  /** Whether try now is currently available */
141
169
  canTryNow: boolean;
142
- /** Whether buy now is currently available */
170
+ /**
171
+ * Whether buy now can call `onPurchaseRequest`. True only when the SDK is
172
+ * ready and a `storeUrl` prop is set. Compatible with mobile/unavailable
173
+ * hosts that still want a store CTA.
174
+ */
143
175
  canBuyNow: boolean;
176
+ /** Mute active stream audio. No acknowledgement is returned. */
177
+ mute: () => void;
178
+ /** Resume and unmute stream audio. No acknowledgement is returned. */
179
+ unmute: () => void;
144
180
  /** Send the password collected by the host to a protected operation. */
145
181
  submitPassword: (password: string) => void;
146
182
  /**
@@ -204,7 +240,7 @@ interface PlayruoEmbedProps {
204
240
  onPurchaseRequest?: (data: {
205
241
  storeUrl: string;
206
242
  }) => void;
207
- onError?: (error: string) => void;
243
+ onError?: (error: string, details: PlayruoErrorDetails) => void;
208
244
  /** The operation is password protected and waits for one. */
209
245
  onPasswordRequired?: () => void;
210
246
  /** Answer to a submitted password. */
@@ -245,7 +281,7 @@ interface EmbedTesterProps {
245
281
  enableWindowedLaunch?: boolean;
246
282
  className?: string;
247
283
  }
248
- declare const EmbedTester: (props?: EmbedTesterProps) => react_jsx_runtime.JSX.Element;
284
+ declare const EmbedTester: (props?: EmbedTesterProps) => React$1.JSX.Element;
249
285
 
250
286
  interface BuildUrlParams {
251
287
  operation: string;
@@ -268,9 +304,9 @@ declare const buildEmbedUrl: ({ operation, operationKey, storeUrl, }: BuildUrlPa
268
304
  declare const PLAYRUO_UUID_STORAGE_KEY = "playruo_uuid";
269
305
  /**
270
306
  * 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.
307
+ * Same rule as PlayruoEmbed `clientFingerprint`: a non-empty trimmed value is
308
+ * used and is not written to localStorage. Otherwise the SDK-managed UUID is used.
273
309
  */
274
310
  declare const resolveVisitorUuid: (clientFingerprint?: string) => string;
275
311
 
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 };
312
+ 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 PlayruoErrorDetails, 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, normalizePlayruoError, resolveVisitorUuid };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { FC, ReactNode } from 'react';
2
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import React$1, { FC, ReactNode } from 'react';
3
2
 
4
3
  /**
5
4
  * iframe `allow` feature policy delegated to the Playruo player.
@@ -28,7 +27,13 @@ interface DemoCompletedPayload {
28
27
  type PlayruoEventPayload = Record<string, unknown> & {
29
28
  action?: string;
30
29
  category?: string;
30
+ /**
31
+ * @deprecated The webapp puts the event identifier on `name`. Nothing it
32
+ * posts carries `event`, so a host switching on it never matches. Kept so
33
+ * older hosts still compile.
34
+ */
31
35
  event?: string;
36
+ /** Event identifier, snake_case (`auth_ok`, `streaming`, `vmnok`). */
32
37
  name?: string;
33
38
  };
34
39
 
@@ -49,12 +54,35 @@ interface PlayruoDemoCompletedMessage {
49
54
  type: "playruo_demo_completed";
50
55
  data: DemoCompletedPayload;
51
56
  }
57
+ /**
58
+ * What React `onError` sees after normalisation. `code` and `event` come from
59
+ * structured hard errors; extras such as `visibility` are dropped on purpose.
60
+ */
61
+ type PlayruoErrorDetails = {
62
+ message: string;
63
+ code?: string;
64
+ event?: string;
65
+ };
52
66
  interface PlayruoErrorMessage {
53
67
  type: "playruo_error";
68
+ /**
69
+ * Production hard errors carry `code` and `event`. Soft capacity blocks and
70
+ * legacy paths may send only `{ message }` or a bare string. Extra keys may
71
+ * appear on the wire; hosts that go through `normalizePlayruoError` never see
72
+ * them.
73
+ */
54
74
  data: {
55
75
  message: string;
76
+ code?: string;
77
+ event?: string;
56
78
  } | string;
57
79
  }
80
+ /**
81
+ * Reduce a `playruo_error.data` value (or a host-local string) to the shape
82
+ * React `onError` receives. Only string `code` / `event` survive; everything
83
+ * else is dropped so a host cannot grow a dependency on accidental extras.
84
+ */
85
+ declare const normalizePlayruoError: (data: unknown) => PlayruoErrorDetails;
58
86
  interface PlayruoSessionOverMessage {
59
87
  type: "playruo_session_over";
60
88
  data: PlayruoEmbedState;
@@ -139,8 +167,16 @@ interface PlayruoEmbedActions {
139
167
  buyNow: () => void;
140
168
  /** Whether try now is currently available */
141
169
  canTryNow: boolean;
142
- /** Whether buy now is currently available */
170
+ /**
171
+ * Whether buy now can call `onPurchaseRequest`. True only when the SDK is
172
+ * ready and a `storeUrl` prop is set. Compatible with mobile/unavailable
173
+ * hosts that still want a store CTA.
174
+ */
143
175
  canBuyNow: boolean;
176
+ /** Mute active stream audio. No acknowledgement is returned. */
177
+ mute: () => void;
178
+ /** Resume and unmute stream audio. No acknowledgement is returned. */
179
+ unmute: () => void;
144
180
  /** Send the password collected by the host to a protected operation. */
145
181
  submitPassword: (password: string) => void;
146
182
  /**
@@ -204,7 +240,7 @@ interface PlayruoEmbedProps {
204
240
  onPurchaseRequest?: (data: {
205
241
  storeUrl: string;
206
242
  }) => void;
207
- onError?: (error: string) => void;
243
+ onError?: (error: string, details: PlayruoErrorDetails) => void;
208
244
  /** The operation is password protected and waits for one. */
209
245
  onPasswordRequired?: () => void;
210
246
  /** Answer to a submitted password. */
@@ -245,7 +281,7 @@ interface EmbedTesterProps {
245
281
  enableWindowedLaunch?: boolean;
246
282
  className?: string;
247
283
  }
248
- declare const EmbedTester: (props?: EmbedTesterProps) => react_jsx_runtime.JSX.Element;
284
+ declare const EmbedTester: (props?: EmbedTesterProps) => React$1.JSX.Element;
249
285
 
250
286
  interface BuildUrlParams {
251
287
  operation: string;
@@ -268,9 +304,9 @@ declare const buildEmbedUrl: ({ operation, operationKey, storeUrl, }: BuildUrlPa
268
304
  declare const PLAYRUO_UUID_STORAGE_KEY = "playruo_uuid";
269
305
  /**
270
306
  * 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.
307
+ * Same rule as PlayruoEmbed `clientFingerprint`: a non-empty trimmed value is
308
+ * used and is not written to localStorage. Otherwise the SDK-managed UUID is used.
273
309
  */
274
310
  declare const resolveVisitorUuid: (clientFingerprint?: string) => string;
275
311
 
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 };
312
+ 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 PlayruoErrorDetails, 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, normalizePlayruoError, resolveVisitorUuid };
package/dist/index.js CHANGED
@@ -4,6 +4,37 @@ var PLAYRUO_IFRAME_ALLOW = "cross-origin-isolated; fullscreen *; autoplay *; gam
4
4
  // src/react/playruo-embed.tsx
5
5
  import { useEffect, useMemo, useRef, useState, useCallback } from "react";
6
6
 
7
+ // src/post-message-types.ts
8
+ var normalizePlayruoError = (data) => {
9
+ if (typeof data === "string") {
10
+ return { message: data || "Unknown error" };
11
+ }
12
+ if (!data || typeof data !== "object") {
13
+ return { message: "Unknown error" };
14
+ }
15
+ const record = data;
16
+ const message = typeof record.message === "string" && record.message ? record.message : "Unknown error";
17
+ const details = { message };
18
+ if (typeof record.code === "string") {
19
+ details.code = record.code;
20
+ }
21
+ if (typeof record.event === "string") {
22
+ details.event = record.event;
23
+ }
24
+ return details;
25
+ };
26
+ var isPlayruoEmbedMessage = (data) => {
27
+ if (!data || typeof data !== "object") return false;
28
+ const msg = data;
29
+ if (typeof msg.type !== "string") return false;
30
+ 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";
31
+ };
32
+ var isPlayruoInternalMessage = (data) => {
33
+ if (!data || typeof data !== "object") return false;
34
+ const msg = data;
35
+ 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;
36
+ };
37
+
7
38
  // src/url.ts
8
39
  var PLAYRUO_ORIGIN = "https://playruo.live";
9
40
  var normalizeOperation = (operation) => {
@@ -136,6 +167,14 @@ var PlayruoEmbed = ({
136
167
  }, []);
137
168
  const isCompatible = !isMobile && isValidChromium;
138
169
  const isReady = isCompatible ? isIframeLoaded : true;
170
+ useEffect(() => {
171
+ const iframe = iframeRef.current;
172
+ if (!iframe || iframe.getAttribute("src") === src) {
173
+ return;
174
+ }
175
+ iframe.setAttribute("credentialless", "");
176
+ iframe.src = src;
177
+ }, [iframeRef, isCompatible, src]);
139
178
  const currentState = useMemo(() => {
140
179
  if (iframeState) {
141
180
  return iframeState;
@@ -208,7 +247,7 @@ var PlayruoEmbed = ({
208
247
  embedOrigin
209
248
  );
210
249
  } catch {
211
- console.error("[PlayruoEmbed] Failed to send the password action");
250
+ console.error("[PlayruoEmbed] Failed to send iframe action");
212
251
  }
213
252
  },
214
253
  [embedOrigin, iframeRef]
@@ -406,7 +445,7 @@ var PlayruoEmbed = ({
406
445
  console.error("[PlayruoEmbed] Failed to start game:", error);
407
446
  setShowGameIframe(false);
408
447
  setErrorMessage("Failed to start game");
409
- onError?.("Failed to start game");
448
+ onError?.("Failed to start game", { message: "Failed to start game" });
410
449
  }
411
450
  }, [
412
451
  enableWindowedLaunch,
@@ -463,8 +502,9 @@ var PlayruoEmbed = ({
463
502
  console.log("\u{1F537} [Embed] playruo_demo_completed received:", data);
464
503
  cleanupEmbedSession({ payload: data });
465
504
  } else if (type === "playruo_error") {
466
- const errorMessage2 = typeof data === "string" ? data : data?.message || "Unknown error";
467
- console.log("\u{1F537} [Embed] playruo_error received:", errorMessage2);
505
+ const details = normalizePlayruoError(data);
506
+ const errorMessage2 = details.message;
507
+ console.log("\u{1F537} [Embed] playruo_error received:", errorMessage2, details);
468
508
  setErrorMessage(errorMessage2);
469
509
  const errorState = {
470
510
  hasPlayedOnce,
@@ -479,7 +519,7 @@ var PlayruoEmbed = ({
479
519
  };
480
520
  setIframeState(errorState);
481
521
  onStateChange?.(errorState);
482
- onError?.(errorMessage2);
522
+ onError?.(errorMessage2, details);
483
523
  } else if (type === "playruo_session_over" && data) {
484
524
  console.log("\u{1F537} [Embed] playruo_session_over received:", data);
485
525
  void exitEmbedFullscreen({ reason: "playruo_session_over" });
@@ -730,7 +770,9 @@ var PlayruoEmbed = ({
730
770
  tryNow: handleTryNow,
731
771
  buyNow: handleBuyNow,
732
772
  canTryNow,
733
- canBuyNow: Boolean(isReady),
773
+ canBuyNow: Boolean(isReady && storeUrl),
774
+ mute: () => postPasswordAction({ action: "mute" }),
775
+ unmute: () => postPasswordAction({ action: "unmute" }),
734
776
  submitPassword: (password2) => {
735
777
  if (!password2) {
736
778
  return;
@@ -796,10 +838,8 @@ var PlayruoEmbed = ({
796
838
  "iframe",
797
839
  {
798
840
  ref: iframeRef,
799
- src,
800
841
  allow: PLAYRUO_IFRAME_ALLOW,
801
842
  className: "w-full h-[70vh] rounded-lg border",
802
- ...{ credentialless: "true" },
803
843
  sandbox: "allow-same-origin allow-scripts allow-popups allow-forms allow-presentation allow-top-navigation allow-modals allow-pointer-lock",
804
844
  title: "Playruo",
805
845
  referrerPolicy: "strict-origin-when-cross-origin",
@@ -811,13 +851,15 @@ var PlayruoEmbed = ({
811
851
  height: "100%",
812
852
  border: 0
813
853
  },
814
- onLoad: () => {
815
- setIsIframeLoaded(true);
854
+ onLoad: (event) => {
855
+ if (event.currentTarget.getAttribute("src")) {
856
+ setIsIframeLoaded(true);
857
+ }
816
858
  },
817
859
  onError: () => {
818
860
  console.error("[PlayruoEmbed] Iframe failed to load");
819
861
  setErrorMessage("Failed to load game");
820
- onError?.("Failed to load game");
862
+ onError?.("Failed to load game", { message: "Failed to load game" });
821
863
  }
822
864
  }
823
865
  )
@@ -967,7 +1009,7 @@ var EmbedTester = (props = {}) => {
967
1009
  }
968
1010
  }, [addEvent, enableConsoleLog]);
969
1011
  const handleEvent = useCallback2((data) => {
970
- const eventName = typeof data.event === "string" ? data.event : typeof data.name === "string" ? data.name : "unknown";
1012
+ const eventName = typeof data.name === "string" ? data.name : typeof data.event === "string" ? data.event : "unknown";
971
1013
  addEvent(`Event received: ${eventName}`);
972
1014
  if (enableConsoleLog) {
973
1015
  console.log("Embed event:", data);
@@ -2090,19 +2132,6 @@ var EmbedTester = (props = {}) => {
2090
2132
  }
2091
2133
  );
2092
2134
  };
2093
-
2094
- // src/post-message-types.ts
2095
- var isPlayruoEmbedMessage = (data) => {
2096
- if (!data || typeof data !== "object") return false;
2097
- const msg = data;
2098
- if (typeof msg.type !== "string") return false;
2099
- 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";
2100
- };
2101
- var isPlayruoInternalMessage = (data) => {
2102
- if (!data || typeof data !== "object") return false;
2103
- const msg = data;
2104
- 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;
2105
- };
2106
2135
  export {
2107
2136
  EmbedTester,
2108
2137
  PLAYRUO_IFRAME_ALLOW,
@@ -2111,5 +2140,6 @@ export {
2111
2140
  buildEmbedUrl,
2112
2141
  isPlayruoEmbedMessage,
2113
2142
  isPlayruoInternalMessage,
2143
+ normalizePlayruoError,
2114
2144
  resolveVisitorUuid
2115
2145
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playruo/embed",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "React SDK to embed Playruo with postMessage API.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,15 +35,21 @@
35
35
  "@types/node": "^26.2.0",
36
36
  "@types/react": "^19",
37
37
  "@types/react-dom": "^19",
38
- "@vitest/browser": "^3.2.7",
39
- "@vitest/coverage-v8": "^3.2.4",
38
+ "@vitest/browser": "^5.0.2",
39
+ "@vitest/browser-playwright": "^5.0.2",
40
+ "@vitest/coverage-v8": "^5.0.2",
40
41
  "jsdom": "^26.1.0",
41
- "playwright": "^1.62.1",
42
+ "playwright": "1.62.1",
42
43
  "react": "^19.1.0",
43
44
  "react-dom": "^19.1.0",
44
45
  "tsup": "^8.0.0",
45
46
  "typescript": "^5.4.0",
46
- "vitest": "^3.2.4"
47
+ "vitest": "^5.0.2"
48
+ },
49
+ "overrides": {
50
+ "esbuild": "^0.28.1",
51
+ "picomatch": "^4.0.4",
52
+ "rollup": "^4.59.0"
47
53
  },
48
54
  "scripts": {
49
55
  "build": "tsup src/index.ts --format esm,cjs --dts --clean",
@@ -52,9 +58,11 @@
52
58
  "typecheck": "tsc -p tsconfig.test.json",
53
59
  "check:exports": "attw --pack .",
54
60
  "test": "npm run build && vitest run --coverage",
61
+ "test:smoke": "vitest run tests/smoke",
55
62
  "test:browser": "vitest run --config vitest.browser.config.ts",
56
63
  "test:watch": "npm run build && vitest",
57
64
  "docs:notion:dry-run": "node scripts/sync-notion-docs.mjs",
58
- "docs:notion:publish": "node scripts/sync-notion-docs.mjs --publish"
65
+ "docs:notion:publish": "node scripts/sync-notion-docs.mjs --publish",
66
+ "docs:catalog:check": "node scripts/check-embed-event-catalog.mjs && node scripts/check-embed-error-catalog.mjs"
59
67
  }
60
68
  }