mirror of
https://github.com/Eugeny/tabby.git
synced 2024-11-25 19:17:39 +03:00
group various stream processors into middleware
This commit is contained in:
parent
b0c300be43
commit
076b1c7129
@ -3,7 +3,7 @@ import SerialPort from 'serialport'
|
|||||||
import { LogService, NotificationsService, Profile } from 'tabby-core'
|
import { LogService, NotificationsService, Profile } from 'tabby-core'
|
||||||
import { Subject, Observable } from 'rxjs'
|
import { Subject, Observable } from 'rxjs'
|
||||||
import { Injector, NgZone } from '@angular/core'
|
import { Injector, NgZone } from '@angular/core'
|
||||||
import { BaseSession, LoginScriptsOptions, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal'
|
import { BaseSession, LoginScriptsOptions, SessionMiddleware, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal'
|
||||||
import { SerialService } from './services/serial.service'
|
import { SerialService } from './services/serial.service'
|
||||||
|
|
||||||
export interface SerialProfile extends Profile {
|
export interface SerialProfile extends Profile {
|
||||||
@ -32,6 +32,14 @@ export interface SerialPortInfo {
|
|||||||
description?: string
|
description?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SlowFeedMiddleware extends SessionMiddleware {
|
||||||
|
feedFromTerminal (data: Buffer): void {
|
||||||
|
for (const byte of data) {
|
||||||
|
this.outputToSession.next(Buffer.from([byte]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class SerialSession extends BaseSession {
|
export class SerialSession extends BaseSession {
|
||||||
serial: SerialPort
|
serial: SerialPort
|
||||||
|
|
||||||
@ -50,13 +58,11 @@ export class SerialSession extends BaseSession {
|
|||||||
this.notifications = injector.get(NotificationsService)
|
this.notifications = injector.get(NotificationsService)
|
||||||
|
|
||||||
this.streamProcessor = new TerminalStreamProcessor(profile.options)
|
this.streamProcessor = new TerminalStreamProcessor(profile.options)
|
||||||
this.streamProcessor.outputToSession$.subscribe(data => {
|
this.middleware.push(this.streamProcessor)
|
||||||
this.serial?.write(data.toString())
|
|
||||||
})
|
if (this.profile.options.slowSend) {
|
||||||
this.streamProcessor.outputToTerminal$.subscribe(data => {
|
this.middleware.unshift(new SlowFeedMiddleware())
|
||||||
this.emitOutput(data)
|
}
|
||||||
this.loginScriptProcessor?.feedFromSession(data)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.setLoginScriptsOptions(profile.options)
|
this.setLoginScriptsOptions(profile.options)
|
||||||
}
|
}
|
||||||
@ -110,7 +116,7 @@ export class SerialSession extends BaseSession {
|
|||||||
setTimeout(() => this.streamProcessor.start())
|
setTimeout(() => this.streamProcessor.start())
|
||||||
|
|
||||||
this.serial.on('readable', () => {
|
this.serial.on('readable', () => {
|
||||||
this.streamProcessor.feedFromSession(this.serial.read())
|
this.emitOutput(this.serial.read())
|
||||||
})
|
})
|
||||||
|
|
||||||
this.serial.on('end', () => {
|
this.serial.on('end', () => {
|
||||||
@ -124,17 +130,10 @@ export class SerialSession extends BaseSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
write (data: Buffer): void {
|
write (data: Buffer): void {
|
||||||
if (!this.profile.options.slowSend) {
|
this.serial?.write(data.toString())
|
||||||
this.streamProcessor.feedFromTerminal(data)
|
|
||||||
} else {
|
|
||||||
for (const byte of data) {
|
|
||||||
this.streamProcessor.feedFromTerminal(Buffer.from([byte]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async destroy (): Promise<void> {
|
async destroy (): Promise<void> {
|
||||||
this.streamProcessor.close()
|
|
||||||
this.serviceMessage.complete()
|
this.serviceMessage.complete()
|
||||||
await super.destroy()
|
await super.destroy()
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import colors from 'ansi-colors'
|
|||||||
import stripAnsi from 'strip-ansi'
|
import stripAnsi from 'strip-ansi'
|
||||||
import { Injector } from '@angular/core'
|
import { Injector } from '@angular/core'
|
||||||
import { Profile, LogService } from 'tabby-core'
|
import { Profile, LogService } from 'tabby-core'
|
||||||
import { BaseSession, LoginScriptsOptions, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal'
|
import { BaseSession, LoginScriptsOptions, SessionMiddleware, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal'
|
||||||
import { Subject, Observable } from 'rxjs'
|
import { Subject, Observable } from 'rxjs'
|
||||||
|
|
||||||
|
|
||||||
@ -41,6 +41,21 @@ enum TelnetOptions {
|
|||||||
NEW_ENVIRON = 0x27,
|
NEW_ENVIRON = 0x27,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UnescapeFFMiddleware extends SessionMiddleware {
|
||||||
|
feedFromSession (data: Buffer): void {
|
||||||
|
while (data.includes(0xff)) {
|
||||||
|
const pos = data.indexOf(0xff)
|
||||||
|
|
||||||
|
this.outputToTerminal.next(data.slice(0, pos))
|
||||||
|
this.outputToTerminal.next(Buffer.from([0xff, 0xff]))
|
||||||
|
|
||||||
|
data = data.slice(pos + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.outputToTerminal.next(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class TelnetSession extends BaseSession {
|
export class TelnetSession extends BaseSession {
|
||||||
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
||||||
|
|
||||||
@ -48,7 +63,6 @@ export class TelnetSession extends BaseSession {
|
|||||||
private socket: Socket
|
private socket: Socket
|
||||||
private streamProcessor: TerminalStreamProcessor
|
private streamProcessor: TerminalStreamProcessor
|
||||||
private telnetProtocol = false
|
private telnetProtocol = false
|
||||||
private echoEnabled = false
|
|
||||||
private lastWidth = 0
|
private lastWidth = 0
|
||||||
private lastHeight = 0
|
private lastHeight = 0
|
||||||
private requestedOptions = new Set<number>()
|
private requestedOptions = new Set<number>()
|
||||||
@ -59,33 +73,10 @@ export class TelnetSession extends BaseSession {
|
|||||||
) {
|
) {
|
||||||
super(injector.get(LogService).create(`telnet-${profile.options.host}-${profile.options.port}`))
|
super(injector.get(LogService).create(`telnet-${profile.options.host}-${profile.options.port}`))
|
||||||
this.streamProcessor = new TerminalStreamProcessor(profile.options)
|
this.streamProcessor = new TerminalStreamProcessor(profile.options)
|
||||||
this.streamProcessor.outputToSession$.subscribe(data => {
|
this.middleware.push(this.streamProcessor)
|
||||||
this.socket.write(this.unescapeFF(data))
|
|
||||||
})
|
|
||||||
this.streamProcessor.outputToTerminal$.subscribe(data => {
|
|
||||||
this.emitOutput(data)
|
|
||||||
})
|
|
||||||
this.setLoginScriptsOptions(profile.options)
|
this.setLoginScriptsOptions(profile.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
unescapeFF (data: Buffer): Buffer {
|
|
||||||
if (!this.telnetProtocol) {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
const result: Buffer[] = []
|
|
||||||
while (data.includes(0xff)) {
|
|
||||||
const pos = data.indexOf(0xff)
|
|
||||||
|
|
||||||
result.push(data.slice(0, pos))
|
|
||||||
result.push(Buffer.from([0xff, 0xff]))
|
|
||||||
|
|
||||||
data = data.slice(pos + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
result.push(data)
|
|
||||||
return Buffer.concat(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
async start (): Promise<void> {
|
async start (): Promise<void> {
|
||||||
this.socket = new Socket()
|
this.socket = new Socket()
|
||||||
this.emitServiceMessage(`Connecting to ${this.profile.options.host}`)
|
this.emitServiceMessage(`Connecting to ${this.profile.options.host}`)
|
||||||
@ -124,6 +115,7 @@ export class TelnetSession extends BaseSession {
|
|||||||
onData (data: Buffer): void {
|
onData (data: Buffer): void {
|
||||||
if (!this.telnetProtocol && data[0] === TelnetCommands.IAC) {
|
if (!this.telnetProtocol && data[0] === TelnetCommands.IAC) {
|
||||||
this.telnetProtocol = true
|
this.telnetProtocol = true
|
||||||
|
this.middleware.push(new UnescapeFFMiddleware())
|
||||||
this.requestOption(TelnetCommands.DO, TelnetOptions.SUPPRESS_GO_AHEAD)
|
this.requestOption(TelnetCommands.DO, TelnetOptions.SUPPRESS_GO_AHEAD)
|
||||||
this.emitTelnet(TelnetCommands.WILL, TelnetOptions.TERMINAL_TYPE)
|
this.emitTelnet(TelnetCommands.WILL, TelnetOptions.TERMINAL_TYPE)
|
||||||
this.emitTelnet(TelnetCommands.WILL, TelnetOptions.NEGO_WINDOW_SIZE)
|
this.emitTelnet(TelnetCommands.WILL, TelnetOptions.NEGO_WINDOW_SIZE)
|
||||||
@ -131,7 +123,7 @@ export class TelnetSession extends BaseSession {
|
|||||||
if (this.telnetProtocol) {
|
if (this.telnetProtocol) {
|
||||||
data = this.processTelnetProtocol(data)
|
data = this.processTelnetProtocol(data)
|
||||||
}
|
}
|
||||||
this.streamProcessor.feedFromSession(data)
|
this.emitOutput(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
emitTelnet (command: TelnetCommands, option: TelnetOptions): void {
|
emitTelnet (command: TelnetCommands, option: TelnetOptions): void {
|
||||||
@ -190,7 +182,7 @@ export class TelnetSession extends BaseSession {
|
|||||||
this.emitTelnet(TelnetCommands.WILL, option)
|
this.emitTelnet(TelnetCommands.WILL, option)
|
||||||
this.emitSize()
|
this.emitSize()
|
||||||
} else if (option === TelnetOptions.ECHO) {
|
} else if (option === TelnetOptions.ECHO) {
|
||||||
this.echoEnabled = true
|
this.streamProcessor.forceEcho = true
|
||||||
this.emitTelnet(TelnetCommands.WILL, option)
|
this.emitTelnet(TelnetCommands.WILL, option)
|
||||||
} else if (option === TelnetOptions.TERMINAL_TYPE) {
|
} else if (option === TelnetOptions.TERMINAL_TYPE) {
|
||||||
this.emitTelnet(TelnetCommands.WILL, option)
|
this.emitTelnet(TelnetCommands.WILL, option)
|
||||||
@ -201,7 +193,7 @@ export class TelnetSession extends BaseSession {
|
|||||||
}
|
}
|
||||||
if (command === TelnetCommands.DONT) {
|
if (command === TelnetCommands.DONT) {
|
||||||
if (option === TelnetOptions.ECHO) {
|
if (option === TelnetOptions.ECHO) {
|
||||||
this.echoEnabled = false
|
this.streamProcessor.forceEcho = false
|
||||||
this.emitTelnet(TelnetCommands.WONT, option)
|
this.emitTelnet(TelnetCommands.WONT, option)
|
||||||
} else {
|
} else {
|
||||||
this.logger.debug('(!) Unhandled option')
|
this.logger.debug('(!) Unhandled option')
|
||||||
@ -249,10 +241,7 @@ export class TelnetSession extends BaseSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
write (data: Buffer): void {
|
write (data: Buffer): void {
|
||||||
if (this.echoEnabled) {
|
this.socket.write(data)
|
||||||
this.emitOutput(data)
|
|
||||||
}
|
|
||||||
this.streamProcessor.feedFromTerminal(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kill (_signal?: string): void {
|
kill (_signal?: string): void {
|
||||||
|
@ -389,7 +389,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
if (!(data instanceof Buffer)) {
|
if (!(data instanceof Buffer)) {
|
||||||
data = Buffer.from(data, 'utf-8')
|
data = Buffer.from(data, 'utf-8')
|
||||||
}
|
}
|
||||||
this.session?.write(data)
|
this.session?.feedFromTerminal(data)
|
||||||
if (this.config.store.terminal.scrollOnInput) {
|
if (this.config.store.terminal.scrollOnInput) {
|
||||||
this.frontend?.scrollToBottom()
|
this.frontend?.scrollToBottom()
|
||||||
}
|
}
|
||||||
|
101
tabby-terminal/src/api/middleware.ts
Normal file
101
tabby-terminal/src/api/middleware.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { Subject, Observable } from 'rxjs'
|
||||||
|
import { SubscriptionContainer } from 'tabby-core'
|
||||||
|
|
||||||
|
export class SessionMiddleware {
|
||||||
|
get outputToSession$ (): Observable<Buffer> { return this.outputToSession }
|
||||||
|
get outputToTerminal$ (): Observable<Buffer> { return this.outputToTerminal }
|
||||||
|
|
||||||
|
protected outputToSession = new Subject<Buffer>()
|
||||||
|
protected outputToTerminal = new Subject<Buffer>()
|
||||||
|
|
||||||
|
feedFromSession (data: Buffer): void {
|
||||||
|
this.outputToTerminal.next(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
feedFromTerminal (data: Buffer): void {
|
||||||
|
this.outputToSession.next(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
close (): void {
|
||||||
|
this.outputToSession.complete()
|
||||||
|
this.outputToTerminal.complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SesssionMiddlewareStack extends SessionMiddleware {
|
||||||
|
private stack: SessionMiddleware[] = []
|
||||||
|
private subs = new SubscriptionContainer()
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
super()
|
||||||
|
this.push(new SessionMiddleware())
|
||||||
|
}
|
||||||
|
|
||||||
|
push (middleware: SessionMiddleware): void {
|
||||||
|
this.stack.push(middleware)
|
||||||
|
this.relink()
|
||||||
|
}
|
||||||
|
|
||||||
|
unshift (middleware: SessionMiddleware): void {
|
||||||
|
this.stack.unshift(middleware)
|
||||||
|
this.relink()
|
||||||
|
}
|
||||||
|
|
||||||
|
remove (middleware: SessionMiddleware): void {
|
||||||
|
this.stack = this.stack.filter(m => m !== middleware)
|
||||||
|
this.relink()
|
||||||
|
}
|
||||||
|
|
||||||
|
replace (middleware: SessionMiddleware, newMiddleware: SessionMiddleware): void {
|
||||||
|
const index = this.stack.indexOf(middleware)
|
||||||
|
if (index >= 0) {
|
||||||
|
this.stack[index].close()
|
||||||
|
this.stack[index] = newMiddleware
|
||||||
|
} else {
|
||||||
|
this.stack.push(newMiddleware)
|
||||||
|
}
|
||||||
|
this.relink()
|
||||||
|
}
|
||||||
|
|
||||||
|
feedFromSession (data: Buffer): void {
|
||||||
|
this.stack[0].feedFromSession(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
feedFromTerminal (data: Buffer): void {
|
||||||
|
this.stack[this.stack.length - 1].feedFromTerminal(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
close (): void {
|
||||||
|
for (const m of this.stack) {
|
||||||
|
m.close()
|
||||||
|
}
|
||||||
|
this.subs.cancelAll()
|
||||||
|
super.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
private relink () {
|
||||||
|
this.subs.cancelAll()
|
||||||
|
|
||||||
|
for (let i = 0; i < this.stack.length - 1; i++) {
|
||||||
|
this.subs.subscribe(
|
||||||
|
this.stack[i].outputToTerminal$,
|
||||||
|
x => this.stack[i + 1].feedFromSession(x)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.subs.subscribe(
|
||||||
|
this.stack[this.stack.length - 1].outputToTerminal$,
|
||||||
|
x => this.outputToTerminal.next(x),
|
||||||
|
)
|
||||||
|
|
||||||
|
for (let i = this.stack.length - 2; i >= 0; i--) {
|
||||||
|
this.subs.subscribe(
|
||||||
|
this.stack[i + 1].outputToSession$,
|
||||||
|
x => this.stack[i].feedFromTerminal(x)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.subs.subscribe(
|
||||||
|
this.stack[0].outputToSession$,
|
||||||
|
x => this.outputToSession.next(x),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
import { Component, Input } from '@angular/core'
|
import { Component, Input } from '@angular/core'
|
||||||
|
|
||||||
import { PlatformService } from 'tabby-core'
|
import { PlatformService } from 'tabby-core'
|
||||||
import { LoginScript, LoginScriptsOptions } from '../api/loginScriptProcessing'
|
import { LoginScript, LoginScriptsOptions } from '../middleware/loginScriptProcessing'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component, Input } from '@angular/core'
|
import { Component, Input } from '@angular/core'
|
||||||
import { StreamProcessingOptions } from '../api/streamProcessing'
|
import { StreamProcessingOptions } from '../middleware/streamProcessing'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -32,7 +32,7 @@ export class ZModemDecorator extends TerminalDecorator {
|
|||||||
terminal.write(data)
|
terminal.write(data)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sender: data => terminal.session!.write(Buffer.from(data)),
|
sender: data => terminal.session!.feedFromTerminal(Buffer.from(data)),
|
||||||
on_detect: async detection => {
|
on_detect: async detection => {
|
||||||
try {
|
try {
|
||||||
terminal.enablePassthrough = false
|
terminal.enablePassthrough = false
|
||||||
|
@ -87,8 +87,9 @@ export { TerminalFrontendService, TerminalDecorator, TerminalContextMenuItemProv
|
|||||||
export { Frontend, XTermFrontend, XTermWebGLFrontend }
|
export { Frontend, XTermFrontend, XTermWebGLFrontend }
|
||||||
export { BaseTerminalTabComponent } from './api/baseTerminalTab.component'
|
export { BaseTerminalTabComponent } from './api/baseTerminalTab.component'
|
||||||
export * from './api/interfaces'
|
export * from './api/interfaces'
|
||||||
export * from './api/streamProcessing'
|
export * from './middleware/streamProcessing'
|
||||||
export * from './api/loginScriptProcessing'
|
export * from './middleware/loginScriptProcessing'
|
||||||
export * from './api/osc1337Processing'
|
export * from './middleware/oscProcessing'
|
||||||
|
export * from './api/middleware'
|
||||||
export * from './session'
|
export * from './session'
|
||||||
export { LoginScriptsSettingsComponent, StreamProcessingSettingsComponent }
|
export { LoginScriptsSettingsComponent, StreamProcessingSettingsComponent }
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import deepClone from 'clone-deep'
|
import deepClone from 'clone-deep'
|
||||||
import { Subject, Observable } from 'rxjs'
|
|
||||||
import { Logger } from 'tabby-core'
|
import { Logger } from 'tabby-core'
|
||||||
|
import { SessionMiddleware } from '../api/middleware'
|
||||||
|
|
||||||
export interface LoginScript {
|
export interface LoginScript {
|
||||||
expect: string
|
expect: string
|
||||||
@ -13,10 +13,7 @@ export interface LoginScriptsOptions {
|
|||||||
scripts?: LoginScript[]
|
scripts?: LoginScript[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LoginScriptProcessor {
|
export class LoginScriptProcessor extends SessionMiddleware {
|
||||||
get outputToSession$ (): Observable<Buffer> { return this.outputToSession }
|
|
||||||
|
|
||||||
private outputToSession = new Subject<Buffer>()
|
|
||||||
private remainingScripts: LoginScript[] = []
|
private remainingScripts: LoginScript[] = []
|
||||||
|
|
||||||
private escapeSeqMap = {
|
private escapeSeqMap = {
|
||||||
@ -34,6 +31,7 @@ export class LoginScriptProcessor {
|
|||||||
private logger: Logger,
|
private logger: Logger,
|
||||||
options: LoginScriptsOptions
|
options: LoginScriptsOptions
|
||||||
) {
|
) {
|
||||||
|
super()
|
||||||
this.remainingScripts = deepClone(options.scripts ?? [])
|
this.remainingScripts = deepClone(options.scripts ?? [])
|
||||||
for (const script of this.remainingScripts) {
|
for (const script of this.remainingScripts) {
|
||||||
if (!script.isRegex) {
|
if (!script.isRegex) {
|
||||||
@ -43,10 +41,9 @@ export class LoginScriptProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
feedFromSession (data: Buffer): boolean {
|
feedFromSession (data: Buffer): void {
|
||||||
const dataString = data.toString()
|
const dataString = data.toString()
|
||||||
|
|
||||||
let found = false
|
|
||||||
for (const script of this.remainingScripts) {
|
for (const script of this.remainingScripts) {
|
||||||
if (!script.expect) {
|
if (!script.expect) {
|
||||||
continue
|
continue
|
||||||
@ -60,14 +57,12 @@ export class LoginScriptProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
found = true
|
|
||||||
this.logger.info('Executing script:', script)
|
this.logger.info('Executing script:', script)
|
||||||
this.outputToSession.next(Buffer.from(script.send + '\n'))
|
this.outputToSession.next(Buffer.from(script.send + '\n'))
|
||||||
this.remainingScripts = this.remainingScripts.filter(x => x !== script)
|
this.remainingScripts = this.remainingScripts.filter(x => x !== script)
|
||||||
} else {
|
} else {
|
||||||
if (script.optional) {
|
if (script.optional) {
|
||||||
this.logger.debug('Skip optional script: ' + script.expect)
|
this.logger.debug('Skip optional script: ' + script.expect)
|
||||||
found = true
|
|
||||||
this.remainingScripts = this.remainingScripts.filter(x => x !== script)
|
this.remainingScripts = this.remainingScripts.filter(x => x !== script)
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
@ -75,7 +70,7 @@ export class LoginScriptProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return found
|
super.feedFromSession(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
close (): void {
|
close (): void {
|
@ -1,17 +1,18 @@
|
|||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import { Subject, Observable } from 'rxjs'
|
import { Subject, Observable } from 'rxjs'
|
||||||
|
import { SessionMiddleware } from '../api/middleware'
|
||||||
|
|
||||||
const OSCPrefix = Buffer.from('\x1b]')
|
const OSCPrefix = Buffer.from('\x1b]')
|
||||||
const OSCSuffix = Buffer.from('\x07')
|
const OSCSuffix = Buffer.from('\x07')
|
||||||
|
|
||||||
export class OSCProcessor {
|
export class OSCProcessor extends SessionMiddleware {
|
||||||
get cwdReported$ (): Observable<string> { return this.cwdReported }
|
get cwdReported$ (): Observable<string> { return this.cwdReported }
|
||||||
get copyRequested$ (): Observable<string> { return this.copyRequested }
|
get copyRequested$ (): Observable<string> { return this.copyRequested }
|
||||||
|
|
||||||
private cwdReported = new Subject<string>()
|
private cwdReported = new Subject<string>()
|
||||||
private copyRequested = new Subject<string>()
|
private copyRequested = new Subject<string>()
|
||||||
|
|
||||||
process (data: Buffer): Buffer {
|
feedFromSession (data: Buffer): void {
|
||||||
let startIndex = 0
|
let startIndex = 0
|
||||||
while (data.includes(OSCPrefix, startIndex) && data.includes(OSCSuffix, startIndex)) {
|
while (data.includes(OSCPrefix, startIndex) && data.includes(OSCSuffix, startIndex)) {
|
||||||
const params = data.subarray(data.indexOf(OSCPrefix, startIndex) + OSCPrefix.length)
|
const params = data.subarray(data.indexOf(OSCPrefix, startIndex) + OSCPrefix.length)
|
||||||
@ -42,10 +43,12 @@ export class OSCProcessor {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return data
|
super.feedFromSession(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
close (): void {
|
close (): void {
|
||||||
this.cwdReported.complete()
|
this.cwdReported.complete()
|
||||||
|
this.copyRequested.complete()
|
||||||
|
super.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,9 +2,10 @@ import hexdump from 'hexer'
|
|||||||
import bufferReplace from 'buffer-replace'
|
import bufferReplace from 'buffer-replace'
|
||||||
import colors from 'ansi-colors'
|
import colors from 'ansi-colors'
|
||||||
import binstring from 'binstring'
|
import binstring from 'binstring'
|
||||||
import { Subject, Observable, interval, debounce } from 'rxjs'
|
import { interval, debounce } from 'rxjs'
|
||||||
import { PassThrough, Readable, Writable } from 'stream'
|
import { PassThrough, Readable, Writable } from 'stream'
|
||||||
import { ReadLine, createInterface as createReadline, clearLine } from 'readline'
|
import { ReadLine, createInterface as createReadline, clearLine } from 'readline'
|
||||||
|
import { SessionMiddleware } from '../api/middleware'
|
||||||
|
|
||||||
export type InputMode = null | 'local-echo' | 'readline' | 'readline-hex'
|
export type InputMode = null | 'local-echo' | 'readline' | 'readline-hex'
|
||||||
export type OutputMode = null | 'hex'
|
export type OutputMode = null | 'hex'
|
||||||
@ -17,13 +18,8 @@ export interface StreamProcessingOptions {
|
|||||||
outputNewlines?: NewlineMode
|
outputNewlines?: NewlineMode
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TerminalStreamProcessor {
|
export class TerminalStreamProcessor extends SessionMiddleware {
|
||||||
get outputToSession$ (): Observable<Buffer> { return this.outputToSession }
|
forceEcho = false
|
||||||
get outputToTerminal$ (): Observable<Buffer> { return this.outputToTerminal }
|
|
||||||
|
|
||||||
protected outputToSession = new Subject<Buffer>()
|
|
||||||
protected outputToTerminal = new Subject<Buffer>()
|
|
||||||
|
|
||||||
private inputReadline: ReadLine
|
private inputReadline: ReadLine
|
||||||
private inputPromptVisible = false
|
private inputPromptVisible = false
|
||||||
private inputReadlineInStream: Readable & Writable
|
private inputReadlineInStream: Readable & Writable
|
||||||
@ -31,6 +27,7 @@ export class TerminalStreamProcessor {
|
|||||||
private started = false
|
private started = false
|
||||||
|
|
||||||
constructor (private options: StreamProcessingOptions) {
|
constructor (private options: StreamProcessingOptions) {
|
||||||
|
super()
|
||||||
this.inputReadlineInStream = new PassThrough()
|
this.inputReadlineInStream = new PassThrough()
|
||||||
this.inputReadlineOutStream = new PassThrough()
|
this.inputReadlineOutStream = new PassThrough()
|
||||||
this.inputReadlineOutStream.on('data', data => {
|
this.inputReadlineOutStream.on('data', data => {
|
||||||
@ -85,7 +82,7 @@ export class TerminalStreamProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
feedFromTerminal (data: Buffer): void {
|
feedFromTerminal (data: Buffer): void {
|
||||||
if (this.options.inputMode === 'local-echo') {
|
if (this.options.inputMode === 'local-echo' || this.forceEcho) {
|
||||||
this.outputToTerminal.next(this.replaceNewlines(data, 'crlf'))
|
this.outputToTerminal.next(this.replaceNewlines(data, 'crlf'))
|
||||||
}
|
}
|
||||||
if (this.options.inputMode?.startsWith('readline')) {
|
if (this.options.inputMode?.startsWith('readline')) {
|
||||||
@ -103,8 +100,7 @@ export class TerminalStreamProcessor {
|
|||||||
|
|
||||||
close (): void {
|
close (): void {
|
||||||
this.inputReadline.close()
|
this.inputReadline.close()
|
||||||
this.outputToSession.complete()
|
super.close()
|
||||||
this.outputToTerminal.complete()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onTerminalInput (data: Buffer) {
|
private onTerminalInput (data: Buffer) {
|
@ -1,7 +1,8 @@
|
|||||||
import { Observable, Subject } from 'rxjs'
|
import { Observable, Subject } from 'rxjs'
|
||||||
import { Logger } from 'tabby-core'
|
import { Logger } from 'tabby-core'
|
||||||
import { LoginScriptProcessor, LoginScriptsOptions } from './api/loginScriptProcessing'
|
import { LoginScriptProcessor, LoginScriptsOptions } from './middleware/loginScriptProcessing'
|
||||||
import { OSCProcessor } from './api/osc1337Processing'
|
import { OSCProcessor } from './middleware/oscProcessing'
|
||||||
|
import { SesssionMiddlewareStack } from './api/middleware'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A session object for a [[BaseTerminalTabComponent]]
|
* A session object for a [[BaseTerminalTabComponent]]
|
||||||
@ -11,6 +12,7 @@ export abstract class BaseSession {
|
|||||||
open: boolean
|
open: boolean
|
||||||
truePID?: number
|
truePID?: number
|
||||||
oscProcessor = new OSCProcessor()
|
oscProcessor = new OSCProcessor()
|
||||||
|
protected readonly middleware = new SesssionMiddlewareStack()
|
||||||
protected output = new Subject<string>()
|
protected output = new Subject<string>()
|
||||||
protected binaryOutput = new Subject<Buffer>()
|
protected binaryOutput = new Subject<Buffer>()
|
||||||
protected closed = new Subject<void>()
|
protected closed = new Subject<void>()
|
||||||
@ -26,20 +28,29 @@ export abstract class BaseSession {
|
|||||||
get destroyed$ (): Observable<void> { return this.destroyed }
|
get destroyed$ (): Observable<void> { return this.destroyed }
|
||||||
|
|
||||||
constructor (protected logger: Logger) {
|
constructor (protected logger: Logger) {
|
||||||
|
this.middleware.push(this.oscProcessor)
|
||||||
this.oscProcessor.cwdReported$.subscribe(cwd => {
|
this.oscProcessor.cwdReported$.subscribe(cwd => {
|
||||||
this.reportedCWD = cwd
|
this.reportedCWD = cwd
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.middleware.outputToTerminal$.subscribe(data => {
|
||||||
|
if (!this.initialDataBufferReleased) {
|
||||||
|
this.initialDataBuffer = Buffer.concat([this.initialDataBuffer, data])
|
||||||
|
} else {
|
||||||
|
this.output.next(data.toString())
|
||||||
|
this.binaryOutput.next(data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.middleware.outputToSession$.subscribe(data => this.write(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
emitOutput (data: Buffer): void {
|
feedFromTerminal (data: Buffer): void {
|
||||||
data = this.oscProcessor.process(data)
|
this.middleware.feedFromTerminal(data)
|
||||||
if (!this.initialDataBufferReleased) {
|
}
|
||||||
this.initialDataBuffer = Buffer.concat([this.initialDataBuffer, data])
|
|
||||||
} else {
|
protected emitOutput (data: Buffer): void {
|
||||||
this.output.next(data.toString())
|
this.middleware.feedFromSession(data)
|
||||||
this.binaryOutput.next(data)
|
|
||||||
this.loginScriptProcessor?.feedFromSession(data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
releaseInitialDataBuffer (): void {
|
releaseInitialDataBuffer (): void {
|
||||||
@ -50,21 +61,24 @@ export abstract class BaseSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLoginScriptsOptions (options: LoginScriptsOptions): void {
|
setLoginScriptsOptions (options: LoginScriptsOptions): void {
|
||||||
this.loginScriptProcessor?.close()
|
const newProcessor = new LoginScriptProcessor(this.logger, options)
|
||||||
this.loginScriptProcessor = new LoginScriptProcessor(this.logger, options)
|
if (this.loginScriptProcessor) {
|
||||||
this.loginScriptProcessor.outputToSession$.subscribe(data => this.write(data))
|
this.middleware.replace(this.loginScriptProcessor, newProcessor)
|
||||||
|
} else {
|
||||||
|
this.middleware.push(newProcessor)
|
||||||
|
}
|
||||||
|
this.loginScriptProcessor = newProcessor
|
||||||
}
|
}
|
||||||
|
|
||||||
async destroy (): Promise<void> {
|
async destroy (): Promise<void> {
|
||||||
if (this.open) {
|
if (this.open) {
|
||||||
this.logger.info('Destroying')
|
this.logger.info('Destroying')
|
||||||
this.open = false
|
this.open = false
|
||||||
this.loginScriptProcessor?.close()
|
|
||||||
this.closed.next()
|
this.closed.next()
|
||||||
this.destroyed.next()
|
this.destroyed.next()
|
||||||
await this.gracefullyKillProcess()
|
await this.gracefullyKillProcess()
|
||||||
}
|
}
|
||||||
this.oscProcessor.close()
|
this.middleware.close()
|
||||||
this.closed.complete()
|
this.closed.complete()
|
||||||
this.destroyed.complete()
|
this.destroyed.complete()
|
||||||
this.output.complete()
|
this.output.complete()
|
||||||
|
Loading…
Reference in New Issue
Block a user