├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── LICENSE ├── README.md ├── package.json ├── pnpm-lock.yaml ├── scripts └── build.js ├── src ├── _.d.ts ├── _vendor.d.ts ├── api │ ├── Cache.js │ ├── CacheStorage.js │ ├── Client.js │ ├── Clients.js │ ├── ContentIndex.js │ ├── MessageChannel.js │ ├── MessagePort.js │ ├── NavigationPreloadManager.js │ ├── Notification.js │ ├── PushManager.js │ ├── PushMessageData.js │ ├── PushSubscription.js │ ├── ServiceWorker.js │ ├── ServiceWorkerContainer.js │ ├── ServiceWorkerGlobalScope.js │ ├── ServiceWorkerRegistration.js │ ├── WindowClient.js │ ├── _.d.ts │ └── events │ │ ├── ContentIndexEvent.js │ │ ├── ErrorEvent.js │ │ ├── EventTarget.js │ │ ├── ExtendableEvent.js │ │ ├── FetchEvent.js │ │ ├── MessageEvent.js │ │ ├── NotificationEvent.js │ │ ├── PushEvent.js │ │ └── _.d.ts ├── createContext.js ├── events.js └── index.js ├── test ├── 1-caching-test.js ├── 2-clients-test.js ├── 3-index-test.js ├── 4-messaging-test.js └── fixtures │ ├── sw-client-message.js │ ├── sw-empty.js │ ├── sw-error.js │ ├── sw-fetch.js │ ├── sw-import-scripts.js │ ├── sw-install-activate.js │ ├── sw-install-precache-activate.js │ ├── sw-install-wait-until.js │ ├── sw-install.js │ ├── sw-message.js │ └── sw-oninstall.js └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | *.html 2 | test/fixtures 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "es6": true, 6 | "node": true, 7 | "mocha": true, 8 | "jasmine": true 9 | }, 10 | "parser": "@typescript-eslint/parser", 11 | "plugins": ["prettier", "@typescript-eslint"], 12 | "extends": [ 13 | "eslint:recommended", 14 | "plugin:@typescript-eslint/eslint-recommended", 15 | "plugin:@typescript-eslint/recommended", 16 | "plugin:prettier/recommended" 17 | ], 18 | "rules": { 19 | "no-prototype-builtins": "off", 20 | "sort-imports": [ 21 | "warn", 22 | { 23 | "ignoreCase": true, 24 | "memberSyntaxSortOrder": ["none", "all", "multiple", "single"] 25 | } 26 | ], 27 | "@typescript-eslint/no-use-before-define": "off", 28 | "@typescript-eslint/no-this-alias": "off", 29 | "@typescript-eslint/no-empty-function": "off", 30 | "@typescript-eslint/no-unused-vars": [ 31 | "error", 32 | { 33 | "args": "none", 34 | "ignoreRestSiblings": true, 35 | "vars": "all" 36 | } 37 | ], 38 | "@typescript-eslint/ban-ts-ignore": "off", 39 | "@typescript-eslint/ban-ts-comment": "off", 40 | "@typescript-eslint/ban-types": "off", 41 | "@typescript-eslint/explicit-function-return-type": "off", 42 | "@typescript-eslint/explicit-module-boundary-types": "off", 43 | "@typescript-eslint/no-explicit-any": "off", 44 | "@typescript-eslint/no-var-requires": "off", 45 | "@typescript-eslint/no-non-null-assertion": "off", 46 | "@typescript-eslint/camelcase": "off" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: dvlp 2 | 3 | on: push 4 | 5 | env: 6 | PNPM_CACHE_FOLDER: .pnpm-store 7 | HUSKY: 0 # Bypass husky commit hook for CI 8 | 9 | jobs: 10 | build_deploy: 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, windows-latest] 14 | node: ['16', '17'] 15 | runs-on: ${{ matrix.os }} 16 | name: Install, build, and test (OS ${{ matrix.os }} - Node ${{ matrix.node }}) 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | 21 | - name: Cache pnpm modules 22 | uses: actions/cache@v2 23 | with: 24 | path: ${{ env.PNPM_CACHE_FOLDER }} 25 | key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} 26 | restore-keys: | 27 | ${{ runner.os }}-pnpm- 28 | 29 | - name: 'Install pnpm' 30 | uses: pnpm/action-setup@v2.0.1 31 | with: 32 | version: 6.x 33 | 34 | - name: Setup Node.js 35 | uses: actions/setup-node@v2 36 | with: 37 | node-version: ${{ matrix.node }} 38 | cache: 'pnpm' 39 | 40 | - name: 'Configure pnpm' 41 | run: pnpm config set store-dir $PNPM_CACHE_FOLDER 42 | 43 | - name: 'Install dependencies' 44 | run: pnpm --frozen-lockfile install 45 | 46 | - name: Build 47 | run: pnpm run build 48 | 49 | - name: Test:unit 50 | run: pnpm run test 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /sw-test-env.js 3 | /sw-test-env.d.ts 4 | .dvlp -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | *.min.js 3 | .DS_Store 4 | .dvlp/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Alexander Pope; 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM Version](https://img.shields.io/npm/v/sw-test-env.svg?style=flat)](https://npmjs.org/package/sw-test-env) 2 | [![Build Status](https://img.shields.io/travis/popeindustries/sw-test-env.svg?style=flat)](https://github.com/popeindustries/sw-test-env/actions) 3 | 4 | # ServiceWorker Test Environment 5 | 6 | A sandboxed `ServiceWorker` context for testing your `ServiceWorker` code on the command line. 7 | 8 | Testing code written to run in a `ServiceWorker` is hard, and generally requires a browser environment and lots of ceremony to work. `sw-test-env` is the magic ingredient for easy unit/integration testing of `ServiceWorker` code. Just load your script, and poke, prod, inspect, and manipulate the `ServiceWorker` context: 9 | 10 | ```js 11 | import assert from 'assert'; 12 | import { connect } from 'sw-test-env'; 13 | 14 | // Equivalent to opening a browser window and accessing window.navigator.serviceWorker 15 | const sw = connect('http://localhost:3000', 'path/to/webroot'); 16 | 17 | async function test() { 18 | // Load and execute sw.js in a sandboxed ServiceWorker context 19 | const registration = await sw.register('sw.js'); 20 | // Trigger the 'install' event 21 | await sw.trigger('install'); 22 | // Inspect the cache contents by reading from the installing service worker's internal scope 23 | const cache = await sw.__serviceWorker__.self.caches.open('v1'); 24 | const requests = await cache.keys(); 25 | const urls = requests.map((request) => request.url); 26 | assert.ok(urls.includes('assets/index.js')); 27 | } 28 | ``` 29 | 30 | ## Features 31 | 32 | - load and execute `ServiceWorker` script files in a sandboxed context 33 | - inspect the properties of the `ServiceWorker` scope (`clients`, `caches`, `registration`, variables, etc) 34 | - manually trigger events on `ServiceWorker` (`install`, `activate`, `fetch`, `error`, etc) 35 | - connect multiple clients 36 | - register multiple, scoped `ServiceWorker` instances 37 | - `postMessage` between clients and registered `ServiceWorker` instances 38 | - use `indexedDB` 39 | - TODO: register for notifications and push messages to connected clients 40 | 41 | ## Caveats 42 | 43 | - limited `Response` streaming and body conversion (uses the primitives from [node-fetch](https://github.com/bitinn/node-fetch)) 44 | - `fetch` calls will be executed, so a request mocking tool like [nock](https://github.com/node-nock/nock) is recommended 45 | - `importScripts()` in service worker files not supported (use `import` statements instead) 46 | - requires at least version 16 of Node 47 | - not yet possible to cache based on `VARY` header 48 | - not tested against spec test suite or specific browser behaviour 49 | 50 | ## API 51 | 52 | #### **`connect(url: string, webroot: string): Promise`** 53 | 54 | Create a new `MockServiceWorkerContainer` instance at `url` (default is `http://localhost:3333/`) with `webroot` (default is current working directory). This is equivalent to opening a browser at `url` and accessing the `window.navigator.serviceworker` object. See [MockServiceWorkerContainer](#mockserviceworkercontainer) below for additional behaviour. 55 | 56 | Multiple connections to same/different origins are supported, with access to `MockServiceWorker` instances determined by `scope`. 57 | 58 | **Note**: the `webroot` argument is used to resolve the path for registering the `MockServiceWorker`. 59 | 60 | #### **`destroy(): Promise`** 61 | 62 | Destroy all active `MockServiceWorkerContainer` instances and their registered `MockServiceWorker` instances. Should generally be called after each test (for example, in `afterEach()` when using Mocha/Jest/etc). 63 | 64 | #### **`Headers, MessageChannel, Request, Response`** 65 | 66 | Classes for creating instances of `Headers`, `MessageChannel`, `Request`, and `Response` to be used when interacting with the `MockServiceWorker` context. 67 | 68 | ### MockServiceWorkerContainer 69 | 70 | In addition to the behaviour documented [here](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer), a `MockServiceWorkerContainer` instance returned by `connect()` has the following additions: 71 | 72 | #### **`register(scriptURL: String, options: { scope: string }): Promise`** 73 | 74 | Load and execute `scriptURL` in a `MockServiceWorker` context. `scriptURL` may be a relative or absolute filepath. 75 | 76 | **`options`** include: 77 | 78 | - **`scope: String`** the `MockServiceWorker` registration scope (defaults to `./`). Multiple `MockServiceWorker` instances can be registered on the same origin with different scopes. 79 | 80 | #### **`ready: Promise`** 81 | 82 | Force registered script to `install` and `activate`: 83 | 84 | ```js 85 | const registration = await sw.register('sw.js'); 86 | await sw.ready; 87 | assert.equal(sw.controller.state, 'activated'); 88 | ``` 89 | 90 | #### **`trigger(eventType: 'install' | 'activate'): Promise`** 91 | 92 | #### **`trigger(eventType: 'fetch', options: FetchEventInit): Promise`** 93 | 94 | #### **`trigger(eventType: 'error' | 'unhandledrejection', error: Error): Promise`** 95 | 96 | Manually trigger an event in the `MockServiceWorker` scope: 97 | 98 | ```js 99 | const registration = await sw.register('sw.js'); 100 | await sw.ready; 101 | const response = await sw.trigger('fetch', { request: '/assets/index.js' }); 102 | assert.equal(response.url, 'http://localhost:3333/assets/index.js'); 103 | ``` 104 | 105 | #### **`__serviceWorker__: MockServiceWorker`** 106 | 107 | Access the registered `MockServiceWorker`, including it's internal `self` scope: 108 | 109 | ```js 110 | const registration = await sw.register('sw.js'); 111 | await sw.ready; 112 | const cache = sw.__serviceWorker__.self.caches.open('v1'); 113 | const requests = await cache.keys(); 114 | const urls = requests.map((request) => request.url); 115 | assert.ok(urls.includes('assets/index.js')); 116 | ``` 117 | 118 | ## Inspiration & Thanks 119 | 120 | Special thanks goes to Pinterest ([service-worker-mock](https://github.com/pinterest/service-workers/tree/master/packages/service-worker-mock)) and Nolan Lawson ([pseudo-worker](https://github.com/nolanlawson/pseudo-worker)) for their ideas (some of which were borrowed here) and inspiring work. 121 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sw-test-env", 3 | "description": "A sandboxed ServiceWorker environment for testing", 4 | "version": "3.0.0", 5 | "author": "Alexander Pope ", 6 | "contributors": [ 7 | "Ola Christian Gundelsby " 8 | ], 9 | "repository": "https://github.com/popeindustries/sw-test-env.git", 10 | "license": "MIT", 11 | "keywords": [ 12 | "mock", 13 | "pseudo", 14 | "sandbox", 15 | "service worker", 16 | "ServiceWorker", 17 | "test", 18 | "testing", 19 | "worker" 20 | ], 21 | "type": "module", 22 | "main": "sw-test-env.js", 23 | "files": [ 24 | "bin", 25 | "*.d.ts", 26 | "*.js", 27 | "README.MD" 28 | ], 29 | "engines": { 30 | "node": ">=16" 31 | }, 32 | "scripts": { 33 | "build": "node ./scripts/build.js", 34 | "clean": "rm -f ./test/*.js && rm -rf docs", 35 | "format": "prettier --write './{src,test}/**/*'", 36 | "lint": "pnpm run lint:src && pnpm run lint:types", 37 | "lint:src": "eslint './{src,test}/**/*.js'", 38 | "lint:types": "tsc --noEmit --skipLibCheck", 39 | "preinstall": "npx only-allow pnpm", 40 | "prepare": "husky install", 41 | "test": "pnpm run build && mocha test/*.js --reporter spec --bail" 42 | }, 43 | "dependencies": { 44 | "esbuild": "~0.14.27", 45 | "fake-indexeddb": "^3.1.7", 46 | "form-data": "^4.0.0", 47 | "mime-types": "^2.1.35", 48 | "node-fetch": "^3.2.3" 49 | }, 50 | "devDependencies": { 51 | "@types/chai": "^4.3.0", 52 | "@types/mime-types": "^2.1.1", 53 | "@types/mocha": "^9.1.0", 54 | "@types/node": "^17.0.23", 55 | "@typescript-eslint/eslint-plugin": "^5.16.0", 56 | "@typescript-eslint/parser": "^5.16.0", 57 | "chai": "^4.3.6", 58 | "dvlp": "^14.2.0", 59 | "eslint": "^8.11.0", 60 | "eslint-config-prettier": "^8.5.0", 61 | "eslint-plugin-prettier": "^4.0.0", 62 | "glob": "^7.2.0", 63 | "husky": "^7.0.4", 64 | "lint-staged": "^12.3.7", 65 | "mocha": "^9.2.2", 66 | "prettier": "^2.6.1", 67 | "typescript": "4.6.3" 68 | }, 69 | "prettier": { 70 | "arrowParens": "always", 71 | "printWidth": 120, 72 | "singleQuote": true, 73 | "trailingComma": "all" 74 | }, 75 | "lint-staged": { 76 | "*.js": [ 77 | "eslint" 78 | ], 79 | "*.{js,json,md,html}": [ 80 | "prettier --write" 81 | ] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.3 2 | 3 | specifiers: 4 | '@types/chai': ^4.3.0 5 | '@types/mime-types': ^2.1.1 6 | '@types/mocha': ^9.1.0 7 | '@types/node': ^17.0.23 8 | '@typescript-eslint/eslint-plugin': ^5.16.0 9 | '@typescript-eslint/parser': ^5.16.0 10 | chai: ^4.3.6 11 | dvlp: ^14.2.0 12 | esbuild: ~0.14.27 13 | eslint: ^8.11.0 14 | eslint-config-prettier: ^8.5.0 15 | eslint-plugin-prettier: ^4.0.0 16 | fake-indexeddb: ^3.1.7 17 | form-data: ^4.0.0 18 | glob: ^7.2.0 19 | husky: ^7.0.4 20 | lint-staged: ^12.3.7 21 | mime-types: ^2.1.35 22 | mocha: ^9.2.2 23 | node-fetch: ^3.2.3 24 | prettier: ^2.6.1 25 | typescript: 4.6.3 26 | 27 | dependencies: 28 | esbuild: 0.14.27 29 | fake-indexeddb: 3.1.7 30 | form-data: 4.0.0 31 | mime-types: 2.1.35 32 | node-fetch: 3.2.3 33 | 34 | devDependencies: 35 | '@types/chai': 4.3.0 36 | '@types/mime-types': 2.1.1 37 | '@types/mocha': 9.1.0 38 | '@types/node': 17.0.23 39 | '@typescript-eslint/eslint-plugin': 5.16.0_bc68a9cd5bf604202498b1a9faaf9387 40 | '@typescript-eslint/parser': 5.16.0_eslint@8.11.0+typescript@4.6.3 41 | chai: 4.3.6 42 | dvlp: 14.2.0 43 | eslint: 8.11.0 44 | eslint-config-prettier: 8.5.0_eslint@8.11.0 45 | eslint-plugin-prettier: 4.0.0_e2923b5169e23c4db59b86a638c599a3 46 | glob: 7.2.0 47 | husky: 7.0.4 48 | lint-staged: 12.3.7 49 | mocha: 9.2.2 50 | prettier: 2.6.1 51 | typescript: 4.6.3 52 | 53 | packages: 54 | 55 | /@eslint/eslintrc/1.2.1: 56 | resolution: {integrity: sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==} 57 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 58 | dependencies: 59 | ajv: 6.12.6 60 | debug: 4.3.4 61 | espree: 9.3.1 62 | globals: 13.13.0 63 | ignore: 5.2.0 64 | import-fresh: 3.3.0 65 | js-yaml: 4.1.0 66 | minimatch: 3.1.2 67 | strip-json-comments: 3.1.1 68 | transitivePeerDependencies: 69 | - supports-color 70 | dev: true 71 | 72 | /@humanwhocodes/config-array/0.9.5: 73 | resolution: {integrity: sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==} 74 | engines: {node: '>=10.10.0'} 75 | dependencies: 76 | '@humanwhocodes/object-schema': 1.2.1 77 | debug: 4.3.4 78 | minimatch: 3.1.2 79 | transitivePeerDependencies: 80 | - supports-color 81 | dev: true 82 | 83 | /@humanwhocodes/object-schema/1.2.1: 84 | resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} 85 | dev: true 86 | 87 | /@nodelib/fs.scandir/2.1.5: 88 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 89 | engines: {node: '>= 8'} 90 | dependencies: 91 | '@nodelib/fs.stat': 2.0.5 92 | run-parallel: 1.2.0 93 | dev: true 94 | 95 | /@nodelib/fs.stat/2.0.5: 96 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 97 | engines: {node: '>= 8'} 98 | dev: true 99 | 100 | /@nodelib/fs.walk/1.2.8: 101 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 102 | engines: {node: '>= 8'} 103 | dependencies: 104 | '@nodelib/fs.scandir': 2.1.5 105 | fastq: 1.13.0 106 | dev: true 107 | 108 | /@types/chai/4.3.0: 109 | resolution: {integrity: sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==} 110 | dev: true 111 | 112 | /@types/json-schema/7.0.10: 113 | resolution: {integrity: sha512-BLO9bBq59vW3fxCpD4o0N4U+DXsvwvIcl+jofw0frQo/GrBFC+/jRZj1E7kgp6dvTyNmA4y6JCV5Id/r3mNP5A==} 114 | dev: true 115 | 116 | /@types/mime-types/2.1.1: 117 | resolution: {integrity: sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==} 118 | dev: true 119 | 120 | /@types/mocha/9.1.0: 121 | resolution: {integrity: sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==} 122 | dev: true 123 | 124 | /@types/node/17.0.23: 125 | resolution: {integrity: sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==} 126 | dev: true 127 | 128 | /@typescript-eslint/eslint-plugin/5.16.0_bc68a9cd5bf604202498b1a9faaf9387: 129 | resolution: {integrity: sha512-SJoba1edXvQRMmNI505Uo4XmGbxCK9ARQpkvOd00anxzri9RNQk0DDCxD+LIl+jYhkzOJiOMMKYEHnHEODjdCw==} 130 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 131 | peerDependencies: 132 | '@typescript-eslint/parser': ^5.0.0 133 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 134 | typescript: '*' 135 | peerDependenciesMeta: 136 | typescript: 137 | optional: true 138 | dependencies: 139 | '@typescript-eslint/parser': 5.16.0_eslint@8.11.0+typescript@4.6.3 140 | '@typescript-eslint/scope-manager': 5.16.0 141 | '@typescript-eslint/type-utils': 5.16.0_eslint@8.11.0+typescript@4.6.3 142 | '@typescript-eslint/utils': 5.16.0_eslint@8.11.0+typescript@4.6.3 143 | debug: 4.3.4 144 | eslint: 8.11.0 145 | functional-red-black-tree: 1.0.1 146 | ignore: 5.2.0 147 | regexpp: 3.2.0 148 | semver: 7.3.5 149 | tsutils: 3.21.0_typescript@4.6.3 150 | typescript: 4.6.3 151 | transitivePeerDependencies: 152 | - supports-color 153 | dev: true 154 | 155 | /@typescript-eslint/parser/5.16.0_eslint@8.11.0+typescript@4.6.3: 156 | resolution: {integrity: sha512-fkDq86F0zl8FicnJtdXakFs4lnuebH6ZADDw6CYQv0UZeIjHvmEw87m9/29nk2Dv5Lmdp0zQ3zDQhiMWQf/GbA==} 157 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 158 | peerDependencies: 159 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 160 | typescript: '*' 161 | peerDependenciesMeta: 162 | typescript: 163 | optional: true 164 | dependencies: 165 | '@typescript-eslint/scope-manager': 5.16.0 166 | '@typescript-eslint/types': 5.16.0 167 | '@typescript-eslint/typescript-estree': 5.16.0_typescript@4.6.3 168 | debug: 4.3.4 169 | eslint: 8.11.0 170 | typescript: 4.6.3 171 | transitivePeerDependencies: 172 | - supports-color 173 | dev: true 174 | 175 | /@typescript-eslint/scope-manager/5.16.0: 176 | resolution: {integrity: sha512-P+Yab2Hovg8NekLIR/mOElCDPyGgFZKhGoZA901Yax6WR6HVeGLbsqJkZ+Cvk5nts/dAlFKm8PfL43UZnWdpIQ==} 177 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 178 | dependencies: 179 | '@typescript-eslint/types': 5.16.0 180 | '@typescript-eslint/visitor-keys': 5.16.0 181 | dev: true 182 | 183 | /@typescript-eslint/type-utils/5.16.0_eslint@8.11.0+typescript@4.6.3: 184 | resolution: {integrity: sha512-SKygICv54CCRl1Vq5ewwQUJV/8padIWvPgCxlWPGO/OgQLCijY9G7lDu6H+mqfQtbzDNlVjzVWQmeqbLMBLEwQ==} 185 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 186 | peerDependencies: 187 | eslint: '*' 188 | typescript: '*' 189 | peerDependenciesMeta: 190 | typescript: 191 | optional: true 192 | dependencies: 193 | '@typescript-eslint/utils': 5.16.0_eslint@8.11.0+typescript@4.6.3 194 | debug: 4.3.4 195 | eslint: 8.11.0 196 | tsutils: 3.21.0_typescript@4.6.3 197 | typescript: 4.6.3 198 | transitivePeerDependencies: 199 | - supports-color 200 | dev: true 201 | 202 | /@typescript-eslint/types/5.16.0: 203 | resolution: {integrity: sha512-oUorOwLj/3/3p/HFwrp6m/J2VfbLC8gjW5X3awpQJ/bSG+YRGFS4dpsvtQ8T2VNveV+LflQHjlLvB6v0R87z4g==} 204 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 205 | dev: true 206 | 207 | /@typescript-eslint/typescript-estree/5.16.0_typescript@4.6.3: 208 | resolution: {integrity: sha512-SE4VfbLWUZl9MR+ngLSARptUv2E8brY0luCdgmUevU6arZRY/KxYoLI/3V/yxaURR8tLRN7bmZtJdgmzLHI6pQ==} 209 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 210 | peerDependencies: 211 | typescript: '*' 212 | peerDependenciesMeta: 213 | typescript: 214 | optional: true 215 | dependencies: 216 | '@typescript-eslint/types': 5.16.0 217 | '@typescript-eslint/visitor-keys': 5.16.0 218 | debug: 4.3.4 219 | globby: 11.1.0 220 | is-glob: 4.0.3 221 | semver: 7.3.5 222 | tsutils: 3.21.0_typescript@4.6.3 223 | typescript: 4.6.3 224 | transitivePeerDependencies: 225 | - supports-color 226 | dev: true 227 | 228 | /@typescript-eslint/utils/5.16.0_eslint@8.11.0+typescript@4.6.3: 229 | resolution: {integrity: sha512-iYej2ER6AwmejLWMWzJIHy3nPJeGDuCqf8Jnb+jAQVoPpmWzwQOfa9hWVB8GIQE5gsCv/rfN4T+AYb/V06WseQ==} 230 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 231 | peerDependencies: 232 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 233 | dependencies: 234 | '@types/json-schema': 7.0.10 235 | '@typescript-eslint/scope-manager': 5.16.0 236 | '@typescript-eslint/types': 5.16.0 237 | '@typescript-eslint/typescript-estree': 5.16.0_typescript@4.6.3 238 | eslint: 8.11.0 239 | eslint-scope: 5.1.1 240 | eslint-utils: 3.0.0_eslint@8.11.0 241 | transitivePeerDependencies: 242 | - supports-color 243 | - typescript 244 | dev: true 245 | 246 | /@typescript-eslint/visitor-keys/5.16.0: 247 | resolution: {integrity: sha512-jqxO8msp5vZDhikTwq9ubyMHqZ67UIvawohr4qF3KhlpL7gzSjOd+8471H3nh5LyABkaI85laEKKU8SnGUK5/g==} 248 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 249 | dependencies: 250 | '@typescript-eslint/types': 5.16.0 251 | eslint-visitor-keys: 3.3.0 252 | dev: true 253 | 254 | /@ungap/promise-all-settled/1.1.2: 255 | resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==} 256 | dev: true 257 | 258 | /acorn-jsx/5.3.2_acorn@8.7.0: 259 | resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 260 | peerDependencies: 261 | acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 262 | dependencies: 263 | acorn: 8.7.0 264 | dev: true 265 | 266 | /acorn/8.7.0: 267 | resolution: {integrity: sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==} 268 | engines: {node: '>=0.4.0'} 269 | hasBin: true 270 | dev: true 271 | 272 | /aggregate-error/3.1.0: 273 | resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} 274 | engines: {node: '>=8'} 275 | dependencies: 276 | clean-stack: 2.2.0 277 | indent-string: 4.0.0 278 | dev: true 279 | 280 | /ajv/6.12.6: 281 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 282 | dependencies: 283 | fast-deep-equal: 3.1.3 284 | fast-json-stable-stringify: 2.1.0 285 | json-schema-traverse: 0.4.1 286 | uri-js: 4.4.1 287 | dev: true 288 | 289 | /ansi-colors/4.1.1: 290 | resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} 291 | engines: {node: '>=6'} 292 | dev: true 293 | 294 | /ansi-escapes/4.3.2: 295 | resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} 296 | engines: {node: '>=8'} 297 | dependencies: 298 | type-fest: 0.21.3 299 | dev: true 300 | 301 | /ansi-regex/5.0.1: 302 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 303 | engines: {node: '>=8'} 304 | dev: true 305 | 306 | /ansi-regex/6.0.1: 307 | resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} 308 | engines: {node: '>=12'} 309 | dev: true 310 | 311 | /ansi-styles/4.3.0: 312 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 313 | engines: {node: '>=8'} 314 | dependencies: 315 | color-convert: 2.0.1 316 | dev: true 317 | 318 | /ansi-styles/6.1.0: 319 | resolution: {integrity: sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==} 320 | engines: {node: '>=12'} 321 | dev: true 322 | 323 | /anymatch/3.1.2: 324 | resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} 325 | engines: {node: '>= 8'} 326 | dependencies: 327 | normalize-path: 3.0.0 328 | picomatch: 2.3.1 329 | dev: true 330 | 331 | /argparse/2.0.1: 332 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 333 | dev: true 334 | 335 | /array-union/2.1.0: 336 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 337 | engines: {node: '>=8'} 338 | dev: true 339 | 340 | /assertion-error/1.1.0: 341 | resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} 342 | dev: true 343 | 344 | /astral-regex/2.0.0: 345 | resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} 346 | engines: {node: '>=8'} 347 | dev: true 348 | 349 | /asynckit/0.4.0: 350 | resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=} 351 | dev: false 352 | 353 | /balanced-match/1.0.2: 354 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 355 | dev: true 356 | 357 | /base64-arraybuffer-es6/0.7.0: 358 | resolution: {integrity: sha512-ESyU/U1CFZDJUdr+neHRhNozeCv72Y7Vm0m1DCbjX3KBjT6eYocvAJlSk6+8+HkVwXlT1FNxhGW6q3UKAlCvvw==} 359 | engines: {node: '>=6.0.0'} 360 | dev: false 361 | 362 | /binary-extensions/2.2.0: 363 | resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} 364 | engines: {node: '>=8'} 365 | dev: true 366 | 367 | /brace-expansion/1.1.11: 368 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 369 | dependencies: 370 | balanced-match: 1.0.2 371 | concat-map: 0.0.1 372 | dev: true 373 | 374 | /braces/3.0.2: 375 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 376 | engines: {node: '>=8'} 377 | dependencies: 378 | fill-range: 7.0.1 379 | dev: true 380 | 381 | /browser-stdout/1.3.1: 382 | resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} 383 | dev: true 384 | 385 | /callsites/3.1.0: 386 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 387 | engines: {node: '>=6'} 388 | dev: true 389 | 390 | /camelcase/6.3.0: 391 | resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} 392 | engines: {node: '>=10'} 393 | dev: true 394 | 395 | /chai/4.3.6: 396 | resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==} 397 | engines: {node: '>=4'} 398 | dependencies: 399 | assertion-error: 1.1.0 400 | check-error: 1.0.2 401 | deep-eql: 3.0.1 402 | get-func-name: 2.0.0 403 | loupe: 2.3.4 404 | pathval: 1.1.1 405 | type-detect: 4.0.8 406 | dev: true 407 | 408 | /chalk/4.1.2: 409 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 410 | engines: {node: '>=10'} 411 | dependencies: 412 | ansi-styles: 4.3.0 413 | supports-color: 7.2.0 414 | dev: true 415 | 416 | /check-error/1.0.2: 417 | resolution: {integrity: sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=} 418 | dev: true 419 | 420 | /chokidar/3.5.3: 421 | resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} 422 | engines: {node: '>= 8.10.0'} 423 | dependencies: 424 | anymatch: 3.1.2 425 | braces: 3.0.2 426 | glob-parent: 5.1.2 427 | is-binary-path: 2.1.0 428 | is-glob: 4.0.3 429 | normalize-path: 3.0.0 430 | readdirp: 3.6.0 431 | optionalDependencies: 432 | fsevents: 2.3.2 433 | dev: true 434 | 435 | /clean-stack/2.2.0: 436 | resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} 437 | engines: {node: '>=6'} 438 | dev: true 439 | 440 | /cli-cursor/3.1.0: 441 | resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} 442 | engines: {node: '>=8'} 443 | dependencies: 444 | restore-cursor: 3.1.0 445 | dev: true 446 | 447 | /cli-truncate/2.1.0: 448 | resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} 449 | engines: {node: '>=8'} 450 | dependencies: 451 | slice-ansi: 3.0.0 452 | string-width: 4.2.3 453 | dev: true 454 | 455 | /cli-truncate/3.1.0: 456 | resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} 457 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 458 | dependencies: 459 | slice-ansi: 5.0.0 460 | string-width: 5.1.2 461 | dev: true 462 | 463 | /cliui/7.0.4: 464 | resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} 465 | dependencies: 466 | string-width: 4.2.3 467 | strip-ansi: 6.0.1 468 | wrap-ansi: 7.0.0 469 | dev: true 470 | 471 | /color-convert/2.0.1: 472 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 473 | engines: {node: '>=7.0.0'} 474 | dependencies: 475 | color-name: 1.1.4 476 | dev: true 477 | 478 | /color-name/1.1.4: 479 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 480 | dev: true 481 | 482 | /colorette/2.0.16: 483 | resolution: {integrity: sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==} 484 | dev: true 485 | 486 | /combined-stream/1.0.8: 487 | resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} 488 | engines: {node: '>= 0.8'} 489 | dependencies: 490 | delayed-stream: 1.0.0 491 | dev: false 492 | 493 | /commander/8.3.0: 494 | resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} 495 | engines: {node: '>= 12'} 496 | dev: true 497 | 498 | /concat-map/0.0.1: 499 | resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} 500 | dev: true 501 | 502 | /core-js/3.21.1: 503 | resolution: {integrity: sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==} 504 | requiresBuild: true 505 | dev: false 506 | 507 | /cross-spawn/7.0.3: 508 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 509 | engines: {node: '>= 8'} 510 | dependencies: 511 | path-key: 3.1.1 512 | shebang-command: 2.0.0 513 | which: 2.0.2 514 | dev: true 515 | 516 | /data-uri-to-buffer/4.0.0: 517 | resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==} 518 | engines: {node: '>= 12'} 519 | dev: false 520 | 521 | /debug/4.3.3_supports-color@8.1.1: 522 | resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} 523 | engines: {node: '>=6.0'} 524 | peerDependencies: 525 | supports-color: '*' 526 | peerDependenciesMeta: 527 | supports-color: 528 | optional: true 529 | dependencies: 530 | ms: 2.1.2 531 | supports-color: 8.1.1 532 | dev: true 533 | 534 | /debug/4.3.4: 535 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 536 | engines: {node: '>=6.0'} 537 | peerDependencies: 538 | supports-color: '*' 539 | peerDependenciesMeta: 540 | supports-color: 541 | optional: true 542 | dependencies: 543 | ms: 2.1.2 544 | dev: true 545 | 546 | /debug/4.3.4_supports-color@9.2.1: 547 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 548 | engines: {node: '>=6.0'} 549 | peerDependencies: 550 | supports-color: '*' 551 | peerDependenciesMeta: 552 | supports-color: 553 | optional: true 554 | dependencies: 555 | ms: 2.1.2 556 | supports-color: 9.2.1 557 | dev: true 558 | 559 | /decamelize/4.0.0: 560 | resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} 561 | engines: {node: '>=10'} 562 | dev: true 563 | 564 | /deep-eql/3.0.1: 565 | resolution: {integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==} 566 | engines: {node: '>=0.12'} 567 | dependencies: 568 | type-detect: 4.0.8 569 | dev: true 570 | 571 | /deep-is/0.1.4: 572 | resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 573 | dev: true 574 | 575 | /delayed-stream/1.0.0: 576 | resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=} 577 | engines: {node: '>=0.4.0'} 578 | dev: false 579 | 580 | /diff/5.0.0: 581 | resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} 582 | engines: {node: '>=0.3.1'} 583 | dev: true 584 | 585 | /dir-glob/3.0.1: 586 | resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 587 | engines: {node: '>=8'} 588 | dependencies: 589 | path-type: 4.0.0 590 | dev: true 591 | 592 | /doctrine/3.0.0: 593 | resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} 594 | engines: {node: '>=6.0.0'} 595 | dependencies: 596 | esutils: 2.0.3 597 | dev: true 598 | 599 | /domexception/1.0.1: 600 | resolution: {integrity: sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==} 601 | dependencies: 602 | webidl-conversions: 4.0.2 603 | dev: false 604 | 605 | /dvlp/14.2.0: 606 | resolution: {integrity: sha512-873JCJzUSWVbto3QB3v9ausie5CgWoNLHxw43IfcU1bKHMkPCvm8ttkf1Kp3H4GpMaXoHpvYJTP7SXVKMu+8WA==} 607 | engines: {node: '>=14', pnpm: '>=6'} 608 | hasBin: true 609 | dependencies: 610 | commander: 8.3.0 611 | esbuild: 0.14.27 612 | dev: true 613 | 614 | /eastasianwidth/0.2.0: 615 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 616 | dev: true 617 | 618 | /emoji-regex/8.0.0: 619 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 620 | dev: true 621 | 622 | /emoji-regex/9.2.2: 623 | resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 624 | dev: true 625 | 626 | /esbuild-android-64/0.14.27: 627 | resolution: {integrity: sha512-LuEd4uPuj/16Y8j6kqy3Z2E9vNY9logfq8Tq+oTE2PZVuNs3M1kj5Qd4O95ee66yDGb3isaOCV7sOLDwtMfGaQ==} 628 | engines: {node: '>=12'} 629 | cpu: [x64] 630 | os: [android] 631 | requiresBuild: true 632 | optional: true 633 | 634 | /esbuild-android-arm64/0.14.27: 635 | resolution: {integrity: sha512-E8Ktwwa6vX8q7QeJmg8yepBYXaee50OdQS3BFtEHKrzbV45H4foMOeEE7uqdjGQZFBap5VAqo7pvjlyA92wznQ==} 636 | engines: {node: '>=12'} 637 | cpu: [arm64] 638 | os: [android] 639 | requiresBuild: true 640 | optional: true 641 | 642 | /esbuild-darwin-64/0.14.27: 643 | resolution: {integrity: sha512-czw/kXl/1ZdenPWfw9jDc5iuIYxqUxgQ/Q+hRd4/3udyGGVI31r29LCViN2bAJgGvQkqyLGVcG03PJPEXQ5i2g==} 644 | engines: {node: '>=12'} 645 | cpu: [x64] 646 | os: [darwin] 647 | requiresBuild: true 648 | optional: true 649 | 650 | /esbuild-darwin-arm64/0.14.27: 651 | resolution: {integrity: sha512-BEsv2U2U4o672oV8+xpXNxN9bgqRCtddQC6WBh4YhXKDcSZcdNh7+6nS+DM2vu7qWIWNA4JbRG24LUUYXysimQ==} 652 | engines: {node: '>=12'} 653 | cpu: [arm64] 654 | os: [darwin] 655 | requiresBuild: true 656 | optional: true 657 | 658 | /esbuild-freebsd-64/0.14.27: 659 | resolution: {integrity: sha512-7FeiFPGBo+ga+kOkDxtPmdPZdayrSzsV9pmfHxcyLKxu+3oTcajeZlOO1y9HW+t5aFZPiv7czOHM4KNd0tNwCA==} 660 | engines: {node: '>=12'} 661 | cpu: [x64] 662 | os: [freebsd] 663 | requiresBuild: true 664 | optional: true 665 | 666 | /esbuild-freebsd-arm64/0.14.27: 667 | resolution: {integrity: sha512-8CK3++foRZJluOWXpllG5zwAVlxtv36NpHfsbWS7TYlD8S+QruXltKlXToc/5ZNzBK++l6rvRKELu/puCLc7jA==} 668 | engines: {node: '>=12'} 669 | cpu: [arm64] 670 | os: [freebsd] 671 | requiresBuild: true 672 | optional: true 673 | 674 | /esbuild-linux-32/0.14.27: 675 | resolution: {integrity: sha512-qhNYIcT+EsYSBClZ5QhLzFzV5iVsP1YsITqblSaztr3+ZJUI+GoK8aXHyzKd7/CKKuK93cxEMJPpfi1dfsOfdw==} 676 | engines: {node: '>=12'} 677 | cpu: [ia32] 678 | os: [linux] 679 | requiresBuild: true 680 | optional: true 681 | 682 | /esbuild-linux-64/0.14.27: 683 | resolution: {integrity: sha512-ESjck9+EsHoTaKWlFKJpPZRN26uiav5gkI16RuI8WBxUdLrrAlYuYSndxxKgEn1csd968BX/8yQZATYf/9+/qg==} 684 | engines: {node: '>=12'} 685 | cpu: [x64] 686 | os: [linux] 687 | requiresBuild: true 688 | optional: true 689 | 690 | /esbuild-linux-arm/0.14.27: 691 | resolution: {integrity: sha512-JnnmgUBdqLQO9hoNZQqNHFWlNpSX82vzB3rYuCJMhtkuaWQEmQz6Lec1UIxJdC38ifEghNTBsF9bbe8dFilnCw==} 692 | engines: {node: '>=12'} 693 | cpu: [arm] 694 | os: [linux] 695 | requiresBuild: true 696 | optional: true 697 | 698 | /esbuild-linux-arm64/0.14.27: 699 | resolution: {integrity: sha512-no6Mi17eV2tHlJnqBHRLekpZ2/VYx+NfGxKcBE/2xOMYwctsanCaXxw4zapvNrGE9X38vefVXLz6YCF8b1EHiQ==} 700 | engines: {node: '>=12'} 701 | cpu: [arm64] 702 | os: [linux] 703 | requiresBuild: true 704 | optional: true 705 | 706 | /esbuild-linux-mips64le/0.14.27: 707 | resolution: {integrity: sha512-NolWP2uOvIJpbwpsDbwfeExZOY1bZNlWE/kVfkzLMsSgqeVcl5YMen/cedRe9mKnpfLli+i0uSp7N+fkKNU27A==} 708 | engines: {node: '>=12'} 709 | cpu: [mips64el] 710 | os: [linux] 711 | requiresBuild: true 712 | optional: true 713 | 714 | /esbuild-linux-ppc64le/0.14.27: 715 | resolution: {integrity: sha512-/7dTjDvXMdRKmsSxKXeWyonuGgblnYDn0MI1xDC7J1VQXny8k1qgNp6VmrlsawwnsymSUUiThhkJsI+rx0taNA==} 716 | engines: {node: '>=12'} 717 | cpu: [ppc64] 718 | os: [linux] 719 | requiresBuild: true 720 | optional: true 721 | 722 | /esbuild-linux-riscv64/0.14.27: 723 | resolution: {integrity: sha512-D+aFiUzOJG13RhrSmZgrcFaF4UUHpqj7XSKrIiCXIj1dkIkFqdrmqMSOtSs78dOtObWiOrFCDDzB24UyeEiNGg==} 724 | engines: {node: '>=12'} 725 | cpu: [riscv64] 726 | os: [linux] 727 | requiresBuild: true 728 | optional: true 729 | 730 | /esbuild-linux-s390x/0.14.27: 731 | resolution: {integrity: sha512-CD/D4tj0U4UQjELkdNlZhQ8nDHU5rBn6NGp47Hiz0Y7/akAY5i0oGadhEIg0WCY/HYVXFb3CsSPPwaKcTOW3bg==} 732 | engines: {node: '>=12'} 733 | cpu: [s390x] 734 | os: [linux] 735 | requiresBuild: true 736 | optional: true 737 | 738 | /esbuild-netbsd-64/0.14.27: 739 | resolution: {integrity: sha512-h3mAld69SrO1VoaMpYl3a5FNdGRE/Nqc+E8VtHOag4tyBwhCQXxtvDDOAKOUQexBGca0IuR6UayQ4ntSX5ij1Q==} 740 | engines: {node: '>=12'} 741 | cpu: [x64] 742 | os: [netbsd] 743 | requiresBuild: true 744 | optional: true 745 | 746 | /esbuild-openbsd-64/0.14.27: 747 | resolution: {integrity: sha512-xwSje6qIZaDHXWoPpIgvL+7fC6WeubHHv18tusLYMwL+Z6bEa4Pbfs5IWDtQdHkArtfxEkIZz77944z8MgDxGw==} 748 | engines: {node: '>=12'} 749 | cpu: [x64] 750 | os: [openbsd] 751 | requiresBuild: true 752 | optional: true 753 | 754 | /esbuild-sunos-64/0.14.27: 755 | resolution: {integrity: sha512-/nBVpWIDjYiyMhuqIqbXXsxBc58cBVH9uztAOIfWShStxq9BNBik92oPQPJ57nzWXRNKQUEFWr4Q98utDWz7jg==} 756 | engines: {node: '>=12'} 757 | cpu: [x64] 758 | os: [sunos] 759 | requiresBuild: true 760 | optional: true 761 | 762 | /esbuild-windows-32/0.14.27: 763 | resolution: {integrity: sha512-Q9/zEjhZJ4trtWhFWIZvS/7RUzzi8rvkoaS9oiizkHTTKd8UxFwn/Mm2OywsAfYymgUYm8+y2b+BKTNEFxUekw==} 764 | engines: {node: '>=12'} 765 | cpu: [ia32] 766 | os: [win32] 767 | requiresBuild: true 768 | optional: true 769 | 770 | /esbuild-windows-64/0.14.27: 771 | resolution: {integrity: sha512-b3y3vTSl5aEhWHK66ngtiS/c6byLf6y/ZBvODH1YkBM+MGtVL6jN38FdHUsZasCz9gFwYs/lJMVY9u7GL6wfYg==} 772 | engines: {node: '>=12'} 773 | cpu: [x64] 774 | os: [win32] 775 | requiresBuild: true 776 | optional: true 777 | 778 | /esbuild-windows-arm64/0.14.27: 779 | resolution: {integrity: sha512-I/reTxr6TFMcR5qbIkwRGvldMIaiBu2+MP0LlD7sOlNXrfqIl9uNjsuxFPGEG4IRomjfQ5q8WT+xlF/ySVkqKg==} 780 | engines: {node: '>=12'} 781 | cpu: [arm64] 782 | os: [win32] 783 | requiresBuild: true 784 | optional: true 785 | 786 | /esbuild/0.14.27: 787 | resolution: {integrity: sha512-MZQt5SywZS3hA9fXnMhR22dv0oPGh6QtjJRIYbgL1AeqAoQZE+Qn5ppGYQAoHv/vq827flj4tIJ79Mrdiwk46Q==} 788 | engines: {node: '>=12'} 789 | hasBin: true 790 | requiresBuild: true 791 | optionalDependencies: 792 | esbuild-android-64: 0.14.27 793 | esbuild-android-arm64: 0.14.27 794 | esbuild-darwin-64: 0.14.27 795 | esbuild-darwin-arm64: 0.14.27 796 | esbuild-freebsd-64: 0.14.27 797 | esbuild-freebsd-arm64: 0.14.27 798 | esbuild-linux-32: 0.14.27 799 | esbuild-linux-64: 0.14.27 800 | esbuild-linux-arm: 0.14.27 801 | esbuild-linux-arm64: 0.14.27 802 | esbuild-linux-mips64le: 0.14.27 803 | esbuild-linux-ppc64le: 0.14.27 804 | esbuild-linux-riscv64: 0.14.27 805 | esbuild-linux-s390x: 0.14.27 806 | esbuild-netbsd-64: 0.14.27 807 | esbuild-openbsd-64: 0.14.27 808 | esbuild-sunos-64: 0.14.27 809 | esbuild-windows-32: 0.14.27 810 | esbuild-windows-64: 0.14.27 811 | esbuild-windows-arm64: 0.14.27 812 | 813 | /escalade/3.1.1: 814 | resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} 815 | engines: {node: '>=6'} 816 | dev: true 817 | 818 | /escape-string-regexp/4.0.0: 819 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 820 | engines: {node: '>=10'} 821 | dev: true 822 | 823 | /eslint-config-prettier/8.5.0_eslint@8.11.0: 824 | resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} 825 | hasBin: true 826 | peerDependencies: 827 | eslint: '>=7.0.0' 828 | dependencies: 829 | eslint: 8.11.0 830 | dev: true 831 | 832 | /eslint-plugin-prettier/4.0.0_e2923b5169e23c4db59b86a638c599a3: 833 | resolution: {integrity: sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==} 834 | engines: {node: '>=6.0.0'} 835 | peerDependencies: 836 | eslint: '>=7.28.0' 837 | eslint-config-prettier: '*' 838 | prettier: '>=2.0.0' 839 | peerDependenciesMeta: 840 | eslint-config-prettier: 841 | optional: true 842 | dependencies: 843 | eslint: 8.11.0 844 | eslint-config-prettier: 8.5.0_eslint@8.11.0 845 | prettier: 2.6.1 846 | prettier-linter-helpers: 1.0.0 847 | dev: true 848 | 849 | /eslint-scope/5.1.1: 850 | resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} 851 | engines: {node: '>=8.0.0'} 852 | dependencies: 853 | esrecurse: 4.3.0 854 | estraverse: 4.3.0 855 | dev: true 856 | 857 | /eslint-scope/7.1.1: 858 | resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} 859 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 860 | dependencies: 861 | esrecurse: 4.3.0 862 | estraverse: 5.3.0 863 | dev: true 864 | 865 | /eslint-utils/3.0.0_eslint@8.11.0: 866 | resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} 867 | engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} 868 | peerDependencies: 869 | eslint: '>=5' 870 | dependencies: 871 | eslint: 8.11.0 872 | eslint-visitor-keys: 2.1.0 873 | dev: true 874 | 875 | /eslint-visitor-keys/2.1.0: 876 | resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} 877 | engines: {node: '>=10'} 878 | dev: true 879 | 880 | /eslint-visitor-keys/3.3.0: 881 | resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} 882 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 883 | dev: true 884 | 885 | /eslint/8.11.0: 886 | resolution: {integrity: sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA==} 887 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 888 | hasBin: true 889 | dependencies: 890 | '@eslint/eslintrc': 1.2.1 891 | '@humanwhocodes/config-array': 0.9.5 892 | ajv: 6.12.6 893 | chalk: 4.1.2 894 | cross-spawn: 7.0.3 895 | debug: 4.3.4 896 | doctrine: 3.0.0 897 | escape-string-regexp: 4.0.0 898 | eslint-scope: 7.1.1 899 | eslint-utils: 3.0.0_eslint@8.11.0 900 | eslint-visitor-keys: 3.3.0 901 | espree: 9.3.1 902 | esquery: 1.4.0 903 | esutils: 2.0.3 904 | fast-deep-equal: 3.1.3 905 | file-entry-cache: 6.0.1 906 | functional-red-black-tree: 1.0.1 907 | glob-parent: 6.0.2 908 | globals: 13.13.0 909 | ignore: 5.2.0 910 | import-fresh: 3.3.0 911 | imurmurhash: 0.1.4 912 | is-glob: 4.0.3 913 | js-yaml: 4.1.0 914 | json-stable-stringify-without-jsonify: 1.0.1 915 | levn: 0.4.1 916 | lodash.merge: 4.6.2 917 | minimatch: 3.1.2 918 | natural-compare: 1.4.0 919 | optionator: 0.9.1 920 | regexpp: 3.2.0 921 | strip-ansi: 6.0.1 922 | strip-json-comments: 3.1.1 923 | text-table: 0.2.0 924 | v8-compile-cache: 2.3.0 925 | transitivePeerDependencies: 926 | - supports-color 927 | dev: true 928 | 929 | /espree/9.3.1: 930 | resolution: {integrity: sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==} 931 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 932 | dependencies: 933 | acorn: 8.7.0 934 | acorn-jsx: 5.3.2_acorn@8.7.0 935 | eslint-visitor-keys: 3.3.0 936 | dev: true 937 | 938 | /esquery/1.4.0: 939 | resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} 940 | engines: {node: '>=0.10'} 941 | dependencies: 942 | estraverse: 5.3.0 943 | dev: true 944 | 945 | /esrecurse/4.3.0: 946 | resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 947 | engines: {node: '>=4.0'} 948 | dependencies: 949 | estraverse: 5.3.0 950 | dev: true 951 | 952 | /estraverse/4.3.0: 953 | resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} 954 | engines: {node: '>=4.0'} 955 | dev: true 956 | 957 | /estraverse/5.3.0: 958 | resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 959 | engines: {node: '>=4.0'} 960 | dev: true 961 | 962 | /esutils/2.0.3: 963 | resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 964 | engines: {node: '>=0.10.0'} 965 | dev: true 966 | 967 | /execa/5.1.1: 968 | resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} 969 | engines: {node: '>=10'} 970 | dependencies: 971 | cross-spawn: 7.0.3 972 | get-stream: 6.0.1 973 | human-signals: 2.1.0 974 | is-stream: 2.0.1 975 | merge-stream: 2.0.0 976 | npm-run-path: 4.0.1 977 | onetime: 5.1.2 978 | signal-exit: 3.0.7 979 | strip-final-newline: 2.0.0 980 | dev: true 981 | 982 | /fake-indexeddb/3.1.7: 983 | resolution: {integrity: sha512-CUGeCzCOVjmeKi2C0pcvSh6NDU6uQIaS+7YyR++tO/atJJujkBYVhDvfePdz/U8bD33BMVWirsr1MKczfAqbjA==} 984 | dependencies: 985 | realistic-structured-clone: 2.0.4 986 | dev: false 987 | 988 | /fast-deep-equal/3.1.3: 989 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 990 | dev: true 991 | 992 | /fast-diff/1.2.0: 993 | resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} 994 | dev: true 995 | 996 | /fast-glob/3.2.11: 997 | resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==} 998 | engines: {node: '>=8.6.0'} 999 | dependencies: 1000 | '@nodelib/fs.stat': 2.0.5 1001 | '@nodelib/fs.walk': 1.2.8 1002 | glob-parent: 5.1.2 1003 | merge2: 1.4.1 1004 | micromatch: 4.0.5 1005 | dev: true 1006 | 1007 | /fast-json-stable-stringify/2.1.0: 1008 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 1009 | dev: true 1010 | 1011 | /fast-levenshtein/2.0.6: 1012 | resolution: {integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=} 1013 | dev: true 1014 | 1015 | /fastq/1.13.0: 1016 | resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} 1017 | dependencies: 1018 | reusify: 1.0.4 1019 | dev: true 1020 | 1021 | /fetch-blob/3.1.5: 1022 | resolution: {integrity: sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==} 1023 | engines: {node: ^12.20 || >= 14.13} 1024 | dependencies: 1025 | node-domexception: 1.0.0 1026 | web-streams-polyfill: 3.2.0 1027 | dev: false 1028 | 1029 | /file-entry-cache/6.0.1: 1030 | resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} 1031 | engines: {node: ^10.12.0 || >=12.0.0} 1032 | dependencies: 1033 | flat-cache: 3.0.4 1034 | dev: true 1035 | 1036 | /fill-range/7.0.1: 1037 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 1038 | engines: {node: '>=8'} 1039 | dependencies: 1040 | to-regex-range: 5.0.1 1041 | dev: true 1042 | 1043 | /find-up/5.0.0: 1044 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 1045 | engines: {node: '>=10'} 1046 | dependencies: 1047 | locate-path: 6.0.0 1048 | path-exists: 4.0.0 1049 | dev: true 1050 | 1051 | /flat-cache/3.0.4: 1052 | resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} 1053 | engines: {node: ^10.12.0 || >=12.0.0} 1054 | dependencies: 1055 | flatted: 3.2.5 1056 | rimraf: 3.0.2 1057 | dev: true 1058 | 1059 | /flat/5.0.2: 1060 | resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} 1061 | hasBin: true 1062 | dev: true 1063 | 1064 | /flatted/3.2.5: 1065 | resolution: {integrity: sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==} 1066 | dev: true 1067 | 1068 | /form-data/4.0.0: 1069 | resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} 1070 | engines: {node: '>= 6'} 1071 | dependencies: 1072 | asynckit: 0.4.0 1073 | combined-stream: 1.0.8 1074 | mime-types: 2.1.35 1075 | dev: false 1076 | 1077 | /formdata-polyfill/4.0.10: 1078 | resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} 1079 | engines: {node: '>=12.20.0'} 1080 | dependencies: 1081 | fetch-blob: 3.1.5 1082 | dev: false 1083 | 1084 | /fs.realpath/1.0.0: 1085 | resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} 1086 | dev: true 1087 | 1088 | /fsevents/2.3.2: 1089 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 1090 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 1091 | os: [darwin] 1092 | requiresBuild: true 1093 | dev: true 1094 | optional: true 1095 | 1096 | /functional-red-black-tree/1.0.1: 1097 | resolution: {integrity: sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=} 1098 | dev: true 1099 | 1100 | /get-caller-file/2.0.5: 1101 | resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} 1102 | engines: {node: 6.* || 8.* || >= 10.*} 1103 | dev: true 1104 | 1105 | /get-func-name/2.0.0: 1106 | resolution: {integrity: sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=} 1107 | dev: true 1108 | 1109 | /get-stream/6.0.1: 1110 | resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} 1111 | engines: {node: '>=10'} 1112 | dev: true 1113 | 1114 | /glob-parent/5.1.2: 1115 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 1116 | engines: {node: '>= 6'} 1117 | dependencies: 1118 | is-glob: 4.0.3 1119 | dev: true 1120 | 1121 | /glob-parent/6.0.2: 1122 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 1123 | engines: {node: '>=10.13.0'} 1124 | dependencies: 1125 | is-glob: 4.0.3 1126 | dev: true 1127 | 1128 | /glob/7.2.0: 1129 | resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} 1130 | dependencies: 1131 | fs.realpath: 1.0.0 1132 | inflight: 1.0.6 1133 | inherits: 2.0.4 1134 | minimatch: 3.1.2 1135 | once: 1.4.0 1136 | path-is-absolute: 1.0.1 1137 | dev: true 1138 | 1139 | /globals/13.13.0: 1140 | resolution: {integrity: sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==} 1141 | engines: {node: '>=8'} 1142 | dependencies: 1143 | type-fest: 0.20.2 1144 | dev: true 1145 | 1146 | /globby/11.1.0: 1147 | resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} 1148 | engines: {node: '>=10'} 1149 | dependencies: 1150 | array-union: 2.1.0 1151 | dir-glob: 3.0.1 1152 | fast-glob: 3.2.11 1153 | ignore: 5.2.0 1154 | merge2: 1.4.1 1155 | slash: 3.0.0 1156 | dev: true 1157 | 1158 | /growl/1.10.5: 1159 | resolution: {integrity: sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==} 1160 | engines: {node: '>=4.x'} 1161 | dev: true 1162 | 1163 | /has-flag/4.0.0: 1164 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 1165 | engines: {node: '>=8'} 1166 | dev: true 1167 | 1168 | /he/1.2.0: 1169 | resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} 1170 | hasBin: true 1171 | dev: true 1172 | 1173 | /human-signals/2.1.0: 1174 | resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} 1175 | engines: {node: '>=10.17.0'} 1176 | dev: true 1177 | 1178 | /husky/7.0.4: 1179 | resolution: {integrity: sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==} 1180 | engines: {node: '>=12'} 1181 | hasBin: true 1182 | dev: true 1183 | 1184 | /ignore/5.2.0: 1185 | resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} 1186 | engines: {node: '>= 4'} 1187 | dev: true 1188 | 1189 | /import-fresh/3.3.0: 1190 | resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 1191 | engines: {node: '>=6'} 1192 | dependencies: 1193 | parent-module: 1.0.1 1194 | resolve-from: 4.0.0 1195 | dev: true 1196 | 1197 | /imurmurhash/0.1.4: 1198 | resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=} 1199 | engines: {node: '>=0.8.19'} 1200 | dev: true 1201 | 1202 | /indent-string/4.0.0: 1203 | resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} 1204 | engines: {node: '>=8'} 1205 | dev: true 1206 | 1207 | /inflight/1.0.6: 1208 | resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} 1209 | dependencies: 1210 | once: 1.4.0 1211 | wrappy: 1.0.2 1212 | dev: true 1213 | 1214 | /inherits/2.0.4: 1215 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 1216 | dev: true 1217 | 1218 | /is-binary-path/2.1.0: 1219 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 1220 | engines: {node: '>=8'} 1221 | dependencies: 1222 | binary-extensions: 2.2.0 1223 | dev: true 1224 | 1225 | /is-extglob/2.1.1: 1226 | resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} 1227 | engines: {node: '>=0.10.0'} 1228 | dev: true 1229 | 1230 | /is-fullwidth-code-point/3.0.0: 1231 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 1232 | engines: {node: '>=8'} 1233 | dev: true 1234 | 1235 | /is-fullwidth-code-point/4.0.0: 1236 | resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} 1237 | engines: {node: '>=12'} 1238 | dev: true 1239 | 1240 | /is-glob/4.0.3: 1241 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 1242 | engines: {node: '>=0.10.0'} 1243 | dependencies: 1244 | is-extglob: 2.1.1 1245 | dev: true 1246 | 1247 | /is-number/7.0.0: 1248 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 1249 | engines: {node: '>=0.12.0'} 1250 | dev: true 1251 | 1252 | /is-plain-obj/2.1.0: 1253 | resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} 1254 | engines: {node: '>=8'} 1255 | dev: true 1256 | 1257 | /is-stream/2.0.1: 1258 | resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} 1259 | engines: {node: '>=8'} 1260 | dev: true 1261 | 1262 | /is-unicode-supported/0.1.0: 1263 | resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} 1264 | engines: {node: '>=10'} 1265 | dev: true 1266 | 1267 | /isexe/2.0.0: 1268 | resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} 1269 | dev: true 1270 | 1271 | /js-yaml/4.1.0: 1272 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 1273 | hasBin: true 1274 | dependencies: 1275 | argparse: 2.0.1 1276 | dev: true 1277 | 1278 | /json-schema-traverse/0.4.1: 1279 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 1280 | dev: true 1281 | 1282 | /json-stable-stringify-without-jsonify/1.0.1: 1283 | resolution: {integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=} 1284 | dev: true 1285 | 1286 | /levn/0.4.1: 1287 | resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 1288 | engines: {node: '>= 0.8.0'} 1289 | dependencies: 1290 | prelude-ls: 1.2.1 1291 | type-check: 0.4.0 1292 | dev: true 1293 | 1294 | /lilconfig/2.0.4: 1295 | resolution: {integrity: sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==} 1296 | engines: {node: '>=10'} 1297 | dev: true 1298 | 1299 | /lint-staged/12.3.7: 1300 | resolution: {integrity: sha512-/S4D726e2GIsDVWIk1XGvheCaDm1SJRQp8efamZFWJxQMVEbOwSysp7xb49Oo73KYCdy97mIWinhlxcoNqIfIQ==} 1301 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1302 | hasBin: true 1303 | dependencies: 1304 | cli-truncate: 3.1.0 1305 | colorette: 2.0.16 1306 | commander: 8.3.0 1307 | debug: 4.3.4_supports-color@9.2.1 1308 | execa: 5.1.1 1309 | lilconfig: 2.0.4 1310 | listr2: 4.0.5 1311 | micromatch: 4.0.5 1312 | normalize-path: 3.0.0 1313 | object-inspect: 1.12.0 1314 | pidtree: 0.5.0 1315 | string-argv: 0.3.1 1316 | supports-color: 9.2.1 1317 | yaml: 1.10.2 1318 | transitivePeerDependencies: 1319 | - enquirer 1320 | dev: true 1321 | 1322 | /listr2/4.0.5: 1323 | resolution: {integrity: sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==} 1324 | engines: {node: '>=12'} 1325 | peerDependencies: 1326 | enquirer: '>= 2.3.0 < 3' 1327 | peerDependenciesMeta: 1328 | enquirer: 1329 | optional: true 1330 | dependencies: 1331 | cli-truncate: 2.1.0 1332 | colorette: 2.0.16 1333 | log-update: 4.0.0 1334 | p-map: 4.0.0 1335 | rfdc: 1.3.0 1336 | rxjs: 7.5.5 1337 | through: 2.3.8 1338 | wrap-ansi: 7.0.0 1339 | dev: true 1340 | 1341 | /locate-path/6.0.0: 1342 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 1343 | engines: {node: '>=10'} 1344 | dependencies: 1345 | p-locate: 5.0.0 1346 | dev: true 1347 | 1348 | /lodash.merge/4.6.2: 1349 | resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 1350 | dev: true 1351 | 1352 | /lodash/4.17.21: 1353 | resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 1354 | dev: false 1355 | 1356 | /log-symbols/4.1.0: 1357 | resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} 1358 | engines: {node: '>=10'} 1359 | dependencies: 1360 | chalk: 4.1.2 1361 | is-unicode-supported: 0.1.0 1362 | dev: true 1363 | 1364 | /log-update/4.0.0: 1365 | resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} 1366 | engines: {node: '>=10'} 1367 | dependencies: 1368 | ansi-escapes: 4.3.2 1369 | cli-cursor: 3.1.0 1370 | slice-ansi: 4.0.0 1371 | wrap-ansi: 6.2.0 1372 | dev: true 1373 | 1374 | /loupe/2.3.4: 1375 | resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==} 1376 | dependencies: 1377 | get-func-name: 2.0.0 1378 | dev: true 1379 | 1380 | /lru-cache/6.0.0: 1381 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 1382 | engines: {node: '>=10'} 1383 | dependencies: 1384 | yallist: 4.0.0 1385 | dev: true 1386 | 1387 | /merge-stream/2.0.0: 1388 | resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} 1389 | dev: true 1390 | 1391 | /merge2/1.4.1: 1392 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 1393 | engines: {node: '>= 8'} 1394 | dev: true 1395 | 1396 | /micromatch/4.0.5: 1397 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 1398 | engines: {node: '>=8.6'} 1399 | dependencies: 1400 | braces: 3.0.2 1401 | picomatch: 2.3.1 1402 | dev: true 1403 | 1404 | /mime-db/1.52.0: 1405 | resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} 1406 | engines: {node: '>= 0.6'} 1407 | dev: false 1408 | 1409 | /mime-types/2.1.35: 1410 | resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} 1411 | engines: {node: '>= 0.6'} 1412 | dependencies: 1413 | mime-db: 1.52.0 1414 | dev: false 1415 | 1416 | /mimic-fn/2.1.0: 1417 | resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} 1418 | engines: {node: '>=6'} 1419 | dev: true 1420 | 1421 | /minimatch/3.1.2: 1422 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 1423 | dependencies: 1424 | brace-expansion: 1.1.11 1425 | dev: true 1426 | 1427 | /minimatch/4.2.1: 1428 | resolution: {integrity: sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==} 1429 | engines: {node: '>=10'} 1430 | dependencies: 1431 | brace-expansion: 1.1.11 1432 | dev: true 1433 | 1434 | /mocha/9.2.2: 1435 | resolution: {integrity: sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==} 1436 | engines: {node: '>= 12.0.0'} 1437 | hasBin: true 1438 | dependencies: 1439 | '@ungap/promise-all-settled': 1.1.2 1440 | ansi-colors: 4.1.1 1441 | browser-stdout: 1.3.1 1442 | chokidar: 3.5.3 1443 | debug: 4.3.3_supports-color@8.1.1 1444 | diff: 5.0.0 1445 | escape-string-regexp: 4.0.0 1446 | find-up: 5.0.0 1447 | glob: 7.2.0 1448 | growl: 1.10.5 1449 | he: 1.2.0 1450 | js-yaml: 4.1.0 1451 | log-symbols: 4.1.0 1452 | minimatch: 4.2.1 1453 | ms: 2.1.3 1454 | nanoid: 3.3.1 1455 | serialize-javascript: 6.0.0 1456 | strip-json-comments: 3.1.1 1457 | supports-color: 8.1.1 1458 | which: 2.0.2 1459 | workerpool: 6.2.0 1460 | yargs: 16.2.0 1461 | yargs-parser: 20.2.4 1462 | yargs-unparser: 2.0.0 1463 | dev: true 1464 | 1465 | /ms/2.1.2: 1466 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 1467 | dev: true 1468 | 1469 | /ms/2.1.3: 1470 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1471 | dev: true 1472 | 1473 | /nanoid/3.3.1: 1474 | resolution: {integrity: sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==} 1475 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 1476 | hasBin: true 1477 | dev: true 1478 | 1479 | /natural-compare/1.4.0: 1480 | resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=} 1481 | dev: true 1482 | 1483 | /node-domexception/1.0.0: 1484 | resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} 1485 | engines: {node: '>=10.5.0'} 1486 | dev: false 1487 | 1488 | /node-fetch/3.2.3: 1489 | resolution: {integrity: sha512-AXP18u4pidSZ1xYXRDPY/8jdv3RAozIt/WLNR/MBGZAz+xjtlr90RvCnsvHQRiXyWliZF/CpytExp32UU67/SA==} 1490 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1491 | dependencies: 1492 | data-uri-to-buffer: 4.0.0 1493 | fetch-blob: 3.1.5 1494 | formdata-polyfill: 4.0.10 1495 | dev: false 1496 | 1497 | /normalize-path/3.0.0: 1498 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 1499 | engines: {node: '>=0.10.0'} 1500 | dev: true 1501 | 1502 | /npm-run-path/4.0.1: 1503 | resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} 1504 | engines: {node: '>=8'} 1505 | dependencies: 1506 | path-key: 3.1.1 1507 | dev: true 1508 | 1509 | /object-inspect/1.12.0: 1510 | resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==} 1511 | dev: true 1512 | 1513 | /once/1.4.0: 1514 | resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} 1515 | dependencies: 1516 | wrappy: 1.0.2 1517 | dev: true 1518 | 1519 | /onetime/5.1.2: 1520 | resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} 1521 | engines: {node: '>=6'} 1522 | dependencies: 1523 | mimic-fn: 2.1.0 1524 | dev: true 1525 | 1526 | /optionator/0.9.1: 1527 | resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} 1528 | engines: {node: '>= 0.8.0'} 1529 | dependencies: 1530 | deep-is: 0.1.4 1531 | fast-levenshtein: 2.0.6 1532 | levn: 0.4.1 1533 | prelude-ls: 1.2.1 1534 | type-check: 0.4.0 1535 | word-wrap: 1.2.3 1536 | dev: true 1537 | 1538 | /p-limit/3.1.0: 1539 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 1540 | engines: {node: '>=10'} 1541 | dependencies: 1542 | yocto-queue: 0.1.0 1543 | dev: true 1544 | 1545 | /p-locate/5.0.0: 1546 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1547 | engines: {node: '>=10'} 1548 | dependencies: 1549 | p-limit: 3.1.0 1550 | dev: true 1551 | 1552 | /p-map/4.0.0: 1553 | resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} 1554 | engines: {node: '>=10'} 1555 | dependencies: 1556 | aggregate-error: 3.1.0 1557 | dev: true 1558 | 1559 | /parent-module/1.0.1: 1560 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1561 | engines: {node: '>=6'} 1562 | dependencies: 1563 | callsites: 3.1.0 1564 | dev: true 1565 | 1566 | /path-exists/4.0.0: 1567 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1568 | engines: {node: '>=8'} 1569 | dev: true 1570 | 1571 | /path-is-absolute/1.0.1: 1572 | resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} 1573 | engines: {node: '>=0.10.0'} 1574 | dev: true 1575 | 1576 | /path-key/3.1.1: 1577 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1578 | engines: {node: '>=8'} 1579 | dev: true 1580 | 1581 | /path-type/4.0.0: 1582 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 1583 | engines: {node: '>=8'} 1584 | dev: true 1585 | 1586 | /pathval/1.1.1: 1587 | resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} 1588 | dev: true 1589 | 1590 | /picomatch/2.3.1: 1591 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1592 | engines: {node: '>=8.6'} 1593 | dev: true 1594 | 1595 | /pidtree/0.5.0: 1596 | resolution: {integrity: sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA==} 1597 | engines: {node: '>=0.10'} 1598 | hasBin: true 1599 | dev: true 1600 | 1601 | /prelude-ls/1.2.1: 1602 | resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 1603 | engines: {node: '>= 0.8.0'} 1604 | dev: true 1605 | 1606 | /prettier-linter-helpers/1.0.0: 1607 | resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} 1608 | engines: {node: '>=6.0.0'} 1609 | dependencies: 1610 | fast-diff: 1.2.0 1611 | dev: true 1612 | 1613 | /prettier/2.6.1: 1614 | resolution: {integrity: sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==} 1615 | engines: {node: '>=10.13.0'} 1616 | hasBin: true 1617 | dev: true 1618 | 1619 | /punycode/2.1.1: 1620 | resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} 1621 | engines: {node: '>=6'} 1622 | 1623 | /queue-microtask/1.2.3: 1624 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1625 | dev: true 1626 | 1627 | /randombytes/2.1.0: 1628 | resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} 1629 | dependencies: 1630 | safe-buffer: 5.2.1 1631 | dev: true 1632 | 1633 | /readdirp/3.6.0: 1634 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 1635 | engines: {node: '>=8.10.0'} 1636 | dependencies: 1637 | picomatch: 2.3.1 1638 | dev: true 1639 | 1640 | /realistic-structured-clone/2.0.4: 1641 | resolution: {integrity: sha512-lItAdBIFHUSe6fgztHPtmmWqKUgs+qhcYLi3wTRUl4OTB3Vb8aBVSjGfQZUvkmJCKoX3K9Wf7kyLp/F/208+7A==} 1642 | dependencies: 1643 | core-js: 3.21.1 1644 | domexception: 1.0.1 1645 | typeson: 6.1.0 1646 | typeson-registry: 1.0.0-alpha.39 1647 | dev: false 1648 | 1649 | /regexpp/3.2.0: 1650 | resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} 1651 | engines: {node: '>=8'} 1652 | dev: true 1653 | 1654 | /require-directory/2.1.1: 1655 | resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=} 1656 | engines: {node: '>=0.10.0'} 1657 | dev: true 1658 | 1659 | /resolve-from/4.0.0: 1660 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 1661 | engines: {node: '>=4'} 1662 | dev: true 1663 | 1664 | /restore-cursor/3.1.0: 1665 | resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} 1666 | engines: {node: '>=8'} 1667 | dependencies: 1668 | onetime: 5.1.2 1669 | signal-exit: 3.0.7 1670 | dev: true 1671 | 1672 | /reusify/1.0.4: 1673 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 1674 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1675 | dev: true 1676 | 1677 | /rfdc/1.3.0: 1678 | resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} 1679 | dev: true 1680 | 1681 | /rimraf/3.0.2: 1682 | resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 1683 | hasBin: true 1684 | dependencies: 1685 | glob: 7.2.0 1686 | dev: true 1687 | 1688 | /run-parallel/1.2.0: 1689 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1690 | dependencies: 1691 | queue-microtask: 1.2.3 1692 | dev: true 1693 | 1694 | /rxjs/7.5.5: 1695 | resolution: {integrity: sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==} 1696 | dependencies: 1697 | tslib: 2.3.1 1698 | dev: true 1699 | 1700 | /safe-buffer/5.2.1: 1701 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1702 | dev: true 1703 | 1704 | /semver/7.3.5: 1705 | resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==} 1706 | engines: {node: '>=10'} 1707 | hasBin: true 1708 | dependencies: 1709 | lru-cache: 6.0.0 1710 | dev: true 1711 | 1712 | /serialize-javascript/6.0.0: 1713 | resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} 1714 | dependencies: 1715 | randombytes: 2.1.0 1716 | dev: true 1717 | 1718 | /shebang-command/2.0.0: 1719 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1720 | engines: {node: '>=8'} 1721 | dependencies: 1722 | shebang-regex: 3.0.0 1723 | dev: true 1724 | 1725 | /shebang-regex/3.0.0: 1726 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1727 | engines: {node: '>=8'} 1728 | dev: true 1729 | 1730 | /signal-exit/3.0.7: 1731 | resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} 1732 | dev: true 1733 | 1734 | /slash/3.0.0: 1735 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 1736 | engines: {node: '>=8'} 1737 | dev: true 1738 | 1739 | /slice-ansi/3.0.0: 1740 | resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} 1741 | engines: {node: '>=8'} 1742 | dependencies: 1743 | ansi-styles: 4.3.0 1744 | astral-regex: 2.0.0 1745 | is-fullwidth-code-point: 3.0.0 1746 | dev: true 1747 | 1748 | /slice-ansi/4.0.0: 1749 | resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} 1750 | engines: {node: '>=10'} 1751 | dependencies: 1752 | ansi-styles: 4.3.0 1753 | astral-regex: 2.0.0 1754 | is-fullwidth-code-point: 3.0.0 1755 | dev: true 1756 | 1757 | /slice-ansi/5.0.0: 1758 | resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} 1759 | engines: {node: '>=12'} 1760 | dependencies: 1761 | ansi-styles: 6.1.0 1762 | is-fullwidth-code-point: 4.0.0 1763 | dev: true 1764 | 1765 | /string-argv/0.3.1: 1766 | resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} 1767 | engines: {node: '>=0.6.19'} 1768 | dev: true 1769 | 1770 | /string-width/4.2.3: 1771 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 1772 | engines: {node: '>=8'} 1773 | dependencies: 1774 | emoji-regex: 8.0.0 1775 | is-fullwidth-code-point: 3.0.0 1776 | strip-ansi: 6.0.1 1777 | dev: true 1778 | 1779 | /string-width/5.1.2: 1780 | resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 1781 | engines: {node: '>=12'} 1782 | dependencies: 1783 | eastasianwidth: 0.2.0 1784 | emoji-regex: 9.2.2 1785 | strip-ansi: 7.0.1 1786 | dev: true 1787 | 1788 | /strip-ansi/6.0.1: 1789 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1790 | engines: {node: '>=8'} 1791 | dependencies: 1792 | ansi-regex: 5.0.1 1793 | dev: true 1794 | 1795 | /strip-ansi/7.0.1: 1796 | resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} 1797 | engines: {node: '>=12'} 1798 | dependencies: 1799 | ansi-regex: 6.0.1 1800 | dev: true 1801 | 1802 | /strip-final-newline/2.0.0: 1803 | resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} 1804 | engines: {node: '>=6'} 1805 | dev: true 1806 | 1807 | /strip-json-comments/3.1.1: 1808 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1809 | engines: {node: '>=8'} 1810 | dev: true 1811 | 1812 | /supports-color/7.2.0: 1813 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1814 | engines: {node: '>=8'} 1815 | dependencies: 1816 | has-flag: 4.0.0 1817 | dev: true 1818 | 1819 | /supports-color/8.1.1: 1820 | resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} 1821 | engines: {node: '>=10'} 1822 | dependencies: 1823 | has-flag: 4.0.0 1824 | dev: true 1825 | 1826 | /supports-color/9.2.1: 1827 | resolution: {integrity: sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ==} 1828 | engines: {node: '>=12'} 1829 | dev: true 1830 | 1831 | /text-table/0.2.0: 1832 | resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=} 1833 | dev: true 1834 | 1835 | /through/2.3.8: 1836 | resolution: {integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=} 1837 | dev: true 1838 | 1839 | /to-regex-range/5.0.1: 1840 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1841 | engines: {node: '>=8.0'} 1842 | dependencies: 1843 | is-number: 7.0.0 1844 | dev: true 1845 | 1846 | /tr46/2.1.0: 1847 | resolution: {integrity: sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==} 1848 | engines: {node: '>=8'} 1849 | dependencies: 1850 | punycode: 2.1.1 1851 | dev: false 1852 | 1853 | /tslib/1.14.1: 1854 | resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} 1855 | dev: true 1856 | 1857 | /tslib/2.3.1: 1858 | resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==} 1859 | dev: true 1860 | 1861 | /tsutils/3.21.0_typescript@4.6.3: 1862 | resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} 1863 | engines: {node: '>= 6'} 1864 | peerDependencies: 1865 | 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' 1866 | dependencies: 1867 | tslib: 1.14.1 1868 | typescript: 4.6.3 1869 | dev: true 1870 | 1871 | /type-check/0.4.0: 1872 | resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1873 | engines: {node: '>= 0.8.0'} 1874 | dependencies: 1875 | prelude-ls: 1.2.1 1876 | dev: true 1877 | 1878 | /type-detect/4.0.8: 1879 | resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} 1880 | engines: {node: '>=4'} 1881 | dev: true 1882 | 1883 | /type-fest/0.20.2: 1884 | resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} 1885 | engines: {node: '>=10'} 1886 | dev: true 1887 | 1888 | /type-fest/0.21.3: 1889 | resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} 1890 | engines: {node: '>=10'} 1891 | dev: true 1892 | 1893 | /typescript/4.6.3: 1894 | resolution: {integrity: sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==} 1895 | engines: {node: '>=4.2.0'} 1896 | hasBin: true 1897 | dev: true 1898 | 1899 | /typeson-registry/1.0.0-alpha.39: 1900 | resolution: {integrity: sha512-NeGDEquhw+yfwNhguLPcZ9Oj0fzbADiX4R0WxvoY8nGhy98IbzQy1sezjoEFWOywOboj/DWehI+/aUlRVrJnnw==} 1901 | engines: {node: '>=10.0.0'} 1902 | dependencies: 1903 | base64-arraybuffer-es6: 0.7.0 1904 | typeson: 6.1.0 1905 | whatwg-url: 8.7.0 1906 | dev: false 1907 | 1908 | /typeson/6.1.0: 1909 | resolution: {integrity: sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA==} 1910 | engines: {node: '>=0.1.14'} 1911 | dev: false 1912 | 1913 | /uri-js/4.4.1: 1914 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1915 | dependencies: 1916 | punycode: 2.1.1 1917 | dev: true 1918 | 1919 | /v8-compile-cache/2.3.0: 1920 | resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} 1921 | dev: true 1922 | 1923 | /web-streams-polyfill/3.2.0: 1924 | resolution: {integrity: sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==} 1925 | engines: {node: '>= 8'} 1926 | dev: false 1927 | 1928 | /webidl-conversions/4.0.2: 1929 | resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} 1930 | dev: false 1931 | 1932 | /webidl-conversions/6.1.0: 1933 | resolution: {integrity: sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==} 1934 | engines: {node: '>=10.4'} 1935 | dev: false 1936 | 1937 | /whatwg-url/8.7.0: 1938 | resolution: {integrity: sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==} 1939 | engines: {node: '>=10'} 1940 | dependencies: 1941 | lodash: 4.17.21 1942 | tr46: 2.1.0 1943 | webidl-conversions: 6.1.0 1944 | dev: false 1945 | 1946 | /which/2.0.2: 1947 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1948 | engines: {node: '>= 8'} 1949 | hasBin: true 1950 | dependencies: 1951 | isexe: 2.0.0 1952 | dev: true 1953 | 1954 | /word-wrap/1.2.3: 1955 | resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} 1956 | engines: {node: '>=0.10.0'} 1957 | dev: true 1958 | 1959 | /workerpool/6.2.0: 1960 | resolution: {integrity: sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==} 1961 | dev: true 1962 | 1963 | /wrap-ansi/6.2.0: 1964 | resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} 1965 | engines: {node: '>=8'} 1966 | dependencies: 1967 | ansi-styles: 4.3.0 1968 | string-width: 4.2.3 1969 | strip-ansi: 6.0.1 1970 | dev: true 1971 | 1972 | /wrap-ansi/7.0.0: 1973 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 1974 | engines: {node: '>=10'} 1975 | dependencies: 1976 | ansi-styles: 4.3.0 1977 | string-width: 4.2.3 1978 | strip-ansi: 6.0.1 1979 | dev: true 1980 | 1981 | /wrappy/1.0.2: 1982 | resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} 1983 | dev: true 1984 | 1985 | /y18n/5.0.8: 1986 | resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} 1987 | engines: {node: '>=10'} 1988 | dev: true 1989 | 1990 | /yallist/4.0.0: 1991 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1992 | dev: true 1993 | 1994 | /yaml/1.10.2: 1995 | resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} 1996 | engines: {node: '>= 6'} 1997 | dev: true 1998 | 1999 | /yargs-parser/20.2.4: 2000 | resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} 2001 | engines: {node: '>=10'} 2002 | dev: true 2003 | 2004 | /yargs-unparser/2.0.0: 2005 | resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} 2006 | engines: {node: '>=10'} 2007 | dependencies: 2008 | camelcase: 6.3.0 2009 | decamelize: 4.0.0 2010 | flat: 5.0.2 2011 | is-plain-obj: 2.1.0 2012 | dev: true 2013 | 2014 | /yargs/16.2.0: 2015 | resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} 2016 | engines: {node: '>=10'} 2017 | dependencies: 2018 | cliui: 7.0.4 2019 | escalade: 3.1.1 2020 | get-caller-file: 2.0.5 2021 | require-directory: 2.1.1 2022 | string-width: 4.2.3 2023 | y18n: 5.0.8 2024 | yargs-parser: 20.2.4 2025 | dev: true 2026 | 2027 | /yocto-queue/0.1.0: 2028 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 2029 | engines: {node: '>=10'} 2030 | dev: true 2031 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | import esbuild from 'esbuild'; 2 | import fs from 'fs'; 3 | import glob from 'glob'; 4 | import path from 'path'; 5 | 6 | await esbuild.build({ 7 | bundle: true, 8 | entryPoints: ['src/index.js'], 9 | format: 'esm', 10 | platform: 'node', 11 | splitting: false, 12 | target: 'node16', 13 | outfile: './sw-test-env.js', 14 | plugins: [ 15 | { 16 | name: 'external', 17 | setup(build) { 18 | build.onResolve({ filter: /^[@a-zA-Z]/ }, (args) => { 19 | return { external: true }; 20 | }); 21 | }, 22 | }, 23 | ], 24 | }); 25 | 26 | let types = ''; 27 | 28 | for (const typePath of glob.sync('src/**/_.d.ts')) { 29 | types += `// ${typePath}\n${fs.readFileSync(path.resolve(typePath), 'utf-8')}\n`; 30 | } 31 | 32 | fs.writeFileSync( 33 | 'sw-test-env.d.ts', 34 | types.replace(/(declare) (interface|type|enum|namespace|function|class)/g, 'export $2'), 35 | ); 36 | -------------------------------------------------------------------------------- /src/_.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | /** 4 | * Create/retrieve `ServiceWorkerContainer` instance for `origin`. 5 | * @param origin - the origin under which to host the service worker (default is `http://localhost:3333`) 6 | * @param webroot - the filepath used to resolve path to service worker on disk when registering (default is `process.cwd`) 7 | */ 8 | declare function connect(origin?: string, webroot?: string): Promise; 9 | 10 | /** 11 | * Destroy all active `ServiceWorkerContainer` instances and their associated `ServiceWorker` contexts 12 | */ 13 | declare function destroy(): Promise; 14 | -------------------------------------------------------------------------------- /src/_vendor.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'fake-indexeddb/build/fakeIndexedDB.js' {} 2 | declare module 'fake-indexeddb/build/FDBCursor.js' {} 3 | declare module 'fake-indexeddb/build/FDBCursorWithValue.js' {} 4 | declare module 'fake-indexeddb/build/FDBDatabase.js' {} 5 | declare module 'fake-indexeddb/build/FDBFactory.js' {} 6 | declare module 'fake-indexeddb/build/FDBIndex.js' {} 7 | declare module 'fake-indexeddb/build/FDBKeyRange.js' {} 8 | declare module 'fake-indexeddb/build/FDBObjectStore.js' {} 9 | declare module 'fake-indexeddb/build/FDBOpenDBRequest.js' {} 10 | declare module 'fake-indexeddb/build/FDBRequest.js' {} 11 | declare module 'fake-indexeddb/build/FDBTransaction.js' {} 12 | declare module 'fake-indexeddb/build/FDBVersionChangeEvent.js' {} 13 | -------------------------------------------------------------------------------- /src/api/Cache.js: -------------------------------------------------------------------------------- 1 | import fetch, { Request } from 'node-fetch'; 2 | 3 | /** 4 | * @implements MockCache 5 | */ 6 | export default class Cache { 7 | /** 8 | * Constructor 9 | * @param { string } cacheName 10 | * @param { string } origin 11 | */ 12 | constructor(cacheName, origin = 'http://localhost:3333') { 13 | this.name = cacheName; 14 | 15 | this._items = new Map(); 16 | this._origin = origin; 17 | } 18 | 19 | /** 20 | * Retrieve 'response' for matching 'request' 21 | * @param { import('node-fetch').Request | string } request 22 | * @param { CacheQueryOptions } [options] 23 | * @returns { Promise } 24 | */ 25 | match(request, options = {}) { 26 | const results = this._match(request, options); 27 | 28 | return Promise.resolve(results.length ? results[0][1] : undefined); 29 | } 30 | 31 | /** 32 | * Retrieve one or more 'response's for matching 'request' 33 | * @param { import('node-fetch').Request | string } request 34 | * @param { CacheQueryOptions } [options] 35 | * @returns { Promise> } resolves with Array of Responses 36 | */ 37 | matchAll(request, options = {}) { 38 | const results = this._match(request, options); 39 | 40 | return Promise.resolve(results.map((result) => result[1])); 41 | } 42 | 43 | /** 44 | * Fetch and store a 'request' 45 | * @param { import('node-fetch').Request | string } request 46 | * @returns { Promise } 47 | */ 48 | add(request) { 49 | request = this._normalizeRequest(request); 50 | 51 | return fetch(request.url).then((response) => { 52 | if (!response.ok) { 53 | throw new TypeError('bad response status'); 54 | } 55 | // @ts-ignore 56 | return this.put(request, response); 57 | }); 58 | } 59 | 60 | /** 61 | * Fetch and store one or more 'request's 62 | * @param { Array } requests 63 | * @returns { Promise> } resolves with Array of void 64 | */ 65 | addAll(requests) { 66 | return Promise.all(requests.map((request) => this.add(request))); 67 | } 68 | 69 | /** 70 | * Store 'response' keyed by 'request' 71 | * @param { import('node-fetch').Request | string } request 72 | * @param { import('node-fetch').Response } response 73 | * @returns { Promise } 74 | */ 75 | put(request, response) { 76 | // Allow duplicates if different VARY headers 77 | const existing = this._match(request, { ignoreVary: true })[0]; 78 | 79 | if (existing) { 80 | request = existing[0]; 81 | } 82 | request = this._normalizeRequest(request); 83 | this._items.set(request, response); 84 | return Promise.resolve(); 85 | } 86 | 87 | /** 88 | * Remove 'response' matching 'request' 89 | * @param { import('node-fetch').Request | string } request 90 | * @param { CacheQueryOptions } [options] 91 | * @returns { Promise } resolves with 'true' if deleted 92 | */ 93 | delete(request, options = {}) { 94 | const results = this._match(request, options); 95 | let success = false; 96 | 97 | results.forEach(([req]) => { 98 | const s = this._items.delete(req); 99 | 100 | if (s) { 101 | success = s; 102 | } 103 | }); 104 | 105 | return Promise.resolve(success); 106 | } 107 | 108 | /** 109 | * Retrieve all keys 110 | * @param { import('node-fetch').Request | string } [request] optionally filter based on Request 111 | * @param { CacheQueryOptions } [options] 112 | * @returns { Promise> } resolves with Array of Requests 113 | */ 114 | keys(request, options = {}) { 115 | if (!request) { 116 | return Promise.resolve(Array.from(this._items.keys())); 117 | } 118 | 119 | const results = this._match(request, options); 120 | 121 | return Promise.resolve(results.map(([req]) => req)); 122 | } 123 | 124 | /** 125 | * @param { import('node-fetch').Request | string } request 126 | * @param { CacheQueryOptions } options 127 | * @returns { Array<[import('node-fetch').Request, import('node-fetch').Response]> } 128 | * @private 129 | */ 130 | _match(request, { ignoreSearch = false, ignoreMethod = false }) { 131 | request = this._normalizeRequest(request); 132 | 133 | /** @type { Array<[import('node-fetch').Request, import('node-fetch').Response]> } */ 134 | const results = []; 135 | const url = new URL(request.url); 136 | const pathname = this._normalizePathname(url.pathname); 137 | /** @type { string | null } */ 138 | let method = request.method; 139 | /** @type { string | null } */ 140 | let search = url.search; 141 | 142 | if (ignoreSearch) { 143 | search = null; 144 | } 145 | if (ignoreMethod) { 146 | method = null; 147 | } 148 | 149 | // TODO: handle VARY header 150 | 151 | this._items.forEach((res, req) => { 152 | const u = new URL(req.url); 153 | const s = ignoreSearch ? null : u.search; 154 | const m = ignoreMethod ? null : req.method; 155 | const p = this._normalizePathname(u.pathname); 156 | 157 | if (p && p === pathname && m === method && s === search) { 158 | results.push([req, res]); 159 | } 160 | }); 161 | 162 | return results; 163 | } 164 | 165 | /** 166 | * @param { import('node-fetch').Request | string } request 167 | * @returns { import('node-fetch').Request } 168 | * @private 169 | */ 170 | _normalizeRequest(request) { 171 | if (typeof request === 'string') { 172 | // @ts-ignore 173 | request = new Request(new URL(request, this._origin).href); 174 | } 175 | // @ts-ignore 176 | return request; 177 | } 178 | 179 | /** 180 | * @param { string } pathname 181 | * @returns { string } 182 | */ 183 | _normalizePathname(pathname) { 184 | return pathname.charAt(0) !== '/' ? `/${pathname}` : pathname; 185 | } 186 | 187 | _destroy() { 188 | this._items.clear(); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/api/CacheStorage.js: -------------------------------------------------------------------------------- 1 | import Cache from './Cache.js'; 2 | 3 | /** 4 | * @implements MockCacheStorage 5 | */ 6 | export default class CacheStorage { 7 | /** 8 | * Constructor 9 | * @param { string } origin 10 | */ 11 | constructor(origin) { 12 | this._caches = new Map(); 13 | this._origin = origin; 14 | } 15 | 16 | /** 17 | * Determine if cache with 'cacheName' exists 18 | * @param { string } cacheName 19 | * @returns { Promise } 20 | */ 21 | has(cacheName) { 22 | return Promise.resolve(this._caches.has(cacheName)); 23 | } 24 | 25 | /** 26 | * Open cache with 'cacheName' 27 | * Create if it doesn't exist 28 | * @param { string } cacheName 29 | * @returns { Promise } 30 | */ 31 | open(cacheName) { 32 | let cache = this._caches.get(cacheName); 33 | 34 | if (!cache) { 35 | cache = new Cache(cacheName, this._origin); 36 | this._caches.set(cacheName, cache); 37 | } 38 | 39 | return Promise.resolve(cache); 40 | } 41 | 42 | /** 43 | * Retrieve 'response' for matching 'request' 44 | * @param { import('node-fetch').Request | string } request 45 | * @param { CacheQueryOptions & { cacheName?: string } } [options] 46 | * @returns { Promise } 47 | */ 48 | match(request, options = {}) { 49 | if (options.cacheName) { 50 | const cache = this._caches.get(options.cacheName); 51 | 52 | if (!cache) { 53 | return Promise.reject(Error(`cache with name '${options.cacheName}' not found`)); 54 | } 55 | return cache.match(request, options); 56 | } 57 | 58 | for (const cache of this._caches.values()) { 59 | const results = cache._match(request, options); 60 | 61 | if (results.length) { 62 | return Promise.resolve(results[0][1]); 63 | } 64 | } 65 | 66 | return Promise.resolve(undefined); 67 | } 68 | 69 | /** 70 | * Retrieve all cache names 71 | * @returns { Promise> } 72 | */ 73 | keys() { 74 | return Promise.resolve(Array.from(this._caches.keys())); 75 | } 76 | 77 | /** 78 | * Delete cache with 'cacheName' 79 | * @param { string } cacheName 80 | * @returns { Promise } 81 | */ 82 | delete(cacheName) { 83 | return Promise.resolve(this._caches.delete(cacheName)); 84 | } 85 | 86 | _destroy() { 87 | this._caches.clear(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/api/Client.js: -------------------------------------------------------------------------------- 1 | let uid = 0; 2 | 3 | /** 4 | * @implements MockClient 5 | */ 6 | export default class Client { 7 | /** 8 | * Constructor 9 | * @param { string } url 10 | * @param { PostMessage } [postMessage] 11 | */ 12 | constructor(url, postMessage) { 13 | this.id = String(++uid); 14 | this.type = ''; 15 | this.url = url; 16 | this.postMessage = postMessage || function () {}; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/api/Clients.js: -------------------------------------------------------------------------------- 1 | import Client from './Client.js'; 2 | import WindowClient from './WindowClient.js'; 3 | 4 | /** 5 | * @implements MockClients 6 | */ 7 | export default class Clients { 8 | constructor() { 9 | /** @type { Array } */ 10 | this._clients = []; 11 | } 12 | 13 | /** 14 | * Retrieve a client matching a given id 15 | * @param { string } id 16 | * @returns { Promise } 17 | */ 18 | async get(id) { 19 | return this._clients.find((client) => client.id === id); 20 | } 21 | 22 | /** 23 | * Retrieve all matching clients 24 | * @param { { includeUncontrolled?: boolean, type?: string }} [options] 25 | * @returns { Promise> } 26 | */ 27 | async matchAll({ type = 'any' } = {}) { 28 | // TODO: handle `includeUncontrolled` 29 | return this._clients.filter((client) => type === 'any' || client.type === type); 30 | } 31 | 32 | /** 33 | * Creates a new top level browsing context and loads 'url' 34 | * @param { string } url 35 | * @returns { Promise } 36 | */ 37 | async openWindow(url) { 38 | const client = new WindowClient(url); 39 | 40 | this._clients.push(client); 41 | 42 | return client; 43 | } 44 | 45 | /** 46 | * Activate ServiceWorker for all clients 47 | * @returns { Promise } 48 | */ 49 | claim() { 50 | return Promise.resolve(); 51 | } 52 | 53 | /** 54 | * Create new client on connect 55 | * @param { string } url 56 | * @param { PostMessage } postMessage 57 | */ 58 | _connect(url, postMessage) { 59 | this._clients.push(new Client(url, postMessage)); 60 | } 61 | 62 | _destroy() { 63 | this._clients = []; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/api/ContentIndex.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @implements MockContentIndex 3 | */ 4 | export default class ContentIndex { 5 | constructor() { 6 | /** @type { Map } */ 7 | this._items = new Map(); 8 | } 9 | 10 | /** 11 | * Register item 12 | * @param { ContentDescription } description 13 | */ 14 | async add(description) { 15 | this._items.set(description.id, description); 16 | } 17 | 18 | /** 19 | * Unregister item 20 | * @param { string } id 21 | */ 22 | async delete(id) { 23 | this._items.delete(id); 24 | } 25 | 26 | /** 27 | * @returns { Promise> } 28 | */ 29 | async getAll() { 30 | return Array.from(this._items.values()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/MessageChannel.js: -------------------------------------------------------------------------------- 1 | import MessagePort from './MessagePort.js'; 2 | 3 | /** 4 | * @implements MockMessageChannel 5 | */ 6 | export default class MessageChannel { 7 | constructor() { 8 | this.port1 = new MessagePort(); 9 | this.port2 = new MessagePort(this.port1); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/api/MessagePort.js: -------------------------------------------------------------------------------- 1 | import EventTarget from './events/EventTarget.js'; 2 | import { handle } from '../events.js'; 3 | 4 | /** 5 | * @implements MockMessagePort 6 | */ 7 | export default class MessagePort extends EventTarget { 8 | /** 9 | * Constructor 10 | * @param { MessagePort } [otherPort] 11 | */ 12 | constructor(otherPort) { 13 | super(); 14 | this._otherPort = otherPort; 15 | 16 | /** @type { ((this: MockMessagePort, evt: MockMessageEvent) => void) | undefined } */ 17 | this.onmessage; 18 | } 19 | 20 | /** 21 | * Send 'message' 22 | * @param { unknown } message 23 | * @param { Array } [transferList] 24 | * @returns { void } 25 | */ 26 | postMessage(message, transferList = []) { 27 | const ports = transferList.filter((item) => item instanceof MessagePort); 28 | 29 | if (this._otherPort) { 30 | handle(this._otherPort, 'message', { data: message, ports }); 31 | } 32 | } 33 | 34 | /** 35 | * Send queued messages 36 | * @returns { void } 37 | */ 38 | start() { 39 | // no-op 40 | } 41 | 42 | /** 43 | * Stop sending messages 44 | * @returns { void } 45 | */ 46 | close() { 47 | // no-op 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/api/NavigationPreloadManager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @implements MockNavigationPreloadManager 3 | */ 4 | export default class NavigationPreloadManager { 5 | constructor() { 6 | this.enabled = false; 7 | this.headerValue = 'Service-Worker-Navigation-Preload'; 8 | } 9 | 10 | async enable() { 11 | this.enabled = true; 12 | return; 13 | } 14 | 15 | async disable() { 16 | this.enabled = false; 17 | return; 18 | } 19 | 20 | /** 21 | * @param { string } headerValue 22 | */ 23 | async setHeaderValue(headerValue) { 24 | this.headerValue = headerValue; 25 | } 26 | 27 | async getState() { 28 | return { enabled: this.enabled, headerValue: this.headerValue }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/api/Notification.js: -------------------------------------------------------------------------------- 1 | import EventTarget from './events/EventTarget.js'; 2 | 3 | export default class Notification extends EventTarget { 4 | /** 5 | * @param { string } title 6 | * @param { NotificationOptions } [options] 7 | */ 8 | constructor(title, options) { 9 | super(); 10 | this.title = title; 11 | Object.assign(this, options); 12 | } 13 | 14 | onclick() { 15 | // 16 | } 17 | 18 | close() { 19 | // Implemented at creation 20 | } 21 | } 22 | 23 | Notification.maxActions = 16; 24 | Notification.permission = 'default'; 25 | Notification.requestPermission = async () => { 26 | Notification.permission = 'granted'; 27 | return Notification.permission; 28 | }; 29 | -------------------------------------------------------------------------------- /src/api/PushManager.js: -------------------------------------------------------------------------------- 1 | import PushSubscription from './PushSubscription.js'; 2 | 3 | /** 4 | * @implements MockPushManager 5 | */ 6 | export default class PushManager { 7 | constructor() { 8 | this.subscription = new PushSubscription({ 9 | userVisibleOnly: true, 10 | applicationServerKey: 'BCnKOeg_Ly8MuvV3CIn21OahjnOOq8zeo_J0ojOPMD6RhxruIVFpLZzPi0huCn45aLq8RcHjOIMol0ytRhgAu8k', 11 | }); 12 | } 13 | 14 | /** 15 | * Retrieve subscription 16 | * @returns { Promise } 17 | */ 18 | getSubscription() { 19 | return Promise.resolve(this.subscription); 20 | } 21 | 22 | /** 23 | * Retrieve permission state 24 | * @param { MockPushSubscriptionOptions } [options] 25 | * @returns { Promise } 26 | */ 27 | permissionState(options) { 28 | return Promise.resolve('granted'); 29 | } 30 | 31 | /** 32 | * Retrieve subscription 33 | * @param { MockPushSubscriptionOptions } [options] 34 | * @returns { Promise } 35 | */ 36 | subscribe(options) { 37 | return Promise.resolve(this.subscription); 38 | } 39 | 40 | _destroy() { 41 | // @ts-ignore 42 | this.subscription = undefined; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/api/PushMessageData.js: -------------------------------------------------------------------------------- 1 | import { Blob } from 'buffer'; 2 | 3 | /** 4 | * @implements MockPushMessageData 5 | */ 6 | export default class PushMessageData { 7 | /** 8 | * @param { Object } [data] 9 | */ 10 | constructor(data) { 11 | this._data = data ?? {}; 12 | } 13 | 14 | arrayBuffer() { 15 | return new ArrayBuffer(20); 16 | } 17 | 18 | blob() { 19 | return new Blob([this.text()]); 20 | } 21 | 22 | json() { 23 | return this._data; 24 | } 25 | 26 | text() { 27 | return JSON.stringify(this._data); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/PushSubscription.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @implements MockPushSubscription 3 | */ 4 | export default class PushSubscription { 5 | /** 6 | * Constructor 7 | * @param { MockPushSubscriptionOptions } [options] 8 | */ 9 | constructor(options) { 10 | this.endpoint = 'test'; 11 | /** @type { number | null } */ 12 | this.expirationTime = null; 13 | this.options = options; 14 | this._keys = { 15 | p256dh: new ArrayBuffer(65), 16 | auth: new ArrayBuffer(16), 17 | applicationServerKey: new ArrayBuffer(87), 18 | }; 19 | } 20 | 21 | /** 22 | * Retrieve public key 23 | * @param { 'p256dh' | 'auth' } name 24 | */ 25 | getKey(name) { 26 | return this._keys[name]; 27 | } 28 | 29 | /** 30 | * Serialize 31 | */ 32 | toJSON() { 33 | return { 34 | endpoint: this.endpoint, 35 | expirationTime: this.expirationTime, 36 | options: this.options, 37 | }; 38 | } 39 | 40 | /** 41 | * Unregister subscription 42 | */ 43 | unsubscribe() { 44 | // TODO: remove from PushManager 45 | return Promise.resolve(true); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/api/ServiceWorker.js: -------------------------------------------------------------------------------- 1 | import EventTarget from './events/EventTarget.js'; 2 | 3 | /** 4 | * @implements MockServiceWorker 5 | */ 6 | export default class ServiceWorker extends EventTarget { 7 | /** 8 | * Constructor 9 | * @param { string } scriptURL 10 | * @param { PostMessage } postMessage 11 | */ 12 | constructor(scriptURL, postMessage) { 13 | super(); 14 | this.scriptURL = scriptURL; 15 | /** @type { MockServiceWorkerGlobalScope & Record } */ 16 | this.self; 17 | /** @type { 'installing' | 'installed' | 'activating' | 'activated' | 'redundant' } */ 18 | this.state = 'installing'; 19 | this.postMessage = postMessage; 20 | 21 | /** @type { ((this: MockServiceWorker, evt: Event) => void) | undefined } */ 22 | this.onstatechange; 23 | } 24 | 25 | _destroy() { 26 | // @ts-ignore 27 | this.self._destroy(); 28 | // @ts-ignore 29 | this.self = undefined; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/api/ServiceWorkerContainer.js: -------------------------------------------------------------------------------- 1 | import EventTarget from './events/EventTarget.js'; 2 | 3 | /** 4 | * @implements MockServiceWorkerContainer 5 | */ 6 | export default class ServiceWorkerContainer extends EventTarget { 7 | /** 8 | * Constructor 9 | * @param { string } href 10 | * @param { string } webroot 11 | * @param { (container: ServiceWorkerContainer, scriptURL: string, options?: { scope: string }) => Promise } register 12 | * @param { (container: ServiceWorkerContainer, origin: string, eventType: string, ...args: Array) => Promise } trigger 13 | */ 14 | constructor(href, webroot, register, trigger) { 15 | super(); 16 | /** @type { MockServiceWorker | null } */ 17 | this.controller = null; 18 | /** @type { MockServiceWorker } */ 19 | this.__serviceWorker__; 20 | 21 | /** @type { MockServiceWorkerRegistration } */ 22 | this._registration; 23 | this._href = href; 24 | this._webroot = webroot; 25 | 26 | this.register = register.bind(this, this); 27 | this.trigger = trigger.bind(this, this, href); 28 | 29 | /** @type { ((this: MockServiceWorkerContainer, evt: MockMessageEvent) => void) | undefined } */ 30 | this.onmessage; 31 | } 32 | 33 | /** 34 | * Retrieve ServiceWorkerRegistration when active 35 | * Will trigger install/activate lifecycle 36 | * @returns { Promise } 37 | */ 38 | get ready() { 39 | if (!this._registration) { 40 | throw Error('no script registered yet'); 41 | } 42 | if (this.controller) { 43 | return Promise.resolve(this._registration); 44 | } 45 | 46 | return this.trigger('install') 47 | .then(() => this.trigger('activate')) 48 | .then(() => this._registration); 49 | } 50 | 51 | /** 52 | * Retrieve current ServiceWorker registration 53 | * @param { string } [scope] 54 | * @returns { Promise } 55 | */ 56 | getRegistration(scope) { 57 | return Promise.resolve(this._registration); 58 | } 59 | 60 | /** 61 | * Retrieve all current ServiceWorker registrations 62 | * @returns { Promise> } 63 | */ 64 | getRegistrations() { 65 | return Promise.resolve([this._registration]); 66 | } 67 | 68 | startMessages() { 69 | // TODO 70 | } 71 | 72 | _destroy() { 73 | // @ts-ignore 74 | this.controller = this._registration = this.__serviceWorker__ = undefined; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/api/ServiceWorkerGlobalScope.js: -------------------------------------------------------------------------------- 1 | import CacheStorage from './CacheStorage.js'; 2 | import Clients from './Clients.js'; 3 | import EventTarget from './events/EventTarget.js'; 4 | 5 | /** 6 | * @implements MockServiceWorkerGlobalScope 7 | */ 8 | export default class ServiceWorkerGlobalScope extends EventTarget { 9 | /** 10 | * @param { ServiceWorkerGlobalScope } instance 11 | */ 12 | static [Symbol.hasInstance](instance) { 13 | return instance.registration && instance.caches && instance.clients; 14 | } 15 | 16 | /** 17 | * Constructor 18 | * @param { MockServiceWorkerRegistration } registration 19 | * @param { string } origin 20 | * @param { () => Promise } skipWaiting 21 | */ 22 | constructor(registration, origin, skipWaiting) { 23 | super(); 24 | this.caches = new CacheStorage(origin); 25 | this.clients = new Clients(); 26 | this.registration = registration; 27 | this.skipWaiting = skipWaiting.bind(this); 28 | 29 | /** @type { (this: MockServiceWorkerGlobalScope, evt: MockExtendableEvent) => void } */ 30 | this.oninstall; 31 | /** @type { (this: MockServiceWorkerGlobalScope, evt: MockExtendableEvent) => void } */ 32 | this.onactivate; 33 | /** @type { (this: MockServiceWorkerGlobalScope, evt: MockFetchEvent) => void } */ 34 | this.onfetch; 35 | /** @type { (this: MockServiceWorkerGlobalScope, evt: MockMessageEvent) => void } */ 36 | this.onmessage; 37 | /** @type { (this: MockServiceWorkerGlobalScope, evt: MockErrorEvent) => void } */ 38 | this.onerror; 39 | } 40 | 41 | _destroy() { 42 | this.caches._destroy(); 43 | this.clients._destroy(); 44 | this.removeAllEventListeners(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/api/ServiceWorkerRegistration.js: -------------------------------------------------------------------------------- 1 | import ContentIndex from './ContentIndex.js'; 2 | import EventTarget from './events/EventTarget.js'; 3 | import NavigationPreloadManager from './NavigationPreloadManager.js'; 4 | import PushManager from './PushManager.js'; 5 | 6 | /** 7 | * @implements MockServiceWorkerRegistration 8 | */ 9 | export default class ServiceWorkerRegistration extends EventTarget { 10 | /** 11 | * Constructor 12 | * @param { string } scope 13 | * @param { () => Promise } unregister 14 | */ 15 | constructor(scope, unregister) { 16 | super(); 17 | this.unregister = unregister; 18 | this.scope = scope; 19 | this.index = new ContentIndex(); 20 | this.pushManager = new PushManager(); 21 | this.navigationPreload = new NavigationPreloadManager(); 22 | 23 | /** @type { ((this: MockServiceWorkerRegistration, evt: Event) => void) | undefined } */ 24 | this.onupdatefound; 25 | 26 | /** @type { MockServiceWorker | null } */ 27 | this.installing = null; 28 | /** @type { MockServiceWorker | null } */ 29 | this.waiting = null; 30 | /** @type { MockServiceWorker | null } */ 31 | this.activating = null; 32 | /** @type { MockServiceWorker | null } */ 33 | this.active = null; 34 | 35 | /** @type { Set } */ 36 | this._notifications = new Set(); 37 | } 38 | 39 | /** 40 | * Retrieve notifications 41 | * @param { { tag?: string }} [options] 42 | * @returns { Promise> } 43 | */ 44 | async getNotifications(options) { 45 | const notifications = Array.from(this._notifications); 46 | 47 | if (options?.tag && options.tag.length > 0) { 48 | return notifications.filter((notification) => (notification.tag ? notification.tag === options.tag : false)); 49 | } 50 | 51 | return notifications; 52 | } 53 | 54 | /** 55 | * Create notification 56 | * @param { string } title 57 | * @param { NotificationOptions } [options] 58 | * @returns { Promise } 59 | */ 60 | async showNotification(title, options) { 61 | const notification = new Notification(title, options); 62 | 63 | this._notifications.add(notification); 64 | 65 | notification.close = () => { 66 | this._notifications.delete(notification); 67 | }; 68 | } 69 | 70 | /** 71 | * Update worker script 72 | */ 73 | update() { 74 | // No-op 75 | } 76 | 77 | _destroy() { 78 | // @ts-ignore 79 | this.index = undefined; 80 | this.pushManager._destroy(); 81 | // @ts-ignore 82 | this.pushManager = undefined; 83 | // @ts-ignore 84 | this.navigationPreload = undefined; 85 | this.installing = this.waiting = this.activating = this.active = null; 86 | this.removeAllEventListeners(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/api/WindowClient.js: -------------------------------------------------------------------------------- 1 | import Client from './Client.js'; 2 | 3 | /** 4 | * @implements MockWindowClient 5 | */ 6 | export default class WindowClient extends Client { 7 | /** 8 | * Constructor 9 | * @param { string } url 10 | * @param { PostMessage } [postMessage] 11 | */ 12 | constructor(url, postMessage) { 13 | super(url, postMessage); 14 | this.type = 'window'; 15 | this.focused = false; 16 | /** @type { 'hidden' | 'visible' } */ 17 | this.visibilityState = 'hidden'; 18 | } 19 | 20 | /** 21 | * Focus window 22 | */ 23 | async focus() { 24 | this.focused = true; 25 | this.visibilityState = 'visible'; 26 | return this; 27 | } 28 | 29 | /** 30 | * Navigate to new `url` 31 | * @param { string } url 32 | */ 33 | async navigate(url) { 34 | this.url = url; 35 | return this; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/api/_.d.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | declare type Headers = import('node-fetch').Headers; 3 | // @ts-ignore 4 | declare type Request = import('node-fetch').Request; 5 | // @ts-ignore 6 | declare type Response = import('node-fetch').Response; 7 | 8 | declare interface MockServiceWorkerContainer extends MockEventTarget { 9 | /** The current `ServiceWorker`, if active */ 10 | controller: MockServiceWorker | null; 11 | /** Resolves with `ServiceWorkerRegistration` when `ServiceWorker` becomes active */ 12 | ready: Promise; 13 | /** The `ServiceWorker` instance associated with this container */ 14 | __serviceWorker__: MockServiceWorker; 15 | 16 | onmessage: ((this: MockServiceWorkerContainer, evt: MockMessageEvent) => void) | undefined; 17 | 18 | /** 19 | * Install or update a service worker 20 | * @param scriptURL - the path to your service worker file (resolved based on optional `webroot` passed to `connect()`) 21 | * @param options - optional directory `scope` under which your service worker should be registered 22 | */ 23 | register(scriptURL: string, options?: { scope: string }): Promise; 24 | /** 25 | * Trigger event in active service worker context 26 | */ 27 | trigger( 28 | eventType: EventType, 29 | ...args: Array 30 | ): Promise; 31 | /** 32 | * Retrieve the current registration for `scope` 33 | * @param scope 34 | */ 35 | getRegistration(scope: string): Promise; 36 | /** 37 | * Retrieve all current registrations for this container 38 | * @param scope 39 | */ 40 | getRegistrations(scope: string): Promise>; 41 | } 42 | 43 | declare interface MockServiceWorker extends MockEventTarget { 44 | scriptURL: string; 45 | state: 'installing' | 'installed' | 'activating' | 'activated' | 'redundant'; 46 | /** The global execution context of your service worker (`self` inside the worker script) */ 47 | self: MockServiceWorkerGlobalScope & Record; 48 | 49 | onstatechange: ((this: MockServiceWorker, evt: Event) => void) | undefined; 50 | 51 | postMessage(message: unknown, transferList?: Array): void; 52 | } 53 | 54 | declare interface MockServiceWorkerRegistration extends MockEventTarget { 55 | scope: string; 56 | index?: MockContentIndex; 57 | navigationPreload?: MockNavigationPreloadManager; 58 | pushManager?: MockPushManager; 59 | installing: MockServiceWorker | null; 60 | waiting: MockServiceWorker | null; 61 | activating: MockServiceWorker | null; 62 | active: MockServiceWorker | null; 63 | 64 | onupdatefound: ((this: MockServiceWorkerRegistration, evt: Event) => void) | undefined; 65 | 66 | getNotifications(options?: { tag?: string }): Promise>; 67 | showNotification(title: string, options?: NotificationOptions): Promise; 68 | update(): void; 69 | unregister(): Promise; 70 | } 71 | 72 | declare interface MockServiceWorkerGlobalScope extends MockEventTarget { 73 | caches: MockCacheStorage; 74 | clients: MockClients; 75 | registration: MockServiceWorkerRegistration; 76 | 77 | oninstall: ((this: MockServiceWorkerGlobalScope, evt: MockExtendableEvent) => void) | undefined; 78 | onactivate: ((this: MockServiceWorkerGlobalScope, evt: MockExtendableEvent) => void) | undefined; 79 | onfetch: ((this: MockServiceWorkerGlobalScope, evt: MockFetchEvent) => void) | undefined; 80 | onmessage: ((this: MockServiceWorkerGlobalScope, evt: MockMessageEvent) => void) | undefined; 81 | onerror: ((this: MockServiceWorkerGlobalScope, evt: MockErrorEvent) => void) | undefined; 82 | 83 | skipWaiting(): Promise; 84 | } 85 | 86 | declare interface MockContentIndex { 87 | add(description: ContentDescription): void; 88 | delete(id: string): void; 89 | getAll(): Promise>; 90 | } 91 | 92 | declare interface MockNavigationPreloadManager { 93 | enable(): Promise; 94 | disable(): Promise; 95 | setHeaderValue(headerValue: string): Promise; 96 | getState(): Promise<{ enabled: boolean; headerValue: string }>; 97 | } 98 | 99 | declare interface MockCacheStorage { 100 | has(cacheName: string): Promise; 101 | open(cacheName: string): Promise; 102 | match( 103 | request: import('node-fetch').Request | string, 104 | options?: CacheQueryOptions & { cacheName?: string }, 105 | ): Promise; 106 | keys(): Promise>; 107 | delete(cacheName: string): Promise; 108 | } 109 | 110 | declare interface MockCache { 111 | match( 112 | request: import('node-fetch').Request | string, 113 | options?: CacheQueryOptions, 114 | ): Promise; 115 | matchAll( 116 | request: import('node-fetch').Request | string, 117 | options?: CacheQueryOptions, 118 | ): Promise>; 119 | add(request: import('node-fetch').Request | string): Promise; 120 | addAll(requests: Array): Promise>; 121 | put(request: import('node-fetch').Request | string, response: import('node-fetch').Response): Promise; 122 | keys( 123 | request?: import('node-fetch').Request | string, 124 | options?: CacheQueryOptions, 125 | ): Promise>; 126 | delete(request: import('node-fetch').Request | string, options?: CacheQueryOptions): Promise; 127 | } 128 | 129 | declare interface MockClients { 130 | get(id: string): Promise; 131 | matchAll(options?: { includeUncontrolled?: boolean; type?: string }): Promise>; 132 | openWindow(url: string): Promise; 133 | claim(): Promise; 134 | } 135 | 136 | declare interface MockClient { 137 | id: string; 138 | type: string; 139 | url: string; 140 | 141 | postMessage(message: unknown, transferList?: Array): void; 142 | } 143 | 144 | declare interface MockWindowClient extends MockClient { 145 | focused: boolean; 146 | visibilityState: 'hidden' | 'visible'; 147 | 148 | focus(): Promise; 149 | navigate(url: string): Promise; 150 | } 151 | 152 | declare type PostMessage = (message: unknown, transferList: Array) => void; 153 | 154 | declare interface MockMessageChannel { 155 | port1: MockMessagePort; 156 | port2: MockMessagePort; 157 | } 158 | 159 | // @ts-ignore 160 | declare class MessageChannel { 161 | port1: MockMessagePort; 162 | port2: MockMessagePort; 163 | } 164 | 165 | declare interface MockMessagePort extends MockEventTarget { 166 | onmessage: ((this: MockMessagePort, evt: MockMessageEvent) => void) | undefined; 167 | 168 | postMessage: PostMessage; 169 | start(): void; 170 | close(): void; 171 | } 172 | 173 | declare interface MockPushManager { 174 | subscription: MockPushSubscription; 175 | 176 | getSubscription(): Promise; 177 | permissionState(): Promise; 178 | subscribe(): Promise; 179 | } 180 | 181 | declare interface MockNotification { 182 | body: string; 183 | data: any; 184 | dir: 'auto' | 'ltr' | 'rtl'; 185 | icon: string; 186 | lang: string; 187 | tag: string; 188 | title: string; 189 | 190 | close(): void; 191 | } 192 | 193 | declare interface MockPushMessageData { 194 | arrayBuffer(): ArrayBuffer; 195 | blob(): import('buffer').Blob; 196 | json(): Object; 197 | text(): string; 198 | } 199 | 200 | declare interface MockPushSubscription { 201 | endpoint: string; 202 | expirationTime: number | null; 203 | options?: { userVisibleOnly: boolean; applicationServerKey: string }; 204 | 205 | getKey(name: 'p256dh' | 'auth'): ArrayBuffer; 206 | toJSON(): Object; 207 | unsubscribe(): Promise; 208 | } 209 | 210 | type MockPushSubscriptionOptions = { 211 | userVisibleOnly: boolean; 212 | applicationServerKey: string; 213 | }; 214 | 215 | type ContentDescription = { 216 | id: string; 217 | title: string; 218 | description: string; 219 | category: '' | 'homepage' | 'article' | 'video' | 'audio'; 220 | icons: Array<{ src: string; sizes: string; type: string }>; 221 | url: string; 222 | }; 223 | 224 | interface TriggerEvents { 225 | install: void; 226 | activate: void; 227 | fetch: import('node-fetch').Response; 228 | error: void; 229 | unhandledrejection: void; 230 | } 231 | -------------------------------------------------------------------------------- /src/api/events/ContentIndexEvent.js: -------------------------------------------------------------------------------- 1 | import ExtendableEvent from './ExtendableEvent.js'; 2 | 3 | export default class ContentIndexEvent extends ExtendableEvent { 4 | /** 5 | * Constructor 6 | * @param { string } type 7 | * @param { { id?: number }} data 8 | */ 9 | constructor(type, data = {}) { 10 | super(type); 11 | this.id = data.id; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/api/events/ErrorEvent.js: -------------------------------------------------------------------------------- 1 | import ExtendableEvent from './ExtendableEvent.js'; 2 | 3 | /** 4 | * @implements MockErrorEvent 5 | */ 6 | export default class ErrorEvent extends ExtendableEvent { 7 | /** 8 | * 9 | * @param { string } type 10 | * @param { Error } error 11 | */ 12 | constructor(type, error) { 13 | super(type); 14 | this.message = error?.message ?? 'Error'; 15 | this.error = error; 16 | this.promise = Promise.resolve(error); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/api/events/EventTarget.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple EventTarget base class to enable `removeAllEventListeners()` 3 | * @implements MockEventTarget 4 | */ 5 | export default class EventTarget { 6 | constructor() { 7 | /** @type { Record void >> } */ 8 | this.listeners = {}; 9 | } 10 | 11 | /** 12 | * @param { string } eventType 13 | * @param { (event: Event) => void } listener 14 | */ 15 | addEventListener(eventType, listener) { 16 | if (!this.listeners[eventType]) { 17 | this.listeners[eventType] = []; 18 | } 19 | this.listeners[eventType].push(listener); 20 | } 21 | 22 | /** 23 | * @param { string } eventType 24 | * @param { (event: Event) => void } listener 25 | */ 26 | removeEventListener(eventType, listener) { 27 | if (!this.listeners[eventType]) { 28 | return; 29 | } 30 | this.listeners[eventType].splice(this.listeners[eventType].indexOf(listener), 1); 31 | } 32 | 33 | removeAllEventListeners() { 34 | this.listeners = {}; 35 | } 36 | 37 | /** 38 | * @param { Event } event 39 | * @returns { boolean } 40 | */ 41 | dispatchEvent(event) { 42 | return false; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/api/events/ExtendableEvent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @implements MockExtendableEvent 3 | */ 4 | export default class ExtendableEvent extends Event { 5 | /** 6 | * 7 | * @param { string } type 8 | * @param { Object } [init] 9 | */ 10 | constructor(type, init) { 11 | super(type); 12 | /** @type { Promise | undefined } */ 13 | this.promise; 14 | } 15 | 16 | /** 17 | * Wait until 'promise' resolves 18 | * @param { Promise } promise 19 | * @returns { void } 20 | */ 21 | waitUntil(promise) { 22 | this.promise = promise; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/api/events/FetchEvent.js: -------------------------------------------------------------------------------- 1 | import { contentType } from 'mime-types'; 2 | import ExtendableEvent from './ExtendableEvent.js'; 3 | import path from 'path'; 4 | import { Request } from 'node-fetch'; 5 | 6 | /** 7 | * @implements MockFetchEvent 8 | */ 9 | export default class FetchEvent extends ExtendableEvent { 10 | /** 11 | * Constructor 12 | * @param { string } type 13 | * @param { string } origin 14 | * @param { FetchEventInit } init 15 | */ 16 | constructor(type, origin, { clientId, request, resultingClientId, replacesClientId, preloadResponse }) { 17 | super(type); 18 | 19 | this.request = request; 20 | this.preloadResponse = preloadResponse ?? Promise.resolve(undefined); 21 | this.clientId = clientId ?? ''; 22 | this.resultingClientId = resultingClientId ?? ''; 23 | this.replacesClientId = replacesClientId ?? ''; 24 | 25 | /** @type { URL } */ 26 | let url; 27 | /** @type { import('node-fetch').RequestInit } */ 28 | let requestInit; 29 | 30 | if (typeof request === 'string') { 31 | url = new URL(request, origin); 32 | const ext = path.extname(url.pathname) || '.html'; 33 | const accept = contentType(ext) || '*/*'; 34 | requestInit = { 35 | headers: { 36 | accept, 37 | }, 38 | }; 39 | } else { 40 | const { body, headers = {}, method = 'GET', redirect = 'follow' } = request; 41 | url = new URL(request.url, origin); 42 | requestInit = { 43 | body, 44 | headers, 45 | method, 46 | redirect, 47 | }; 48 | } 49 | 50 | this.request = new Request(url.href, requestInit); 51 | } 52 | 53 | /** 54 | * Store response 55 | * @param { Promise } promise 56 | * @returns { void } 57 | */ 58 | respondWith(promise) { 59 | this.promise = promise; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/api/events/MessageEvent.js: -------------------------------------------------------------------------------- 1 | import ExtendableEvent from './ExtendableEvent.js'; 2 | 3 | /** 4 | * @implements MockMessageEvent 5 | */ 6 | export default class MessageEvent extends ExtendableEvent { 7 | /** 8 | * Constructor 9 | * @param { string } type 10 | * @param { { data?: unknown, origin?: string, lastEventId?: string, source?: MessageEventSource, ports?: Array } } [init] 11 | */ 12 | constructor(type, init = {}) { 13 | super(type); 14 | this.data = init.data ?? null; 15 | this.origin = init.origin ?? ''; 16 | this.source = init.source ?? null; 17 | this.ports = init.ports ?? []; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/api/events/NotificationEvent.js: -------------------------------------------------------------------------------- 1 | import ExtendableEvent from './ExtendableEvent.js'; 2 | 3 | /** 4 | * @implements MockNotificationEvent 5 | */ 6 | export default class NotificationEvent extends ExtendableEvent { 7 | /** 8 | * 9 | * @param { string } type 10 | * @param { MockNotification } notification 11 | */ 12 | constructor(type, notification) { 13 | super(type); 14 | this.notification = notification; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/api/events/PushEvent.js: -------------------------------------------------------------------------------- 1 | import ExtendableEvent from './ExtendableEvent.js'; 2 | import PushMessageData from '../PushMessageData.js'; 3 | 4 | /** 5 | * @implements MockPushEvent 6 | */ 7 | export default class PushEvent extends ExtendableEvent { 8 | /** 9 | * 10 | * @param { string } type 11 | * @param { { data?: Object } } [init] 12 | */ 13 | constructor(type, init = {}) { 14 | super(type); 15 | this.data = new PushMessageData(init.data); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/api/events/_.d.ts: -------------------------------------------------------------------------------- 1 | declare interface MockEventTarget { 2 | listeners: Record void>>; 3 | 4 | addEventListener(eventType: string, listener: (event: Event) => void): void; 5 | removeEventListener(eventType: string, listener: (event: Event) => void): void; 6 | removeAllEventListeners(): void; 7 | dispatchEvent(event: Event): boolean; 8 | } 9 | 10 | declare interface MockExtendableEvent extends Event { 11 | waitUntil(promise: Promise): void; 12 | } 13 | 14 | declare interface MockFetchEvent extends MockExtendableEvent { 15 | request: import('node-fetch').Request; 16 | preloadResponse: Promise; 17 | clientId: string; 18 | resultingClientId: string; 19 | replacesClientId: string; 20 | 21 | respondWith(promise: Promise): void; 22 | } 23 | 24 | declare interface MockMessageEvent extends MockExtendableEvent { 25 | data: unknown | null; 26 | origin: string; 27 | source: MessageEventSource | null; 28 | ports: Array; 29 | } 30 | 31 | declare interface MockErrorEvent extends MockExtendableEvent { 32 | message: string | null; 33 | error: Error; 34 | } 35 | 36 | declare interface MockPushEvent extends MockExtendableEvent { 37 | data: MockPushMessageData; 38 | } 39 | 40 | declare interface MockNotificationEvent extends MockExtendableEvent { 41 | notification: MockNotification; 42 | } 43 | 44 | type FetchEventInit = { 45 | request: import('node-fetch').Request; 46 | preloadResponse?: Promise; 47 | clientId?: string; 48 | resultingClientId?: string; 49 | replacesClientId?: string; 50 | isReload?: boolean; 51 | }; 52 | -------------------------------------------------------------------------------- /src/createContext.js: -------------------------------------------------------------------------------- 1 | /* // All ServiceWorker properties: 2 | const swProps = 'Object,Function,Array,Number,parseFloat,parseInt,Infinity,NaN,undefined,Boolean,String,Symbol,Date,Promise,RegExp,Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError,JSON,Math,console,Intl,ArrayBuffer,Uint8Array,Int8Array,Uint16Array,Int16Array,Uint32Array,Int32Array,Float32Array,Float64Array,Uint8ClampedArray,DataView,Map,Set,WeakMap,WeakSet,Proxy,Reflect,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,escape,unescape,eval,isFinite,isNaN,ByteLengthQueuingStrategy,CountQueuingStrategy,ReadableStream,WritableStream,NavigationPreloadManager,PushSubscriptionOptions,PushSubscription,PushManager,PushEvent,PermissionStatus,Permissions,PerformanceObserverEntryList,NotificationEvent,Notification,DOMRectReadOnly,DOMRect,DOMQuad,DOMPointReadOnly,DOMPoint,DOMMatrixReadOnly,DOMMatrix,StorageManager,BudgetService,BroadcastChannel,SyncManager,SyncEvent,WindowClient,WebSocket,TextEncoder,TextDecoder,SubtleCrypto,ServiceWorkerRegistration,ServiceWorkerGlobalScope,Response,Request,PushMessageData,InstallEvent,IDBVersionChangeEvent,IDBTransaction,IDBRequest,IDBOpenDBRequest,IDBObjectStore,IDBKeyRange,IDBIndex,IDBFactory,IDBDatabase,IDBCursorWithValue,IDBCursor,Headers,FetchEvent,ExtendableMessageEvent,ExtendableEvent,EventSource,CryptoKey,Crypto,CloseEvent,Clients,Client,CacheStorage,Cache,WorkerNavigator,WorkerLocation,WorkerGlobalScope,URLSearchParams,URL,PromiseRejectionEvent,MessagePort,MessageEvent,MessageChannel,ImageData,ImageBitmap,FormData,FileReader,FileList,File,EventTarget,Event,DOMStringList,DOMException,CustomEvent,Blob,clients,registration,onactivate,onfetch,oninstall,onmessage,fetch,skipWaiting,onsync,onnotificationclick,onnotificationclose,onpush,SharedArrayBuffer,Atomics,WebAssembly,getAllPropertyNames,constructor,self,location,onerror,navigator,onrejectionhandled,onunhandledrejection,isSecureContext,origin,performance,importScripts,btoa,atob,setTimeout,clearTimeout,setInterval,clearInterval,createImageBitmap,crypto,indexedDB,fetch,caches,constructor,addEventListener,removeEventListener,dispatchEvent,constructor,constructor,__defineGetter__,__defineSetter__,hasOwnProperty,__lookupGetter__,__lookupSetter__,isPrototypeOf,propertyIsEnumerable,toString,valueOf,__proto__,toLocaleString' 3 | */ 4 | /* // Missing Context properties: 5 | 'Atomics,Blob,BroadcastChannel,BudgetService,ByteLengthQueuingStrategy,CloseEvent,CountQueuingStrategy,Crypto,CryptoKey,CustomEvent,DOMException,DOMMatrix,DOMMatrixReadOnly,DOMPoint,DOMPointReadOnly,DOMQuad,DOMRect,DOMRectReadOnly,DOMStringList,Event,EventSource,EventTarget,ExtendableMessageEvent,File,FileList,FileReader,ImageBitmap,ImageData,InstallEvent,NavigationPreloadManager,PerformanceObserverEntryList,PermissionStatus,Permissions,PromiseRejectionEvent,PushMessageData,PushSubscriptionOptions,ReadableStream,SharedArrayBuffer,StorageManager,SubtleCrypto,SyncEvent,SyncManager,TextDecoder,TextEncoder,URLSearchParams,WebSocket,WindowClient,WorkerGlobalScope,WorkerLocation,WorkerNavigator,WritableStream,atob,btoa,createImageBitmap,crypto,dispatchEvent,isSecureContext,onactivate,onerror,onfetch,oninstall,onmessage,onnotificationclick,onnotificationclose,onpush,onrejectionhandled,onsync,onunhandledrejection,performance,removeEventListener' 6 | */ 7 | /* 8 | function getAllPropertyNames(obj) { 9 | let props = []; 10 | do { 11 | props = props.concat(Object.getOwnPropertyNames(obj)); 12 | } while ((obj = Object.getPrototypeOf(obj))); 13 | return props; 14 | } 15 | */ 16 | 17 | import fetch, { Headers, Request, Response } from 'node-fetch'; 18 | import Cache from './api/Cache.js'; 19 | import CacheStorage from './api/CacheStorage.js'; 20 | import Client from './api/Client.js'; 21 | import Clients from './api/Clients.js'; 22 | import { createRequire } from 'module'; 23 | import ExtendableEvent from './api/events/ExtendableEvent.js'; 24 | import fakeIndexedDB from 'fake-indexeddb/build/fakeIndexedDB.js'; 25 | import FDBCursor from 'fake-indexeddb/build/FDBCursor.js'; 26 | import FDBCursorWithValue from 'fake-indexeddb/build/FDBCursorWithValue.js'; 27 | import FDBDatabase from 'fake-indexeddb/build/FDBDatabase.js'; 28 | import FDBFactory from 'fake-indexeddb/build/FDBFactory.js'; 29 | import FDBIndex from 'fake-indexeddb/build/FDBIndex.js'; 30 | import FDBKeyRange from 'fake-indexeddb/build/FDBKeyRange.js'; 31 | import FDBObjectStore from 'fake-indexeddb/build/FDBObjectStore.js'; 32 | import FDBOpenDBRequest from 'fake-indexeddb/build/FDBOpenDBRequest.js'; 33 | import FDBRequest from 'fake-indexeddb/build/FDBRequest.js'; 34 | import FDBTransaction from 'fake-indexeddb/build/FDBTransaction.js'; 35 | import FDBVersionChangeEvent from 'fake-indexeddb/build/FDBVersionChangeEvent.js'; 36 | import FetchEvent from './api/events/FetchEvent.js'; 37 | import FormData from 'form-data'; 38 | import MessageChannel from './api/MessageChannel.js'; 39 | import MessageEvent from './api/events/MessageEvent.js'; 40 | import MessagePort from './api/MessagePort.js'; 41 | import Notification from './api/Notification.js'; 42 | import NotificationEvent from './api/events/NotificationEvent.js'; 43 | import PushEvent from './api/events/PushEvent.js'; 44 | import PushManager from './api/PushManager.js'; 45 | import PushSubscription from './api/PushSubscription.js'; 46 | import ServiceWorkerGlobalScope from './api/ServiceWorkerGlobalScope.js'; 47 | import ServiceWorkerRegistration from './api/ServiceWorkerRegistration.js'; 48 | 49 | /** 50 | * Create context object for 'globalScope' 51 | * @param { Object } globalScope 52 | * @param { Object } contextlocation 53 | * @param { string } contextpath 54 | * @param { string } origin 55 | * @returns { ServiceWorkerGlobalScope } 56 | */ 57 | export default function createContext(globalScope, contextlocation, contextpath, origin) { 58 | const context = Object.assign(globalScope, { 59 | Cache, 60 | CacheStorage, 61 | Client, 62 | Clients, 63 | ExtendableEvent, 64 | FetchEvent, 65 | FormData, 66 | Headers, 67 | IDBCursor: FDBCursor, 68 | IDBCursorWithValue: FDBCursorWithValue, 69 | IDBDatabase: FDBDatabase, 70 | IDBFactory: FDBFactory, 71 | IDBIndex: FDBIndex, 72 | IDBKeyRange: FDBKeyRange, 73 | IDBObjectStore: FDBObjectStore, 74 | IDBOpenDBRequest: FDBOpenDBRequest, 75 | IDBRequest: FDBRequest, 76 | IDBTransaction: FDBTransaction, 77 | IDBVersionChangeEvent: FDBVersionChangeEvent, 78 | MessageChannel, 79 | MessageEvent, 80 | MessagePort, 81 | navigator: { 82 | connection: 'not implemented', 83 | online: true, 84 | permissions: 'not implemented', 85 | storage: 'not implemented', 86 | userAgent: 'sw-test-env', 87 | }, 88 | Notification, 89 | NotificationEvent, 90 | PushEvent, 91 | PushManager, 92 | PushSubscription, 93 | Request, 94 | Response, 95 | ServiceWorkerGlobalScope, 96 | ServiceWorkerRegistration, 97 | URL, 98 | console, 99 | clearImmediate, 100 | clearInterval, 101 | clearTimeout, 102 | fetch, 103 | indexedDB: fakeIndexedDB, 104 | location: contextlocation, 105 | origin, 106 | process, 107 | require: createRequire(contextpath), 108 | setImmediate, 109 | setTimeout, 110 | setInterval, 111 | self: globalScope, 112 | }); 113 | 114 | // @ts-ignore 115 | return context; 116 | } 117 | -------------------------------------------------------------------------------- /src/events.js: -------------------------------------------------------------------------------- 1 | import ErrorEvent from './api/events/ErrorEvent.js'; 2 | import ExtendableEvent from './api/events/ExtendableEvent.js'; 3 | import FetchEvent from './api/events/FetchEvent.js'; 4 | import MessageEvent from './api/events/MessageEvent.js'; 5 | import NotificationEvent from './api/events/NotificationEvent.js'; 6 | 7 | /** 8 | * Create 'event' instance 9 | * @param { EventTarget } target 10 | * @param { string } type 11 | * @param { Array } args 12 | */ 13 | export function create(target, type, ...args) { 14 | let event; 15 | 16 | switch (type) { 17 | case 'error': 18 | // @ts-ignore 19 | event = new ErrorEvent('error', args[0]); 20 | break; 21 | case 'fetch': 22 | // @ts-ignore 23 | event = new FetchEvent(type, ...args); 24 | break; 25 | case 'message': 26 | // @ts-ignore 27 | event = new MessageEvent(type, args[0]); 28 | break; 29 | case 'notificationclick': 30 | // @ts-ignore 31 | return new NotificationEvent(type, args[0]); 32 | default: 33 | event = new ExtendableEvent(type); 34 | } 35 | 36 | Object.defineProperty(event, 'target', { 37 | value: target, 38 | writable: false, 39 | }); 40 | 41 | return event; 42 | } 43 | 44 | /** 45 | * Handle event 'type' from 'target' 46 | * @param { MockEventTarget } target 47 | * @param { string } type 48 | * @param { Array } args 49 | * @returns { Promise } 50 | */ 51 | export function handle(target, type, ...args) { 52 | const listeners = target.listeners[type]?.slice() || []; 53 | // @ts-ignore 54 | const onevent = target[`on${type}`]; 55 | 56 | if (onevent) { 57 | listeners.push(onevent); 58 | } 59 | 60 | if ((type === 'error' || type === 'unhandledrejection') && !listeners.length) { 61 | throw args[0] || Error(`unhandled error of type ${type}`); 62 | } 63 | 64 | if (listeners.length === 1) { 65 | return doHandle(target, listeners[0], type, args); 66 | } 67 | 68 | return Promise.all(listeners.map((listener) => doHandle(target, listener, type, args))); 69 | } 70 | 71 | /** 72 | * Execute handle of 'listener' 73 | * @param { EventTarget } target 74 | * @param { (event: Event) => void } listener 75 | * @param { string } type 76 | * @param { Array } args 77 | * @returns { Promise } 78 | */ 79 | function doHandle(target, listener, type, args) { 80 | const event = create(target, type, ...args); 81 | 82 | listener(event); 83 | return event.promise ?? Promise.resolve(); 84 | } 85 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef { Object } Context 3 | * @property { import('./api/ServiceWorkerRegistration').default } registration 4 | * @property { import('./api/ServiceWorker').default } sw 5 | */ 6 | import createContext from './createContext.js'; 7 | import esbuild from 'esbuild'; 8 | import { handle } from './events.js'; 9 | import path from 'path'; 10 | import ServiceWorker from './api/ServiceWorker.js'; 11 | import ServiceWorkerContainer from './api/ServiceWorkerContainer.js'; 12 | import ServiceWorkerGlobalScope from './api/ServiceWorkerGlobalScope.js'; 13 | import ServiceWorkerRegistration from './api/ServiceWorkerRegistration.js'; 14 | import vm from 'vm'; 15 | 16 | const DEFAULT_ORIGIN = 'http://localhost:3333/'; 17 | const DEFAULT_SCOPE = '/'; 18 | 19 | /** @type { Set } */ 20 | const containers = new Set(); 21 | /** @type { Map } */ 22 | const contexts = new Map(); 23 | 24 | export { Headers, Request, Response } from 'node-fetch'; 25 | export { default as MessageChannel } from './api/MessageChannel.js'; 26 | 27 | /** 28 | * Create/retrieve ServiceWorkerContainer instance for `origin` 29 | * @param { string } [origin] 30 | * @param { string } [webroot] 31 | * @returns { Promise } 32 | */ 33 | export async function connect(origin = DEFAULT_ORIGIN, webroot = process.cwd()) { 34 | if (!origin.endsWith('/')) { 35 | origin += '/'; 36 | } 37 | 38 | const container = new ServiceWorkerContainer(origin, webroot, register, trigger); 39 | 40 | containers.add(container); 41 | // TODO: check if active context and apply state 42 | return container; 43 | } 44 | 45 | /** 46 | * Destroy all active containers/contexts 47 | * @returns { Promise } 48 | */ 49 | export async function destroy() { 50 | for (const container of containers) { 51 | container._destroy(); 52 | } 53 | for (const context of contexts.values()) { 54 | context.registration._destroy(); 55 | context.sw._destroy(); 56 | } 57 | containers.clear(); 58 | contexts.clear(); 59 | } 60 | 61 | /** 62 | * Register ServiceWorker script at 'scriptURL' 63 | * @param { ServiceWorkerContainer } container 64 | * @param { string } scriptURL 65 | * @param { { scope?: string } } [options] 66 | * @returns { Promise } 67 | */ 68 | async function register(container, scriptURL, { scope = DEFAULT_SCOPE } = {}) { 69 | if (scriptURL.charAt(0) == '/') { 70 | scriptURL = scriptURL.slice(1); 71 | } 72 | const origin = getOrigin(container._href); 73 | const scopeHref = new URL(scope, origin).href; 74 | const webroot = container._webroot; 75 | let context = contexts.get(scopeHref); 76 | 77 | if (!context) { 78 | const contextPath = getResolvedPath(webroot, scriptURL); 79 | const contextLocation = new URL(scriptURL, origin); 80 | const registration = new ServiceWorkerRegistration(scopeHref, unregister.bind(null, scopeHref)); 81 | const globalScope = new ServiceWorkerGlobalScope(registration, origin, async () => { 82 | if (registration.waiting !== null) { 83 | trigger(container, origin, 'activate'); 84 | } 85 | }); 86 | const sw = new ServiceWorker(scriptURL, swPostMessage.bind(null, container, origin)); 87 | const scriptPath = isRelativePath(scriptURL) ? path.resolve(webroot, scriptURL) : scriptURL; 88 | try { 89 | const bundledSrc = esbuild.buildSync({ 90 | bundle: true, 91 | entryPoints: [scriptPath], 92 | format: 'cjs', 93 | target: 'node16', 94 | platform: 'node', 95 | write: false, 96 | }); 97 | const vmContext = createContext(globalScope, contextLocation, contextPath, origin); 98 | const sandbox = /** @type { ServiceWorkerGlobalScope & Record } */ (vm.createContext(vmContext)); 99 | vm.runInContext(bundledSrc.outputFiles[0].text, sandbox); 100 | 101 | sw.self = sandbox; 102 | context = { 103 | registration, 104 | sw, 105 | }; 106 | contexts.set(scopeHref, context); 107 | } catch (err) { 108 | throw /** @type { Error } */ (err).message.includes('importScripts') 109 | ? Error('"importScripts" not supported in esm ServiceWorker. Use esm "import" statement instead') 110 | : err; 111 | } 112 | } 113 | 114 | for (const container of getContainersForUrlScope(scopeHref)) { 115 | container._registration = context.registration; 116 | container.__serviceWorker__ = context.sw; 117 | 118 | // @ts-ignore 119 | // Create client for container 120 | context.sw.self.clients._connect(container._href, clientPostMessage.bind(null, container)); 121 | } 122 | 123 | return container._registration; 124 | } 125 | 126 | /** 127 | * Unregister a ServiceWorker registration 128 | * @param { string } contextKey 129 | * @returns { Promise } 130 | */ 131 | function unregister(contextKey) { 132 | const context = contexts.get(contextKey); 133 | 134 | if (!context) { 135 | return Promise.resolve(false); 136 | } 137 | 138 | for (const container of getContainersForContext(context)) { 139 | container._destroy(); 140 | } 141 | 142 | context.registration._destroy(); 143 | context.sw._destroy(); 144 | 145 | contexts.delete(contextKey); 146 | 147 | return Promise.resolve(true); 148 | } 149 | 150 | /** 151 | * Send 'message' to client listeners 152 | * @param { ServiceWorkerContainer } container 153 | * @param { unknown } message 154 | * @param { Array } [transferList] 155 | */ 156 | function clientPostMessage(container, message, transferList) { 157 | handle(container, 'message', { data: message, source: container.controller, ports: transferList }); 158 | } 159 | 160 | /** 161 | * Send 'message' to active ServiceWorker 162 | * @param { ServiceWorkerContainer } container 163 | * @param { string } origin 164 | * @param { unknown } message 165 | * @param { Array } [transferList] 166 | * */ 167 | function swPostMessage(container, origin, message, transferList) { 168 | trigger(container, origin, 'message', { data: message, origin, ports: transferList }); 169 | } 170 | 171 | /** 172 | * Trigger 'eventType' in current scope 173 | * @param { ServiceWorkerContainer } container 174 | * @param { string } origin 175 | * @param { string } eventType 176 | * @param { Array } args 177 | * @returns { Promise } 178 | */ 179 | async function trigger(container, origin, eventType, ...args) { 180 | const context = getContextForContainer(container); 181 | 182 | if (!context) { 183 | throw Error('no script registered yet'); 184 | } 185 | 186 | const containers = getContainersForContext(context); 187 | 188 | switch (eventType) { 189 | case 'install': 190 | setState('installing', context, containers); 191 | break; 192 | case 'activate': 193 | setState('activating', context, containers); 194 | break; 195 | case 'fetch': 196 | args.unshift(origin); 197 | break; 198 | default: 199 | // No state mgmt necessary 200 | } 201 | 202 | const result = await handle(context.sw.self, eventType, ...args); 203 | 204 | switch (eventType) { 205 | case 'install': 206 | setState('installed', context, containers); 207 | break; 208 | case 'activate': 209 | setState('activated', context, containers); 210 | break; 211 | default: 212 | // No state mgmt necessary 213 | } 214 | 215 | return result; 216 | } 217 | 218 | /** 219 | * Store 'state' 220 | * @param { string } state 221 | * @param { Context } context 222 | * @param { Array } containers 223 | */ 224 | function setState(state, context, containers) { 225 | switch (state) { 226 | case 'installing': 227 | if (context.sw.state !== 'installing') { 228 | return; 229 | } 230 | context.registration.installing = context.sw; 231 | setControllerForContainers(null, containers); 232 | handle(context.registration, 'updatefound'); 233 | break; 234 | case 'installed': 235 | context.sw.state = state; 236 | context.registration.installing = null; 237 | context.registration.waiting = context.sw; 238 | handle(context.sw, 'statechange'); 239 | break; 240 | case 'activating': 241 | if (!context.sw.state.includes('install')) { 242 | throw Error('ServiceWorker not yet installed'); 243 | } 244 | context.sw.state = state; 245 | context.registration.activating = context.sw; 246 | setControllerForContainers(null, containers); 247 | handle(context.sw, 'statechange'); 248 | break; 249 | case 'activated': 250 | context.sw.state = state; 251 | context.registration.waiting = null; 252 | context.registration.active = context.sw; 253 | setControllerForContainers(context.sw, containers); 254 | handle(context.sw, 'statechange'); 255 | break; 256 | default: 257 | if (context.sw.state !== 'activated') { 258 | throw Error('ServiceWorker not yet active'); 259 | } 260 | } 261 | } 262 | 263 | /** 264 | * Set 'controller' for 'containers' 265 | * @param { ServiceWorker | null } controller 266 | * @param { Array } containers 267 | */ 268 | function setControllerForContainers(controller, containers) { 269 | for (const container of containers) { 270 | container.controller = controller; 271 | } 272 | } 273 | 274 | /** 275 | * Retrieve all containers associated with 'context' 276 | * @param { Context } context 277 | * @returns { Array } 278 | */ 279 | function getContainersForContext(context) { 280 | const results = []; 281 | 282 | for (const container of containers) { 283 | if (container.__serviceWorker__ === context.sw) { 284 | results.push(container); 285 | } 286 | } 287 | 288 | return results; 289 | } 290 | 291 | /** 292 | * Retrieve all containers that fall under 'urlScope' 293 | * @param { string } urlScope 294 | * @returns { Array } 295 | */ 296 | function getContainersForUrlScope(urlScope) { 297 | const results = []; 298 | 299 | for (const container of containers) { 300 | if (container._href.indexOf(urlScope) === 0) { 301 | results.push(container); 302 | } 303 | } 304 | 305 | return results; 306 | } 307 | 308 | /** 309 | * Retrieve context for 'container' 310 | * @param { ServiceWorkerContainer } container 311 | * @returns { Context | undefined } 312 | */ 313 | function getContextForContainer(container) { 314 | for (const context of contexts.values()) { 315 | if (context.sw === container.__serviceWorker__) { 316 | return context; 317 | } 318 | } 319 | } 320 | 321 | /** 322 | * Retrieve origin from 'urlString' 323 | * @param { string } urlString 324 | * @returns { string } 325 | */ 326 | function getOrigin(urlString) { 327 | const parsedUrl = new URL(urlString); 328 | 329 | return `${parsedUrl.protocol}//${parsedUrl.host}`; 330 | } 331 | 332 | /** 333 | * Retrieve the fully resolved path 334 | * @param { string } contextPath 335 | * @param { string } p 336 | * @returns { string } 337 | */ 338 | function getResolvedPath(contextPath, p) { 339 | return isRelativePath(p) ? path.resolve(contextPath, p) : p; 340 | } 341 | 342 | /** 343 | * Determine if 'p' is relative path 344 | * @param { string } p 345 | * @returns { boolean } 346 | */ 347 | function isRelativePath(p) { 348 | return !path.isAbsolute(p); 349 | } 350 | -------------------------------------------------------------------------------- /test/1-caching-test.js: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'node-fetch'; 2 | import Cache from '../src/api/Cache.js'; 3 | import CacheStorage from '../src/api/CacheStorage.js'; 4 | import { expect } from 'chai'; 5 | import { testServer } from 'dvlp/dvlp-test.js'; 6 | 7 | /** @type { Cache } */ 8 | let cache; 9 | /** @type { CacheStorage } */ 10 | let caches; 11 | /** @type { import('dvlp').TestServer } */ 12 | let fake; 13 | 14 | describe('caching', () => { 15 | describe('Cache', () => { 16 | before(() => { 17 | testServer.disableNetwork(); 18 | }); 19 | beforeEach(async () => { 20 | fake = await testServer({ port: 3333 }); 21 | cache = new Cache('test'); 22 | }); 23 | afterEach(() => { 24 | fake.destroy(); 25 | cache._destroy(); 26 | }); 27 | after(() => { 28 | testServer.enableNetwork(); 29 | }); 30 | 31 | describe('put()', () => { 32 | it('should store a request/response', () => { 33 | const req = new Request('http://localhost:3333/foo.js'); 34 | const res = new Response('foo'); 35 | 36 | cache.put(req, res); 37 | expect(cache._items.has(req)).to.equal(true); 38 | expect(cache._items.get(req)).to.equal(res); 39 | }); 40 | it('should overwrite an existing request/response', () => { 41 | const req1 = new Request('http://localhost:3333/foo.js'); 42 | const res1 = new Response('foo1'); 43 | const req2 = new Request('http://localhost:3333/foo.js'); 44 | const res2 = new Response('foo2'); 45 | 46 | cache.put(req1, res1); 47 | cache.put(req2, res2); 48 | expect(cache._items.has(req2)).to.equal(false); 49 | expect(cache._items.has(req1)).to.equal(true); 50 | expect(cache._items.get(req1)).to.equal(res2); 51 | }); 52 | it('should not overwrite an existing request/response if different VARY header'); 53 | }); 54 | 55 | describe('add()', () => { 56 | it('should fetch and store a request url', () => { 57 | fake.mockResponse('/foo', { body: { foo: 'foo' } }); 58 | 59 | return cache.add('http://localhost:3333/foo').then(() => { 60 | expect(cache._items.size).to.equal(1); 61 | }); 62 | }); 63 | it('should fetch and store a request', () => { 64 | fake.mockResponse('/foo', { body: { foo: 'foo' } }); 65 | 66 | const req = new Request('http://localhost:3333/foo'); 67 | 68 | return cache 69 | .add(req) 70 | .then(() => cache._items.get(req).json()) 71 | .then((body) => { 72 | expect(body).to.deep.equal({ foo: 'foo' }); 73 | }); 74 | }); 75 | }); 76 | 77 | describe('addAll()', () => { 78 | it('should fetch and store multiple request urls', () => { 79 | fake.mockResponse('/foo', { body: { foo: 'foo' } }); 80 | fake.mockResponse('/bar', { body: { bar: 'bar' } }); 81 | 82 | return cache.addAll(['http://localhost:3333/foo', 'http://localhost:3333/bar']).then(() => { 83 | expect(cache._items.size).to.equal(2); 84 | }); 85 | }); 86 | it('should fetch and store multiple requests', () => { 87 | fake.mockResponse('/foo', { body: { foo: 'foo' } }); 88 | fake.mockResponse('/bar', { body: { bar: 'bar' } }); 89 | 90 | const req1 = new Request('http://localhost:3333/foo'); 91 | const req2 = new Request('http://localhost:3333/bar'); 92 | 93 | return cache 94 | .addAll([req1, req2]) 95 | .then(() => Promise.all([cache._items.get(req1).json(), cache._items.get(req2).json()])) 96 | .then((bodies) => { 97 | expect(bodies[0]).to.deep.equal({ foo: 'foo' }); 98 | expect(bodies[1]).to.deep.equal({ bar: 'bar' }); 99 | }); 100 | }); 101 | }); 102 | 103 | describe('match()', () => { 104 | it('should resolve with "undefined" if no match', () => { 105 | return cache.match(new Request('http://localhost:3333/foo.js')).then((response) => { 106 | expect(response).to.equal(undefined); 107 | }); 108 | }); 109 | it('should retrieve matching response', () => { 110 | const req = new Request('http://localhost:3333/foo.js'); 111 | const res = new Response('foo'); 112 | 113 | cache.put(req, res); 114 | return cache.match(req).then((response) => { 115 | expect(response).to.equal(res); 116 | }); 117 | }); 118 | it('should retrieve matching response when passed string request', () => { 119 | const req = new Request('http://localhost:3333/foo.js'); 120 | const res = new Response('foo'); 121 | 122 | cache.put(req, res); 123 | return cache.match('foo.js').then((response) => { 124 | expect(response).to.equal(res); 125 | }); 126 | }); 127 | it('should retrieve matching response, ignoring search query', () => { 128 | const req = new Request('http://localhost:3333/foo.js?q=foo'); 129 | const res = new Response('foo'); 130 | 131 | cache.put(req, res); 132 | return cache.match(new Request('http://localhost:3333/foo.js'), { ignoreSearch: true }).then((response) => { 133 | expect(response).to.equal(res); 134 | }); 135 | }); 136 | it('should retrieve matching response for fully qualified string request, ignoring search query', () => { 137 | const req = new Request('http://localhost:3333/foo.js?q=foo'); 138 | const res = new Response('foo'); 139 | 140 | cache.put(req, res); 141 | return cache.match('http://localhost:3333/foo.js', { ignoreSearch: true }).then((response) => { 142 | expect(response).to.equal(res); 143 | }); 144 | }); 145 | it('should retrieve matching response, ignoring method'); 146 | it('should retrieve matching response, ignoring VARY header'); 147 | }); 148 | 149 | describe('matchAll()', () => { 150 | it('should resolve with empty array if no match', () => { 151 | return cache.matchAll('http://localhost:3333/foo.js').then((responses) => { 152 | expect(responses).to.deep.equal([]); 153 | }); 154 | }); 155 | it('should retrieve matching responses', () => { 156 | const req = new Request('http://localhost:3333/foo.js'); 157 | const res = new Response('foo'); 158 | 159 | cache.put(req, res); 160 | return cache.matchAll(req).then((responses) => { 161 | expect(responses[0]).to.equal(res); 162 | }); 163 | }); 164 | it('should retrieve matching responses, ignoring search query', () => { 165 | const req1 = new Request('http://localhost:3333/foo.js?q=foo'); 166 | const res1 = new Response('foo'); 167 | const req2 = new Request('http://localhost:3333/foo.js?q=bar'); 168 | const res2 = new Response('bar'); 169 | 170 | cache.put(req1, res1); 171 | cache.put(req2, res2); 172 | return cache.matchAll(req1, { ignoreSearch: true }).then((responses) => { 173 | expect(responses).to.have.length(2); 174 | expect(responses[0]).to.equal(res1); 175 | expect(responses[1]).to.equal(res2); 176 | }); 177 | }); 178 | it('should retrieve matching responses, ignoring method'); 179 | it('should retrieve matching responses, ignoring VARY header'); 180 | }); 181 | 182 | describe('delete()', () => { 183 | it('should resolve with "false" if no match', () => { 184 | return cache.delete(new Request('http://localhost:3333/foo.js')).then((success) => { 185 | expect(success).to.equal(false); 186 | }); 187 | }); 188 | it('should remove matching request', () => { 189 | const req = new Request('http://localhost:3333/foo.js'); 190 | const res = new Response('foo'); 191 | 192 | cache.put(req, res); 193 | return cache.delete(req).then((success) => { 194 | expect(success).to.equal(true); 195 | expect(cache._items.size).to.equal(0); 196 | }); 197 | }); 198 | it('should remove matching requests, ignoring search query', () => { 199 | const req1 = new Request('http://localhost:3333/foo.js?q=foo'); 200 | const res1 = new Response('foo'); 201 | const req2 = new Request('http://localhost:3333/foo.js?q=bar'); 202 | const res2 = new Response('bar'); 203 | 204 | cache.put(req1, res1); 205 | cache.put(req2, res2); 206 | return cache.delete(req1, { ignoreSearch: true }).then((success) => { 207 | expect(success).to.equal(true); 208 | expect(cache._items.size).to.equal(0); 209 | }); 210 | }); 211 | it('should remove matching requests, ignoring method'); 212 | it('should remove matching requests, ignoring VARY header'); 213 | }); 214 | 215 | describe('keys()', () => { 216 | it('should resolve with empty array if no cached items', () => { 217 | return cache.keys().then((results) => { 218 | expect(results).to.deep.equal([]); 219 | }); 220 | }); 221 | it('should resolve with all keys', () => { 222 | const req = new Request('http://localhost:3333/foo.js'); 223 | const res = new Response('foo'); 224 | 225 | cache.put(req, res); 226 | return cache.keys().then((results) => { 227 | expect(results).to.deep.equal([req]); 228 | }); 229 | }); 230 | it('should resolve with keys matching passed request', () => { 231 | const req = new Request('http://localhost:3333/foo.js'); 232 | const res = new Response('foo'); 233 | 234 | cache.put(req, res); 235 | return cache.keys(req).then((results) => { 236 | expect(results).to.deep.equal([req]); 237 | }); 238 | }); 239 | it('should resolve with keys matching passed request, ignoring search query', () => { 240 | const req1 = new Request('http://localhost:3333/foo.js?q=foo'); 241 | const res1 = new Response('foo'); 242 | const req2 = new Request('http://localhost:3333/foo.js?q=bar'); 243 | const res2 = new Response('bar'); 244 | 245 | cache.put(req1, res1); 246 | cache.put(req2, res2); 247 | return cache.keys(req1, { ignoreSearch: true }).then((results) => { 248 | expect(results).to.deep.equal([req1, req2]); 249 | }); 250 | }); 251 | it('should resolve with keys matching passed request, ignoring method'); 252 | it('should resolve with keys matching passed request, ignoring VARY header'); 253 | }); 254 | }); 255 | 256 | describe('CacheStorage', () => { 257 | beforeEach(() => { 258 | caches = new CacheStorage('http://localhost:3333/'); 259 | }); 260 | afterEach(() => { 261 | caches._destroy(); 262 | }); 263 | 264 | describe('open()', () => { 265 | it("should create new cache instance if it doesn't exist", () => { 266 | return caches.open('foo').then((cache) => { 267 | expect(cache).to.have.property('name', 'foo'); 268 | expect(caches._caches.size).to.equal(1); 269 | }); 270 | }); 271 | it('should return existing cache instance', () => { 272 | return caches 273 | .open('foo') 274 | .then((cache) => caches.open('foo')) 275 | .then((cache) => { 276 | expect(cache).to.have.property('name', 'foo'); 277 | expect(caches._caches.size).to.equal(1); 278 | }); 279 | }); 280 | }); 281 | 282 | describe('match()', () => { 283 | it('should resolve with "undefined" if no match', () => { 284 | return caches.match(new Request('http://localhost:3333/foo.js')).then((response) => { 285 | expect(response).to.equal(undefined); 286 | }); 287 | }); 288 | it('should resolve with response if match', () => { 289 | const req = new Request('http://localhost:3333/foo.js'); 290 | const res = new Response('foo'); 291 | 292 | return caches 293 | .open('foo') 294 | .then((cache) => cache.put(req, res)) 295 | .then(() => caches.match(req)) 296 | .then((response) => { 297 | expect(response).to.equal(res); 298 | }); 299 | }); 300 | it('should resolve with response if match and "options.cacheName"', () => { 301 | const req = new Request('http://localhost:3333/foo.js'); 302 | const res = new Response('foo'); 303 | 304 | return caches 305 | .open('foo') 306 | .then((cache) => cache.put(req, res)) 307 | .then(() => caches.match(req, { cacheName: 'foo' })) 308 | .then((response) => { 309 | expect(response).to.equal(res); 310 | }); 311 | }); 312 | it('should reject if passed "options.cacheName" doesn\'t exist', (done) => { 313 | caches.match(new Request('http://localhost:3333/foo.js'), { cacheName: 'foo' }).catch((err) => { 314 | expect(err.message).to.equal("cache with name 'foo' not found"); 315 | done(); 316 | }); 317 | }); 318 | }); 319 | 320 | describe('has()', () => { 321 | it('should resolve with "false" if cache doesn\'t exist', () => { 322 | return caches.has('foo').then((success) => { 323 | expect(success).to.equal(false); 324 | }); 325 | }); 326 | it('should resolve with "true" if cache exists', () => { 327 | return caches 328 | .open('foo') 329 | .then((cache) => caches.has('foo')) 330 | .then((success) => { 331 | expect(success).to.equal(true); 332 | }); 333 | }); 334 | }); 335 | 336 | describe('keys()', () => { 337 | it('should resolve with an empty array if no caches', () => { 338 | return caches.keys().then((keys) => { 339 | expect(keys).to.deep.equal([]); 340 | }); 341 | }); 342 | it('should resolve with an array of cache keys', () => { 343 | return caches 344 | .open('foo') 345 | .then((cache) => caches.keys()) 346 | .then((keys) => { 347 | expect(keys).to.deep.equal(['foo']); 348 | }); 349 | }); 350 | }); 351 | 352 | describe('delete()', () => { 353 | it('should resolve with "false" if no caches', () => { 354 | return caches.delete('foo').then((success) => { 355 | expect(success).to.equal(false); 356 | }); 357 | }); 358 | it('should resolve with "true" if successfully removed', () => { 359 | return caches 360 | .open('foo') 361 | .then((cache) => caches.delete('foo')) 362 | .then((success) => { 363 | expect(success).to.equal(true); 364 | }); 365 | }); 366 | }); 367 | }); 368 | }); 369 | -------------------------------------------------------------------------------- /test/2-clients-test.js: -------------------------------------------------------------------------------- 1 | import Client from '../src/api/Client.js'; 2 | import Clients from '../src/api/Clients.js'; 3 | import { expect } from 'chai'; 4 | 5 | describe('clients', () => { 6 | describe('Client', () => { 7 | describe('constructor()', () => { 8 | it('should add url property using value from argument', () => { 9 | const expected = 'https://somewhere.com/in/time'; 10 | const actual = new Client(expected); 11 | 12 | expect(actual.url).to.equal(expected); 13 | }); 14 | it('should generate an id for the created object', () => { 15 | const actual = new Client('https://7thson.com/7thson'); 16 | 17 | expect(actual).to.have.ownProperty('id'); 18 | }); 19 | }); 20 | }); 21 | 22 | describe('Clients', () => { 23 | /** @type { Clients } */ 24 | let clients; 25 | 26 | beforeEach(() => { 27 | clients = new Clients(); 28 | }); 29 | 30 | describe('openWindow()', () => { 31 | it('should return a Promise that resolves to a Client object', () => { 32 | const actual = clients.openWindow('whatever'); 33 | 34 | expect(actual).to.be.a('promise'); 35 | return actual.then((client) => { 36 | expect(client).to.be.an.instanceof(Client); 37 | }); 38 | }); 39 | describe('returned Client', () => { 40 | it('should be created with url from openWindow argument', () => { 41 | const expected = 'https://whatever.com:8000/lol'; 42 | 43 | return clients.openWindow(expected).then((client) => { 44 | expect(client.url).to.equal(expected); 45 | }); 46 | }); 47 | }); 48 | }); 49 | 50 | describe('get()', () => { 51 | it('should return a Promise resolving to the Client with the given id', () => { 52 | return clients.openWindow('https://xfactor.com/').then((expected) => { 53 | return clients.get(expected.id).then((actual) => { 54 | expect(actual).to.equal(expected); 55 | }); 56 | }); 57 | }); 58 | 59 | it('should return a Promise resolving to undefined if a Client with the given id cant be found', () => { 60 | return clients.get('lol').then((actual) => { 61 | expect(actual).to.be.an('undefined'); 62 | }); 63 | }); 64 | }); 65 | 66 | describe('claim()', () => { 67 | it('should return a Promise', () => { 68 | const actual = clients.claim(); 69 | 70 | expect(actual).to.be.a('promise'); 71 | }); 72 | }); 73 | 74 | describe('matchAll()', () => { 75 | it('should return a Promise', () => { 76 | const actual = clients.matchAll(); 77 | 78 | expect(actual).to.be.a('promise'); 79 | }); 80 | it('Promise should resolve to an array containing the clients controlled by the current service worker', () => { 81 | return clients.openWindow('https://powerslave.com').then((expected) => { 82 | return clients.matchAll().then((clients) => { 83 | expect(clients).to.be.an('array'); 84 | expect(clients).to.include(expected); 85 | }); 86 | }); 87 | }); 88 | it('should care about the options parameter'); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /test/3-index-test.js: -------------------------------------------------------------------------------- 1 | import { connect, destroy } from '../sw-test-env.js'; 2 | import { expect } from 'chai'; 3 | import { testServer } from 'dvlp/dvlp-test.js'; 4 | 5 | /** @type { import('dvlp').TestServer } */ 6 | let fake; 7 | /** @type { MockServiceWorkerContainer } */ 8 | let sw; 9 | 10 | describe('sw-test-env', () => { 11 | before(() => { 12 | testServer.disableNetwork(); 13 | }); 14 | beforeEach(async () => { 15 | fake = await testServer({ autorespond: true, latency: 0, port: 3333 }); 16 | sw = await connect('http://localhost:3333', 'test/fixtures'); 17 | }); 18 | afterEach(async () => { 19 | await fake.destroy(); 20 | await destroy(); 21 | }); 22 | after(() => { 23 | testServer.enableNetwork(); 24 | }); 25 | 26 | describe('register()', () => { 27 | it('should execute script path in ServiceWorker context', async () => { 28 | const registration = await sw.register('sw-empty.js'); 29 | expect(registration.scope).to.equal('http://localhost:3333/'); 30 | expect(sw.__serviceWorker__.self.caches).to.exist; 31 | }); 32 | it('should define "location" object in ServiceWorker context', async () => { 33 | await sw.register('sw-empty.js'); 34 | expect(sw.__serviceWorker__.self.location).to.have.property('href', 'http://localhost:3333/sw-empty.js'); 35 | }); 36 | it('should not allow "importScripts()"', async () => { 37 | try { 38 | await sw.register('sw-import-scripts.js'); 39 | expect(true).to.be.false; 40 | } catch (err) { 41 | expect(err).to.exist; 42 | expect(err).to.have.property( 43 | 'message', 44 | '"importScripts" not supported in esm ServiceWorker. Use esm "import" statement instead', 45 | ); 46 | } 47 | }); 48 | }); 49 | 50 | describe('unregister()', () => { 51 | it('should unregister a registered ServiceWorker context', async () => { 52 | const registration = await sw.register('sw-empty.js'); 53 | const success = await registration.unregister(); 54 | expect(success).to.equal(true); 55 | expect(sw.controller).to.equal(undefined); 56 | }); 57 | }); 58 | 59 | describe('trigger()', () => { 60 | it('should trigger an install event', async () => { 61 | await sw.register('sw-empty.js'); 62 | expect(sw.__serviceWorker__.state).to.equal('installing'); 63 | await sw.trigger('install'); 64 | expect(sw.__serviceWorker__.state).to.equal('installed'); 65 | expect(sw.controller).to.equal(null); 66 | }); 67 | it('should trigger an install event and emit `updatefound` and `statechange` events', (done) => { 68 | sw.register('sw-empty.js').then((registration) => { 69 | registration.addEventListener('updatefound', () => { 70 | registration.installing?.addEventListener('statechange', (event) => { 71 | expect(event.target).to.equal(sw.__serviceWorker__); 72 | expect(registration.waiting).to.equal(sw.__serviceWorker__); 73 | done(); 74 | }); 75 | }); 76 | sw.trigger('install'); 77 | }); 78 | }); 79 | it('should trigger an activate event', async () => { 80 | await sw.register('sw-empty.js'); 81 | await sw.trigger('install'); 82 | expect(sw.__serviceWorker__.state).to.equal('installed'); 83 | await sw.trigger('activate'); 84 | expect(sw.controller).to.have.property('state', 'activated'); 85 | }); 86 | it('should throw when invalid state while triggering an event', async () => { 87 | await sw.register('sw-empty.js'); 88 | try { 89 | await sw.trigger('fetch'); 90 | } catch (err) { 91 | expect(err).to.to.have.property('message', 'ServiceWorker not yet active'); 92 | } 93 | }); 94 | it('should trigger a ServiceWorker event handler', async () => { 95 | await sw.register('sw-install.js'); 96 | await sw.trigger('install'); 97 | expect(sw.__serviceWorker__.self.installed).to.equal(true); 98 | }); 99 | it('should trigger a ServiceWorker fetch handler', async () => { 100 | await sw.register('sw-fetch.js'); 101 | await sw.trigger('install'); 102 | await sw.trigger('activate'); 103 | const response = await sw.trigger('fetch', { request: '/index.js' }); 104 | expect(response.status).to.equal(200); 105 | }); 106 | it('should trigger a ServiceWorker on* handler', async () => { 107 | await sw.register('sw-oninstall.js'); 108 | await sw.trigger('install'); 109 | expect(sw.__serviceWorker__.self.installed).to.equal(true); 110 | }); 111 | it('should trigger a ServiceWorker event handler with waitUntil()', async () => { 112 | await sw.register('sw-install-wait-until.js'); 113 | await sw.trigger('install'); 114 | expect(sw.__serviceWorker__.self.installed).to.equal(true); 115 | }); 116 | it('should trigger a handled error event', async () => { 117 | await sw.register('sw-error.js'); 118 | const err = Error('ooops!'); 119 | const resolvedErr = await sw.trigger('error', err); 120 | expect(sw.__serviceWorker__.self.message).to.equal('ooops!'); 121 | expect(err).to.equal(resolvedErr); 122 | }); 123 | it('should trigger an unhandled error event', async () => { 124 | await sw.register('sw-empty.js'); 125 | try { 126 | await sw.trigger('error', Error('ooops!')); 127 | expect(true).to.not.exist; 128 | } catch (err) { 129 | expect(err).to.have.property('message', 'ooops!'); 130 | } 131 | }); 132 | }); 133 | 134 | describe('ready', () => { 135 | it('should execute install/activate lifecyle', async () => { 136 | await sw.register('sw-install-activate.js'); 137 | await sw.ready; 138 | expect(sw.__serviceWorker__.state).to.equal('activated'); 139 | expect(sw.__serviceWorker__.self.installed).to.equal(true); 140 | expect(sw.__serviceWorker__.self.activated).to.equal(true); 141 | }); 142 | it('should ignore existing install/activate lifecyle', async () => { 143 | await sw.register('sw-install-activate.js'); 144 | await sw.trigger('install'); 145 | await sw.ready; 146 | expect(sw.__serviceWorker__.state).to.equal('activated'); 147 | expect(sw.__serviceWorker__.self.installed).to.equal(true); 148 | expect(sw.__serviceWorker__.self.activated).to.equal(true); 149 | }); 150 | it('should execute install/activate lifecyle for multiple connected pages', async () => { 151 | const sw2 = await connect(); 152 | await sw.register('sw-install-activate.js'); 153 | await sw.ready; 154 | expect(sw.__serviceWorker__.state).to.equal('activated'); 155 | expect(sw2.__serviceWorker__.state).to.equal('activated'); 156 | expect(sw.__serviceWorker__.self.installed).to.equal(true); 157 | expect(sw2.__serviceWorker__.self.activated).to.equal(true); 158 | expect(await sw.__serviceWorker__.self.clients.matchAll()).to.have.length(2); 159 | }); 160 | it('should install and cache assets', async () => { 161 | await sw.register('sw-install-precache-activate.js'); 162 | await sw.ready; 163 | expect(sw.__serviceWorker__.state).to.equal('activated'); 164 | // @ts-ignore 165 | const urls = (await (await sw.__serviceWorker__.self.caches.open('v1')).keys()).map((req) => req.url); 166 | expect(urls).to.have.length(2); 167 | expect(urls).to.include('http://localhost:3333/index.css'); 168 | expect(urls).to.include('http://localhost:3333/index.js'); 169 | }); 170 | }); 171 | }); 172 | -------------------------------------------------------------------------------- /test/4-messaging-test.js: -------------------------------------------------------------------------------- 1 | import { connect, destroy, MessageChannel } from '../sw-test-env.js'; 2 | import { expect } from 'chai'; 3 | 4 | /** @type { MockServiceWorkerContainer } */ 5 | let sw; 6 | 7 | describe('messaging', () => { 8 | beforeEach(async () => { 9 | sw = await connect('http://localhost:3333', 'test/fixtures'); 10 | }); 11 | afterEach(() => { 12 | destroy(); 13 | }); 14 | 15 | describe('unicast', () => { 16 | it('should send client message to ServiceWorker', async () => { 17 | await sw.register('sw-client-message.js'); 18 | await sw.ready; 19 | sw.controller?.postMessage({ foo: 'foo' }); 20 | expect(sw.controller?.self.message).to.deep.equal({ foo: 'foo' }); 21 | }); 22 | it('should send ServiceWorker reply to client', (done) => { 23 | sw.register('sw-message.js') 24 | .then(() => sw.ready) 25 | .then(() => { 26 | const mc = new MessageChannel(); 27 | mc.port1.addEventListener('message', (evt) => { 28 | expect(/** @type { MessageEvent } */ (evt).data).to.deep.equal({ foo: 'bar' }); 29 | done(); 30 | }); 31 | sw.controller?.postMessage({ foo: 'foo' }, [mc.port2]); 32 | }); 33 | }); 34 | it('should send ServiceWorker reply to client with onmessage', (done) => { 35 | sw.register('sw-message.js') 36 | .then(() => sw.ready) 37 | .then(() => { 38 | const mc = new MessageChannel(); 39 | mc.port1.onmessage = (evt) => { 40 | expect(evt.data).to.deep.equal({ foo: 'bar' }); 41 | done(); 42 | }; 43 | sw.controller?.postMessage({ foo: 'foo' }, [mc.port2]); 44 | }); 45 | }); 46 | }); 47 | 48 | describe('broadcast', () => { 49 | it('should send message to all connected clients', (done) => { 50 | const data = { foo: 'foo' }; 51 | let count = 0; 52 | /** @type { MockServiceWorkerContainer } */ 53 | let sw2; 54 | 55 | connect('http://localhost:3333', 'test/fixtures') 56 | .then((s) => { 57 | sw2 = s; 58 | return sw.register('sw-empty.js'); 59 | }) 60 | .then(() => sw.ready) 61 | .then(() => { 62 | sw.addEventListener('message', (evt) => { 63 | const { data, source } = /** @type { MockMessageEvent } */ (evt); 64 | count++; 65 | expect(data).to.equal(data); 66 | expect(source).to.equal(sw.controller); 67 | expect(count).to.equal(1); 68 | }); 69 | sw2.onmessage = (evt) => { 70 | const { data, source } = /** @type { MockMessageEvent } */ (evt); 71 | count++; 72 | expect(data).to.equal(data); 73 | expect(source).to.equal(sw2.controller); 74 | expect(count).to.equal(2); 75 | done(); 76 | }; 77 | sw.controller?.self.clients.matchAll().then((all) => { 78 | all.forEach((client) => client.postMessage(data)); 79 | }); 80 | }); 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/fixtures/sw-client-message.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | self.addEventListener('message', (evt) => { 4 | self.message = evt.data; 5 | }); 6 | -------------------------------------------------------------------------------- /test/fixtures/sw-empty.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeindustries/sw-test-env/614d1dff5cd57f3349dd60f6686e6e92f7fbb0f0/test/fixtures/sw-empty.js -------------------------------------------------------------------------------- /test/fixtures/sw-error.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | self.onerror = (evt) => { 4 | self.message = evt.message; 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/sw-fetch.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | self.addEventListener('fetch', (evt) => { 3 | evt.respondWith(fetch(evt.request)); 4 | }); 5 | -------------------------------------------------------------------------------- /test/fixtures/sw-import-scripts.js: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | self.importScripts('./bar.js'); 3 | -------------------------------------------------------------------------------- /test/fixtures/sw-install-activate.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | self.addEventListener('install', (evt) => { 4 | evt.waitUntil( 5 | self.skipWaiting().then(() => { 6 | self.installed = true; 7 | }), 8 | ); 9 | }); 10 | 11 | self.addEventListener('activate', (evt) => { 12 | evt.waitUntil( 13 | self.clients.claim().then(() => { 14 | self.activated = true; 15 | }), 16 | ); 17 | }); 18 | -------------------------------------------------------------------------------- /test/fixtures/sw-install-precache-activate.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | self.addEventListener('install', (evt) => { 4 | evt.waitUntil( 5 | self.caches 6 | .open('v1') 7 | .then((cache) => { 8 | return cache.addAll(['/index.js', '/index.css']); 9 | }) 10 | .then(() => self.skipWaiting()), 11 | ); 12 | }); 13 | 14 | self.addEventListener('activate', (evt) => { 15 | evt.waitUntil(self.clients.claim()); 16 | }); 17 | -------------------------------------------------------------------------------- /test/fixtures/sw-install-wait-until.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | self.addEventListener('install', (evt) => 4 | evt.waitUntil( 5 | new Promise((resolve) => { 6 | self.installed = true; 7 | resolve(); 8 | }), 9 | ), 10 | ); 11 | -------------------------------------------------------------------------------- /test/fixtures/sw-install.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | self.addEventListener('install', (evt) => { 4 | evt.waitUntil( 5 | self.skipWaiting().then(() => { 6 | self.installed = true; 7 | }), 8 | ); 9 | }); 10 | -------------------------------------------------------------------------------- /test/fixtures/sw-message.js: -------------------------------------------------------------------------------- 1 | self.addEventListener('message', (evt) => { 2 | evt.ports[0].postMessage({ foo: 'bar' }); 3 | }); 4 | -------------------------------------------------------------------------------- /test/fixtures/sw-oninstall.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | self.oninstall = (evt) => { 4 | evt.waitUntil( 5 | self.skipWaiting().then(() => { 6 | self.installed = true; 7 | }), 8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "checkJs": true, 6 | "declaration": false, 7 | "isolatedModules": true, 8 | "lib": ["esnext"], 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "noEmit": true, 12 | "strict": true, 13 | "target": "esnext", 14 | "useDefineForClassFields": true 15 | }, 16 | "include": ["src", "test"], 17 | "exclude": ["node_modules"] 18 | } 19 | --------------------------------------------------------------------------------