-
Notifications
You must be signed in to change notification settings - Fork 5
/
actions.ts
147 lines (114 loc) 路 4.42 KB
/
actions.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import msgpack5 from "msgpack5"
import { ThunkAction } from "redux-thunk"
import { AnyAction } from "redux";
import { TERMINAL_SESSION, TERMINAL_SESSION_DISCONNECTED, TERMINAL_NEW_TAB, AppState, AppConfig, TerminalActions, TERMINAL_NEW_TAB_CREATED } from "./types"
import fromEmitter, { $terminated } from "../utils/fromEmitter";
import { TerminalNewTabMessage, TerminalOutputMessage, TerminalInputMessage, TerminalResizeMessage, TerminalNewTabCreatedMessage } from "./models";
import MicroEmitter from "../utils/MicroEmitter";
let terminal: WebSocket | undefined;
let terminalEvenTarget = new MicroEmitter();
const terminalSessionStarted = (): TerminalActions => ({
type: TERMINAL_SESSION
})
const terminalSessionDisconnected = (): TerminalActions => ({
type: TERMINAL_SESSION_DISCONNECTED
})
const terminalNewTab = (): TerminalActions => ({
type: TERMINAL_NEW_TAB,
})
const terminalNewTabCreated = (msg: TerminalNewTabCreatedMessage): TerminalActions => ({
type: TERMINAL_NEW_TAB_CREATED,
payload: { id: msg.id },
})
const startTerminal = (): ThunkAction<Promise<void>, AppState, AppConfig, AnyAction> =>
async (dispatch) => {
if (terminal) return
terminal = await dispatch(connectToRemoteTerminal())
const dataSource = fromEmitter<MessageEvent>(terminal)
dispatch(createNewTab())
const msgpack = msgpack5()
const decoder = new TextDecoder()
for await (let message of dataSource) {
if (message === $terminated) break
const properties = msgpack.decode(Buffer.from(message.data))
const type: number = properties[0]
switch (type) {
case 0:
case 1:
case 3:
break
case 2:
const id = properties[1]
const body = decoder.decode(properties[2])
const msg = new TerminalOutputMessage(id, body)
terminalEvenTarget.emit("message", msg)
break
case 5:
const newTabMsg = new TerminalNewTabCreatedMessage(properties[1])
dispatch(terminalNewTabCreated(newTabMsg))
break
default:
throw new Error("unknown type")
}
}
terminalEvenTarget.emit("close", {});
dispatch(terminalSessionDisconnected())
}
const connectToRemoteTerminal = (): ThunkAction<Promise<WebSocket>, AppState, AppConfig, AnyAction> =>
async (dispatch, _, config) => {
const socket = new WebSocket(config.socketUrl)
socket.binaryType = "arraybuffer"
await isSocketReady(socket);
dispatch(terminalSessionStarted())
return socket;
}
const createNewTab = (): ThunkAction<void, AppState, AppConfig, AnyAction> =>
(dispatch) => {
const msg = new TerminalNewTabMessage()
writeToTerminal(msg)
dispatch(terminalNewTab())
}
async function writeToTerminal(msg: TerminalResizeMessage | TerminalInputMessage | TerminalNewTabMessage) {
const msgpack = msgpack5()
if (!terminal) return
const payload = msgpack.encode(msg.serialize())
terminal.send(payload.slice())
}
const isSocketReady = (socket: WebSocket) =>
new Promise((res, rej) => {
const isOpen = () => {
socket.removeEventListener("open", isOpen)
socket.removeEventListener("error", hasError)
res()
};
const hasError = () => {
socket.removeEventListener("open", isOpen)
socket.removeEventListener("error", hasError)
rej()
}
socket.addEventListener("open", isOpen)
socket.addEventListener("error", hasError)
})
const getTabStdoutStream = (id: number) =>
(async function* () {
const source = fromEmitter<TerminalOutputMessage>(terminalEvenTarget)
for await (let message of source) {
if (message === $terminated) break
if (message.id == id) yield message.payload
}
})()
const writeStdin = (id: number, input: string) => {
const msg = new TerminalInputMessage(id, input)
writeToTerminal(msg)
}
const resizeTerminal = (id: number, cols: number, rows: number) => {
const msg = new TerminalResizeMessage(id, cols, rows)
writeToTerminal(msg)
}
export {
startTerminal,
getTabStdoutStream,
createNewTab,
writeStdin,
resizeTerminal,
}