├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── playwright.config.ts ├── src ├── app.d.ts ├── app.html ├── hooks.server.ts ├── lib │ ├── core.ts │ ├── crypto.ts │ ├── handle.ts │ ├── index.ts │ ├── types.ts │ └── utils.ts └── routes │ ├── +layout.server.ts │ ├── +page.svelte │ └── tests │ ├── _utils.ts │ ├── benchmark │ ├── +server.ts │ ├── _benchmark.ts │ └── get-session │ │ └── +server.ts │ ├── binary-secret │ └── +server.ts │ ├── chunked-session │ ├── +page.server.ts │ ├── +page.svelte │ ├── delete │ │ ├── +page.server.ts │ │ └── +page.svelte │ └── update │ │ ├── +page.server.ts │ │ └── +page.svelte │ ├── destroy-session │ ├── +page.server.ts │ └── +page.svelte │ ├── handles-special-chars │ ├── +page.server.ts │ └── +page.svelte │ ├── password-rotation │ └── +server.ts │ ├── refresh-session │ └── +server.ts │ ├── rolling │ ├── +server.ts │ └── percentage │ │ └── +server.ts │ ├── save-uninitialized │ └── +server.ts │ ├── set-session │ ├── +page.server.ts │ ├── +page.svelte │ └── expires-in-configurable │ │ └── +server.ts │ ├── sync-session │ ├── +page.server.ts │ └── +page.svelte │ ├── update-session │ ├── +page.server.ts │ ├── +page.svelte │ └── keep-expiry │ │ └── +server.ts │ └── wrong-secret │ └── +server.ts ├── static └── favicon.png ├── svelte.config.js ├── tests └── test.ts ├── tsconfig.json └── vite.config.js /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | env: 9 | CI: true 10 | TEST: true 11 | PLAYWRIGHT_BROWSERS_PATH: 0 12 | 13 | jobs: 14 | Check: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/setup-node@v2 19 | with: 20 | node-version: '16' 21 | - run: npm install 22 | - run: npm run check 23 | Tests: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | - uses: actions/setup-node@v2 28 | with: 29 | node-version: '16' 30 | 31 | - name: Cache node modules 32 | id: playwright-cache 33 | uses: actions/cache@v2 34 | with: 35 | path: node_modules 36 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 37 | restore-keys: ${{ runner.os }}-node- 38 | 39 | - name: Install dependencies 40 | if: steps.playwright-cache.outputs.cache-hit != 'true' 41 | run: npm ci && npx playwright install 42 | 43 | - name: Run tests 44 | run: npm run test 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | dist -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 [pixelmund](https://github.com/pixelmund) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Svelte Kit Cookie Session [![License](https://img.shields.io/github/license/pixelmund/svelte-kit-cookie-session.svg)](https://github.com/pixelmund/svelte-kit-cookie-session) [![Latest Stable Version](https://img.shields.io/npm/v/svelte-kit-cookie-session.svg)](https://www.npmjs.com/package/svelte-kit-cookie-session) 2 | 3 | ⚒️ Encrypted "stateless" cookie sessions for SvelteKit 4 | 5 | --- 6 | 7 | **This [SvelteKit](https://kit.svelte.dev) backend utility** allows you to create a session to be stored in the browser cookies via an encrypted seal. This provides strong client/"stateless" sessions. 8 | 9 | The seal stored on the client contains the session data, not your server, making it a "stateless" session from the server point of view. This is a different take than `express-session` where the cookie contains a session ID to then be used to map data on the server-side. 10 | 11 | --- 12 | 13 | ## 📚  Table of Contents 14 | 15 | 1. [Upgrading](#upgrading) 16 | 1. [Installation](#installation) 17 | 1. [Usage](#usage) 18 | 1. [Initializing](#initializing) 19 | 1. [Secret Rotation](#secret-rotation) 20 | 1. [Setting the Session](#setting-the-session) 21 | 1. [Accessing the Session](#accessing-the-session) 22 | 1. [Destroying the Session](#destroying-the-session) 23 | 1. [Refreshing the Session](#refresh-the-session-with-the-same-data-but-renew-the-expiration-date) 24 | 1. [Configure Expiry Date](#configure-expiry-date) 25 | 1. [Save unsaved session with the initial data](#save-unsaved-session-with-the-initial-data) 26 | 27 | **By default the cookie has an ⏰ expiration time of 7 days**, set via [`expires`] which should be a `number` in either `days | hours | minutes | seconds` configurable by the `expires_in` option. 28 | 29 | ## Upgrading 30 | 31 | #### Version 3.x to 4.x 32 | 33 | The internal encryption library changed to the [@noble/ciphers](https://github.com/paulmillr/noble-ciphers) which is up to 35% faster than the previous implementation. The encryption should also now be even more secure. 34 | Because of the change of the encryption library we have an major version bump. You now have to provide a secret with an exact length of 32 characters or bytes. You can use [Password Generator](https://1password.com/password-generator/) to generate strong secrets. 35 | 36 | ## Installation 37 | 38 | Install into `dependencies` 39 | 40 | ```bash 41 | npm i svelte-kit-cookie-session 42 | 43 | yarn add svelte-kit-cookie-session 44 | 45 | pnpm add svelte-kit-cookie-session 46 | ``` 47 | 48 | Update your `app.d.ts` file to look something like: 49 | 50 | ```ts 51 | import type { Session } from 'svelte-kit-cookie-session'; 52 | 53 | type SessionData = { 54 | views: number; 55 | }; 56 | 57 | // See https://kit.svelte.dev/docs/types#app 58 | // for information about these interfaces 59 | declare global { 60 | namespace App { 61 | // interface Error {} 62 | interface Locals { 63 | session: Session; 64 | } 65 | interface PageData { 66 | // can add any properties here, return it from your root layout 67 | session: SessionData; 68 | } 69 | // interface Platform {} 70 | } 71 | } 72 | 73 | export {}; 74 | ``` 75 | 76 | ## Usage 77 | 78 | You can find some examples in the src/routes/tests folder [Tests](/src/routes/tests). 79 | 80 | The secret is a private key or list of private keys you must pass at runtime, it should be `32 characters` long. Use [Password Generator](https://1password.com/password-generator/) to generate strong secrets. 81 | 82 | ⚠️ You should always store secrets in secret environment variables on your platform. 83 | 84 | ### Initializing 85 | 86 | > src/hooks.server.ts 87 | 88 | ```js 89 | import { handleSession } from 'svelte-kit-cookie-session'; 90 | 91 | // You can do it like this, without passing a own handle function 92 | export const handle = handleSession({ 93 | // Optional initial state of the session, default is an empty object {} 94 | // init: (event) => ({ 95 | // views: 0 96 | // }), 97 | // chunked: true // Optional, default is false - if true, the session will be chunked into multiple cookies avoiding the browser limit for cookies 98 | secret: 'SOME_COMPLEX_SECRET_32_CHARSLONG' 99 | }); 100 | 101 | // Or pass your handle function as second argument to handleSession 102 | 103 | export const handle = handleSession( 104 | { 105 | secret: 'SOME_COMPLEX_SECRET_32_CHARSLONG' 106 | }, 107 | ({ event, resolve }) => { 108 | // event.locals is populated with the session `event.locals.session` 109 | 110 | // Do anything you want here 111 | return resolve(event); 112 | } 113 | ); 114 | ``` 115 | 116 | In case you're using [sequence()](https://kit.svelte.dev/docs/modules#sveltejs-kit-hooks-sequence), do this 117 | 118 | ```js 119 | const sessionHandler = handleSession({ 120 | secret: 'SOME_COMPLEX_SECRET_32_CHARSLONG' 121 | }); 122 | export const handle = sequence(sessionHandler, ({ resolve, event }) => { 123 | // event.locals is populated with the session `event.locals.session` 124 | // event.locals is also populated with all parsed cookies by handleSession, it would cause overhead to parse them again - `event.locals.cookies`. 125 | // Do anything you want here 126 | return resolve(event); 127 | }); 128 | ``` 129 | 130 | ### Secret rotation 131 | 132 | is supported. It allows you to change the secret used to sign and encrypt sessions while still being able to decrypt sessions that were created with a previous secret. 133 | 134 | This is useful if you want to: 135 | 136 | - rotate secrets for better security every two (or more, or less) weeks 137 | - change the secret you previously used because it leaked somewhere (😱) 138 | 139 | Then you can use multiple secrets: 140 | 141 | **Week 1**: 142 | 143 | ```js 144 | export const handle = handleSession({ 145 | secret: 'SOME_COMPLEX_SECRET_32_CHARSLONG' 146 | }); 147 | ``` 148 | 149 | **Week 2**: 150 | 151 | ```js 152 | export const handle = handleSession({ 153 | secret: [ 154 | { 155 | id: 2, 156 | secret: 'SOME_OTHER_COMPLEX_SECR_32_CHARS' 157 | }, 158 | { 159 | id: 1, 160 | secret: 'SOME_COMPLEX_SECRET_32_CHARSLONG' 161 | } 162 | ] 163 | }); 164 | ``` 165 | 166 | Notes: 167 | 168 | - `id` is required so that we do not have to try every secret in the list when decrypting (the `id` is part of the cookies value). 169 | - The secret used to encrypt session data is always the first one in the array, so when rotating to put a new secret, it must be first in the array list 170 | - Even if you do not provide an array at first, you can always move to array based secret afterwards, knowing that your first password (`string`) was given `{id:1}` automatically. 171 | 172 | ### Setting The Session 173 | 174 | Setting the session can be done in two ways, either via the `set` method or via the `update` method. 175 | 176 | `If the session already exists, the data gets updated but the expiration time stays the same` 177 | 178 | > src/routes/counter/+page.server.js 179 | 180 | ```js 181 | /** @type {import('@sveltejs/kit').Actions} */ 182 | export const actions = { 183 | default: async ({ locals }) => { 184 | const { counter = 0 } = locals.session.data; 185 | 186 | await locals.session.set({ counter: counter + 1 }); 187 | 188 | return {}; 189 | } 190 | }; 191 | ``` 192 | 193 | `Sometimes you don't want to get the session data first only to increment a counter or some other value, that's where the update method comes in to play.` 194 | 195 | > src/routes/counter/+page.server.ts 196 | 197 | ```js 198 | /** @type {import('@sveltejs/kit').Actions} */ 199 | export const actions = { 200 | default: async ({ locals, request }) => { 201 | await locals.session.update(({ count }) => ({ count: count ? count + 1 : 0 })); 202 | return {}; 203 | } 204 | }; 205 | ``` 206 | 207 | ### Accessing The Session 208 | 209 | `After initializing the session, your locals will be filled with a session object, we automatically set the cookie if you set the session via locals.session.set({}) to something and receive the current data via locals.session.data only.` 210 | 211 | > src/routes/+layout.server.js 212 | 213 | ```js 214 | /** @type {import('@sveltejs/kit').LayoutServerLoad} */ 215 | export function load({ locals, request }) { 216 | return { 217 | session: locals.session.data 218 | }; 219 | } 220 | ``` 221 | 222 | > src/routes/+page.svelte 223 | 224 | ```svelte 225 | 229 | ``` 230 | 231 | > src/routes/auth/login/+page.server.js 232 | 233 | ```js 234 | /** @type {import('@sveltejs/kit').PageData} */ 235 | export function load({ parent, locals }) { 236 | const { session } = await parent(); 237 | // or 238 | // locals.session.data.session; 239 | 240 | 241 | // Already logged in: 242 | if(session.userId) { 243 | throw redirect(302, '/') 244 | } 245 | 246 | return {}; 247 | } 248 | ``` 249 | 250 | ### Destroying the Session 251 | 252 | > src/routes/logout/+page.server.js 253 | 254 | ```js 255 | /** @type {import('@sveltejs/kit').Actions} */ 256 | export const actions = { 257 | default: async () => { 258 | await locals.session.destroy(); 259 | return {}; 260 | } 261 | }; 262 | ``` 263 | 264 | ### Refresh the session with the same data but renew the expiration date 265 | 266 | > src/routes/refresh/+page.server.js 267 | 268 | ```js 269 | /** @type {import('@sveltejs/kit').Actions} */ 270 | export const actions = { 271 | default: async () => { 272 | await locals.session.refresh(/** Optional new expiration time in days */); 273 | return {}; 274 | } 275 | }; 276 | ``` 277 | 278 | ### Refresh the session expiration on every request `Rolling` -> default is false! 279 | 280 | You can also specify a percentage from 1 to 100 which refreshes the session when a percentage of the expiration date is met. 281 | 282 | > Note this currently only fires if a session is already existing 283 | 284 | ```js 285 | handleSession({ 286 | rolling: true // or 1-100 for percentage o the expiry date met, 287 | }); 288 | ``` 289 | 290 | ### Configure Expiry Date 291 | 292 | You can configure the expiry date of the session cookie via the `expires` option. It should be a `number` in either `days | hours | minutes | seconds`. 293 | 294 | ```js 295 | handleSession({ 296 | expires: 160, // 160 minutes 297 | expires_in: 'minutes', // minutes | hours | days | seconds 298 | }); 299 | ``` 300 | 301 | ### Save unsaved session with the initial data 302 | 303 | You can save unsaved sessions with the initial data via the `saveUninitialized` option. It should be a `boolean` and the default is `false`. 304 | 305 | ```js 306 | handleSession({ 307 | init: () => ({ views: 0 }), 308 | saveUninitialized: true, 309 | }); 310 | ``` -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-kit-cookie-session", 3 | "version": "4.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "svelte-kit-cookie-session", 9 | "version": "4.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@noble/ciphers": "^0.2.0" 13 | }, 14 | "devDependencies": { 15 | "@playwright/test": "^1.25.1", 16 | "@sveltejs/adapter-auto": "2.1.0", 17 | "@sveltejs/kit": "^1.22.4", 18 | "@sveltejs/package": "^2.2.0", 19 | "prettier": "^2.7.1", 20 | "prettier-plugin-svelte": "^2.7.0", 21 | "rimraf": "^3.0.2", 22 | "svelte": "^4.1.2", 23 | "svelte-check": "^3.4.6", 24 | "svelte-preprocess": "^5.0.4", 25 | "svelte2tsx": "^0.6.19", 26 | "tslib": "^2.4.0", 27 | "typescript": "^4.7.4", 28 | "vite": "^4.2.1" 29 | } 30 | }, 31 | "node_modules/@ampproject/remapping": { 32 | "version": "2.2.1", 33 | "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", 34 | "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", 35 | "dev": true, 36 | "dependencies": { 37 | "@jridgewell/gen-mapping": "^0.3.0", 38 | "@jridgewell/trace-mapping": "^0.3.9" 39 | }, 40 | "engines": { 41 | "node": ">=6.0.0" 42 | } 43 | }, 44 | "node_modules/@esbuild/android-arm": { 45 | "version": "0.18.20", 46 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", 47 | "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", 48 | "cpu": [ 49 | "arm" 50 | ], 51 | "dev": true, 52 | "optional": true, 53 | "os": [ 54 | "android" 55 | ], 56 | "engines": { 57 | "node": ">=12" 58 | } 59 | }, 60 | "node_modules/@esbuild/android-arm64": { 61 | "version": "0.18.20", 62 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", 63 | "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", 64 | "cpu": [ 65 | "arm64" 66 | ], 67 | "dev": true, 68 | "optional": true, 69 | "os": [ 70 | "android" 71 | ], 72 | "engines": { 73 | "node": ">=12" 74 | } 75 | }, 76 | "node_modules/@esbuild/android-x64": { 77 | "version": "0.18.20", 78 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", 79 | "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", 80 | "cpu": [ 81 | "x64" 82 | ], 83 | "dev": true, 84 | "optional": true, 85 | "os": [ 86 | "android" 87 | ], 88 | "engines": { 89 | "node": ">=12" 90 | } 91 | }, 92 | "node_modules/@esbuild/darwin-arm64": { 93 | "version": "0.18.20", 94 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", 95 | "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", 96 | "cpu": [ 97 | "arm64" 98 | ], 99 | "dev": true, 100 | "optional": true, 101 | "os": [ 102 | "darwin" 103 | ], 104 | "engines": { 105 | "node": ">=12" 106 | } 107 | }, 108 | "node_modules/@esbuild/darwin-x64": { 109 | "version": "0.18.20", 110 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", 111 | "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", 112 | "cpu": [ 113 | "x64" 114 | ], 115 | "dev": true, 116 | "optional": true, 117 | "os": [ 118 | "darwin" 119 | ], 120 | "engines": { 121 | "node": ">=12" 122 | } 123 | }, 124 | "node_modules/@esbuild/freebsd-arm64": { 125 | "version": "0.18.20", 126 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", 127 | "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", 128 | "cpu": [ 129 | "arm64" 130 | ], 131 | "dev": true, 132 | "optional": true, 133 | "os": [ 134 | "freebsd" 135 | ], 136 | "engines": { 137 | "node": ">=12" 138 | } 139 | }, 140 | "node_modules/@esbuild/freebsd-x64": { 141 | "version": "0.18.20", 142 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", 143 | "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", 144 | "cpu": [ 145 | "x64" 146 | ], 147 | "dev": true, 148 | "optional": true, 149 | "os": [ 150 | "freebsd" 151 | ], 152 | "engines": { 153 | "node": ">=12" 154 | } 155 | }, 156 | "node_modules/@esbuild/linux-arm": { 157 | "version": "0.18.20", 158 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", 159 | "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", 160 | "cpu": [ 161 | "arm" 162 | ], 163 | "dev": true, 164 | "optional": true, 165 | "os": [ 166 | "linux" 167 | ], 168 | "engines": { 169 | "node": ">=12" 170 | } 171 | }, 172 | "node_modules/@esbuild/linux-arm64": { 173 | "version": "0.18.20", 174 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", 175 | "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", 176 | "cpu": [ 177 | "arm64" 178 | ], 179 | "dev": true, 180 | "optional": true, 181 | "os": [ 182 | "linux" 183 | ], 184 | "engines": { 185 | "node": ">=12" 186 | } 187 | }, 188 | "node_modules/@esbuild/linux-ia32": { 189 | "version": "0.18.20", 190 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", 191 | "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", 192 | "cpu": [ 193 | "ia32" 194 | ], 195 | "dev": true, 196 | "optional": true, 197 | "os": [ 198 | "linux" 199 | ], 200 | "engines": { 201 | "node": ">=12" 202 | } 203 | }, 204 | "node_modules/@esbuild/linux-loong64": { 205 | "version": "0.18.20", 206 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", 207 | "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", 208 | "cpu": [ 209 | "loong64" 210 | ], 211 | "dev": true, 212 | "optional": true, 213 | "os": [ 214 | "linux" 215 | ], 216 | "engines": { 217 | "node": ">=12" 218 | } 219 | }, 220 | "node_modules/@esbuild/linux-mips64el": { 221 | "version": "0.18.20", 222 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", 223 | "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", 224 | "cpu": [ 225 | "mips64el" 226 | ], 227 | "dev": true, 228 | "optional": true, 229 | "os": [ 230 | "linux" 231 | ], 232 | "engines": { 233 | "node": ">=12" 234 | } 235 | }, 236 | "node_modules/@esbuild/linux-ppc64": { 237 | "version": "0.18.20", 238 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", 239 | "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", 240 | "cpu": [ 241 | "ppc64" 242 | ], 243 | "dev": true, 244 | "optional": true, 245 | "os": [ 246 | "linux" 247 | ], 248 | "engines": { 249 | "node": ">=12" 250 | } 251 | }, 252 | "node_modules/@esbuild/linux-riscv64": { 253 | "version": "0.18.20", 254 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", 255 | "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", 256 | "cpu": [ 257 | "riscv64" 258 | ], 259 | "dev": true, 260 | "optional": true, 261 | "os": [ 262 | "linux" 263 | ], 264 | "engines": { 265 | "node": ">=12" 266 | } 267 | }, 268 | "node_modules/@esbuild/linux-s390x": { 269 | "version": "0.18.20", 270 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", 271 | "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", 272 | "cpu": [ 273 | "s390x" 274 | ], 275 | "dev": true, 276 | "optional": true, 277 | "os": [ 278 | "linux" 279 | ], 280 | "engines": { 281 | "node": ">=12" 282 | } 283 | }, 284 | "node_modules/@esbuild/linux-x64": { 285 | "version": "0.18.20", 286 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", 287 | "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", 288 | "cpu": [ 289 | "x64" 290 | ], 291 | "dev": true, 292 | "optional": true, 293 | "os": [ 294 | "linux" 295 | ], 296 | "engines": { 297 | "node": ">=12" 298 | } 299 | }, 300 | "node_modules/@esbuild/netbsd-x64": { 301 | "version": "0.18.20", 302 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", 303 | "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", 304 | "cpu": [ 305 | "x64" 306 | ], 307 | "dev": true, 308 | "optional": true, 309 | "os": [ 310 | "netbsd" 311 | ], 312 | "engines": { 313 | "node": ">=12" 314 | } 315 | }, 316 | "node_modules/@esbuild/openbsd-x64": { 317 | "version": "0.18.20", 318 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", 319 | "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", 320 | "cpu": [ 321 | "x64" 322 | ], 323 | "dev": true, 324 | "optional": true, 325 | "os": [ 326 | "openbsd" 327 | ], 328 | "engines": { 329 | "node": ">=12" 330 | } 331 | }, 332 | "node_modules/@esbuild/sunos-x64": { 333 | "version": "0.18.20", 334 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", 335 | "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", 336 | "cpu": [ 337 | "x64" 338 | ], 339 | "dev": true, 340 | "optional": true, 341 | "os": [ 342 | "sunos" 343 | ], 344 | "engines": { 345 | "node": ">=12" 346 | } 347 | }, 348 | "node_modules/@esbuild/win32-arm64": { 349 | "version": "0.18.20", 350 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", 351 | "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", 352 | "cpu": [ 353 | "arm64" 354 | ], 355 | "dev": true, 356 | "optional": true, 357 | "os": [ 358 | "win32" 359 | ], 360 | "engines": { 361 | "node": ">=12" 362 | } 363 | }, 364 | "node_modules/@esbuild/win32-ia32": { 365 | "version": "0.18.20", 366 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", 367 | "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", 368 | "cpu": [ 369 | "ia32" 370 | ], 371 | "dev": true, 372 | "optional": true, 373 | "os": [ 374 | "win32" 375 | ], 376 | "engines": { 377 | "node": ">=12" 378 | } 379 | }, 380 | "node_modules/@esbuild/win32-x64": { 381 | "version": "0.18.20", 382 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", 383 | "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", 384 | "cpu": [ 385 | "x64" 386 | ], 387 | "dev": true, 388 | "optional": true, 389 | "os": [ 390 | "win32" 391 | ], 392 | "engines": { 393 | "node": ">=12" 394 | } 395 | }, 396 | "node_modules/@jridgewell/gen-mapping": { 397 | "version": "0.3.3", 398 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", 399 | "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", 400 | "dev": true, 401 | "dependencies": { 402 | "@jridgewell/set-array": "^1.0.1", 403 | "@jridgewell/sourcemap-codec": "^1.4.10", 404 | "@jridgewell/trace-mapping": "^0.3.9" 405 | }, 406 | "engines": { 407 | "node": ">=6.0.0" 408 | } 409 | }, 410 | "node_modules/@jridgewell/resolve-uri": { 411 | "version": "3.1.0", 412 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", 413 | "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", 414 | "dev": true, 415 | "engines": { 416 | "node": ">=6.0.0" 417 | } 418 | }, 419 | "node_modules/@jridgewell/set-array": { 420 | "version": "1.1.2", 421 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", 422 | "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", 423 | "dev": true, 424 | "engines": { 425 | "node": ">=6.0.0" 426 | } 427 | }, 428 | "node_modules/@jridgewell/sourcemap-codec": { 429 | "version": "1.4.14", 430 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", 431 | "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", 432 | "dev": true 433 | }, 434 | "node_modules/@jridgewell/trace-mapping": { 435 | "version": "0.3.19", 436 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", 437 | "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", 438 | "dev": true, 439 | "dependencies": { 440 | "@jridgewell/resolve-uri": "^3.1.0", 441 | "@jridgewell/sourcemap-codec": "^1.4.14" 442 | } 443 | }, 444 | "node_modules/@noble/ciphers": { 445 | "version": "0.2.0", 446 | "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.2.0.tgz", 447 | "integrity": "sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw==", 448 | "funding": { 449 | "url": "https://paulmillr.com/funding/" 450 | } 451 | }, 452 | "node_modules/@nodelib/fs.scandir": { 453 | "version": "2.1.5", 454 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 455 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 456 | "dev": true, 457 | "dependencies": { 458 | "@nodelib/fs.stat": "2.0.5", 459 | "run-parallel": "^1.1.9" 460 | }, 461 | "engines": { 462 | "node": ">= 8" 463 | } 464 | }, 465 | "node_modules/@nodelib/fs.stat": { 466 | "version": "2.0.5", 467 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 468 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 469 | "dev": true, 470 | "engines": { 471 | "node": ">= 8" 472 | } 473 | }, 474 | "node_modules/@nodelib/fs.walk": { 475 | "version": "1.2.8", 476 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 477 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 478 | "dev": true, 479 | "dependencies": { 480 | "@nodelib/fs.scandir": "2.1.5", 481 | "fastq": "^1.6.0" 482 | }, 483 | "engines": { 484 | "node": ">= 8" 485 | } 486 | }, 487 | "node_modules/@playwright/test": { 488 | "version": "1.32.1", 489 | "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.1.tgz", 490 | "integrity": "sha512-FTwjCuhlm1qHUGf4hWjfr64UMJD/z0hXYbk+O387Ioe6WdyZQ+0TBDAc6P+pHjx2xCv1VYNgrKbYrNixFWy4Dg==", 491 | "dev": true, 492 | "dependencies": { 493 | "@types/node": "*", 494 | "playwright-core": "1.32.1" 495 | }, 496 | "bin": { 497 | "playwright": "cli.js" 498 | }, 499 | "engines": { 500 | "node": ">=14" 501 | }, 502 | "optionalDependencies": { 503 | "fsevents": "2.3.2" 504 | } 505 | }, 506 | "node_modules/@polka/url": { 507 | "version": "1.0.0-next.21", 508 | "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", 509 | "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", 510 | "dev": true 511 | }, 512 | "node_modules/@sveltejs/adapter-auto": { 513 | "version": "2.1.0", 514 | "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-2.1.0.tgz", 515 | "integrity": "sha512-o2pZCfATFtA/Gw/BB0Xm7k4EYaekXxaPGER3xGSY3FvzFJGTlJlZjBseaXwYSM94lZ0HniOjTokN3cWaLX6fow==", 516 | "dev": true, 517 | "dependencies": { 518 | "import-meta-resolve": "^3.0.0" 519 | }, 520 | "peerDependencies": { 521 | "@sveltejs/kit": "^1.0.0" 522 | } 523 | }, 524 | "node_modules/@sveltejs/kit": { 525 | "version": "1.22.4", 526 | "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.22.4.tgz", 527 | "integrity": "sha512-Opkqw1QXk4Cc25b/heJP2D7mX+OUBFAq4MXKfET58svTTxdeiHFKzmnuRsSF3nmxESqrLjqPAgHpib+knNGzRw==", 528 | "dev": true, 529 | "hasInstallScript": true, 530 | "dependencies": { 531 | "@sveltejs/vite-plugin-svelte": "^2.4.1", 532 | "@types/cookie": "^0.5.1", 533 | "cookie": "^0.5.0", 534 | "devalue": "^4.3.1", 535 | "esm-env": "^1.0.0", 536 | "kleur": "^4.1.5", 537 | "magic-string": "^0.30.0", 538 | "mime": "^3.0.0", 539 | "sade": "^1.8.1", 540 | "set-cookie-parser": "^2.6.0", 541 | "sirv": "^2.0.2", 542 | "undici": "~5.22.0" 543 | }, 544 | "bin": { 545 | "svelte-kit": "svelte-kit.js" 546 | }, 547 | "engines": { 548 | "node": "^16.14 || >=18" 549 | }, 550 | "peerDependencies": { 551 | "svelte": "^3.54.0 || ^4.0.0-next.0", 552 | "vite": "^4.0.0" 553 | } 554 | }, 555 | "node_modules/@sveltejs/package": { 556 | "version": "2.2.0", 557 | "resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-2.2.0.tgz", 558 | "integrity": "sha512-TXbrzsk+T5WNcSzrU41D8P32vU5guo96lVS11/R+rpLhZBH5sORh0Qp6r68Jg4O5vcdS3JLwpwcpe8VFbT/QeA==", 559 | "dev": true, 560 | "dependencies": { 561 | "chokidar": "^3.5.3", 562 | "kleur": "^4.1.5", 563 | "sade": "^1.8.1", 564 | "semver": "^7.5.3", 565 | "svelte2tsx": "~0.6.19" 566 | }, 567 | "bin": { 568 | "svelte-package": "svelte-package.js" 569 | }, 570 | "engines": { 571 | "node": "^16.14 || >=18" 572 | }, 573 | "peerDependencies": { 574 | "svelte": "^3.44.0 || ^4.0.0" 575 | } 576 | }, 577 | "node_modules/@sveltejs/vite-plugin-svelte": { 578 | "version": "2.4.4", 579 | "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.4.tgz", 580 | "integrity": "sha512-Q5z7+iIjs3sw/Jquxaa9KSY5/MShboNjvsxnQYRMdREx/SBDmEYTjeXenpMBh6k0IQ3tMKESCiwKq3/TeAQ8Og==", 581 | "dev": true, 582 | "dependencies": { 583 | "@sveltejs/vite-plugin-svelte-inspector": "^1.0.3", 584 | "debug": "^4.3.4", 585 | "deepmerge": "^4.3.1", 586 | "kleur": "^4.1.5", 587 | "magic-string": "^0.30.2", 588 | "svelte-hmr": "^0.15.3", 589 | "vitefu": "^0.2.4" 590 | }, 591 | "engines": { 592 | "node": "^14.18.0 || >= 16" 593 | }, 594 | "peerDependencies": { 595 | "svelte": "^3.54.0 || ^4.0.0", 596 | "vite": "^4.0.0" 597 | } 598 | }, 599 | "node_modules/@sveltejs/vite-plugin-svelte-inspector": { 600 | "version": "1.0.3", 601 | "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.3.tgz", 602 | "integrity": "sha512-Khdl5jmmPN6SUsVuqSXatKpQTMIifoQPDanaxC84m9JxIibWvSABJyHpyys0Z+1yYrxY5TTEQm+6elh0XCMaOA==", 603 | "dev": true, 604 | "dependencies": { 605 | "debug": "^4.3.4" 606 | }, 607 | "engines": { 608 | "node": "^14.18.0 || >= 16" 609 | }, 610 | "peerDependencies": { 611 | "@sveltejs/vite-plugin-svelte": "^2.2.0", 612 | "svelte": "^3.54.0 || ^4.0.0", 613 | "vite": "^4.0.0" 614 | } 615 | }, 616 | "node_modules/@types/cookie": { 617 | "version": "0.5.1", 618 | "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.1.tgz", 619 | "integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==", 620 | "dev": true 621 | }, 622 | "node_modules/@types/estree": { 623 | "version": "1.0.1", 624 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", 625 | "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", 626 | "dev": true 627 | }, 628 | "node_modules/@types/node": { 629 | "version": "18.15.11", 630 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", 631 | "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", 632 | "dev": true 633 | }, 634 | "node_modules/@types/pug": { 635 | "version": "2.0.6", 636 | "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz", 637 | "integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==", 638 | "dev": true 639 | }, 640 | "node_modules/acorn": { 641 | "version": "8.10.0", 642 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", 643 | "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", 644 | "dev": true, 645 | "bin": { 646 | "acorn": "bin/acorn" 647 | }, 648 | "engines": { 649 | "node": ">=0.4.0" 650 | } 651 | }, 652 | "node_modules/anymatch": { 653 | "version": "3.1.3", 654 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 655 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 656 | "dev": true, 657 | "dependencies": { 658 | "normalize-path": "^3.0.0", 659 | "picomatch": "^2.0.4" 660 | }, 661 | "engines": { 662 | "node": ">= 8" 663 | } 664 | }, 665 | "node_modules/aria-query": { 666 | "version": "5.3.0", 667 | "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", 668 | "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", 669 | "dev": true, 670 | "dependencies": { 671 | "dequal": "^2.0.3" 672 | } 673 | }, 674 | "node_modules/axobject-query": { 675 | "version": "3.2.1", 676 | "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", 677 | "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", 678 | "dev": true, 679 | "dependencies": { 680 | "dequal": "^2.0.3" 681 | } 682 | }, 683 | "node_modules/balanced-match": { 684 | "version": "1.0.2", 685 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 686 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 687 | "dev": true 688 | }, 689 | "node_modules/binary-extensions": { 690 | "version": "2.2.0", 691 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 692 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 693 | "dev": true, 694 | "engines": { 695 | "node": ">=8" 696 | } 697 | }, 698 | "node_modules/brace-expansion": { 699 | "version": "1.1.11", 700 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 701 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 702 | "dev": true, 703 | "dependencies": { 704 | "balanced-match": "^1.0.0", 705 | "concat-map": "0.0.1" 706 | } 707 | }, 708 | "node_modules/braces": { 709 | "version": "3.0.2", 710 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 711 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 712 | "dev": true, 713 | "dependencies": { 714 | "fill-range": "^7.0.1" 715 | }, 716 | "engines": { 717 | "node": ">=8" 718 | } 719 | }, 720 | "node_modules/buffer-crc32": { 721 | "version": "0.2.13", 722 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", 723 | "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", 724 | "dev": true, 725 | "engines": { 726 | "node": "*" 727 | } 728 | }, 729 | "node_modules/busboy": { 730 | "version": "1.6.0", 731 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", 732 | "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", 733 | "dev": true, 734 | "dependencies": { 735 | "streamsearch": "^1.1.0" 736 | }, 737 | "engines": { 738 | "node": ">=10.16.0" 739 | } 740 | }, 741 | "node_modules/callsites": { 742 | "version": "3.1.0", 743 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 744 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 745 | "dev": true, 746 | "engines": { 747 | "node": ">=6" 748 | } 749 | }, 750 | "node_modules/chokidar": { 751 | "version": "3.5.3", 752 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 753 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 754 | "dev": true, 755 | "funding": [ 756 | { 757 | "type": "individual", 758 | "url": "https://paulmillr.com/funding/" 759 | } 760 | ], 761 | "dependencies": { 762 | "anymatch": "~3.1.2", 763 | "braces": "~3.0.2", 764 | "glob-parent": "~5.1.2", 765 | "is-binary-path": "~2.1.0", 766 | "is-glob": "~4.0.1", 767 | "normalize-path": "~3.0.0", 768 | "readdirp": "~3.6.0" 769 | }, 770 | "engines": { 771 | "node": ">= 8.10.0" 772 | }, 773 | "optionalDependencies": { 774 | "fsevents": "~2.3.2" 775 | } 776 | }, 777 | "node_modules/code-red": { 778 | "version": "1.0.3", 779 | "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.3.tgz", 780 | "integrity": "sha512-kVwJELqiILQyG5aeuyKFbdsI1fmQy1Cmf7dQ8eGmVuJoaRVdwey7WaMknr2ZFeVSYSKT0rExsa8EGw0aoI/1QQ==", 781 | "dev": true, 782 | "dependencies": { 783 | "@jridgewell/sourcemap-codec": "^1.4.14", 784 | "@types/estree": "^1.0.0", 785 | "acorn": "^8.8.2", 786 | "estree-walker": "^3.0.3", 787 | "periscopic": "^3.1.0" 788 | } 789 | }, 790 | "node_modules/concat-map": { 791 | "version": "0.0.1", 792 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 793 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 794 | "dev": true 795 | }, 796 | "node_modules/cookie": { 797 | "version": "0.5.0", 798 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 799 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", 800 | "dev": true, 801 | "engines": { 802 | "node": ">= 0.6" 803 | } 804 | }, 805 | "node_modules/css-tree": { 806 | "version": "2.3.1", 807 | "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", 808 | "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", 809 | "dev": true, 810 | "dependencies": { 811 | "mdn-data": "2.0.30", 812 | "source-map-js": "^1.0.1" 813 | }, 814 | "engines": { 815 | "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" 816 | } 817 | }, 818 | "node_modules/debug": { 819 | "version": "4.3.4", 820 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 821 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 822 | "dev": true, 823 | "dependencies": { 824 | "ms": "2.1.2" 825 | }, 826 | "engines": { 827 | "node": ">=6.0" 828 | }, 829 | "peerDependenciesMeta": { 830 | "supports-color": { 831 | "optional": true 832 | } 833 | } 834 | }, 835 | "node_modules/dedent-js": { 836 | "version": "1.0.1", 837 | "resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz", 838 | "integrity": "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==", 839 | "dev": true 840 | }, 841 | "node_modules/deepmerge": { 842 | "version": "4.3.1", 843 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", 844 | "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", 845 | "dev": true, 846 | "engines": { 847 | "node": ">=0.10.0" 848 | } 849 | }, 850 | "node_modules/dequal": { 851 | "version": "2.0.3", 852 | "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", 853 | "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", 854 | "dev": true, 855 | "engines": { 856 | "node": ">=6" 857 | } 858 | }, 859 | "node_modules/detect-indent": { 860 | "version": "6.1.0", 861 | "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", 862 | "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", 863 | "dev": true, 864 | "engines": { 865 | "node": ">=8" 866 | } 867 | }, 868 | "node_modules/devalue": { 869 | "version": "4.3.2", 870 | "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.2.tgz", 871 | "integrity": "sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==", 872 | "dev": true 873 | }, 874 | "node_modules/es6-promise": { 875 | "version": "3.3.1", 876 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", 877 | "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", 878 | "dev": true 879 | }, 880 | "node_modules/esbuild": { 881 | "version": "0.18.20", 882 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", 883 | "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", 884 | "dev": true, 885 | "hasInstallScript": true, 886 | "bin": { 887 | "esbuild": "bin/esbuild" 888 | }, 889 | "engines": { 890 | "node": ">=12" 891 | }, 892 | "optionalDependencies": { 893 | "@esbuild/android-arm": "0.18.20", 894 | "@esbuild/android-arm64": "0.18.20", 895 | "@esbuild/android-x64": "0.18.20", 896 | "@esbuild/darwin-arm64": "0.18.20", 897 | "@esbuild/darwin-x64": "0.18.20", 898 | "@esbuild/freebsd-arm64": "0.18.20", 899 | "@esbuild/freebsd-x64": "0.18.20", 900 | "@esbuild/linux-arm": "0.18.20", 901 | "@esbuild/linux-arm64": "0.18.20", 902 | "@esbuild/linux-ia32": "0.18.20", 903 | "@esbuild/linux-loong64": "0.18.20", 904 | "@esbuild/linux-mips64el": "0.18.20", 905 | "@esbuild/linux-ppc64": "0.18.20", 906 | "@esbuild/linux-riscv64": "0.18.20", 907 | "@esbuild/linux-s390x": "0.18.20", 908 | "@esbuild/linux-x64": "0.18.20", 909 | "@esbuild/netbsd-x64": "0.18.20", 910 | "@esbuild/openbsd-x64": "0.18.20", 911 | "@esbuild/sunos-x64": "0.18.20", 912 | "@esbuild/win32-arm64": "0.18.20", 913 | "@esbuild/win32-ia32": "0.18.20", 914 | "@esbuild/win32-x64": "0.18.20" 915 | } 916 | }, 917 | "node_modules/esm-env": { 918 | "version": "1.0.0", 919 | "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", 920 | "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==", 921 | "dev": true 922 | }, 923 | "node_modules/estree-walker": { 924 | "version": "3.0.3", 925 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", 926 | "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", 927 | "dev": true, 928 | "dependencies": { 929 | "@types/estree": "^1.0.0" 930 | } 931 | }, 932 | "node_modules/fast-glob": { 933 | "version": "3.2.12", 934 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", 935 | "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", 936 | "dev": true, 937 | "dependencies": { 938 | "@nodelib/fs.stat": "^2.0.2", 939 | "@nodelib/fs.walk": "^1.2.3", 940 | "glob-parent": "^5.1.2", 941 | "merge2": "^1.3.0", 942 | "micromatch": "^4.0.4" 943 | }, 944 | "engines": { 945 | "node": ">=8.6.0" 946 | } 947 | }, 948 | "node_modules/fastq": { 949 | "version": "1.15.0", 950 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", 951 | "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", 952 | "dev": true, 953 | "dependencies": { 954 | "reusify": "^1.0.4" 955 | } 956 | }, 957 | "node_modules/fill-range": { 958 | "version": "7.0.1", 959 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 960 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 961 | "dev": true, 962 | "dependencies": { 963 | "to-regex-range": "^5.0.1" 964 | }, 965 | "engines": { 966 | "node": ">=8" 967 | } 968 | }, 969 | "node_modules/fs.realpath": { 970 | "version": "1.0.0", 971 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 972 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 973 | "dev": true 974 | }, 975 | "node_modules/fsevents": { 976 | "version": "2.3.2", 977 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 978 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 979 | "dev": true, 980 | "hasInstallScript": true, 981 | "optional": true, 982 | "os": [ 983 | "darwin" 984 | ], 985 | "engines": { 986 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 987 | } 988 | }, 989 | "node_modules/glob": { 990 | "version": "7.2.3", 991 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 992 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 993 | "dev": true, 994 | "dependencies": { 995 | "fs.realpath": "^1.0.0", 996 | "inflight": "^1.0.4", 997 | "inherits": "2", 998 | "minimatch": "^3.1.1", 999 | "once": "^1.3.0", 1000 | "path-is-absolute": "^1.0.0" 1001 | }, 1002 | "engines": { 1003 | "node": "*" 1004 | }, 1005 | "funding": { 1006 | "url": "https://github.com/sponsors/isaacs" 1007 | } 1008 | }, 1009 | "node_modules/glob-parent": { 1010 | "version": "5.1.2", 1011 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 1012 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 1013 | "dev": true, 1014 | "dependencies": { 1015 | "is-glob": "^4.0.1" 1016 | }, 1017 | "engines": { 1018 | "node": ">= 6" 1019 | } 1020 | }, 1021 | "node_modules/graceful-fs": { 1022 | "version": "4.2.11", 1023 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 1024 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 1025 | "dev": true 1026 | }, 1027 | "node_modules/immutable": { 1028 | "version": "4.3.0", 1029 | "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", 1030 | "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", 1031 | "dev": true, 1032 | "optional": true, 1033 | "peer": true 1034 | }, 1035 | "node_modules/import-fresh": { 1036 | "version": "3.3.0", 1037 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 1038 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 1039 | "dev": true, 1040 | "dependencies": { 1041 | "parent-module": "^1.0.0", 1042 | "resolve-from": "^4.0.0" 1043 | }, 1044 | "engines": { 1045 | "node": ">=6" 1046 | }, 1047 | "funding": { 1048 | "url": "https://github.com/sponsors/sindresorhus" 1049 | } 1050 | }, 1051 | "node_modules/import-meta-resolve": { 1052 | "version": "3.0.0", 1053 | "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz", 1054 | "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==", 1055 | "dev": true, 1056 | "funding": { 1057 | "type": "github", 1058 | "url": "https://github.com/sponsors/wooorm" 1059 | } 1060 | }, 1061 | "node_modules/inflight": { 1062 | "version": "1.0.6", 1063 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1064 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 1065 | "dev": true, 1066 | "dependencies": { 1067 | "once": "^1.3.0", 1068 | "wrappy": "1" 1069 | } 1070 | }, 1071 | "node_modules/inherits": { 1072 | "version": "2.0.4", 1073 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1074 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1075 | "dev": true 1076 | }, 1077 | "node_modules/is-binary-path": { 1078 | "version": "2.1.0", 1079 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 1080 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 1081 | "dev": true, 1082 | "dependencies": { 1083 | "binary-extensions": "^2.0.0" 1084 | }, 1085 | "engines": { 1086 | "node": ">=8" 1087 | } 1088 | }, 1089 | "node_modules/is-extglob": { 1090 | "version": "2.1.1", 1091 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1092 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1093 | "dev": true, 1094 | "engines": { 1095 | "node": ">=0.10.0" 1096 | } 1097 | }, 1098 | "node_modules/is-glob": { 1099 | "version": "4.0.3", 1100 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1101 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1102 | "dev": true, 1103 | "dependencies": { 1104 | "is-extglob": "^2.1.1" 1105 | }, 1106 | "engines": { 1107 | "node": ">=0.10.0" 1108 | } 1109 | }, 1110 | "node_modules/is-number": { 1111 | "version": "7.0.0", 1112 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1113 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1114 | "dev": true, 1115 | "engines": { 1116 | "node": ">=0.12.0" 1117 | } 1118 | }, 1119 | "node_modules/is-reference": { 1120 | "version": "3.0.1", 1121 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.1.tgz", 1122 | "integrity": "sha512-baJJdQLiYaJdvFbJqXrcGv3WU3QCzBlUcI5QhbesIm6/xPsvmO+2CDoi/GMOFBQEQm+PXkwOPrp9KK5ozZsp2w==", 1123 | "dev": true, 1124 | "dependencies": { 1125 | "@types/estree": "*" 1126 | } 1127 | }, 1128 | "node_modules/kleur": { 1129 | "version": "4.1.5", 1130 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", 1131 | "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", 1132 | "dev": true, 1133 | "engines": { 1134 | "node": ">=6" 1135 | } 1136 | }, 1137 | "node_modules/locate-character": { 1138 | "version": "3.0.0", 1139 | "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", 1140 | "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", 1141 | "dev": true 1142 | }, 1143 | "node_modules/lower-case": { 1144 | "version": "2.0.2", 1145 | "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", 1146 | "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", 1147 | "dev": true, 1148 | "dependencies": { 1149 | "tslib": "^2.0.3" 1150 | } 1151 | }, 1152 | "node_modules/lru-cache": { 1153 | "version": "6.0.0", 1154 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 1155 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1156 | "dev": true, 1157 | "dependencies": { 1158 | "yallist": "^4.0.0" 1159 | }, 1160 | "engines": { 1161 | "node": ">=10" 1162 | } 1163 | }, 1164 | "node_modules/magic-string": { 1165 | "version": "0.30.2", 1166 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz", 1167 | "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==", 1168 | "dev": true, 1169 | "dependencies": { 1170 | "@jridgewell/sourcemap-codec": "^1.4.15" 1171 | }, 1172 | "engines": { 1173 | "node": ">=12" 1174 | } 1175 | }, 1176 | "node_modules/magic-string/node_modules/@jridgewell/sourcemap-codec": { 1177 | "version": "1.4.15", 1178 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 1179 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 1180 | "dev": true 1181 | }, 1182 | "node_modules/mdn-data": { 1183 | "version": "2.0.30", 1184 | "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", 1185 | "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", 1186 | "dev": true 1187 | }, 1188 | "node_modules/merge2": { 1189 | "version": "1.4.1", 1190 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 1191 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 1192 | "dev": true, 1193 | "engines": { 1194 | "node": ">= 8" 1195 | } 1196 | }, 1197 | "node_modules/micromatch": { 1198 | "version": "4.0.5", 1199 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", 1200 | "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", 1201 | "dev": true, 1202 | "dependencies": { 1203 | "braces": "^3.0.2", 1204 | "picomatch": "^2.3.1" 1205 | }, 1206 | "engines": { 1207 | "node": ">=8.6" 1208 | } 1209 | }, 1210 | "node_modules/mime": { 1211 | "version": "3.0.0", 1212 | "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", 1213 | "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", 1214 | "dev": true, 1215 | "bin": { 1216 | "mime": "cli.js" 1217 | }, 1218 | "engines": { 1219 | "node": ">=10.0.0" 1220 | } 1221 | }, 1222 | "node_modules/min-indent": { 1223 | "version": "1.0.1", 1224 | "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", 1225 | "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", 1226 | "dev": true, 1227 | "engines": { 1228 | "node": ">=4" 1229 | } 1230 | }, 1231 | "node_modules/minimatch": { 1232 | "version": "3.1.2", 1233 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1234 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1235 | "dev": true, 1236 | "dependencies": { 1237 | "brace-expansion": "^1.1.7" 1238 | }, 1239 | "engines": { 1240 | "node": "*" 1241 | } 1242 | }, 1243 | "node_modules/minimist": { 1244 | "version": "1.2.8", 1245 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 1246 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 1247 | "dev": true, 1248 | "funding": { 1249 | "url": "https://github.com/sponsors/ljharb" 1250 | } 1251 | }, 1252 | "node_modules/mkdirp": { 1253 | "version": "0.5.6", 1254 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", 1255 | "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", 1256 | "dev": true, 1257 | "dependencies": { 1258 | "minimist": "^1.2.6" 1259 | }, 1260 | "bin": { 1261 | "mkdirp": "bin/cmd.js" 1262 | } 1263 | }, 1264 | "node_modules/mri": { 1265 | "version": "1.2.0", 1266 | "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", 1267 | "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", 1268 | "dev": true, 1269 | "engines": { 1270 | "node": ">=4" 1271 | } 1272 | }, 1273 | "node_modules/mrmime": { 1274 | "version": "1.0.1", 1275 | "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", 1276 | "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", 1277 | "dev": true, 1278 | "engines": { 1279 | "node": ">=10" 1280 | } 1281 | }, 1282 | "node_modules/ms": { 1283 | "version": "2.1.2", 1284 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1285 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1286 | "dev": true 1287 | }, 1288 | "node_modules/nanoid": { 1289 | "version": "3.3.6", 1290 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", 1291 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", 1292 | "dev": true, 1293 | "funding": [ 1294 | { 1295 | "type": "github", 1296 | "url": "https://github.com/sponsors/ai" 1297 | } 1298 | ], 1299 | "bin": { 1300 | "nanoid": "bin/nanoid.cjs" 1301 | }, 1302 | "engines": { 1303 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1304 | } 1305 | }, 1306 | "node_modules/no-case": { 1307 | "version": "3.0.4", 1308 | "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", 1309 | "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", 1310 | "dev": true, 1311 | "dependencies": { 1312 | "lower-case": "^2.0.2", 1313 | "tslib": "^2.0.3" 1314 | } 1315 | }, 1316 | "node_modules/normalize-path": { 1317 | "version": "3.0.0", 1318 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1319 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1320 | "dev": true, 1321 | "engines": { 1322 | "node": ">=0.10.0" 1323 | } 1324 | }, 1325 | "node_modules/once": { 1326 | "version": "1.4.0", 1327 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1328 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1329 | "dev": true, 1330 | "dependencies": { 1331 | "wrappy": "1" 1332 | } 1333 | }, 1334 | "node_modules/parent-module": { 1335 | "version": "1.0.1", 1336 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1337 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1338 | "dev": true, 1339 | "dependencies": { 1340 | "callsites": "^3.0.0" 1341 | }, 1342 | "engines": { 1343 | "node": ">=6" 1344 | } 1345 | }, 1346 | "node_modules/pascal-case": { 1347 | "version": "3.1.2", 1348 | "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", 1349 | "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", 1350 | "dev": true, 1351 | "dependencies": { 1352 | "no-case": "^3.0.4", 1353 | "tslib": "^2.0.3" 1354 | } 1355 | }, 1356 | "node_modules/path-is-absolute": { 1357 | "version": "1.0.1", 1358 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1359 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 1360 | "dev": true, 1361 | "engines": { 1362 | "node": ">=0.10.0" 1363 | } 1364 | }, 1365 | "node_modules/periscopic": { 1366 | "version": "3.1.0", 1367 | "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", 1368 | "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", 1369 | "dev": true, 1370 | "dependencies": { 1371 | "@types/estree": "^1.0.0", 1372 | "estree-walker": "^3.0.0", 1373 | "is-reference": "^3.0.0" 1374 | } 1375 | }, 1376 | "node_modules/picocolors": { 1377 | "version": "1.0.0", 1378 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 1379 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 1380 | "dev": true 1381 | }, 1382 | "node_modules/picomatch": { 1383 | "version": "2.3.1", 1384 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1385 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1386 | "dev": true, 1387 | "engines": { 1388 | "node": ">=8.6" 1389 | }, 1390 | "funding": { 1391 | "url": "https://github.com/sponsors/jonschlinkert" 1392 | } 1393 | }, 1394 | "node_modules/playwright-core": { 1395 | "version": "1.32.1", 1396 | "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.1.tgz", 1397 | "integrity": "sha512-KZYUQC10mXD2Am1rGlidaalNGYk3LU1vZqqNk0gT4XPty1jOqgup8KDP8l2CUlqoNKhXM5IfGjWgW37xvGllBA==", 1398 | "dev": true, 1399 | "bin": { 1400 | "playwright": "cli.js" 1401 | }, 1402 | "engines": { 1403 | "node": ">=14" 1404 | } 1405 | }, 1406 | "node_modules/postcss": { 1407 | "version": "8.4.27", 1408 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz", 1409 | "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==", 1410 | "dev": true, 1411 | "funding": [ 1412 | { 1413 | "type": "opencollective", 1414 | "url": "https://opencollective.com/postcss/" 1415 | }, 1416 | { 1417 | "type": "tidelift", 1418 | "url": "https://tidelift.com/funding/github/npm/postcss" 1419 | }, 1420 | { 1421 | "type": "github", 1422 | "url": "https://github.com/sponsors/ai" 1423 | } 1424 | ], 1425 | "dependencies": { 1426 | "nanoid": "^3.3.6", 1427 | "picocolors": "^1.0.0", 1428 | "source-map-js": "^1.0.2" 1429 | }, 1430 | "engines": { 1431 | "node": "^10 || ^12 || >=14" 1432 | } 1433 | }, 1434 | "node_modules/prettier": { 1435 | "version": "2.8.7", 1436 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", 1437 | "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", 1438 | "dev": true, 1439 | "bin": { 1440 | "prettier": "bin-prettier.js" 1441 | }, 1442 | "engines": { 1443 | "node": ">=10.13.0" 1444 | }, 1445 | "funding": { 1446 | "url": "https://github.com/prettier/prettier?sponsor=1" 1447 | } 1448 | }, 1449 | "node_modules/prettier-plugin-svelte": { 1450 | "version": "2.10.0", 1451 | "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-2.10.0.tgz", 1452 | "integrity": "sha512-GXMY6t86thctyCvQq+jqElO+MKdB09BkL3hexyGP3Oi8XLKRFaJP1ud/xlWCZ9ZIa2BxHka32zhHfcuU+XsRQg==", 1453 | "dev": true, 1454 | "peerDependencies": { 1455 | "prettier": "^1.16.4 || ^2.0.0", 1456 | "svelte": "^3.2.0" 1457 | } 1458 | }, 1459 | "node_modules/queue-microtask": { 1460 | "version": "1.2.3", 1461 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 1462 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 1463 | "dev": true, 1464 | "funding": [ 1465 | { 1466 | "type": "github", 1467 | "url": "https://github.com/sponsors/feross" 1468 | }, 1469 | { 1470 | "type": "patreon", 1471 | "url": "https://www.patreon.com/feross" 1472 | }, 1473 | { 1474 | "type": "consulting", 1475 | "url": "https://feross.org/support" 1476 | } 1477 | ] 1478 | }, 1479 | "node_modules/readdirp": { 1480 | "version": "3.6.0", 1481 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1482 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1483 | "dev": true, 1484 | "dependencies": { 1485 | "picomatch": "^2.2.1" 1486 | }, 1487 | "engines": { 1488 | "node": ">=8.10.0" 1489 | } 1490 | }, 1491 | "node_modules/resolve-from": { 1492 | "version": "4.0.0", 1493 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1494 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1495 | "dev": true, 1496 | "engines": { 1497 | "node": ">=4" 1498 | } 1499 | }, 1500 | "node_modules/reusify": { 1501 | "version": "1.0.4", 1502 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 1503 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 1504 | "dev": true, 1505 | "engines": { 1506 | "iojs": ">=1.0.0", 1507 | "node": ">=0.10.0" 1508 | } 1509 | }, 1510 | "node_modules/rimraf": { 1511 | "version": "3.0.2", 1512 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1513 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1514 | "dev": true, 1515 | "dependencies": { 1516 | "glob": "^7.1.3" 1517 | }, 1518 | "bin": { 1519 | "rimraf": "bin.js" 1520 | }, 1521 | "funding": { 1522 | "url": "https://github.com/sponsors/isaacs" 1523 | } 1524 | }, 1525 | "node_modules/rollup": { 1526 | "version": "3.27.2", 1527 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.27.2.tgz", 1528 | "integrity": "sha512-YGwmHf7h2oUHkVBT248x0yt6vZkYQ3/rvE5iQuVBh3WO8GcJ6BNeOkpoX1yMHIiBm18EMLjBPIoUDkhgnyxGOQ==", 1529 | "dev": true, 1530 | "bin": { 1531 | "rollup": "dist/bin/rollup" 1532 | }, 1533 | "engines": { 1534 | "node": ">=14.18.0", 1535 | "npm": ">=8.0.0" 1536 | }, 1537 | "optionalDependencies": { 1538 | "fsevents": "~2.3.2" 1539 | } 1540 | }, 1541 | "node_modules/run-parallel": { 1542 | "version": "1.2.0", 1543 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 1544 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 1545 | "dev": true, 1546 | "funding": [ 1547 | { 1548 | "type": "github", 1549 | "url": "https://github.com/sponsors/feross" 1550 | }, 1551 | { 1552 | "type": "patreon", 1553 | "url": "https://www.patreon.com/feross" 1554 | }, 1555 | { 1556 | "type": "consulting", 1557 | "url": "https://feross.org/support" 1558 | } 1559 | ], 1560 | "dependencies": { 1561 | "queue-microtask": "^1.2.2" 1562 | } 1563 | }, 1564 | "node_modules/sade": { 1565 | "version": "1.8.1", 1566 | "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", 1567 | "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", 1568 | "dev": true, 1569 | "dependencies": { 1570 | "mri": "^1.1.0" 1571 | }, 1572 | "engines": { 1573 | "node": ">=6" 1574 | } 1575 | }, 1576 | "node_modules/sander": { 1577 | "version": "0.5.1", 1578 | "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz", 1579 | "integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==", 1580 | "dev": true, 1581 | "dependencies": { 1582 | "es6-promise": "^3.1.2", 1583 | "graceful-fs": "^4.1.3", 1584 | "mkdirp": "^0.5.1", 1585 | "rimraf": "^2.5.2" 1586 | } 1587 | }, 1588 | "node_modules/sander/node_modules/rimraf": { 1589 | "version": "2.7.1", 1590 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 1591 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 1592 | "dev": true, 1593 | "dependencies": { 1594 | "glob": "^7.1.3" 1595 | }, 1596 | "bin": { 1597 | "rimraf": "bin.js" 1598 | } 1599 | }, 1600 | "node_modules/sass": { 1601 | "version": "1.60.0", 1602 | "resolved": "https://registry.npmjs.org/sass/-/sass-1.60.0.tgz", 1603 | "integrity": "sha512-updbwW6fNb5gGm8qMXzVO7V4sWf7LMXnMly/JEyfbfERbVH46Fn6q02BX7/eHTdKpE7d+oTkMMQpFWNUMfFbgQ==", 1604 | "dev": true, 1605 | "optional": true, 1606 | "peer": true, 1607 | "dependencies": { 1608 | "chokidar": ">=3.0.0 <4.0.0", 1609 | "immutable": "^4.0.0", 1610 | "source-map-js": ">=0.6.2 <2.0.0" 1611 | }, 1612 | "bin": { 1613 | "sass": "sass.js" 1614 | }, 1615 | "engines": { 1616 | "node": ">=12.0.0" 1617 | } 1618 | }, 1619 | "node_modules/semver": { 1620 | "version": "7.5.4", 1621 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", 1622 | "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", 1623 | "dev": true, 1624 | "dependencies": { 1625 | "lru-cache": "^6.0.0" 1626 | }, 1627 | "bin": { 1628 | "semver": "bin/semver.js" 1629 | }, 1630 | "engines": { 1631 | "node": ">=10" 1632 | } 1633 | }, 1634 | "node_modules/set-cookie-parser": { 1635 | "version": "2.6.0", 1636 | "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", 1637 | "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", 1638 | "dev": true 1639 | }, 1640 | "node_modules/sirv": { 1641 | "version": "2.0.2", 1642 | "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.2.tgz", 1643 | "integrity": "sha512-4Qog6aE29nIjAOKe/wowFTxOdmbEZKb+3tsLljaBRzJwtqto0BChD2zzH0LhgCSXiI+V7X+Y45v14wBZQ1TK3w==", 1644 | "dev": true, 1645 | "dependencies": { 1646 | "@polka/url": "^1.0.0-next.20", 1647 | "mrmime": "^1.0.0", 1648 | "totalist": "^3.0.0" 1649 | }, 1650 | "engines": { 1651 | "node": ">= 10" 1652 | } 1653 | }, 1654 | "node_modules/sorcery": { 1655 | "version": "0.11.0", 1656 | "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz", 1657 | "integrity": "sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==", 1658 | "dev": true, 1659 | "dependencies": { 1660 | "@jridgewell/sourcemap-codec": "^1.4.14", 1661 | "buffer-crc32": "^0.2.5", 1662 | "minimist": "^1.2.0", 1663 | "sander": "^0.5.0" 1664 | }, 1665 | "bin": { 1666 | "sorcery": "bin/sorcery" 1667 | } 1668 | }, 1669 | "node_modules/source-map-js": { 1670 | "version": "1.0.2", 1671 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 1672 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 1673 | "dev": true, 1674 | "engines": { 1675 | "node": ">=0.10.0" 1676 | } 1677 | }, 1678 | "node_modules/streamsearch": { 1679 | "version": "1.1.0", 1680 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", 1681 | "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", 1682 | "dev": true, 1683 | "engines": { 1684 | "node": ">=10.0.0" 1685 | } 1686 | }, 1687 | "node_modules/strip-indent": { 1688 | "version": "3.0.0", 1689 | "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", 1690 | "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", 1691 | "dev": true, 1692 | "dependencies": { 1693 | "min-indent": "^1.0.0" 1694 | }, 1695 | "engines": { 1696 | "node": ">=8" 1697 | } 1698 | }, 1699 | "node_modules/svelte": { 1700 | "version": "4.1.2", 1701 | "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.1.2.tgz", 1702 | "integrity": "sha512-/evA8U6CgOHe5ZD1C1W3va9iJG7mWflcCdghBORJaAhD2JzrVERJty/2gl0pIPrJYBGZwZycH6onYf+64XXF9g==", 1703 | "dev": true, 1704 | "dependencies": { 1705 | "@ampproject/remapping": "^2.2.1", 1706 | "@jridgewell/sourcemap-codec": "^1.4.15", 1707 | "@jridgewell/trace-mapping": "^0.3.18", 1708 | "acorn": "^8.9.0", 1709 | "aria-query": "^5.3.0", 1710 | "axobject-query": "^3.2.1", 1711 | "code-red": "^1.0.3", 1712 | "css-tree": "^2.3.1", 1713 | "estree-walker": "^3.0.3", 1714 | "is-reference": "^3.0.1", 1715 | "locate-character": "^3.0.0", 1716 | "magic-string": "^0.30.0", 1717 | "periscopic": "^3.1.0" 1718 | }, 1719 | "engines": { 1720 | "node": ">=16" 1721 | } 1722 | }, 1723 | "node_modules/svelte-check": { 1724 | "version": "3.4.6", 1725 | "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.4.6.tgz", 1726 | "integrity": "sha512-OBlY8866Zh1zHQTkBMPS6psPi7o2umTUyj6JWm4SacnIHXpWFm658pG32m3dKvKFL49V4ntAkfFHKo4ztH07og==", 1727 | "dev": true, 1728 | "dependencies": { 1729 | "@jridgewell/trace-mapping": "^0.3.17", 1730 | "chokidar": "^3.4.1", 1731 | "fast-glob": "^3.2.7", 1732 | "import-fresh": "^3.2.1", 1733 | "picocolors": "^1.0.0", 1734 | "sade": "^1.7.4", 1735 | "svelte-preprocess": "^5.0.4", 1736 | "typescript": "^5.0.3" 1737 | }, 1738 | "bin": { 1739 | "svelte-check": "bin/svelte-check" 1740 | }, 1741 | "peerDependencies": { 1742 | "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0" 1743 | } 1744 | }, 1745 | "node_modules/svelte-check/node_modules/typescript": { 1746 | "version": "5.1.6", 1747 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", 1748 | "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", 1749 | "dev": true, 1750 | "bin": { 1751 | "tsc": "bin/tsc", 1752 | "tsserver": "bin/tsserver" 1753 | }, 1754 | "engines": { 1755 | "node": ">=14.17" 1756 | } 1757 | }, 1758 | "node_modules/svelte-hmr": { 1759 | "version": "0.15.3", 1760 | "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz", 1761 | "integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==", 1762 | "dev": true, 1763 | "engines": { 1764 | "node": "^12.20 || ^14.13.1 || >= 16" 1765 | }, 1766 | "peerDependencies": { 1767 | "svelte": "^3.19.0 || ^4.0.0" 1768 | } 1769 | }, 1770 | "node_modules/svelte-preprocess": { 1771 | "version": "5.0.4", 1772 | "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.4.tgz", 1773 | "integrity": "sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==", 1774 | "dev": true, 1775 | "hasInstallScript": true, 1776 | "dependencies": { 1777 | "@types/pug": "^2.0.6", 1778 | "detect-indent": "^6.1.0", 1779 | "magic-string": "^0.27.0", 1780 | "sorcery": "^0.11.0", 1781 | "strip-indent": "^3.0.0" 1782 | }, 1783 | "engines": { 1784 | "node": ">= 14.10.0" 1785 | }, 1786 | "peerDependencies": { 1787 | "@babel/core": "^7.10.2", 1788 | "coffeescript": "^2.5.1", 1789 | "less": "^3.11.3 || ^4.0.0", 1790 | "postcss": "^7 || ^8", 1791 | "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0", 1792 | "pug": "^3.0.0", 1793 | "sass": "^1.26.8", 1794 | "stylus": "^0.55.0", 1795 | "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", 1796 | "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0", 1797 | "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0" 1798 | }, 1799 | "peerDependenciesMeta": { 1800 | "@babel/core": { 1801 | "optional": true 1802 | }, 1803 | "coffeescript": { 1804 | "optional": true 1805 | }, 1806 | "less": { 1807 | "optional": true 1808 | }, 1809 | "postcss": { 1810 | "optional": true 1811 | }, 1812 | "postcss-load-config": { 1813 | "optional": true 1814 | }, 1815 | "pug": { 1816 | "optional": true 1817 | }, 1818 | "sass": { 1819 | "optional": true 1820 | }, 1821 | "stylus": { 1822 | "optional": true 1823 | }, 1824 | "sugarss": { 1825 | "optional": true 1826 | }, 1827 | "typescript": { 1828 | "optional": true 1829 | } 1830 | } 1831 | }, 1832 | "node_modules/svelte-preprocess/node_modules/magic-string": { 1833 | "version": "0.27.0", 1834 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", 1835 | "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", 1836 | "dev": true, 1837 | "dependencies": { 1838 | "@jridgewell/sourcemap-codec": "^1.4.13" 1839 | }, 1840 | "engines": { 1841 | "node": ">=12" 1842 | } 1843 | }, 1844 | "node_modules/svelte/node_modules/@jridgewell/sourcemap-codec": { 1845 | "version": "1.4.15", 1846 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 1847 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 1848 | "dev": true 1849 | }, 1850 | "node_modules/svelte2tsx": { 1851 | "version": "0.6.19", 1852 | "resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.6.19.tgz", 1853 | "integrity": "sha512-h3b5OtcO8zyVL/RiB2zsDwCopeo/UH+887uyhgb2mjnewOFwiTxu+4IGuVwrrlyuh2onM2ktfUemNrNmQwXONQ==", 1854 | "dev": true, 1855 | "dependencies": { 1856 | "dedent-js": "^1.0.1", 1857 | "pascal-case": "^3.1.1" 1858 | }, 1859 | "peerDependencies": { 1860 | "svelte": "^3.55 || ^4.0.0-next.0 || ^4.0", 1861 | "typescript": "^4.9.4 || ^5.0.0" 1862 | } 1863 | }, 1864 | "node_modules/to-regex-range": { 1865 | "version": "5.0.1", 1866 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1867 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1868 | "dev": true, 1869 | "dependencies": { 1870 | "is-number": "^7.0.0" 1871 | }, 1872 | "engines": { 1873 | "node": ">=8.0" 1874 | } 1875 | }, 1876 | "node_modules/totalist": { 1877 | "version": "3.0.0", 1878 | "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.0.tgz", 1879 | "integrity": "sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==", 1880 | "dev": true, 1881 | "engines": { 1882 | "node": ">=6" 1883 | } 1884 | }, 1885 | "node_modules/tslib": { 1886 | "version": "2.5.0", 1887 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", 1888 | "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", 1889 | "dev": true 1890 | }, 1891 | "node_modules/typescript": { 1892 | "version": "4.9.5", 1893 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", 1894 | "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", 1895 | "dev": true, 1896 | "bin": { 1897 | "tsc": "bin/tsc", 1898 | "tsserver": "bin/tsserver" 1899 | }, 1900 | "engines": { 1901 | "node": ">=4.2.0" 1902 | } 1903 | }, 1904 | "node_modules/undici": { 1905 | "version": "5.22.1", 1906 | "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", 1907 | "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", 1908 | "dev": true, 1909 | "dependencies": { 1910 | "busboy": "^1.6.0" 1911 | }, 1912 | "engines": { 1913 | "node": ">=14.0" 1914 | } 1915 | }, 1916 | "node_modules/vite": { 1917 | "version": "4.4.9", 1918 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", 1919 | "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", 1920 | "dev": true, 1921 | "dependencies": { 1922 | "esbuild": "^0.18.10", 1923 | "postcss": "^8.4.27", 1924 | "rollup": "^3.27.1" 1925 | }, 1926 | "bin": { 1927 | "vite": "bin/vite.js" 1928 | }, 1929 | "engines": { 1930 | "node": "^14.18.0 || >=16.0.0" 1931 | }, 1932 | "funding": { 1933 | "url": "https://github.com/vitejs/vite?sponsor=1" 1934 | }, 1935 | "optionalDependencies": { 1936 | "fsevents": "~2.3.2" 1937 | }, 1938 | "peerDependencies": { 1939 | "@types/node": ">= 14", 1940 | "less": "*", 1941 | "lightningcss": "^1.21.0", 1942 | "sass": "*", 1943 | "stylus": "*", 1944 | "sugarss": "*", 1945 | "terser": "^5.4.0" 1946 | }, 1947 | "peerDependenciesMeta": { 1948 | "@types/node": { 1949 | "optional": true 1950 | }, 1951 | "less": { 1952 | "optional": true 1953 | }, 1954 | "lightningcss": { 1955 | "optional": true 1956 | }, 1957 | "sass": { 1958 | "optional": true 1959 | }, 1960 | "stylus": { 1961 | "optional": true 1962 | }, 1963 | "sugarss": { 1964 | "optional": true 1965 | }, 1966 | "terser": { 1967 | "optional": true 1968 | } 1969 | } 1970 | }, 1971 | "node_modules/vitefu": { 1972 | "version": "0.2.4", 1973 | "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz", 1974 | "integrity": "sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==", 1975 | "dev": true, 1976 | "peerDependencies": { 1977 | "vite": "^3.0.0 || ^4.0.0" 1978 | }, 1979 | "peerDependenciesMeta": { 1980 | "vite": { 1981 | "optional": true 1982 | } 1983 | } 1984 | }, 1985 | "node_modules/wrappy": { 1986 | "version": "1.0.2", 1987 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1988 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1989 | "dev": true 1990 | }, 1991 | "node_modules/yallist": { 1992 | "version": "4.0.0", 1993 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1994 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 1995 | "dev": true 1996 | } 1997 | } 1998 | } 1999 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-kit-cookie-session", 3 | "version": "4.1.1", 4 | "description": "⚒️ Encrypted 'stateless' cookie sessions for SvelteKit", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/pixelmund/svelte-kit-cookie-session.git" 8 | }, 9 | "exports": { 10 | "./package.json": "./package.json", 11 | "./core": "./core.js", 12 | "./handle": "./handle.js", 13 | ".": "./index.js", 14 | "./types": "./types.js", 15 | "./utils": "./utils.js" 16 | }, 17 | "scripts": { 18 | "dev": "vite dev", 19 | "build": "vite build", 20 | "package": "rimraf ./.svelte-kit/types && svelte-package && cp package.json dist/package.json && cp LICENSE dist/LICENSE && cp README.md dist/README.md", 21 | "preview": "vite preview", 22 | "test": "playwright test", 23 | "check": "svelte-check --tsconfig ./tsconfig.json", 24 | "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", 25 | "lint": "prettier --check --plugin-search-dir=. .", 26 | "format": "prettier --write --plugin-search-dir=. .", 27 | "package:publish": "npm run package && cd dist && npm publish" 28 | }, 29 | "devDependencies": { 30 | "@playwright/test": "^1.25.1", 31 | "@sveltejs/adapter-auto": "2.1.0", 32 | "@sveltejs/kit": "^1.22.4", 33 | "@sveltejs/package": "^2.2.0", 34 | "prettier": "^2.7.1", 35 | "prettier-plugin-svelte": "^2.7.0", 36 | "rimraf": "^3.0.2", 37 | "svelte": "^4.1.2", 38 | "svelte-check": "^3.4.6", 39 | "svelte-preprocess": "^5.0.4", 40 | "svelte2tsx": "^0.6.19", 41 | "tslib": "^2.4.0", 42 | "typescript": "^4.7.4", 43 | "vite": "^4.2.1" 44 | }, 45 | "type": "module", 46 | "dependencies": { 47 | "@noble/ciphers": "^0.2.0" 48 | }, 49 | "keywords": [ 50 | "SvelteKit", 51 | "Cookie", 52 | "CookieSession", 53 | "Session", 54 | "IronSession", 55 | "Svelte" 56 | ], 57 | "author": "pixelmund", 58 | "license": "MIT" 59 | } 60 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | webServer: { 5 | command: 'npm run build && npm run preview', 6 | port: 4173 7 | } 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | type SessionData = any; 4 | 5 | // See https://kit.svelte.dev/docs/types#app 6 | // for information about these interfaces 7 | declare namespace App { 8 | interface Locals { 9 | session: import('./lib').Session; 10 | } 11 | 12 | // interface Platform {} 13 | 14 | interface Session extends SessionData {} 15 | 16 | interface PageData { 17 | session: SessionData; 18 | } 19 | // interface Stuff {} 20 | } 21 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/hooks.server.ts: -------------------------------------------------------------------------------- 1 | import { handleSession } from '$lib'; 2 | 3 | export const handle = handleSession({ 4 | // init: () => ({ 5 | // views: 0 6 | // }), 7 | chunked: true, 8 | secret: '728hH4HPFNCduN6js58D3ZAfHeoRZc4v' 9 | }); 10 | -------------------------------------------------------------------------------- /src/lib/core.ts: -------------------------------------------------------------------------------- 1 | import type { Cookies, RequestEvent } from '@sveltejs/kit'; 2 | import { encrypt, decrypt, getKey, generateNonce } from './crypto.js'; 3 | import type { SessionOptions } from './types'; 4 | import { 5 | expiresToMaxage, 6 | maxAgeToDateOfExpiry, 7 | normalizeConfig, 8 | type NormalizedConfig 9 | } from './utils.js'; 10 | 11 | export class CookieSession> { 12 | #config: NormalizedConfig; 13 | #cookies: Cookies; 14 | #event: RequestEvent; 15 | #initialData: any = {}; 16 | #sessionData: any = undefined; 17 | #state: { 18 | needsSync: boolean; 19 | invalidDate: boolean; 20 | reEncrypt: boolean; 21 | destroy: boolean; 22 | } = { destroy: false, reEncrypt: false, invalidDate: false, needsSync: false }; 23 | 24 | constructor(event: RequestEvent, userConfig: SessionOptions) { 25 | this.#event = event; 26 | const isSecure = 27 | event.request.headers.get('x-forwarded-proto') === 'https' || 28 | event.request.url.startsWith('https'); 29 | this.#config = normalizeConfig(userConfig, isSecure); 30 | this.#cookies = event.cookies; 31 | } 32 | 33 | get expires(): Date | undefined { 34 | return this.#sessionData ? this.#sessionData.expires : undefined; 35 | } 36 | 37 | get data(): SessionType { 38 | return this.#sessionData && !this.#state.invalidDate && !this.#state.destroy 39 | ? { ...this.#sessionData } 40 | : this.#initialData; 41 | } 42 | 43 | get needsSync() { 44 | return this.#state.needsSync; 45 | } 46 | 47 | async init() { 48 | const { data, state } = await this.getSessionData(); 49 | 50 | if (this.#config.saveUninitialized && !data) { 51 | this.#initialData = await this.#config.init(this.#event); 52 | await this.set(this.#initialData); 53 | } 54 | 55 | if (data) { 56 | this.#sessionData = data; 57 | } 58 | 59 | if (state.destroy || state.invalidDate) { 60 | this.destroy(); 61 | } 62 | 63 | if (state.reEncrypt) { 64 | await this.reEncrypt(); 65 | } 66 | 67 | // If rolling is activated and the session exists we refresh the session on every request. 68 | if (this.#config?.rolling) { 69 | if (typeof this.#config.rolling === 'number' && this.#sessionData?.expires) { 70 | // refreshes when a percentage of the expiration date is met 71 | const differenceInSeconds = Math.round( 72 | new Date(this.#sessionData.expires).getTime() / 1000 - new Date().getTime() / 1000 73 | ); 74 | 75 | if (differenceInSeconds < (this.#config.rolling / 100) * this.#config.cookie.maxAge) { 76 | await this.refreshSession(); 77 | } 78 | } else { 79 | await this.refreshSession(); 80 | } 81 | } 82 | } 83 | 84 | public async set(data: SessionType) { 85 | let maxAge = this.#config.cookie.maxAge; 86 | 87 | if (this.#sessionData?.expires) { 88 | maxAge = Math.round( 89 | new Date(this.#sessionData.expires).getTime() / 1000 - new Date().getTime() / 1000 90 | ); 91 | } 92 | 93 | this.#state.needsSync = true; 94 | 95 | this.#sessionData = { 96 | ...data, 97 | expires: maxAgeToDateOfExpiry(maxAge) 98 | }; 99 | 100 | await this.setCookie(maxAge); 101 | 102 | return this.#sessionData; 103 | } 104 | 105 | public async update( 106 | updateFn: (data: SessionType) => Partial | Promise> 107 | ) { 108 | const dt = this.data; 109 | const sd = await updateFn(dt); 110 | return await this.set({ ...dt, ...sd }); 111 | } 112 | 113 | public destroy() { 114 | this.#state.needsSync = true; 115 | this.#sessionData = {}; 116 | 117 | if (!this.#config.chunked) { 118 | this.deleteCookies([ 119 | { 120 | name: this.#config.key, 121 | value: '' 122 | } 123 | ]); 124 | } 125 | 126 | const chunks = this.getChunkedCookies(); 127 | const cookiesToDelete = chunks; 128 | 129 | const meta = this.#cookies.get(`${this.#config.key}.meta`); 130 | 131 | if (meta) { 132 | cookiesToDelete.push({ name: `${this.#config.key}.meta`, value: meta }); 133 | } 134 | 135 | this.deleteCookies(cookiesToDelete); 136 | } 137 | 138 | public async refresh(expiresInDays?: number) { 139 | await this.refreshSession(expiresInDays); 140 | return true; 141 | } 142 | 143 | private async reEncrypt() { 144 | let maxAge = this.#config.cookie.maxAge; 145 | 146 | if (this.#sessionData?.expires) { 147 | maxAge = Math.round( 148 | new Date(this.#sessionData.expires).getTime() / 1000 - new Date().getTime() / 1000 149 | ); 150 | } 151 | 152 | await this.setCookie(maxAge); 153 | } 154 | 155 | private async refreshSession(expires?: number) { 156 | if (!this.#sessionData) { 157 | return false; 158 | } 159 | 160 | this.#state.needsSync = true; 161 | 162 | const newMaxAge = expiresToMaxage( 163 | expires ? expires : this.#config.expires, 164 | this.#config.expires_in 165 | ); 166 | 167 | this.#sessionData = { 168 | ...this.#sessionData, 169 | expires: maxAgeToDateOfExpiry(newMaxAge) 170 | }; 171 | 172 | await this.setCookie(newMaxAge); 173 | } 174 | 175 | private chunkString(str: string, chunkSize: number) { 176 | const chunks = []; 177 | for (let i = 0; i < str.length; i += chunkSize) { 178 | chunks.push(str.substring(i, i + chunkSize)); 179 | } 180 | return chunks; 181 | } 182 | 183 | private async setCookie(maxAge: number) { 184 | const cookieOptions = { 185 | httpOnly: this.#config.cookie.httpOnly, 186 | path: this.#config.cookie.path, 187 | sameSite: this.#config.cookie.sameSite, 188 | secure: this.#config.cookie.secure, 189 | domain: this.#config.cookie?.domain, 190 | maxAge: maxAge, 191 | priority: this.#config.cookie.priority, 192 | partitioned: this.#config.cookie.partitioned 193 | }; 194 | 195 | const nonce = generateNonce(); 196 | const key = getKey(this.#config.secrets[0].secret as string); 197 | 198 | const encode = () => { 199 | return encrypt(key, nonce, this.#sessionData); 200 | }; 201 | 202 | const id = String(this.#config.secrets[0].id); 203 | 204 | if (!this.#config.chunked) { 205 | return this.#cookies.set(this.#config.key, `${await encode()}&id=${id}`, cookieOptions); 206 | } 207 | 208 | const metaCookie = this.#cookies.get(`${this.#config.key}.meta`); 209 | const [currentChunkAmount] = metaCookie ? metaCookie.split('-') : [0]; 210 | 211 | // We need to check if the user has chunks enabled and if so we need to encrypt the data in chunks 212 | const encoded = await encode(); 213 | 214 | const chunkSize = 3996 - this.#config.key.length - 16 - id.length; 215 | const chunks = this.chunkString(encoded, chunkSize); 216 | 217 | // If the amount of chunks is different from the amount of chunks we have stored in the meta cookie we need to delete the old ones 218 | 219 | if (currentChunkAmount !== String(chunks.length)) { 220 | const cookiesToDelete = this.getChunkedCookies(); 221 | this.deleteCookies(cookiesToDelete); 222 | } 223 | 224 | if (chunks.length > 0) { 225 | for (let i = 0; i < chunks.length; i++) { 226 | this.#cookies.set(`${this.#config.key}.${i}`, chunks[i], cookieOptions); 227 | } 228 | 229 | this.#cookies.set( 230 | `${this.#config.key}.meta`, 231 | `${chunks.length}-${this.#config.secrets[0].id}`, 232 | cookieOptions 233 | ); 234 | } 235 | } 236 | 237 | private deleteCookies(cookies: { name: string; value: string }[]) { 238 | for (const cookie of cookies) { 239 | this.#cookies.delete(cookie.name, { 240 | httpOnly: this.#config.cookie.httpOnly, 241 | path: this.#config.cookie.path, 242 | sameSite: this.#config.cookie.sameSite, 243 | secure: this.#config.cookie.secure, 244 | domain: this.#config.cookie?.domain, 245 | priority: this.#config.cookie.priority, 246 | // @ts-expect-error need to update sveltekit types 247 | partitioned: this.#config.cookie.partitioned 248 | }); 249 | } 250 | } 251 | 252 | private async getSessionData() { 253 | const session = { 254 | state: { 255 | invalidDate: false, 256 | reEncrypt: false, 257 | destroy: false 258 | }, 259 | data: undefined 260 | }; 261 | 262 | let secret_id = this.#config.secrets[0].id; 263 | let sessionCookie: string = ''; 264 | 265 | if (!this.#config.chunked) { 266 | const splitted = (this.#cookies.get(this.#config.key) || '').split('&id='); 267 | sessionCookie = splitted[0]; 268 | secret_id = Number(splitted[1]); 269 | } else { 270 | const chunks = this.getChunkedCookies(); 271 | const metaCookie = this.#cookies.get(`${this.#config.key}.meta`); 272 | 273 | if (metaCookie) { 274 | const meta = metaCookie?.split('-'); 275 | secret_id = Number(meta[1]); 276 | } 277 | 278 | sessionCookie = chunks.map((chunk) => chunk.value).join(''); 279 | } 280 | 281 | if (sessionCookie.length === 0) { 282 | return session; 283 | } 284 | 285 | // If we have a session cookie we try to get the id from the cookie value and use it to decode the cookie. 286 | // If the decodeID is not the first secret in the secrets array we should re encrypt to the newest secret. 287 | 288 | // Split the sessionCookie on the &id= field to get the id we used to encrypt the session. 289 | const decodeID = secret_id ? Number(secret_id) : this.#config.secrets[0].id; 290 | 291 | // Use the id from the cookie or the initial one which is always 1. 292 | let secret = this.#config.secrets.find((sec) => sec.id === decodeID); 293 | 294 | // If there is no secret found try the first in the secrets array. 295 | if (!secret) secret = this.#config.secrets[0]; 296 | 297 | // Try to decode with the given sessionCookie and secret 298 | try { 299 | const key = getKey(secret.secret); 300 | const decrypted = await decrypt(key, sessionCookie); 301 | 302 | if ( 303 | decrypted && 304 | decrypted.expires && 305 | new Date(decrypted.expires).getTime() < new Date().getTime() 306 | ) { 307 | session.state.invalidDate = true; 308 | session.state.destroy = true; 309 | } 310 | 311 | // If the decodeID unequals the newest secret id in the array, we should re-encrypt the session with the newest secret. 312 | if (this.#config.secrets[0].id !== decodeID) { 313 | session.state.reEncrypt = true; 314 | } 315 | 316 | session.data = decrypted; 317 | 318 | return session; 319 | } catch (error) { 320 | session.state.destroy = true; 321 | return session; 322 | } 323 | } 324 | 325 | private getChunkedCookies() { 326 | const allCookies = this.#cookies 327 | .getAll() 328 | .filter((cookie) => cookie.name.startsWith(this.#config.key)); 329 | 330 | const chunks = allCookies 331 | .filter((cookie) => cookie.name.endsWith('.meta') === false) 332 | .sort((a, b) => { 333 | const aIndex = Number(a.name.split('.')[1]); 334 | const bIndex = Number(b.name.split('.')[1]); 335 | return aIndex - bIndex; 336 | }); 337 | 338 | return chunks; 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /src/lib/crypto.ts: -------------------------------------------------------------------------------- 1 | import { aes_256_gcm } from '@noble/ciphers/webcrypto/aes'; 2 | import { randomBytes } from '@noble/ciphers/webcrypto/utils'; 3 | import { utf8ToBytes, hexToBytes, bytesToHex, bytesToUtf8, concatBytes } from '@noble/ciphers/utils'; 4 | import type { BinaryLike } from './types'; 5 | 6 | export async function encrypt(key: Uint8Array, nonce: Uint8Array, data: any) { 7 | const cipher = aes_256_gcm(key, nonce); 8 | const ciphertext = await cipher.encrypt(utf8ToBytes(JSON.stringify(data))); 9 | return bytesToHex(concatBytes(nonce, ciphertext)); 10 | } 11 | 12 | export async function decrypt(key: Uint8Array, encryptedString: string) { 13 | const ciphertext = hexToBytes(encryptedString); 14 | const nonce = ciphertext.subarray(0, 12); 15 | const ciphertextWithoutNonce = ciphertext.subarray(12); 16 | const cipher = aes_256_gcm(key, nonce); 17 | return JSON.parse(bytesToUtf8(await cipher.decrypt(ciphertextWithoutNonce))); 18 | } 19 | 20 | export function getKey(secret: BinaryLike) { 21 | if (typeof secret === 'string') { 22 | return utf8ToBytes(secret); 23 | } 24 | if (secret instanceof Uint8Array) { 25 | return secret; 26 | } 27 | if (secret instanceof Uint16Array) { 28 | return new Uint8Array(secret.buffer); 29 | } 30 | if (secret instanceof ArrayBuffer) { 31 | return new Uint8Array(secret); 32 | } 33 | if (secret instanceof Buffer) { 34 | return new Uint8Array(secret.buffer); 35 | } 36 | throw new Error('Invalid secret type'); 37 | } 38 | 39 | export function generateNonce() { 40 | return randomBytes(12); 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/handle.ts: -------------------------------------------------------------------------------- 1 | import { CookieSession } from './core.js'; 2 | import type { Handle } from '@sveltejs/kit'; 3 | import type { SessionOptions } from './types'; 4 | 5 | export function handleSession( 6 | options: SessionOptions, 7 | passedHandle: Handle = async ({ event, resolve }) => resolve(event) 8 | ): Handle { 9 | return async function handle({ event, resolve }) { 10 | const session = new CookieSession(event, options); 11 | await session.init(); 12 | 13 | (event.locals as any).session = session; 14 | 15 | const response = await passedHandle({ event, resolve }); 16 | 17 | if (session.needsSync) { 18 | response.headers.set('x-svelte-kit-cookie-session-needs-sync', '1'); 19 | } 20 | 21 | return response; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export { handleSession } from './handle.js'; 2 | 3 | export type { SessionOptions, Session } from './types'; 4 | -------------------------------------------------------------------------------- /src/lib/types.ts: -------------------------------------------------------------------------------- 1 | import type { RequestEvent } from '@sveltejs/kit'; 2 | import type { MaybePromise } from '@sveltejs/kit'; 3 | 4 | export interface SessionOptions { 5 | /** 6 | * An optional function that is called when no stored session exists, this would be the initial session state. 7 | * This function is called with the request event and should return an object that will be used as the initial session state. 8 | * Can be asynchronous. 9 | * @param event The request event 10 | * @returns The initial session state 11 | * 12 | */ 13 | init?: (event: RequestEvent) => MaybePromise>; 14 | 15 | /** 16 | * Weither or not the session should be saved initially. 17 | */ 18 | saveUninitialized?: boolean; 19 | 20 | /** 21 | * The name of the cookie to use for the session. 22 | */ 23 | key?: string; 24 | /** 25 | * The secret(s) used to sign the session cookie. 26 | */ 27 | secret: BinaryLike | { id: number; secret: BinaryLike }[]; 28 | 29 | /** 30 | * 31 | * The expiration time of the session. 32 | * 33 | **/ 34 | expires?: number; 35 | 36 | /** 37 | * expires in Minutes 38 | */ 39 | expires_in?: 'days' | 'hours' | 'minutes' | 'seconds'; 40 | 41 | /** 42 | * 43 | * Determines if the session cookie should be chunked. 44 | * If the session cookie exceeds the browser's maximum cookie size, it will be split into multiple cookies. 45 | * @default false 46 | * 47 | **/ 48 | chunked?: boolean; 49 | 50 | /** 51 | * Should the session refresh on every request? 52 | */ 53 | rolling?: true | number; 54 | cookie?: { 55 | /** 56 | * Specifies the boolean value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.6|`HttpOnly` `Set-Cookie` attribute}. 57 | * When truthy, the `HttpOnly` attribute is set, otherwise it is not. By 58 | * default, the `HttpOnly` attribute is not set. 59 | * 60 | * *Note* be careful when setting this to true, as compliant clients will 61 | * not allow client-side JavaScript to see the cookie in `document.cookie`. 62 | */ 63 | httpOnly?: boolean | undefined; 64 | /** 65 | * Specifies the boolean or string to be the value for the {@link https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|`SameSite` `Set-Cookie` attribute}. 66 | * 67 | * - `true` will set the `SameSite` attribute to `Strict` for strict same 68 | * site enforcement. 69 | * - `false` will not set the `SameSite` attribute. 70 | * - `'lax'` will set the `SameSite` attribute to Lax for lax same site 71 | * enforcement. 72 | * - `'strict'` will set the `SameSite` attribute to Strict for strict same 73 | * site enforcement. 74 | * - `'none'` will set the SameSite attribute to None for an explicit 75 | * cross-site cookie. 76 | * 77 | * More information about the different enforcement levels can be found in {@link https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|the specification}. 78 | * 79 | * *note* This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it. 80 | */ 81 | sameSite?: true | false | 'lax' | 'strict' | 'none' | undefined; 82 | /** 83 | * Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.4|`Path` `Set-Cookie` attribute}. 84 | * By default, the path is considered the "default path". 85 | */ 86 | path?: string | undefined; 87 | /** 88 | * Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.3|Domain Set-Cookie attribute}. By default, no 89 | * domain is set, and most clients will consider the cookie to apply to only 90 | * the current domain. 91 | */ 92 | domain?: string | undefined; 93 | /** 94 | * Specifies the boolean value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.5|`Secure` `Set-Cookie` attribute}. When truthy, the 95 | * `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set. 96 | * 97 | * *Note* be careful when setting this to `true`, as compliant clients will 98 | * not send the cookie back to the server in the future if the browser does 99 | * not have an HTTPS connection. 100 | */ 101 | secure?: boolean | undefined; 102 | /** 103 | * Enables the [`Partitioned` `Set-Cookie` attribute](https://tools.ietf.org/html/draft-cutler-httpbis-partitioned-cookies/). 104 | * When enabled, clients will only send the cookie back when the current domain _and_ top-level domain matches. 105 | * 106 | * This is an attribute that has not yet been fully standardized, and may change in the future. 107 | * This also means clients may ignore this attribute until they understand it. More information 108 | * about can be found in [the proposal](https://github.com/privacycg/CHIPS). 109 | */ 110 | partitioned?: boolean; 111 | /** 112 | * Specifies the value for the [`Priority` `Set-Cookie` attribute](https://tools.ietf.org/html/draft-west-cookie-priority-00#section-4.1). 113 | * 114 | * - `'low'` will set the `Priority` attribute to `Low`. 115 | * - `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set. 116 | * - `'high'` will set the `Priority` attribute to `High`. 117 | * 118 | * More information about priority levels can be found in [the specification](https://tools.ietf.org/html/draft-west-cookie-priority-00#section-4.1). 119 | */ 120 | priority?: 'low' | 'medium' | 'high'; 121 | }; 122 | } 123 | 124 | export interface Session> { 125 | data: SessionType; 126 | expires: Date | undefined; 127 | update: ( 128 | updateFn: (data: SessionType) => Partial | Promise> 129 | ) => Promise; 130 | set: (data?: SessionType) => Promise; 131 | refresh: (expires_in_days?: number) => Promise; 132 | destroy: () => Promise; 133 | } 134 | 135 | export interface PrivateSession { 136 | 'set-cookie'?: string | undefined; 137 | } 138 | 139 | export type BinaryLike = string | Uint8Array | Uint16Array | ArrayBufferView | Buffer; 140 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import type { BinaryLike, SessionOptions } from './types'; 2 | import type { RequestEvent } from '@sveltejs/kit'; 3 | import type { MaybePromise } from '@sveltejs/kit'; 4 | 5 | export function expiresToMaxage( 6 | expires: number, 7 | expires_in: 'days' | 'hours' | 'minutes' | 'seconds' 8 | ) { 9 | switch (expires_in) { 10 | case 'days': 11 | return Math.round(expires * 24 * 60 * 60); 12 | case 'hours': 13 | return Math.round(expires * 60 * 60); 14 | case 'minutes': 15 | return Math.round(expires * 60); 16 | case 'seconds': 17 | return expires; 18 | default: 19 | return expires; 20 | } 21 | } 22 | 23 | export function maxAgeToDateOfExpiry(maxAge: number) { 24 | return new Date(Date.now() + maxAge * 1000); 25 | } 26 | 27 | export interface Secret { 28 | id: number; 29 | secret: BinaryLike; 30 | } 31 | 32 | export interface NormalizedConfig { 33 | init: (event: RequestEvent) => MaybePromise; 34 | saveUninitialized: boolean; 35 | key: string; 36 | expires: number; 37 | expires_in: 'days' | 'hours' | 'minutes' | 'seconds'; 38 | chunked: boolean; 39 | cookie: { 40 | maxAge: number; 41 | httpOnly: boolean; 42 | sameSite: any; 43 | path: string; 44 | domain: string | undefined; 45 | secure: boolean; 46 | priority?: 'low' | 'medium' | 'high'; 47 | partitioned?: boolean; 48 | }; 49 | rolling: number | boolean | undefined; 50 | secrets: Array; 51 | } 52 | 53 | export function normalizeConfig( 54 | options: SessionOptions, 55 | isSecure: boolean = false 56 | ): NormalizedConfig { 57 | if (options.secret == null) { 58 | throw new Error('Please provide at least one secret'); 59 | } 60 | 61 | const init = options.init ? options.init : () => ({}); 62 | 63 | return { 64 | init, 65 | saveUninitialized: options?.saveUninitialized ?? false, 66 | key: options.key || 'kit.session', 67 | expires: options.expires ? options.expires : 7, 68 | expires_in: options.expires_in ? options.expires_in : 'days', 69 | cookie: { 70 | maxAge: expiresToMaxage(options.expires || 7, options.expires_in || 'days'), 71 | httpOnly: options?.cookie?.httpOnly ?? true, 72 | sameSite: options?.cookie?.sameSite || 'lax', 73 | path: options?.cookie?.path || '/', 74 | domain: options?.cookie?.domain || undefined, 75 | secure: options?.cookie?.secure ?? isSecure, 76 | priority: options?.cookie?.priority ?? undefined, 77 | partitioned: options?.cookie?.partitioned ?? undefined 78 | }, 79 | chunked: options?.chunked ?? false, 80 | rolling: options?.rolling ?? false, 81 | secrets: Array.isArray(options.secret) ? options.secret : [{ id: 1, secret: options.secret }] 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /src/routes/+layout.server.ts: -------------------------------------------------------------------------------- 1 | import type { LayoutServerLoad } from './$types'; 2 | 3 | export const load: LayoutServerLoad = ({ locals }) => { 4 | return { 5 | session: locals.session.data 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 |

Welcome to the svelte-kit-cookie-session tests

2 | -------------------------------------------------------------------------------- /src/routes/tests/_utils.ts: -------------------------------------------------------------------------------- 1 | export const getCookieValue = (cookie: string) => { 2 | return cookie.split(';')[0].trim(); 3 | }; 4 | 5 | export const getCookieMaxage = (cookie: string) => { 6 | return cookie.split(';')[1].trim().replace('Max-Age=', ''); 7 | }; 8 | 9 | export const SECRET = 'hu1hqY2XB7EFB5Uedor5Jm7rBbyd1qJA'; 10 | 11 | export const initialData = { 12 | username: 'patrick', 13 | email: 'patrick@patrick.com', 14 | theme: 'light', 15 | lang: 'de' 16 | }; 17 | -------------------------------------------------------------------------------- /src/routes/tests/benchmark/+server.ts: -------------------------------------------------------------------------------- 1 | import { CookieSession } from '$lib/core'; 2 | import { json, type RequestHandler } from '@sveltejs/kit'; 3 | import { initialData, SECRET } from '../_utils'; 4 | import { Benchmark } from './_benchmark'; 5 | 6 | export const POST: RequestHandler = async (event) => { 7 | const { runs = 1000 } = await event.request.json(); 8 | 9 | const initialSession = new CookieSession(event, { secret: SECRET }); 10 | await initialSession.init(); 11 | await initialSession.set(initialData); 12 | 13 | const benchmark = new Benchmark(); 14 | 15 | for (let index = 0; index < runs; index += 1) { 16 | const session = new CookieSession(event, { secret: SECRET }); 17 | await session.init(); 18 | } 19 | 20 | const elapsed = benchmark.elapsed(); 21 | 22 | return json({ 23 | runs, 24 | elapsed: `${elapsed}ms`, 25 | each: `${elapsed / runs}ms` 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /src/routes/tests/benchmark/_benchmark.ts: -------------------------------------------------------------------------------- 1 | export class Benchmark { 2 | private start = process.hrtime(); 3 | 4 | public elapsed(): number { 5 | const end = process.hrtime(this.start); 6 | return Math.round(end[0] * 1000 + end[1] / 1000000); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/routes/tests/benchmark/get-session/+server.ts: -------------------------------------------------------------------------------- 1 | import { CookieSession } from '$lib/core'; 2 | import { json, type RequestHandler } from '@sveltejs/kit'; 3 | import { getCookieValue, initialData, SECRET } from '../../_utils'; 4 | import { Benchmark } from '../_benchmark'; 5 | 6 | export const POST: RequestHandler = async (event) => { 7 | const { runs = 1000 } = await event.request.json(); 8 | 9 | const initialSession = new CookieSession(event, { secret: SECRET }) 10 | await initialSession.init(); 11 | 12 | await initialSession.set(initialData); 13 | 14 | const benchmark = new Benchmark(); 15 | 16 | for (let index = 0; index < runs; index += 1) { 17 | const session = new CookieSession(event, { secret: SECRET }); 18 | await initialSession.init(); 19 | session.data; 20 | } 21 | 22 | const elapsed = benchmark.elapsed(); 23 | 24 | return json({ 25 | runs, 26 | elapsed: `${elapsed}ms`, 27 | each: `${elapsed / runs}ms` 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /src/routes/tests/binary-secret/+server.ts: -------------------------------------------------------------------------------- 1 | import { CookieSession } from '$lib/core'; 2 | import { json, type RequestHandler } from '@sveltejs/kit'; 3 | import { getCookieValue, initialData } from '../_utils'; 4 | 5 | const BINARY_SECRET = new Uint8Array(32); 6 | 7 | export const GET: RequestHandler = async (event) => { 8 | const newSession = new CookieSession(event, { secret: BINARY_SECRET }); 9 | await newSession.init(); 10 | await newSession.set(initialData); 11 | 12 | const sessionWithInitialCookie = new CookieSession(event, { secret: BINARY_SECRET }); 13 | await sessionWithInitialCookie.init(); 14 | const sessionData = sessionWithInitialCookie.data; 15 | 16 | if (initialData.username !== sessionData.username) { 17 | return json({ 18 | ok: false 19 | }); 20 | } 21 | 22 | return json({ 23 | ok: true 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /src/routes/tests/chunked-session/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { ServerLoad } from '@sveltejs/kit'; 2 | 3 | export const load: ServerLoad = async ({ locals }) => { 4 | // for testing the chunked cookies feature we have to build up a huge array, greater than 4096 bytes 5 | const hugeArray = []; 6 | 7 | for (let i = 0; i < 1000; i += 1) { 8 | hugeArray.push(i); 9 | } 10 | 11 | await locals.session.set({ 12 | views: hugeArray 13 | }); 14 | 15 | return { 16 | views: locals.session.data.views 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /src/routes/tests/chunked-session/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {data.views} 7 | {data.session?.views} -------------------------------------------------------------------------------- /src/routes/tests/chunked-session/delete/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { ServerLoad } from '@sveltejs/kit'; 2 | 3 | export const load: ServerLoad = async ({ locals }) => { 4 | // for testing the chunked cookies feature we have to build up a huge array, greater than 4096 bytes 5 | if (locals.session.data.views && Array.isArray(locals.session.data.views)) { 6 | await locals.session.destroy(); 7 | } else { 8 | const hugeArray = []; 9 | 10 | for (let i = 0; i < 1000; i += 1) { 11 | hugeArray.push(i); 12 | } 13 | 14 | await locals.session.set({ 15 | views: hugeArray 16 | }); 17 | } 18 | 19 | return { 20 | views: locals.session.data.views 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /src/routes/tests/chunked-session/delete/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {data.views} 7 | {data.session?.views} -------------------------------------------------------------------------------- /src/routes/tests/chunked-session/update/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { ServerLoad } from '@sveltejs/kit'; 2 | 3 | export const load: ServerLoad = async ({ locals }) => { 4 | const hugeArray: number[] = []; 5 | 6 | for (let i = 0; i < 1500; i += 1) { 7 | hugeArray.push(i); 8 | } 9 | 10 | await locals.session.update(() => ({ views: hugeArray })); 11 | 12 | return { 13 | views: locals.session.data.views 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /src/routes/tests/chunked-session/update/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {data.views} 7 | {data.session?.views} -------------------------------------------------------------------------------- /src/routes/tests/destroy-session/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { ServerLoad } from '@sveltejs/kit'; 2 | 3 | export const load: ServerLoad = async ({ locals }) => { 4 | if (locals.session.data.views === 999) { 5 | await locals.session.destroy(); 6 | } else { 7 | await locals.session.set({ views: 999 }); 8 | } 9 | 10 | return { 11 | views: locals.session.data.views 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /src/routes/tests/destroy-session/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {data.views} -------------------------------------------------------------------------------- /src/routes/tests/handles-special-chars/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { ServerLoad } from '@sveltejs/kit'; 2 | 3 | export const load: ServerLoad = async ({ locals }) => { 4 | if (locals.session.data.name) { 5 | return { name: locals.session.data.name }; 6 | } else { 7 | await locals.session.update(() => ({ name: 'Jürgen 🤩' })); 8 | } 9 | 10 | return { name: locals.session.data.name }; 11 | }; 12 | -------------------------------------------------------------------------------- /src/routes/tests/handles-special-chars/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {data.name} 7 | {data.session.name} 8 | 9 | -------------------------------------------------------------------------------- /src/routes/tests/password-rotation/+server.ts: -------------------------------------------------------------------------------- 1 | import { CookieSession } from '$lib/core'; 2 | import { json, type RequestHandler } from '@sveltejs/kit'; 3 | import { getCookieValue, initialData, SECRET } from '../_utils'; 4 | 5 | export const GET: RequestHandler = async (event) => { 6 | const newSession = new CookieSession(event, { secret: SECRET }); 7 | await newSession.init(); 8 | await newSession.set(initialData); 9 | 10 | // @ts-ignore 11 | const initialCookie = event.cookies.get('kit.session'); 12 | 13 | const sessionWithNewSecret = new CookieSession(event, { 14 | secret: [ 15 | { id: 2, secret: '728hH4HPFNCduN6js58D3ZAfHeoRZc4v' }, 16 | { id: 1, secret: SECRET } 17 | ] 18 | }); 19 | await sessionWithNewSecret.init(); 20 | 21 | const sessionWithNewSecretData = sessionWithNewSecret.data; 22 | 23 | if (initialData.username !== sessionWithNewSecretData?.username) { 24 | return json({ 25 | reason: 'Should have the same data', 26 | ok: false 27 | }); 28 | } 29 | 30 | // @ts-ignore 31 | if (initialCookie === event.cookies.get('kit.session')) { 32 | return json({ 33 | reason: 'Cookie should get re-encrypted', 34 | ok: false 35 | }); 36 | } 37 | 38 | return json({ 39 | ok: true 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /src/routes/tests/refresh-session/+server.ts: -------------------------------------------------------------------------------- 1 | import { json, type RequestHandler } from '@sveltejs/kit'; 2 | 3 | export const GET: RequestHandler = async ({ locals }) => { 4 | if (locals.session.data.views === 999) { 5 | await locals.session.refresh(30); 6 | } else { 7 | await locals.session.set({ views: 999 }); 8 | } 9 | 10 | return json({ views: locals.session.data.views }); 11 | }; 12 | -------------------------------------------------------------------------------- /src/routes/tests/rolling/+server.ts: -------------------------------------------------------------------------------- 1 | import { CookieSession } from '$lib/core'; 2 | import { json, type RequestHandler } from '@sveltejs/kit'; 3 | import { initialData, SECRET } from '../_utils'; 4 | 5 | const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); 6 | 7 | export const GET: RequestHandler = async (event) => { 8 | const session = new CookieSession(event, { secret: SECRET, rolling: true }); 9 | await session.init(); 10 | await session.set(initialData); 11 | 12 | await sleep(4000); 13 | 14 | const newSession = new CookieSession(event, { secret: SECRET, rolling: true }); 15 | await newSession.init(); 16 | 17 | if (new Date(newSession.expires!).getTime() === new Date(session.expires!).getTime()) { 18 | return json({ ok: false }); 19 | } 20 | 21 | return json({ ok: true }); 22 | }; 23 | -------------------------------------------------------------------------------- /src/routes/tests/rolling/percentage/+server.ts: -------------------------------------------------------------------------------- 1 | import { CookieSession } from '$lib/core'; 2 | import { json, type RequestHandler } from '@sveltejs/kit'; 3 | import { initialData, SECRET } from '../../_utils'; 4 | 5 | const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); 6 | 7 | export const GET: RequestHandler = async (event) => { 8 | const session = new CookieSession(event, { 9 | secret: SECRET, 10 | rolling: true, 11 | expires: 1 12 | }); 13 | await session.init(); 14 | await session.set(initialData); 15 | 16 | await sleep(5000); 17 | 18 | const newSession = new CookieSession(event, { 19 | secret: SECRET, 20 | expires: 1, 21 | rolling: 99.9999999999 22 | }); 23 | await newSession.init(); 24 | 25 | 26 | if (new Date(newSession.expires!).getTime() === new Date(session.expires!).getTime()) { 27 | return json({ ok: false }); 28 | } 29 | 30 | return json({ ok: true }); 31 | }; 32 | -------------------------------------------------------------------------------- /src/routes/tests/save-uninitialized/+server.ts: -------------------------------------------------------------------------------- 1 | import { CookieSession } from '$lib/core'; 2 | import { json, type RequestHandler } from '@sveltejs/kit'; 3 | import { SECRET } from '../_utils'; 4 | 5 | export const GET: RequestHandler = async (event) => { 6 | const withoutInitialization = new CookieSession(event, { 7 | secret: SECRET, 8 | init: () => ({ initialized: true }), 9 | saveUninitialized: false 10 | }); 11 | await withoutInitialization.init(); 12 | 13 | // @ts-ignore 14 | const initialCookie = event.cookies.get('kit.session'); 15 | 16 | if (initialCookie !== undefined) { 17 | return json({ 18 | ok: false 19 | }); 20 | } 21 | 22 | const withInitialization = new CookieSession(event, { 23 | secret: SECRET, 24 | init: () => ({ initialized: true }), 25 | saveUninitialized: true 26 | }); 27 | 28 | await withInitialization.init(); 29 | 30 | // @ts-ignore 31 | const newCookie = event.cookies.get('kit.session'); 32 | 33 | if (newCookie === undefined) { 34 | return json({ 35 | ok: false 36 | }); 37 | } 38 | 39 | return json({ 40 | ok: true 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /src/routes/tests/set-session/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { ServerLoad } from '@sveltejs/kit'; 2 | 3 | export const load: ServerLoad = async ({ locals }) => { 4 | await locals.session.set({ views: 42 }); 5 | 6 | return { 7 | views: locals.session.data.views 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /src/routes/tests/set-session/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {data.views} 7 | {data.session?.views} -------------------------------------------------------------------------------- /src/routes/tests/set-session/expires-in-configurable/+server.ts: -------------------------------------------------------------------------------- 1 | import { CookieSession } from '$lib/core'; 2 | import { json, type RequestHandler } from '@sveltejs/kit'; 3 | import { initialData, SECRET } from '../../_utils'; 4 | 5 | export const GET: RequestHandler = async (event) => { 6 | const newSession = new CookieSession(event, { 7 | secret: SECRET, 8 | expires_in: 'minutes', 9 | expires: 60 10 | }); 11 | await newSession.init(); 12 | await newSession.set(initialData); 13 | 14 | if (newSession.expires === undefined) { 15 | return json({ 16 | ok: false 17 | }); 18 | } 19 | 20 | const minutes = Math.floor((newSession.expires.getTime() - Date.now()) / 1000 / 60); 21 | 22 | if (minutes <= 60 && minutes > 58) { 23 | return json({ 24 | ok: true 25 | }); 26 | } else { 27 | return json({ 28 | ok: false 29 | }); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/routes/tests/sync-session/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { Actions } from '@sveltejs/kit'; 2 | 3 | export const actions: Actions = { 4 | default: async ({ locals }) => { 5 | await locals.session.update((sd) => 6 | sd.views == null ? { views: 1 } : { views: sd.views + 1 } 7 | ); 8 | 9 | return {}; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/routes/tests/sync-session/+page.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 |

{$page.data.session.views}

23 | -------------------------------------------------------------------------------- /src/routes/tests/update-session/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { ServerLoad } from '@sveltejs/kit'; 2 | 3 | export const load: ServerLoad = async ({ locals }) => { 4 | await locals.session.update((sd) => (sd.views == null ? { views: 0 } : { views: sd.views + 1 })); 5 | 6 | return { 7 | views: locals.session.data.views 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /src/routes/tests/update-session/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {data.views} 7 | {data.session?.views} 8 | -------------------------------------------------------------------------------- /src/routes/tests/update-session/keep-expiry/+server.ts: -------------------------------------------------------------------------------- 1 | import { json, type RequestHandler } from '@sveltejs/kit'; 2 | 3 | export const GET: RequestHandler = async ({ locals }) => { 4 | await locals.session.update((sd) => (sd.views == null ? { views: 0 } : { views: sd.views + 1 })); 5 | 6 | return json({ 7 | data: locals.session.data as {}, 8 | expires: locals.session.expires, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /src/routes/tests/wrong-secret/+server.ts: -------------------------------------------------------------------------------- 1 | import { CookieSession } from '$lib/core'; 2 | import { json, type RequestHandler } from '@sveltejs/kit'; 3 | import { getCookieValue, initialData, SECRET } from '../_utils'; 4 | 5 | export const GET: RequestHandler = async (event) => { 6 | const newSession = new CookieSession(event, { 7 | secret: SECRET 8 | }); 9 | await newSession.init(); 10 | await newSession.set(initialData); 11 | 12 | const sessionWithWrongSecret = new CookieSession(event, { 13 | secret: 'die79AYyyE9MYr3Rmbh4TeCphipyz6aT' 14 | }); 15 | await sessionWithWrongSecret.init(); 16 | sessionWithWrongSecret.data; 17 | 18 | const wrongCookie = event.cookies.get('kit.session'); 19 | 20 | if (wrongCookie !== '') { 21 | return json({ ok: false }); 22 | } 23 | 24 | return json({ ok: true }); 25 | }; 26 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixelmund/svelte-kit-cookie-session/79d34cfcc471a6aec9e370629fcb6975b7c3e8df/static/favicon.png -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import preprocess from 'svelte-preprocess'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://github.com/sveltejs/svelte-preprocess 7 | // for more information about preprocessors 8 | preprocess: preprocess(), 9 | 10 | kit: { 11 | adapter: adapter() 12 | } 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /tests/test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | const getCookieValue = (cookie: string) => { 4 | return cookie.split(';')[0].trim(); 5 | }; 6 | 7 | const getCookieMaxage = (cookie: string) => { 8 | return cookie.split(';')[1].trim().replace('Max-Age=', ''); 9 | }; 10 | 11 | test('[FEAT]: Setting the session', async ({ page, request, context }) => { 12 | await context.clearCookies(); 13 | 14 | await page.goto('/tests/set-session'); 15 | expect(await page.textContent('#views')).toBe('42'); 16 | expect(await page.textContent('#session-store-views')).toBe('undefined'); 17 | 18 | await page.reload(); 19 | 20 | expect(await page.textContent('#views')).toBe('42'); 21 | expect(await page.textContent('#session-store-views')).toBe('42'); 22 | }); 23 | 24 | test('[FEAT]: Expires in configurable', async ({ page, request, context }) => { 25 | await context.clearCookies(); 26 | 27 | const response = await request.get('/tests/set-session/expires-in-configurable'); 28 | 29 | await expect(response).toBeOK(); 30 | 31 | const data = await response.json(); 32 | 33 | expect(data.ok).toBe(true); 34 | }); 35 | 36 | test('[FEAT]: Save uninititialized', async ({ page, request, context }) => { 37 | await context.clearCookies(); 38 | 39 | const response = await request.get('/tests/save-uninitialized'); 40 | 41 | await expect(response).toBeOK(); 42 | 43 | const data = await response.json(); 44 | 45 | expect(data.ok).toBe(true); 46 | }); 47 | 48 | test('[FEAT]: Updating the session', async ({ page, request, context }) => { 49 | await context.clearCookies(); 50 | 51 | await page.goto('/tests/update-session'); 52 | expect(await page.textContent('#views')).toBe('0'); 53 | expect(await page.textContent('#session-store-views')).toBe('undefined'); 54 | 55 | await page.reload(); 56 | 57 | expect(await page.textContent('#views')).toBe('1'); 58 | expect(await page.textContent('#session-store-views')).toBe('0'); 59 | 60 | await page.reload(); 61 | 62 | expect(await page.textContent('#views')).toBe('2'); 63 | expect(await page.textContent('#session-store-views')).toBe('1'); 64 | }); 65 | 66 | test('[FEAT]: Updating the session, keeps the expiry', async ({ page, request, context }) => { 67 | await context.clearCookies(); 68 | 69 | const initialResponse = await request.get('/tests/update-session/keep-expiry'); 70 | const initialCookieValue = getCookieValue(initialResponse.headers()['set-cookie']); 71 | const initialMaxage = Number(getCookieMaxage(initialResponse.headers()['set-cookie'])); 72 | const initialData = await initialResponse.json(); 73 | 74 | await new Promise((resolve) => setTimeout(resolve, 2000)); 75 | 76 | const updatedResponse = await request.get('/tests/update-session/keep-expiry', { 77 | headers: { cookie: initialCookieValue } 78 | }); 79 | const updatedCookieValue = getCookieValue(updatedResponse.headers()['set-cookie']); 80 | const updatedMaxage = Number(getCookieMaxage(updatedResponse.headers()['set-cookie'])); 81 | const updatedData = await updatedResponse.json(); 82 | 83 | expect(updatedMaxage).toBeLessThan(initialMaxage); 84 | 85 | const updatedExpires = updatedData.expires.split('.')[0]; 86 | const initialExpires = initialData.expires.split('.')[0]; 87 | 88 | expect(updatedExpires).toStrictEqual(initialExpires); 89 | expect(updatedData.data.views).toBeGreaterThan(initialData.data.views); 90 | }); 91 | 92 | test('[FEAT]: Sync the session between server and client', async ({ page, context }) => { 93 | await context.clearCookies(); 94 | 95 | await page.goto('/tests/sync-session'); 96 | await page.waitForTimeout(1000); 97 | expect(await page.textContent('#session-store-views')).toBe('1'); 98 | }); 99 | 100 | test('[FEAT]: Destroying the session', async ({ page, request, context }) => { 101 | await context.clearCookies(); 102 | 103 | await page.goto('/tests/destroy-session'); 104 | expect(await page.textContent('#views')).toBe('999'); 105 | 106 | await page.reload(); 107 | 108 | expect(await page.textContent('#views')).toBe('undefined'); 109 | }); 110 | 111 | test('[FEAT]: Refreshing the session', async ({ page, request, context }) => { 112 | await context.clearCookies(); 113 | 114 | const initialResponse = await request.get('/tests/refresh-session'); 115 | const initialCookie = getCookieValue(initialResponse.headers()['set-cookie']); 116 | 117 | const response = await request.get('/tests/refresh-session', { 118 | headers: { cookie: initialCookie } 119 | }); 120 | 121 | const maxAge = getCookieMaxage(response.headers()['set-cookie']); 122 | expect(maxAge).toBe('2592000'); 123 | }); 124 | 125 | test('[FEAT]: Password rotation', async ({ page, request, context }) => { 126 | await context.clearCookies(); 127 | 128 | const response = await request.get('/tests/password-rotation'); 129 | 130 | await expect(response).toBeOK(); 131 | 132 | const data = await response.json(); 133 | 134 | expect(data.ok).toBe(true); 135 | }); 136 | 137 | test('[FEAT]: Chunked cookies', async ({ page, request, context }) => { 138 | await context.clearCookies(); 139 | 140 | await page.goto('/tests/chunked-session'); 141 | expect(await page.textContent('#views')).toContain('0,1,2,3,4,5'); 142 | expect(await page.textContent('#session-store-views')).toBe('undefined'); 143 | 144 | await page.reload(); 145 | 146 | expect(await page.textContent('#views')).toContain('0,1,2,3,4,5'); 147 | expect(await page.textContent('#session-store-views')).toContain('0,1,2,3,4,5'); 148 | 149 | await context.clearCookies(); 150 | 151 | await page.goto('/tests/chunked-session/delete'); 152 | expect(await page.textContent('#views')).toContain('0,1,2,3,4,5'); 153 | expect(await page.textContent('#session-store-views')).toBe('undefined'); 154 | 155 | await page.reload(); 156 | expect(await page.textContent('#views')).toBe('undefined'); 157 | expect(await page.textContent('#session-store-views')).toContain('0,1,2,3,4,5'); 158 | }); 159 | 160 | test('[FEAT]: Rolling = true should refresh the session every request', async ({ 161 | page, 162 | request, 163 | context 164 | }) => { 165 | await context.clearCookies(); 166 | 167 | const response = await request.get('/tests/rolling'); 168 | 169 | await expect(response).toBeOK(); 170 | 171 | const data = await response.json(); 172 | 173 | expect(data.ok).toBe(true); 174 | }); 175 | 176 | test('[FEAT]: Rolling = number (percentage) should refresh the session if a certain percentage of the expiry is met', async ({ 177 | page, 178 | request, 179 | context 180 | }) => { 181 | await context.clearCookies(); 182 | 183 | const response = await request.get('/tests/rolling/percentage'); 184 | 185 | await expect(response).toBeOK(); 186 | 187 | const data = await response.json(); 188 | 189 | expect(data.ok).toBe(true); 190 | }); 191 | 192 | test('[INTEGRATION]: Binary Secrets', async ({ page, request, context }) => { 193 | await context.clearCookies(); 194 | 195 | const response = await request.get('/tests/binary-secret'); 196 | 197 | await expect(response).toBeOK(); 198 | 199 | const data = await response.json(); 200 | 201 | expect(data.ok, data.reason).toBe(true); 202 | }); 203 | 204 | test('[INTEGRATION]: Handles special characters', async ({ page, request, context }) => { 205 | await context.clearCookies(); 206 | 207 | await page.goto('/tests/handles-special-chars'); 208 | expect(await page.textContent('#name')).toBe('Jürgen 🤩'); 209 | expect(await page.textContent('#session_name')).toBe('undefined'); 210 | 211 | await page.reload(); 212 | 213 | expect(await page.textContent('#name')).toBe('Jürgen 🤩'); 214 | expect(await page.textContent('#session_name')).toBe('Jürgen 🤩'); 215 | }); 216 | 217 | test('[INTEGRATION]: Wrong secret deletes the session', async ({ page, request, context }) => { 218 | await context.clearCookies(); 219 | 220 | const response = await request.get('/tests/wrong-secret'); 221 | 222 | await expect(response).toBeOK(); 223 | 224 | const data = await response.json(); 225 | 226 | expect(data.ok).toBe(true); 227 | }); 228 | 229 | test('[BENCHMARK]: Get session', async ({ request }) => { 230 | const response = await request.post('/tests/benchmark/get-session', { 231 | data: { 232 | runs: 5000 233 | } 234 | }); 235 | 236 | await expect(response).toBeOK(); 237 | 238 | const data = await response.json(); 239 | 240 | console.log('[BENCHMARK]: Get -> ', { ...data }); 241 | }); 242 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | 3 | /** @type {import('vite').UserConfig} */ 4 | const config = { 5 | plugins: [sveltekit()] 6 | }; 7 | 8 | export default config; 9 | --------------------------------------------------------------------------------