diff --git a/.eslintrc.js b/.eslintrc.js index e0cfec85..79ed811d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,6 +5,7 @@ module.exports = { root: true, parserOptions: { + parser: require.resolve("@typescript-eslint/parser"), ecmaVersion: "2021", // Allows for the parsing of modern ECMAScript features }, diff --git a/.vscode/settings.json b/.vscode/settings.json index bcfdd156..022fb962 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,8 +14,6 @@ "**/Thumbs.db": true, ".editorconfig": true, ".eslintignore": true, - ".eslintrc.js": true, - ".gitignore": true, ".npmrc": true, ".postcssrc.js": true, ".quasar": true, diff --git a/package-lock.json b/package-lock.json index 98f2dfc1..69f0323a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,9 @@ "@quasar/app-vite": "^1.0.0", "@quasar/quasar-app-extension-testing": "^2.1.0", "@quasar/quasar-app-extension-testing-unit-vitest": "^0.2.1", + "@types/node": "^20.2.1", + "@typescript-eslint/eslint-plugin": "^5.59.6", + "@typescript-eslint/parser": "^5.59.6", "@vue/test-utils": "^2.0.0", "autoprefixer": "^10.4.14", "electron": "^23.1.1", @@ -38,6 +41,7 @@ "eslint-config-prettier": "^8.1.0", "eslint-plugin-vue": "^9.0.0", "prettier": "^2.5.1", + "typescript": "^5.0.4", "vitest": "^0.29.1", "workbox-build": "^6.5.4", "workbox-cacheable-response": "^6.5.4", @@ -2161,6 +2165,42 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", @@ -3117,6 +3157,12 @@ "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", "dev": true }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -3146,9 +3192,9 @@ "optional": true }, "node_modules/@types/node": { - "version": "18.14.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.0.tgz", - "integrity": "sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==", + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.1.tgz", + "integrity": "sha512-DqJociPbZP1lbZ5SQPk4oag6W7AyaGMO6gSfRwq3PWl4PXTwJpRQJhDq4W0kzrg3w6tJ1SwlvGZ5uKFHY13LIg==", "dev": true }, "node_modules/@types/qs": { @@ -3181,6 +3227,12 @@ "@types/node": "*" } }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, "node_modules/@types/serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", @@ -3212,6 +3264,305 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.6.tgz", + "integrity": "sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/type-utils": "5.59.6", + "@typescript-eslint/utils": "5.59.6", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.6.tgz", + "integrity": "sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/typescript-estree": "5.59.6", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz", + "integrity": "sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/visitor-keys": "5.59.6" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz", + "integrity": "sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.59.6", + "@typescript-eslint/utils": "5.59.6", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.6.tgz", + "integrity": "sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz", + "integrity": "sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/visitor-keys": "5.59.6", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.6.tgz", + "integrity": "sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/typescript-estree": "5.59.6", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz", + "integrity": "sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.6", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@vitejs/plugin-vue": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-2.3.4.tgz", @@ -3802,6 +4153,15 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/asar": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/asar/-/asar-3.2.0.tgz", @@ -5217,6 +5577,18 @@ "minimatch": "^3.0.4" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -6459,6 +6831,19 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/eslint-utils": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", @@ -6721,6 +7106,15 @@ "node": ">=4.0" } }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", @@ -7563,6 +7957,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globrex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", @@ -9413,6 +9827,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -9982,6 +10402,15 @@ "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", "dev": true }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.0.tgz", @@ -10615,15 +11044,6 @@ "tslib": "^1.9.3" } }, - "node_modules/recrawl-sync/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/recrawl-sync/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -11408,6 +11828,15 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", @@ -12162,6 +12591,27 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -12233,17 +12683,16 @@ } }, "node_modules/typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "optional": true, - "peer": true, + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=12.20" } }, "node_modules/ufo": { diff --git a/package.json b/package.json index e4e950ba..3673a518 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,10 @@ "@quasar/app-vite": "^1.0.0", "@quasar/quasar-app-extension-testing": "^2.1.0", "@quasar/quasar-app-extension-testing-unit-vitest": "^0.2.1", + "@types/node": "^20.2.1", + "@typescript-eslint/eslint-plugin": "^5.59.6", + "@typescript-eslint/parser": "^5.59.6", + "@vue/test-utils": "^2.0.0", "autoprefixer": "^10.4.14", "electron": "^23.1.1", "electron-packager": "^15.5.2", @@ -45,15 +49,15 @@ "eslint-config-prettier": "^8.1.0", "eslint-plugin-vue": "^9.0.0", "prettier": "^2.5.1", + "typescript": "^5.0.4", + "vitest": "^0.29.1", "workbox-build": "^6.5.4", "workbox-cacheable-response": "^6.5.4", "workbox-core": "^6.5.4", "workbox-expiration": "^6.5.4", "workbox-precaching": "^6.5.4", "workbox-routing": "^6.5.4", - "workbox-strategies": "^6.5.4", - "@vue/test-utils": "^2.0.0", - "vitest": "^0.29.1" + "workbox-strategies": "^6.5.4" }, "engines": { "node": ">=16.11.0", diff --git a/src/js/notify.js b/src/js/notify.ts similarity index 57% rename from src/js/notify.js rename to src/js/notify.ts index e53d03fd..08e1f662 100644 --- a/src/js/notify.js +++ b/src/js/notify.ts @@ -1,25 +1,35 @@ -import { Notify } from "quasar"; +import { Notify, QNotifyCreateOptions } from "quasar"; import axios, { AxiosError } from "axios"; -function notifyApiError(error) { - let types = { - 400: "warning", - 401: "warning", - 500: "negative", - }; +type ApiError = + | AxiosError + | { + response: { + status: number; + statusText: string; + data: { message?: string; detail?: string }; + }; + }; + +type StatusMap = { [x: number]: "warning" | "negative" }; +const errorTypes = { + 400: "warning", + 401: "warning", + 500: "negative", +} as StatusMap; + +function notifyApiError(error: ApiError) { if (axios.isAxiosError(error)) { notifyAxiosError(error); return; } Notify.create({ timeout: 5000, - type: types[error.response.status] || "warning", - message: error.response.data.message || error.response.data.detail || null, - caption: - [error.response.status, " ", error.response.statusText] - .join("") - .toUpperCase() || null, - icon: null, + type: errorTypes[error.response.status] ?? "warning", + message: error.response.data.message ?? error.response.data.detail, + caption: [error.response.status, " ", error.response.statusText] + .join("") + .toUpperCase(), }); } @@ -27,22 +37,24 @@ function notifyApiError(error) { * Cashu-TS will return axios errors when certain calls fail, so we should handle those * @param {AxiosError} error */ -function notifyAxiosError(error) { +function notifyAxiosError(error: AxiosError) { Notify.create({ timeout: 5000, - type: types[error.status] || "warning", + type: errorTypes[error.status!] || "warning", message: error.message, caption: error.code, - icon: null, }); } -async function notifySuccess(message, position = "top") { +async function notifySuccess( + message: string, + position = "top" as QNotifyCreateOptions["position"] +) { Notify.create({ timeout: 5000, type: "positive", message: message, - position: position, + position, progress: true, actions: [ { @@ -54,11 +66,11 @@ async function notifySuccess(message, position = "top") { }); } -async function notifyError(message, caption = null) { +async function notifyError(message: string, caption?: string) { Notify.create({ color: "red", message: message, - caption: caption, + caption, position: "top", progress: true, actions: [ @@ -71,7 +83,11 @@ async function notifyError(message, caption = null) { }); } -async function notifyWarning(message, caption = null, timeout = 5000) { +async function notifyWarning( + message: string, + caption?: string, + timeout = 5000 +) { Notify.create({ timeout: timeout, type: "warning", @@ -89,20 +105,13 @@ async function notifyWarning(message, caption = null, timeout = 5000) { }); } -async function notify( - message, - type = "null", - position = "top", - caption = null, - color = null -) { +async function notify(message: string) { // failure Notify.create({ timeout: 5000, type: "null", color: "grey", message: message, - caption: null, position: "top", actions: [ { diff --git a/src/js/token.js b/src/js/token.ts similarity index 51% rename from src/js/token.js rename to src/js/token.ts index 674d2a60..b7e87dbc 100644 --- a/src/js/token.js +++ b/src/js/token.ts @@ -1,28 +1,19 @@ -import { getDecodedToken } from "@cashu/cashu-ts"; -/** - * Functions related to cashu tokens - * @typedef {{C: string, amount: number, id: number, secret: number}} Proof - * @typedef {{proofs: Proof[], mint: string}} Token - */ +import { type Token, getDecodedToken } from "@cashu/cashu-ts"; export default { decode, getProofs, getMint }; /** * Decodes an encoded cashu token - * @param {string} encoded_token - * @returns {{token: Token[]}} */ -function decode(encoded_token) { - if (!encoded_token || encoded_token === "") return ""; +function decode(encoded_token: string) { + if (!encoded_token || encoded_token === "") return; return getDecodedToken(encoded_token); } /** * Returns a list of proofs from a decoded token - * @param {{token: Token[]}} decoded_token - * @returns {Proof[]} */ -function getProofs(decoded_token) { +function getProofs(decoded_token: Token) { if ( !(decoded_token.token.length > 0) || !(decoded_token.token[0].proofs.length > 0) @@ -32,11 +23,7 @@ function getProofs(decoded_token) { return decoded_token.token.map((t) => t.proofs).flat(); } -/** - * @param {{token: Token[]}} decoded_token - * @returns {string} - */ -function getMint(decoded_token) { +function getMint(decoded_token: Token) { /* Returns first mint of a token (very rough way). */ diff --git a/src/stores/mints.js b/src/stores/mints.ts similarity index 84% rename from src/stores/mints.js rename to src/stores/mints.ts index 0e883e8f..4aa3a34b 100644 --- a/src/stores/mints.js +++ b/src/stores/mints.ts @@ -2,18 +2,25 @@ import { defineStore } from "pinia"; import { useLocalStorage } from "@vueuse/core"; import { useWorkersStore } from "./workers"; import { notifyApiError, notifyError, notifySuccess } from "src/js/notify"; -import { CashuMint } from "@cashu/cashu-ts"; +import { CashuMint, MintKeys, Proof } from "@cashu/cashu-ts"; + +type Mint = { + url: string; + balance: number; + keys?: MintKeys; + keysets?: string[]; +}; export const useMintsStore = defineStore("mints", { state: () => { return { - activeMintUrl: useLocalStorage("cashu.activeMintUrl", ""), - activeProofs: useLocalStorage("cashu.activeProofs", []), - keys: useLocalStorage("cashu.keys", {}), - keysets: useLocalStorage("cashu.keysets", []), + activeMintUrl: useLocalStorage("cashu.activeMintUrl", ""), + activeProofs: useLocalStorage("cashu.activeProofs", [] as Proof[]), + keys: useLocalStorage("cashu.keys", {} as MintKeys), + keysets: useLocalStorage("cashu.keysets", [] as string[]), mintToAdd: "https://8333.space:3338", - mints: useLocalStorage("cashu.mints", []), - proofs: useLocalStorage("cashu.proofs", []), + mints: useLocalStorage("cashu.mints", [] as Mint[]), + proofs: useLocalStorage("cashu.proofs", [] as Proof[]), showAddMintDialog: false, }; }, @@ -23,13 +30,13 @@ export const useMintsStore = defineStore("mints", { }, }, actions: { - setShowAddMintDialog(show) { + setShowAddMintDialog(show: boolean) { this.showAddMintDialog = show; }, - setMintToAdd(mint) { + setMintToAdd(mint: string) { this.mintToAdd = mint; }, - setProofs(proofs) { + setProofs(proofs: Proof[]) { this.proofs = proofs; if (this.keysets) { this.activeProofs = this.proofs.filter((p) => @@ -37,14 +44,14 @@ export const useMintsStore = defineStore("mints", { ); } }, - setActiveProofs(proofs) { + setActiveProofs(proofs: Proof[]) { this.activeProofs = proofs; }, - addMint: async function (url, verbose = false) { + addMint: async function (url: string, verbose = false) { try { // we have no mints at all if (this.mints.length === 0) { - this.mints = [{ url: url, balance: 0 }]; + this.mints = [{ url, balance: 0 }]; } else if (this.mints.filter((m) => m.url === url).length === 0) { // we don't have this mint yet this.mints.push({ url: url, balance: 0 }); @@ -59,7 +66,7 @@ export const useMintsStore = defineStore("mints", { this.showAddMintDialog = false; } }, - activateMint: async function (url, verbose = false, force = false) { + activateMint: async function (url: string, verbose = false, force = false) { const workers = useWorkersStore(); if (url === this.activeMintUrl && !force) { // return here because this function is called repeatedly by the @@ -100,7 +107,7 @@ export const useMintsStore = defineStore("mints", { "balance", this.getBalance() ); - } catch (error) { + } catch (error: any) { // restore previous values because the activation errored this.activeMintUrl = previousUrl; this.keys = previousKeys; @@ -121,7 +128,6 @@ export const useMintsStore = defineStore("mints", { console.log("### GET", `${this.activeMintUrl}/keys`); const data = await this.activeMint.getKeys(); const keys = data; - this.assertMintError(keys); this.keys = keys; const keysets = await this.fetchMintKeysets(); @@ -133,7 +139,7 @@ export const useMintsStore = defineStore("mints", { } return keys; - } catch (error) { + } catch (error: any) { console.error(error); try { notifyApiError(error); @@ -145,11 +151,10 @@ export const useMintsStore = defineStore("mints", { // attention: this function overwrites this.keysets try { const data = await this.activeMint.getKeySets(); - this.assertMintError(data); this.keysets = data.keysets; return data.keysets; - } catch (error) { + } catch (error: any) { console.error(error); try { notifyApiError(error); @@ -157,7 +162,7 @@ export const useMintsStore = defineStore("mints", { throw error; } }, - removeMint: async function (url) { + removeMint: async function (url: string) { this.mints = this.mints.filter((m) => m.url !== url); if (url === this.activeMintUrl) { this.activeMintUrl = ""; @@ -168,7 +173,7 @@ export const useMintsStore = defineStore("mints", { } notifySuccess("Mint removed."); }, - restoreFromBackup: function (backup) { + restoreFromBackup: function (backup: any) { if (!backup || !backup["cashu.welcomeDialogSeen"]) { notifyError("Unrecognized Backup Format!"); } else { @@ -197,7 +202,7 @@ export const useMintsStore = defineStore("mints", { notifySuccess("Backup successfully restored!"); } }, - assertMintError: function (response, verbose = true) { + assertMintError: function (response: { error: any }, verbose = true) { if (response.error != null) { if (verbose) { notifyError(response.error, "Mint error"); diff --git a/src/stores/tokens.js b/src/stores/tokens.ts similarity index 56% rename from src/stores/tokens.js rename to src/stores/tokens.ts index 488dedf9..8cea9127 100644 --- a/src/stores/tokens.js +++ b/src/stores/tokens.ts @@ -1,16 +1,35 @@ import { useLocalStorage } from "@vueuse/core"; import { date } from "quasar"; import { defineStore } from "pinia"; +import { Proof, Token } from "@cashu/cashu-ts"; + +/** + * The tokens store handles everything related to tokens and proofs + */ + +type HistoryToken = { + status: "paid" | "pending"; + amount: number; + date: string; + token: string; +}; export const useTokensStore = defineStore("tokens", { state: () => ({ - historyTokens: useLocalStorage("cashu.historyTokens", []), + historyTokens: useLocalStorage("cashu.historyTokens", [] as HistoryToken[]), + proofs: useLocalStorage("cashu.proofs", [] as Proof[]), }), actions: { /** * @param {{amount: number, serializedProofs: string}} */ - addPaidToken({ amount, serializedProofs }) { + addPaidToken({ + amount, + serializedProofs, + }: { + amount: number; + serializedProofs: string; + }) { this.historyTokens.push({ status: "paid", amount, @@ -18,10 +37,13 @@ export const useTokensStore = defineStore("tokens", { token: serializedProofs, }); }, - /** - * @param {{amount: number, serializedProofs: string}} param0 - */ - addPendingToken({ amount, serializedProofs }) { + addPendingToken({ + amount, + serializedProofs, + }: { + amount: number; + serializedProofs: string; + }) { this.historyTokens.push({ status: "pending", amount, @@ -29,10 +51,7 @@ export const useTokensStore = defineStore("tokens", { token: serializedProofs, }); }, - /** - * @param {string} token - */ - setTokenPaid(token) { + setTokenPaid(token: string) { const index = this.historyTokens.findIndex((t) => t.token === token); if (index >= 0) { this.historyTokens[index].status = "paid"; diff --git a/src/stores/wallet.js b/src/stores/wallet.ts similarity index 63% rename from src/stores/wallet.js rename to src/stores/wallet.ts index c04fd01f..fcc11fd5 100644 --- a/src/stores/wallet.js +++ b/src/stores/wallet.ts @@ -3,17 +3,34 @@ import { currentDateStr } from "src/js/utils"; import { notifyApiError } from "src/js/notify"; import { useMintsStore } from "./mints"; import { useLocalStorage } from "@vueuse/core"; +import { CashuMint, CashuWallet } from "@cashu/cashu-ts"; + +type Invoice = { + amount: number; + bolt11: string; + hash: string; + memo: string; +}; + +type InvoiceHistory = Invoice & { + date: string; + status: "pending" | "paid"; + mint?: string; +}; export const useWalletStore = defineStore("wallet", { state: () => { return { - invoiceHistory: useLocalStorage("cashu.invoiceHistory", []), + invoiceHistory: useLocalStorage( + "cashu.invoiceHistory", + [] as InvoiceHistory[] + ), invoiceData: { amount: 0, memo: "", bolt11: "", hash: "", - }, + } as Invoice, payInvoiceData: { blocking: false, bolt11: "", @@ -32,7 +49,8 @@ export const useWalletStore = defineStore("wallet", { getters: { wallet() { const mints = useMintsStore(); - const wallet = new CashuWallet(mints.keys, mints.activeMint); + const mint = new CashuMint(mints.activeMintUrl); + const wallet = new CashuWallet(mint); return wallet; }, }, @@ -41,12 +59,10 @@ export const useWalletStore = defineStore("wallet", { * Ask the mint to generate an invoice for the given amount * Upon paying the request, the mint will credit the wallet with * cashu tokens - * @param {number | null} amount - * @returns */ - requestMint: async function (amount = null) { + requestMint: async function (amount?: number) { const mints = useMintsStore(); - if (amount != null) { + if (amount) { this.invoiceData.amount = amount; } try { @@ -55,26 +71,21 @@ export const useWalletStore = defineStore("wallet", { ); this.invoiceData.bolt11 = data.pr; this.invoiceData.hash = data.hash; - this.invoiceHistory.push( - // extend dictionary - Object.assign({}, this.invoiceData, { - date: currentDateStr(), - status: "pending", - mint: this.activeMintUrl, - }) - ); + this.invoiceHistory.push({ + ...this.invoiceData, + date: currentDateStr(), + status: "pending", + mint: mints.activeMintUrl, + }); return data; - } catch (error) { + } catch (error: any) { console.error(error); notifyApiError(error); } }, - /** - * Sets an invoice status to paid - * @param {string} payment_hash - */ - setInvoicePaid(payment_hash) { + setInvoicePaid(payment_hash: string) { const invoice = this.invoiceHistory.find((i) => i.hash === payment_hash); + if (!invoice) return; invoice.status = "paid"; }, }, diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..edb75bbf --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "@quasar/app-vite/tsconfig-preset", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "src/*": ["src/*"], + "app/*": ["*"], + "components/*": ["src/components/*"], + "layouts/*": ["src/layouts/*"], + "pages/*": ["src/pages/*"], + "assets/*": ["src/assets/*"], + "boot/*": ["src/boot/*"], + "stores/*": ["src/stores/*"], + "vue$": ["node_modules/vue/dist/vue.runtime.esm-bundler.js"] + } + } +}