diff --git a/frontends/election-manager/.eslintignore b/frontends/election-manager/.eslintignore
index 5e4306738b3..f4c038b7e2a 100644
--- a/frontends/election-manager/.eslintignore
+++ b/frontends/election-manager/.eslintignore
@@ -6,3 +6,5 @@
/prodserver
/src/**/*.js
*.d.ts
+*.config.js
+*.config.ts
diff --git a/frontends/election-manager/index.html b/frontends/election-manager/index.html
new file mode 100644
index 00000000000..5eaaa968e5c
--- /dev/null
+++ b/frontends/election-manager/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+ VotingWorks VxAdmin
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontends/election-manager/package.json b/frontends/election-manager/package.json
index 62b477b3b8d..90c24a80833 100644
--- a/frontends/election-manager/package.json
+++ b/frontends/election-manager/package.json
@@ -10,12 +10,14 @@
"scripts": {
"type-check": "tsc --build",
"build": "tsc --build && react-scripts build",
+ "build:vite": "vite build",
"build:watch": "tsc --build --watch",
"eject": "react-scripts eject",
"format": "prettier '**/*.+(css|graphql|json|less|md|mdx|sass|scss|yaml|yml)' --write",
"lint": "pnpm type-check && eslint . && pnpm stylelint:run",
"lint:fix": "pnpm type-check && eslint . --fix && pnpm stylelint:run:fix",
"start": "react-scripts start",
+ "start:vite": "vite",
"stylelint:run": "stylelint 'src/**/*.{js,jsx,ts,tsx}' && stylelint 'src/**/*.css' --config .stylelintrc-css.js",
"stylelint:run:fix": "stylelint 'src/**/*.{js,jsx,ts,tsx}' --fix && stylelint 'src/**/*.css' --config .stylelintrc-css.js --fix",
"test": "is-ci test:coverage test:watch",
@@ -92,13 +94,17 @@
"@votingworks/types": "workspace:*",
"@votingworks/ui": "workspace:*",
"@votingworks/utils": "workspace:*",
+ "@zip.js/zip.js": "^2.4.12",
"array-unique": "^0.3.2",
+ "assert": "^2.0.0",
"base64-js": "^1.3.1",
+ "browserify-zlib": "^0.2.0",
"buffer": "^6.0.3",
"canvas": "2.9.1",
"dashify": "^2.0.0",
"debug": "^4.3.2",
"dompurify": "^2.0.12",
+ "events": "^3.3.0",
"fetch-mock": "^9.10.7",
"history": "^4.10.1",
"http-proxy-middleware": "1.0.6",
@@ -111,6 +117,7 @@
"node-fetch": "^2.6.0",
"normalize.css": "^8.0.1",
"pagedjs": "^0.1.40",
+ "path": "^0.12.7",
"pdfjs-dist": "2.4.456",
"pluralize": "^8.0.0",
"react": "^17.0.1",
@@ -119,9 +126,12 @@
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.1",
"react-textarea-autosize": "^8.2.0",
+ "setimmediate": "^1.0.5",
+ "stream-browserify": "^3.0.0",
"styled-components": "^5.2.1",
"typescript": "4.6.3",
"use-interval": "^1.2.1",
+ "util": "^0.12.4",
"zip-stream": "^3.0.1",
"zod": "3.14.4"
},
@@ -129,6 +139,7 @@
"@codemod/parser": "^1.0.6",
"@testing-library/jest-dom": "^5.16.4",
"@types/base64-js": "^1.3.0",
+ "@types/connect": "^3.4.35",
"@types/debug": "^4.1.6",
"@types/history": "^4.7.8",
"@types/kiosk-browser": "workspace:*",
@@ -154,6 +165,7 @@
"eslint-plugin-react": "^7.18.3",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-vx": "workspace:*",
+ "express": "^4.18.1",
"is-ci-cli": "^2.1.2",
"jest": "^27.3.1",
"jest-environment-jsdom-sixteen": "^1.0.3",
@@ -166,7 +178,8 @@
"stylelint-config-palantir": "^4.0.1",
"stylelint-config-prettier": "^8.0.1",
"stylelint-config-styled-components": "^0.1.1",
- "stylelint-processor-styled-components": "^1.10.0"
+ "stylelint-processor-styled-components": "^1.10.0",
+ "vite": "^2.9.9"
},
"vx": {
"isBundled": true,
diff --git a/frontends/election-manager/prodserver/setupProxy.js b/frontends/election-manager/prodserver/setupProxy.js
index d12b47fc35e..bbe931f96ae 100644
--- a/frontends/election-manager/prodserver/setupProxy.js
+++ b/frontends/election-manager/prodserver/setupProxy.js
@@ -11,16 +11,22 @@ const express = require('express');
const { createProxyMiddleware: proxy } = require('http-proxy-middleware');
const { dirname, join } = require('path');
+/**
+ * @param {import('connect').Server} app
+ */
module.exports = function (app) {
app.use(proxy('/card', { target: 'http://localhost:3001/' }));
app.use(proxy('/convert', { target: 'http://localhost:3003/' }));
app.use(proxy('/admin', { target: 'http://localhost:3004/' }));
- app.get('/machine-config', (req, res) => {
- res.json({
- machineId: process.env.VX_MACHINE_ID || '0000',
- codeVersion: process.env.VX_CODE_VERSION || 'dev',
- });
+ app.use('/machine-config', (req, res) => {
+ res.setHeader('Content-Type', 'application/json');
+ res.end(
+ JSON.stringify({
+ machineId: process.env.VX_MACHINE_ID || '0000',
+ codeVersion: process.env.VX_CODE_VERSION || 'dev',
+ })
+ );
});
const pdfjsDistBuildPath = dirname(
diff --git a/frontends/election-manager/src/index.tsx b/frontends/election-manager/src/index.tsx
index 91b2f68c488..1a91e29b789 100644
--- a/frontends/election-manager/src/index.tsx
+++ b/frontends/election-manager/src/index.tsx
@@ -1,3 +1,4 @@
+import './polyfills';
import React from 'react';
import ReactDom from 'react-dom';
import './i18n';
diff --git a/frontends/election-manager/src/polyfills.ts b/frontends/election-manager/src/polyfills.ts
new file mode 100644
index 00000000000..de592f1022d
--- /dev/null
+++ b/frontends/election-manager/src/polyfills.ts
@@ -0,0 +1,13 @@
+/**
+ * Provides polyfills needed for this application and its dependencies.
+ */
+
+/* istanbul ignore file */
+import { Buffer } from 'buffer';
+import 'setimmediate';
+
+globalThis.global = globalThis;
+globalThis.Buffer = Buffer;
+globalThis.process ??= {} as unknown as typeof process;
+
+process.nextTick = setImmediate;
diff --git a/frontends/election-manager/src/stubs/glob.ts b/frontends/election-manager/src/stubs/glob.ts
new file mode 100644
index 00000000000..fc3e9c48ca2
--- /dev/null
+++ b/frontends/election-manager/src/stubs/glob.ts
@@ -0,0 +1,4 @@
+// This file exists to serve as a stub for the `glob` module.
+// See `vite.config.ts` under `resolve.alias` for the configuration.
+
+export {};
diff --git a/frontends/election-manager/src/utils/downloadable_archive.test.ts b/frontends/election-manager/src/utils/downloadable_archive.test.ts
index 7dc9e8f02b5..25f5082270b 100644
--- a/frontends/election-manager/src/utils/downloadable_archive.test.ts
+++ b/frontends/election-manager/src/utils/downloadable_archive.test.ts
@@ -1,11 +1,10 @@
import { fakeKiosk } from '@votingworks/test-utils';
-import { Buffer } from 'buffer';
import { fakeFileWriter } from '../../test/helpers/fake_file_writer';
import { DownloadableArchive } from './downloadable_archive';
// https://en.wikipedia.org/wiki/List_of_file_signatures
-const ZIP_MAGIC_BYTES = Buffer.of(0x50, 0x4b, 0x03, 0x04);
-const EMPTY_ZIP_MAGIC_BYTES = Buffer.of(0x50, 0x4b, 0x05, 0x06);
+const ZIP_MAGIC_BYTES = [0x50, 0x4b, 0x03, 0x04];
+const EMPTY_ZIP_MAGIC_BYTES = [0x50, 0x4b, 0x05, 0x06];
test('file prompt fails', async () => {
const kiosk = fakeKiosk();
@@ -39,9 +38,8 @@ test('empty zip file when user is prompted for file location', async () => {
await archive.end();
expect(fileWriter.chunks).not.toHaveLength(0);
- const firstChunk = fileWriter.chunks[0] as Buffer;
- expect(firstChunk).toBeInstanceOf(Buffer);
- expect(firstChunk.slice(0, EMPTY_ZIP_MAGIC_BYTES.length)).toEqual(
+ const firstChunk = fileWriter.chunks[0] as Uint8Array;
+ expect(Array.from(firstChunk.slice(0, EMPTY_ZIP_MAGIC_BYTES.length))).toEqual(
EMPTY_ZIP_MAGIC_BYTES
);
});
@@ -63,9 +61,8 @@ test('empty zip file when file is saved directly and passes path to kiosk proper
});
expect(kiosk.writeFile).toHaveBeenCalledWith('/path/to/folder/file.zip');
- const firstChunk = fileWriter.chunks[0] as Buffer;
- expect(firstChunk).toBeInstanceOf(Buffer);
- expect(firstChunk.slice(0, EMPTY_ZIP_MAGIC_BYTES.length)).toEqual(
+ const firstChunk = fileWriter.chunks[0] as Uint8Array;
+ expect(Array.from(firstChunk.slice(0, EMPTY_ZIP_MAGIC_BYTES.length))).toEqual(
EMPTY_ZIP_MAGIC_BYTES
);
});
@@ -83,9 +80,10 @@ test('zip file containing a file', async () => {
await archive.end();
expect(fileWriter.chunks).not.toHaveLength(0);
- const firstChunk = fileWriter.chunks[0] as Buffer;
- expect(firstChunk).toBeInstanceOf(Buffer);
- expect(firstChunk.slice(0, ZIP_MAGIC_BYTES.length)).toEqual(ZIP_MAGIC_BYTES);
+ const firstChunk = fileWriter.chunks[0] as Uint8Array;
+ expect(Array.from(firstChunk.slice(0, ZIP_MAGIC_BYTES.length))).toEqual(
+ ZIP_MAGIC_BYTES
+ );
});
test('passes options to kiosk.saveAs', async () => {
diff --git a/frontends/election-manager/src/utils/downloadable_archive.ts b/frontends/election-manager/src/utils/downloadable_archive.ts
index 6675533995e..891a4fb2113 100644
--- a/frontends/election-manager/src/utils/downloadable_archive.ts
+++ b/frontends/election-manager/src/utils/downloadable_archive.ts
@@ -1,6 +1,35 @@
-import { assert, deferred } from '@votingworks/utils';
-import ZipStream from 'zip-stream';
+/* eslint-disable max-classes-per-file */
+import { assert } from '@votingworks/utils';
+import { Buffer } from 'buffer';
import path from 'path';
+import { configure, Uint8ArrayReader, ZipWriter, Writer } from '@zip.js/zip.js';
+
+configure({ useWebWorkers: false });
+
+/**
+ * Forwards data from a `ZipWriter` to a kiosk-browser file writer.
+ */
+class KioskBrowserZipFileWriter extends Writer {
+ constructor(private readonly fileWriter: KioskBrowser.FileWriter) {
+ super();
+ }
+
+ /**
+ * Called whenever there is new data to write to the zip file.
+ */
+ async writeUint8Array(array: Uint8Array): Promise {
+ await super.writeUint8Array(array);
+ await this.fileWriter.write(array);
+ }
+
+ /**
+ * This function is required by the ZipWriter interface, but we ignore its
+ * return value. It is called when closing the zip file.
+ */
+ async getData(): Promise {
+ return Promise.resolve(Uint8Array.of());
+ }
+}
/**
* Provides support for downloading a Zip archive of files. Requires
@@ -8,8 +37,7 @@ import path from 'path';
* that the executing host is allowed to use the `saveAs` API.
*/
export class DownloadableArchive {
- private zip?: ZipStream;
- private endPromise?: Promise;
+ private writer?: ZipWriter;
constructor(private readonly kiosk = window.kiosk) {}
@@ -30,13 +58,7 @@ export class DownloadableArchive {
throw new Error('could not begin download; no file was chosen');
}
- let endResolve: () => void;
- this.endPromise = new Promise((resolve) => {
- endResolve = resolve;
- });
- this.zip = new ZipStream()
- .on('data', (chunk) => fileWriter.write(chunk))
- .on('end', () => fileWriter.end().then(endResolve));
+ this.prepareZip(fileWriter);
}
/**
@@ -57,42 +79,38 @@ export class DownloadableArchive {
throw new Error('could not begin download; an error occurred');
}
- const { promise: endPromise, resolve: endResolve } = deferred();
- this.endPromise = endPromise;
- this.zip = new ZipStream()
- .on('data', (chunk) => fileWriter.write(chunk))
- .on('end', () => fileWriter.end().then(endResolve));
+ this.prepareZip(fileWriter);
+ }
+
+ /**
+ * Prepares the zip archive for writing to the given file writer.
+ */
+ private prepareZip(fileWriter: KioskBrowser.FileWriter): void {
+ this.writer = new ZipWriter(new KioskBrowserZipFileWriter(fileWriter));
}
/**
* Writes a file to the archive, resolves when complete.
*/
- async file(
- name: string,
- data: Parameters[0]
- ): Promise {
- const { zip } = this;
+ async file(name: string, data: string | Buffer): Promise {
+ const { writer } = this;
- if (!zip) {
+ if (!writer) {
throw new Error('cannot call file() before begin()');
}
- return new Promise((resolve, reject) => {
- zip.entry(data, { name }, (err) => (err ? reject(err) : resolve()));
- });
+ await writer.add(name, new Uint8ArrayReader(Buffer.from(data)));
}
/**
* Finishes the zip archive and ends the download.
*/
async end(): Promise {
- if (!this.zip) {
+ if (!this.writer) {
throw new Error('cannot call end() before begin()');
}
- this.zip.finalize();
- await this.endPromise;
- this.zip = undefined;
- this.endPromise = undefined;
+ await this.writer.close();
+ this.writer = undefined;
}
}
diff --git a/frontends/election-manager/vite.config.ts b/frontends/election-manager/vite.config.ts
new file mode 100644
index 00000000000..5bb2487822c
--- /dev/null
+++ b/frontends/election-manager/vite.config.ts
@@ -0,0 +1,101 @@
+import { join } from 'path';
+import { defineConfig, loadEnv } from 'vite';
+import { getWorkspacePackageInfo } from '../../script/src/validate-monorepo/pnpm';
+import setupProxy from './prodserver/setupProxy';
+
+export default defineConfig(async (env) => {
+ const workspacePackages = await getWorkspacePackageInfo(
+ join(__dirname, '../..')
+ );
+
+ const envPrefix = 'REACT_APP_';
+ const dotenvValues = loadEnv(env.mode, __dirname, envPrefix);
+ const processEnvDefines = Object.entries(dotenvValues).reduce<
+ Record
+ >(
+ (acc, [key, value]) => ({
+ ...acc,
+ [`process.env.${key}`]: JSON.stringify(value),
+ }),
+ {}
+ );
+
+ return {
+ build: {
+ // Write build files to `build` directory.
+ outDir: 'build',
+
+ // Do not minify build files. We don't need the space savings and this is
+ // a minor transparency improvement.
+ minify: false,
+ },
+
+ // Replace some code in Node modules, `#define`-style, to avoid referencing
+ // Node-only globals like `process`.
+ define: {
+ 'process.env.NODE_DEBUG': JSON.stringify(undefined),
+ 'process.version': JSON.stringify(process.version),
+
+ // TODO: Replace these with the appropriate `import.meta.env` values (#1907).
+ ...processEnvDefines,
+ },
+
+ resolve: {
+ alias: [
+ // Replace NodeJS built-in modules with polyfills.
+ //
+ // The trailing slash is important for the ones with the same name.
+ // Without it, they will be resolved as built-in NodeJS modules.
+ { find: 'assert', replacement: require.resolve('assert/') },
+ { find: 'buffer', replacement: require.resolve('buffer/') },
+ { find: 'events', replacement: require.resolve('events/') },
+ { find: 'stream', replacement: require.resolve('stream-browserify') },
+ { find: 'util', replacement: require.resolve('util/') },
+ { find: 'zlib', replacement: require.resolve('browserify-zlib') },
+
+ // Work around a broken `module` entry in pagedjs's `package.json`.
+ // https://github.com/vitejs/vite/issues/1488
+ {
+ find: 'pagedjs',
+ replacement: require.resolve('pagedjs/dist/paged.esm'),
+ },
+
+ // Work around an internet curmudgeon.
+ // Problem: https://github.com/isaacs/node-glob/pull/374
+ // Fix: https://github.com/isaacs/node-glob/pull/479
+ { find: 'glob', replacement: join(__dirname, './src/stubs/glob.ts') },
+
+ // Create aliases for all workspace packages, i.e.
+ //
+ // {
+ // '@votingworks/types': '…/libs/types/src/index.ts',
+ // '@votingworks/utils': '…/libs/utils/src/index.ts',
+ // …
+ // }
+ //
+ // This allows re-mapping imports for workspace packages to their
+ // TypeScript source code rather than the built JavaScript.
+ ...Array.from(workspacePackages.values()).reduce(
+ (aliases, { path, name, source }) =>
+ !source
+ ? aliases
+ : [...aliases, { find: name, replacement: join(path, source) }],
+ []
+ ),
+ ],
+ },
+
+ plugins: [
+ // Setup the proxy to local services, e.g. `smartcards`.
+ {
+ name: 'development-proxy',
+ configureServer: (app) => {
+ setupProxy(app.middlewares);
+ },
+ },
+ ],
+
+ // Pass some environment variables to the client in `import.meta.env`.
+ envPrefix,
+ };
+});
diff --git a/libs/ballot-interpreter-vx/package.json b/libs/ballot-interpreter-vx/package.json
index d1ff09e90c1..2cd7a9f4e32 100644
--- a/libs/ballot-interpreter-vx/package.json
+++ b/libs/ballot-interpreter-vx/package.json
@@ -53,6 +53,7 @@
"node-quirc": "^2.2.1",
"randombytes": "^2.1.0",
"table": "^6.0.3",
+ "util": "^0.12.4",
"uuid": "^8.3.1"
},
"devDependencies": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2b2024dc3f8..61ec54fd83c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -734,13 +734,17 @@ importers:
'@votingworks/types': link:../../libs/types
'@votingworks/ui': link:../../libs/ui
'@votingworks/utils': link:../../libs/utils
+ '@zip.js/zip.js': 2.4.12
array-unique: 0.3.2
+ assert: 2.0.0
base64-js: 1.5.1
+ browserify-zlib: 0.2.0
buffer: 6.0.3
canvas: 2.9.1
dashify: 2.0.0
debug: 4.3.2
dompurify: 2.2.6
+ events: 3.3.0
fetch-mock: 9.11.0
history: 4.10.1
http-proxy-middleware: 1.0.6
@@ -753,6 +757,7 @@ importers:
node-fetch: 2.6.1
normalize.css: 8.0.1
pagedjs: 0.1.43
+ path: 0.12.7
pdfjs-dist: 2.4.456
pluralize: 8.0.0
react: 17.0.1
@@ -761,15 +766,19 @@ importers:
react-router-dom: 5.2.0_react@17.0.1
react-scripts: 4.0.1_typescript@4.6.3
react-textarea-autosize: 8.3.0_e8f6b04531727420b27210e589e7d031
+ setimmediate: 1.0.5
+ stream-browserify: 3.0.0
styled-components: 5.2.1_react-dom@17.0.1+react@17.0.1
typescript: 4.6.3
use-interval: 1.3.0_react@17.0.1
+ util: 0.12.4
zip-stream: 3.0.1
zod: 3.14.4
devDependencies:
'@codemod/parser': 1.1.0
'@testing-library/jest-dom': 5.16.4
'@types/base64-js': 1.3.0
+ '@types/connect': 3.4.35
'@types/debug': 4.1.6
'@types/history': 4.7.8
'@types/kiosk-browser': link:../../libs/@types/kiosk-browser
@@ -795,6 +804,7 @@ importers:
eslint-plugin-react: 7.22.0_eslint@7.17.0
eslint-plugin-react-hooks: 4.2.0_eslint@7.17.0
eslint-plugin-vx: link:../../libs/eslint-plugin-vx
+ express: 4.18.1
is-ci-cli: 2.1.2
jest: 27.5.1_canvas@2.9.1
jest-environment-jsdom-sixteen: 1.0.3_canvas@2.9.1
@@ -808,6 +818,7 @@ importers:
stylelint-config-prettier: 8.0.2_stylelint@13.8.0
stylelint-config-styled-components: 0.1.1
stylelint-processor-styled-components: 1.10.0
+ vite: 2.9.9
specifiers:
'@codemod/parser': ^1.0.6
'@testing-library/jest-dom': ^5.16.4
@@ -815,6 +826,7 @@ importers:
'@testing-library/user-event': ^12.6.0
'@types/array-unique': ^0.3.0
'@types/base64-js': ^1.3.0
+ '@types/connect': ^3.4.35
'@types/dashify': ^1.0.0
'@types/debug': ^4.1.6
'@types/dompurify': ^2.0.3
@@ -844,8 +856,11 @@ importers:
'@votingworks/types': workspace:*
'@votingworks/ui': workspace:*
'@votingworks/utils': workspace:*
+ '@zip.js/zip.js': ^2.4.12
array-unique: ^0.3.2
+ assert: ^2.0.0
base64-js: ^1.3.1
+ browserify-zlib: ^0.2.0
buffer: ^6.0.3
canvas: 2.9.1
dashify: ^2.0.0
@@ -865,6 +880,8 @@ importers:
eslint-plugin-react: ^7.18.3
eslint-plugin-react-hooks: ^4.2.0
eslint-plugin-vx: workspace:*
+ events: ^3.3.0
+ express: ^4.18.1
fetch-mock: ^9.10.7
history: ^4.10.1
http-proxy-middleware: 1.0.6
@@ -881,6 +898,7 @@ importers:
node-fetch: ^2.6.0
normalize.css: ^8.0.1
pagedjs: ^0.1.40
+ path: ^0.12.7
pdfjs-dist: 2.4.456
pluralize: ^8.0.0
prettier: ^2.6.2
@@ -892,7 +910,9 @@ importers:
react-scripts: 4.0.1
react-test-renderer: ^16.13.1
react-textarea-autosize: ^8.2.0
+ setimmediate: ^1.0.5
sort-package-json: ^1.50.0
+ stream-browserify: ^3.0.0
styled-components: ^5.2.1
stylelint: ^13.1.0
stylelint-config-palantir: ^4.0.1
@@ -901,6 +921,8 @@ importers:
stylelint-processor-styled-components: ^1.10.0
typescript: 4.6.3
use-interval: ^1.2.1
+ util: ^0.12.4
+ vite: ^2.9.9
zip-stream: ^3.0.1
zod: 3.14.4
frontends/election-manager/prodserver:
@@ -1404,6 +1426,7 @@ importers:
node-quirc: 2.3.0
randombytes: 2.1.0
table: 6.0.7
+ util: 0.12.4
uuid: 8.3.2
devDependencies:
'@types/benchmark': 1.0.33
@@ -1494,6 +1517,7 @@ importers:
tmp: ^0.2.1
ts-jest: ^27.0.7
typescript: 4.6.3
+ util: ^0.12.4
uuid: ^8.3.1
libs/cdf-schema-builder:
dependencies:
@@ -2387,6 +2411,11 @@ importers:
ts-jest: ^27.0.7
typescript: 4.6.3
zod: 3.14.4
+ libs/zip.js:
+ dependencies:
+ '@zip.js/zip.js': 2.4.12
+ specifiers:
+ '@zip.js/zip.js': ^2.4.12
script:
dependencies:
resolve-from: 5.0.0
@@ -8766,7 +8795,7 @@ packages:
integrity: sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==
/@types/connect/3.4.35:
dependencies:
- '@types/node': 17.0.36
+ '@types/node': 16.11.29
dev: true
resolution:
integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==
@@ -11260,6 +11289,10 @@ packages:
/@xtuc/long/4.2.2:
resolution:
integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
+ /@zip.js/zip.js/2.4.12:
+ dev: false
+ resolution:
+ integrity: sha512-Y9VTILBi067CUNyaCTeLXxAVbB547EdUfAJZndfpnqYm8r2blVmHAqvggPYw2p89rTCvJAhDXlGkY4cv0+JYmA==
/abab/2.0.6:
resolution:
integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
@@ -11766,6 +11799,15 @@ packages:
util: 0.10.3
resolution:
integrity: sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==
+ /assert/2.0.0:
+ dependencies:
+ es6-object-assign: 1.1.0
+ is-nan: 1.3.2
+ object-is: 1.1.5
+ util: 0.12.4
+ dev: false
+ resolution:
+ integrity: sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==
/assign-symbols/1.0.0:
engines:
node: '>=0.10.0'
@@ -14920,6 +14962,10 @@ packages:
es6-symbol: 3.1.3
resolution:
integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==
+ /es6-object-assign/1.1.0:
+ dev: false
+ resolution:
+ integrity: sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==
/es6-symbol/3.1.3:
dependencies:
d: 1.0.1
@@ -17737,7 +17783,7 @@ packages:
'@typescript-eslint/eslint-plugin': 5.21.0_94a944f85573090af1491a822dcbe40b
'@typescript-eslint/utils': 5.22.0_eslint@7.19.0+typescript@4.6.3
eslint: 7.19.0
- jest: 27.3.1
+ jest: 27.3.1_canvas@2.9.1
dev: true
engines:
node: ^12.22.0 || ^14.17.0 || >=16.0.0
@@ -19583,6 +19629,44 @@ packages:
node: '>= 0.10.0'
resolution:
integrity: sha512-EJEXxiTQJS3lIPrU1AE2vRuT7X7E+0KBbpm5GSoK524yl0K8X+er8zS2P14E64eqsVNoWbMCT7MpmQ+ErAhgRg==
+ /express/4.18.1:
+ dependencies:
+ accepts: 1.3.8
+ array-flatten: 1.1.1
+ body-parser: 1.20.0
+ content-disposition: 0.5.4
+ content-type: 1.0.4
+ cookie: 0.5.0
+ cookie-signature: 1.0.6
+ debug: 2.6.9
+ depd: 2.0.0
+ encodeurl: 1.0.2
+ escape-html: 1.0.3
+ etag: 1.8.1
+ finalhandler: 1.2.0
+ fresh: 0.5.2
+ http-errors: 2.0.0
+ merge-descriptors: 1.0.1
+ methods: 1.1.2
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ path-to-regexp: 0.1.7
+ proxy-addr: 2.0.7
+ qs: 6.10.3
+ range-parser: 1.2.1
+ safe-buffer: 5.2.1
+ send: 0.18.0
+ serve-static: 1.15.0
+ setprototypeof: 1.2.0
+ statuses: 2.0.1
+ type-is: 1.6.18
+ utils-merge: 1.0.1
+ vary: 1.1.2
+ dev: true
+ engines:
+ node: '>= 0.10.0'
+ resolution:
+ integrity: sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==
/ext/1.4.0:
dependencies:
type: 2.1.0
@@ -21150,7 +21234,7 @@ packages:
/immediate/3.0.6:
dev: false
resolution:
- integrity: sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
+ integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
/immer/8.0.1:
dev: false
resolution:
@@ -21637,6 +21721,15 @@ packages:
dev: false
resolution:
integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==
+ /is-nan/1.3.2:
+ dependencies:
+ call-bind: 1.0.2
+ define-properties: 1.1.4
+ dev: false
+ engines:
+ node: '>= 0.4'
+ resolution:
+ integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==
/is-negative-zero/2.0.1:
engines:
node: '>= 0.4'
@@ -26167,6 +26260,15 @@ packages:
node: '>= 0.4'
resolution:
integrity: sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg==
+ /object-is/1.1.5:
+ dependencies:
+ call-bind: 1.0.2
+ define-properties: 1.1.4
+ dev: false
+ engines:
+ node: '>= 0.4'
+ resolution:
+ integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==
/object-keys/1.1.1:
engines:
node: '>= 0.4'
@@ -26660,7 +26762,7 @@ packages:
integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
/path-to-regexp/0.1.7:
resolution:
- integrity: sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
+ integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
/path-to-regexp/1.8.0:
dependencies:
isarray: 0.0.1
@@ -26690,6 +26792,13 @@ packages:
node: '>=8'
resolution:
integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
+ /path/0.12.7:
+ dependencies:
+ process: 0.11.10
+ util: 0.10.4
+ dev: false
+ resolution:
+ integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==
/pause-stream/0.0.11:
dependencies:
through: 2.3.8
@@ -27818,7 +27927,7 @@ packages:
engines:
node: '>= 0.6.0'
resolution:
- integrity: sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
+ integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
/progress/2.0.3:
engines:
node: '>=0.4.0'
@@ -29826,6 +29935,13 @@ packages:
readable-stream: 2.3.7
resolution:
integrity: sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==
+ /stream-browserify/3.0.0:
+ dependencies:
+ inherits: 2.0.4
+ readable-stream: 3.6.0
+ dev: false
+ resolution:
+ integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==
/stream-combiner/0.0.4:
dependencies:
duplexer: 0.1.2
@@ -31767,6 +31883,12 @@ packages:
inherits: 2.0.1
resolution:
integrity: sha1-evsa/lCAUkZInj23/g7TeTNqwPk=
+ /util/0.10.4:
+ dependencies:
+ inherits: 2.0.3
+ dev: false
+ resolution:
+ integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==
/util/0.11.1:
dependencies:
inherits: 2.0.3