@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.
Files changed (86) hide show
  1. package/README.md +85 -12
  2. package/dist/launch/launch.js +4 -1
  3. package/dist/launch/launch.js.map +1 -1
  4. package/dist/main/air.d.ts +3 -3
  5. package/dist/main/air.d.ts.map +1 -1
  6. package/dist/main/air.js +17 -8
  7. package/dist/main/air.js.map +1 -1
  8. package/dist/main/case-paths.d.ts +12 -0
  9. package/dist/main/case-paths.d.ts.map +1 -0
  10. package/dist/main/case-paths.js +84 -0
  11. package/dist/main/case-paths.js.map +1 -0
  12. package/dist/main/connections.d.ts +20 -1
  13. package/dist/main/connections.d.ts.map +1 -1
  14. package/dist/main/connections.js +71 -8
  15. package/dist/main/connections.js.map +1 -1
  16. package/dist/main/main.js +133 -9
  17. package/dist/main/main.js.map +1 -1
  18. package/dist/main/netlog.d.ts +21 -0
  19. package/dist/main/netlog.d.ts.map +1 -0
  20. package/dist/main/netlog.js +90 -0
  21. package/dist/main/netlog.js.map +1 -0
  22. package/dist/main/network.d.ts +10 -0
  23. package/dist/main/network.d.ts.map +1 -1
  24. package/dist/main/network.js +111 -35
  25. package/dist/main/network.js.map +1 -1
  26. package/dist/main/proxy.d.ts +48 -0
  27. package/dist/main/proxy.d.ts.map +1 -0
  28. package/dist/main/proxy.js +402 -0
  29. package/dist/main/proxy.js.map +1 -0
  30. package/dist/main/sandbox.d.ts.map +1 -1
  31. package/dist/main/sandbox.js +2 -1
  32. package/dist/main/sandbox.js.map +1 -1
  33. package/dist/main/settings.d.ts +2 -0
  34. package/dist/main/settings.d.ts.map +1 -1
  35. package/dist/main/settings.js +4 -0
  36. package/dist/main/settings.js.map +1 -1
  37. package/dist/main/sockets.d.ts +6 -2
  38. package/dist/main/sockets.d.ts.map +1 -1
  39. package/dist/main/sockets.js +18 -3
  40. package/dist/main/sockets.js.map +1 -1
  41. package/dist/main/websocket-connections.d.ts +73 -0
  42. package/dist/main/websocket-connections.d.ts.map +1 -0
  43. package/dist/main/websocket-connections.js +266 -0
  44. package/dist/main/websocket-connections.js.map +1 -0
  45. package/dist/main/websockets.d.ts +12 -0
  46. package/dist/main/websockets.d.ts.map +1 -0
  47. package/dist/main/websockets.js +94 -0
  48. package/dist/main/websockets.js.map +1 -0
  49. package/dist/preload/preload.cjs +20 -0
  50. package/dist/preload/preload.cjs.map +1 -1
  51. package/dist/renderer/air.d.ts.map +1 -1
  52. package/dist/renderer/air.js +1 -0
  53. package/dist/renderer/air.js.map +1 -1
  54. package/dist/renderer/full-screen.d.ts +4 -0
  55. package/dist/renderer/full-screen.d.ts.map +1 -0
  56. package/dist/renderer/full-screen.js +32 -0
  57. package/dist/renderer/full-screen.js.map +1 -0
  58. package/dist/renderer/index.d.ts.map +1 -1
  59. package/dist/renderer/index.js +4 -0
  60. package/dist/renderer/index.js.map +1 -1
  61. package/dist/renderer/websockets.d.ts +4 -0
  62. package/dist/renderer/websockets.d.ts.map +1 -0
  63. package/dist/renderer/websockets.js +36 -0
  64. package/dist/renderer/websockets.js.map +1 -0
  65. package/dist/shared/api.d.ts +53 -0
  66. package/dist/shared/api.d.ts.map +1 -1
  67. package/package.json +7 -7
  68. package/src/launch/launch.ts +5 -2
  69. package/src/main/air.ts +18 -8
  70. package/src/main/case-paths.ts +97 -0
  71. package/src/main/connections.ts +90 -8
  72. package/src/main/main.ts +174 -10
  73. package/src/main/netlog.ts +108 -0
  74. package/src/main/network.ts +140 -37
  75. package/src/main/proxy.ts +497 -0
  76. package/src/main/sandbox.ts +2 -1
  77. package/src/main/settings.ts +5 -0
  78. package/src/main/sockets.ts +22 -3
  79. package/src/main/websocket-connections.ts +344 -0
  80. package/src/main/websockets.ts +117 -0
  81. package/src/preload/preload.cts +23 -0
  82. package/src/renderer/air.ts +1 -0
  83. package/src/renderer/full-screen.ts +44 -0
  84. package/src/renderer/index.ts +4 -0
  85. package/src/renderer/websockets.ts +43 -0
  86. package/src/shared/api.ts +40 -0
