@playruo/embed 1.2.2 → 1.2.5
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 +162 -153
- package/dist/index.d.cts +22 -20
- package/dist/index.d.ts +22 -20
- package/dist/index.js +160 -151
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -33,8 +33,8 @@ __export(index_exports, {
|
|
|
33
33
|
EmbedTester: () => EmbedTester,
|
|
34
34
|
PlayruoEmbed: () => PlayruoEmbed,
|
|
35
35
|
buildEmbedUrl: () => buildEmbedUrl,
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
isPlayruoEmbedMessage: () => isPlayruoEmbedMessage,
|
|
37
|
+
isPlayruoInternalMessage: () => isPlayruoInternalMessage
|
|
38
38
|
});
|
|
39
39
|
module.exports = __toCommonJS(index_exports);
|
|
40
40
|
|
|
@@ -76,9 +76,7 @@ var PlayruoEmbed = ({
|
|
|
76
76
|
const [isMobile, setIsMobile] = (0, import_react.useState)(false);
|
|
77
77
|
const [isValidChromium, setIsValidChromium] = (0, import_react.useState)(true);
|
|
78
78
|
const [errorMessage, setErrorMessage] = (0, import_react.useState)(null);
|
|
79
|
-
const
|
|
80
|
-
const escapeKeyPressed = (0, import_react.useRef)(false);
|
|
81
|
-
const ESCAPE_HOLD_DURATION = 1500;
|
|
79
|
+
const [iframeState, setIframeState] = (0, import_react.useState)(null);
|
|
82
80
|
const src = (0, import_react.useMemo)(() => buildEmbedUrl({ baseUrl, operation, storeUrl }), [baseUrl, operation, storeUrl]);
|
|
83
81
|
(0, import_react.useEffect)(() => {
|
|
84
82
|
if (typeof window === "undefined") return;
|
|
@@ -96,81 +94,34 @@ var PlayruoEmbed = ({
|
|
|
96
94
|
}, []);
|
|
97
95
|
const isCompatible = !isMobile && isValidChromium;
|
|
98
96
|
const isReady = isCompatible ? isIframeLoaded : true;
|
|
99
|
-
const currentState = (0, import_react.useMemo)(() =>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
available: isCompatible,
|
|
103
|
-
status: !isCompatible ? "Browser not compatible" : hasPlayedOnce ? "Click to replay" : "Ready"
|
|
104
|
-
},
|
|
105
|
-
buyNow: {
|
|
106
|
-
available: true,
|
|
107
|
-
status: "Ready"
|
|
108
|
-
}
|
|
109
|
-
}), [hasPlayedOnce, isCompatible]);
|
|
110
|
-
const checkKeyboardLockPermission = (0, import_react.useCallback)(async () => {
|
|
111
|
-
if (!navigator.permissions) {
|
|
112
|
-
console.log("[PlayruoEmbed] Permissions API not supported");
|
|
113
|
-
return false;
|
|
97
|
+
const currentState = (0, import_react.useMemo)(() => {
|
|
98
|
+
if (iframeState) {
|
|
99
|
+
return iframeState;
|
|
114
100
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
101
|
+
return {
|
|
102
|
+
hasPlayedOnce,
|
|
103
|
+
tryNow: {
|
|
104
|
+
available: isCompatible,
|
|
105
|
+
status: !isCompatible ? "Browser not compatible" : hasPlayedOnce ? "Click to resume" : "Ready"
|
|
106
|
+
},
|
|
107
|
+
buyNow: {
|
|
108
|
+
available: true,
|
|
109
|
+
status: "Ready"
|
|
122
110
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
console.log("[PlayruoEmbed] Fullscreen not supported on this browser");
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
try {
|
|
137
|
-
const fullscreenPromise = iframe.requestFullscreen();
|
|
138
|
-
if (navigator.keyboard && window.self === window.top) {
|
|
139
|
-
try {
|
|
140
|
-
const permissionPromise = checkKeyboardLockPermission();
|
|
141
|
-
const keyboardLockPromise = navigator.keyboard.lock(["Escape"]);
|
|
142
|
-
void Promise.all([permissionPromise, keyboardLockPromise]).then(([hasPermission]) => {
|
|
143
|
-
if (hasPermission) {
|
|
144
|
-
console.log("[PlayruoEmbed] Keyboard locked successfully");
|
|
145
|
-
}
|
|
146
|
-
}).catch((err) => {
|
|
147
|
-
const isTopLevelContextError = err?.name === "InvalidStateError" && err?.message?.includes("primary top-level browsing context");
|
|
148
|
-
if (!isTopLevelContextError) {
|
|
149
|
-
console.warn("[PlayruoEmbed] Keyboard lock failed:", err);
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
} catch (err) {
|
|
153
|
-
const isTopLevelContextError = err?.name === "InvalidStateError" && err?.message?.includes("primary top-level browsing context");
|
|
154
|
-
if (!isTopLevelContextError) {
|
|
155
|
-
console.warn("[PlayruoEmbed] Keyboard lock error:", err);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
} else if (!navigator.keyboard) {
|
|
159
|
-
console.log("[PlayruoEmbed] Keyboard API not supported");
|
|
160
|
-
} else {
|
|
161
|
-
console.log("[PlayruoEmbed] Keyboard lock not available (not in top-level context)");
|
|
111
|
+
};
|
|
112
|
+
}, [hasPlayedOnce, isCompatible, iframeState]);
|
|
113
|
+
const hasIframeConfirmed = Boolean(iframeState?.tryNow?.available === true);
|
|
114
|
+
const exposedState = (0, import_react.useMemo)(
|
|
115
|
+
() => ({
|
|
116
|
+
...currentState,
|
|
117
|
+
tryNow: {
|
|
118
|
+
...currentState.tryNow,
|
|
119
|
+
available: hasIframeConfirmed,
|
|
120
|
+
status: currentState.tryNow.status ?? (hasIframeConfirmed ? "Ready" : isCompatible ? "Checking availability\u2026" : "Browser not compatible")
|
|
162
121
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
console.warn("[PlayruoEmbed] Fullscreen failed:", err);
|
|
167
|
-
});
|
|
168
|
-
return true;
|
|
169
|
-
} catch (error) {
|
|
170
|
-
console.error("[PlayruoEmbed] Error requesting fullscreen:", error);
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
173
|
-
}, [iframeRef, checkKeyboardLockPermission]);
|
|
122
|
+
}),
|
|
123
|
+
[currentState, hasIframeConfirmed, isCompatible]
|
|
124
|
+
);
|
|
174
125
|
const handleTryNow = (0, import_react.useCallback)(() => {
|
|
175
126
|
if (!isCompatible) {
|
|
176
127
|
console.warn("[PlayruoEmbed] Browser not compatible");
|
|
@@ -190,12 +141,10 @@ var PlayruoEmbed = ({
|
|
|
190
141
|
} catch {
|
|
191
142
|
}
|
|
192
143
|
}
|
|
193
|
-
requestFullscreenWithKeyboardLock();
|
|
194
144
|
iframe.contentWindow?.postMessage({
|
|
195
145
|
type: "playruo",
|
|
196
146
|
action: "tryNowClick",
|
|
197
147
|
fromEmbed: true,
|
|
198
|
-
isFullscreen: true,
|
|
199
148
|
isReplay: hasPlayedOnce
|
|
200
149
|
}, "*");
|
|
201
150
|
console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
|
|
@@ -209,7 +158,7 @@ var PlayruoEmbed = ({
|
|
|
209
158
|
setErrorMessage("Failed to start game");
|
|
210
159
|
onError?.("Failed to start game");
|
|
211
160
|
}
|
|
212
|
-
}, [isCompatible, hasPlayedOnce, onError, iframeRef
|
|
161
|
+
}, [isCompatible, hasPlayedOnce, onError, iframeRef]);
|
|
213
162
|
const handleBuyNow = (0, import_react.useCallback)(() => {
|
|
214
163
|
if (!storeUrl) {
|
|
215
164
|
console.warn("[PlayruoEmbed] No store URL provided");
|
|
@@ -219,9 +168,9 @@ var PlayruoEmbed = ({
|
|
|
219
168
|
}, [storeUrl, onPurchaseRequest]);
|
|
220
169
|
(0, import_react.useEffect)(() => {
|
|
221
170
|
if (isReady) {
|
|
222
|
-
onStateChange?.(
|
|
171
|
+
onStateChange?.(exposedState);
|
|
223
172
|
}
|
|
224
|
-
}, [isReady,
|
|
173
|
+
}, [isReady, exposedState, onStateChange]);
|
|
225
174
|
(0, import_react.useEffect)(() => {
|
|
226
175
|
if (isReady) {
|
|
227
176
|
onReady?.();
|
|
@@ -229,18 +178,112 @@ var PlayruoEmbed = ({
|
|
|
229
178
|
}, [isReady, onReady]);
|
|
230
179
|
(0, import_react.useEffect)(() => {
|
|
231
180
|
const handleGameMessage = (event) => {
|
|
232
|
-
const { type } = event.data || {};
|
|
181
|
+
const { type, data } = event.data || {};
|
|
182
|
+
const iframe = iframeRef.current;
|
|
183
|
+
if (!iframe || event.source !== iframe.contentWindow) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
console.log("\u{1F537} [Embed] Message received from iframe:", { type, data });
|
|
187
|
+
if (type === "playruo_ready") {
|
|
188
|
+
console.log("\u{1F537} [Embed] playruo_ready received");
|
|
189
|
+
onReady?.();
|
|
190
|
+
} else if (type === "playruo_state_update" && data) {
|
|
191
|
+
console.log("\u{1F537} [Embed] playruo_state_update received:", data);
|
|
192
|
+
setIframeState(data);
|
|
193
|
+
if (data.hasPlayedOnce !== void 0) {
|
|
194
|
+
setHasPlayedOnce(data.hasPlayedOnce);
|
|
195
|
+
}
|
|
196
|
+
onStateChange?.(data);
|
|
197
|
+
} else if (type === "playruo_purchase_request" && data?.storeUrl) {
|
|
198
|
+
console.log("\u{1F537} [Embed] playruo_purchase_request received:", data);
|
|
199
|
+
onPurchaseRequest?.(data);
|
|
200
|
+
} else if (type === "playruo_demo_completed" && data) {
|
|
201
|
+
console.log("\u{1F537} [Embed] playruo_demo_completed received:", data);
|
|
202
|
+
setHasPlayedOnce(true);
|
|
203
|
+
onDemoCompleted?.(data);
|
|
204
|
+
} else if (type === "playruo_error") {
|
|
205
|
+
const errorMessage2 = typeof data === "string" ? data : data?.message || "Unknown error";
|
|
206
|
+
console.log("\u{1F537} [Embed] playruo_error received:", errorMessage2);
|
|
207
|
+
setErrorMessage(errorMessage2);
|
|
208
|
+
const errorState = {
|
|
209
|
+
hasPlayedOnce,
|
|
210
|
+
tryNow: {
|
|
211
|
+
available: false,
|
|
212
|
+
status: `Error: ${errorMessage2}`
|
|
213
|
+
},
|
|
214
|
+
buyNow: {
|
|
215
|
+
available: true,
|
|
216
|
+
status: "Ready"
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
setIframeState(errorState);
|
|
220
|
+
onStateChange?.(errorState);
|
|
221
|
+
onError?.(errorMessage2);
|
|
222
|
+
} else if (type === "playruo_session_over" && data) {
|
|
223
|
+
console.log("\u{1F537} [Embed] playruo_session_over received:", data);
|
|
224
|
+
setHasPlayedOnce(true);
|
|
225
|
+
setIframeState(data);
|
|
226
|
+
onStateChange?.(data);
|
|
227
|
+
} else if (type === "playruo_stream_start") {
|
|
228
|
+
console.log("\u{1F537} [Embed] playruo_stream_start received");
|
|
229
|
+
}
|
|
233
230
|
if (type === "error" || type === "closeForBuy" || type === "goToStore") {
|
|
231
|
+
console.log("\u{1F537} [Embed] Legacy message received:", type);
|
|
234
232
|
setHasPlayedOnce(true);
|
|
235
|
-
|
|
233
|
+
const payload = {
|
|
236
234
|
reason: "Demo session ended",
|
|
237
235
|
storeUrl
|
|
238
|
-
}
|
|
236
|
+
};
|
|
237
|
+
onDemoCompleted?.(payload);
|
|
239
238
|
}
|
|
240
239
|
};
|
|
241
240
|
window.addEventListener("message", handleGameMessage);
|
|
242
241
|
return () => window.removeEventListener("message", handleGameMessage);
|
|
243
|
-
}, [storeUrl, onDemoCompleted]);
|
|
242
|
+
}, [storeUrl, onDemoCompleted, onReady, onStateChange, onPurchaseRequest, onError, iframeRef]);
|
|
243
|
+
const generateUUID = () => {
|
|
244
|
+
if (self.crypto.randomUUID) return self.crypto.randomUUID();
|
|
245
|
+
const bytes = new Uint8Array(16);
|
|
246
|
+
self.crypto.getRandomValues(bytes);
|
|
247
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
248
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
249
|
+
return [...bytes].map(
|
|
250
|
+
(b, i) => ([4, 6, 8, 10].includes(i) ? "-" : "") + b.toString(16).padStart(2, "0")
|
|
251
|
+
).join("");
|
|
252
|
+
};
|
|
253
|
+
(0, import_react.useEffect)(() => {
|
|
254
|
+
const handleUUIDMessage = (event) => {
|
|
255
|
+
const iframe = iframeRef.current;
|
|
256
|
+
if (!iframe || event.source !== iframe.contentWindow) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const { type, requestId } = event.data || {};
|
|
260
|
+
if (type === "playruo_uuid_request") {
|
|
261
|
+
try {
|
|
262
|
+
let storedUUID = localStorage.getItem("playruo_uuid");
|
|
263
|
+
if (!storedUUID) {
|
|
264
|
+
storedUUID = generateUUID();
|
|
265
|
+
localStorage.setItem("playruo_uuid", storedUUID);
|
|
266
|
+
}
|
|
267
|
+
iframe.contentWindow?.postMessage(
|
|
268
|
+
{
|
|
269
|
+
type: "playruo_uuid_response",
|
|
270
|
+
requestId,
|
|
271
|
+
uuid: storedUUID
|
|
272
|
+
},
|
|
273
|
+
"*"
|
|
274
|
+
);
|
|
275
|
+
} catch (e) {
|
|
276
|
+
}
|
|
277
|
+
} else if (type === "playruo_uuid_remove") {
|
|
278
|
+
try {
|
|
279
|
+
localStorage.removeItem("playruo_uuid");
|
|
280
|
+
} catch (e) {
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
window.addEventListener("message", handleUUIDMessage);
|
|
285
|
+
return () => window.removeEventListener("message", handleUUIDMessage);
|
|
286
|
+
}, [iframeRef]);
|
|
244
287
|
(0, import_react.useEffect)(() => {
|
|
245
288
|
const handleFullscreenChange = () => {
|
|
246
289
|
const doc = document;
|
|
@@ -254,10 +297,17 @@ var PlayruoEmbed = ({
|
|
|
254
297
|
}, 200);
|
|
255
298
|
}
|
|
256
299
|
} else {
|
|
257
|
-
|
|
258
|
-
if (
|
|
259
|
-
|
|
260
|
-
|
|
300
|
+
const iframe = iframeRef.current;
|
|
301
|
+
if (iframe) {
|
|
302
|
+
try {
|
|
303
|
+
iframe.contentWindow?.postMessage({
|
|
304
|
+
type: "playruo",
|
|
305
|
+
action: "exitFullscreen",
|
|
306
|
+
fromEmbed: true
|
|
307
|
+
}, "*");
|
|
308
|
+
} catch {
|
|
309
|
+
console.error("[PlayruoEmbed] Failed to exit fullscreen");
|
|
310
|
+
}
|
|
261
311
|
}
|
|
262
312
|
setShowGameIframe(false);
|
|
263
313
|
setHasPlayedOnce(true);
|
|
@@ -278,56 +328,6 @@ var PlayruoEmbed = ({
|
|
|
278
328
|
document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
|
|
279
329
|
};
|
|
280
330
|
}, [storeUrl, onDemoCompleted, iframeRef]);
|
|
281
|
-
(0, import_react.useEffect)(() => {
|
|
282
|
-
const handleKeyDown = (event) => {
|
|
283
|
-
if (event.key === "Escape" && !escapeKeyPressed.current) {
|
|
284
|
-
escapeKeyPressed.current = true;
|
|
285
|
-
event.preventDefault();
|
|
286
|
-
escapeKeyTimer.current = setTimeout(() => {
|
|
287
|
-
if (document.fullscreenElement) {
|
|
288
|
-
document.exitFullscreen();
|
|
289
|
-
}
|
|
290
|
-
}, ESCAPE_HOLD_DURATION);
|
|
291
|
-
}
|
|
292
|
-
if (event.ctrlKey && event.shiftKey && event.key === "R") {
|
|
293
|
-
event.preventDefault();
|
|
294
|
-
if (document.fullscreenElement) {
|
|
295
|
-
document.exitFullscreen().catch(console.warn);
|
|
296
|
-
}
|
|
297
|
-
setTimeout(() => {
|
|
298
|
-
window.location.reload();
|
|
299
|
-
}, 500);
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
const handleKeyUp = (event) => {
|
|
303
|
-
if (event.key === "Escape" && escapeKeyPressed.current) {
|
|
304
|
-
escapeKeyPressed.current = false;
|
|
305
|
-
if (escapeKeyTimer.current) {
|
|
306
|
-
clearTimeout(escapeKeyTimer.current);
|
|
307
|
-
escapeKeyTimer.current = null;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
};
|
|
311
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
312
|
-
document.addEventListener("keyup", handleKeyUp);
|
|
313
|
-
return () => {
|
|
314
|
-
document.removeEventListener("keydown", handleKeyDown);
|
|
315
|
-
document.removeEventListener("keyup", handleKeyUp);
|
|
316
|
-
};
|
|
317
|
-
}, []);
|
|
318
|
-
(0, import_react.useEffect)(() => {
|
|
319
|
-
const isVisible = showGameIframe || showIframe;
|
|
320
|
-
const iframe = iframeRef.current;
|
|
321
|
-
if (!iframe || !isIframeLoaded) return;
|
|
322
|
-
try {
|
|
323
|
-
iframe.contentWindow?.postMessage({
|
|
324
|
-
type: "playruo",
|
|
325
|
-
action: isVisible ? "unmute" : "mute",
|
|
326
|
-
fromEmbed: true
|
|
327
|
-
}, "*");
|
|
328
|
-
} catch {
|
|
329
|
-
}
|
|
330
|
-
}, [showGameIframe, showIframe, isIframeLoaded, iframeRef]);
|
|
331
331
|
(0, import_react.useEffect)(() => {
|
|
332
332
|
const logCrossOriginStatus = () => {
|
|
333
333
|
const isIsolated = self.crossOriginIsolated;
|
|
@@ -406,22 +406,28 @@ var PlayruoEmbed = ({
|
|
|
406
406
|
logCrossOriginStatus();
|
|
407
407
|
}
|
|
408
408
|
}, [isReady]);
|
|
409
|
-
const isAvailable = Boolean(isReady && isCompatible);
|
|
409
|
+
const isAvailable = Boolean(isReady && isCompatible && hasIframeConfirmed);
|
|
410
410
|
const renderContent = () => {
|
|
411
411
|
if (!renderState) return null;
|
|
412
|
+
const canTryNow = Boolean(
|
|
413
|
+
!errorMessage && isReady && isCompatible && hasIframeConfirmed
|
|
414
|
+
);
|
|
412
415
|
const actions = {
|
|
413
416
|
tryNow: handleTryNow,
|
|
414
417
|
buyNow: handleBuyNow,
|
|
415
|
-
canTryNow
|
|
418
|
+
canTryNow,
|
|
416
419
|
canBuyNow: Boolean(isReady)
|
|
417
420
|
};
|
|
418
421
|
if (errorMessage) {
|
|
419
422
|
return renderState({ status: "error", message: errorMessage, actions });
|
|
420
423
|
}
|
|
421
424
|
if (!isReady) {
|
|
422
|
-
return renderState({ status: "loading", actions });
|
|
425
|
+
return renderState({ status: "loading", message: "Loading\u2026", actions });
|
|
426
|
+
}
|
|
427
|
+
if (isCompatible && !iframeState) {
|
|
428
|
+
return renderState({ status: "loading", message: "Checking availability\u2026", actions });
|
|
423
429
|
}
|
|
424
|
-
const message = !isAvailable ? currentState.tryNow.status : void 0;
|
|
430
|
+
const message = !isAvailable ? currentState.tryNow.status ?? exposedState.tryNow.status : void 0;
|
|
425
431
|
return renderState({
|
|
426
432
|
status: isAvailable ? "available" : "unavailable",
|
|
427
433
|
message,
|
|
@@ -436,7 +442,6 @@ var PlayruoEmbed = ({
|
|
|
436
442
|
ref: iframeRef,
|
|
437
443
|
src,
|
|
438
444
|
allow: "cross-origin-isolated; fullscreen *; autoplay *; gamepad *; clipboard-write *; web-share *; encrypted-media *; accelerometer *; gyroscope *",
|
|
439
|
-
allowFullScreen: true,
|
|
440
445
|
className: "w-full h-[70vh] rounded-lg border",
|
|
441
446
|
...{ credentialless: "true" },
|
|
442
447
|
sandbox: "allow-same-origin allow-scripts allow-popups allow-forms allow-presentation allow-top-navigation allow-modals allow-pointer-lock",
|
|
@@ -655,6 +660,10 @@ var EmbedTester = (props = {}) => {
|
|
|
655
660
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: embedState.hasPlayedOnce ? successStatus : errorStatus, children: embedState.hasPlayedOnce ? "\u2705 Played" : "\u274C Not Played" }),
|
|
656
661
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: embedState.tryNow?.available ? successStatus : errorStatus, children: embedState.tryNow?.available ? "\u2705 Try Now" : "\u274C Try Now" }),
|
|
657
662
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: embedState.buyNow?.available ? successStatus : errorStatus, children: embedState.buyNow?.available ? "\u2705 Buy Now" : "\u274C Buy Now" })
|
|
663
|
+
] }),
|
|
664
|
+
!embedState.tryNow?.available && embedState.tryNow?.status && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { style: { margin: "8px 0 0 0", fontSize: "12px", color: "#a3a3a3" }, children: [
|
|
665
|
+
"Try Now reason: ",
|
|
666
|
+
embedState.tryNow.status
|
|
658
667
|
] })
|
|
659
668
|
] }),
|
|
660
669
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: cardStyle, children: [
|
|
@@ -684,7 +693,8 @@ var EmbedTester = (props = {}) => {
|
|
|
684
693
|
backgroundColor: "#0a0a0a",
|
|
685
694
|
padding: "12px",
|
|
686
695
|
borderRadius: "4px",
|
|
687
|
-
|
|
696
|
+
minHeight: "200px",
|
|
697
|
+
height: "min(50vh, 480px)",
|
|
688
698
|
overflowY: "auto",
|
|
689
699
|
fontFamily: "monospace",
|
|
690
700
|
fontSize: "12px",
|
|
@@ -829,19 +839,18 @@ var isPlayruoEmbedMessage = (data) => {
|
|
|
829
839
|
if (!data || typeof data !== "object") return false;
|
|
830
840
|
const msg = data;
|
|
831
841
|
if (typeof msg.type !== "string") return false;
|
|
832
|
-
return msg.type === "playruo_ready" || msg.type === "playruo_state_update" || msg.type === "playruo_purchase_request" || msg.type === "playruo_demo_completed" || msg.type === "playruo_error";
|
|
842
|
+
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";
|
|
833
843
|
};
|
|
834
|
-
var
|
|
844
|
+
var isPlayruoInternalMessage = (data) => {
|
|
835
845
|
if (!data || typeof data !== "object") return false;
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
return cmd.type === "try_now" || cmd.type === "buy_now" || cmd.type === "get_state";
|
|
846
|
+
const msg = data;
|
|
847
|
+
return msg.type === "playruo" && typeof msg.action === "string" && (msg.action === "tryNowClick" || msg.action === "exitFullscreen" || msg.action === "mute" || msg.action === "unmute") && msg.fromEmbed === true;
|
|
839
848
|
};
|
|
840
849
|
// Annotate the CommonJS export names for ESM import in node:
|
|
841
850
|
0 && (module.exports = {
|
|
842
851
|
EmbedTester,
|
|
843
852
|
PlayruoEmbed,
|
|
844
853
|
buildEmbedUrl,
|
|
845
|
-
|
|
846
|
-
|
|
854
|
+
isPlayruoEmbedMessage,
|
|
855
|
+
isPlayruoInternalMessage
|
|
847
856
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -10,6 +10,7 @@ interface PlayruoEmbedState {
|
|
|
10
10
|
buyNow: {
|
|
11
11
|
available: boolean;
|
|
12
12
|
status?: string;
|
|
13
|
+
storeUrl?: string;
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
interface DemoCompletedPayload {
|
|
@@ -17,15 +18,6 @@ interface DemoCompletedPayload {
|
|
|
17
18
|
storeUrl?: string;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
interface Keyboard {
|
|
21
|
-
lock: (keys: string[]) => Promise<void>;
|
|
22
|
-
unlock: () => void;
|
|
23
|
-
}
|
|
24
|
-
declare global {
|
|
25
|
-
interface Navigator {
|
|
26
|
-
keyboard?: Keyboard;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
21
|
interface PlayruoEmbedActions {
|
|
30
22
|
/** Start the demo session */
|
|
31
23
|
tryNow: () => void;
|
|
@@ -98,25 +90,35 @@ interface PlayruoErrorMessage {
|
|
|
98
90
|
message: string;
|
|
99
91
|
} | string;
|
|
100
92
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
93
|
+
interface PlayruoSessionOverMessage {
|
|
94
|
+
type: "playruo_session_over";
|
|
95
|
+
data: PlayruoEmbedState;
|
|
96
|
+
}
|
|
97
|
+
interface PlayruoStreamStartMessage {
|
|
98
|
+
type: "playruo_stream_start";
|
|
104
99
|
}
|
|
105
|
-
interface
|
|
106
|
-
type: "
|
|
100
|
+
interface PlayruoStreamStopMessage {
|
|
101
|
+
type: "playruo_stream_stop";
|
|
107
102
|
}
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
type PlayruoEmbedMessage = PlayruoReadyMessage | PlayruoStateUpdateMessage | PlayruoPurchaseRequestMessage | PlayruoDemoCompletedMessage | PlayruoErrorMessage | PlayruoSessionOverMessage | PlayruoStreamStartMessage | PlayruoStreamStopMessage;
|
|
104
|
+
interface PlayruoInternalMessage {
|
|
105
|
+
type: "playruo";
|
|
106
|
+
action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
|
|
107
|
+
fromEmbed: true;
|
|
108
|
+
isReplay?: boolean;
|
|
109
|
+
parentWillHandleFullscreen?: boolean;
|
|
110
110
|
}
|
|
111
|
-
type PlayruoEmbedCommand = TryNowCommand | BuyNowCommand | GetStateCommand;
|
|
112
111
|
/**
|
|
113
112
|
* Type guard to check if a message event is a Playruo embed message
|
|
113
|
+
* Messages FROM Embed → Host via postMessage
|
|
114
114
|
*/
|
|
115
115
|
declare const isPlayruoEmbedMessage: (data: unknown) => data is PlayruoEmbedMessage;
|
|
116
116
|
/**
|
|
117
|
-
* Type guard to check if a
|
|
117
|
+
* Type guard to check if a message is a Playruo internal message
|
|
118
|
+
* Format: { type: 'playruo', action: '...', fromEmbed: true }
|
|
119
|
+
* These are messages sent TO the iframe (game), not to the parent
|
|
118
120
|
*/
|
|
119
|
-
declare const
|
|
121
|
+
declare const isPlayruoInternalMessage: (data: unknown) => data is PlayruoInternalMessage;
|
|
120
122
|
|
|
121
123
|
interface BuildUrlParams {
|
|
122
124
|
baseUrl: string;
|
|
@@ -131,4 +133,4 @@ interface BuildUrlParams {
|
|
|
131
133
|
*/
|
|
132
134
|
declare const buildEmbedUrl: ({ baseUrl, operation, storeUrl }: BuildUrlParams) => string;
|
|
133
135
|
|
|
134
|
-
export { type
|
|
136
|
+
export { type DemoCompletedPayload, EmbedTester, type EmbedTesterProps, type PlayruoDemoCompletedMessage, PlayruoEmbed, type PlayruoEmbedActions, type PlayruoEmbedMessage, type PlayruoEmbedProps, type PlayruoEmbedRenderState, type PlayruoEmbedState, type PlayruoErrorMessage, type PlayruoInternalMessage, type PlayruoPurchaseRequestMessage, type PlayruoReadyMessage, type PlayruoSessionOverMessage, type PlayruoStateUpdateMessage, type PlayruoStreamStartMessage, buildEmbedUrl, isPlayruoEmbedMessage, isPlayruoInternalMessage };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ interface PlayruoEmbedState {
|
|
|
10
10
|
buyNow: {
|
|
11
11
|
available: boolean;
|
|
12
12
|
status?: string;
|
|
13
|
+
storeUrl?: string;
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
interface DemoCompletedPayload {
|
|
@@ -17,15 +18,6 @@ interface DemoCompletedPayload {
|
|
|
17
18
|
storeUrl?: string;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
interface Keyboard {
|
|
21
|
-
lock: (keys: string[]) => Promise<void>;
|
|
22
|
-
unlock: () => void;
|
|
23
|
-
}
|
|
24
|
-
declare global {
|
|
25
|
-
interface Navigator {
|
|
26
|
-
keyboard?: Keyboard;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
21
|
interface PlayruoEmbedActions {
|
|
30
22
|
/** Start the demo session */
|
|
31
23
|
tryNow: () => void;
|
|
@@ -98,25 +90,35 @@ interface PlayruoErrorMessage {
|
|
|
98
90
|
message: string;
|
|
99
91
|
} | string;
|
|
100
92
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
93
|
+
interface PlayruoSessionOverMessage {
|
|
94
|
+
type: "playruo_session_over";
|
|
95
|
+
data: PlayruoEmbedState;
|
|
96
|
+
}
|
|
97
|
+
interface PlayruoStreamStartMessage {
|
|
98
|
+
type: "playruo_stream_start";
|
|
104
99
|
}
|
|
105
|
-
interface
|
|
106
|
-
type: "
|
|
100
|
+
interface PlayruoStreamStopMessage {
|
|
101
|
+
type: "playruo_stream_stop";
|
|
107
102
|
}
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
type PlayruoEmbedMessage = PlayruoReadyMessage | PlayruoStateUpdateMessage | PlayruoPurchaseRequestMessage | PlayruoDemoCompletedMessage | PlayruoErrorMessage | PlayruoSessionOverMessage | PlayruoStreamStartMessage | PlayruoStreamStopMessage;
|
|
104
|
+
interface PlayruoInternalMessage {
|
|
105
|
+
type: "playruo";
|
|
106
|
+
action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
|
|
107
|
+
fromEmbed: true;
|
|
108
|
+
isReplay?: boolean;
|
|
109
|
+
parentWillHandleFullscreen?: boolean;
|
|
110
110
|
}
|
|
111
|
-
type PlayruoEmbedCommand = TryNowCommand | BuyNowCommand | GetStateCommand;
|
|
112
111
|
/**
|
|
113
112
|
* Type guard to check if a message event is a Playruo embed message
|
|
113
|
+
* Messages FROM Embed → Host via postMessage
|
|
114
114
|
*/
|
|
115
115
|
declare const isPlayruoEmbedMessage: (data: unknown) => data is PlayruoEmbedMessage;
|
|
116
116
|
/**
|
|
117
|
-
* Type guard to check if a
|
|
117
|
+
* Type guard to check if a message is a Playruo internal message
|
|
118
|
+
* Format: { type: 'playruo', action: '...', fromEmbed: true }
|
|
119
|
+
* These are messages sent TO the iframe (game), not to the parent
|
|
118
120
|
*/
|
|
119
|
-
declare const
|
|
121
|
+
declare const isPlayruoInternalMessage: (data: unknown) => data is PlayruoInternalMessage;
|
|
120
122
|
|
|
121
123
|
interface BuildUrlParams {
|
|
122
124
|
baseUrl: string;
|
|
@@ -131,4 +133,4 @@ interface BuildUrlParams {
|
|
|
131
133
|
*/
|
|
132
134
|
declare const buildEmbedUrl: ({ baseUrl, operation, storeUrl }: BuildUrlParams) => string;
|
|
133
135
|
|
|
134
|
-
export { type
|
|
136
|
+
export { type DemoCompletedPayload, EmbedTester, type EmbedTesterProps, type PlayruoDemoCompletedMessage, PlayruoEmbed, type PlayruoEmbedActions, type PlayruoEmbedMessage, type PlayruoEmbedProps, type PlayruoEmbedRenderState, type PlayruoEmbedState, type PlayruoErrorMessage, type PlayruoInternalMessage, type PlayruoPurchaseRequestMessage, type PlayruoReadyMessage, type PlayruoSessionOverMessage, type PlayruoStateUpdateMessage, type PlayruoStreamStartMessage, buildEmbedUrl, isPlayruoEmbedMessage, isPlayruoInternalMessage };
|
package/dist/index.js
CHANGED
|
@@ -36,9 +36,7 @@ var PlayruoEmbed = ({
|
|
|
36
36
|
const [isMobile, setIsMobile] = useState(false);
|
|
37
37
|
const [isValidChromium, setIsValidChromium] = useState(true);
|
|
38
38
|
const [errorMessage, setErrorMessage] = useState(null);
|
|
39
|
-
const
|
|
40
|
-
const escapeKeyPressed = useRef(false);
|
|
41
|
-
const ESCAPE_HOLD_DURATION = 1500;
|
|
39
|
+
const [iframeState, setIframeState] = useState(null);
|
|
42
40
|
const src = useMemo(() => buildEmbedUrl({ baseUrl, operation, storeUrl }), [baseUrl, operation, storeUrl]);
|
|
43
41
|
useEffect(() => {
|
|
44
42
|
if (typeof window === "undefined") return;
|
|
@@ -56,81 +54,34 @@ var PlayruoEmbed = ({
|
|
|
56
54
|
}, []);
|
|
57
55
|
const isCompatible = !isMobile && isValidChromium;
|
|
58
56
|
const isReady = isCompatible ? isIframeLoaded : true;
|
|
59
|
-
const currentState = useMemo(() =>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
available: isCompatible,
|
|
63
|
-
status: !isCompatible ? "Browser not compatible" : hasPlayedOnce ? "Click to replay" : "Ready"
|
|
64
|
-
},
|
|
65
|
-
buyNow: {
|
|
66
|
-
available: true,
|
|
67
|
-
status: "Ready"
|
|
68
|
-
}
|
|
69
|
-
}), [hasPlayedOnce, isCompatible]);
|
|
70
|
-
const checkKeyboardLockPermission = useCallback(async () => {
|
|
71
|
-
if (!navigator.permissions) {
|
|
72
|
-
console.log("[PlayruoEmbed] Permissions API not supported");
|
|
73
|
-
return false;
|
|
57
|
+
const currentState = useMemo(() => {
|
|
58
|
+
if (iframeState) {
|
|
59
|
+
return iframeState;
|
|
74
60
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
61
|
+
return {
|
|
62
|
+
hasPlayedOnce,
|
|
63
|
+
tryNow: {
|
|
64
|
+
available: isCompatible,
|
|
65
|
+
status: !isCompatible ? "Browser not compatible" : hasPlayedOnce ? "Click to resume" : "Ready"
|
|
66
|
+
},
|
|
67
|
+
buyNow: {
|
|
68
|
+
available: true,
|
|
69
|
+
status: "Ready"
|
|
82
70
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
console.log("[PlayruoEmbed] Fullscreen not supported on this browser");
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
const fullscreenPromise = iframe.requestFullscreen();
|
|
98
|
-
if (navigator.keyboard && window.self === window.top) {
|
|
99
|
-
try {
|
|
100
|
-
const permissionPromise = checkKeyboardLockPermission();
|
|
101
|
-
const keyboardLockPromise = navigator.keyboard.lock(["Escape"]);
|
|
102
|
-
void Promise.all([permissionPromise, keyboardLockPromise]).then(([hasPermission]) => {
|
|
103
|
-
if (hasPermission) {
|
|
104
|
-
console.log("[PlayruoEmbed] Keyboard locked successfully");
|
|
105
|
-
}
|
|
106
|
-
}).catch((err) => {
|
|
107
|
-
const isTopLevelContextError = err?.name === "InvalidStateError" && err?.message?.includes("primary top-level browsing context");
|
|
108
|
-
if (!isTopLevelContextError) {
|
|
109
|
-
console.warn("[PlayruoEmbed] Keyboard lock failed:", err);
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
} catch (err) {
|
|
113
|
-
const isTopLevelContextError = err?.name === "InvalidStateError" && err?.message?.includes("primary top-level browsing context");
|
|
114
|
-
if (!isTopLevelContextError) {
|
|
115
|
-
console.warn("[PlayruoEmbed] Keyboard lock error:", err);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
} else if (!navigator.keyboard) {
|
|
119
|
-
console.log("[PlayruoEmbed] Keyboard API not supported");
|
|
120
|
-
} else {
|
|
121
|
-
console.log("[PlayruoEmbed] Keyboard lock not available (not in top-level context)");
|
|
71
|
+
};
|
|
72
|
+
}, [hasPlayedOnce, isCompatible, iframeState]);
|
|
73
|
+
const hasIframeConfirmed = Boolean(iframeState?.tryNow?.available === true);
|
|
74
|
+
const exposedState = useMemo(
|
|
75
|
+
() => ({
|
|
76
|
+
...currentState,
|
|
77
|
+
tryNow: {
|
|
78
|
+
...currentState.tryNow,
|
|
79
|
+
available: hasIframeConfirmed,
|
|
80
|
+
status: currentState.tryNow.status ?? (hasIframeConfirmed ? "Ready" : isCompatible ? "Checking availability\u2026" : "Browser not compatible")
|
|
122
81
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
console.warn("[PlayruoEmbed] Fullscreen failed:", err);
|
|
127
|
-
});
|
|
128
|
-
return true;
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error("[PlayruoEmbed] Error requesting fullscreen:", error);
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
|
-
}, [iframeRef, checkKeyboardLockPermission]);
|
|
82
|
+
}),
|
|
83
|
+
[currentState, hasIframeConfirmed, isCompatible]
|
|
84
|
+
);
|
|
134
85
|
const handleTryNow = useCallback(() => {
|
|
135
86
|
if (!isCompatible) {
|
|
136
87
|
console.warn("[PlayruoEmbed] Browser not compatible");
|
|
@@ -150,12 +101,10 @@ var PlayruoEmbed = ({
|
|
|
150
101
|
} catch {
|
|
151
102
|
}
|
|
152
103
|
}
|
|
153
|
-
requestFullscreenWithKeyboardLock();
|
|
154
104
|
iframe.contentWindow?.postMessage({
|
|
155
105
|
type: "playruo",
|
|
156
106
|
action: "tryNowClick",
|
|
157
107
|
fromEmbed: true,
|
|
158
|
-
isFullscreen: true,
|
|
159
108
|
isReplay: hasPlayedOnce
|
|
160
109
|
}, "*");
|
|
161
110
|
console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
|
|
@@ -169,7 +118,7 @@ var PlayruoEmbed = ({
|
|
|
169
118
|
setErrorMessage("Failed to start game");
|
|
170
119
|
onError?.("Failed to start game");
|
|
171
120
|
}
|
|
172
|
-
}, [isCompatible, hasPlayedOnce, onError, iframeRef
|
|
121
|
+
}, [isCompatible, hasPlayedOnce, onError, iframeRef]);
|
|
173
122
|
const handleBuyNow = useCallback(() => {
|
|
174
123
|
if (!storeUrl) {
|
|
175
124
|
console.warn("[PlayruoEmbed] No store URL provided");
|
|
@@ -179,9 +128,9 @@ var PlayruoEmbed = ({
|
|
|
179
128
|
}, [storeUrl, onPurchaseRequest]);
|
|
180
129
|
useEffect(() => {
|
|
181
130
|
if (isReady) {
|
|
182
|
-
onStateChange?.(
|
|
131
|
+
onStateChange?.(exposedState);
|
|
183
132
|
}
|
|
184
|
-
}, [isReady,
|
|
133
|
+
}, [isReady, exposedState, onStateChange]);
|
|
185
134
|
useEffect(() => {
|
|
186
135
|
if (isReady) {
|
|
187
136
|
onReady?.();
|
|
@@ -189,18 +138,112 @@ var PlayruoEmbed = ({
|
|
|
189
138
|
}, [isReady, onReady]);
|
|
190
139
|
useEffect(() => {
|
|
191
140
|
const handleGameMessage = (event) => {
|
|
192
|
-
const { type } = event.data || {};
|
|
141
|
+
const { type, data } = event.data || {};
|
|
142
|
+
const iframe = iframeRef.current;
|
|
143
|
+
if (!iframe || event.source !== iframe.contentWindow) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
console.log("\u{1F537} [Embed] Message received from iframe:", { type, data });
|
|
147
|
+
if (type === "playruo_ready") {
|
|
148
|
+
console.log("\u{1F537} [Embed] playruo_ready received");
|
|
149
|
+
onReady?.();
|
|
150
|
+
} else if (type === "playruo_state_update" && data) {
|
|
151
|
+
console.log("\u{1F537} [Embed] playruo_state_update received:", data);
|
|
152
|
+
setIframeState(data);
|
|
153
|
+
if (data.hasPlayedOnce !== void 0) {
|
|
154
|
+
setHasPlayedOnce(data.hasPlayedOnce);
|
|
155
|
+
}
|
|
156
|
+
onStateChange?.(data);
|
|
157
|
+
} else if (type === "playruo_purchase_request" && data?.storeUrl) {
|
|
158
|
+
console.log("\u{1F537} [Embed] playruo_purchase_request received:", data);
|
|
159
|
+
onPurchaseRequest?.(data);
|
|
160
|
+
} else if (type === "playruo_demo_completed" && data) {
|
|
161
|
+
console.log("\u{1F537} [Embed] playruo_demo_completed received:", data);
|
|
162
|
+
setHasPlayedOnce(true);
|
|
163
|
+
onDemoCompleted?.(data);
|
|
164
|
+
} else if (type === "playruo_error") {
|
|
165
|
+
const errorMessage2 = typeof data === "string" ? data : data?.message || "Unknown error";
|
|
166
|
+
console.log("\u{1F537} [Embed] playruo_error received:", errorMessage2);
|
|
167
|
+
setErrorMessage(errorMessage2);
|
|
168
|
+
const errorState = {
|
|
169
|
+
hasPlayedOnce,
|
|
170
|
+
tryNow: {
|
|
171
|
+
available: false,
|
|
172
|
+
status: `Error: ${errorMessage2}`
|
|
173
|
+
},
|
|
174
|
+
buyNow: {
|
|
175
|
+
available: true,
|
|
176
|
+
status: "Ready"
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
setIframeState(errorState);
|
|
180
|
+
onStateChange?.(errorState);
|
|
181
|
+
onError?.(errorMessage2);
|
|
182
|
+
} else if (type === "playruo_session_over" && data) {
|
|
183
|
+
console.log("\u{1F537} [Embed] playruo_session_over received:", data);
|
|
184
|
+
setHasPlayedOnce(true);
|
|
185
|
+
setIframeState(data);
|
|
186
|
+
onStateChange?.(data);
|
|
187
|
+
} else if (type === "playruo_stream_start") {
|
|
188
|
+
console.log("\u{1F537} [Embed] playruo_stream_start received");
|
|
189
|
+
}
|
|
193
190
|
if (type === "error" || type === "closeForBuy" || type === "goToStore") {
|
|
191
|
+
console.log("\u{1F537} [Embed] Legacy message received:", type);
|
|
194
192
|
setHasPlayedOnce(true);
|
|
195
|
-
|
|
193
|
+
const payload = {
|
|
196
194
|
reason: "Demo session ended",
|
|
197
195
|
storeUrl
|
|
198
|
-
}
|
|
196
|
+
};
|
|
197
|
+
onDemoCompleted?.(payload);
|
|
199
198
|
}
|
|
200
199
|
};
|
|
201
200
|
window.addEventListener("message", handleGameMessage);
|
|
202
201
|
return () => window.removeEventListener("message", handleGameMessage);
|
|
203
|
-
}, [storeUrl, onDemoCompleted]);
|
|
202
|
+
}, [storeUrl, onDemoCompleted, onReady, onStateChange, onPurchaseRequest, onError, iframeRef]);
|
|
203
|
+
const generateUUID = () => {
|
|
204
|
+
if (self.crypto.randomUUID) return self.crypto.randomUUID();
|
|
205
|
+
const bytes = new Uint8Array(16);
|
|
206
|
+
self.crypto.getRandomValues(bytes);
|
|
207
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
208
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
209
|
+
return [...bytes].map(
|
|
210
|
+
(b, i) => ([4, 6, 8, 10].includes(i) ? "-" : "") + b.toString(16).padStart(2, "0")
|
|
211
|
+
).join("");
|
|
212
|
+
};
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
const handleUUIDMessage = (event) => {
|
|
215
|
+
const iframe = iframeRef.current;
|
|
216
|
+
if (!iframe || event.source !== iframe.contentWindow) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const { type, requestId } = event.data || {};
|
|
220
|
+
if (type === "playruo_uuid_request") {
|
|
221
|
+
try {
|
|
222
|
+
let storedUUID = localStorage.getItem("playruo_uuid");
|
|
223
|
+
if (!storedUUID) {
|
|
224
|
+
storedUUID = generateUUID();
|
|
225
|
+
localStorage.setItem("playruo_uuid", storedUUID);
|
|
226
|
+
}
|
|
227
|
+
iframe.contentWindow?.postMessage(
|
|
228
|
+
{
|
|
229
|
+
type: "playruo_uuid_response",
|
|
230
|
+
requestId,
|
|
231
|
+
uuid: storedUUID
|
|
232
|
+
},
|
|
233
|
+
"*"
|
|
234
|
+
);
|
|
235
|
+
} catch (e) {
|
|
236
|
+
}
|
|
237
|
+
} else if (type === "playruo_uuid_remove") {
|
|
238
|
+
try {
|
|
239
|
+
localStorage.removeItem("playruo_uuid");
|
|
240
|
+
} catch (e) {
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
window.addEventListener("message", handleUUIDMessage);
|
|
245
|
+
return () => window.removeEventListener("message", handleUUIDMessage);
|
|
246
|
+
}, [iframeRef]);
|
|
204
247
|
useEffect(() => {
|
|
205
248
|
const handleFullscreenChange = () => {
|
|
206
249
|
const doc = document;
|
|
@@ -214,10 +257,17 @@ var PlayruoEmbed = ({
|
|
|
214
257
|
}, 200);
|
|
215
258
|
}
|
|
216
259
|
} else {
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
|
|
260
|
+
const iframe = iframeRef.current;
|
|
261
|
+
if (iframe) {
|
|
262
|
+
try {
|
|
263
|
+
iframe.contentWindow?.postMessage({
|
|
264
|
+
type: "playruo",
|
|
265
|
+
action: "exitFullscreen",
|
|
266
|
+
fromEmbed: true
|
|
267
|
+
}, "*");
|
|
268
|
+
} catch {
|
|
269
|
+
console.error("[PlayruoEmbed] Failed to exit fullscreen");
|
|
270
|
+
}
|
|
221
271
|
}
|
|
222
272
|
setShowGameIframe(false);
|
|
223
273
|
setHasPlayedOnce(true);
|
|
@@ -238,56 +288,6 @@ var PlayruoEmbed = ({
|
|
|
238
288
|
document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
|
|
239
289
|
};
|
|
240
290
|
}, [storeUrl, onDemoCompleted, iframeRef]);
|
|
241
|
-
useEffect(() => {
|
|
242
|
-
const handleKeyDown = (event) => {
|
|
243
|
-
if (event.key === "Escape" && !escapeKeyPressed.current) {
|
|
244
|
-
escapeKeyPressed.current = true;
|
|
245
|
-
event.preventDefault();
|
|
246
|
-
escapeKeyTimer.current = setTimeout(() => {
|
|
247
|
-
if (document.fullscreenElement) {
|
|
248
|
-
document.exitFullscreen();
|
|
249
|
-
}
|
|
250
|
-
}, ESCAPE_HOLD_DURATION);
|
|
251
|
-
}
|
|
252
|
-
if (event.ctrlKey && event.shiftKey && event.key === "R") {
|
|
253
|
-
event.preventDefault();
|
|
254
|
-
if (document.fullscreenElement) {
|
|
255
|
-
document.exitFullscreen().catch(console.warn);
|
|
256
|
-
}
|
|
257
|
-
setTimeout(() => {
|
|
258
|
-
window.location.reload();
|
|
259
|
-
}, 500);
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
const handleKeyUp = (event) => {
|
|
263
|
-
if (event.key === "Escape" && escapeKeyPressed.current) {
|
|
264
|
-
escapeKeyPressed.current = false;
|
|
265
|
-
if (escapeKeyTimer.current) {
|
|
266
|
-
clearTimeout(escapeKeyTimer.current);
|
|
267
|
-
escapeKeyTimer.current = null;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
272
|
-
document.addEventListener("keyup", handleKeyUp);
|
|
273
|
-
return () => {
|
|
274
|
-
document.removeEventListener("keydown", handleKeyDown);
|
|
275
|
-
document.removeEventListener("keyup", handleKeyUp);
|
|
276
|
-
};
|
|
277
|
-
}, []);
|
|
278
|
-
useEffect(() => {
|
|
279
|
-
const isVisible = showGameIframe || showIframe;
|
|
280
|
-
const iframe = iframeRef.current;
|
|
281
|
-
if (!iframe || !isIframeLoaded) return;
|
|
282
|
-
try {
|
|
283
|
-
iframe.contentWindow?.postMessage({
|
|
284
|
-
type: "playruo",
|
|
285
|
-
action: isVisible ? "unmute" : "mute",
|
|
286
|
-
fromEmbed: true
|
|
287
|
-
}, "*");
|
|
288
|
-
} catch {
|
|
289
|
-
}
|
|
290
|
-
}, [showGameIframe, showIframe, isIframeLoaded, iframeRef]);
|
|
291
291
|
useEffect(() => {
|
|
292
292
|
const logCrossOriginStatus = () => {
|
|
293
293
|
const isIsolated = self.crossOriginIsolated;
|
|
@@ -366,22 +366,28 @@ var PlayruoEmbed = ({
|
|
|
366
366
|
logCrossOriginStatus();
|
|
367
367
|
}
|
|
368
368
|
}, [isReady]);
|
|
369
|
-
const isAvailable = Boolean(isReady && isCompatible);
|
|
369
|
+
const isAvailable = Boolean(isReady && isCompatible && hasIframeConfirmed);
|
|
370
370
|
const renderContent = () => {
|
|
371
371
|
if (!renderState) return null;
|
|
372
|
+
const canTryNow = Boolean(
|
|
373
|
+
!errorMessage && isReady && isCompatible && hasIframeConfirmed
|
|
374
|
+
);
|
|
372
375
|
const actions = {
|
|
373
376
|
tryNow: handleTryNow,
|
|
374
377
|
buyNow: handleBuyNow,
|
|
375
|
-
canTryNow
|
|
378
|
+
canTryNow,
|
|
376
379
|
canBuyNow: Boolean(isReady)
|
|
377
380
|
};
|
|
378
381
|
if (errorMessage) {
|
|
379
382
|
return renderState({ status: "error", message: errorMessage, actions });
|
|
380
383
|
}
|
|
381
384
|
if (!isReady) {
|
|
382
|
-
return renderState({ status: "loading", actions });
|
|
385
|
+
return renderState({ status: "loading", message: "Loading\u2026", actions });
|
|
386
|
+
}
|
|
387
|
+
if (isCompatible && !iframeState) {
|
|
388
|
+
return renderState({ status: "loading", message: "Checking availability\u2026", actions });
|
|
383
389
|
}
|
|
384
|
-
const message = !isAvailable ? currentState.tryNow.status : void 0;
|
|
390
|
+
const message = !isAvailable ? currentState.tryNow.status ?? exposedState.tryNow.status : void 0;
|
|
385
391
|
return renderState({
|
|
386
392
|
status: isAvailable ? "available" : "unavailable",
|
|
387
393
|
message,
|
|
@@ -396,7 +402,6 @@ var PlayruoEmbed = ({
|
|
|
396
402
|
ref: iframeRef,
|
|
397
403
|
src,
|
|
398
404
|
allow: "cross-origin-isolated; fullscreen *; autoplay *; gamepad *; clipboard-write *; web-share *; encrypted-media *; accelerometer *; gyroscope *",
|
|
399
|
-
allowFullScreen: true,
|
|
400
405
|
className: "w-full h-[70vh] rounded-lg border",
|
|
401
406
|
...{ credentialless: "true" },
|
|
402
407
|
sandbox: "allow-same-origin allow-scripts allow-popups allow-forms allow-presentation allow-top-navigation allow-modals allow-pointer-lock",
|
|
@@ -615,6 +620,10 @@ var EmbedTester = (props = {}) => {
|
|
|
615
620
|
/* @__PURE__ */ jsx2("span", { style: embedState.hasPlayedOnce ? successStatus : errorStatus, children: embedState.hasPlayedOnce ? "\u2705 Played" : "\u274C Not Played" }),
|
|
616
621
|
/* @__PURE__ */ jsx2("span", { style: embedState.tryNow?.available ? successStatus : errorStatus, children: embedState.tryNow?.available ? "\u2705 Try Now" : "\u274C Try Now" }),
|
|
617
622
|
/* @__PURE__ */ jsx2("span", { style: embedState.buyNow?.available ? successStatus : errorStatus, children: embedState.buyNow?.available ? "\u2705 Buy Now" : "\u274C Buy Now" })
|
|
623
|
+
] }),
|
|
624
|
+
!embedState.tryNow?.available && embedState.tryNow?.status && /* @__PURE__ */ jsxs2("p", { style: { margin: "8px 0 0 0", fontSize: "12px", color: "#a3a3a3" }, children: [
|
|
625
|
+
"Try Now reason: ",
|
|
626
|
+
embedState.tryNow.status
|
|
618
627
|
] })
|
|
619
628
|
] }),
|
|
620
629
|
/* @__PURE__ */ jsxs2("div", { style: cardStyle, children: [
|
|
@@ -644,7 +653,8 @@ var EmbedTester = (props = {}) => {
|
|
|
644
653
|
backgroundColor: "#0a0a0a",
|
|
645
654
|
padding: "12px",
|
|
646
655
|
borderRadius: "4px",
|
|
647
|
-
|
|
656
|
+
minHeight: "200px",
|
|
657
|
+
height: "min(50vh, 480px)",
|
|
648
658
|
overflowY: "auto",
|
|
649
659
|
fontFamily: "monospace",
|
|
650
660
|
fontSize: "12px",
|
|
@@ -789,18 +799,17 @@ var isPlayruoEmbedMessage = (data) => {
|
|
|
789
799
|
if (!data || typeof data !== "object") return false;
|
|
790
800
|
const msg = data;
|
|
791
801
|
if (typeof msg.type !== "string") return false;
|
|
792
|
-
return msg.type === "playruo_ready" || msg.type === "playruo_state_update" || msg.type === "playruo_purchase_request" || msg.type === "playruo_demo_completed" || msg.type === "playruo_error";
|
|
802
|
+
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";
|
|
793
803
|
};
|
|
794
|
-
var
|
|
804
|
+
var isPlayruoInternalMessage = (data) => {
|
|
795
805
|
if (!data || typeof data !== "object") return false;
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
return cmd.type === "try_now" || cmd.type === "buy_now" || cmd.type === "get_state";
|
|
806
|
+
const msg = data;
|
|
807
|
+
return msg.type === "playruo" && typeof msg.action === "string" && (msg.action === "tryNowClick" || msg.action === "exitFullscreen" || msg.action === "mute" || msg.action === "unmute") && msg.fromEmbed === true;
|
|
799
808
|
};
|
|
800
809
|
export {
|
|
801
810
|
EmbedTester,
|
|
802
811
|
PlayruoEmbed,
|
|
803
812
|
buildEmbedUrl,
|
|
804
|
-
|
|
805
|
-
|
|
813
|
+
isPlayruoEmbedMessage,
|
|
814
|
+
isPlayruoInternalMessage
|
|
806
815
|
};
|