@design.estate/wcctools 6.2.0 → 7.0.0

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 (67) hide show
  1. package/changelog.md +21 -0
  2. package/dist_shell/bundle.js +4 -4
  3. package/dist_shell/bundle.js.map +1 -1
  4. package/dist_ts/00_commitinfo_data.js +1 -1
  5. package/dist_ts/capture/classes.capturebrowser.d.ts +65 -0
  6. package/dist_ts/capture/classes.capturebrowser.js +251 -0
  7. package/dist_ts/capture/classes.captureservice.d.ts +39 -0
  8. package/dist_ts/capture/classes.captureservice.js +119 -0
  9. package/dist_ts/capture/classes.screencast.d.ts +47 -0
  10. package/dist_ts/capture/classes.screencast.js +127 -0
  11. package/dist_ts/capture/errors.d.ts +14 -0
  12. package/dist_ts/capture/errors.js +23 -0
  13. package/dist_ts/capture/images.d.ts +3 -0
  14. package/dist_ts/capture/images.js +58 -0
  15. package/dist_ts/capture/index.d.ts +4 -0
  16. package/dist_ts/capture/index.js +5 -0
  17. package/dist_ts/capture/interaction.d.ts +11 -0
  18. package/dist_ts/capture/interaction.js +138 -0
  19. package/dist_ts/capture/navigationguard.d.ts +44 -0
  20. package/dist_ts/capture/navigationguard.js +129 -0
  21. package/dist_ts/capture/previewpage.d.ts +29 -0
  22. package/dist_ts/capture/previewpage.js +96 -0
  23. package/dist_ts/capture/request.d.ts +69 -0
  24. package/dist_ts/capture/request.js +239 -0
  25. package/dist_ts/classes.devserver.d.ts +25 -9
  26. package/dist_ts/classes.devserver.js +45 -14
  27. package/dist_ts/classes.hostpolicy.d.ts +63 -0
  28. package/dist_ts/classes.hostpolicy.js +171 -0
  29. package/dist_ts/cli.js +25 -3
  30. package/dist_ts/cli.screenshot.d.ts +6 -0
  31. package/dist_ts/cli.screenshot.js +89 -0
  32. package/dist_ts/plugins.d.ts +6 -2
  33. package/dist_ts/plugins.js +8 -3
  34. package/dist_ts_interfaces/capture.d.ts +151 -0
  35. package/dist_ts_interfaces/capture.js +2 -0
  36. package/dist_ts_interfaces/index.d.ts +1 -0
  37. package/dist_ts_interfaces/index.js +2 -1
  38. package/dist_ts_interfaces/requests.d.ts +24 -0
  39. package/dist_ts_shell/elements/wcc-recording-panel.d.ts +9 -0
  40. package/dist_ts_shell/elements/wcc-recording-panel.js +12 -1
  41. package/dist_ts_shell/services/framesampler.service.d.ts +23 -0
  42. package/dist_ts_shell/services/framesampler.service.js +101 -0
  43. package/dist_ts_web/00_commitinfo_data.js +1 -1
  44. package/package.json +2 -1
  45. package/readme.md +104 -6
  46. package/ts/00_commitinfo_data.ts +1 -1
  47. package/ts/capture/classes.capturebrowser.ts +290 -0
  48. package/ts/capture/classes.captureservice.ts +152 -0
  49. package/ts/capture/classes.screencast.ts +152 -0
  50. package/ts/capture/errors.ts +25 -0
  51. package/ts/capture/images.ts +61 -0
  52. package/ts/capture/index.ts +4 -0
  53. package/ts/capture/interaction.ts +146 -0
  54. package/ts/capture/navigationguard.ts +147 -0
  55. package/ts/capture/previewpage.ts +130 -0
  56. package/ts/capture/request.ts +304 -0
  57. package/ts/classes.devserver.ts +55 -13
  58. package/ts/classes.hostpolicy.ts +201 -0
  59. package/ts/cli.screenshot.ts +95 -0
  60. package/ts/cli.ts +25 -2
  61. package/ts/plugins.ts +9 -2
  62. package/ts_interfaces/capture.ts +144 -0
  63. package/ts_interfaces/index.ts +1 -0
  64. package/ts_interfaces/requests.ts +39 -0
  65. package/ts_shell/elements/wcc-recording-panel.ts +12 -0
  66. package/ts_shell/services/framesampler.service.ts +112 -0
  67. package/ts_web/00_commitinfo_data.ts +1 -1
@@ -2,6 +2,8 @@ import * as plugins from './plugins.js';
2
2
  import * as paths from './paths.js';
3
3
  import { commitinfo } from './00_commitinfo_data.js';
4
4
  import { WccDevApi } from './classes.devapi.js';
5
+ import { WccHostPolicy, defaultListenHost, formatUrlHost } from './classes.hostpolicy.js';
6
+ import { WccCaptureService } from './capture/index.js';
5
7
  import { loadShellContent, shellPathPrefix } from './shellcontent.js';
6
8
  /** tswatch's default port and serve directory, used when the configuration names none. */
7
9
  const defaultPort = 3002;
@@ -11,6 +13,7 @@ const securityHeaders = {
11
13
  crossOriginOpenerPolicy: 'same-origin',
12
14
  crossOriginEmbedderPolicy: 'require-corp',
13
15
  };
16
+ /** Every surface takes every hostname: the server-wide Host admission refuses the ones it does not serve. */
14
17
  const acceptAnyHostname = () => true;
15
18
  /**
16
19
  * `wcctools dev`: bundles the catalog of a project with tswatch, as its `@git.zone/tswatch`
@@ -19,7 +22,12 @@ const acceptAnyHostname = () => true;
19
22
  * - the shell on `/wcctools/` (`/` redirects there) and its routes `/wcctools-route/...`,
20
23
  * - the typed API on `/wcctools/typedrequest`, including the bundles' status,
21
24
  * - the catalog's preview document and bundle from the tswatch serve directory on every other
22
- * path, with live reload that reloads only the preview document.
25
+ * path, with live reload that reloads only the preview document,
26
+ * - captures of single demos in a headless browser (`capture`, also on the typed API).
27
+ *
28
+ * It listens on loopback unless told otherwise and admits requests as its `WccHostPolicy`
29
+ * describes: only the hosts it serves, and state-changing requests and WebSockets only from its
30
+ * own origin.
23
31
  *
24
32
  * The server owns no process lifecycle: while it runs, a process shutdown through smartexit's
25
33
  * `ProcessLifecycle` stops it. The entry point installs that lifecycle with
@@ -29,6 +37,10 @@ export class WccDevServer {
29
37
  options;
30
38
  /** The typed API on `/wcctools/typedrequest`; register further handlers before `start()`. */
31
39
  api = new WccDevApi();
40
+ /** Screenshots and interaction captures of the served catalog; available while the server runs. */
41
+ capture;
42
+ /** Who may talk to the server: the hosts it answers and the origin of its requests. */
43
+ hostPolicy;
32
44
  /** The project directory everything resolves against. */
33
45
  cwd;
34
46
  /** The project's tswatch configuration, or the `element` preset; read once, on construction. */