@@ -21,12 +21,17 @@
21
21
  // that changes its answer (rebinding) changes nothing.
22
22
  // - Requests are bounded: methods, headers, sizes, redirects, time, and
23
23
  // how many run at once; and each movie's are aborted as the next opens.
24
+ // - Through a proxy (--proxy, proxy.ts), each connection is a tunnel to
25
+ // the address its host was pinned to, judged as without one: the proxy
26
+ // is asked for that address, not the name, so a proxy's DNS that answers
27
+ // otherwise than this machine's reaches nothing the judgement did not.
24
28
  //
25
29
  // Without Electron, so that node tests it (tests/desktop/network.test.ts).
26
30
  import { lookup } from "node:dns/promises";
27
31
  import { type ClientRequest, request as httpRequest, type IncomingMessage } from "node:http";
28
32
  import { request as httpsRequest } from "node:https";
29
- import { connect, isIP, isIPv4 } from "node:net";
33
+ import { connect, isIP, isIPv4, type Socket } from "node:net";
34
+ import type { Duplex } from "node:stream";
30
35
  import * as tls from "node:tls";
31
36
  import { type AddressClass, addressClass, badPort, RANK } from "./addresses.ts";
32
37
  import { DECODED, Decompressor } from "./decompress.ts";
@@ -44,6 +49,7 @@ import {
44
49
  urlMetaPolicy,
45
50
  usablePolicies,
46
51
  } from "./policy.ts";
52
+ import { connectThrough, type ProxyConfig, ProxyError } from "./proxy.ts";
47
53
 
48
54
  export type Purpose = "movie" | "content" | "data" | "send";
49
55
 
@@ -145,6 +151,8 @@ export interface NetworkOptions {
145
151
  limits?: Partial<Limits>;
146
152
  /** Where a socket server keeps its master policy: 843. */
147
153
  socketPolicyPort?: number;
154
+ /** What every connection goes through (--proxy); none by default. */
155
+ proxy?: ProxyConfig | null;
148
156
  }
149
157
 
150
158
  /** What Flash Player 32 reported, sent as it sent it. */
