144 lines
4.6 KiB
TypeScript
144 lines
4.6 KiB
TypeScript
import { decrypt } from './e2ee-crypto'
|
|
import { encrypt } from './e2ee-crypto'
|
|
import type WebSocket from 'ws'
|
|
import { RemoteRuntimeClientError } from './remote-runtime-client'
|
|
import {
|
|
invalidRemoteRuntimeResponseError,
|
|
parseRemoteRuntimeRpcFrame
|
|
} from './remote-runtime-request-frames'
|
|
import type {
|
|
SharedControlConnectionState,
|
|
SharedControlLogicalSubscription
|
|
} from './remote-runtime-shared-control-types'
|
|
|
|
export function parseSharedControlFrame(
|
|
frame: string,
|
|
sharedKey: Uint8Array | null,
|
|
state: SharedControlConnectionState
|
|
):
|
|
| { type: 'auth'; plaintext: string }
|
|
| {
|
|
type: 'frame'
|
|
frame: Exclude<ReturnType<typeof parseRemoteRuntimeRpcFrame>, { type: 'error' }>
|
|
}
|
|
| { type: 'error'; error: RemoteRuntimeClientError } {
|
|
if (!sharedKey) {
|
|
return {
|
|
type: 'error',
|
|
error: invalidRemoteRuntimeResponseError('Remote Orca runtime returned a frame before E2EE.')
|
|
}
|
|
}
|
|
const plaintext = decrypt(frame, sharedKey)
|
|
if (plaintext === null) {
|
|
return {
|
|
type: 'error',
|
|
error: invalidRemoteRuntimeResponseError(
|
|
'Remote Orca runtime returned an undecryptable frame.'
|
|
)
|
|
}
|
|
}
|
|
if (state === 'awaiting_authenticated') {
|
|
return { type: 'auth', plaintext }
|
|
}
|
|
const parsed = parseRemoteRuntimeRpcFrame(plaintext)
|
|
if (parsed.type === 'error') {
|
|
return parsed
|
|
}
|
|
return { type: 'frame', frame: parsed }
|
|
}
|
|
|
|
export function getSubscriptionId(result: unknown): string | null {
|
|
if (typeof result !== 'object' || result === null) {
|
|
return null
|
|
}
|
|
const value = (result as { subscriptionId?: unknown }).subscriptionId
|
|
return typeof value === 'string' && value.length > 0 ? value : null
|
|
}
|
|
|
|
export function isEndResult(result: unknown): boolean {
|
|
return (
|
|
typeof result === 'object' && result !== null && (result as { type?: unknown }).type === 'end'
|
|
)
|
|
}
|
|
|
|
export function getCleanupRequest(
|
|
subscription: SharedControlLogicalSubscription<unknown>
|
|
): { method: string; params: unknown } | null {
|
|
if (subscription.method === 'accounts.subscribe' && subscription.remoteSubscriptionId) {
|
|
return cleanupBySubscriptionId('accounts.unsubscribe', subscription.remoteSubscriptionId)
|
|
}
|
|
if (subscription.method === 'notifications.subscribe' && subscription.remoteSubscriptionId) {
|
|
return cleanupBySubscriptionId('notifications.unsubscribe', subscription.remoteSubscriptionId)
|
|
}
|
|
if (
|
|
subscription.method === 'runtime.clientEvents.subscribe' &&
|
|
subscription.remoteSubscriptionId
|
|
) {
|
|
return cleanupBySubscriptionId(
|
|
'runtime.clientEvents.unsubscribe',
|
|
subscription.remoteSubscriptionId
|
|
)
|
|
}
|
|
if (subscription.method === 'files.watch' && subscription.remoteSubscriptionId) {
|
|
return cleanupBySubscriptionId('files.unwatch', subscription.remoteSubscriptionId)
|
|
}
|
|
if (subscription.method === 'session.tabs.subscribe') {
|
|
const params =
|
|
typeof subscription.params === 'object' && subscription.params !== null
|
|
? { ...subscription.params, subscriptionId: subscription.requestId }
|
|
: subscription.params
|
|
return { method: 'session.tabs.unsubscribe', params }
|
|
}
|
|
if (subscription.method === 'session.tabs.subscribeAll') {
|
|
return {
|
|
method: 'session.tabs.unsubscribeAll',
|
|
params: { subscriptionId: subscription.requestId }
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
export function formatSharedControlCloseMessage(code: number, reason: Buffer): string {
|
|
const reasonText = reason.toString().trim()
|
|
if (code !== 1005 && code !== 1006 && reasonText) {
|
|
return `Remote Orca runtime closed the connection (${code}: ${reasonText}).`
|
|
}
|
|
if (code !== 1005 && code !== 1006) {
|
|
return `Remote Orca runtime closed the connection (${code}).`
|
|
}
|
|
return 'Remote Orca runtime closed the connection.'
|
|
}
|
|
|
|
export function sendSharedControlEncrypted(args: {
|
|
state: SharedControlConnectionState
|
|
ws: WebSocket | null
|
|
sharedKey: Uint8Array | null
|
|
payload: unknown
|
|
}): boolean {
|
|
if (args.state !== 'ready' && args.state !== 'awaiting_authenticated') {
|
|
return false
|
|
}
|
|
if (!args.ws || args.ws.readyState !== 1 || !args.sharedKey) {
|
|
return false
|
|
}
|
|
args.ws.send(encrypt(JSON.stringify(args.payload), args.sharedKey))
|
|
return true
|
|
}
|
|
|
|
export function toRemoteRuntimeClientError(error: unknown): RemoteRuntimeClientError {
|
|
if (error instanceof RemoteRuntimeClientError) {
|
|
return error
|
|
}
|
|
if (error instanceof Error) {
|
|
return new RemoteRuntimeClientError('runtime_error', error.message)
|
|
}
|
|
return new RemoteRuntimeClientError('runtime_error', String(error))
|
|
}
|
|
|
|
function cleanupBySubscriptionId(
|
|
method: string,
|
|
subscriptionId: string
|
|
): { method: string; params: unknown } {
|
|
return { method, params: { subscriptionId } }
|
|
}
|