Skip to content
Source
Sign inSign up
Open History to see the last commit

device-stream-document.ts

72 lines3.0 KB

1import type { DeviceHubAccess } from "@t3tools/client-runtime/device/hub-access";
2import type { DevicePlatform } from "@t3tools/contracts";
3import type { PlatformOSType } from "react-native";
4​
5export interface DeviceStreamConfiguration {
6 readonly access: DeviceHubAccess;
7 readonly platform: DevicePlatform;
8 readonly deviceId: string;
9 /** See `DeviceStreamTarget.serveSimOnly`. */
10 readonly serveSimOnly?: boolean;
11 readonly colors: {
12 readonly background: string;
13 readonly foreground: string;
14 readonly muted: string;
15 readonly buttonBackground: string;
16 readonly buttonForeground: string;
17 readonly buttonBorder: string;
18 };
20​
21export function deviceStreamBaseUrl(
22 viewerPlatform: PlatformOSType,
23 devicePlatform: DevicePlatform,
24 httpBase: string,
25) {
26 if (viewerPlatform !== "android") return "file:///";
27 // Android targets stream through WebCodecs, which needs a secure context. iOS targets
28 // stream an MJPEG image from the environment, which a synthetic https document blocks as
29 // mixed content on plain-http environments even with mixedContentMode="always".
30 if (devicePlatform === "android") return "https://localhost/";
31 return `${new URL(httpBase).origin}/`;
33​
34export function deviceStreamDocument(configuration: string, script: string) {
35 const safeConfiguration = configuration.replace(/</g, "\\u003c");
36 const safeScript = script.replace(/<\/script/gi, "<\\/script");
37 const failure = `window.ReactNativeWebView.postMessage(JSON.stringify({type:"status",status:"error",detail:"Device viewer stopped unexpectedly."}));`;
38 return `<!doctype html><html><head><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"></head><body><script>window.addEventListener("error",function(){${failure}});window.addEventListener("unhandledrejection",function(){${failure}});\n${safeScript}\ntry{T3DeviceStream.start(${safeConfiguration});}catch{${failure}}</script></body></html>`;
40​
41export function deviceStreamMessage(data: string) {
42 try {
43 const message: unknown = JSON.parse(data);
44 if (typeof message !== "object" || message === null || !("type" in message)) return null;
45 if (message.type === "unauthorized" || message.type === "retry") {
46 return { type: message.type } as const;
47 }
48 if (
49 message.type === "input" &&
50 "connected" in message &&
51 typeof message.connected === "boolean"
52 ) {
53 return { type: message.type, connected: message.connected } as const;
54 }
55 if (
56 message.type === "status" &&
57 "status" in message &&
58 (message.status === "connecting" ||
59 message.status === "streaming" ||
60 message.status === "error") &&
61 (!("detail" in message) || typeof message.detail === "string")
62 ) {
63 return {
64 type: message.type,
65 status: message.status,
66 detail:
67 "detail" in message && typeof message.detail === "string" ? message.detail : undefined,
68 } as const;
69 }
70 } catch {}
71 return null;