@@ -48,6 +60,9 @@ export class WccDevServer {
48
60
  const configHandler = new plugins.tswatch.ConfigHandler(this.cwd);
49
61
  this.config = configHandler.loadConfig() ?? this.getElementPreset(configHandler);
50
62
  this.tsWatch = new plugins.tswatch.TsWatch({ ...this.config, server: { enabled: false } }, { cwd: this.cwd });
63
+ this.hostPolicy = new WccHostPolicy({ listenHost: options.host ?? defaultListenHost, allowedHosts: options.allowedHosts });
64
+ this.capture = new WccCaptureService(this.api.bundleStatus);
65
+ this.capture.addTypedHandlers(this.api.typedrouter);
51
66
  }
52
67
  /**
53
68
  * The shutdown deadline the server needs, tswatch's: the entry point installs smartexit's
@@ -113,9 +128,12 @@ export class WccDevServer {
113
128
  // requests until the server stops listening are refused too: the shell backs off
114
129
  this.api.bundleStatus.close();
115
130
  try {
131
+ // Captures end and the capture browser closes before the server stops answering its pages
132
+ await this.capture.close();
116
133
  await typedServer?.stop();
117
134
  }
118
135
  finally {
136
+ this.hostPolicy.setPort(null);
119
137
  try {
120
138
  await this.tsWatch.stop();
121
139
  }
@@ -135,7 +153,15 @@ export class WccDevServer {
135
153
  projectName: await this.readProjectName(),
136
154
  });
137
155
  const surfaces = [await this.createShellSurface(), this.createPreviewSurface(serveDir)];
138
- const typedServer = new plugins.typedserver.TypedServer({ cors: false, port, surfaces });
156
+ const typedServer = new plugins.typedserver.TypedServer({
157
+ cors: false,
158
+ port,
159
+ listenHostname: this.hostPolicy.listenHost,
160
+ // Requests and WebSocket upgrades for a Host the server does not serve are refused
161
+ requestAdmission: this.hostPolicy.admitHost,
162
+ websocketAdmission: this.hostPolicy.admitHost,
163
+ surfaces,
164
+ });
139
165
  this.typedServer = typedServer;
140
166
  this.api.bundleStatus.open();
141
167
  const bundleStatus = this.api.bundleStatus;
@@ -144,7 +170,14 @@ export class WccDevServer {
144
170
  await this.tsWatch.start();
145
171
  await typedServer.start();
146
172
  const boundPort = typedServer.listeningPort ?? port;
147
- return { port: boundPort, url: `http://localhost:${boundPort}${shellPathPrefix}/` };
173
+ this.hostPolicy.setPort(boundPort);
174
+ this.capture.open(`http://${formatUrlHost(this.hostPolicy.getLocalHost())}:${boundPort}`);
175
+ return {
176
+ port: boundPort,
177
+ url: `http://${formatUrlHost(this.hostPolicy.getDisplayHost())}:${boundPort}${shellPathPrefix}/`,
178
+ host: this.hostPolicy.listenHost,
179
+ allowedHosts: [...this.hostPolicy.allowedHostnames],
180
+ };
148
181
  }
149
182
  getElementPreset(configHandlerArg) {
150
183
  const preset = configHandlerArg.getPreset('element');
@@ -154,14 +187,8 @@ export class WccDevServer {
154
187
  return preset;
155
188
  }
156
189
  /**
157
- * The shell: the prebuilt shell bundle and the typed API.
158
- *
159
- * Seam for side-effecting APIs (assistant, capture, standards fixes): before any handler with
160
- * side effects is registered here, the server binds to localhost by default (another interface
161
- * only on explicit request), validates the Host header against the addresses it serves instead
162
- * of accepting any hostname, and admits typedsocket connections and typed requests only from the
163
- * shell's own origin (a `websocketAdmission`/`requestAdmission`), keeping surface mode's strict
164
- * authority validation. Until then every handler here is read-only.
190
+ * The shell: the prebuilt shell bundle and the typed API, whose requests (captures among them)
191
+ * are admitted only from the server's own origin.
165
192
  */
