format import { ParsedUrlQuery } from 'querystring' ; import { BrowserClientConnection } from './BrowserClientConnection' ; import { getFlipperServerConfig } from '../FlipperServerConfig' ; import ws from 'ws' ; import { IncomingMessage } from 'http' ; import { assertNotNull , parseClientQuery } from './Utilities' ; import SecureServerWebSocket , { SecureConnectionCtx , } from './SecureServerWebSocket' ; import { SecureClientQuery } from './ServerAdapter' ; import { ClientDescription , DeviceOS } from 'flipper-common' ; interface BrowserConnectionCtx extends SecureConnectionCtx { clientConnection ? : BrowserClientConnection ; } type LegacyWsMessage = { app : string ; payload : object ; type ? : never ; plugins ? : never ; } | { app : string ; payload ? : never ; type : 'connect' ; plugins ? : string [ ] ; } ; function isLegacyMessage ( message ) { return typeof ( message as LegacyWsMessage ) . app === 'string' ; } /**
 * WebSocket-based server over an insecure channel that does not support the certificate exchange flow. E.g. web browser.
 */ class BrowserServerWebSocket extends SecureServerWebSocket { protected handleConnectionAttempt ( ctx ) { const { clientQuery , ws } = ctx ; assertNotNull ( clientQuery ) ; console . info ( `[conn] Local websocket connection attempt: ${ clientQuery . app } on ${ clientQuery . device_id }.` ) ; this . listener . onConnectionAttempt ( clientQuery ) ; this . listener . onSecureConnectionAttempt ( clientQuery ) ; const clientConnection = new BrowserClientConnection ( ws ) ; const client = this . listener . onConnectionCreated ( clientQuery , clientConnection ) ; ctx . clientConnection = clientConnection ; ctx . clientPromise = client ; } protected async handleMessage ( ctx , parsedMessage , rawMessage ) { const { clientQuery , clientConnection } = ctx ; assertNotNull ( clientQuery ) ; assertNotNull ( clientConnection ) ; if ( isLegacyMessage ( parsedMessage ) ) { if ( parsedMessage . type === 'connect' ) { console . warn ( '[conn] Legacy WebSocket connection. Please, upgrade. See https://github.com/facebook/flipper/tree/main/js/js-flipper for references.' ) ; const plugins = parsedMessage . plugins ; clientConnection . plugins = plugins ; clientConnection . legacyFormat = true ; if ( plugins ) { const getPluginsCallbacks = clientConnection . matchPendingRequest ( 0 ) ; if ( ! getPluginsCallbacks ) { return ; } getPluginsCallbacks . resolve ( { success : { plugins } , length : rawMessage . length , } ) ; } return ; } parsedMessage = parsedMessage . payload ; rawMessage = JSON . stringify ( parsedMessage ) ; } super . handleMessage ( ctx , parsedMessage , rawMessage ) ; } protected parseClientQuery ( query ) { const fallbackOS = 'MacOS' ; const fallbackApp = query . device ; const fallbackDeviceId = query . deviceId ; const fallbackSdkVersion = '4' ; query = { app : fallbackApp , os : fallbackOS , device_id : fallbackDeviceId , sdk_version : fallbackSdkVersion , ... query , } ; const parsedBaseQuery = parseClientQuery ( query ) ; if ( ! parsedBaseQuery ) { return ; } return { ... parsedBaseQuery , medium : 3 } ; } protected verifyClient ( ) { return ( info ) => { const ok = getFlipperServerConfig ( ) . validWebSocketOrigins . some ( ( validPrefix ) => info . origin . startsWith ( validPrefix ) ) ; if ( ! ok ) { console . warn ( `[conn] Refused webSocket connection from ${ info . origin } (secure: ${ info . secure })` ) ; } return ok ; } ; } } export default BrowserServerWebSocket ;