import { spawn , IPty } from "node-pty" ; import * as os from "os" ; import { Hash , RoomKey } from "../surya/types" ; import { getDimensions , dimensions , initScreen , areDimensionEqual , resizeBestFit , } from "./auxiliary" ; import chalk = require ( "chalk" ) ; import { Unauthorized } from "../surya/errors" ; import { encrypt , decrypt } from "../encryption" ; import { JoinChannelOptions } from "../surya" ; import { Channel } from "../surya/vendor/phoenix" ; enum MessageType { IN = "i" , OUT = "o" , DIMENSIONS = "d" } export type TeletypeOptions = { userId : string ; roomKey : RoomKey ; shell : string ; multiplex : boolean ; process : NodeJS . Process ; joinChannel : ( options : JoinChannelOptions < any > ) => Channel ; } ; const SELF = "self" ; export const teletypeApp = ( options ) => { const username = os . userInfo ( ) . username ; const hostname = os . hostname ( ) ; const userDimensions = { } ; userDimensions [ SELF ] = getDimensions ( ) ; let term ; const reEvaluateOwnDimensions = ( ) => { const lastKnown = userDimensions [ SELF ] ; const latest = getDimensions ( ) ; if ( areDimensionEqual ( lastKnown , latest ) ) { return ; } userDimensions [ SELF ] = latest ; resizeBestFit ( term , userDimensions ) ; } ; return new Promise ( ( resolve , reject ) => { const channel = options . joinChannel ( { channel : `teletype:${ options . roomKey . roomId }` , params : { username , hostname , multiplexed : options . multiplex , } , onJoin : ( ) => { initScreen ( username , hostname , options . shell , options . multiplex ) ; const stdin = options . process . stdin ; const stdout = options . process . stdout ; const dimensions = userDimensions [ SELF ] ; term = spawn ( options . shell , [ ] , { name : "xterm-256color" , cols : dimensions . cols , rows : dimensions . rows , cwd : options . process . cwd ( ) , env : options . process . env , } ) ; setInterval ( reEvaluateOwnDimensions , 1000 ) ; term . on ( "data" , ( d ) => { stdout . write ( d ) ; channel . push ( "new_msg" , { t : MessageType . OUT , b : true , d : encrypt ( d , options . roomKey ) , } ) ; } ) ; term . on ( "exit" , ( ) => { console . log ( chalk . blueBright ( "terminated shell stream to oorja. byee!" ) ) ; resolve ( ) ; } ) ; stdin . setEncoding ( "utf8" ) ; stdin . setRawMode ! ( true ) ; stdin . on ( "data" , ( d ) => term . write ( d ) ) ; } , onClose : ( ) => { console . log ( chalk . redBright ( "connection closed, terminated stream." ) ) ; process . exit ( 3 ) ; } , onError : ( err ? ) => { if ( err instanceof Unauthorized ) { console . log ( chalk . redBright ( err . message ) ) ; } else { console . log ( chalk . redBright ( "connection error, terminated stream." ) ) ; } process . exit ( 4 ) ; } , onMessage : ( { from : { session } , t , d } ) => { switch ( t ) { case MessageType . DIMENSIONS : userDimensions [ session ] = d ; resizeBestFit ( term , userDimensions ) ; break ; case MessageType . IN : const data = decrypt ( d , options . roomKey ) ; const userId = session . split ( ":" ) [ 0 ] ; if ( options . multiplex ) { term . write ( data ) ; return ; } if ( userId === options . userId ) { term . write ( data ) ; } else { console . log ( chalk . redBright ( `unexpected input from user: ${ userId }, terminating stream for safety. Please report this issue` ) ) ; process . exit ( 5 ) ; } } } , handleSessionJoin : ( s ) => { } , handleSessionLeave : ( s ) => { delete userDimensions [ s ] ; resizeBestFit ( term , userDimensions ) ; } , } ) ; } ) ; } ;