-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
159 lines (149 loc) · 5.57 KB
/
index.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
148
149
150
151
152
153
154
155
156
157
158
159
import { launch as chrome, LaunchedChrome } from 'chrome-launcher';
import express from 'express';
import { promisify } from 'util';
import expressWs from 'express-ws';
import WS from 'ws';
const app = expressWs(express()).app;
const node_functions: {
[index: string]: CallableFunction,
/** Logs error to console */
error?: (...text: any[]) => void;
} = {};
/** Used to exchange with the frontend */
interface Message {
reply?: any;
fid?: string;
func?: string;
args?: any[];
endInit?: boolean;
expose?: boolean;
}
interface StartParams {
/** Start page, e.g. index.html */
startPage?: string,
/** Directory where frontend resides */
webdir?: string,
closeOnExit?: boolean,
serverPort?: number,
/** Port where your frontend server is on. Useful when your HTTP and Websocket server is not the same thing. See my F0Talk repo for example of usage */
clientPort?: number,
/** Chromium should be found automatically, but if not, you can pass the path here */
chromiumPath?: string
}
class FWGUI {
[index: string]: any;
static #Waiting = class Waiting {};
#waitingForReply: { [index: string]: any } = {};
#ws?: WS;
#endInit?: boolean;
#serve(dir: string, serverPort: number) {
return new Promise<void>(resolve => {
app.use(express.static(dir));
app.use(express.static('node_modules/fwgui/frontend'));
app.ws('/', async ws => {
this.#ws = ws;
ws.on('message', (_msg: any) => {
try {
const msg: Message = JSON.parse(_msg);
if (msg.endInit) {
this.#endInit = true;
resolve();
}
else if (msg.expose) {
this[msg.func] = async (...args: any[]) => {
let fid = `${Date.now().toString(16)}${Math.random().toString(16)}`;
ws.send(JSON.stringify(<Message>{ func: msg.func, args, fid }));
this.#waitingForReply[fid] = new FWGUI.#Waiting();
while (this.#waitingForReply[fid] instanceof FWGUI.#Waiting)
await promisify(setTimeout)(5);
let rs = this.#waitingForReply[fid];
delete this.#waitingForReply[fid];
return rs;
};
}
else if ('reply' in msg)
this.#waitingForReply[msg.fid] = msg.reply;
else
(async () => ws.send(JSON.stringify(<Message>{
reply: await (node_functions[msg.func])(...msg.args) || null,
fid: msg.fid
})))();
}
catch (e) {
node_functions.error('Serverside error: ' + e.stack);
}
});
ws.on('close', () => this.#ws = null);
});
let server = app.listen(serverPort);
process.addListener('exit', () => server.close());
this.expose(function error(...text: any[]) { console.log('\x1b[31m', ...text); });
})
}
constructor() {}
async start({ startPage = '', webdir = 'wgui', closeOnExit = true, serverPort = 8889, clientPort, chromiumPath }: StartParams): Promise<boolean | LaunchedChrome> {
if (!webdir)
return false;
if (!clientPort)
clientPort = serverPort;
let sv = this.#serve(webdir, serverPort);
// setting up websocket server and running chrome
const url = `http://localhost:${clientPort}/` + startPage;
let success: LaunchedChrome | boolean = false;
await chrome({
chromeFlags: [`--app=${url}`, '--window-size=1280,720'],
chromePath: chromiumPath || undefined,
ignoreDefaultFlags: true
}).then(instance => {
if (closeOnExit) {
process.addListener('exit', () => instance ? instance.kill() : null);
instance.process.addListener('exit', () => process.exit())
}
success = instance;
return sv;
});
return success;
}
/**
* Expose backend function to frontend
* @param funcname JS function or frontend alias
* @param func JS function, if alias was passed before
*/
async expose(funcname: CallableFunction | string, func: CallableFunction | null = null) {
if (!funcname)
return;
if (!func) {
func = funcname as CallableFunction;
funcname = (funcname as CallableFunction).name;
}
node_functions[funcname as string] = func;
while (!this.#ws)
await promisify(setTimeout)(50);
this.#ws.send(JSON.stringify(<Message>{
func: funcname,
expose: true
}));
}
/** Tell the frontend that all functions are defined */
async endExpose() {
while (!this.#ws || !this.#endInit)
await promisify(setTimeout)(50);
this.#ws.send(JSON.stringify(<Message>{
endExpose: true
}));
}
/**
* AKA publish (the frontend is the subscriber)
* @param event Name
* @param args The arguments
*/
async emit(event: string, ...args: any[]) {
if (!this.#ws)
return;
this.#ws.send(JSON.stringify(<Message>{
event,
args
}));
}
}
export default new FWGUI();