@@ -239,6 +247,7 @@ export class Network {
239
247
  private readonly ca: string | string[] | undefined;
240
248
  private readonly unverified: (host: string) => boolean;
241
249
  private readonly socketPolicyPort: number;
250
+ private readonly proxy: ProxyConfig | null;
242
251
  private session: Session | null = null;
243
252
  private running = 0;
244
253
  private readonly waiting: (() => void)[] = [];
@@ -253,6 +262,7 @@ export class Network {
253
262
  this.ca = options.ca ?? defaultCa();
254
263
  this.unverified = options.unverified ?? (() => false);
255
264
  this.socketPolicyPort = options.socketPolicyPort ?? 843;
265
+ this.proxy = options.proxy ?? null;
256
266
  }
257
267
 
258
268
  /**
@@ -753,7 +763,13 @@ export class Network {
753
763
  timeout: this.limits.policyTimeout,
754
764
  deadline: this.limits.policyTimeout,
755
765
  });
756
- } catch {
766
+ } catch (error) {
767
+ // A proxy that failed is said, as the request that needed the policy fails;
768
+ // a server the proxy did not reach has no policy, as one that is not there.
769
+ if (proxyFault(error)) {
770
+ throw error;
771
+ }
772
+
757
773
  return null;
758
774
  }
759
775
 
@@ -790,15 +806,27 @@ export class Network {
790
806
  timeout: number,
791
807
  signal: AbortSignal,
792
808
  ): Promise<SocketPolicy | null> {
793
- return new Promise((answer) => {
809
+ return new Promise((answer, fail) => {
794
810
  const chunks: Buffer[] = [];
795
811
  let length = 0;
796
- const socket = connect({ host: address, port });
797
- const finish = (policy: SocketPolicy | null) => {
812
+ let socket: Socket | null = null;
813
+ let finished = false;
814
+ const opening = new AbortController();
815
+ const finish = (policy: SocketPolicy | null, error?: Error) => {
816
+ if (finished) {
817
+ return;
818
+ }
819
+
820
+ finished = true;
798
821
  clearTimeout(timer);
799
822
  signal.removeEventListener("abort", abort);
800
- socket.destroy();
801
- answer(policy);
823
+ opening.abort();
824
+ socket?.destroy();
825
+ if (error) {
826
+ fail(error);
827
+ } else {
828
+ answer(policy);
829
+ }
802
830
  };
803
831
  const read = () => {
804
832
  const text = Buffer.concat(chunks).toString("utf8");
@@ -808,24 +836,70 @@ export class Network {
808
836
  const abort = () => finish(null);
809
837
  const timer = setTimeout(abort, timeout);
810
838
  signal.addEventListener("abort", abort, { once: true });
811
- socket.on("connect", () => socket.write("<policy-file-request/>\0"));
812
- socket.on("data", (chunk: Buffer) => {
813
- length += chunk.length;
814
- if (length > this.limits.policy) {
815
- finish(null);
816
- return;
817
- }
839
+ const talk = (connected: Socket) => {
840
+ socket = connected;
841
+ connected.on("data", (chunk: Buffer) => {
842
+ length += chunk.length;
843
+ if (length > this.limits.policy) {
844
+ finish(null);
845
+ return;
846
+ }
818
847
 
819
- chunks.push(chunk);
820
- if (chunk.includes(0)) {
821
- read();
822
- }
823
- });
824
- socket.on("end", read);
825
- socket.on("error", () => finish(null));
848
+ chunks.push(chunk);
849
+ if (chunk.includes(0)) {
850
+ read();
851
+ }
852
+ });
853
+ connected.on("end", read);
854
+ connected.on("error", () => finish(null));
855
+ };
856
+
857
+ if (!this.proxy) {
858
+ const direct = connect({ host: address, port });
859
+ talk(direct);
860
+ direct.on("connect", () => direct.write("<policy-file-request/>\0"));
861
+ return;
862
+ }
863
+
864
+ connectThrough(this.proxy, address, port, { signal: opening.signal }).then(
865
+ (tunnel) => {
866
+ if (finished) {
867
+ tunnel.destroy();
868
+ return;
869
+ }
870
+
871
+ talk(tunnel);
872
+ tunnel.write("<policy-file-request/>\0");
873
+ },
874
+ (error: ProxyError) =>
875
+ // A server the proxy did not reach has no policy; a proxy that failed is said.
876
+ finish(null, error.fault ? new Refused(error.message, { cause: error }) : undefined),
877
+ );
826
878
  });
827
879
  }
828
880
 
881
+ /**
882
+ * A request's createConnection: a tunnel through `proxy` to `address`,
883
+ * with TLS over it where `secure` says how, as Node's callback form takes
884
+ * it. A proxy's failure is a refusal saying why, the ProxyError its cause.
885
+ */
886
+ private tunnel(
887
+ proxy: ProxyConfig,
888
+ address: string,
889
+ port: number,
890
+ secure: tls.ConnectionOptions | false,
891
+ signal: AbortSignal,
892
+ ) {
893
+ return (_options: unknown, done: (error: Error | null, socket: Duplex) => void) => {
894
+ connectThrough(proxy, address, port, { signal }).then(
895
+ (tunnel) => done(null, secure ? tls.connect({ ...secure, socket: tunnel }) : tunnel),
896
+ (error: ProxyError) =>
897
+ done(new Refused(error.message, { cause: error }), undefined as unknown as Duplex),
898
+ );
899
+ return undefined;
900
+ };
901
+ }
902
+
829
903
  /**
830
904
  * One HTTP exchange with `address`, the URL's host pinned to it: its
831
905
  * status and headers, and its body if `read` and the status is a
@@ -877,28 +951,41 @@ export class Network {
877
951
 
878
952
  const https = url.protocol === "https:";
879
953
  const family = isIP(address);
954
+ const port = Number(url.port || (https ? 443 : 80));
955
+ const secure: tls.ConnectionOptions = {
956
+ servername: isIP(host) ? undefined : host,
957
+ ca: this.ca,
958
+ // Only where the user asked for this host: its certificate goes unchecked.
959
+ ...(this.unverified(host) ? { rejectUnauthorized: false } : {}),
960
+ };
880
961
  let request: ClientRequest;
881
962
  try {
882
963
  request = (https ? httpsRequest : httpRequest)({
883
964
  host,
884
- port: url.port || (https ? 443 : 80),
965
+ port,
885
966
  path: `${url.pathname}${url.search}`,
886
967
  method,
887
968
  headers: outgoing,
888
- agent: false,
889
- // The address pinned for this request, whatever the resolver would say now.
890
- lookup: (_name, opts, callback) => {
891
- if ((opts as { all?: boolean }).all) {
892
- (callback as (e: null, a: { address: string; family: number }[]) => void)(null, [
893
- { address, family },
894
- ]);
895
- } else {
896
- (callback as (e: null, a: string, f: number) => void)(null, address, family);
897
- }
898
- },
899
- ...(https ? { servername: isIP(host) ? undefined : host, ca: this.ca } : {}),
900
- // Only where the user asked for this host: its certificate goes unchecked.
901
- ...(https && this.unverified(host) ? { rejectUnauthorized: false } : {}),
969
+ ...(this.proxy
970
+ ? // Through the proxy to the pinned address, TLS over the tunnel for https; the
971
+ // request's own timers and signal bound the proxy's part too. No agent: the
972
+ // connection is this one's.
973
+ { createConnection: this.tunnel(this.proxy, address, port, https && secure, signal) }
974
+ : {
975
+ agent: false,
976
+ // The address pinned for this request, whatever the resolver would say now.
977
+ lookup: (_name, opts, callback) => {
978
+ if ((opts as { all?: boolean }).all) {
979
+ (callback as (e: null, a: { address: string; family: number }[]) => void)(
980
+ null,
981
+ [{ address, family }],
982
+ );
983
+ } else {
984
+ (callback as (e: null, a: string, f: number) => void)(null, address, family);
985
+ }
986
+ },
987
+ ...(https ? secure : {}),
988
+ }),
902
989
  });
903
990
  } catch (error) {
904
991
  // What check() let through and Node still refuses: a refusal like any other.
@@ -931,7 +1018,10 @@ export class Network {
931
1018
  const abort = () => stop(new Refused("aborted"));
932
1019
  signal.addEventListener("abort", abort, { once: true });
933
1020
  arm();
934
- request.on("error", (error) => stop(new Refused(error.message)));
1021
+ // A proxy's refusal comes as it was made, with its cause.
1022
+ request.on("error", (error) =>
1023
+ stop(error instanceof Refused ? error : new Refused(error.message)),
1024
+ );
935
1025
  request.on("response", (response: IncomingMessage) => {
936
1026
  const status = response.statusCode ?? 0;
937
1027
  const received: [string, string][] = [];
@@ -1195,11 +1285,24 @@ function cached<T>(
1195
1285
 
1196
1286
  value = make();
1197
1287
  cache.set(key, value);
1288
+ // A failure, a proxy's, is not kept: the next request asks again.
1289
+ const made = value;
1290
+ made.catch(() => {
1291
+ if (cache.get(key) === made) {
1292
+ cache.delete(key);
1293
+ }
1294
+ });
1198
1295
  }
1199
1296
 
1200
1297
  return value;
1201
1298
  }
1202
1299
 
1300
+ /** Whether `error` is a refusal for a proxy that failed, not a server it did not reach. */
1301
+ function proxyFault(error: unknown): boolean {
1302
+ const cause = (error as Error | null)?.cause;
1303
+ return cause instanceof ProxyError && cause.fault;
1304
+ }
1305
+
1203
1306
  function describe(requester: Requester): string {
1204
1307
  return requester.host === null ? 'a local SWF (domain="*")' : requester.host;
1205
1308
  }