@playruo/embed 1.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Playruo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # @playruo/embed
2
+
3
+ React SDK to embed Playruo via iframe + postMessage.
4
+
5
+ See the official docs: [Playruo Embed Documentation](https://playruo.notion.site/Playruo-Embed-Documentation-268a5e7d674180dd901ae0753187d7c1?pvs=74).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm i @playruo/embed
11
+ ```
12
+
13
+ ## Quick start (Next.js / React)
14
+
15
+ ```tsx
16
+ "use client";
17
+ import { PlayruoEmbed } from "@playruo/embed";
18
+
19
+ export const Demo = () => (
20
+ <PlayruoEmbed
21
+ operation="iframe"
22
+ storeUrl="https://store.steampowered.com/app/2751000/..."
23
+ showIframe
24
+ onReady={() => console.log("ready")}
25
+ onStateChange={(state) => console.log("state", state)}
26
+ onDemoCompleted={(data) => console.log("completed", data)}
27
+ onPurchaseRequest={({ storeUrl }) => {
28
+ // Example: open the store page in a new tab
29
+ window.open(storeUrl, "_blank", "noopener,noreferrer");
30
+ }}
31
+ onError={(message) => console.error(message)}
32
+ />
33
+ );
34
+ ```
35
+
36
+ ## Headless mode (with renderState)
37
+
38
+ ```tsx
39
+ import { PlayruoEmbed } from "@playruo/embed";
40
+
41
+ <PlayruoEmbed
42
+ // operation is optional, defaults to "iframe"
43
+ storeUrl="https://store.steampowered.com/app/2751000/..."
44
+ showIframe
45
+ renderState={({ status, message }) => {
46
+ if (status === "loading") return <div>Loading demo…</div>;
47
+ if (status === "error") return <div>Error: {message}</div>;
48
+ if (status === "available")
49
+ return (
50
+ <div className="flex gap-3">
51
+ <button>Try Now</button>
52
+ </div>
53
+ );
54
+ // unavailable
55
+ return (
56
+ <div className="flex gap-3">
57
+ <span>{message ?? "Unavailable"}</span>
58
+ <button>Buy Now</button>
59
+ </div>
60
+ );
61
+ }}
62
+ />
63
+ ```
64
+
65
+ ### Props
66
+
67
+ - **operation**: string (optional, default: "iframe")
68
+ - **storeUrl**: string (optional)
69
+ - **showIframe**: boolean (optional)
70
+ - **className**: string (optional)
71
+ - **onReady()**: callback when the iframe signals readiness
72
+ - **onStateChange(state)**: receives live availability state
73
+ - **onDemoCompleted(data)**: fired when the demo session completes
74
+ - **onPurchaseRequest({ storeUrl })**: fired when purchase is requested
75
+ - **onError(message)**: fired on error
76
+
77
+ #### Render API
78
+
79
+ - **renderState({ status, message })**: single render entry point
80
+ - `status`: "loading" | "available" | "unavailable" | "error"
81
+ - `message?`: optional status message (e.g. availability reason)
82
+
83
+
84
+ ### Events
85
+
86
+ - **onReady()**
87
+ - **onStateChange(state)**
88
+ - **onDemoCompleted(data)**
89
+ - **onPurchaseRequest({ storeUrl })**
90
+ - **onError(error)**
91
+
92
+ ## State shape
93
+
94
+ ```ts
95
+ export interface PlayruoEmbedState {
96
+ hasPlayedOnce: boolean;
97
+ tryNow: { available: boolean; status?: string };
98
+ buyNow: { available: boolean; status?: string };
99
+ }
100
+ ```
101
+
102
+ ## Commands (postMessage)
103
+
104
+ The component exposes two imperative actions via the render API:
105
+
106
+ - `tryNow()` → posts `"try_now"` to the iframe
107
+ - `buyNow()` → posts `"buy_now"` to the iframe
108
+
109
+ ## Incoming messages (from iframe)
110
+
111
+ The component listens to Playruo postMessage events and triggers the corresponding callbacks:
112
+
113
+ - `"playruo:ready"` (also accepts legacy `"playruo_ready"`) → `onReady()`
114
+ - `"playruo:state"` (also accepts legacy `"playruo_state_update"`) → `onStateChange(state)`
115
+ - `"playruo:demoCompleted"` (also accepts legacy `"playruo_demo_completed"`) → `onDemoCompleted(data)`
116
+ - `"playruo:purchaseRequest"` (also accepts legacy `"playruo_purchase_request"`) → `onPurchaseRequest({ storeUrl })`
117
+ - `"playruo:error"` (also accepts legacy `"playruo_error"`) → `onError(message)`
118
+
119
+ ## URL builder
120
+
121
+ You can build the iframe URL manually if needed:
122
+
123
+ ```ts
124
+ import { buildEmbedUrl } from "@playruo/embed";
125
+
126
+ const url = buildEmbedUrl({
127
+ operation: "iframe", // default
128
+ storeUrl: "https://store.steampowered.com/app/2751000/...", // optional
129
+ });
130
+ ``;
131
+
132
+ The URL points to `"/embed"` and accepts the query params:
133
+
134
+ - `operation` (string, default: `"iframe"`)
135
+ - `storeUrl` (string, optional)
136
+
137
+ ## Iframe behavior
138
+
139
+ - The iframe is always present in the DOM. Set `showIframe` to control visibility.
140
+ - The iframe has `allow="fullscreen *; autoplay *; gamepad *"`.
141
+
142
+ ## Types
143
+
144
+ Named exports:
145
+
146
+ - `PlayruoEmbed`
147
+ - `buildEmbedUrl`
148
+ - `PlayruoEmbedState`
149
+ - `PlayruoEmbedMessage<T>`
package/dist/index.cjs ADDED
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ PlayruoEmbed: () => PlayruoEmbed,
24
+ buildEmbedUrl: () => buildEmbedUrl
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/react/playruo-embed.tsx
29
+ var import_react = require("react");
30
+
31
+ // src/url.ts
32
+ var buildEmbedUrl = ({ baseUrl, operation = "iframe", storeUrl }) => {
33
+ const url = new URL("/embed", baseUrl);
34
+ url.searchParams.set("operation", operation);
35
+ if (storeUrl) url.searchParams.set("storeUrl", storeUrl);
36
+ return url.toString();
37
+ };
38
+
39
+ // src/react/playruo-embed.tsx
40
+ var import_jsx_runtime = require("react/jsx-runtime");
41
+ var PlayruoEmbed = ({
42
+ baseUrl,
43
+ operation = "iframe",
44
+ storeUrl,
45
+ showIframe = false,
46
+ className,
47
+ onReady,
48
+ onStateChange,
49
+ onDemoCompleted,
50
+ onPurchaseRequest,
51
+ onError,
52
+ renderState
53
+ }) => {
54
+ const iframeRef = (0, import_react.useRef)(null);
55
+ const [isReady, setIsReady] = (0, import_react.useState)(false);
56
+ const [state, setState] = (0, import_react.useState)(null);
57
+ const [errorMessage, setErrorMessage] = (0, import_react.useState)(null);
58
+ const src = (0, import_react.useMemo)(() => buildEmbedUrl({ baseUrl, operation, storeUrl }), [baseUrl, operation, storeUrl]);
59
+ (0, import_react.useEffect)(() => {
60
+ const handler = (event) => {
61
+ const msg = event?.data ?? {};
62
+ if (!msg || typeof msg.type !== "string" || !msg.type.startsWith("playruo")) return;
63
+ if (msg.type === "playruo:ready" || msg.type === "playruo_ready") {
64
+ setIsReady(true);
65
+ onReady?.();
66
+ } else if (msg.type === "playruo:state" || msg.type === "playruo_state_update") {
67
+ setState(msg.data);
68
+ onStateChange?.(msg.data);
69
+ } else if (msg.type === "playruo:demoCompleted" || msg.type === "playruo_demo_completed") {
70
+ onDemoCompleted?.(msg.data);
71
+ } else if (msg.type === "playruo:purchaseRequest" || msg.type === "playruo_purchase_request") {
72
+ onPurchaseRequest?.(msg.data);
73
+ } else if (msg.type === "playruo:error" || msg.type === "playruo_error") {
74
+ const message = msg.data?.message ?? "Unknown error";
75
+ setErrorMessage(message);
76
+ onError?.(message);
77
+ }
78
+ };
79
+ window.addEventListener("message", handler);
80
+ return () => window.removeEventListener("message", handler);
81
+ }, [onReady, onStateChange, onDemoCompleted, onPurchaseRequest, onError]);
82
+ const isAvailable = Boolean(isReady && state?.tryNow?.available);
83
+ const renderContent = () => {
84
+ if (!renderState) return null;
85
+ if (errorMessage) return renderState({ status: "error", message: errorMessage });
86
+ if (!isReady) return renderState({ status: "loading" });
87
+ const message = !isAvailable ? state?.tryNow?.status ?? state?.buyNow?.status : void 0;
88
+ return renderState({ status: isAvailable ? "available" : "unavailable", message });
89
+ };
90
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, children: [
91
+ renderContent(),
92
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
93
+ "iframe",
94
+ {
95
+ ref: iframeRef,
96
+ src,
97
+ allow: "fullscreen *; autoplay *; gamepad *",
98
+ className: "w-full h-[70vh] rounded-lg border",
99
+ title: "Playruo",
100
+ style: {
101
+ position: showIframe ? void 0 : "absolute",
102
+ top: showIframe ? void 0 : -9999,
103
+ left: showIframe ? void 0 : -9999,
104
+ zIndex: showIframe ? void 0 : -10,
105
+ opacity: showIframe ? 1 : 0,
106
+ pointerEvents: showIframe ? "auto" : "none"
107
+ }
108
+ }
109
+ )
110
+ ] });
111
+ };
112
+ // Annotate the CommonJS export names for ESM import in node:
113
+ 0 && (module.exports = {
114
+ PlayruoEmbed,
115
+ buildEmbedUrl
116
+ });
@@ -0,0 +1,53 @@
1
+ import { FC, ReactNode } from 'react';
2
+
3
+ interface PlayruoEmbedState {
4
+ hasPlayedOnce: boolean;
5
+ tryNow: {
6
+ available: boolean;
7
+ status?: string;
8
+ };
9
+ buyNow: {
10
+ available: boolean;
11
+ status?: string;
12
+ };
13
+ }
14
+ interface PlayruoEmbedMessage<T = unknown> {
15
+ type: `playruo:${string}`;
16
+ data?: T;
17
+ }
18
+ interface DemoCompletedPayload {
19
+ reason?: string;
20
+ storeUrl?: string;
21
+ }
22
+
23
+ interface PlayruoEmbedProps {
24
+ baseUrl: string;
25
+ operation?: string;
26
+ storeUrl?: string;
27
+ showIframe?: boolean;
28
+ className?: string;
29
+ onReady?: () => void;
30
+ onStateChange?: (state: PlayruoEmbedState) => void;
31
+ onDemoCompleted?: (data: DemoCompletedPayload) => void;
32
+ onPurchaseRequest?: (data: {
33
+ storeUrl: string;
34
+ }) => void;
35
+ onError?: (error: string) => void;
36
+ /**
37
+ * Single render entry point representing the current status.
38
+ */
39
+ renderState?: (render: {
40
+ status: "loading" | "available" | "unavailable" | "error";
41
+ message?: string;
42
+ }) => ReactNode;
43
+ }
44
+ declare const PlayruoEmbed: FC<PlayruoEmbedProps>;
45
+
46
+ interface BuildUrlParams {
47
+ baseUrl: string;
48
+ operation?: string;
49
+ storeUrl?: string;
50
+ }
51
+ declare const buildEmbedUrl: ({ baseUrl, operation, storeUrl }: BuildUrlParams) => string;
52
+
53
+ export { PlayruoEmbed, type PlayruoEmbedMessage, type PlayruoEmbedState, buildEmbedUrl };
@@ -0,0 +1,53 @@
1
+ import { FC, ReactNode } from 'react';
2
+
3
+ interface PlayruoEmbedState {
4
+ hasPlayedOnce: boolean;
5
+ tryNow: {
6
+ available: boolean;
7
+ status?: string;
8
+ };
9
+ buyNow: {
10
+ available: boolean;
11
+ status?: string;
12
+ };
13
+ }
14
+ interface PlayruoEmbedMessage<T = unknown> {
15
+ type: `playruo:${string}`;
16
+ data?: T;
17
+ }
18
+ interface DemoCompletedPayload {
19
+ reason?: string;
20
+ storeUrl?: string;
21
+ }
22
+
23
+ interface PlayruoEmbedProps {
24
+ baseUrl: string;
25
+ operation?: string;
26
+ storeUrl?: string;
27
+ showIframe?: boolean;
28
+ className?: string;
29
+ onReady?: () => void;
30
+ onStateChange?: (state: PlayruoEmbedState) => void;
31
+ onDemoCompleted?: (data: DemoCompletedPayload) => void;
32
+ onPurchaseRequest?: (data: {
33
+ storeUrl: string;
34
+ }) => void;
35
+ onError?: (error: string) => void;
36
+ /**
37
+ * Single render entry point representing the current status.
38
+ */
39
+ renderState?: (render: {
40
+ status: "loading" | "available" | "unavailable" | "error";
41
+ message?: string;
42
+ }) => ReactNode;
43
+ }
44
+ declare const PlayruoEmbed: FC<PlayruoEmbedProps>;
45
+
46
+ interface BuildUrlParams {
47
+ baseUrl: string;
48
+ operation?: string;
49
+ storeUrl?: string;
50
+ }
51
+ declare const buildEmbedUrl: ({ baseUrl, operation, storeUrl }: BuildUrlParams) => string;
52
+
53
+ export { PlayruoEmbed, type PlayruoEmbedMessage, type PlayruoEmbedState, buildEmbedUrl };
package/dist/index.js ADDED
@@ -0,0 +1,88 @@
1
+ // src/react/playruo-embed.tsx
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+
4
+ // src/url.ts
5
+ var buildEmbedUrl = ({ baseUrl, operation = "iframe", storeUrl }) => {
6
+ const url = new URL("/embed", baseUrl);
7
+ url.searchParams.set("operation", operation);
8
+ if (storeUrl) url.searchParams.set("storeUrl", storeUrl);
9
+ return url.toString();
10
+ };
11
+
12
+ // src/react/playruo-embed.tsx
13
+ import { jsx, jsxs } from "react/jsx-runtime";
14
+ var PlayruoEmbed = ({
15
+ baseUrl,
16
+ operation = "iframe",
17
+ storeUrl,
18
+ showIframe = false,
19
+ className,
20
+ onReady,
21
+ onStateChange,
22
+ onDemoCompleted,
23
+ onPurchaseRequest,
24
+ onError,
25
+ renderState
26
+ }) => {
27
+ const iframeRef = useRef(null);
28
+ const [isReady, setIsReady] = useState(false);
29
+ const [state, setState] = useState(null);
30
+ const [errorMessage, setErrorMessage] = useState(null);
31
+ const src = useMemo(() => buildEmbedUrl({ baseUrl, operation, storeUrl }), [baseUrl, operation, storeUrl]);
32
+ useEffect(() => {
33
+ const handler = (event) => {
34
+ const msg = event?.data ?? {};
35
+ if (!msg || typeof msg.type !== "string" || !msg.type.startsWith("playruo")) return;
36
+ if (msg.type === "playruo:ready" || msg.type === "playruo_ready") {
37
+ setIsReady(true);
38
+ onReady?.();
39
+ } else if (msg.type === "playruo:state" || msg.type === "playruo_state_update") {
40
+ setState(msg.data);
41
+ onStateChange?.(msg.data);
42
+ } else if (msg.type === "playruo:demoCompleted" || msg.type === "playruo_demo_completed") {
43
+ onDemoCompleted?.(msg.data);
44
+ } else if (msg.type === "playruo:purchaseRequest" || msg.type === "playruo_purchase_request") {
45
+ onPurchaseRequest?.(msg.data);
46
+ } else if (msg.type === "playruo:error" || msg.type === "playruo_error") {
47
+ const message = msg.data?.message ?? "Unknown error";
48
+ setErrorMessage(message);
49
+ onError?.(message);
50
+ }
51
+ };
52
+ window.addEventListener("message", handler);
53
+ return () => window.removeEventListener("message", handler);
54
+ }, [onReady, onStateChange, onDemoCompleted, onPurchaseRequest, onError]);
55
+ const isAvailable = Boolean(isReady && state?.tryNow?.available);
56
+ const renderContent = () => {
57
+ if (!renderState) return null;
58
+ if (errorMessage) return renderState({ status: "error", message: errorMessage });
59
+ if (!isReady) return renderState({ status: "loading" });
60
+ const message = !isAvailable ? state?.tryNow?.status ?? state?.buyNow?.status : void 0;
61
+ return renderState({ status: isAvailable ? "available" : "unavailable", message });
62
+ };
63
+ return /* @__PURE__ */ jsxs("div", { className, children: [
64
+ renderContent(),
65
+ /* @__PURE__ */ jsx(
66
+ "iframe",
67
+ {
68
+ ref: iframeRef,
69
+ src,
70
+ allow: "fullscreen *; autoplay *; gamepad *",
71
+ className: "w-full h-[70vh] rounded-lg border",
72
+ title: "Playruo",
73
+ style: {
74
+ position: showIframe ? void 0 : "absolute",
75
+ top: showIframe ? void 0 : -9999,
76
+ left: showIframe ? void 0 : -9999,
77
+ zIndex: showIframe ? void 0 : -10,
78
+ opacity: showIframe ? 1 : 0,
79
+ pointerEvents: showIframe ? "auto" : "none"
80
+ }
81
+ }
82
+ )
83
+ ] });
84
+ };
85
+ export {
86
+ PlayruoEmbed,
87
+ buildEmbedUrl
88
+ };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@playruo/embed",
3
+ "version": "1.1.1",
4
+ "description": "React SDK to embed Playruo with postMessage API.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.cjs",
8
+ "module": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": ["dist"],
19
+ "sideEffects": false,
20
+ "peerDependencies": {
21
+ "react": ">=18",
22
+ "react-dom": ">=18"
23
+ },
24
+ "devDependencies": {
25
+ "tsup": "^8.0.0",
26
+ "typescript": "^5.4.0",
27
+ "@types/react": "^19",
28
+ "@types/react-dom": "^19"
29
+ },
30
+ "scripts": {
31
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
32
+ "dev": "tsup src/index.ts --format esm --dts --watch",
33
+ "prepare": "npm run build"
34
+ }
35
+ }