diff --git a/packages/admin/src/app.ts b/packages/admin/src/app.ts index faaba86d1..c266317ec 100644 --- a/packages/admin/src/app.ts +++ b/packages/admin/src/app.ts @@ -2,6 +2,7 @@ import { css, customElement, html, LitElement, property, query, state } from "@p import { ServiceWorker } from "@padloc/app/src/mixins/service-worker"; import { StateMixin } from "@padloc/app/src/mixins/state"; import { Routing } from "@padloc/app/src/mixins/routing"; +import { ErrorHandling } from "@padloc/app/src/mixins/error-handling"; import { mixins, shared } from "@padloc/app/src/styles"; import "@padloc/app/src/elements/button"; import "@padloc/app/src/elements/icon"; @@ -16,7 +17,7 @@ import "./orgs"; import "./config"; @customElement("pl-admin-app") -export class App extends ServiceWorker(StateMixin(Routing(LitElement))) { +export class App extends ServiceWorker(ErrorHandling(StateMixin(Routing(LitElement)))) { @property({ attribute: false }) readonly routePattern = /^([^\/]*)(?:\/([^\/]+))?/; diff --git a/packages/app/src/elements/login-signup.ts b/packages/app/src/elements/login-signup.ts index a058e27f1..230ac9cad 100644 --- a/packages/app/src/elements/login-signup.ts +++ b/packages/app/src/elements/login-signup.ts @@ -512,7 +512,7 @@ export class LoginOrSignup extends StartForm { this._confirmPasswordButton.start(); try { - await this.app.signup({ email, password, name, authToken: this._authToken }); + await this.app.signup({ email, password, name, authToken: this._authToken, asAdmin: this.asAdmin }); this._confirmPasswordButton.success(); const { email: _email, name: _name, authToken, deviceTrusted, ...params } = this.router.params; this.go("signup/success", params); diff --git a/packages/core/src/app.ts b/packages/core/src/app.ts index 63bf26318..9ef39abcf 100644 --- a/packages/core/src/app.ts +++ b/packages/core/src/app.ts @@ -656,12 +656,14 @@ export class App { authToken, /** Information about the [[Invite]] object if signup was initiated through invite link */ invite, + asAdmin, }: { email: string; password: string; name: string; authToken: string; invite?: { id: string; org: string }; + asAdmin?: boolean; }) { // Inialize account object const account = new Account(); @@ -689,7 +691,7 @@ export class App { ); // Sign into new account - await this.login({ email, password }); + await this.login({ email, password, asAdmin }); } /** diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index dab0cf757..43a1b9f4b 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -13,6 +13,7 @@ interface ParamDefinition { readonly options?: string[]; readonly default?: any; readonly description?: string; + readonly envVars?: string[]; } export function ConfigParam( @@ -22,11 +23,13 @@ export function ConfigParam( options, required = false, default: def, + envVars, }: { secret?: boolean; options?: string[]; required?: boolean | { prop: string; value: string | string[] }; default?: any; + envVars?: string[]; } = {}, description?: string ) { @@ -42,6 +45,7 @@ export function ConfigParam( required, default: def, description, + envVars, }); }; } @@ -59,7 +63,7 @@ export class Config extends Serializable { outputSecrets = false; fromEnv(env: { [prop: string]: string }, prefix = "PL_") { - for (const { prop, type } of this._paramDefinitions || []) { + for (const { prop, type, envVars } of this._paramDefinitions || []) { // type is another config object if (typeof type === "function") { const newPrefix = `${prefix}${prop.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase()}_`; @@ -79,7 +83,13 @@ export class Config extends Serializable { const varName = `${prefix}${prop.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase()}`; - let str = env[varName]; + let str: string | undefined = undefined; + + const vars = [varName, ...(envVars || [])]; + + while (typeof str === "undefined" && vars.length) { + str = env[vars.pop() || ""]; + } if (typeof str === "undefined") { continue; diff --git a/packages/core/src/config/padloc.ts b/packages/core/src/config/padloc.ts index 3a5167554..8635cada4 100644 --- a/packages/core/src/config/padloc.ts +++ b/packages/core/src/config/padloc.ts @@ -169,6 +169,36 @@ export class RequestLogConfig extends RequestLoggerConfig { storage?: DataStorageConfig; } +export class PWAConfig extends Config { + @ConfigParam("string", { required: true, default: "./pwa" }, "The build output directory directory.") + dir: string = "./pwa"; + + @ConfigParam( + "string", + { required: true, default: "http://localhost:8080", envVars: ["PL_CLIENT_URL"] }, + "The url the app will be available on." + ) + url: string = "http://localhost:8080"; + + @ConfigParam( + "number", + { default: 8080 }, + "The port to serve the app over. Omit this if you want to " + + "serve those files another way (like through nginx or caddy)." + ) + port?: number; + + @ConfigParam("boolean", { default: false }, "Set to `true` to disable adding a CSP to the app.") + disableCSP?: boolean = false; +} + +export class AdminConfig extends PWAConfig {} + +export class AssetsConfig extends Config { + @ConfigParam("string", { required: true, default: "./assets" }, "The directory containing the assets.") + dir: string = "./assets"; +} + export class PadlocConfig extends Config { constructor(init: Partial = {}) { super(); @@ -212,6 +242,19 @@ export class PadlocConfig extends Config { ) provisioning = new ProvisioningConfig(); + @ConfigParam(PWAConfig, { required: true }, "The configuration for the web app.") + pwa: PWAConfig = new PWAConfig(); + + @ConfigParam(AdminConfig, { required: true }, "The configuration for the admin portal.") + admin: AdminConfig = new AdminConfig(); + + @ConfigParam( + AssetsConfig, + { required: true }, + "Config for assets like app icon, logos, email templates, custom style sheets etc." + ) + assets: AssetsConfig = new AssetsConfig(); + @ConfigParam( DirectoryConfig, { required: { prop: "provisioning.backend", value: "directory" } }, diff --git a/packages/core/src/config/server.ts b/packages/core/src/config/server.ts index 34f81199e..8449203b2 100644 --- a/packages/core/src/config/server.ts +++ b/packages/core/src/config/server.ts @@ -3,13 +3,12 @@ import { Config, ConfigParam } from "../config"; /** Server configuration */ export class ServerConfig extends Config { - /** URL where the client interface is hosted. Used for creating links into the application */ @ConfigParam( "string", - { required: true, default: "http://localhost:8080" }, - "URL where the client interface is hosted. Used for creating links into the application" + { required: true, default: "http://localhost:3000" }, + "The url the server will be reachable on." ) - clientUrl = "http://localhost:8080"; + url: string = "http://localhost:3000"; /** Email address to report critical errors to */ @ConfigParam("string", {}, "Email address for reporting unexpected server errors.") diff --git a/packages/core/src/encoding.ts b/packages/core/src/encoding.ts index 9bbabc343..e48e35b1d 100644 --- a/packages/core/src/encoding.ts +++ b/packages/core/src/encoding.ts @@ -1,6 +1,6 @@ import { Err, ErrorCode } from "./error"; import { toByteArray, fromByteArray, byteLength, isBase64 } from "./base64"; -import { upgrade, downgrade } from "./migrations"; +import { upgrade, downgrade, LATEST_VERSION } from "./migrations"; import { BigInteger } from "../vendor/jsbn"; export { bytesToBase32, base32ToBytes } from "./base32"; @@ -10,10 +10,51 @@ export interface SerializationOptions { toProperty: string; exclude: boolean; arrayDeserializeIndividually: boolean; + type: "string" | "number" | "boolean" | SerializableConstructor | SerializableConstructor[]; + options?: any[]; + default?: any; + description?: string; fromRaw: (raw: any) => any; toRaw: (val: any, version?: string) => any; } +function schemaFromPropertyDefinition(def: SerializationOptions) { + let schema: any = {}; + + switch (def.type) { + case "string": + schema = def.options + ? { type: "string", enum: def.options, default: def.default } + : { type: "string", default: def.default }; + break; + case "boolean": + schema = { type: "boolean", default: def.default }; + break; + case "number": + schema = { type: "number", default: def.default }; + break; + default: + schema = Array.isArray(def.type) + ? { + type: "object", + oneOf: def.type.map((t) => new t().getSchema()), + } + : new def.type().getSchema(); + } + + if (def.description) { + schema.description = def.description; + } + + if (def.options) { + schema.description = `${schema.description || ""}\n\nPossible values are:${def.options.map( + (option) => ` \`${option}\`` + )}`; + } + + return schema; +} + function registerSerializationOptions(proto: Serializable, property: string, opts: Partial) { if (!proto.hasOwnProperty("_propertySerializationOptions")) { const parentOptions = proto._propertySerializationOptions || []; @@ -22,19 +63,16 @@ function registerSerializationOptions(proto: Serializable, property: string, opt // proto._propertySerializationOptions = proto._propertySerializationOptions.filter(o => o.property === property); - proto._propertySerializationOptions.unshift( - Object.assign( - { - property, - toProperty: property, - exclude: false, - arrayDeserializeIndividually: true, - toRaw: () => {}, - fromRaw: () => {}, - }, - opts - ) - ); + proto._propertySerializationOptions!.unshift({ + property, + toProperty: property, + exclude: false, + arrayDeserializeIndividually: true, + toRaw: () => {}, + fromRaw: () => {}, + type: "string", + ...opts, + }); } /** @@ -46,6 +84,7 @@ export function AsBigInteger(toProperty?: string) { toProperty: toProperty || prop, toRaw: (val: BigInteger) => val.toString(), fromRaw: (raw: string) => new BigInteger(raw), + type: "string", }); } @@ -61,6 +100,7 @@ export function AsSerializable(cls: SerializableConstructor | SerializableConstr const c = Array.isArray(cls) ? cls.find((c) => new c().kind === raw.kind) : cls; return c ? new c().fromRaw(raw) : raw; }, + type: cls, }); } @@ -70,6 +110,7 @@ export function AsBytes(toProperty?: string) { toProperty: toProperty || prop, toRaw: (val: any) => bytesToBase64(val), fromRaw: (raw: any) => base64ToBytes(raw), + type: "string", }); } @@ -163,7 +204,7 @@ export class Serializable { return this.constructor.name.toLowerCase(); } - _propertySerializationOptions!: SerializationOptions[]; + _propertySerializationOptions?: SerializationOptions[]; /** * This is called during deserialization and should verify that all @@ -249,6 +290,31 @@ export class Serializable { return new this.constructor().fromRaw(this.toRaw()); } + getSchema(): any { + const schema: any = { + type: "object", + properties: { + kind: { const: this.kind }, + version: { const: LATEST_VERSION }, + }, + additionalProperties: false, + }; + + for (const [prop, val] of Object.entries(this)) { + const def = this._propertySerializationOptions?.find((opts) => opts.property === prop); + if (prop.startsWith("_") || (def && def.exclude)) { + continue; + } + if (def) { + schema.properties[prop] = schemaFromPropertyDefinition(def); + } else { + schema.properties[prop] = val instanceof Serializable ? val.getSchema() : typeof val; + } + } + + return schema; + } + /** * Transform this object into a raw javascript object used for * serialization. The default implementation simply copies all iterable @@ -261,9 +327,7 @@ export class Serializable { let raw = {} as any; for (const [prop, val] of Object.entries(this)) { - const opts = - this._propertySerializationOptions && - this._propertySerializationOptions.find((opts) => opts.property === prop); + const opts = this._propertySerializationOptions?.find((opts) => opts.property === prop); if (prop.startsWith("_") || (opts && opts.exclude)) { continue; @@ -292,9 +356,7 @@ export class Serializable { continue; } - const opts = - this._propertySerializationOptions && - this._propertySerializationOptions.find((opts) => opts.toProperty === prop); + const opts = this._propertySerializationOptions?.find((opts) => opts.toProperty === prop); // Skip properties that have no serialization options associated with them // and are not explicitly defined as a property on the class diff --git a/packages/core/src/server.ts b/packages/core/src/server.ts index e719037d9..0ad2fe140 100644 --- a/packages/core/src/server.ts +++ b/packages/core/src/server.ts @@ -329,7 +329,8 @@ export class Controller extends API { authenticatorId: authenticator.id, type: authenticator.type, purpose: - purpose === AuthPurpose.Login && auth.accountStatus !== AccountStatus.Active + [AuthPurpose.Login, AuthPurpose.AdminLogin].includes(purpose) && + auth.accountStatus !== AccountStatus.Active ? AuthPurpose.Signup : purpose, device: this.context.device, @@ -1241,7 +1242,7 @@ export class Controller extends API { orgName: invite.org.name, invitedBy: invite.invitedBy!.name || invite.invitedBy!.email, acceptInviteUrl: `${removeTrailingSlash( - this.config.server.clientUrl + this.config.pwa.url )}${path}?${params.toString()}`, }) ); @@ -1309,7 +1310,7 @@ export class Controller extends API { member.email, new JoinOrgInviteCompletedMessage({ orgName: org.name, - openAppUrl: `${removeTrailingSlash(this.config.server.clientUrl)}/org/${org.id}`, + openAppUrl: `${removeTrailingSlash(this.config.pwa.url)}/org/${org.id}`, }) ); } catch (e) {} @@ -1608,9 +1609,7 @@ export class Controller extends API { new JoinOrgInviteAcceptedMessage({ orgName: org.name, invitee: invite.invitee.name || invite.invitee.email, - confirmMemberUrl: `${removeTrailingSlash(this.config.server.clientUrl)}/invite/${org.id}/${ - invite.id - }`, + confirmMemberUrl: `${removeTrailingSlash(this.config.pwa.url)}/invite/${org.id}/${invite.id}`, }) ); } catch (e) {} diff --git a/packages/pwa/package-lock.json b/packages/pwa/package-lock.json index b88d3f0ba..924d8a566 100644 --- a/packages/pwa/package-lock.json +++ b/packages/pwa/package-lock.json @@ -9,13 +9,14 @@ "version": "4.3.0", "license": "GPL-3.0", "devDependencies": { + "@types/sharp": "0.32.0", "clean-webpack-plugin": "3.0.0", "css-loader": "5.2.6", "file-loader": "6.2.0", "html-webpack-plugin": "5.3.1", "http-server": "14.1.0", "raw-loader": "4.0.2", - "sharp": "0.29.3", + "sharp": "0.32.1", "style-loader": "2.0.0", "ts-loader": "9.2.2", "ts-node": "10.0.0", @@ -2671,6 +2672,16 @@ "@types/node": "*" } }, + "node_modules/@types/sharp": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.32.0.tgz", + "integrity": "sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw==", + "deprecated": "This is a stub types definition. sharp provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "sharp": "*" + } + }, "node_modules/@types/sockjs": { "version": "0.3.33", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", @@ -3187,22 +3198,6 @@ "node": ">= 8" } }, - "node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "node_modules/are-we-there-yet": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", - "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", - "dev": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -3973,23 +3968,17 @@ "node": ">=4" } }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/color": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/color/-/color-4.1.0.tgz", - "integrity": "sha512-o2rkkxyLGgYoeUy1OodXpbPAQNmlNBrirQ8ODO8QutzDiDMNdezSOZLNnusQ6pUpCQJUsaJIo9DZJKqa2HgH7A==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", "dev": true, "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" } }, "node_modules/color-convert": { @@ -4008,9 +3997,9 @@ "dev": true }, "node_modules/color-string": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", - "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "dev": true, "dependencies": { "color-name": "^1.0.0", @@ -4136,12 +4125,6 @@ "node": ">=0.8" } }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true - }, "node_modules/content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -4500,12 +4483,6 @@ "node": ">=6" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true - }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -4522,15 +4499,12 @@ "dev": true }, "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", "dev": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/detect-node": { @@ -5410,22 +5384,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dev": true, - "dependencies": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5496,7 +5454,7 @@ "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "dev": true }, "node_modules/glob": { @@ -5735,12 +5693,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true - }, "node_modules/has-yarn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", @@ -7334,9 +7286,9 @@ } }, "node_modules/node-abi": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.5.0.tgz", - "integrity": "sha512-LtHvNIBgOy5mO8mPEUtkCW/YCRWYEKshIvqhe1GHHyXEHEB5mgICyYnAcl4qan3uFeRROErKGzatFHPf6kDxWw==", + "version": "3.45.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.45.0.tgz", + "integrity": "sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==", "dev": true, "dependencies": { "semver": "^7.3.5" @@ -7346,9 +7298,9 @@ } }, "node_modules/node-addon-api": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.2.0.tgz", - "integrity": "sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", "dev": true }, "node_modules/node-forge": { @@ -7417,18 +7369,6 @@ "node": ">=8" } }, - "node_modules/npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dev": true, - "dependencies": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, "node_modules/nth-check": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", @@ -7441,15 +7381,6 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -8086,19 +8017,18 @@ "dev": true }, "node_modules/prebuild-install": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.0.0.tgz", - "integrity": "sha512-IvSenf33K7JcgddNz2D5w521EgO+4aMMjFt73Uk9FRzQ7P+QZPKrp7qPsDydsSwjGt3T5xRNnM1bj1zMTD5fTA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", "dev": true, "dependencies": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", "node-abi": "^3.3.0", - "npmlog": "^4.0.1", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", @@ -8864,9 +8794,9 @@ } }, "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -9028,12 +8958,6 @@ "node": ">= 0.8.0" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, "node_modules/setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -9053,23 +8977,23 @@ } }, "node_modules/sharp": { - "version": "0.29.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.29.3.tgz", - "integrity": "sha512-fKWUuOw77E4nhpyzCCJR1ayrttHoFHBT2U/kR/qEMRhvPEcluG4BKj324+SCO1e84+knXHwhJ1HHJGnUt4ElGA==", + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.1.tgz", + "integrity": "sha512-kQTFtj7ldpUqSe8kDxoGLZc1rnMFU0AO2pqbX6pLy3b7Oj8ivJIdoKNwxHVQG2HN6XpHPJqCSM2nsma2gOXvOg==", "dev": true, "hasInstallScript": true, "dependencies": { - "color": "^4.0.1", - "detect-libc": "^1.0.3", - "node-addon-api": "^4.2.0", - "prebuild-install": "^7.0.0", - "semver": "^7.3.5", - "simple-get": "^4.0.0", + "color": "^4.2.3", + "detect-libc": "^2.0.1", + "node-addon-api": "^6.1.0", + "prebuild-install": "^7.1.1", + "semver": "^7.5.0", + "simple-get": "^4.0.1", "tar-fs": "^2.1.1", "tunnel-agent": "^0.6.0" }, "engines": { - "node": ">=12.13.0" + "node": ">=14.15.0" }, "funding": { "url": "https://opencollective.com/libvips" @@ -9164,7 +9088,7 @@ "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", "dev": true, "dependencies": { "is-arrayish": "^0.3.1" @@ -9376,32 +9300,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/string.prototype.matchall": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", @@ -9584,9 +9482,9 @@ } }, "node_modules/tar-stream/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { "inherits": "^2.0.3", @@ -9893,7 +9791,7 @@ "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, "dependencies": { "safe-buffer": "^5.0.1" @@ -10798,15 +10696,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", @@ -13261,6 +13150,15 @@ "@types/node": "*" } }, + "@types/sharp": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.32.0.tgz", + "integrity": "sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw==", + "dev": true, + "requires": { + "sharp": "*" + } + }, "@types/sockjs": { "version": "0.3.33", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", @@ -13701,22 +13599,6 @@ "picomatch": "^2.0.4" } }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "are-we-there-yet": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", - "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", - "dev": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -14284,16 +14166,10 @@ } } }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, "color": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/color/-/color-4.1.0.tgz", - "integrity": "sha512-o2rkkxyLGgYoeUy1OodXpbPAQNmlNBrirQ8ODO8QutzDiDMNdezSOZLNnusQ6pUpCQJUsaJIo9DZJKqa2HgH7A==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", "dev": true, "requires": { "color-convert": "^2.0.1", @@ -14333,9 +14209,9 @@ "dev": true }, "color-string": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", - "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "dev": true, "requires": { "color-name": "^1.0.0", @@ -14427,12 +14303,6 @@ "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", "dev": true }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true - }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -14702,12 +14572,6 @@ "rimraf": "^2.6.3" } }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true - }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -14721,9 +14585,9 @@ "dev": true }, "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", "dev": true }, "detect-node": { @@ -15419,22 +15283,6 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dev": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -15490,7 +15338,7 @@ "github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "dev": true }, "glob": { @@ -15675,12 +15523,6 @@ "has-symbols": "^1.0.2" } }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true - }, "has-yarn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", @@ -16874,18 +16716,18 @@ } }, "node-abi": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.5.0.tgz", - "integrity": "sha512-LtHvNIBgOy5mO8mPEUtkCW/YCRWYEKshIvqhe1GHHyXEHEB5mgICyYnAcl4qan3uFeRROErKGzatFHPf6kDxWw==", + "version": "3.45.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.45.0.tgz", + "integrity": "sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==", "dev": true, "requires": { "semver": "^7.3.5" } }, "node-addon-api": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.2.0.tgz", - "integrity": "sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", "dev": true }, "node-forge": { @@ -16941,18 +16783,6 @@ "path-key": "^3.0.0" } }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dev": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, "nth-check": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", @@ -16962,12 +16792,6 @@ "boolbase": "^1.0.0" } }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -17439,19 +17263,18 @@ "dev": true }, "prebuild-install": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.0.0.tgz", - "integrity": "sha512-IvSenf33K7JcgddNz2D5w521EgO+4aMMjFt73Uk9FRzQ7P+QZPKrp7qPsDydsSwjGt3T5xRNnM1bj1zMTD5fTA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", "dev": true, "requires": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", "node-abi": "^3.3.0", - "npmlog": "^4.0.1", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", @@ -18032,9 +17855,9 @@ } }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -18180,12 +18003,6 @@ "send": "0.17.1" } }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -18202,17 +18019,17 @@ } }, "sharp": { - "version": "0.29.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.29.3.tgz", - "integrity": "sha512-fKWUuOw77E4nhpyzCCJR1ayrttHoFHBT2U/kR/qEMRhvPEcluG4BKj324+SCO1e84+knXHwhJ1HHJGnUt4ElGA==", + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.1.tgz", + "integrity": "sha512-kQTFtj7ldpUqSe8kDxoGLZc1rnMFU0AO2pqbX6pLy3b7Oj8ivJIdoKNwxHVQG2HN6XpHPJqCSM2nsma2gOXvOg==", "dev": true, "requires": { - "color": "^4.0.1", - "detect-libc": "^1.0.3", - "node-addon-api": "^4.2.0", - "prebuild-install": "^7.0.0", - "semver": "^7.3.5", - "simple-get": "^4.0.0", + "color": "^4.2.3", + "detect-libc": "^2.0.1", + "node-addon-api": "^6.1.0", + "prebuild-install": "^7.1.1", + "semver": "^7.5.0", + "simple-get": "^4.0.1", "tar-fs": "^2.1.1", "tunnel-agent": "^0.6.0" } @@ -18269,7 +18086,7 @@ "simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", "dev": true, "requires": { "is-arrayish": "^0.3.1" @@ -18453,28 +18270,6 @@ "safe-buffer": "~5.1.0" } }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - } - } - }, "string.prototype.matchall": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", @@ -18611,9 +18406,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -18829,7 +18624,7 @@ "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, "requires": { "safe-buffer": "^5.0.1" @@ -19484,15 +19279,6 @@ "is-symbol": "^1.0.3" } }, - "wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", diff --git a/packages/pwa/tsconfig.json b/packages/pwa/tsconfig.json index 39fec57e2..0f4fa5b9c 100644 --- a/packages/pwa/tsconfig.json +++ b/packages/pwa/tsconfig.json @@ -1,3 +1,9 @@ { - "extends": "../app/tsconfig.json" + "extends": "../app/tsconfig.json", + "include": ["src/**/*.ts", "../app/types/**/*.ts", "webpack.config.tsm"], + "ts-node": { + "compilerOptions": { + "module": "CommonJS" + } + }, } diff --git a/packages/pwa/webpack.config.js b/packages/pwa/webpack.config.js deleted file mode 100644 index ddd916947..000000000 --- a/packages/pwa/webpack.config.js +++ /dev/null @@ -1,260 +0,0 @@ -const { resolve, join } = require("path"); -const { readFileSync, writeFileSync } = require("fs"); -const { EnvironmentPlugin } = require("webpack"); -const { InjectManifest } = require("workbox-webpack-plugin"); -const HtmlWebpackPlugin = require("html-webpack-plugin"); -const { CleanWebpackPlugin } = require("clean-webpack-plugin"); -const WebpackPwaManifest = require("webpack-pwa-manifest"); -const sharp = require("sharp"); -const { version } = require("../../package.json"); - -function removeTrailingSlash(url) { - return url.replace(/(\/*)$/, ""); -} - -const out = process.env.PL_PWA_DIR || resolve(__dirname, "dist"); -const serverUrl = removeTrailingSlash( - process.env.PL_SERVER_URL || `http://0.0.0.0:${process.env.PL_SERVER_PORT || 3000}` -); -const pwaUrl = removeTrailingSlash(process.env.PL_PWA_URL || `http://localhost:${process.env.PL_PWA_PORT || 8080}`); -const rootDir = resolve(__dirname, "../.."); -const assetsDir = resolve(rootDir, process.env.PL_ASSETS_DIR || "assets"); -const disableCsp = process.env.PL_PWA_DISABLE_CSP === "true"; - -const { name, terms_of_service } = require(join(assetsDir, "manifest.json")); - -const isBuildingLocally = pwaUrl.startsWith("http://localhost"); - -const htmlMetaTags = disableCsp - ? {} - : { - "Content-Security-Policy": { - "http-equiv": "Content-Security-Policy", - content: `default-src 'none'; base-uri 'none'; script-src blob: [REPLACE_SCRIPT]; connect-src ${serverUrl} https://api.pwnedpasswords.com [REPLACE_CONNECT]; style-src 'unsafe-inline'; font-src [REPLACE_FONT]; object-src blob:; frame-src blob:; img-src [REPLACE_IMG] blob: data: https://icons.duckduckgo.com; manifest-src [REPLACE_MANIFEST]; worker-src ${pwaUrl}/sw.js;`, - }, - }; - -module.exports = { - entry: resolve(__dirname, "src/index.ts"), - output: { - path: out, - filename: "[name].js", - chunkFilename: "[name].chunk.js", - publicPath: "/", - }, - mode: "development", - devtool: "source-map", - stats: "minimal", - resolve: { - extensions: [".ts", ".js", ".css", ".svg", ".png", ".jpg"], - alias: { - assets: assetsDir, - }, - }, - module: { - rules: [ - { - test: /\.ts$/, - loader: "ts-loader", - }, - { - test: /\.css$/, - use: ["style-loader", "css-loader"], - }, - { - test: /\.(woff|woff2|eot|ttf|otf|svg)$/, - use: ["file-loader"], - }, - { - test: /\.txt|md$/i, - use: "raw-loader", - }, - ], - }, - plugins: [ - new EnvironmentPlugin({ - PL_APP_NAME: name, - PL_PWA_URL: pwaUrl, - PL_SERVER_URL: serverUrl, - PL_BILLING_ENABLED: null, - PL_BILLING_DISABLE_PAYMENT: null, - PL_BILLING_STRIPE_PUBLIC_KEY: null, - PL_SUPPORT_EMAIL: "support@padloc.app", - PL_VERSION: version, - PL_VENDOR_VERSION: version, - PL_DISABLE_SW: false, - PL_CLIENT_SUPPORTED_AUTH_TYPES: "email", - PL_TERMS_OF_SERVICE: terms_of_service, - }), - new CleanWebpackPlugin(), - { - apply(compiler) { - if (disableCsp) { - return; - } - - compiler.hooks.compilation.tap("Update CSP - dev", (compilation) => { - HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync( - "Update CSP - dev", - (data, callback) => { - if (!isBuildingLocally) { - callback(null, data); - return; - } - - const builtFilesForCsp = new Map([ - ["script-src", [""]], - ["font-src", [""]], - ["img-src", [""]], - ["manifest-src", [""]], - ]); - - // Manually add the root for the CSP meta tag - for (const cspRule of builtFilesForCsp.keys()) { - const files = builtFilesForCsp.get(cspRule); - - data.html = data.html.replace( - `[REPLACE_${cspRule.replace("-src", "").toUpperCase()}]`, - `${files.map((file) => `${pwaUrl}/${file}`).join(" ")}` - ); - } - - // Add the websocket URL + PWA URL of webpack-dev-server to connect-src when building locally, or nothing otherwise - const connectReplacement = `ws://localhost:${process.env.PL_PWA_PORT || 8080}/ws ${pwaUrl}`; - data.html = data.html.replace("[REPLACE_CONNECT]", connectReplacement); - - callback(null, data); - } - ); - - return true; - }); - }, - }, - new HtmlWebpackPlugin({ - title: name, - template: resolve(__dirname, "src/index.html"), - meta: htmlMetaTags, - }), - new WebpackPwaManifest({ - name: name, - short_name: name, - icons: [ - { - src: resolve(__dirname, assetsDir, "app-icon.png"), - sizes: [96, 128, 192, 256, 384, 512], - }, - ], - }), - new InjectManifest({ - swSrc: resolve(__dirname, "../app/src/sw.ts"), - swDest: "sw.js", - exclude: [/favicon\.png$/, /\.map$/], - }), - { - apply(compiler) { - compiler.hooks.emit.tapPromise("Generate Favicon", async (compilation) => { - const icon = await sharp(resolve(__dirname, assetsDir, "app-icon.png")) - .resize({ - width: 256, - height: 256, - }) - .toBuffer(); - - compilation.assets["favicon.png"] = { - source: () => icon, - size: () => Buffer.byteLength(icon), - }; - - return true; - }); - }, - }, - { - apply(compiler) { - if (disableCsp) { - return; - } - - compiler.hooks.afterEmit.tapPromise("Store Built Files for CSP - non-dev", async (compilation) => { - if (isBuildingLocally) { - // Skip - return true; - } - - const fileExtensionsToCspRule = new Map([ - ["js", "script-src"], - ["map", "script-src"], - ["woff2", "font-src"], - ["svg", "img-src"], - ["png", "img-src"], - ["json", "manifest-src"], - ]); - const builtFilesForCsp = new Map([ - ["script-src", []], - ["font-src", []], - ["img-src", []], - ["manifest-src", []], - ]); - - const assets = compilation.getAssets(); - - const htmlFilePath = resolve(out, "index.html"); - let htmlFileContents = readFileSync(htmlFilePath, "utf-8"); - - for (const asset of assets) { - // Skip the file we're writing to! - if (asset.name === "index.html") { - continue; - } - - const fileExtension = asset.name.split(".").pop(); - - if (!fileExtensionsToCspRule.has(fileExtension)) { - // NOTE: Throwing an error in this hook is silently ignored, so we need to just log it and keep going - console.error(`No CSP rule found for ".${fileExtension}"! (${asset.name})`); - continue; - } - - const cspRule = fileExtensionsToCspRule.get(fileExtension); - - if (!builtFilesForCsp.has(cspRule)) { - // NOTE: Throwing an error in this hook is silently ignored, so we need to just log it and keep going - console.error(`No CSP rule found for "${cspRule}"! (${fileExtension})`); - continue; - } - - builtFilesForCsp.get(cspRule).push(asset.name); - } - - // Manually add the files in for the CSP meta tag - for (const cspRule of builtFilesForCsp.keys()) { - // Sort all files first - const files = builtFilesForCsp.get(cspRule); - files.sort(); - - htmlFileContents = htmlFileContents.replace( - `[REPLACE_${cspRule.replace("-src", "").toUpperCase()}]`, - `${files.map((file) => `${pwaUrl}/${file}`).join(" ")}` - ); - } - - // Nothing more to connect to, in non-dev - htmlFileContents = htmlFileContents.replace("[REPLACE_CONNECT]", ""); - - writeFileSync(htmlFilePath, htmlFileContents, "utf-8"); - - return true; - }); - }, - }, - ], - devServer: { - historyApiFallback: true, - host: "0.0.0.0", - port: process.env.PL_PWA_PORT || 8080, - // hot: false, - // liveReload: false, - client: { overlay: false }, - }, -}; diff --git a/packages/pwa/webpack.config.ts b/packages/pwa/webpack.config.ts new file mode 100644 index 000000000..c91094156 --- /dev/null +++ b/packages/pwa/webpack.config.ts @@ -0,0 +1,272 @@ +import { resolve, join } from "path"; +import { readFileSync, writeFileSync } from "fs"; +import { EnvironmentPlugin, Configuration, WebpackPluginInstance } from "webpack"; +import "webpack-dev-server"; +import { InjectManifest } from "workbox-webpack-plugin"; +import HtmlWebpackPlugin from "html-webpack-plugin"; +import { CleanWebpackPlugin } from "clean-webpack-plugin"; +import WebpackPwaManifest from "webpack-pwa-manifest"; +import sharp from "sharp"; +import { version } from "../../package.json"; +import { Compiler } from "webpack"; + +function removeTrailingSlash(url: string) { + return url.replace(/(\/*)$/, ""); +} + +const outDir = process.env.PL_PWA_DIR || resolve(__dirname, "dist"); +const serverUrl = removeTrailingSlash( + process.env.PL_SERVER_URL || `http://0.0.0.0:${process.env.PL_SERVER_PORT || 3000}` +); +const pwaUrl = removeTrailingSlash(process.env.PL_PWA_URL || `http://localhost:${process.env.PL_PWA_PORT || 8080}`); +const rootDir = resolve(__dirname, "../.."); +const assetsDir = resolve(rootDir, process.env.PL_ASSETS_DIR || "assets"); +const disableCsp = process.env.PL_PWA_DISABLE_CSP === "true"; + +const { name, terms_of_service } = require(join(assetsDir, "manifest.json")); + +const isBuildingLocally = pwaUrl.startsWith("http://localhost"); + +const htmlMetaTags = disableCsp + ? false + : { + "Content-Security-Policy": { + "http-equiv": "Content-Security-Policy", + content: `default-src 'none'; base-uri 'none'; script-src blob: [REPLACE_SCRIPT]; connect-src ${serverUrl} https://api.pwnedpasswords.com [REPLACE_CONNECT]; style-src 'unsafe-inline'; font-src [REPLACE_FONT]; object-src blob:; frame-src blob:; img-src [REPLACE_IMG] blob: data: https://icons.duckduckgo.com; manifest-src [REPLACE_MANIFEST]; worker-src ${pwaUrl}/sw.js;`, + }, + }; + +export class FaviconWebpackPlugin implements WebpackPluginInstance { + constructor(public config: { iconPath: string }) {} + + apply(compiler: Compiler) { + compiler.hooks.emit.tapPromise("Generate Favicon", async (compilation) => { + const icon = await sharp(this.config.iconPath) + .resize({ + width: 256, + height: 256, + }) + .toBuffer(); + + compilation.assets["favicon.png"] = { + source: () => icon, + size: () => Buffer.byteLength(icon), + } as any; + }); + } +} + +export class CSPWebpackPlugin implements WebpackPluginInstance { + constructor(public config: { outDir: string; enabled: boolean; mode?: "development" | "production" }) {} + + apply(compiler: Compiler) { + return this.config.mode === "production" ? this._applyProd(compiler) : this._applyDev(compiler); + } + + private _applyDev(compiler: Compiler) { + if (disableCsp) { + return; + } + + compiler.hooks.compilation.tap("Update CSP - dev", (compilation) => { + HtmlWebpackPlugin.getHooks(compilation as any).beforeEmit.tapAsync("Update CSP - dev", (data, callback) => { + if (!isBuildingLocally) { + callback(null, data); + return; + } + + const builtFilesForCsp = new Map([ + ["script-src", [""]], + ["font-src", [""]], + ["img-src", [""]], + ["manifest-src", [""]], + ]); + + // Manually add the root for the CSP meta tag + for (const cspRule of builtFilesForCsp.keys()) { + const files = builtFilesForCsp.get(cspRule); + + data.html = data.html.replace( + `[REPLACE_${cspRule.replace("-src", "").toUpperCase()}]`, + `${files?.map((file) => `${pwaUrl}/${file}`).join(" ")}` + ); + } + + // Add the websocket URL + PWA URL of webpack-dev-server to connect-src when building locally, or nothing otherwise + const connectReplacement = `ws://localhost:${process.env.PL_PWA_PORT || 8080}/ws ${pwaUrl}`; + data.html = data.html.replace("[REPLACE_CONNECT]", connectReplacement); + + callback(null, data); + }); + + return true; + }); + } + + private _applyProd(compiler: Compiler) { + if (!this.config.enabled) { + return; + } + + compiler.hooks.afterEmit.tapPromise("Store Built Files for CSP - non-dev", async (compilation) => { + if (isBuildingLocally) { + // Skip + return; + } + + const fileExtensionsToCspRule = new Map([ + ["js", "script-src"], + ["map", "script-src"], + ["woff2", "font-src"], + ["svg", "img-src"], + ["png", "img-src"], + ["json", "manifest-src"], + ]); + const builtFilesForCsp = new Map([ + ["script-src", []], + ["font-src", []], + ["img-src", []], + ["manifest-src", []], + ]); + + const assets = compilation.getAssets(); + + const htmlFilePath = resolve(this.config.outDir, "index.html"); + let htmlFileContents = readFileSync(htmlFilePath, "utf-8"); + + for (const asset of assets) { + // Skip the file we're writing to! + if (asset.name === "index.html") { + continue; + } + + const fileExtension = asset.name.split(".").pop() || ""; + + if (!fileExtensionsToCspRule.has(fileExtension)) { + // NOTE: Throwing an error in this hook is silently ignored, so we need to just log it and keep going + console.error(`No CSP rule found for ".${fileExtension}"! (${asset.name})`); + continue; + } + + const cspRule = fileExtensionsToCspRule.get(fileExtension) || ""; + + if (!builtFilesForCsp.has(cspRule)) { + // NOTE: Throwing an error in this hook is silently ignored, so we need to just log it and keep going + console.error(`No CSP rule found for "${cspRule}"! (${fileExtension})`); + continue; + } + + builtFilesForCsp.get(cspRule)?.push(asset.name); + } + + // Manually add the files in for the CSP meta tag + for (const cspRule of builtFilesForCsp.keys()) { + // Sort all files first + const files = builtFilesForCsp.get(cspRule) || []; + files.sort(); + + htmlFileContents = htmlFileContents.replace( + `[REPLACE_${cspRule.replace("-src", "").toUpperCase()}]`, + `${files.map((file) => `${pwaUrl}/${file}`).join(" ")}` + ); + } + + // Nothing more to connect to, in non-dev + htmlFileContents = htmlFileContents.replace("[REPLACE_CONNECT]", ""); + + writeFileSync(htmlFilePath, htmlFileContents, "utf-8"); + }); + } +} + +export default { + entry: resolve(__dirname, "src/index.ts"), + output: { + path: outDir, + filename: "[name].js", + chunkFilename: "[name].chunk.js", + publicPath: "/", + }, + mode: "development", + devtool: "source-map", + stats: "minimal", + resolve: { + extensions: [".ts", ".js", ".css", ".svg", ".png", ".jpg"], + alias: { + assets: assetsDir, + }, + }, + module: { + rules: [ + { + test: /\.ts$/, + loader: "ts-loader", + }, + { + test: /\.css$/, + use: ["style-loader", "css-loader"], + }, + { + test: /\.(woff|woff2|eot|ttf|otf|svg)$/, + use: ["file-loader"], + }, + { + test: /\.txt|md$/i, + use: "raw-loader", + }, + ], + }, + plugins: [ + new EnvironmentPlugin({ + PL_APP_NAME: name, + PL_PWA_URL: pwaUrl, + PL_SERVER_URL: serverUrl, + PL_BILLING_ENABLED: null, + PL_BILLING_DISABLE_PAYMENT: null, + PL_BILLING_STRIPE_PUBLIC_KEY: null, + PL_SUPPORT_EMAIL: "support@padloc.app", + PL_VERSION: version, + PL_VENDOR_VERSION: version, + PL_DISABLE_SW: false, + PL_CLIENT_SUPPORTED_AUTH_TYPES: "email", + PL_TERMS_OF_SERVICE: terms_of_service, + }), + new CleanWebpackPlugin(), + new CSPWebpackPlugin({ + enabled: !disableCsp, + mode: isBuildingLocally ? "development" : "production", + outDir, + }), + new HtmlWebpackPlugin({ + title: name, + template: resolve(__dirname, "src/index.html"), + meta: htmlMetaTags, + }), + new WebpackPwaManifest({ + name, + short_name: name, + icons: [ + { + src: resolve(__dirname, assetsDir, "app-icon.png"), + sizes: [96, 128, 192, 256, 384, 512], + }, + ], + }) as any, + new InjectManifest({ + swSrc: resolve(__dirname, "../app/src/sw.ts"), + swDest: "sw.js", + exclude: [/favicon\.png$/, /\.map$/], + }), + new FaviconWebpackPlugin({ + iconPath: resolve(__dirname, assetsDir, "app-icon.png"), + }), + ], + devServer: { + historyApiFallback: true, + host: "0.0.0.0", + port: process.env.PL_PWA_PORT || 8080, + // hot: false, + // liveReload: false, + client: { overlay: false }, + }, +} as Configuration; diff --git a/tsconfig.json b/tsconfig.json index 791aeead1..45d8dafa6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "es2019", - "module": "es2015", + "module": "ESNext", "moduleResolution": "node", "isolatedModules": false, "noImplicitAny": true, @@ -23,5 +23,10 @@ "allowJs": true, "useUnknownInCatchVariables": false }, + "ts-node": { + "compilerOptions": { + "module": "CommonJS" + } + }, "exclude": ["docs"] }