diff --git a/src/index.ts b/src/index.ts index ac89f9a..9e2cade 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,9 @@ export const CONTROL_PLANE_ROUTES = { deviceRemove: "/devices/remove", deviceSession: "/device/session", deviceSocket: "/device-ws", + publicInvite: "/public-invite", + publicSession: "/public-session", + surfaceSession: "/surface-session", replEval: "/repl/eval", replClose: "/repl/close", browserSocket: "/ws", @@ -123,6 +126,7 @@ export type ControlPlaneDeviceIdentifyRequest = { export type ControlPlaneDeviceSessionRequest = { deviceId: string; + sessionToken: string; requestedTarget?: ControlPlaneDeviceTarget | null; userAgent?: string | null; viewport?: ControlPlaneDeviceViewport | null; @@ -132,6 +136,56 @@ export type ControlPlaneDeviceSessionResponse = { device: ControlPlaneDevice; }; +export type ControlPlanePublicAccessTarget = + | { + kind: "device"; + target?: ControlPlaneDeviceTarget | null; + } + | { + kind: "browser-surface"; + packageName?: string; + stateContextId: string; + surfaceId: string; + requestFullscreen?: boolean; + } + | { + kind: "remote-dom"; + packageName?: string; + stateContextId: string; + }; + +export type ControlPlanePublicInviteRequest = { + target: ControlPlanePublicAccessTarget; +}; + +export type ControlPlanePublicInviteResponse = { + inviteToken: string; + target: ControlPlanePublicAccessTarget; +}; + +export type ControlPlanePublicSessionRequest = { + inviteToken: string; +}; + +export type ControlPlanePublicSessionResponse = { + sessionToken: string; + target: ControlPlanePublicAccessTarget; + expiresAt: string; +}; + +export type ControlPlaneSurfaceSessionRequest = { + target: Extract< + ControlPlanePublicAccessTarget, + { kind: "browser-surface" | "remote-dom" } + >; +}; + +export type ControlPlaneSurfaceSessionResponse = { + sessionToken: string; + target: ControlPlaneSurfaceSessionRequest["target"]; + expiresAt: string; +}; + export type ControlPlaneWorkspacePackage = { packageName: string; headCommit: string; @@ -290,6 +344,7 @@ export type ControlPlaneBrowserStatusInvalidatedMessage = { export type ControlPlaneDeviceSocketConnectMessage = { type: "device-connect"; deviceId: string; + sessionToken: string; requestedTarget?: ControlPlaneDeviceTarget | null; userAgent?: string | null; viewport?: ControlPlaneDeviceViewport | null; @@ -537,6 +592,9 @@ const isRecord = (value: unknown): value is Record => const isString = (value: unknown): value is string => typeof value === "string"; +const isNonEmptyString = (value: unknown): value is string => + typeof value === "string" && value.length > 0; + const isNullableString = (value: unknown): value is string | null => value === null || typeof value === "string"; @@ -629,6 +687,65 @@ export const parseControlPlaneDeviceTarget = ( }; }; +export const parseControlPlanePublicAccessTarget = ( + value: unknown, +): ControlPlanePublicAccessTarget | null => { + if (!isRecord(value) || !isNonEmptyString(value.kind)) { + return null; + } + if (value.kind === "device") { + const target = + value.target === undefined + ? undefined + : value.target === null + ? null + : parseControlPlaneDeviceTarget(value.target); + if (value.target !== undefined && value.target !== null && !target) { + return null; + } + return { + kind: "device", + ...(target !== undefined ? { target } : {}), + }; + } + if (value.kind === "browser-surface") { + if ( + !isNonEmptyString(value.stateContextId) || + !isNonEmptyString(value.surfaceId) || + !(value.packageName === undefined || isNonEmptyString(value.packageName)) || + !( + value.requestFullscreen === undefined || + typeof value.requestFullscreen === "boolean" + ) + ) { + return null; + } + return { + kind: "browser-surface", + ...(value.packageName ? { packageName: value.packageName } : {}), + stateContextId: value.stateContextId, + surfaceId: value.surfaceId, + ...(value.requestFullscreen !== undefined + ? { requestFullscreen: value.requestFullscreen } + : {}), + }; + } + if (value.kind === "remote-dom") { + if ( + !isNonEmptyString(value.stateContextId) || + !(value.packageName === undefined || isNonEmptyString(value.packageName)) + ) { + return null; + } + return { + kind: "remote-dom", + ...(value.packageName ? { packageName: value.packageName } : {}), + stateContextId: value.stateContextId, + }; + } + return null; +}; + export const parseControlPlaneDevice = ( value: unknown, ): ControlPlaneDevice | null => { @@ -907,6 +1024,7 @@ export const parseControlPlaneDeviceSessionRequest = ( if ( !isRecord(value) || !isNonEmptyString(value.deviceId) || + !isNonEmptyString(value.sessionToken) || !( value.userAgent === undefined || value.userAgent === null || @@ -937,12 +1055,45 @@ export const parseControlPlaneDeviceSessionRequest = ( } return { deviceId: value.deviceId, + sessionToken: value.sessionToken, ...(requestedTarget !== undefined ? { requestedTarget } : {}), userAgent: value.userAgent ?? null, viewport, }; }; +export const parseControlPlanePublicInviteRequest = ( + value: unknown, +): ControlPlanePublicInviteRequest | null => { + if (!isRecord(value)) { + return null; + } + const target = parseControlPlanePublicAccessTarget(value.target); + return target ? { target } : null; +}; + +export const parseControlPlanePublicSessionRequest = ( + value: unknown, +): ControlPlanePublicSessionRequest | null => { + if (!isRecord(value) || !isNonEmptyString(value.inviteToken)) { + return null; + } + return { inviteToken: value.inviteToken }; +}; + +export const parseControlPlaneSurfaceSessionRequest = ( + value: unknown, +): ControlPlaneSurfaceSessionRequest | null => { + if (!isRecord(value)) { + return null; + } + const target = parseControlPlanePublicAccessTarget(value.target); + if (!target || target.kind === "device") { + return null; + } + return { target }; +}; + export const parseControlPlaneReplEvalRequest = ( value: unknown, ): ControlPlaneReplEvalRequest | null => { @@ -985,9 +1136,6 @@ export const parseControlPlaneReplCloseRequest = ( }; }; -const isNonEmptyString = (value: unknown): value is string => - typeof value === "string" && value.length > 0; - const parseBrowserSubscribeParams = ( value: unknown, ): ControlPlaneBrowserSubscribeParams | null => { @@ -1027,6 +1175,7 @@ export const parseControlPlaneDeviceSocketClientMessage = ( return { type: "device-connect", deviceId: parsed.deviceId, + sessionToken: parsed.sessionToken, ...(parsed.requestedTarget !== undefined ? { requestedTarget: parsed.requestedTarget } : {}),