From 20620c3aaea038501c030e756dd800d7bc028221 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Wed, 26 Feb 2025 10:28:55 +0000 Subject: [PATCH] refactor: reorganize deep linking types and interfaces Changes: - Move deep link types from types/ to interfaces/ - Export baseUrlSchema for external use - Add trailing commas for better git diffs - Fix type inference for deepLinkSchemas - Add deepLinks export to interfaces/index.ts - Remove duplicate SuccessResult interface - Update import paths in services/deepLinks.ts This improves code organization by centralizing interface definitions and fixing type inference issues. --- README.md | 33 +- package-lock.json | 915 +++++++++++++++++++++++++++++++++++- package.json | 2 + src/interfaces/common.ts | 5 - src/interfaces/deepLinks.ts | 13 + src/interfaces/index.ts | 1 + src/main.capacitor.ts | 19 +- src/services/deepLinks.ts | 97 ++-- src/types/deepLinks.ts | 34 +- web-push.md | 149 +++--- 10 files changed, 1110 insertions(+), 158 deletions(-) create mode 100644 src/interfaces/deepLinks.ts diff --git a/README.md b/README.md index 0607f5c..21c0cd4 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ and expand to crowd-fund with time & money, then record and see the impact of co See [project.task.yaml](project.task.yaml) for current priorities. (Numbers at the beginning of lines are estimated hours. See [taskyaml.org](https://taskyaml.org/) for details.) - - ## Setup & Building Quick start: @@ -19,15 +17,17 @@ npm install npm run dev ``` -See the test locations for "IMAGE_API_SERVER" or "PARTNER_API_SERVER" below, or use http://localhost:3000 for local endorser.ch +See the test locations for "IMAGE_API_SERVER" or "PARTNER_API_SERVER" below, or use for local endorser.ch ### Build the test & production app -``` + +```bash npm run serve ``` ### Lint and fix files -``` + +```bash npm run lint ``` @@ -35,7 +35,6 @@ npm run lint Look below for the "test-all" instructions. - ### Compile and minify for test & production * If there are DB changes: before updating the test server, open browser(s) with current version to test DB migrations. @@ -52,15 +51,19 @@ Look below for the "test-all" instructions. * For test, build the app (because test server is not yet set up to build): -``` +```bash TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_PASSKEYS_ENABLED=true npm run build ``` - ... and transfer to the test server: `rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safari` + ... and transfer to the test server: - (Let's replace that with a .env.development or .env.staging file.) + ```bash + rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safari + ``` - (Note: The test BVC_MEETUPS_PROJECT_CLAIM_ID does not resolve as a URL because it's only in the test DB and the prod redirect won't redirect there.) +(Let's replace that with a .env.development or .env.staging file.) + +(Note: The test BVC_MEETUPS_PROJECT_CLAIM_ID does not resolve as a URL because it's only in the test DB and the prod redirect won't redirect there.) * For prod, get on the server and run the correct build: @@ -76,23 +79,14 @@ TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari. * Record the new hash in the changelog. Edit package.json to increment version & add "-beta", `npm install`, and commit. Also record what version is on production. - - - - ## Tests See [TESTING.md](test-playwright/TESTING.md) for detailed test instructions. - - - ## Icons To add an icon, add to main.ts and reference with `fa` element and `icon` attribute with the hyphenated name. - - ## Other ### Reference Material @@ -104,7 +98,6 @@ To add an icon, add to main.ts and reference with `fa` element and `icon` attrib * If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",` - ### Kudos Gifts make the world go 'round! diff --git a/package-lock.json b/package-lock.json index 2ff4fdf..6ed0e14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -101,6 +101,8 @@ "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-vue": "^9.32.0", "fs-extra": "^11.3.0", + "markdownlint": "^0.37.4", + "markdownlint-cli": "^0.44.0", "npm-check-updates": "^17.1.13", "postcss": "^8.4.38", "prettier": "^3.2.5", @@ -9412,6 +9414,13 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/keyv": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", @@ -9596,6 +9605,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/verror": { "version": "1.10.10", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.10.tgz", @@ -12554,6 +12570,39 @@ "node": ">=8" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/charenc": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", @@ -13708,6 +13757,20 @@ "node": ">=0.10.0" } }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -13741,9 +13804,8 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "devOptional": true, "license": "MIT", - "optional": true, - "peer": true, "engines": { "node": ">=4.0.0" } @@ -13917,6 +13979,16 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -13947,6 +14019,20 @@ "license": "MIT", "optional": true }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dexie": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.7.tgz", @@ -17230,6 +17316,32 @@ "uint8arraylist": "^2.4.8" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -17407,6 +17519,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-directory": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", @@ -17510,6 +17633,17 @@ "npm": ">=3" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -18381,6 +18515,13 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -18468,6 +18609,33 @@ "node": ">=0.10.0" } }, + "node_modules/katex": { + "version": "0.16.21", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.21.tgz", + "integrity": "sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/keccak": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", @@ -19032,6 +19200,16 @@ "devOptional": true, "license": "MIT" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/localstorage-slim": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/localstorage-slim/-/localstorage-slim-2.7.1.tgz", @@ -19563,6 +19741,130 @@ "tmpl": "1.0.5" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdownlint": { + "version": "0.37.4", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.37.4.tgz", + "integrity": "sha512-u00joA/syf3VhWh6/ybVFkib5Zpj2e5KB/cfCei8fkSRuums6nyisTWGqjTWIOFoFwuXoTBQQiqlB4qFKp8ncQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "markdown-it": "14.1.0", + "micromark": "4.0.1", + "micromark-core-commonmark": "2.0.2", + "micromark-extension-directive": "3.0.2", + "micromark-extension-gfm-autolink-literal": "2.1.0", + "micromark-extension-gfm-footnote": "2.1.0", + "micromark-extension-gfm-table": "2.1.0", + "micromark-extension-math": "3.1.0", + "micromark-util-types": "2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.44.0.tgz", + "integrity": "sha512-ZJTAONlvF9NkrIBltCdW15DxN9UTbPiKMEqAh2EU2gwIFlrCMavyCEPPO121cqfYOrLUJWW8/XKWongstmmTeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "~13.1.0", + "glob": "~10.4.5", + "ignore": "~7.0.3", + "js-yaml": "~4.1.0", + "jsonc-parser": "~3.3.1", + "jsonpointer": "~5.0.1", + "markdownlint": "~0.37.4", + "minimatch": "~9.0.5", + "run-con": "~1.3.2", + "smol-toml": "~1.3.1" + }, + "bin": { + "markdownlint": "markdownlint.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/markdownlint-cli/node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/markdownlint-cli/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/markdownlint-cli/node_modules/ignore": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz", + "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/markdownlint-cli/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/marky": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", @@ -19635,6 +19937,13 @@ "safe-buffer": "^5.1.2" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", @@ -20104,6 +20413,542 @@ "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==", "license": "MIT" }, + "node_modules/micromark": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", + "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.4.tgz", + "integrity": "sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -21400,6 +22245,26 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -22208,6 +23073,16 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pvtsutils": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", @@ -23585,6 +24460,22 @@ "fsevents": "~2.3.2" } }, + "node_modules/run-con": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.3.2.tgz", + "integrity": "sha512-CcfE+mYiTcKEzg0IqS08+efdnH0oJ3zV0wSUFBNrMHMuxCtXvBCLzCJHatwuXDcu/RlhjTziTo/a1ruQik6/Yg==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~4.1.0", + "minimist": "^1.2.8", + "strip-json-comments": "~3.1.1" + }, + "bin": { + "run-con": "cli.js" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -24389,6 +25280,19 @@ "dev": true, "license": "MIT" }, + "node_modules/smol-toml": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.1.tgz", + "integrity": "sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, "node_modules/socks": { "version": "2.8.4", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", @@ -25941,6 +26845,13 @@ "node": "*" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, "node_modules/uint8-varint": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.4.tgz", diff --git a/package.json b/package.json index 625ce98..190146d 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,8 @@ "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-vue": "^9.32.0", "fs-extra": "^11.3.0", + "markdownlint": "^0.37.4", + "markdownlint-cli": "^0.44.0", "npm-check-updates": "^17.1.13", "postcss": "^8.4.38", "prettier": "^3.2.5", diff --git a/src/interfaces/common.ts b/src/interfaces/common.ts index 8b0589d..e450ccb 100644 --- a/src/interfaces/common.ts +++ b/src/interfaces/common.ts @@ -19,11 +19,6 @@ export interface ResultWithType { type: string; } -export interface SuccessResult extends ResultWithType { - type: "success"; - response: unknown; -} - export interface ErrorResponse { error?: { message?: string; diff --git a/src/interfaces/deepLinks.ts b/src/interfaces/deepLinks.ts new file mode 100644 index 0000000..a708a32 --- /dev/null +++ b/src/interfaces/deepLinks.ts @@ -0,0 +1,13 @@ +/** + * @file Deep Link Interface Definitions + * @author Matthew Raymer + * + * Defines the core interfaces for the deep linking system. + * These interfaces are used across the deep linking implementation + * to ensure type safety and consistent error handling. + */ + +export interface DeepLinkError extends Error { + code: string; + details?: unknown; +} diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 644c2e1..1028025 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -4,3 +4,4 @@ export * from "./common"; export * from "./limits"; export * from "./records"; export * from "./user"; +export * from "./deepLinks"; diff --git a/src/main.capacitor.ts b/src/main.capacitor.ts index 24fbe27..2b60ff0 100644 --- a/src/main.capacitor.ts +++ b/src/main.capacitor.ts @@ -1,27 +1,27 @@ /** * @file Capacitor Main Entry Point * @author Matthew Raymer - * + * * This file initializes the deep linking system for the TimeSafari app. * It sets up the connection between Capacitor's URL handling and our deep link processor. - * + * * Deep Linking Flow: * 1. Capacitor receives URL open event * 2. Event is passed to DeepLinkHandler * 3. URL is validated and processed * 4. Router navigates to appropriate view - * + * * Integration Points: * - Capacitor App plugin for URL handling * - Vue Router for navigation * - Error handling system * - Logging system - * + * * Type Safety: * - Uses DeepLinkHandler for type-safe parameter processing * - Ensures type safety between Capacitor events and app routing * - Maintains type checking through the entire deep link flow - * + * * @example * // URL open event from OS * timesafari://claim/123?view=details @@ -72,9 +72,12 @@ const handleDeepLink = async (data: { url: string }) => { await deepLinkHandler.handleDeepLink(data.url); } catch (error) { logConsoleAndDb("[DeepLink] Error handling deep link: " + error, true); - handleApiError({ - message: error instanceof Error ? error.message : String(error) - } as AxiosError, "deep-link"); + handleApiError( + { + message: error instanceof Error ? error.message : String(error), + } as AxiosError, + "deep-link", + ); } }; diff --git a/src/services/deepLinks.ts b/src/services/deepLinks.ts index 19be23c..25cc14e 100644 --- a/src/services/deepLinks.ts +++ b/src/services/deepLinks.ts @@ -1,41 +1,37 @@ /** * @file Deep Link Handler Service * @author Matthew Raymer - * + * * This service handles the processing and routing of deep links in the TimeSafari app. * It provides a type-safe interface between the raw deep links and the application router. - * + * * Architecture: * 1. DeepLinkHandler class encapsulates all deep link processing logic * 2. Uses Zod schemas from types/deepLinks for parameter validation * 3. Provides consistent error handling and logging * 4. Maps validated parameters to Vue router calls - * + * * Error Handling Strategy: * - All errors are wrapped in DeepLinkError interface * - Errors include error codes for systematic handling * - Detailed error information is logged for debugging * - Errors are propagated to the global error handler - * + * * Validation Strategy: * - URL structure validation * - Route-specific parameter validation using Zod schemas * - Query parameter validation and sanitization * - Type-safe parameter passing to router - * + * * @example * const handler = new DeepLinkHandler(router); * await handler.handleDeepLink("timesafari://claim/123?view=details"); */ import { Router } from "vue-router"; -import { deepLinkSchemas, DeepLinkParams } from "../types/deepLinks"; +import { deepLinkSchemas, baseUrlSchema } from "../types/deepLinks"; import { logConsoleAndDb } from "../db"; - -interface DeepLinkError extends Error { - code: string; - details?: unknown; -} +import type { DeepLinkError } from "../interfaces/deepLinks"; export class DeepLinkHandler { private router: Router; @@ -44,6 +40,39 @@ export class DeepLinkHandler { this.router = router; } + /** + * Parses deep link URL into path, params and query components + */ + private parseDeepLink(url: string) { + const parts = url.split("://"); + if (parts.length !== 2) { + throw { code: "INVALID_URL", message: "Invalid URL format" }; + } + + // Validate base URL structure + baseUrlSchema.parse({ + scheme: parts[0], + path: parts[1], + queryParams: {}, // Will be populated below + }); + + const [path, queryString] = parts[1].split("?"); + const [routePath, param] = path.split("/"); + + const query: Record = {}; + if (queryString) { + new URLSearchParams(queryString).forEach((value, key) => { + query[key] = value; + }); + } + + return { + path: routePath, + params: param ? { id: param } : {}, + query, + }; + } + /** * Processes incoming deep links and routes them appropriately * @param url The deep link URL to process @@ -51,21 +80,23 @@ export class DeepLinkHandler { async handleDeepLink(url: string): Promise { try { logConsoleAndDb("[DeepLink] Processing URL: " + url, false); - const { path, params, query } = this.parseDeepLink(url); - await this.validateAndRoute(path, params, query); - + // Ensure params is always a Record by converting undefined to empty string + const sanitizedParams = Object.fromEntries( + Object.entries(params).map(([key, value]) => [key, value ?? ""]), + ); + await this.validateAndRoute(path, sanitizedParams, query); } catch (error) { const deepLinkError = error as DeepLinkError; logConsoleAndDb( `[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.message}`, - true + true, ); - + throw { - code: deepLinkError.code || 'UNKNOWN_ERROR', + code: deepLinkError.code || "UNKNOWN_ERROR", message: deepLinkError.message, - details: deepLinkError.details + details: deepLinkError.details, }; } } @@ -76,25 +107,25 @@ export class DeepLinkHandler { private async validateAndRoute( path: string, params: Record, - query: Record + query: Record, ): Promise { const routeMap: Record = { - claim: 'claim', - 'claim-cert': 'claim-cert', - 'claim-add-raw': 'claim-add-raw', - 'contact-edit': 'contact-edit', - 'contact-import': 'contact-import', - project: 'project', - 'invite-one-accept': 'invite-one-accept', - 'offer-details': 'offer-details', - 'confirm-gift': 'confirm-gift' + claim: "claim", + "claim-cert": "claim-cert", + "claim-add-raw": "claim-add-raw", + "contact-edit": "contact-edit", + "contact-import": "contact-import", + project: "project", + "invite-one-accept": "invite-one-accept", + "offer-details": "offer-details", + "confirm-gift": "confirm-gift", }; const routeName = routeMap[path]; if (!routeName) { throw { - code: 'INVALID_ROUTE', - message: `Unsupported route: ${path}` + code: "INVALID_ROUTE", + message: `Unsupported route: ${path}`, }; } @@ -102,13 +133,13 @@ export class DeepLinkHandler { const schema = deepLinkSchemas[path as keyof typeof deepLinkSchemas]; const validatedParams = await schema.parseAsync({ ...params, - ...query + ...query, }); await this.router.replace({ name: routeName, params: validatedParams, - query + query, }); } -} \ No newline at end of file +} diff --git a/src/types/deepLinks.ts b/src/types/deepLinks.ts index 14e0180..a6a049b 100644 --- a/src/types/deepLinks.ts +++ b/src/types/deepLinks.ts @@ -1,25 +1,25 @@ /** * @file Deep Link Type Definitions and Validation Schemas * @author Matthew Raymer - * + * * This file defines the type system and validation schemas for deep linking in the TimeSafari app. * It uses Zod for runtime validation while providing TypeScript types for compile-time checking. - * + * * Type Strategy: * 1. Define base URL schema to validate the fundamental deep link structure * 2. Define route-specific parameter schemas with exact validation rules * 3. Generate TypeScript types from Zod schemas for type safety * 4. Export both schemas and types for use in deep link handling - * + * * Usage: * - Import schemas for runtime validation in deep link handlers * - Import types for type-safe parameter handling in components * - Use DeepLinkParams type for type-safe access to route parameters - * + * * @example * // Runtime validation * const params = deepLinkSchemas.claim.parse({ id: "123", view: "details" }); - * + * * // Type-safe parameter access * function handleClaimParams(params: DeepLinkParams["claim"]) { * // TypeScript knows params.id exists and params.view is optional @@ -28,46 +28,46 @@ import { z } from "zod"; // Base URL validation schema -const baseUrlSchema = z.object({ +export const baseUrlSchema = z.object({ scheme: z.literal("timesafari"), path: z.string(), - queryParams: z.record(z.string()).optional() + queryParams: z.record(z.string()).optional(), }); // Parameter validation schemas for each route type export const deepLinkSchemas = { claim: z.object({ id: z.string().min(1), - view: z.enum(["details", "certificate", "raw"]).optional() + view: z.enum(["details", "certificate", "raw"]).optional(), }), - + contact: z.object({ did: z.string().regex(/^did:/), action: z.enum(["edit", "import"]).optional(), - jwt: z.string().optional() + jwt: z.string().optional(), }), project: z.object({ id: z.string().min(1), - view: z.enum(["details", "edit"]).optional() + view: z.enum(["details", "edit"]).optional(), }), invite: z.object({ jwt: z.string().min(1), - type: z.enum(["one", "many"]).optional() + type: z.enum(["one", "many"]).optional(), }), gift: z.object({ id: z.string().min(1), - action: z.enum(["confirm", "details"]).optional() + action: z.enum(["confirm", "details"]).optional(), }), offer: z.object({ id: z.string().min(1), - view: z.enum(["details"]).optional() - }) + view: z.enum(["details"]).optional(), + }), }; export type DeepLinkParams = { - [K in keyof typeof deepLinkSchemas]: z.infer; -}; \ No newline at end of file + [K in keyof typeof deepLinkSchemas]: z.infer<(typeof deepLinkSchemas)[K]>; +}; diff --git a/web-push.md b/web-push.md index 9a85fea..9784982 100644 --- a/web-push.md +++ b/web-push.md @@ -37,7 +37,7 @@ possibility of users clicking "don't allow". Now, to explain what happens in Typescript, we can activate a browser's permission dialogue in this manner: -``` +```typescript function askPermission(): Promise { return new Promise(function(resolve, reject) { const permissionResult = Notification.requestPermission(function(result) { @@ -59,9 +59,9 @@ function askPermission(): Promise { The Notification.permission property indicates the permission level for the current session and returns one of the following string values: - 'granted': The user has granted permission for notifications. - 'denied': The user has denied permission for notifications. - 'default': The user has not made a choice yet. +- 'granted': The user has granted permission for notifications. +- 'denied': The user has denied permission for notifications. +- 'default': The user has not made a choice yet. Once the user has granted permission, the client application registers a service worker using the `ServiceWorkerRegistration` API. @@ -77,7 +77,7 @@ subscriptions may be done. Let's go through the `register` method first: -``` +```javascript navigator.serviceWorker.register('sw.js', { scope: '/' }) .then(function(registration) { console.log('Service worker registered successfully:', registration); @@ -108,7 +108,8 @@ Here's a version which can be used for testing locally. Note there can be caching issues in your browser! Incognito is highly recommended. sw-dev.ts -``` + +```typescript self.addEventListener('push', function(event: PushEvent) { console.log('Received a push message', event); @@ -127,9 +128,9 @@ self.addEventListener('push', function(event: PushEvent) { }); ``` - vue.config.js -``` + +```javascript module.exports = { pwa: { workboxOptions: { @@ -158,30 +159,30 @@ The VAPID (Voluntary Application Server Identification) key provides more security and authenticity for web push notifications in the following ways: Identifying the Application Server: - - The VAPID key is used to identify the application server that is sending - the push notifications. This ensures that the push notifications are - authentic and not sent by a malicious third party. - + +The VAPID key is used to identify the application server that is sending + the push notifications. This ensures that the push notifications are + authentic and not sent by a malicious third party. + Encrypting the Messages: - - The VAPID key is used to sign the push notifications sent by the - application server, ensuring that they are not tampered with during - transmission. This provides an additional layer of security and - authenticity for the push notifications. + +The VAPID key is used to sign the push notifications sent by the +application server, ensuring that they are not tampered with during +transmission. This provides an additional layer of security and +authenticity for the push notifications. Adding Contact Information: - The VAPID key allows a web application to add contact information to - the push messages sent to the browser push service. This enables the - push service to contact the application server in case of need or - provide additional debug information about the push messages. +The VAPID key allows a web application to add contact information to +the push messages sent to the browser push service. This enables the +push service to contact the application server in case of need or +provide additional debug information about the push messages. Improving Delivery Rates: - Using the VAPID key can help improve the overall performance of web push - notifications, specifically improving delivery rates. By streamlining the - delivery process, the chance of delivery errors along the way is lessened. +Using the VAPID key can help improve the overall performance of web push +notifications, specifically improving delivery rates. By streamlining the +delivery process, the chance of delivery errors along the way is lessened. If the BROWSER accepts and grants permission to subscribe to receiving from the SERVICE Web Push messages, then the BROWSER makes a subscription request to @@ -189,7 +190,7 @@ PROVIDER which creates and stores a special URL for that BROWSER. Here's a bit of code describing the above process: -``` +```typescript // b64 is the VAPID b64 = 'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U'; const applicationServerKey = urlBase64ToUint8Array(b64); @@ -210,38 +211,39 @@ registration.pushManager.subscribe(options) In this example, the `applicationServerKey` variable contains the VAPID public key, which is converted to a `Uint8Array` using a function such as this: -``` +```typescript export function toUint8Array(base64String: string, atobFn: typeof atob): Uint8Array { - const padding = "=".repeat((4 - (base64String.length % 4)) % 4); - const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/"); + const padding = "=".repeat((4 - (base64String.length % 4)) % 4); + const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/"); - const rawData = atobFn(base64); - const outputArray = new Uint8Array(rawData.length); + const rawData = atobFn(base64); + const outputArray = new Uint8Array(rawData.length); - for (let i = 0; i < rawData.length; ++i) { - outputArray[i] = rawData.charCodeAt(i); - } - return outputArray; + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i); + } + return outputArray; } ``` The options object is of type `PushSubscriptionOptions`, which includes the `userVisibleOnly` and `applicationServerKey` (ie VAPID public key) properties. - options: An object that contains the options used for creating the - subscription. This object itself has the following sub-properties: - - applicationServerKey: A public key your push service uses for application - server identification. This is normally a Uint8Array. - - userVisibleOnly: A boolean value indicating that the push messages that - are sent should be made visible to the user through a notification. - This is often set to true. + options: An object that contains the options used for creating the + subscription. This object itself has the following sub-properties: + + applicationServerKey: A public key your push service uses for application + server identification. This is normally a Uint8Array. + + userVisibleOnly: A boolean value indicating that the push messages that + are sent should be made visible to the user through a notification. + This is often set to true. The subscribe() method returns a `Promise` that resolves to a `PushSubscription` object containing details of the subscription, such as the endpoint URL and the public key. The returned data would have a form like this: +```json { "endpoint": "https://some.pushservice.com/some/unique/identifier", "expirationTime": null, @@ -250,14 +252,15 @@ public key. The returned data would have a form like this: "auth": "some_other_base64_encoded_string" } } +``` - endpoint: A string representing the endpoint URL for the push service. This - URL is essentially the push service address to which the push message would - be sent for this particular subscription. + endpoint: A string representing the endpoint URL for the push service. This + URL is essentially the push service address to which the push message would + be sent for this particular subscription. - expirationTime: A DOMHighResTimeStamp (which is basically a number or null) - representing the subscription's expiration time in milliseconds since - 01 January, 1970 UTC. This can be null if the subscription never expires. + expirationTime: A DOMHighResTimeStamp (which is basically a number or null) + representing the subscription's expiration time in milliseconds since + 01 January, 1970 UTC. This can be null if the subscription never expires. The BROWSER will, internally, then use that URL to check for incoming messages by way of the service worker we described earlier. The BROWSER also sends this @@ -273,7 +276,7 @@ via the PROVIDER so that they reach the BROWSER service worker. Just to remind us that in our service worker our code for receiving messages will look something like this: -``` +```typescript self.addEventListener('push', function(event: PushEvent) { console.log('Received a push message', event); @@ -292,7 +295,6 @@ self.addEventListener('push', function(event: PushEvent) { }); ``` - Now to address the issue of receiving notification messages on mobile devices. It should be noted that Web Push messages are only received when BROWSER is open, except in the cases of Chrome and Firefox mobile BROWSERS. In iOS, the @@ -300,13 +302,13 @@ mobile application (in our case a PWA) must be added to the Home Screen and permissions must be explicitly granted that allow the application to receive push notifications. Further, with an iOS device the user must enable wake on notification to have their device light-up when it receives a notification -(https://support.apple.com/enus/HT208081). +(). So what about #4? - The INTERMEDIARY. Well, It is possible under very special circumstances to create your own Web Push PROVIDER. The only case I've found so far relates to making an Android Custom ROM. (An Android Custom ROM is a customized version of the Android Operating System.) There are open source -IMTERMEDIARY products such as UnifiedPush (https://unifiedpush.org/) which can +IMTERMEDIARY products such as UnifiedPush () which can fulfill this role. If you are using iOS you are not permitted to make or use your own custom Web Push PROVIDER. Apple will never allow anyone to do that. Apple has none of its own. @@ -315,8 +317,7 @@ It is, however, possible to have a sort of proxy working between your SERVICE and FCM (or iOS). Services that mash up various Push notification services (like OneSignal) can perform in the role of such proxies. -#4 -The INTERMEDIARY- doesn't appear to be anything we should be spending our -time on. +# 4 -The INTERMEDIARY- doesn't appear to be anything we should be spending our time on A BROWSER may also remove a subscription. In order to remove a subscription, the registration record must be retrieved from the serviceWorker using @@ -326,8 +327,7 @@ subscription object, you may call the `unsubscribe` method. `unsubscribe` is asynchronnous and returns a boolean true if it is successful in removing the subscription and false if not. - -``` +```typescript async function unsubscribeFromPush() { // Check if the browser supports service workers if ("serviceWorker" in navigator) { @@ -362,30 +362,34 @@ unsubscribeFromPush().catch((err) => { NOTE: We could offer an option within the app to "mute" these notifications. This wouldn't turn off the notifications at the browser level, but you could make it so that your Service Worker doesn't display them even if it receives them. - # NOTIFICATION DIALOG WORKFLOW -## ON APP FIRST-LAUNCH: +## ON APP FIRST-LAUNCH + The user is periodically presented with the notification permission dialog that asks them if they want to turn on notifications. User is given 3 choices: - "Turn on Notifications": triggers the browser's own notification permission prompt. - "Maybe Later": dismisses the dialog, to reappear at a later instance. (The next time the user launches the app? After X amount of days? A combination of both?) - "Never": dismisses the dialog; app remembers to not automatically present the dialog again. -## IF THE USER CHOOSES "NEVER": +## IF THE USER CHOOSES "NEVER" + The dialog can still be accessed via the Notifications toggle switch in `AccountViewView` (which also tells the user if notifications are turned on or off). -## TO TEMPORARILY MUTE NOTIFICATIONS: +## TO TEMPORARILY MUTE NOTIFICATIONS + While notifications are turned on, the user can tap on the Mute Notifications toggle switch in `AccountViewView` (visible only when notifications are turned on) to trigger the Mute Notifications Dialog. User is given the following choices: - Several "Mute for X Hour/s" buttons to temporarily mute notifications. - "Mute until I turn it back on" button to indefinitely mute notifications. - "Cancel" to make no changes and dismiss the dialog. -## TO UNMUTE NOTIFICATIONS: +## TO UNMUTE NOTIFICATIONS + Simply tap on the Mute Notifications toggle switch in `AccountViewView` to immediately unmute notifications. No dialog needed. -## TO TURN OFF NOTIFICATIONS: +## TO TURN OFF NOTIFICATIONS + While notifications are turned on, the user can tap on the App Notifications toggle switch in `AccountViewView` to trigger the Turn Off Notifications Dialog. User is given the following choices: - "Turn off Notifications" to fully turn them off (which means the user will need to go through the dialogs agains to turn them back on). @@ -393,29 +397,28 @@ While notifications are turned on, the user can tap on the App Notifications tog # NOTIFICATION STATES -* Unpermissioned. Push server cannot send notifications to the user because it does not have permission. +- Unpermissioned. Push server cannot send notifications to the user because it does not have permission. This may be the same as when the user gave permission in the past but has since revoked it at the OS or browser level, outside the app. (User can change to Permissioned when the user gives permission.) -* Permissioned. (User can change to Unpermissioned via the OS or browser settings.) - * Active. (User can change to Muted when the user mutes notifications.) - * Muted. (User can change to Active when the user toggles it.) +- Permissioned. (User can change to Unpermissioned via the OS or browser settings.) + - Active. (User can change to Muted when the user mutes notifications.) + - Muted. (User can change to Active when the user toggles it.) (Turning mute off automatically after some amount of time is not planned in version 1.) - # TROUBLESHOOTING ## Desktop -#### Firefox +### Firefox Go to `about:debugging` and click on `Inspect` for the service worker. -#### Chrome +### Chrome Go to `chrome://inspect/#service-workers` and click on `Inspect` for the service worker. ## Mobile -#### Android +### Android -#### iOS +### iOS