@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 +56 -25
- package/dist/index.d.cts +44 -8
- package/dist/index.d.ts +44 -8
- package/dist/index.js +55 -25
- package/package.json +14 -6
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
|
|
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
|
|
510
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
/**
|
|
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) =>
|
|
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
|
|
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
|
-
/**
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
467
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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": "^
|
|
39
|
-
"@vitest/
|
|
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": "
|
|
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": "^
|
|
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
|
}
|