166
193
  async createShellSurface() {
167
194
  return {
@@ -174,6 +201,8 @@ export class WccDevServer {
174
201
  spaFallback: true,
175
202
  httpTypedRouter: this.api.typedrouter,
176
203
  typedRequestPath: `${shellPathPrefix}/typedrequest`,
204
+ requestAdmission: this.hostPolicy.admitRequestOrigin,
205
+ websocketAdmission: this.hostPolicy.admitWebsocketOrigin,
177
206
  noCache: true,
178
207
  securityHeaders,
179
208
  };
@@ -196,8 +225,10 @@ export class WccDevServer {
196
225
  watch: true,
197
226
  // Lets the reload client get pushed changes instead of polling
198
227
  websocketTypedRouter: new plugins.typedrequest.TypedRouter(),
199
- // As tswatch serves catalogs
200
- cors: true,
228
+ requestAdmission: this.hostPolicy.admitRequestOrigin,
229
+ websocketAdmission: this.hostPolicy.admitWebsocketOrigin,
230
+ // Shell and preview share one origin, so no other origin may read the catalog
231
+ cors: false,
201
232
  httpHandler: async (contextArg) => {
202
233
  if (contextArg.path === '/' && (contextArg.method === 'GET' || contextArg.method === 'HEAD')) {
203
234
  return new Response(null, { status: 302, headers: { Location: `${shellPathPrefix}/` } });
@@ -213,4 +244,4 @@ export class WccDevServer {
213
244
  return packageJson.name ?? plugins.path.basename(this.cwd);
214
245
  }
215
246
  }
216
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5kZXZzZXJ2ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9jbGFzc2VzLmRldnNlcnZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEtBQUssS0FBSyxNQUFNLFlBQVksQ0FBQztBQUNwQyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDckQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ2hELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxlQUFlLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQWtCdEUsMEZBQTBGO0FBQzFGLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQztBQUN6QixNQUFNLGVBQWUsR0FBRyxlQUFlLENBQUM7QUFFeEMsOEZBQThGO0FBQzlGLE1BQU0sZUFBZSxHQUF5QztJQUM1RCx1QkFBdUIsRUFBRSxhQUFhO0lBQ3RDLHlCQUF5QixFQUFFLGNBQWM7Q0FDMUMsQ0FBQztBQUVGLE1BQU0saUJBQWlCLEdBQUcsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDO0FBRXJDOzs7Ozs7Ozs7Ozs7R0FZRztBQUNILE1BQU0sT0FBTyxZQUFZO0lBaUJNO0lBaEI3Qiw2RkFBNkY7SUFDN0UsR0FBRyxHQUFHLElBQUksU0FBUyxFQUFFLENBQUM7SUFDdEMseURBQXlEO0lBQ3pDLEdBQUcsQ0FBUztJQUM1QixnR0FBZ0c7SUFDL0UsTUFBTSxDQUFpQztJQUN4RCxzRUFBc0U7SUFDckQsT0FBTyxDQUEwQjtJQUMxQyxXQUFXLEdBQTJDLElBQUksQ0FBQztJQUNuRSx5RkFBeUY7SUFDakYsU0FBUyxHQUF1QyxJQUFJLENBQUM7SUFDckQsdUJBQXVCLEdBQXdCLElBQUksQ0FBQztJQUNwRCxPQUFPLEdBQUcsS0FBSyxDQUFDO0lBQ2hCLFFBQVEsR0FBeUMsSUFBSSxDQUFDO0lBQ3RELFFBQVEsR0FBeUIsSUFBSSxDQUFDO0lBRTlDLFlBQTZCLFVBQWdDLEVBQUU7UUFBbEMsWUFBTyxHQUFQLE9BQU8sQ0FBMkI7UUFDN0QsSUFBSSxDQUFDLEdBQUcsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxJQUFJLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDO1FBQzlELE1BQU0sYUFBYSxHQUFHLElBQUksT0FBTyxDQUFDLE9BQU8sQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ2xFLElBQUksQ0FBQyxNQUFNLEdBQUcsYUFBYSxDQUFDLFVBQVUsRUFBRSxJQUFJLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUNqRixJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksT0FBTyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsRUFBRSxHQUFHLElBQUksQ0FBQyxNQUFNLEVBQUUsTUFBTSxFQUFFLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxFQUFFLEVBQUUsRUFBRSxHQUFHLEVBQUUsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUM7SUFDaEgsQ0FBQztJQUVEOzs7T0FHRztJQUNJLG9CQUFvQjtRQUN6QixPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsb0JBQW9CLEVBQUUsQ0FBQztJQUM3QyxDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksS0FBSyxDQUFDLEtBQUs7UUFDaEIsSUFBSSxJQUFJLENBQUMsT0FBTyxJQUFJLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNsQyxNQUFNLElBQUksS0FBSyxDQUFDLGdFQUFnRSxDQUFDLENBQUM7UUFDcEYsQ0FBQztRQUNELElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDO1FBQ3BCLG9GQUFvRjtRQUNwRixNQUFNLFNBQVMsR0FBRyxJQUFJLE9BQU8sQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7UUFDcEUsU0FBUyxDQUFDLGtCQUFrQixDQUFDLEdBQUcsRUFBRSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQ2hELElBQUksQ0FBQyxTQUFTLEdBQUcsU0FBUyxDQUFDO1FBQzNCLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxZQUFZLEVBQUUsQ0FBQztRQUNyQyxJQUFJLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQztRQUN6QixJQUFJLENBQUM7WUFDSCxNQUFNLE9BQU8sR0FBRyxNQUFNLFFBQVEsQ0FBQztZQUMvQixJQUFJLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztnQkFDbEIsTUFBTSxJQUFJLEtBQUssQ0FBQyxzREFBc0QsQ0FBQyxDQUFDO1lBQzFFLENBQUM7WUFDRCxPQUFPLE9BQU8sQ0FBQztRQUNqQixDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLHNEQUFzRDtZQUN0RCxNQUFNLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUNsQixNQUFNLEtBQUssQ0FBQztRQUNkLENBQUM7Z0JBQVMsQ0FBQztZQUNULElBQUksSUFBSSxDQUFDLFFBQVEsS0FBSyxRQUFRLEVBQUUsQ0FBQztnQkFDL0IsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUM7WUFDdkIsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNJLElBQUk7UUFDVCxJQUFJLENBQUMsUUFBUSxLQUFLLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFO1lBQzVDLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxDQUFDO1FBQ3ZCLENBQUMsQ0FBQyxDQUFDO1FBQ0gsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDO0lBQ3ZCLENBQUM7SUFFTyxLQUFLLENBQUMsT0FBTztRQUNuQixNQUFNLElBQUksQ0FBQyxRQUFRLEVBQUUsS0FBSyxDQUFDLEdBQUcsRUFBRSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzVDLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUM7UUFDakMsTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQztRQUNyQyxNQUFNLHVCQUF1QixHQUFHLElBQUksQ0FBQyx1QkFBdUIsQ0FBQztRQUM3RCxJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksQ0FBQztRQUN0QixJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQztRQUN4QixJQUFJLENBQUMsdUJBQXVCLEdBQUcsSUFBSSxDQUFDO1FBQ3BDLFNBQVMsRUFBRSxVQUFVLEVBQUUsQ0FBQztRQUN4Qiw2RkFBNkY7UUFDN0YsaUZBQWlGO1FBQ2pGLElBQUksQ0FBQyxHQUFHLENBQUMsWUFBWSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQzlCLElBQUksQ0FBQztZQUNILE1BQU0sV0FBVyxFQUFFLElBQUksRUFBRSxDQUFDO1FBQzVCLENBQUM7Z0JBQVMsQ0FBQztZQUNULElBQUksQ0FBQztnQkFDSCxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDNUIsQ0FBQztvQkFBUyxDQUFDO2dCQUNULHVCQUF1QixFQUFFLEVBQUUsQ0FBQztnQkFDNUIsSUFBSSxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUM7WUFDdkIsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRU8sS0FBSyxDQUFDLFlBQVk7UUFDeEIsTUFBTSxRQUFRLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRSxRQUFRLElBQUksZUFBZSxDQUFDLENBQUM7UUFDakcsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLElBQUksSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsSUFBSSxJQUFJLFdBQVcsQ0FBQztRQUUxRSw0RkFBNEY7UUFDNUYsTUFBTSxPQUFPLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxRQUFRLEVBQUUsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUN0RCxJQUFJLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQztZQUNyQixlQUFlLEVBQUUsVUFBVSxDQUFDLE9BQU87WUFDbkMsV0FBVyxFQUFFLE1BQU0sSUFBSSxDQUFDLGVBQWUsRUFBRTtTQUMxQyxDQUFDLENBQUM7UUFDSCxNQUFNLFFBQVEsR0FBRyxDQUFDLE1BQU0sSUFBSSxDQUFDLGtCQUFrQixFQUFFLEVBQUUsSUFBSSxDQUFDLG9CQUFvQixDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUM7UUFDeEYsTUFBTSxXQUFXLEdBQUcsSUFBSSxPQUFPLENBQUMsV0FBVyxDQUFDLFdBQVcsQ0FBQyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDekYsSUFBSSxDQUFDLFdBQVcsR0FBRyxXQUFXLENBQUM7UUFFL0IsSUFBSSxDQUFDLEdBQUcsQ0FBQyxZQUFZLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDN0IsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxZQUFZLENBQUM7UUFDM0MsSUFBSSxDQUFDLHVCQUF1QixHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsYUFBYSxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUUsQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUM7UUFDdkcsOEVBQThFO1FBQzlFLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUMzQixNQUFNLFdBQVcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUMxQixNQUFNLFNBQVMsR0FBRyxXQUFXLENBQUMsYUFBYSxJQUFJLElBQUksQ0FBQztRQUNwRCxPQUFPLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUUsb0JBQW9CLFNBQVMsR0FBRyxlQUFlLEdBQUcsRUFBRSxDQUFDO0lBQ3RGLENBQUM7SUFFTyxnQkFBZ0IsQ0FBQyxnQkFBK0M7UUFDdEUsTUFBTSxNQUFNLEdBQUcsZ0JBQWdCLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQ3JELElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNaLE1BQU0sSUFBSSxLQUFLLENBQUMsaURBQWlELENBQUMsQ0FBQztRQUNyRSxDQUFDO1FBQ0QsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUVEOzs7Ozs7Ozs7T0FTRztJQUNLLEtBQUssQ0FBQyxrQkFBa0I7UUFDOUIsT0FBTztZQUNMLElBQUksRUFBRSxPQUFPO1lBQ2IsS0FBSyxFQUFFO2dCQUNMLGdCQUFnQixFQUFFLGlCQUFpQjtnQkFDbkMsWUFBWSxFQUFFLENBQUMsZUFBZSxFQUFFLGlCQUFpQixDQUFDO2FBQ25EO1lBQ0QsY0FBYyxFQUFFLE1BQU0sZ0JBQWdCLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQztZQUMxRCxXQUFXLEVBQUUsSUFBSTtZQUNqQixlQUFlLEVBQUUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxXQUFXO1lBQ3JDLGdCQUFnQixFQUFFLEdBQUcsZUFBZSxlQUFlO1lBQ25ELE9BQU8sRUFBRSxJQUFJO1lBQ2IsZUFBZTtTQUNoQixDQUFDO0lBQ0osQ0FBQztJQUVEOzs7O09BSUc7SUFDSyxvQkFBb0IsQ0FBQyxXQUFtQjtRQUM5QyxPQUFPO1lBQ0wsSUFBSSxFQUFFLFNBQVM7WUFDZixLQUFLLEVBQUU7Z0JBQ0wsZ0JBQWdCLEVBQUUsaUJBQWlCO2dCQUNuQyxZQUFZLEVBQUUsQ0FBQyxHQUFHLENBQUM7YUFDcEI7WUFDRCxRQUFRLEVBQUUsV0FBVztZQUNyQixXQUFXLEVBQUUsSUFBSTtZQUNqQixZQUFZLEVBQUUsSUFBSTtZQUNsQixLQUFLLEVBQUUsSUFBSTtZQUNYLCtEQUErRDtZQUMvRCxvQkFBb0IsRUFBRSxJQUFJLE9BQU8sQ0FBQyxZQUFZLENBQUMsV0FBVyxFQUFFO1lBQzVELDZCQUE2QjtZQUM3QixJQUFJLEVBQUUsSUFBSTtZQUNWLFdBQVcsRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLEVBQUU7Z0JBQ2hDLElBQUksVUFBVSxDQUFDLElBQUksS0FBSyxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsTUFBTSxLQUFLLEtBQUssSUFBSSxVQUFVLENBQUMsTUFBTSxLQUFLLE1BQU0sQ0FBQyxFQUFFLENBQUM7b0JBQzdGLE9BQU8sSUFBSSxRQUFRLENBQUMsSUFBSSxFQUFFLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsRUFBRSxRQUFRLEVBQUUsR0FBRyxlQUFlLEdBQUcsRUFBRSxFQUFFLENBQUMsQ0FBQztnQkFDM0YsQ0FBQztnQkFDRCxPQUFPLElBQUksQ0FBQztZQUNkLENBQUM7WUFDRCxPQUFPLEVBQUUsSUFBSTtZQUNiLGVBQWU7U0FDaEIsQ0FBQztJQUNKLENBQUM7SUFFTyxLQUFLLENBQUMsZUFBZTtRQUMzQixNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sT0FBTyxDQUFDLEVBQUUsQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxjQUFjLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBc0IsQ0FBQztRQUNwSSxPQUFPLFdBQVcsQ0FBQyxJQUFJLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQzdELENBQUM7Q0FDRiJ9
247
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5kZXZzZXJ2ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9jbGFzc2VzLmRldnNlcnZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEtBQUssS0FBSyxNQUFNLFlBQVksQ0FBQztBQUNwQyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDckQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ2hELE9BQU8sRUFBRSxhQUFhLEVBQUUsaUJBQWlCLEVBQUUsYUFBYSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDMUYsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDdkQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLGVBQWUsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBNkJ0RSwwRkFBMEY7QUFDMUYsTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDO0FBQ3pCLE1BQU0sZUFBZSxHQUFHLGVBQWUsQ0FBQztBQUV4Qyw4RkFBOEY7QUFDOUYsTUFBTSxlQUFlLEdBQXlDO0lBQzVELHVCQUF1QixFQUFFLGFBQWE7SUFDdEMseUJBQXlCLEVBQUUsY0FBYztDQUMxQyxDQUFDO0FBRUYsNkdBQTZHO0FBQzdHLE1BQU0saUJBQWlCLEdBQUcsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDO0FBRXJDOzs7Ozs7Ozs7Ozs7Ozs7OztHQWlCRztBQUNILE1BQU0sT0FBTyxZQUFZO0lBcUJNO0lBcEI3Qiw2RkFBNkY7SUFDN0UsR0FBRyxHQUFHLElBQUksU0FBUyxFQUFFLENBQUM7SUFDdEMsbUdBQW1HO0lBQ25GLE9BQU8sQ0FBb0I7SUFDM0MsdUZBQXVGO0lBQ3ZFLFVBQVUsQ0FBZ0I7SUFDMUMseURBQXlEO0lBQ3pDLEdBQUcsQ0FBUztJQUM1QixnR0FBZ0c7SUFDL0UsTUFBTSxDQUFpQztJ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
@@ -0,0 +1,63 @@
1
+ import * as plugins from './plugins.js';
2
+ type TRequestContext = plugins.typedserver.IRequestContext;
3
+ type TAdmissionResult = plugins.typedserver.TRequestAdmissionResult;
4
+ /** The interface address `wcctools dev` listens on unless told otherwise. */
5
+ export declare const defaultListenHost = "127.0.0.1";
6
+ /** Canonical form of a hostname: lower case, IPv6 without brackets, no trailing dot. */
7
+ export declare const normalizeHostname: (hostnameArg: string) => string;
8
+ /** Whether the address is the wildcard address of all interfaces. */
9
+ export declare const isWildcardHost: (hostArg: string) => boolean;
10
+ /** Whether a peer address is a loopback address (IPv4, IPv6 or IPv4-mapped IPv6). */
11
+ export declare const isLoopbackAddress: (addressArg: string) => boolean;
12
+ /** A hostname as it appears in a URL: IPv6 addresses in brackets. */
13
+ export declare const formatUrlHost: (hostnameArg: string) => string;
14
+ /** An invalid `--host` or `--allowed-host` value. */
15
+ export declare class WccHostPolicyError extends Error {
16
+ constructor(messageArg: string);
17
+ }
18
+ export interface IWccHostPolicyOptions {
19
+ /** The interface address the server listens on. */
20
+ listenHost: string;
21
+ /** Further hostnames or addresses the server answers (`--allowed-host`). */
22
+ allowedHosts?: readonly string[];
23
+ }
24
+ /**
25
+ * Who may talk to a `wcctools dev` server. The server listens on loopback unless told otherwise
26
+ * and answers only the hostnames it serves: the loopback names, the address it listens on, the
27
+ * machine's interface addresses when it listens on all of them, and the hosts allowed explicitly.
28
+ * Every other Host is refused, which defeats DNS rebinding. Requests that can change state
29
+ * (any method but GET, HEAD and OPTIONS) and WebSocket connections are further admitted only from
30
+ * the server's own origin, so other web pages cannot drive the API; a request without an Origin
31
+ * is admitted only from a loopback peer (local tools, such as the CLI and tests).
32
+ */
33
+ export declare class WccHostPolicy {
34
+ /** The canonical hostnames the server answers. */
35
+ readonly allowedHostnames: ReadonlySet<string>;
36
+ readonly listenHost: string;
37
+ private port;
38
+ constructor(optionsArg: IWccHostPolicyOptions);
39
+ /** A hostname or address, as a URL would carry it; anything else is refused. */
40
+ private static parseHost;
41
+ /** Every address of the machine's network interfaces. */
42
+ private static getInterfaceAddresses;
43
+ /** The port the server listens on, once it does: origins are compared against it. */
44
+ setPort(portArg: number | null): void;
45
+ /**
46
+ * The host the server's own tools reach it on: the loopback address when it listens on all
47
+ * interfaces or on loopback, otherwise the address it listens on.
48
+ */
49
+ getLocalHost(): string;
50
+ /** The host shown to the user: `localhost` for a loopback or all-interface server. */
51
+ getDisplayHost(): string;
52
+ /** Server-wide HTTP and WebSocket admission: the Host must be one the server answers. */
53
+ readonly admitHost: (contextArg: TRequestContext) => TAdmissionResult;
54
+ /** Surface HTTP admission: a request that can change state must come from the server's origin. */
55
+ readonly admitRequestOrigin: (contextArg: TRequestContext) => TAdmissionResult;
56
+ /** Surface WebSocket admission: a connection must come from the server's origin. */
57
+ readonly admitWebsocketOrigin: (contextArg: TRequestContext) => TAdmissionResult;
58
+ /** Whether an Origin header names this server: http, an allowed host and the server's port. */
59
+ isOwnOrigin(originArg: string): boolean;
60
+ private admitOrigin;
61
+ private refuse;
62
+ }
63
+ export {};
@@ -0,0 +1,171 @@
1
+ import * as plugins from './plugins.js';
2
+ /** The interface address `wcctools dev` listens on unless told otherwise. */
3
+ export const defaultListenHost = '127.0.0.1';
4
+ /** The hostnames every dev server answers: the loopback names. */
5
+ const loopbackHostnames = ['localhost', '127.0.0.1', '::1'];
6
+ const wildcardHosts = new Set(['0.0.0.0', '::']);
7
+ /** Methods that never change state, which the Origin check leaves to the Host check alone. */
8
+ const safeMethods = new Set(['GET', 'HEAD', 'OPTIONS']);
9
+ /** Canonical form of a hostname: lower case, IPv6 without brackets, no trailing dot. */
10
+ export const normalizeHostname = (hostnameArg) => {
11
+ let hostname = hostnameArg.trim().toLowerCase();
12
+ if (hostname.startsWith('[') && hostname.endsWith(']')) {
13
+ hostname = hostname.slice(1, -1);
14
+ }
15
+ if (hostname.endsWith('.') && hostname.length > 1) {
16
+ hostname = hostname.slice(0, -1);
17
+ }
18
+ return hostname;
19
+ };
20
+ /** Whether the address is the wildcard address of all interfaces. */
21
+ export const isWildcardHost = (hostArg) => wildcardHosts.has(normalizeHostname(hostArg));
22
+ /** Whether a peer address is a loopback address (IPv4, IPv6 or IPv4-mapped IPv6). */
23
+ export const isLoopbackAddress = (addressArg) => {
24
+ const address = normalizeHostname(addressArg);
25
+ return address === '::1' || address.startsWith('127.') || address.startsWith('::ffff:127.');
26
+ };
27
+ /** A hostname as it appears in a URL: IPv6 addresses in brackets. */
28
+ export const formatUrlHost = (hostnameArg) => {
29
+ return hostnameArg.includes(':') ? `[${hostnameArg}]` : hostnameArg;
30
+ };
31
+ /** An invalid `--host` or `--allowed-host` value. */
32
+ export class WccHostPolicyError extends Error {
33
+ constructor(messageArg) {
34
+ super(messageArg);
35
+ this.name = 'WccHostPolicyError';
36
+ }
37
+ }
38
+ /**
39
+ * Who may talk to a `wcctools dev` server. The server listens on loopback unless told otherwise
40
+ * and answers only the hostnames it serves: the loopback names, the address it listens on, the
41
+ * machine's interface addresses when it listens on all of them, and the hosts allowed explicitly.
42
+ * Every other Host is refused, which defeats DNS rebinding. Requests that can change state
43
+ * (any method but GET, HEAD and OPTIONS) and WebSocket connections are further admitted only from
44
+ * the server's own origin, so other web pages cannot drive the API; a request without an Origin
45
+ * is admitted only from a loopback peer (local tools, such as the CLI and tests).
46
+ */
47
+ export class WccHostPolicy {
48
+ /** The canonical hostnames the server answers. */
49
+ allowedHostnames;
50
+ listenHost;
51
+ port = null;
52
+ constructor(optionsArg) {
53
+ this.listenHost = WccHostPolicy.parseHost(optionsArg.listenHost);
54
+ const hostnames = new Set(loopbackHostnames);
55
+ if (isWildcardHost(this.listenHost)) {
56
+ for (const address of WccHostPolicy.getInterfaceAddresses()) {
57
+ hostnames.add(address);
58
+ }
59
+ }
60
+ else {
61
+ hostnames.add(this.listenHost);
62
+ }
63
+ for (const host of optionsArg.allowedHosts ?? []) {
64
+ hostnames.add(WccHostPolicy.parseHost(host));
65
+ }
66
+ this.allowedHostnames = hostnames;
67
+ }
68
+ /** A hostname or address, as a URL would carry it; anything else is refused. */
69
+ static parseHost(hostArg) {
70
+ const hostname = normalizeHostname(hostArg);
71
+ let parsed = null;
72
+ try {
73
+ parsed = normalizeHostname(new URL(`http://${formatUrlHost(hostname)}/`).hostname);
74
+ }
75
+ catch {
76
+ parsed = null;
77
+ }
78
+ if (!hostname || parsed !== hostname) {
79
+ throw new WccHostPolicyError(`wcctools: "${hostArg}" is not a hostname or address.`);
80
+ }
81
+ return hostname;
82
+ }
83
+ /** Every address of the machine's network interfaces. */
84
+ static getInterfaceAddresses() {
85
+ const addresses = [];
86
+ for (const interfaceAddresses of Object.values(plugins.os.networkInterfaces())) {
87
+ for (const interfaceAddress of interfaceAddresses ?? []) {
88
+ addresses.push(normalizeHostname(interfaceAddress.address));
89
+ }
90
+ }
91
+ return addresses;
92
+ }
93
+ /** The port the server listens on, once it does: origins are compared against it. */
94
+ setPort(portArg) {
95
+ this.port = portArg;
96
+ }
97
+ /**
98
+ * The host the server's own tools reach it on: the loopback address when it listens on all
99
+ * interfaces or on loopback, otherwise the address it listens on.
100
+ */
101
+ getLocalHost() {
102
+ if (isWildcardHost(this.listenHost) || isLoopbackAddress(this.listenHost)) {
103
+ return '127.0.0.1';
104
+ }
105
+ return this.listenHost;
106
+ }
107
+ /** The host shown to the user: `localhost` for a loopback or all-interface server. */
108
+ getDisplayHost() {
109
+ if (isWildcardHost(this.listenHost) || isLoopbackAddress(this.listenHost) || this.listenHost === 'localhost') {
110
+ return 'localhost';
111
+ }
112
+ return this.listenHost;
113
+ }
114
+ /** Server-wide HTTP and WebSocket admission: the Host must be one the server answers. */
115
+ admitHost = (contextArg) => {
116
+ const hostname = normalizeHostname(contextArg.url.hostname);
117
+ if (this.allowedHostnames.has(hostname)) {
118
+ return true;
119
+ }
120
+ return this.refuse(`wcctools: the dev server does not answer the host "${hostname}". Open it on localhost, or restart it with --allowed-host ${hostname}.`);
121
+ };
122
+ /** Surface HTTP admission: a request that can change state must come from the server's origin. */
123
+ admitRequestOrigin = (contextArg) => {
124
+ if (safeMethods.has(contextArg.method)) {
125
+ return true;
126
+ }
127
+ return this.admitOrigin(contextArg);
128
+ };
129
+ /** Surface WebSocket admission: a connection must come from the server's origin. */
130
+ admitWebsocketOrigin = (contextArg) => {
131
+ return this.admitOrigin(contextArg);
132
+ };
133
+ /** Whether an Origin header names this server: http, an allowed host and the server's port. */
134
+ isOwnOrigin(originArg) {
135
+ if (this.port === null) {
136
+ return false;
137
+ }
138
+ let origin;
139
+ try {
140
+ origin = new URL(originArg);
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ const port = origin.port === '' ? 80 : Number(origin.port);
146
+ return origin.protocol === 'http:'
147
+ && port === this.port
148
+ && this.allowedHostnames.has(normalizeHostname(origin.hostname));
149
+ }
150
+ admitOrigin(contextArg) {
151
+ const origin = contextArg.headers.get('origin');
152
+ if (origin === null) {
153
+ const peer = contextArg.connectionInfo?.remoteAddr ?? 'unknown';
154
+ if (isLoopbackAddress(peer)) {
155
+ return true;
156
+ }
157
+ return this.refuse('wcctools: requests without an Origin are admitted only from this machine.');
158
+ }
159
+ if (this.isOwnOrigin(origin)) {
160
+ return true;
161
+ }
162
+ return this.refuse(`wcctools: requests from the origin "${origin}" are not admitted.`);
163
+ }
164
+ refuse(messageArg) {
165
+ return new Response(`${messageArg}\n`, {
166
+ status: 403,
167
+ headers: { 'Content-Type': 'text/plain; charset=utf-8' },
168
+ });
169
+ }
170
+ }
171
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ob3N0cG9saWN5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvY2xhc3Nlcy5ob3N0cG9saWN5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBS3hDLDZFQUE2RTtBQUM3RSxNQUFNLENBQUMsTUFBTSxpQkFBaUIsR0FBRyxXQUFXLENBQUM7QUFFN0Msa0VBQWtFO0FBQ2xFLE1BQU0saUJBQWlCLEdBQUcsQ0FBQyxXQUFXLEVBQUUsV0FBVyxFQUFFLEtBQUssQ0FBQyxDQUFDO0FBRTVELE1BQU0sYUFBYSxHQUFHLElBQUksR0FBRyxDQUFDLENBQUMsU0FBUyxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUM7QUFFakQsOEZBQThGO0FBQzlGLE1BQU0sV0FBVyxHQUFHLElBQUksR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFFLE1BQU0sRUFBRSxTQUFTLENBQUMsQ0FBQyxDQUFDO0FBRXhELHdGQUF3RjtBQUN4RixNQUFNLENBQUMsTUFBTSxpQkFBaUIsR0FBRyxDQUFDLFdBQW1CLEVBQVUsRUFBRTtJQUMvRCxJQUFJLFFBQVEsR0FBRyxXQUFXLENBQUMsSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDaEQsSUFBSSxRQUFRLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxJQUFJLFFBQVEsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUN2RCxRQUFRLEdBQUcsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNuQyxDQUFDO0lBQ0QsSUFBSSxRQUFRLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxJQUFJLFFBQVEsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7UUFDbEQsUUFBUSxHQUFHLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFD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
package/dist_ts/cli.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import { commitinfo } from './00_commitinfo_data.js';
3
3
  import { WccDevServer } from './classes.devserver.js';
4
+ import { WccHostPolicyError } from './classes.hostpolicy.js';
5
+ import { WccCaptureRequestError } from './capture/index.js';
6
+ import { registerScreenshotCommand } from './cli.screenshot.js';
4
7
  import { StandardConfigError, StandardUsageError, registerStandardCommands } from './standards/index.js';
5
8
  /**
6
9
  * Registers the wcctools commands on the given console.
@@ -19,15 +22,32 @@ export const registerCommands = (out) => {
19
22
  type: 'number',
20
23
  description: 'Port to listen on (0 picks a free port); overrides the tswatch configuration',
21
24
  },
25
+ host: {
26
+ type: 'string',
27
+ description: 'Interface address to listen on (default 127.0.0.1, this machine only; 0.0.0.0 for every interface)',
28
+ },
29
+ allowedHost: {
30
+ type: 'strings',
31
+ description: 'A further hostname or address the server answers, besides localhost and the listen address (repeatable)',
32
+ },
22
33
  },
23
34
  }, async ({ options }) => {
24
- const devServer = new WccDevServer({ port: options.port, cwd: process.cwd() });
35
+ const devServer = new WccDevServer({
36
+ port: options.port,
37
+ cwd: process.cwd(),
38
+ host: options.host,
39
+ allowedHosts: options.allowedHost,
40
+ });
25
41
  // The entry point owns process signals: SIGINT and SIGTERM run every registered cleanup, the
26
42
  // dev server's stop among them, once within tswatch's shutdown deadline, then exit.
27
43
  plugins.smartexit.ProcessLifecycle.install({ shutdownTimeoutMs: devServer.getShutdownTimeoutMs() });
28
44
  const address = await devServer.start();
29
45
  await out.log(`wcctools dev: ${address.url}`);
46
+ if (address.host !== '127.0.0.1' && address.host !== '::1' && address.host !== 'localhost') {
47
+ await out.log(`wcctools dev: listening on ${address.host}, answering the hosts ${address.allowedHosts.join(', ')}`);
48
+ }
30
49
  });
50
+ registerScreenshotCommand(out);
31
51
  registerStandardCommands(out);
32
52
  out.cli.default({}, async () => {
33
53
  await out.log(out.cli.renderHelp());
@@ -44,11 +64,13 @@ export const runCli = async () => {
44
64
  // Configuration and usage errors exit with 2, every other failure with 1.
45
65
  const usageError = error instanceof plugins.smartconsole.CliUsageError
46
66
  || error instanceof StandardConfigError
47
- || error instanceof StandardUsageError;
67
+ || error instanceof StandardUsageError
68
+ || error instanceof WccCaptureRequestError
69
+ || error instanceof WccHostPolicyError;
48
70
  process.exitCode = usageError ? 2 : 1;
49
71
  }
50
72
  finally {
51
73
  await out.dispose();
52
74
  }
53
75
  };
54
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvY2xpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDdEQsT0FBTyxFQUFFLG1CQUFtQixFQUFFLGtCQUFrQixFQUFFLHdCQUF3QixFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFFekc7O0dBRUc7QUFDSCxNQUFNLENBQUMsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLEdBQXNDLEVBQVEsRUFBRTtJQUMvRSxHQUFHLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQztRQUNoQixJQUFJLEVBQUUsVUFBVTtRQUNoQixPQUFPLEVBQUUsVUFBVSxDQUFDLE9BQU87UUFDM0IsV0FBVyxFQUFFLHVEQUF1RDtLQUNyRSxDQUFDLENBQUM7SUFFSCxHQUFHLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQztRQUNkLElBQUksRUFBRSxLQUFLO1FBQ1gsV0FBVyxFQUFFLHdFQUF3RTtRQUNyRixPQUFPLEVBQUU7WUFDUCxJQUFJLEVBQUU7Z0JBQ0osSUFBSSxFQUFFLFFBQVE7Z0JBQ2QsV0FBVyxFQUFFLDhFQUE4RTthQUM1RjtTQUNGO0tBQ0YsRUFBRSxLQUFLLEVBQUUsRUFBRSxPQUFPLEVBQUUsRUFBRSxFQUFFO1FBQ3ZCLE1BQU0sU0FBUyxHQUFHLElBQUksWUFBWSxDQUFDLEVBQUUsSUFBSSxFQUFFLE9BQU8sQ0FBQyxJQUFJLEVBQUUsR0FBRyxFQUFFLE9BQU8sQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDL0UsNkZBQTZGO1FBQzdGLG9GQUFvRjtRQUNwRixPQUFPLENBQUMsU0FBUyxDQUFDLGdCQUFnQixDQUFDLE9BQU8sQ0FBQyxFQUFFLGlCQUFpQixFQUFFLFNBQVMsQ0FBQyxvQkFBb0IsRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNwRyxNQUFNLE9BQU8sR0FBRyxNQUFNLFNBQVMsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUN4QyxNQUFNLEdBQUcsQ0FBQyxHQUFHLENBQUMsaUJBQWlCLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDO0lBQ2hELENBQUMsQ0FBQyxDQUFDO0lBRUgsd0JBQXdCLENBQUMsR0FBRyxDQUFDLENBQUM7SUFFOUIsR0FBRyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsRUFBRSxFQUFFLEtBQUssSUFBSSxFQUFFO1FBQzdCLE1BQU0sR0FBRyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLFVBQVUsRUFBRSxDQUFDLENBQUM7SUFDdEMsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDLENBQUM7QUFFRixNQUFNLENBQUMsTUFBTSxNQUFNLEdBQUcsS0FBSyxJQUFJLEVBQUU7SUFDL0IsTUFBTSxHQUFHLEdBQUcsSUFBSSxPQUFPLENBQUMsWUFBWSxDQUFDLFlBQVksRUFBRSxDQUFDO0lBQ3BELElBQUksQ0FBQztRQUNILGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3RCLE1BQU0sR0FBRyxDQUFDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsQ0FBQztJQUN0QixDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLE1BQU0sR0FBRyxDQUFDLEtBQUssQ0FBQyxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUN4RSwwRUFBMEU7UUFDMUUsTUFBTSxVQUFVLEdBQUcsS0FBSyxZQUFZLE9BQU8sQ0FBQyxZQUFZLENBQUMsYUFBYTtlQUNqRSxLQUFLLFlBQVksbUJBQW1CO2VBQ3BDLEtBQUssWUFBWSxrQkFBa0IsQ0FBQztRQUN6QyxPQUFPLENBQUMsUUFBUSxHQUFHLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDeEMsQ0FBQztZQUFTLENBQUM7UUFDVCxNQUFNLEdBQUcsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUN0QixDQUFDO0FBQ0gsQ0FBQyxDQUFDIn0=
76
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvY2xpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDdEQsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDN0QsT0FBTyxFQUFFLHNCQUFzQixFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDNUQsT0FBTyxFQUFFLHlCQUF5QixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDaEUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLGtCQUFrQixFQUFFLHdCQUF3QixFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFFekc7O0dBRUc7QUFDSCxNQUFNLENBQUMsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLEdBQXNDLEVBQVEsRUFBRTtJQUMvRSxHQUFHLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQztRQUNoQixJQUFJLEVBQUUsVUFBVTtRQUNoQixPQUFPLEVBQUUsVUFBVSxDQUFDLE9BQU87UUFDM0IsV0FBVyxFQUFFLHVEQUF1RDtLQUNyRSxDQUFDLENBQUM7SUFFSCxHQUFHLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQztRQUNkLElBQUksRUFBRSxLQUFLO1FBQ1gsV0FBVyxFQUFFLHdFQUF3RTtRQUNyRixPQUFPLEVBQUU7WUFDUCxJQUFJLEVBQUU7Z0JBQ0osSUFBSSxFQUFFLFFBQVE7Z0JBQ2QsV0FBVyxFQUFFLDhFQUE4RTthQUM1RjtZQUNELElBQUksRUFBRTtnQkFDSixJQUFJLEVBQUUsUUFBUTtnQkFDZCxXQUFXLEVBQUUsb0dBQW9HO2FBQ2xIO1lBQ0QsV0FBVyxFQUFFO2dCQUNYLElBQUksRUFBRSxTQUFTO2dCQUNmLFdBQVcsRUFBRSx5R0FBeUc7YUFD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
@@ -0,0 +1,6 @@
1
+ import * as plugins from './plugins.js';
2
+ /**
3
+ * `wcctools screenshot`: bundles the catalog of the working directory, serves it on a free
4
+ * loopback port, screenshots one demo in a headless browser, writes the image and stops.
5
+ */
6
+ export declare const registerScreenshotCommand: (out: plugins.smartconsole.SmartConsole) => void;
@@ -0,0 +1,89 @@
1
+ import * as plugins from './plugins.js';
2
+ import { WccDevServer } from './classes.devserver.js';
3
+ import { WccCaptureRequestError, captureViewportWidths, resolveScreenshotRequest } from './capture/index.js';
4
+ /** Reads `<item>` or `<section>/<item>`. */
5
+ const parseSubject = (argsArg, demoArg) => {
6
+ if (argsArg.length !== 1 || argsArg[0].trim() === '') {
7
+ throw new WccCaptureRequestError('wcctools screenshot: name one catalog entry, as <item> or <section>/<item>.');
8
+ }
9
+ const [subject] = argsArg;
10
+ const slash = subject.indexOf('/');
11
+ const named = slash === -1
12
+ ? { itemName: subject }
13
+ : { sectionName: subject.slice(0, slash), itemName: subject.slice(slash + 1) };
14
+ return demoArg === undefined ? named : { ...named, demoIndex: demoArg };
15
+ };
16
+ /** Reads a named viewport or a width in pixels. */
17
+ const parseViewport = (valueArg) => {
18
+ if (valueArg === undefined) {
19
+ return undefined;
20
+ }
21
+ if (Object.hasOwn(captureViewportWidths, valueArg)) {
22
+ return valueArg;
23
+ }
24
+ if (/^\d+$/.test(valueArg)) {
25
+ return Number(valueArg);
26
+ }
27
+ throw new WccCaptureRequestError(`wcctools screenshot: --viewport must be one of ${Object.keys(captureViewportWidths).join(', ')} or a width in pixels.`);
28
+ };
29
+ /** The image format the output file's extension names. */
30
+ const formatForFile = (fileArg) => {
31
+ const extension = plugins.path.extname(fileArg).toLowerCase();
32
+ if (extension === '.png') {
33
+ return 'png';
34
+ }
35
+ if (extension === '.jpg' || extension === '.jpeg') {
36
+ return 'jpeg';
37
+ }
38
+ throw new WccCaptureRequestError('wcctools screenshot: --out must name a .png, .jpg or .jpeg file.');
39
+ };
40
+ /**
41
+ * `wcctools screenshot`: bundles the catalog of the working directory, serves it on a free
42
+ * loopback port, screenshots one demo in a headless browser, writes the image and stops.
43
+ */
44
+ export const registerScreenshotCommand = (out) => {
45
+ out.cli.command({
46
+ name: 'screenshot',
47
+ description: 'Screenshot one demo of the catalog in a headless browser: wcctools screenshot <item | section/item> --out <file.png>',
48
+ options: {
49
+ out: { type: 'string', required: true, description: 'The image file to write; .png, .jpg or .jpeg' },
50
+ demo: { type: 'number', description: 'The demo to render, counted from 0 as in the shell URL (default 0)' },
51
+ viewport: { type: 'string', description: `The width: ${Object.entries(captureViewportWidths).map(([nameArg, widthArg]) => `${nameArg} (${widthArg})`).join(', ')} or pixels (default desktop)` },
52
+ height: { type: 'number', description: 'The height of the preview window in pixels (default 800)' },
53
+ theme: { type: 'string', description: 'dark or bright (default dark)' },
54
+ framing: { type: 'string', description: 'element, viewport or fullpage (default element; fullpage for pages)' },
55
+ scale: { type: 'number', description: 'Device pixel ratio, 1 or 2 (default 1)' },
56
+ quality: { type: 'number', description: 'JPEG quality from 1 to 100 (default 80)' },
57
+ },
58
+ }, async ({ args, options }) => {
59
+ const format = formatForFile(options.out);
60
+ const request = {
61
+ subject: parseSubject(args, options.demo),
62
+ viewport: parseViewport(options.viewport),
63
+ height: options.height,
64
+ theme: options.theme,
65
+ framing: options.framing,
66
+ scale: options.scale,
67
+ format,
68
+ quality: options.quality,
69
+ };
70
+ // Invalid options are refused before anything is bundled
71
+ resolveScreenshotRequest(request);
72
+ const outFile = plugins.path.resolve(options.out);
73
+ const devServer = new WccDevServer({ port: 0, cwd: process.cwd() });
74
+ // As for `wcctools dev`: SIGINT and SIGTERM stop the server and the capture browser once
75
+ plugins.smartexit.ProcessLifecycle.install({ shutdownTimeoutMs: devServer.getShutdownTimeoutMs() });
76
+ let result;
77
+ try {
78
+ await devServer.start();
79
+ result = await devServer.capture.captureScreenshot(request);
80
+ }
81
+ finally {
82
+ await devServer.stop();
83
+ }
84
+ await plugins.fs.writeFile(outFile, Buffer.from(result.image.dataBase64, 'base64'));
85
+ const { selection, image } = result;
86
+ await out.log(`wcctools screenshot: wrote ${outFile} (${selection.sectionName}/${selection.itemName} demo ${selection.demoIndex}, ${result.framing}, ${image.width}×${image.height}${result.truncated ? ', cut at the maximum height' : ''})`);
87
+ });
88
+ };
89
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLnNjcmVlbnNob3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9jbGkuc2NyZWVuc2hvdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDdEQsT0FBTyxFQUFFLHNCQUFzQixFQUFFLHFCQUFxQixFQUFFLHdCQUF3QixFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFN0csNENBQTRDO0FBQzVDLE1BQU0sWUFBWSxHQUFHLENBQUMsT0FBMEIsRUFBRSxPQUEyQixFQUF5QyxFQUFFO0lBQ3RILElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxDQUFDLElBQUksT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUFDO1FBQ3JELE1BQU0sSUFBSSxzQkFBc0IsQ0FBQyw2RUFBNkUsQ0FBQyxDQUFDO0lBQ2xILENBQUM7SUFDRCxNQUFNLENBQUMsT0FBTyxDQUFDLEdBQUcsT0FBTyxDQUFDO0lBQzFCLE1BQU0sS0FBSyxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDbkMsTUFBTSxLQUFLLEdBQTBDLEtBQUssS0FBSyxDQUFDLENBQUM7UUFDL0QsQ0FBQyxDQUFDLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRTtRQUN2QixDQUFDLENBQUMsRUFBRSxXQUFXLEVBQUUsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsS0FBSyxDQUFDLEVBQUUsUUFBUSxFQUFFLE9BQU8sQ0FBQyxLQUFLLENBQUMsS0FBSyxHQUFHLENBQUMsQ0FBQyxFQUFFLENBQUM7SUFDakYsT0FBTyxPQUFPLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxLQUFLLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFB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
@@ -1,18 +1,22 @@
1
1
  import * as childProcess from 'node:child_process';
2
2
  import * as fs from 'node:fs/promises';
3
3
  import * as fsSync from 'node:fs';
4
+ import * as os from 'node:os';
4
5
  import * as path from 'node:path';
5
6
  import * as url from 'node:url';
6
- export { childProcess, fs, fsSync, path, url };
7
+ export { childProcess, fs, fsSync, os, path, url };
7
8
  import * as typedrequest from '@api.global/typedrequest';
8
9
  import * as typedserver from '@api.global/typedserver';
9
10
  export { typedrequest, typedserver };
11
+ import * as deesDomtools from '@design.estate/dees-domtools';
12
+ export { deesDomtools };
10
13
  import * as tswatch from '@git.zone/tswatch';
11
14
  export { tswatch };
15
+ import * as smartbrowser from '@push.rocks/smartbrowser/automation';
12
16
  import * as smartconfig from '@push.rocks/smartconfig';
13
17
  import * as smartconsole from '@push.rocks/smartconsole';
14
18
  import * as smartexit from '@push.rocks/smartexit';
15
- export { smartconfig, smartconsole, smartexit };
19
+ export { smartbrowser, smartconfig, smartconsole, smartexit };
16
20
  import typescript from 'typescript';
17
21
  export { typescript };
18
22
  import * as interfaces from '../dist_ts_interfaces/index.js';