@swf2es/desktop 0.1.22 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main/connections.d.ts +14 -2
- package/dist/main/connections.d.ts.map +1 -1
- package/dist/main/connections.js +44 -8
- package/dist/main/connections.js.map +1 -1
- package/dist/main/network.d.ts.map +1 -1
- package/dist/main/network.js +2 -1
- package/dist/main/network.js.map +1 -1
- package/dist/main/proxy.d.ts +16 -1
- package/dist/main/proxy.d.ts.map +1 -1
- package/dist/main/proxy.js +67 -12
- package/dist/main/proxy.js.map +1 -1
- package/dist/main/relay.d.ts +55 -0
- package/dist/main/relay.d.ts.map +1 -0
- package/dist/main/relay.js +140 -0
- package/dist/main/relay.js.map +1 -0
- package/dist/main/sockets.d.ts.map +1 -1
- package/dist/main/sockets.js +21 -5
- package/dist/main/sockets.js.map +1 -1
- package/dist/main/websockets.d.ts.map +1 -1
- package/dist/main/websockets.js +14 -4
- package/dist/main/websockets.js.map +1 -1
- package/dist/preload/preload.cjs +7 -6
- package/dist/preload/preload.cjs.map +1 -1
- package/dist/renderer/sockets.d.ts.map +1 -1
- package/dist/renderer/sockets.js +31 -21
- package/dist/renderer/sockets.js.map +1 -1
- package/dist/renderer/websockets.d.ts.map +1 -1
- package/dist/renderer/websockets.js +29 -19
- package/dist/renderer/websockets.js.map +1 -1
- package/dist/shared/api.d.ts +12 -4
- package/dist/shared/api.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/main/connections.ts +55 -10
- package/src/main/network.ts +3 -1
- package/src/main/proxy.ts +87 -14
- package/src/main/relay.ts +169 -0
- package/src/main/sockets.ts +49 -29
- package/src/main/websockets.ts +17 -4
- package/src/preload/preload.cts +10 -8
- package/src/renderer/sockets.ts +30 -21
- package/src/renderer/websockets.ts +28 -19
- package/src/shared/api.ts +13 -4
package/src/main/sockets.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
// flash.net.Socket's TCP connections, made here for the page, which has
|
|
2
2
|
// no network of its own beyond fetch, and carried over IPC. Each page's
|
|
3
|
-
// connections (connections.ts) close as it navigates, reloads or goes
|
|
3
|
+
// connections (connections.ts) close as it navigates, reloads or goes, and
|
|
4
|
+
// their events go to it gathered, a message a millisecond at most
|
|
5
|
+
// (relay.ts).
|
|
4
6
|
import { type IpcMainEvent, ipcMain, type WebContents } from "electron";
|
|
7
|
+
import type { SocketEvent } from "../shared/api.js";
|
|
5
8
|
import { type ConnectionOptions, Connections, validEndpoint } from "./connections.ts";
|
|
6
9
|
import { socketDataLine, socketLine } from "./netlog.ts";
|
|
10
|
+
import { Relay } from "./relay.ts";
|
|
11
|
+
|
|
12
|
+
const dataEvent = (bytes: Uint8Array): SocketEvent => ({ type: "data", bytes });
|
|
7
13
|
|
|
8
14
|
/**
|
|
9
15
|
* Carry socket messages from pages that `trusted` accepts, each connection
|
|
@@ -28,6 +34,11 @@ export function bridgeSockets(
|
|
|
28
34
|
return known;
|
|
29
35
|
}
|
|
30
36
|
|
|
37
|
+
const relay = new Relay<SocketEvent>((batch) => {
|
|
38
|
+
if (!contents.isDestroyed()) {
|
|
39
|
+
contents.send("socket:events", batch);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
31
42
|
const connections = new Connections((id, event) => {
|
|
32
43
|
if (log && event.type !== "data") {
|
|
33
44
|
log(socketLine(id, event.type === "error" ? `error: ${event.message}` : event.type));
|
|
@@ -35,52 +46,61 @@ export function bridgeSockets(
|
|
|
35
46
|
log(socketDataLine(id, false, event.bytes));
|
|
36
47
|
}
|
|
37
48
|
|
|
38
|
-
if (
|
|
39
|
-
|
|
49
|
+
if (event.type === "data") {
|
|
50
|
+
relay.bytes(id, event.bytes, dataEvent);
|
|
51
|
+
} else {
|
|
52
|
+
relay.push(id, event);
|
|
40
53
|
}
|
|
41
54
|
}, options);
|
|
42
55
|
pages.set(contents, connections);
|
|
43
56
|
contents.on("did-start-navigation", (details) => {
|
|
44
57
|
if (details.isMainFrame && !details.isSameDocument) {
|
|
45
58
|
connections.closeAll();
|
|
59
|
+
relay.drop();
|
|
46
60
|
}
|
|
47
61
|
});
|
|
48
62
|
contents.once("destroyed", () => {
|
|
49
63
|
connections.closeAll();
|
|
64
|
+
relay.drop();
|
|
50
65
|
pages.delete(contents);
|
|
51
66
|
});
|
|
52
67
|
return connections;
|
|
53
68
|
};
|
|
54
69
|
|
|
55
|
-
ipcMain.on(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
70
|
+
ipcMain.on(
|
|
71
|
+
"socket:connect",
|
|
72
|
+
(event, id: unknown, host: unknown, port: unknown, asked: unknown) => {
|
|
73
|
+
if (!trusted(event) || typeof id !== "number") {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
59
76
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
connections.
|
|
63
|
-
|
|
64
|
-
|
|
77
|
+
// At once unless the SWF set tcpNoDelay to false (connections.ts).
|
|
78
|
+
const noDelay = asked !== false;
|
|
79
|
+
const connections = connectionsOf(event.sender);
|
|
80
|
+
if (!validEndpoint(host, port)) {
|
|
81
|
+
connections.refuse(id);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
65
84
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
85
|
+
const generation = connections.generation;
|
|
86
|
+
void permit(host, port as number)
|
|
87
|
+
.catch(() => null)
|
|
88
|
+
.then((allowed) => {
|
|
89
|
+
// The page that asked may have gone while the user was asked.
|
|
90
|
+
if (connections.generation !== generation) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
74
93
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
const via = allowed && options.proxy ? " via proxy" : "";
|
|
95
|
+
log?.(socketLine(id, `${allowed ? "connecting to" : "refused"} ${host}:${port}${via}`));
|
|
96
|
+
if (allowed) {
|
|
97
|
+
connections.connect(id, host, port, allowed.address, noDelay);
|
|
98
|
+
} else {
|
|
99
|
+
connections.refuse(id);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
);
|
|
84
104
|
|
|
85
105
|
ipcMain.on("socket:send", (event, id: unknown, bytes: unknown) => {
|
|
86
106
|
if (trusted(event) && typeof id === "number" && bytes instanceof Uint8Array) {
|
package/src/main/websockets.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
// air.net.WebSocket's connections, made here for the page, which has no
|
|
2
2
|
// network of its own, and carried over IPC as flash.net.Socket's are
|
|
3
3
|
// (sockets.ts). Each page's connections (websocket-connections.ts) close
|
|
4
|
-
// as it navigates, reloads or goes
|
|
4
|
+
// as it navigates, reloads or goes, and their events go to it gathered,
|
|
5
|
+
// a message a millisecond at most (relay.ts), each message an event of its
|
|
6
|
+
// own.
|
|
5
7
|
import { type IpcMainEvent, ipcMain, type WebContents } from "electron";
|
|
8
|
+
import type { WebSocketEvent } from "../shared/api.js";
|
|
6
9
|
import { webSocketDataLine, webSocketLine } from "./netlog.ts";
|
|
10
|
+
import { Relay } from "./relay.ts";
|
|
7
11
|
import {
|
|
8
12
|
type Opener,
|
|
9
13
|
validProtocols,
|
|
@@ -11,6 +15,10 @@ import {
|
|
|
11
15
|
WebSocketConnections,
|
|
12
16
|
} from "./websocket-connections.ts";
|
|
13
17
|
|
|
18
|
+
/** About what a message takes to send: a string's UTF-16 is two bytes a unit. */
|
|
19
|
+
const messageSize = (data: string | Uint8Array): number =>
|
|
20
|
+
typeof data === "string" ? data.length * 2 : data.length;
|
|
21
|
+
|
|
14
22
|
/**
|
|
15
23
|
* Carry WebSocket messages from pages that `trusted` accepts, each
|
|
16
24
|
* connection made only where `permit` allows its URL, with `open` if
|
|
@@ -35,6 +43,11 @@ export function bridgeWebSockets(
|
|
|
35
43
|
return known;
|
|
36
44
|
}
|
|
37
45
|
|
|
46
|
+
const relay = new Relay<WebSocketEvent>((batch) => {
|
|
47
|
+
if (!contents.isDestroyed()) {
|
|
48
|
+
contents.send("websocket:events", batch);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
38
51
|
const connections = new WebSocketConnections((id, event) => {
|
|
39
52
|
if (log) {
|
|
40
53
|
switch (event.type) {
|
|
@@ -56,18 +69,18 @@ export function bridgeWebSockets(
|
|
|
56
69
|
}
|
|
57
70
|
}
|
|
58
71
|
|
|
59
|
-
|
|
60
|
-
contents.send("websocket:event", id, event);
|
|
61
|
-
}
|
|
72
|
+
relay.push(id, event, event.type === "message" ? messageSize(event.data) : 0);
|
|
62
73
|
}, open);
|
|
63
74
|
pages.set(contents, connections);
|
|
64
75
|
contents.on("did-start-navigation", (details) => {
|
|
65
76
|
if (details.isMainFrame && !details.isSameDocument) {
|
|
66
77
|
connections.closeAll();
|
|
78
|
+
relay.drop();
|
|
67
79
|
}
|
|
68
80
|
});
|
|
69
81
|
contents.once("destroyed", () => {
|
|
70
82
|
connections.closeAll();
|
|
83
|
+
relay.drop();
|
|
71
84
|
pages.delete(contents);
|
|
72
85
|
});
|
|
73
86
|
return connections;
|
package/src/preload/preload.cts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// each checked again by the main process. A sandboxed preload is a
|
|
3
3
|
// CommonJS script that may require only Electron's renderer modules.
|
|
4
4
|
import type {
|
|
5
|
+
ConnectionEvents,
|
|
5
6
|
DesktopApi,
|
|
6
7
|
LibraryName,
|
|
7
8
|
NetworkResponse,
|
|
@@ -48,16 +49,17 @@ const api: DesktopApi = {
|
|
|
48
49
|
openDropped: (file) => ipcRenderer.send("desktop:open-path", webUtils.getPathForFile(file)),
|
|
49
50
|
chooseLibrary: (name: LibraryName) => ipcRenderer.send("desktop:choose-library", name),
|
|
50
51
|
sockets: {
|
|
51
|
-
connect: (host, port) => {
|
|
52
|
+
connect: (host, port, noDelay) => {
|
|
52
53
|
const id = nextSocket++;
|
|
53
|
-
|
|
54
|
+
const asked = typeof noDelay === "boolean" ? noDelay : undefined;
|
|
55
|
+
ipcRenderer.send("socket:connect", id, String(host), Number(port), asked);
|
|
54
56
|
return id;
|
|
55
57
|
},
|
|
56
58
|
send: (id, bytes) => ipcRenderer.send("socket:send", id, bytes),
|
|
57
59
|
close: (id) => ipcRenderer.send("socket:close", id),
|
|
58
|
-
|
|
59
|
-
ipcRenderer.on("socket:
|
|
60
|
-
listener(
|
|
60
|
+
onEvents: (listener) => {
|
|
61
|
+
ipcRenderer.on("socket:events", (_event, events: ConnectionEvents<SocketEvent>) =>
|
|
62
|
+
listener(events),
|
|
61
63
|
);
|
|
62
64
|
},
|
|
63
65
|
},
|
|
@@ -69,9 +71,9 @@ const api: DesktopApi = {
|
|
|
69
71
|
},
|
|
70
72
|
send: (id, data) => ipcRenderer.send("websocket:send", id, data),
|
|
71
73
|
close: (id, code) => ipcRenderer.send("websocket:close", id, code),
|
|
72
|
-
|
|
73
|
-
ipcRenderer.on("websocket:
|
|
74
|
-
listener(
|
|
74
|
+
onEvents: (listener) => {
|
|
75
|
+
ipcRenderer.on("websocket:events", (_event, events: ConnectionEvents<WebSocketEvent>) =>
|
|
76
|
+
listener(events),
|
|
75
77
|
);
|
|
76
78
|
},
|
|
77
79
|
},
|
package/src/renderer/sockets.ts
CHANGED
|
@@ -6,32 +6,41 @@ import type { DesktopSockets } from "../shared/api.js";
|
|
|
6
6
|
export function shellSocketHost(sockets: DesktopSockets): SocketHost {
|
|
7
7
|
// Until its close, which the shell sends after an error and after a close asked for.
|
|
8
8
|
const open = new Map<number, SocketEvents>();
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
// A batch is one task: its events go to their connections in one loop.
|
|
10
|
+
sockets.onEvents((batch) => {
|
|
11
|
+
for (let i = 0; i < batch.length; i++) {
|
|
12
|
+
const [id, event] = batch[i];
|
|
13
|
+
const events = open.get(id);
|
|
14
|
+
if (!events) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
// One that throws leaves the rest to run, as when each came in a message of its own.
|
|
19
|
+
try {
|
|
20
|
+
switch (event.type) {
|
|
21
|
+
case "open":
|
|
22
|
+
events.open(event);
|
|
23
|
+
break;
|
|
24
|
+
case "data":
|
|
25
|
+
events.data(event.bytes);
|
|
26
|
+
break;
|
|
27
|
+
case "error":
|
|
28
|
+
events.error(event.message);
|
|
29
|
+
break;
|
|
30
|
+
case "close":
|
|
31
|
+
open.delete(id);
|
|
32
|
+
events.close();
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
reportError(error);
|
|
37
|
+
}
|
|
29
38
|
}
|
|
30
39
|
});
|
|
31
40
|
|
|
32
41
|
return {
|
|
33
|
-
connect(host, port, events) {
|
|
34
|
-
const id = sockets.connect(host, port);
|
|
42
|
+
connect(host, port, events, options) {
|
|
43
|
+
const id = sockets.connect(host, port, options?.noDelay);
|
|
35
44
|
open.set(id, events);
|
|
36
45
|
return {
|
|
37
46
|
send: (bytes) => sockets.send(id, bytes),
|
|
@@ -7,26 +7,35 @@ import type { DesktopWebSockets } from "../shared/api.js";
|
|
|
7
7
|
export function shellWebSocketHost(webSockets: DesktopWebSockets): WebSocketHost {
|
|
8
8
|
// Until its close, which the shell sends after a failure too.
|
|
9
9
|
const open = new Map<number, WebSocketEvents>();
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
// A batch is one task: its events, a message each, go to their connections in one loop.
|
|
11
|
+
webSockets.onEvents((batch) => {
|
|
12
|
+
for (let i = 0; i < batch.length; i++) {
|
|
13
|
+
const [id, event] = batch[i];
|
|
14
|
+
const events = open.get(id);
|
|
15
|
+
if (!events) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
// One that throws leaves the rest to run, as when each came in a message of its own.
|
|
20
|
+
try {
|
|
21
|
+
switch (event.type) {
|
|
22
|
+
case "open":
|
|
23
|
+
events.open(event.protocol);
|
|
24
|
+
break;
|
|
25
|
+
case "message":
|
|
26
|
+
events.message(event.data);
|
|
27
|
+
break;
|
|
28
|
+
case "error":
|
|
29
|
+
events.error();
|
|
30
|
+
break;
|
|
31
|
+
case "close":
|
|
32
|
+
open.delete(id);
|
|
33
|
+
events.close(event.code);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
reportError(error);
|
|
38
|
+
}
|
|
30
39
|
}
|
|
31
40
|
});
|
|
32
41
|
|
package/src/shared/api.ts
CHANGED
|
@@ -135,13 +135,22 @@ export type SocketEvent =
|
|
|
135
135
|
| { type: "close" }
|
|
136
136
|
| { type: "error"; message: string };
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Events as the shell sends them, gathered over a millisecond at most:
|
|
140
|
+
* each with its connection's id, in the order they came.
|
|
141
|
+
*/
|
|
142
|
+
export type ConnectionEvents<E> = readonly (readonly [id: number, event: E])[];
|
|
143
|
+
|
|
138
144
|
/** flash.net.Socket's TCP connections, opened by the shell. */
|
|
139
145
|
export interface DesktopSockets {
|
|
140
|
-
/**
|
|
141
|
-
|
|
146
|
+
/**
|
|
147
|
+
* A connection's id; its events follow, an "error" one if the shell refuses it. `noDelay` is
|
|
148
|
+
* tcpNoDelay as the SWF set it, absent where it did not: the shell then sends at once.
|
|
149
|
+
*/
|
|
150
|
+
connect(host: string, port: number, noDelay?: boolean): number;
|
|
142
151
|
send(id: number, bytes: Uint8Array): void;
|
|
143
152
|
close(id: number): void;
|
|
144
|
-
|
|
153
|
+
onEvents(listener: (events: ConnectionEvents<SocketEvent>) => void): void;
|
|
145
154
|
}
|
|
146
155
|
|
|
147
156
|
/** What befalls a WebSocket, as player's WebSocketEvents has it. */
|
|
@@ -158,7 +167,7 @@ export interface DesktopWebSockets {
|
|
|
158
167
|
/** A text message for a string, a binary one for bytes. */
|
|
159
168
|
send(id: number, data: string | Uint8Array): void;
|
|
160
169
|
close(id: number, code?: number): void;
|
|
161
|
-
|
|
170
|
+
onEvents(listener: (events: ConnectionEvents<WebSocketEvent>) => void): void;
|
|
162
171
|
}
|
|
163
172
|
|
|
164
173
|
/** A request of a SWF's, as player's FetchRequest has it. */
|