@swf2es/desktop 0.1.15 → 0.1.17
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/README.md +85 -12
- package/dist/launch/launch.js +4 -1
- package/dist/launch/launch.js.map +1 -1
- package/dist/main/air.d.ts +3 -3
- package/dist/main/air.d.ts.map +1 -1
- package/dist/main/air.js +17 -8
- package/dist/main/air.js.map +1 -1
- package/dist/main/case-paths.d.ts +12 -0
- package/dist/main/case-paths.d.ts.map +1 -0
- package/dist/main/case-paths.js +84 -0
- package/dist/main/case-paths.js.map +1 -0
- package/dist/main/connections.d.ts +20 -1
- package/dist/main/connections.d.ts.map +1 -1
- package/dist/main/connections.js +71 -8
- package/dist/main/connections.js.map +1 -1
- package/dist/main/main.js +133 -9
- package/dist/main/main.js.map +1 -1
- package/dist/main/netlog.d.ts +21 -0
- package/dist/main/netlog.d.ts.map +1 -0
- package/dist/main/netlog.js +90 -0
- package/dist/main/netlog.js.map +1 -0
- package/dist/main/network.d.ts +10 -0
- package/dist/main/network.d.ts.map +1 -1
- package/dist/main/network.js +111 -35
- package/dist/main/network.js.map +1 -1
- package/dist/main/proxy.d.ts +48 -0
- package/dist/main/proxy.d.ts.map +1 -0
- package/dist/main/proxy.js +402 -0
- package/dist/main/proxy.js.map +1 -0
- package/dist/main/sandbox.d.ts.map +1 -1
- package/dist/main/sandbox.js +2 -1
- package/dist/main/sandbox.js.map +1 -1
- package/dist/main/settings.d.ts +2 -0
- package/dist/main/settings.d.ts.map +1 -1
- package/dist/main/settings.js +4 -0
- package/dist/main/settings.js.map +1 -1
- package/dist/main/sockets.d.ts +6 -2
- package/dist/main/sockets.d.ts.map +1 -1
- package/dist/main/sockets.js +18 -3
- package/dist/main/sockets.js.map +1 -1
- package/dist/main/websocket-connections.d.ts +73 -0
- package/dist/main/websocket-connections.d.ts.map +1 -0
- package/dist/main/websocket-connections.js +266 -0
- package/dist/main/websocket-connections.js.map +1 -0
- package/dist/main/websockets.d.ts +12 -0
- package/dist/main/websockets.d.ts.map +1 -0
- package/dist/main/websockets.js +94 -0
- package/dist/main/websockets.js.map +1 -0
- package/dist/preload/preload.cjs +20 -0
- package/dist/preload/preload.cjs.map +1 -1
- package/dist/renderer/air.d.ts.map +1 -1
- package/dist/renderer/air.js +1 -0
- package/dist/renderer/air.js.map +1 -1
- package/dist/renderer/full-screen.d.ts +4 -0
- package/dist/renderer/full-screen.d.ts.map +1 -0
- package/dist/renderer/full-screen.js +32 -0
- package/dist/renderer/full-screen.js.map +1 -0
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +4 -0
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/websockets.d.ts +4 -0
- package/dist/renderer/websockets.d.ts.map +1 -0
- package/dist/renderer/websockets.js +36 -0
- package/dist/renderer/websockets.js.map +1 -0
- package/dist/shared/api.d.ts +53 -0
- package/dist/shared/api.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/launch/launch.ts +5 -2
- package/src/main/air.ts +18 -8
- package/src/main/case-paths.ts +97 -0
- package/src/main/connections.ts +90 -8
- package/src/main/main.ts +174 -10
- package/src/main/netlog.ts +108 -0
- package/src/main/network.ts +140 -37
- package/src/main/proxy.ts +497 -0
- package/src/main/sandbox.ts +2 -1
- package/src/main/settings.ts +5 -0
- package/src/main/sockets.ts +22 -3
- package/src/main/websocket-connections.ts +344 -0
- package/src/main/websockets.ts +117 -0
- package/src/preload/preload.cts +23 -0
- package/src/renderer/air.ts +1 -0
- package/src/renderer/full-screen.ts +44 -0
- package/src/renderer/index.ts +4 -0
- package/src/renderer/websockets.ts +43 -0
- package/src/shared/api.ts +40 -0
package/src/main/main.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// electron apps/desktop [--trace] [file.swf | http(s)://host/movie.swf]
|
|
6
6
|
// electron apps/desktop [--trace] app.xml [root-dir] [-- arguments...]
|
|
7
7
|
//
|
|
8
|
-
// --ignore-certificate-errors[=host,...] leaves HTTPS certificates unchecked.
|
|
8
|
+
// --ignore-certificate-errors[=host,...] leaves HTTPS and WSS certificates unchecked.
|
|
9
|
+
// --proxy=socks5://[user:pass@]host:port (or http://...) sends the SWF's network through a proxy.
|
|
9
10
|
//
|
|
10
11
|
// The second runs an AIR application by its descriptor, as adl does.
|
|
11
12
|
import { statSync } from "node:fs";
|
|
@@ -23,11 +24,13 @@ import {
|
|
|
23
24
|
Menu,
|
|
24
25
|
type MenuItemConstructorOptions,
|
|
25
26
|
safeStorage,
|
|
27
|
+
screen,
|
|
26
28
|
session,
|
|
27
29
|
shell,
|
|
28
30
|
type WebContents,
|
|
29
31
|
} from "electron";
|
|
30
32
|
import type {
|
|
33
|
+
AirScreenState,
|
|
31
34
|
AirWindowState,
|
|
32
35
|
LibraryName,
|
|
33
36
|
NetworkResponse,
|
|
@@ -47,12 +50,24 @@ import {
|
|
|
47
50
|
statOf,
|
|
48
51
|
writeOf,
|
|
49
52
|
} from "./air.ts";
|
|
53
|
+
import { resolveCase } from "./case-paths.ts";
|
|
50
54
|
import { bridgeFiles } from "./files.ts";
|
|
55
|
+
import { failureLine, requestLine } from "./netlog.ts";
|
|
51
56
|
import { type Movie, Network, Refused, unverifiedHosts } from "./network.ts";
|
|
52
57
|
import { APP_ORIGIN, registerScheme, serve } from "./protocol.ts";
|
|
58
|
+
import { describeProxy, proxyArgument, sameProxy } from "./proxy.ts";
|
|
53
59
|
import { Sandbox } from "./sandbox.ts";
|
|
54
60
|
import { SettingsFile } from "./settings.ts";
|
|
55
61
|
import { bridgeSockets } from "./sockets.ts";
|
|
62
|
+
import {
|
|
63
|
+
dispatchedOpener,
|
|
64
|
+
hasWebSocket,
|
|
65
|
+
type Opener,
|
|
66
|
+
proxiedDispatcher,
|
|
67
|
+
unverifiedDispatcher,
|
|
68
|
+
webSocketOpener,
|
|
69
|
+
} from "./websocket-connections.ts";
|
|
70
|
+
import { bridgeWebSockets } from "./websockets.ts";
|
|
56
71
|
|
|
57
72
|
const preload = fileURLToPath(new URL("../preload/preload.cjs", import.meta.url));
|
|
58
73
|
const promptPreload = fileURLToPath(new URL("../preload/prompt.cjs", import.meta.url));
|
|
@@ -77,6 +92,11 @@ app.enableSandbox();
|
|
|
77
92
|
|
|
78
93
|
/** --trace: what the page logs, a SWF's trace() among it, goes to the terminal. */
|
|
79
94
|
const trace = process.argv.includes("--trace");
|
|
95
|
+
// Each request and socket of the SWF's said on stderr: the page's DevTools show none of them.
|
|
96
|
+
// --log-sockets says what each socket sends and receives as well, and so the rest too.
|
|
97
|
+
const logSockets = process.argv.includes("--log-sockets");
|
|
98
|
+
const logNetwork = logSockets || process.argv.includes("--log-network");
|
|
99
|
+
const netLog = logNetwork ? (line: string) => process.stderr.write(`${line}\n`) : null;
|
|
80
100
|
const sandbox = new Sandbox();
|
|
81
101
|
/** The SWF's http and https requests, judged as Flash's sandboxes had them. */
|
|
82
102
|
/**
|
|
@@ -89,15 +109,61 @@ const unverified = unverifiedHosts(process.argv.slice(1));
|
|
|
89
109
|
if (unverified) {
|
|
90
110
|
process.stderr.write(
|
|
91
111
|
unverified.all
|
|
92
|
-
? "swf2es: --ignore-certificate-errors: no HTTPS server's certificate is verified\n"
|
|
112
|
+
? "swf2es: --ignore-certificate-errors: no HTTPS or WSS server's certificate is verified\n"
|
|
93
113
|
: `swf2es: --ignore-certificate-errors: certificates are not verified for ${unverified.hosts.join(", ") || "no host"}\n`,
|
|
94
114
|
);
|
|
95
115
|
}
|
|
96
116
|
|
|
97
|
-
const
|
|
98
|
-
unverified
|
|
99
|
-
|
|
100
|
-
|
|
117
|
+
const unverifiedHost = (host: string) =>
|
|
118
|
+
!!unverified && (unverified.all || unverified.hosts.includes(host.toLowerCase()));
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* --proxy=<url>: what the SWF's requests, sockets and WebSockets go
|
|
122
|
+
* through. For this run alone, never kept, and said aloud, without its
|
|
123
|
+
* credentials. One that does not parse stops the app rather than letting
|
|
124
|
+
* it run without.
|
|
125
|
+
*/
|
|
126
|
+
const proxied = proxyArgument(process.argv.slice(1));
|
|
127
|
+
if (proxied && "error" in proxied) {
|
|
128
|
+
process.stderr.write(`swf2es: ${proxied.error}\n`);
|
|
129
|
+
process.exit(2);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const proxy = proxied?.proxy ?? null;
|
|
133
|
+
if (proxy) {
|
|
134
|
+
process.stderr.write(
|
|
135
|
+
`swf2es: --proxy: the SWF's requests, sockets and WebSockets go through ${describeProxy(proxy)}\n`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** What --log-network adds to a line of a connection that went through the proxy. */
|
|
140
|
+
const via = proxy ? " via proxy" : "";
|
|
141
|
+
const warn = (message: string) => process.stderr.write(`swf2es: ${message}\n`);
|
|
142
|
+
const network = new Network({ unverified: unverifiedHost, proxy });
|
|
143
|
+
// What opens an AIR application's WebSockets: the global's client, a wss: server's certificate
|
|
144
|
+
// unchecked where the switch names its host; or every one through the proxy.
|
|
145
|
+
const insecureWebSockets = unverified && !proxy ? unverifiedDispatcher() : null;
|
|
146
|
+
if (unverified && !proxy && !insecureWebSockets) {
|
|
147
|
+
process.stderr.write(
|
|
148
|
+
"swf2es: --ignore-certificate-errors: a WebSocket's certificate is verified all the same\n",
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const proxiedWebSockets = proxy ? proxiedDispatcher(proxy, unverifiedHost, warn) : null;
|
|
153
|
+
// Never past the proxy: where none can be made, no WebSocket opens.
|
|
154
|
+
const webSocketOpen: Opener | undefined = proxy
|
|
155
|
+
? proxiedWebSockets
|
|
156
|
+
? dispatchedOpener(proxiedWebSockets)
|
|
157
|
+
: () => {
|
|
158
|
+
throw new Error("no WebSocket through the proxy");
|
|
159
|
+
}
|
|
160
|
+
: insecureWebSockets
|
|
161
|
+
? webSocketOpener(unverifiedHost, insecureWebSockets)
|
|
162
|
+
: undefined;
|
|
163
|
+
if (proxy && !proxiedWebSockets) {
|
|
164
|
+
process.stderr.write("swf2es: --proxy: this Electron's WebSocket cannot go through a proxy\n");
|
|
165
|
+
}
|
|
166
|
+
|
|
101
167
|
/** What the page's requests under way abort with, by the preload's number. */
|
|
102
168
|
const requests = new Map<number, AbortController>();
|
|
103
169
|
let settings: SettingsFile;
|
|
@@ -560,6 +626,25 @@ async function askForSocket(name: string, swf: string, endpoint: string): Promis
|
|
|
560
626
|
}
|
|
561
627
|
}
|
|
562
628
|
|
|
629
|
+
/**
|
|
630
|
+
* Whether what plays may open a WebSocket to `url`: an AIR application,
|
|
631
|
+
* unasked and anywhere, as AIR's application sandbox connects and as its
|
|
632
|
+
* sockets do here. air.net.WebSocket is AIR's alone, so nothing else has one.
|
|
633
|
+
*/
|
|
634
|
+
function permitWebSocket(url: string): boolean {
|
|
635
|
+
if (sandbox.application && hasWebSocket() && (!proxy || proxiedWebSockets)) {
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const why = !sandbox.application
|
|
640
|
+
? "only an AIR application has one"
|
|
641
|
+
: !hasWebSocket()
|
|
642
|
+
? "this Electron has no WebSocket client"
|
|
643
|
+
: "this Electron's WebSocket cannot go through the proxy";
|
|
644
|
+
process.stderr.write(`swf2es: no WebSocket to ${url.slice(0, 2048)}: ${why}\n`);
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
|
|
563
648
|
async function showOpenDialog(): Promise<void> {
|
|
564
649
|
if (!window) {
|
|
565
650
|
return;
|
|
@@ -608,10 +693,22 @@ async function chooseLibrary(name: LibraryName): Promise<void> {
|
|
|
608
693
|
refuse("The settings could not be saved.", `swf2es uses ${path} until it quits.`);
|
|
609
694
|
}
|
|
610
695
|
|
|
696
|
+
buildMenu();
|
|
697
|
+
|
|
611
698
|
// The page fetched the libraries once; a fresh page fetches the new one.
|
|
612
699
|
window.webContents.reload();
|
|
613
700
|
}
|
|
614
701
|
|
|
702
|
+
/** Play on the library the app ships again, in place of one the user chose. */
|
|
703
|
+
function resetLibrary(name: LibraryName): void {
|
|
704
|
+
if (!settings.clearLibrary(name)) {
|
|
705
|
+
refuse("The settings could not be saved.", `swf2es uses its own ${name} until it quits.`);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
buildMenu();
|
|
709
|
+
window?.webContents.reload();
|
|
710
|
+
}
|
|
711
|
+
|
|
615
712
|
function buildMenu(): void {
|
|
616
713
|
const recent = settings.settings.recent;
|
|
617
714
|
const template: MenuItemConstructorOptions[] = [
|
|
@@ -646,6 +743,11 @@ function buildMenu(): void {
|
|
|
646
743
|
},
|
|
647
744
|
{ type: "separator" },
|
|
648
745
|
{ label: "Choose playerglobal.abc…", click: () => void chooseLibrary("playerglobal") },
|
|
746
|
+
{
|
|
747
|
+
label: "Use the App's playerglobal",
|
|
748
|
+
enabled: !!settings.settings.playerglobal,
|
|
749
|
+
click: () => resetLibrary("playerglobal"),
|
|
750
|
+
},
|
|
649
751
|
{ type: "separator" },
|
|
650
752
|
{ role: "quit" },
|
|
651
753
|
],
|
|
@@ -800,6 +902,9 @@ function createWindow(): void {
|
|
|
800
902
|
window.on("closed", () => {
|
|
801
903
|
window = null;
|
|
802
904
|
});
|
|
905
|
+
// The page hears of full screen however it came, a SWF's Stage.displayState or the menu's F11.
|
|
906
|
+
window.on("enter-full-screen", () => window?.webContents.send("desktop:full-screen", true));
|
|
907
|
+
window.on("leave-full-screen", () => window?.webContents.send("desktop:full-screen", false));
|
|
803
908
|
guard(window.webContents);
|
|
804
909
|
void window.loadURL(`${APP_ORIGIN}/`);
|
|
805
910
|
}
|
|
@@ -831,6 +936,12 @@ function listen(): void {
|
|
|
831
936
|
build,
|
|
832
937
|
};
|
|
833
938
|
});
|
|
939
|
+
// Stage.displayState of a SWF the user opened: the window's full screen, asked with no gesture.
|
|
940
|
+
ipcMain.on("desktop:full-screen", (event, fullScreen: unknown) => {
|
|
941
|
+
if (trusted(event) && current && window) {
|
|
942
|
+
window.setFullScreen(fullScreen === true);
|
|
943
|
+
}
|
|
944
|
+
});
|
|
834
945
|
ipcMain.on("desktop:open-dialog", (event) => {
|
|
835
946
|
if (trusted(event)) {
|
|
836
947
|
void showOpenDialog();
|
|
@@ -869,7 +980,8 @@ function listen(): void {
|
|
|
869
980
|
void chooseLibrary(name);
|
|
870
981
|
}
|
|
871
982
|
});
|
|
872
|
-
bridgeSockets(trusted, permitSocket);
|
|
983
|
+
bridgeSockets(trusted, permitSocket, netLog, logSockets, { proxy, warn });
|
|
984
|
+
bridgeWebSockets(trusted, permitWebSocket, webSocketOpen, netLog, logSockets, via);
|
|
873
985
|
ipcMain.handle("net:fetch", (event, id: unknown, request: unknown) => {
|
|
874
986
|
if (!trusted(event) || typeof id !== "number") {
|
|
875
987
|
throw new Error("not the app's page");
|
|
@@ -917,6 +1029,17 @@ function windowState(w: BrowserWindow): AirWindowState {
|
|
|
917
1029
|
};
|
|
918
1030
|
}
|
|
919
1031
|
|
|
1032
|
+
/** The displays as an AIR application's Screen reads them: the work area is past the system's bars. */
|
|
1033
|
+
function screenStates(): AirScreenState[] {
|
|
1034
|
+
const primary = screen.getPrimaryDisplay().id;
|
|
1035
|
+
return screen.getAllDisplays().map((display) => ({
|
|
1036
|
+
bounds: display.bounds,
|
|
1037
|
+
visibleBounds: display.workArea,
|
|
1038
|
+
colorDepth: display.colorDepth,
|
|
1039
|
+
main: display.id === primary,
|
|
1040
|
+
}));
|
|
1041
|
+
}
|
|
1042
|
+
|
|
920
1043
|
/**
|
|
921
1044
|
* An AIR application's reach, shared/api.ts DesktopAir: the file system
|
|
922
1045
|
* and the window, only while one the user opened plays and only for its
|
|
@@ -990,7 +1113,7 @@ function listenForAir(): void {
|
|
|
990
1113
|
}
|
|
991
1114
|
|
|
992
1115
|
try {
|
|
993
|
-
const real = await realpath(path);
|
|
1116
|
+
const real = await realpath(resolveCase(path));
|
|
994
1117
|
return sandbox.startApplication(real) ? new Uint8Array(await readFile(real)) : null;
|
|
995
1118
|
} catch {
|
|
996
1119
|
return null;
|
|
@@ -999,6 +1122,9 @@ function listenForAir(): void {
|
|
|
999
1122
|
ipcMain.on("air:window", (event) => {
|
|
1000
1123
|
event.returnValue = playing(event) && window ? windowState(window) : null;
|
|
1001
1124
|
});
|
|
1125
|
+
ipcMain.on("air:screens", (event) => {
|
|
1126
|
+
event.returnValue = playing(event) ? screenStates() : [];
|
|
1127
|
+
});
|
|
1002
1128
|
ipcMain.on("air:set-bounds", (event, bounds: unknown) => {
|
|
1003
1129
|
const b = bounds as { x?: unknown; y?: unknown; width?: unknown; height?: unknown } | null;
|
|
1004
1130
|
const n = (v: unknown) => (typeof v === "number" && Number.isFinite(v) ? Math.round(v) : null);
|
|
@@ -1068,7 +1194,7 @@ async function fetchForPage(
|
|
|
1068
1194
|
request: unknown,
|
|
1069
1195
|
deliver: (response: NetworkResponse) => void,
|
|
1070
1196
|
): Promise<NetworkResponse> {
|
|
1071
|
-
const asked = request as { url?: unknown; purpose?: unknown } | null;
|
|
1197
|
+
const asked = request as { url?: unknown; purpose?: unknown; method?: unknown } | null;
|
|
1072
1198
|
const url = typeof asked?.url === "string" ? asked.url : "";
|
|
1073
1199
|
const shown = url.slice(0, 2048);
|
|
1074
1200
|
const movie = asked?.purpose === "movie" && sandbox.startRemote(url);
|
|
@@ -1079,13 +1205,27 @@ async function fetchForPage(
|
|
|
1079
1205
|
|
|
1080
1206
|
const controller = new AbortController();
|
|
1081
1207
|
requests.set(id, controller);
|
|
1208
|
+
const started = performance.now();
|
|
1209
|
+
const method = typeof asked?.method === "string" ? asked.method.slice(0, 16) : "GET";
|
|
1082
1210
|
try {
|
|
1083
|
-
|
|
1211
|
+
const response = await network.fetch(request, controller.signal, deliver);
|
|
1212
|
+
netLog?.(
|
|
1213
|
+
requestLine(
|
|
1214
|
+
method,
|
|
1215
|
+
url,
|
|
1216
|
+
response.status,
|
|
1217
|
+
response.bytes?.length ?? null,
|
|
1218
|
+
performance.now() - started,
|
|
1219
|
+
) + via,
|
|
1220
|
+
);
|
|
1221
|
+
return response;
|
|
1084
1222
|
} catch (error) {
|
|
1085
1223
|
// A refusal says why; anything else, which should not happen, is said too.
|
|
1086
1224
|
if (!controller.signal.aborted) {
|
|
1087
1225
|
const why = error instanceof Refused ? error.message : `failed: ${error}`;
|
|
1088
1226
|
process.stderr.write(`swf2es: not loading ${shown}: ${why}\n`);
|
|
1227
|
+
} else {
|
|
1228
|
+
netLog?.(failureLine(method, url, "aborted", performance.now() - started) + via);
|
|
1089
1229
|
}
|
|
1090
1230
|
|
|
1091
1231
|
throw error;
|
|
@@ -1099,10 +1239,25 @@ async function fetchForPage(
|
|
|
1099
1239
|
// One app to a user data directory: a second start hands its SWF to the first and quits.
|
|
1100
1240
|
const first = app.requestSingleInstanceLock();
|
|
1101
1241
|
if (!first) {
|
|
1242
|
+
if (proxied) {
|
|
1243
|
+
process.stderr.write(
|
|
1244
|
+
"swf2es: handed to the app already open, which opens it only if started with the same --proxy\n",
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1102
1248
|
app.quit();
|
|
1103
1249
|
}
|
|
1104
1250
|
|
|
1105
1251
|
app.on("second-instance", (_event, argv, workingDirectory) => {
|
|
1252
|
+
// What it names would go through another proxy than this app's, or past it.
|
|
1253
|
+
const asked = proxyArgument(argv.slice(1));
|
|
1254
|
+
if (asked && !("proxy" in asked && sameProxy(asked.proxy, proxy))) {
|
|
1255
|
+
process.stderr.write(
|
|
1256
|
+
"swf2es: not opening what a second start named: its --proxy is not this app's\n",
|
|
1257
|
+
);
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1106
1261
|
if (window) {
|
|
1107
1262
|
if (window.isMinimized()) {
|
|
1108
1263
|
window.restore();
|
|
@@ -1152,6 +1307,15 @@ void app.whenReady().then(() => {
|
|
|
1152
1307
|
}
|
|
1153
1308
|
|
|
1154
1309
|
settings = new SettingsFile(app.getPath("userData"));
|
|
1310
|
+
// A playerglobal chosen once is kept across versions of the app: said at each start, as a
|
|
1311
|
+
// library older or newer than the app's own makes SWFs fail in ways nothing else explains.
|
|
1312
|
+
const chosen = settings.libraries().playerglobal;
|
|
1313
|
+
if (chosen) {
|
|
1314
|
+
process.stderr.write(
|
|
1315
|
+
`swf2es: playing on the playerglobal chosen in File, ${chosen}, not the app's own\n`,
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1155
1319
|
serve(session.defaultSession, sandbox, () => settings.libraries());
|
|
1156
1320
|
// The page never goes to the network itself: a SWF's requests go through
|
|
1157
1321
|
// the main process (network.ts), which sends them without this session.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// --log-network's lines: what a SWF asked of the network, as the terminal
|
|
2
|
+
// shows it. The page has no network of its own, so its DevTools' Network
|
|
3
|
+
// tab shows none of this; the main process, which sends the requests, says
|
|
4
|
+
// them here instead. Apart from Electron, so that node tests it.
|
|
5
|
+
|
|
6
|
+
/** A size as a person reads it: bytes, then kB and MB to one decimal. */
|
|
7
|
+
function size(bytes: number): string {
|
|
8
|
+
if (bytes < 1024) {
|
|
9
|
+
return `${bytes} B`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return bytes < 1024 * 1024
|
|
13
|
+
? `${(bytes / 1024).toFixed(1)} kB`
|
|
14
|
+
: `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A URL short enough for a line, and with nothing a terminal would act on. */
|
|
18
|
+
function shown(url: string): string {
|
|
19
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: control characters are what is taken out
|
|
20
|
+
return url.slice(0, 2048).replace(/[\u0000-\u001f\u007f]/g, "?");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A request answered: its method, URL, status, body's size and how long it took. */
|
|
24
|
+
export function requestLine(
|
|
25
|
+
method: string,
|
|
26
|
+
url: string,
|
|
27
|
+
status: number,
|
|
28
|
+
bytes: number | null,
|
|
29
|
+
ms: number,
|
|
30
|
+
): string {
|
|
31
|
+
const body = bytes === null ? "no body" : size(bytes);
|
|
32
|
+
return `swf2es: net ${method} ${shown(url)} ${status} ${body} ${Math.round(ms)} ms`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A request that got no answer: refused, failed or aborted, and why. */
|
|
36
|
+
export function failureLine(method: string, url: string, why: string, ms: number): string {
|
|
37
|
+
return `swf2es: net ${method} ${shown(url)} ${why} ${Math.round(ms)} ms`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A socket's connection asked for, opened, refused or closed. */
|
|
41
|
+
export function socketLine(id: number, what: string): string {
|
|
42
|
+
return `swf2es: socket #${id} ${shown(what)}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** How much of a message --log-sockets shows. */
|
|
46
|
+
const PREVIEW = 512;
|
|
47
|
+
|
|
48
|
+
/** The start of `bytes`: as text where it is text, bytes that are no printable ASCII as dots, else in hex. */
|
|
49
|
+
function preview(bytes: Uint8Array): string {
|
|
50
|
+
const shownBytes = bytes.subarray(0, PREVIEW);
|
|
51
|
+
let printable = 0;
|
|
52
|
+
for (let i = 0; i < shownBytes.length; i++) {
|
|
53
|
+
const b = shownBytes[i];
|
|
54
|
+
if ((b >= 32 && b < 127) || b === 9 || b === 10 || b === 13 || b === 0) {
|
|
55
|
+
printable++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let text = "";
|
|
60
|
+
// Mostly text, as a line protocol's messages are, a NUL ending each: shown as text.
|
|
61
|
+
if (printable * 10 >= shownBytes.length * 9) {
|
|
62
|
+
for (let i = 0; i < shownBytes.length; i++) {
|
|
63
|
+
const b = shownBytes[i];
|
|
64
|
+
text += b >= 32 && b < 127 ? String.fromCharCode(b) : ".";
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
for (let i = 0; i < shownBytes.length; i++) {
|
|
68
|
+
text += (i > 0 ? " " : "") + shownBytes[i].toString(16).padStart(2, "0");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return text + (bytes.length > PREVIEW ? "…" : "");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* What a socket sent (">") or received ("<"): its size and its start, as
|
|
77
|
+
* text where it is text, bytes that are no printable ASCII as dots, else
|
|
78
|
+
* in hex. A message past PREVIEW bytes ends in an ellipsis.
|
|
79
|
+
*/
|
|
80
|
+
export function socketDataLine(id: number, sent: boolean, bytes: Uint8Array): string {
|
|
81
|
+
return `swf2es: socket #${id} ${sent ? ">" : "<"} ${bytes.length} B ${preview(bytes)}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** A WebSocket's connection asked for, opened, refused, failed or closed. */
|
|
85
|
+
export function webSocketLine(id: number, what: string): string {
|
|
86
|
+
return `swf2es: websocket #${id} ${shown(what)}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* What a WebSocket sent (">") or received ("<"): a text message's size
|
|
91
|
+
* in UTF-8 and its first PREVIEW characters, control characters as dots;
|
|
92
|
+
* a binary one's as a socket's.
|
|
93
|
+
*/
|
|
94
|
+
export function webSocketDataLine(id: number, sent: boolean, data: string | Uint8Array): string {
|
|
95
|
+
const head = `swf2es: websocket #${id} ${sent ? ">" : "<"}`;
|
|
96
|
+
if (typeof data !== "string") {
|
|
97
|
+
return `${head} binary ${data.length} B ${preview(data)}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// By code point, so that no pair is cut in two.
|
|
101
|
+
const characters = Array.from(data.slice(0, PREVIEW * 2)).slice(0, PREVIEW);
|
|
102
|
+
const text = characters
|
|
103
|
+
.join("")
|
|
104
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: control characters are what is taken out
|
|
105
|
+
.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, ".");
|
|
106
|
+
const more = text.length < data.length ? "…" : "";
|
|
107
|
+
return `${head} text ${Buffer.byteLength(data)} B ${text}${more}`;
|
|
108
|
+
}
|