diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d408d0a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +# CI Arboretum : build + tests sur Node 22/24, puis smoke de packaging npx. +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + test: + name: Build & test (Node ${{ matrix.node }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node: [22, 24] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: npm + - run: npm ci + - run: npm run typecheck + - run: npm run build + - run: npx vitest run + + pack-smoke: + name: Pack & boot smoke (Node 22) + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run build + # @arboretum/shared est une dépendance runtime non publiée : on packe les + # deux tarballs et on les installe ensemble dans un projet vierge. + - name: Pack tarballs + run: | + mkdir -p /tmp/tarballs + npm pack -w @arboretum/shared -w git-arboretum --pack-destination /tmp/tarballs + ls -l /tmp/tarballs + - name: Install tarballs in an empty project + run: | + mkdir /tmp/smoke + cd /tmp/smoke + npm init -y + npm i /tmp/tarballs/*.tgz + - name: Boot smoke (expect 401 on /api/v1/sessions) + run: | + cd /tmp/smoke + timeout 30 node_modules/.bin/arboretum --port 7999 --db ./t.db & + server_pid=$! + code=000 + for i in $(seq 1 20); do + sleep 0.5 + code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:7999/api/v1/sessions || true) + if [ "$code" = "401" ]; then break; fi + done + echo "GET /api/v1/sessions -> HTTP $code" + kill "$server_pid" 2>/dev/null || true + test "$code" = "401" diff --git a/.gitignore b/.gitignore index 1e5e42c..f534d40 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ dist/ .DS_Store spikes/**/tmp/ spikes/**/captures/ +packages/server/public/ diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/package-lock.json b/package-lock.json index 7a108d3..79734f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,90 @@ "resolved": "packages/shared", "link": true }, + "node_modules/@arboretum/web": { + "resolved": "packages/web", + "link": true + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", @@ -694,6 +778,67 @@ "node": ">=18.0.0 <25.0.0" } }, + "node_modules/@intlify/core-base": { + "version": "11.4.5", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.4.5.tgz", + "integrity": "sha512-lja3F/iKVIvTa48mIwmrIeDcQUFZ0F0drvFvT8AwINOvbwnAzl/S/p8p2DxILZpWEUHRi1qewfWNIkMvhD3kKA==", + "license": "MIT", + "dependencies": { + "@intlify/devtools-types": "11.4.5", + "@intlify/message-compiler": "11.4.5", + "@intlify/shared": "11.4.5" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/devtools-types": { + "version": "11.4.5", + "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.4.5.tgz", + "integrity": "sha512-W5vydP9Yq3t82IyWqCM6aR0BTWCZrN5RAwjZEPpH8I2OQWp2RLy03Evh2ANZlSMhcvGAoyDg25k0so85Kwncpw==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "11.4.5", + "@intlify/shared": "11.4.5" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "11.4.5", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.4.5.tgz", + "integrity": "sha512-IEOZiHtbQopyPc/Dz2M869lOlZYX1SdcniNJwphATDYHhovvIneEKf1EFF37DE7NAABZtza1FNtnwwqZWInfpw==", + "license": "MIT", + "dependencies": { + "@intlify/shared": "11.4.5", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "11.4.5", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.4.5.tgz", + "integrity": "sha512-g/i5mtdUa9ia/8BaJ4w6ZRHgAXYQd9XyCaQPRMvsd8d5qmZwkjoTmHrNsI28Q/7I8h+2ijUkI4uEnnMCziKupQ==", + "license": "MIT", + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, "node_modules/@isaacs/cliui": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", @@ -703,13 +848,55 @@ "node": ">=18" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@lukeed/ms": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", @@ -719,12 +906,305 @@ "node": ">=8" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@pinojs/redact": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", "license": "MIT" }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.61.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", @@ -1075,6 +1555,289 @@ "win32" ] }, + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "tailwindcss": "4.3.0" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -1120,6 +1883,23 @@ "@types/node": "*" } }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz", + "integrity": "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, "node_modules/@vitest/expect": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.6.tgz", @@ -1235,6 +2015,217 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.38.tgz", + "integrity": "sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.38", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.38.tgz", + "integrity": "sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.38", + "@vue/shared": "3.5.38" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.38.tgz", + "integrity": "sha512-DuA2GiZawSEW442iw/9+Fkol8hTgb4Ke5KkhmSry65QA7YuyMbIdy8p0XZRMvNwJdgRz307W8g1CSzdvS4nuNg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.38", + "@vue/compiler-dom": "3.5.38", + "@vue/compiler-ssr": "3.5.38", + "@vue/shared": "3.5.38", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.38.tgz", + "integrity": "sha512-7s+W5Gc42FGxZMcuwl8H5B29T8BJPMdBT7KHFE+BbAuZ/iTEdTtv7z2XiMjiaUUw4w3ZcCEdHs36RuYJ2VA7bA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.38", + "@vue/shared": "3.5.38" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.4.tgz", + "integrity": "sha512-IuHqQ5zGGOE7CXP72VX6A42IVeIzYv4WAhO6arej11TRNqtdZfGyH8Yr2FOCaDX0dSQG+JwULLoFHGY1igYVjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.2.0", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.38.tgz", + "integrity": "sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.38" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.38.tgz", + "integrity": "sha512-iyW8WVfF1CpCXxncZY5Ei6rSd6oZr5DgEom//fUjRBRl56AXPD+s9ATvukRt77ZFTuYlnVA1bxY+dJB94tWVYw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.38", + "@vue/shared": "3.5.38" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.38.tgz", + "integrity": "sha512-apX2wt9sdfDshS+a2xueFZLVpt0GkRJZSoPmrW/SA4yzXTznhfcMVW59gr7h4YQeY0vJhdJkk2rsIDwgfFgC5A==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.38", + "@vue/runtime-core": "3.5.38", + "@vue/shared": "3.5.38", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.38.tgz", + "integrity": "sha512-vue8vbf2QlV4quHqzwmJy6dWfmRhP1J8l4wtZg60CL6VoKqcPY2oe7may3+1d9qfpedjK5PRLFqd5k3Isj9mUw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.38", + "@vue/shared": "3.5.38" + }, + "peerDependencies": { + "vue": "3.5.38" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.38.tgz", + "integrity": "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==", + "license": "MIT" + }, + "node_modules/@xterm/addon-fit": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz", + "integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==", + "license": "MIT" + }, + "node_modules/@xterm/addon-webgl": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0.tgz", + "integrity": "sha512-b3fMOsyLVuCeNJWxolACEUED0vm7qC0cy4wRvf3oURSzDTYVQiGPhTnhWZwIHdvC48Y+oLhvYXnY4XDXPoJo6A==", + "license": "MIT" + }, + "node_modules/@xterm/xterm": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz", + "integrity": "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==", + "license": "MIT", + "workspaces": [ + "addons/*" + ] + }, "node_modules/abstract-logging": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", @@ -1274,6 +2265,13 @@ } } }, + "node_modules/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "dev": true, + "license": "MIT" + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1342,6 +2340,15 @@ ], "license": "MIT" }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -1457,6 +2464,21 @@ "url": "https://opencollective.com/express" } }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1471,6 +2493,12 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1571,6 +2599,32 @@ "once": "^1.4.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.0.tgz", + "integrity": "sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -1877,6 +2931,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -1938,6 +3005,18 @@ "node": ">= 10" } }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1959,6 +3038,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", @@ -2028,6 +3117,278 @@ ], "license": "MIT" }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/loupe": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", @@ -2048,7 +3409,6 @@ "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -2111,6 +3471,12 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -2124,11 +3490,17 @@ "dev": true, "license": "MIT" }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "dev": true, "funding": [ { "type": "github", @@ -2191,6 +3563,13 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2233,11 +3612,16 @@ "node": ">= 14.16" } }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -2253,6 +3637,27 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/pino": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", @@ -2294,7 +3699,6 @@ "version": "8.5.15", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2450,6 +3854,40 @@ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "license": "MIT" }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, "node_modules/rollup": { "version": "4.61.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", @@ -2684,7 +4122,15 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -2759,6 +4205,39 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/tar-fs": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", @@ -2884,6 +4363,14 @@ "node": ">=0.6" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -2900,7 +4387,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -3094,6 +4581,99 @@ } } }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.38.tgz", + "integrity": "sha512-vAMKHfImQlYSy0C+PBue4s3ERZ2xGKfgZg5GXAsLInq1dyh2H78ILVP5sK0KPFPVW4kv+OGCIvBEondcjpZp7A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.38", + "@vue/compiler-sfc": "3.5.38", + "@vue/runtime-dom": "3.5.38", + "@vue/server-renderer": "3.5.38", + "@vue/shared": "3.5.38" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-i18n": { + "version": "11.4.5", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.5.tgz", + "integrity": "sha512-rm8YJ6RpjOrkcgS2GLrZwLvs/VbhxbTSuEspbyXDo233+fPK0OMFNLOj3fdQYVKdOgcpSfLW91JhbqgpkkcBWA==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "11.4.5", + "@intlify/devtools-types": "11.4.5", + "@intlify/shared": "11.4.5", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue-i18n/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.4.tgz", + "integrity": "sha512-XA/JqmQwS2GZmfgpjOEGdrKwaTSEuPwxpHa7/t6f4yiGrJb3gVHTPb9wBfByMNZwQ+xDXs41b8gaS2DKsOozUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.3.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3178,6 +4758,106 @@ "packages/shared": { "name": "@arboretum/shared", "version": "0.1.0" + }, + "packages/web": { + "name": "@arboretum/web", + "version": "0.1.0", + "dependencies": { + "@arboretum/shared": "*", + "@xterm/addon-fit": "^0.11.0", + "@xterm/addon-webgl": "^0.19.0", + "@xterm/xterm": "^6.0.0", + "pinia": "^3.0.4", + "vue": "^3.5.38", + "vue-i18n": "^11.4.5", + "vue-router": "^4.6.4" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.3.0", + "@vitejs/plugin-vue": "^6.0.7", + "tailwindcss": "^4.3.0", + "typescript": "^5.7.0", + "vite": "^8.0.16", + "vue-tsc": "^3.3.4" + } + }, + "packages/web/node_modules/vite": { + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 8de8b30..f82d786 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "scripts": { "build": "npm run build -w @arboretum/shared -w git-arboretum -w @arboretum/web", "typecheck": "tsc -b packages/shared packages/server", + "pack": "npm run build && npm pack -w git-arboretum", "test": "vitest run", "dev:server": "npm run dev -w git-arboretum", "dev:web": "npm run dev -w @arboretum/web" diff --git a/packages/server/package.json b/packages/server/package.json index 36f7968..4baed9b 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -18,6 +18,7 @@ "scripts": { "build": "tsc -b", "dev": "tsc -b --watch & node --watch dist/index.js", + "prepack": "node scripts/copy-web.mjs", "test": "vitest run" }, "dependencies": { diff --git a/packages/server/scripts/acceptance-p1.mjs b/packages/server/scripts/acceptance-p1.mjs new file mode 100644 index 0000000..cb28521 --- /dev/null +++ b/packages/server/scripts/acceptance-p1.mjs @@ -0,0 +1,167 @@ +#!/usr/bin/env node +// Acceptation P1 (sans navigateur, sans quota Claude) : vrai daemon + vrai client WS. +// Couvre : login, hello, sub, spawn bash, attach+replay, stdin/output, flood 10 Mo +// avec flow control par ACK + RSS borné, déconnexion/reconnexion+replay, kill, drain. +import { spawn } from 'node:child_process'; +import { mkdtempSync, readFileSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { createRequire } from 'node:module'; + +const require = createRequire(import.meta.url); +const WebSocket = require('ws'); + +const PORT = 7541; +const ORIGIN = `http://127.0.0.1:${PORT}`; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +const serverDir = join(dirname(fileURLToPath(import.meta.url)), '..'); +const results = []; +const check = (name, ok, detail = '') => { + results.push({ name, ok, detail }); + console.log(`${ok ? '✅' : '❌'} ${name}${detail ? ` — ${detail}` : ''}`); +}; + +const tmp = mkdtempSync(join(tmpdir(), 'arb-accept-')); +const srv = spawn('node', [join(serverDir, 'dist', 'index.js'), '--port', String(PORT), '--db', join(tmp, 'a.db')], { + env: { ...process.env, ARBORETUM_LOG: 'warn' }, + stdio: ['ignore', 'pipe', 'pipe'], +}); +let srvOut = ''; +srv.stdout.on('data', (d) => (srvOut += d)); +srv.stderr.on('data', (d) => (srvOut += d)); + +try { + await sleep(1500); + const token = /arb_[0-9a-f]+/.exec(srvOut)?.[0]; + check('boot + token bootstrap', !!token); + + const login = await fetch(`${ORIGIN}/api/v1/auth/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', Origin: ORIGIN }, + body: JSON.stringify({ token }), + }); + const cookie = login.headers.get('set-cookie')?.split(';')[0] ?? ''; + check('login → cookie', login.status === 200 && cookie.startsWith('arb_session=')); + + // Client WS minimal multiplexé + function wsClient() { + const ws = new WebSocket(`ws://127.0.0.1:${PORT}/ws`, { headers: { Origin: ORIGIN, Cookie: cookie } }); + ws.binaryType = 'arraybuffer'; + const state = { msgs: [], outputs: new Map(), resyncs: new Map(), bytes: new Map() }; + ws.on('message', (data, isBinary) => { + if (!isBinary) { + state.msgs.push(JSON.parse(String(data))); + return; + } + const buf = Buffer.from(data); + const type = buf.readUInt8(0); + const channel = buf.readUInt32LE(1); + const payload = buf.subarray(5); + if (type === 0x02) { + state.resyncs.set(channel, (state.resyncs.get(channel) ?? 0) + 1); + state.bytes.set(channel, 0); // resync : le compteur ACK repart de zéro + state.outputs.set(channel, payload.toString('latin1')); + } else { + state.outputs.set(channel, ((state.outputs.get(channel) ?? '') + payload.toString('latin1')).slice(-300000)); + const total = (state.bytes.get(channel) ?? 0) + payload.length; + state.bytes.set(channel, total); + // ACK tous les 64 Kio traités (invariant : pas d'ACK <= LOW_WATERMARK) + const acked = state.lastAck?.get(channel) ?? 0; + if (total - acked >= 64 * 1024) { + (state.lastAck ??= new Map()).set(channel, total); + ws.send(JSON.stringify({ type: 'ack', channel, bytes: total })); + } + } + }); + const waitMsg = async (pred, timeout = 8000) => { + const t0 = Date.now(); + while (Date.now() - t0 < timeout) { + const m = state.msgs.find(pred); + if (m) return m; + await sleep(50); + } + return null; + }; + return { ws, state, waitMsg, send: (m) => ws.send(JSON.stringify(m)) }; + } + + const c1 = wsClient(); + await new Promise((res, rej) => (c1.ws.on('open', res), c1.ws.on('error', rej))); + c1.send({ type: 'hello', protocol: 1 }); + check('hello_ok', !!(await c1.waitMsg((m) => m.type === 'hello_ok'))); + c1.send({ type: 'sub', topics: ['sessions'] }); + + // Spawn bash + attach + const created = await fetch(`${ORIGIN}/api/v1/sessions`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', Origin: ORIGIN, Cookie: cookie }, + body: JSON.stringify({ cwd: tmp, command: 'bash' }), + }); + const sid = (await created.json()).session.id; + check('spawn bash', created.status === 201 && !!sid); + + c1.send({ type: 'attach', sessionId: sid, mode: 'interactive', cols: 120, rows: 32 }); + const att = await c1.waitMsg((m) => m.type === 'attached'); + check('attach interactif + controlling', att?.controlling === true); + const ch = att.channel; + + // stdin → output + await sleep(400); + c1.send({ type: 'stdin', channel: ch, data: 'echo ACCEPT-$((21*2))\r' }); + await sleep(800); + check('stdin → output', (c1.state.outputs.get(ch) ?? '').includes('ACCEPT-42')); + + // Flood 10 Mo (base64 → ~14 Mo de flux TTY) avec ACK, RSS serveur borné + const rssBefore = readFileSync(`/proc/${srv.pid}/status`, 'utf8').match(/VmRSS:\s+(\d+) kB/)?.[1]; + c1.send({ type: 'stdin', channel: ch, data: 'head -c 10485760 /dev/urandom | base64; echo FLOOD-END-MARKER\r' }); + const t0 = Date.now(); + let floodOk = false; + let received = 0; + while (Date.now() - t0 < 60000) { + received = c1.state.bytes.get(ch) ?? 0; + if ((c1.state.outputs.get(ch) ?? '').includes('FLOOD-END-MARKER')) { + floodOk = true; + break; + } + await sleep(100); + } + const rssAfter = readFileSync(`/proc/${srv.pid}/status`, 'utf8').match(/VmRSS:\s+(\d+) kB/)?.[1]; + const rssMb = Math.round((Number(rssAfter) - Number(rssBefore)) / 1024); + check('flood 10 Mo complet via ACK', floodOk && received > 13_000_000, `${(received / 1048576).toFixed(1)} Mo reçus en ${Date.now() - t0} ms`); + check('RSS serveur borné pendant le flood', Number(rssAfter) < 300 * 1024, `Δ ${rssMb} Mo (RSS ${Math.round(Number(rssAfter) / 1024)} Mo)`); + + // Déconnexion brutale → la session survit → reconnexion + replay + c1.ws.terminate(); + await sleep(2000); + const c2 = wsClient(); + await new Promise((res, rej) => (c2.ws.on('open', res), c2.ws.on('error', rej))); + c2.send({ type: 'hello', protocol: 1 }); + await c2.waitMsg((m) => m.type === 'hello_ok'); + c2.send({ type: 'attach', sessionId: sid, mode: 'interactive', cols: 100, rows: 30 }); + const att2 = await c2.waitMsg((m) => m.type === 'attached'); + await sleep(500); + const replay = c2.state.outputs.get(att2.channel) ?? ''; + check('reconnexion : session vivante + replay', att2?.controlling === true && c2.state.resyncs.get(att2.channel) === 1 && replay.length > 1000, `replay ${(replay.length / 1024).toFixed(0)} Kio`); + c2.send({ type: 'stdin', channel: att2.channel, data: 'echo AFTER-RECONNECT\r' }); + await sleep(800); + check('terminal utilisable après reconnexion', (c2.state.outputs.get(att2.channel) ?? '').includes('AFTER-RECONNECT')); + + // Kill + notification session_exit (sub) + c2.send({ type: 'sub', topics: ['sessions'] }); + await fetch(`${ORIGIN}/api/v1/sessions/${sid}`, { method: 'DELETE', headers: { Origin: ORIGIN, Cookie: cookie } }); + const exited = await c2.waitMsg((m) => m.type === 'session_exit' && m.sessionId === sid, 8000); + const detached = await c2.waitMsg((m) => m.type === 'detached' && m.reason === 'session_exit', 8000); + check('kill → session_exit + detached broadcast', !!exited && !!detached); + c2.ws.close(); +} catch (err) { + check('exception', false, String(err)); +} finally { + srv.kill('SIGTERM'); + await sleep(1500); + check('arrêt propre du daemon (SIGTERM)', srv.exitCode === 0 || srv.signalCode === null || srv.exitCode === null); + rmSync(tmp, { recursive: true, force: true }); + const failed = results.filter((r) => !r.ok); + console.log(failed.length === 0 ? '\nACCEPTANCE P1: ALL GREEN' : `\nACCEPTANCE P1: ${failed.length} FAILURE(S)`); + process.exit(failed.length === 0 ? 0 : 1); +} diff --git a/packages/server/scripts/copy-web.mjs b/packages/server/scripts/copy-web.mjs new file mode 100644 index 0000000..736a7b1 --- /dev/null +++ b/packages/server/scripts/copy-web.mjs @@ -0,0 +1,31 @@ +#!/usr/bin/env node +// Embarque la SPA buildée (packages/web/dist) dans public/ du package server, +// pour que le tarball npm soit autonome. Branché sur le hook "prepack". +// ARBORETUM_PACK_NO_WEB=1 : mode tolérant (CI/smoke) — placeholder à la place du front. +import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const serverDir = join(dirname(fileURLToPath(import.meta.url)), '..'); +const webDist = join(serverDir, '..', 'web', 'dist'); +const publicDir = join(serverDir, 'public'); + +if (!existsSync(webDist)) { + if (process.env.ARBORETUM_PACK_NO_WEB === '1') { + rmSync(publicDir, { recursive: true, force: true }); + mkdirSync(publicDir, { recursive: true }); + writeFileSync( + join(publicDir, 'index.html'), + '\n\nArboretum\n' + + '

Arboretum server is running, but this package was built without the web UI.

\n\n', + ); + console.log('copy-web: ARBORETUM_PACK_NO_WEB=1 — wrote placeholder public/index.html'); + process.exit(0); + } + console.error(`copy-web: ${webDist} not found — run npm run build -w @arboretum/web first`); + process.exit(1); +} + +rmSync(publicDir, { recursive: true, force: true }); +cpSync(webDist, publicDir, { recursive: true }); +console.log(`copy-web: copied ${webDist} -> ${publicDir}`); diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts index bc9deb3..4d5fc18 100644 --- a/packages/server/src/app.ts +++ b/packages/server/src/app.ts @@ -73,7 +73,11 @@ export function buildApp(config: Config, db: Db, serverVersion: string): AppBund registerAuthRoutes(app, auth, limiter, serverVersion); registerSessionRoutes(app, manager); - registerWsGateway(app, manager, serverVersion); + // La route websocket doit être déclarée APRÈS le chargement du plugin (contexte + // encapsulé) — sinon le handler reçoit la signature REST (request, reply). + void app.register(async (scoped) => { + registerWsGateway(scoped, manager, serverVersion); + }); // SPA buildée embarquée dans le paquet npm (public/) — absente en dev (vite dev sert le front) const publicDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'public'); diff --git a/packages/server/src/auth/service.ts b/packages/server/src/auth/service.ts index dfc2d07..197c030 100644 --- a/packages/server/src/auth/service.ts +++ b/packages/server/src/auth/service.ts @@ -69,7 +69,9 @@ export class AuthService { const expected = this.sign(payload); if (sig.length !== expected.length || !timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return null; const [tokenId, expiresStr] = payload.split('.'); - if (!tokenId || !expiresStr || Number(expiresStr) < Date.now()) return null; + if (!tokenId || !expiresStr) return null; + const expires = Number(expiresStr); + if (!Number.isFinite(expires) || expires < Date.now()) return null; const row = this.db .prepare('SELECT id, label FROM auth_tokens WHERE id = ? AND revoked_at IS NULL') .get(tokenId) as { id: string; label: string } | undefined; diff --git a/packages/server/src/core/pty-manager.ts b/packages/server/src/core/pty-manager.ts index 806dfec..5631782 100644 --- a/packages/server/src/core/pty-manager.ts +++ b/packages/server/src/core/pty-manager.ts @@ -244,14 +244,13 @@ export class PtyManager extends EventEmitter { private handleExit(s: ManagedSession, exitCode: number | null, signal: number | null): void { s.exited = { exitCode, signal }; if (s.killTimer) clearTimeout(s.killTimer); - this.db - .prepare('UPDATE sessions SET ended_at = ?, exit_code = ? WHERE id = ?') - .run(new Date().toISOString(), exitCode, s.id); + const endedAt = new Date().toISOString(); + this.db.prepare('UPDATE sessions SET ended_at = ?, exit_code = ? WHERE id = ?').run(endedAt, exitCode, s.id); for (const c of s.clients) c.onDetached('session_exit'); s.clients.clear(); this.live.delete(s.id); this.emit('session_exit', { sessionId: s.id, exitCode, signal }); - this.emit('session_update', { ...this.summarize(s), live: false, status: 'exited' }); + this.emit('session_update', { ...this.summarize(s), live: false, status: 'exited', endedAt }); } private summarize(s: ManagedSession): SessionSummary { diff --git a/packages/server/src/core/ring-buffer.ts b/packages/server/src/core/ring-buffer.ts index dec462b..0e8a6a6 100644 --- a/packages/server/src/core/ring-buffer.ts +++ b/packages/server/src/core/ring-buffer.ts @@ -22,20 +22,23 @@ export class RingBuffer { } write(chunk: Buffer): void { + // Chunk plus grand que la fenêtre : on ne stocke que la queue, mais en comptant + // les octets sautés dans l'offset pour préserver l'invariant « l'octet k du flux + // est à la position k % capacity » (sinon tail/readFrom rendent la fenêtre désordonnée). if (chunk.length >= this.capacity) { - chunk.copy(this.buf, 0, chunk.length - this.capacity); - this.length = this.capacity; - } else { - const writePos = this.total % this.capacity; - const tailSpace = this.capacity - writePos; - if (chunk.length <= tailSpace) { - chunk.copy(this.buf, writePos); - } else { - chunk.copy(this.buf, writePos, 0, tailSpace); - chunk.copy(this.buf, 0, tailSpace); - } - this.length = Math.min(this.capacity, this.length + chunk.length); + const skipped = chunk.length - this.capacity; + this.total += skipped; + chunk = chunk.subarray(skipped); } + const writePos = this.total % this.capacity; + const tailSpace = this.capacity - writePos; + if (chunk.length <= tailSpace) { + chunk.copy(this.buf, writePos); + } else { + chunk.copy(this.buf, writePos, 0, tailSpace); + chunk.copy(this.buf, 0, tailSpace); + } + this.length = Math.min(this.capacity, this.length + chunk.length); this.total += chunk.length; } diff --git a/packages/server/test/app.e2e.test.ts b/packages/server/test/app.e2e.test.ts new file mode 100644 index 0000000..8767026 --- /dev/null +++ b/packages/server/test/app.e2e.test.ts @@ -0,0 +1,270 @@ +import { mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; +import { buildApp, type AppBundle } from '../src/app.js'; +import { openDb, type Db } from '../src/db/index.js'; +import type { Config } from '../src/config.js'; + +// Aucun vrai PTY en CI : le module node-pty est remplacé par un faux inerte. +vi.mock('@homebridge/node-pty-prebuilt-multiarch', () => { + class FakePty { + pid = 424242; + write = vi.fn(); + resize = vi.fn(); + pause = vi.fn(); + resume = vi.fn(); + kill = vi.fn(); + onData(_cb: (d: string) => void): { dispose: () => void } { + return { dispose: () => {} }; + } + onExit(_cb: (e: { exitCode: number; signal?: number }) => void): { dispose: () => void } { + return { dispose: () => {} }; + } + } + return { default: { spawn: (): FakePty => new FakePty() } }; +}); + +process.env.ARBORETUM_LOG = 'silent'; + +const PORT = 7317; +const ALLOWED_ORIGIN = 'https://arboretum.test-tailnet.example'; + +interface TestApp { + bundle: AppBundle; + db: Db; + token: string; +} + +let dir: string; +const apps: TestApp[] = []; + +function makeApp(name: string): TestApp { + const dbPath = join(dir, `${name}.db`); + const db = openDb(dbPath); + const config: Config = { + port: PORT, + bind: '127.0.0.1', + dbPath, + dataDir: dir, + allowedOrigins: [ALLOWED_ORIGIN], + printToken: false, + }; + const bundle = buildApp(config, db, '0.0.0-test'); + const token = bundle.auth.ensureBootstrapToken(); + if (!token) throw new Error('bootstrap token attendu sur une base vierge'); + const app: TestApp = { bundle, db, token }; + apps.push(app); + return app; +} + +beforeAll(() => { + dir = mkdtempSync(join(tmpdir(), 'arboretum-e2e-')); +}); + +afterAll(async () => { + for (const a of apps) await a.bundle.app.close(); + for (const a of apps) a.db.close(); + rmSync(dir, { recursive: true, force: true }); +}); + +describe('app e2e — auth, origin et sessions', () => { + let t: TestApp; + let cookieValue: string; + + // Le rate limiter du login est GLOBAL à l'app : un seul login réussi ici, + // toutes les variantes d'échec vivent dans le describe rate limit (app dédiée). + beforeAll(async () => { + t = makeApp('main'); + const res = await t.bundle.app.inject({ method: 'POST', url: '/api/v1/auth/login', payload: { token: t.token } }); + expect(res.statusCode).toBe(200); + expect(res.json()).toEqual({ ok: true, label: 'initial' }); + const cookie = res.cookies.find((c) => c.name === 'arb_session'); + expect(cookie).toBeDefined(); + expect(cookie?.httpOnly).toBe(true); + expect(cookie?.sameSite).toBe('Strict'); + cookieValue = cookie!.value; + }); + + it('routes API sans auth → 401', async () => { + for (const url of ['/api/v1/sessions', '/api/v1/auth/me']) { + const res = await t.bundle.app.inject({ method: 'GET', url }); + expect(res.statusCode).toBe(401); + expect(res.json()).toMatchObject({ error: { code: 'UNAUTHORIZED' } }); + } + const post = await t.bundle.app.inject({ method: 'POST', url: '/api/v1/sessions', payload: { cwd: tmpdir() } }); + expect(post.statusCode).toBe(401); + }); + + it('cookie émis au login → /me identifié', async () => { + const me = await t.bundle.app.inject({ + method: 'GET', + url: '/api/v1/auth/me', + cookies: { arb_session: cookieValue }, + }); + expect(me.statusCode).toBe(200); + expect(me.json()).toEqual({ ok: true, tokenLabel: 'initial', serverVersion: '0.0.0-test' }); + }); + + it('cookie altéré → 401', async () => { + const tampered = cookieValue.slice(0, -1) + (cookieValue.endsWith('a') ? 'b' : 'a'); + const me = await t.bundle.app.inject({ method: 'GET', url: '/api/v1/auth/me', cookies: { arb_session: tampered } }); + expect(me.statusCode).toBe(401); + }); + + it('Bearer token accepté sur les routes API', async () => { + const res = await t.bundle.app.inject({ + method: 'GET', + url: '/api/v1/sessions', + headers: { authorization: `Bearer ${t.token}` }, + }); + expect(res.statusCode).toBe(200); + expect(res.json()).toEqual({ sessions: [] }); + }); + + it('Origin interdite → 403 même avec un Bearer valide, et même sur la route publique de login', async () => { + const res = await t.bundle.app.inject({ + method: 'GET', + url: '/api/v1/sessions', + headers: { authorization: `Bearer ${t.token}`, origin: 'https://evil.example' }, + }); + expect(res.statusCode).toBe(403); + expect(res.json()).toMatchObject({ error: { code: 'BAD_ORIGIN' } }); + + const login = await t.bundle.app.inject({ + method: 'POST', + url: '/api/v1/auth/login', + headers: { origin: 'https://evil.example' }, + payload: { token: t.token }, + }); + expect(login.statusCode).toBe(403); + }); + + it('Origins autorisées : loopback du port configuré et --allow-origin', async () => { + for (const origin of [`http://127.0.0.1:${PORT}`, `http://localhost:${PORT}`, ALLOWED_ORIGIN]) { + const res = await t.bundle.app.inject({ + method: 'GET', + url: '/api/v1/sessions', + headers: { authorization: `Bearer ${t.token}`, origin }, + }); + expect(res.statusCode, `origin ${origin}`).toBe(200); + } + }); + + it('POST /sessions : cwd relatif → 400', async () => { + const res = await t.bundle.app.inject({ + method: 'POST', + url: '/api/v1/sessions', + headers: { authorization: `Bearer ${t.token}` }, + payload: { cwd: 'relative/path' }, + }); + expect(res.statusCode).toBe(400); + expect(res.json()).toMatchObject({ error: { code: 'BAD_REQUEST' } }); + }); + + it('POST /sessions : cwd absent ou non-string → 400', async () => { + const res1 = await t.bundle.app.inject({ + method: 'POST', + url: '/api/v1/sessions', + headers: { authorization: `Bearer ${t.token}` }, + payload: {}, + }); + expect(res1.statusCode).toBe(400); + const res2 = await t.bundle.app.inject({ + method: 'POST', + url: '/api/v1/sessions', + headers: { authorization: `Bearer ${t.token}` }, + payload: { cwd: 42 }, + }); + expect(res2.statusCode).toBe(400); + }); + + it('POST /sessions : cwd inexistant → 400 SPAWN_FAILED', async () => { + const res = await t.bundle.app.inject({ + method: 'POST', + url: '/api/v1/sessions', + headers: { authorization: `Bearer ${t.token}` }, + payload: { cwd: '/definitely/not/a/dir' }, + }); + expect(res.statusCode).toBe(400); + expect(res.json()).toMatchObject({ error: { code: 'SPAWN_FAILED' } }); + }); + + it('POST /sessions : command inconnue → 400', async () => { + const res = await t.bundle.app.inject({ + method: 'POST', + url: '/api/v1/sessions', + headers: { authorization: `Bearer ${t.token}` }, + payload: { cwd: tmpdir(), command: 'zsh' }, + }); + expect(res.statusCode).toBe(400); + }); + + it('POST /sessions valide (bash, pty mocké) → 201 puis listée', async () => { + const res = await t.bundle.app.inject({ + method: 'POST', + url: '/api/v1/sessions', + headers: { authorization: `Bearer ${t.token}` }, + payload: { cwd: tmpdir(), command: 'bash' }, + }); + expect(res.statusCode).toBe(201); + const { session } = res.json() as { session: { id: string; cwd: string; command: string; live: boolean; status: string } }; + expect(session).toMatchObject({ cwd: tmpdir(), command: 'bash', live: true, status: 'running' }); + + const list = await t.bundle.app.inject({ + method: 'GET', + url: '/api/v1/sessions', + headers: { authorization: `Bearer ${t.token}` }, + }); + const sessions = (list.json() as { sessions: Array<{ id: string }> }).sessions; + expect(sessions.some((s) => s.id === session.id)).toBe(true); + }); + + it('DELETE /sessions/:id inconnu → 404', async () => { + const res = await t.bundle.app.inject({ + method: 'DELETE', + url: '/api/v1/sessions/unknown-id', + headers: { authorization: `Bearer ${t.token}` }, + }); + expect(res.statusCode).toBe(404); + }); + + it('logout → cookie effacé', async () => { + const res = await t.bundle.app.inject({ method: 'POST', url: '/api/v1/auth/logout', cookies: { arb_session: cookieValue } }); + expect(res.statusCode).toBe(200); + const cleared = res.cookies.find((c) => c.name === 'arb_session'); + expect(cleared?.value).toBe(''); + }); +}); + +describe('app e2e — rate limit du login', () => { + it('mauvais token et bodies invalides → 401, puis 429 dès le 4e essai (backoff après 3 échecs consécutifs)', async () => { + const t = makeApp('ratelimit-failures'); + const bad = await t.bundle.app.inject({ method: 'POST', url: '/api/v1/auth/login', payload: { token: 'arb_badbadbadbad' } }); + expect(bad.statusCode).toBe(401); + expect(bad.json()).toMatchObject({ error: { code: 'BAD_TOKEN' } }); + const noToken = await t.bundle.app.inject({ method: 'POST', url: '/api/v1/auth/login', payload: {} }); + expect(noToken.statusCode).toBe(401); + const numToken = await t.bundle.app.inject({ method: 'POST', url: '/api/v1/auth/login', payload: { token: 42 } }); + expect(numToken.statusCode).toBe(401); + + // 3 échecs consécutifs → verrou exponentiel : le backoff bloque AVANT la fenêtre 5/min + const blocked = await t.bundle.app.inject({ method: 'POST', url: '/api/v1/auth/login', payload: { token: 'arb_badbadbadbad' } }); + expect(blocked.statusCode).toBe(429); + expect(blocked.json()).toMatchObject({ error: { code: 'RATE_LIMITED' } }); + // même un login avec le BON token est bloqué pendant le backoff + const goodButLocked = await t.bundle.app.inject({ method: 'POST', url: '/api/v1/auth/login', payload: { token: t.token } }); + expect(goodButLocked.statusCode).toBe(429); + }); + + it('limite 5 tentatives/minute : la 6e → 429 même avec le bon token', async () => { + const t = makeApp('ratelimit-window'); + for (let i = 0; i < 5; i++) { + const res = await t.bundle.app.inject({ method: 'POST', url: '/api/v1/auth/login', payload: { token: t.token } }); + expect(res.statusCode, `tentative ${i + 1}`).toBe(200); + } + const blocked = await t.bundle.app.inject({ method: 'POST', url: '/api/v1/auth/login', payload: { token: t.token } }); + expect(blocked.statusCode).toBe(429); + expect(blocked.json()).toMatchObject({ error: { code: 'RATE_LIMITED' } }); + }); +}); diff --git a/packages/server/test/auth.test.ts b/packages/server/test/auth.test.ts new file mode 100644 index 0000000..b6b4996 --- /dev/null +++ b/packages/server/test/auth.test.ts @@ -0,0 +1,208 @@ +import { createHmac } from 'node:crypto'; +import { beforeEach, describe, expect, it } from 'vitest'; +import { AuthService, LoginRateLimiter, type AuthContext } from '../src/auth/service.js'; +import { getSetting, openDb, type Db } from '../src/db/index.js'; + +describe('AuthService', () => { + let db: Db; + let auth: AuthService; + + beforeEach(() => { + db = openDb(':memory:'); + auth = new AuthService(db); + }); + + describe('bootstrap token', () => { + it('créé une seule fois : le second appel renvoie null', () => { + const token = auth.ensureBootstrapToken(); + expect(token).toMatch(/^arb_[0-9a-f]{48}$/); + expect(auth.ensureBootstrapToken()).toBeNull(); + }); + + it('recréé si tous les tokens sont révoqués', () => { + const first = auth.ensureBootstrapToken(); + expect(first).not.toBeNull(); + db.prepare('UPDATE auth_tokens SET revoked_at = ?').run(new Date().toISOString()); + const second = auth.ensureBootstrapToken(); + expect(second).not.toBeNull(); + expect(second).not.toBe(first); + }); + }); + + describe('verifyRawToken', () => { + it('token valide → contexte avec label', () => { + const raw = auth.ensureBootstrapToken(); + const ctx = auth.verifyRawToken(raw as string); + expect(ctx).not.toBeNull(); + expect(ctx?.label).toBe('initial'); + expect(typeof ctx?.tokenId).toBe('string'); + }); + + it('met à jour last_used_at au passage', () => { + const raw = auth.ensureBootstrapToken() as string; + const ctx = auth.verifyRawToken(raw) as AuthContext; + const row = db.prepare('SELECT last_used_at FROM auth_tokens WHERE id = ?').get(ctx.tokenId) as { last_used_at: string | null }; + expect(row.last_used_at).not.toBeNull(); + }); + + it('token inconnu, trop court ou trop long → null', () => { + auth.ensureBootstrapToken(); + expect(auth.verifyRawToken('arb_' + 'f'.repeat(48))).toBeNull(); // bon format, mauvaise valeur + expect(auth.verifyRawToken('short')).toBeNull(); // < 8 chars + expect(auth.verifyRawToken('a'.repeat(129))).toBeNull(); // > 128 chars + expect(auth.verifyRawToken('')).toBeNull(); + }); + + it('token révoqué → null', () => { + const raw = auth.ensureBootstrapToken() as string; + expect(auth.verifyRawToken(raw)).not.toBeNull(); + db.prepare('UPDATE auth_tokens SET revoked_at = ?').run(new Date().toISOString()); + expect(auth.verifyRawToken(raw)).toBeNull(); + }); + + it('createToken : deux tokens coexistent avec leurs labels', () => { + auth.ensureBootstrapToken(); + const raw2 = auth.createToken('laptop'); + expect(auth.verifyRawToken(raw2)?.label).toBe('laptop'); + }); + }); + + describe('cookies', () => { + let ctx: AuthContext; + + beforeEach(() => { + const raw = auth.ensureBootstrapToken() as string; + ctx = auth.verifyRawToken(raw) as AuthContext; + }); + + // Signe un payload arbitraire avec le vrai secret du serveur (lu en base), + // pour forger des cookies à la structure valide mais au contenu choisi. + function forge(payload: string): string { + const secretHex = getSetting(db, 'server_secret') as string; + const sig = createHmac('sha256', Buffer.from(secretHex, 'hex')).update(payload).digest('hex'); + return `${payload}.${sig}`; + } + + it('cookie émis puis vérifié → même tokenId', () => { + const cookie = auth.issueCookie(ctx); + const back = auth.verifyCookie(cookie); + expect(back).toEqual({ tokenId: ctx.tokenId, label: 'initial' }); + }); + + it('le secret persiste : un second AuthService sur la même base vérifie le cookie', () => { + const cookie = auth.issueCookie(ctx); + const auth2 = new AuthService(db); + expect(auth2.verifyCookie(cookie)?.tokenId).toBe(ctx.tokenId); + }); + + it('cookie altéré (signature ou payload) → null', () => { + const cookie = auth.issueCookie(ctx); + // un caractère de la signature inversé + const last = cookie.slice(-1); + const tamperedSig = cookie.slice(0, -1) + (last === 'a' ? 'b' : 'a'); + expect(auth.verifyCookie(tamperedSig)).toBeNull(); + // expiration repoussée dans le payload sans re-signer + const lastDot = cookie.lastIndexOf('.'); + const [tokenId, expires] = cookie.slice(0, lastDot).split('.') as [string, string]; + const inflated = `${tokenId}.${Number(expires) + 1_000_000}.${cookie.slice(lastDot + 1)}`; + expect(auth.verifyCookie(inflated)).toBeNull(); + }); + + it('cookie malformé ou absent → null', () => { + expect(auth.verifyCookie(undefined)).toBeNull(); + expect(auth.verifyCookie('')).toBeNull(); + expect(auth.verifyCookie('garbage')).toBeNull(); + expect(auth.verifyCookie('.sig')).toBeNull(); + expect(auth.verifyCookie('a.b.c.d')).toBeNull(); + }); + + it('cookie expiré forgé avec le vrai secret → null (la forge non expirée passe, preuve que seule l’expiration rejette)', () => { + const fresh = forge(`${ctx.tokenId}.${Date.now() + 10_000}`); + expect(auth.verifyCookie(fresh)?.tokenId).toBe(ctx.tokenId); + const expired = forge(`${ctx.tokenId}.${Date.now() - 1}`); + expect(auth.verifyCookie(expired)).toBeNull(); + }); + + it('cookie forgé avec tokenId vide → null', () => { + expect(auth.verifyCookie(forge(`.${Date.now() + 10_000}`))).toBeNull(); + }); + + it('cookie forgé avec expiration non numérique → null', () => { + expect(auth.verifyCookie(forge(`${ctx.tokenId}.notanumber`))).toBeNull(); + }); + + it('cookie valide mais token révoqué entre-temps → null', () => { + const cookie = auth.issueCookie(ctx); + db.prepare('UPDATE auth_tokens SET revoked_at = ? WHERE id = ?').run(new Date().toISOString(), ctx.tokenId); + expect(auth.verifyCookie(cookie)).toBeNull(); + }); + + it('cookie signé pour un tokenId inexistant → null', () => { + const cookie = forge(`00000000-0000-0000-0000-000000000000.${Date.now() + 10_000}`); + expect(auth.verifyCookie(cookie)).toBeNull(); + }); + }); +}); + +describe('LoginRateLimiter', () => { + it('5 tentatives par minute glissante, puis délai renvoyé', () => { + let t = 0; + const lim = new LoginRateLimiter(5, () => t); + for (let i = 0; i < 5; i++) expect(lim.check()).toBeNull(); + expect(lim.check()).toBe(60_000); // toutes les tentatives à t=0 + t = 59_999; + expect(lim.check()).toBe(1); // il reste 1 ms avant que la fenêtre glisse + t = 60_000; + expect(lim.check()).toBeNull(); // fenêtre vidée + }); + + it('la fenêtre glisse tentative par tentative', () => { + let t = 0; + const lim = new LoginRateLimiter(2, () => t); + expect(lim.check()).toBeNull(); // t=0 + t = 30_000; + expect(lim.check()).toBeNull(); // t=30s + t = 40_000; + expect(lim.check()).toBe(20_000); // bloqué par la tentative t=0 + t = 60_000; + expect(lim.check()).toBeNull(); // t=0 expirée, t=30s reste + }); + + it('backoff exponentiel à partir de 3 échecs consécutifs', () => { + let t = 0; + const lim = new LoginRateLimiter(1000, () => t); // plafond haut pour isoler le backoff + lim.recordFailure(); + lim.recordFailure(); + expect(lim.check()).toBeNull(); // 2 échecs : pas encore de lock + lim.recordFailure(); // 3e échec → 30 s + expect(lim.check()).toBe(30_000); + t = 29_999; + expect(lim.check()).toBe(1); + t = 30_000; + expect(lim.check()).toBeNull(); // lock levé + lim.recordFailure(); // 4e échec → 60 s + expect(lim.check()).toBe(60_000); + t = 90_000; + lim.recordFailure(); // 5e échec → 120 s + expect(lim.check()).toBe(120_000); + }); + + it('le backoff est plafonné à 30 minutes', () => { + let t = 0; + const lim = new LoginRateLimiter(1000, () => t); + for (let i = 0; i < 20; i++) lim.recordFailure(); + expect(lim.check()).toBe(30 * 60_000); + }); + + it('reset complet au succès', () => { + let t = 0; + const lim = new LoginRateLimiter(1000, () => t); + for (let i = 0; i < 5; i++) lim.recordFailure(); + expect(lim.check()).not.toBeNull(); // verrouillé + lim.recordSuccess(); + expect(lim.check()).toBeNull(); // déverrouillé immédiatement + lim.recordFailure(); + lim.recordFailure(); + expect(lim.check()).toBeNull(); // le compteur d'échecs consécutifs est bien reparti de zéro + }); +}); diff --git a/packages/server/test/pty-manager.test.ts b/packages/server/test/pty-manager.test.ts new file mode 100644 index 0000000..980ad02 --- /dev/null +++ b/packages/server/test/pty-manager.test.ts @@ -0,0 +1,416 @@ +import { tmpdir } from 'node:os'; +import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest'; +import { FLOW, REPLAY_TAIL_BYTES, type SessionSummary } from '@arboretum/shared'; +import { PtyManager, type ClientBinding } from '../src/core/pty-manager.js'; +import { openDb, type Db } from '../src/db/index.js'; + +interface FakePty { + pid: number; + file: string; + args: string[]; + opts: { cwd: string; cols: number; rows: number }; + write: Mock; + resize: Mock; + pause: Mock; + resume: Mock; + emitData(data: string): void; + emitExit(exitCode: number, signal?: number): void; +} + +const ptyMock = vi.hoisted(() => ({ instances: [] as unknown[] })); + +vi.mock('@homebridge/node-pty-prebuilt-multiarch', () => { + let nextPid = 100_000; + class FakePtyImpl { + pid = nextPid++; + write = vi.fn(); + resize = vi.fn(); + pause = vi.fn(); + resume = vi.fn(); + kill = vi.fn(); + private dataCbs: Array<(d: string) => void> = []; + private exitCbs: Array<(e: { exitCode: number; signal?: number }) => void> = []; + constructor( + readonly file: string, + readonly args: string[], + readonly opts: unknown, + ) {} + onData(cb: (d: string) => void): { dispose: () => void } { + this.dataCbs.push(cb); + return { dispose: () => {} }; + } + onExit(cb: (e: { exitCode: number; signal?: number }) => void): { dispose: () => void } { + this.exitCbs.push(cb); + return { dispose: () => {} }; + } + emitData(d: string): void { + for (const cb of this.dataCbs) cb(d); + } + emitExit(exitCode: number, signal?: number): void { + for (const cb of this.exitCbs) cb(signal === undefined ? { exitCode } : { exitCode, signal }); + } + } + return { + default: { + spawn: (file: string, args: string[], opts: unknown): FakePtyImpl => { + const p = new FakePtyImpl(file, args, opts); + ptyMock.instances.push(p); + return p; + }, + }, + }; +}); + +type BindingSpies = ClientBinding & { + sendOutput: Mock<(payload: Buffer) => void>; + sendResync: Mock<(payload: Buffer) => void>; + onDetached: Mock<(reason: 'session_exit' | 'replaced') => void>; + onControlChanged: Mock<(controlling: boolean) => void>; +}; + +let channelSeq = 1; +function makeBinding(mode: 'interactive' | 'observer'): BindingSpies { + return { + channel: channelSeq++, + mode, + controlling: false, + sentBytes: 0, + ackedBytes: 0, + lagging: false, + sendOutput: vi.fn(), + sendResync: vi.fn(), + onDetached: vi.fn(), + onControlChanged: vi.fn(), + }; +} + +function lastPty(): FakePty { + return ptyMock.instances.at(-1) as FakePty; +} + +describe('PtyManager (pty mocké)', () => { + let db: Db; + let manager: PtyManager; + const cwd = tmpdir(); + + beforeEach(() => { + ptyMock.instances.length = 0; + db = openDb(':memory:'); + manager = new PtyManager(db); + }); + + function spawnBash(): { summary: SessionSummary; pty: FakePty } { + const summary = manager.spawn({ cwd, command: 'bash' }); + return { summary, pty: lastPty() }; + } + + describe('spawn', () => { + it('émet session_update, insère en base et lance le pty avec le bon spec', () => { + const updates: SessionSummary[] = []; + manager.on('session_update', (s) => updates.push(s)); + const { summary, pty } = spawnBash(); + + expect(updates).toHaveLength(1); + expect(updates[0]).toEqual(summary); + expect(summary.status).toBe('running'); + expect(summary.live).toBe(true); + expect(summary.clients).toBe(0); + expect(summary.command).toBe('bash'); + + expect(pty.file).toBe('bash'); + expect(pty.args).toEqual(['--norc']); + expect(pty.opts.cwd).toBe(cwd); + expect(pty.opts.cols).toBe(120); + expect(pty.opts.rows).toBe(32); + + const row = db.prepare('SELECT cwd, command FROM sessions WHERE id = ?').get(summary.id) as { cwd: string; command: string }; + expect(row).toEqual({ cwd, command: 'bash' }); + expect(manager.get(summary.id)?.id).toBe(summary.id); + }); + + it('cwd inexistant → erreur statusCode 400, rien en base', () => { + expect(() => manager.spawn({ cwd: '/definitely/not/a/dir', command: 'bash' })).toThrowError( + expect.objectContaining({ statusCode: 400 }), + ); + const row = db.prepare('SELECT COUNT(*) AS n FROM sessions').get() as { n: number }; + expect(row.n).toBe(0); + }); + }); + + describe('attach', () => { + it('session inconnue → NOT_FOUND', () => { + const res = manager.attach('nope', makeBinding('interactive'), 80, 24); + expect(res).toEqual({ ok: false, code: 'NOT_FOUND' }); + }); + + it('resync à l’attach = queue du ring (REPLAY_TAIL_BYTES max)', () => { + const { summary, pty } = spawnBash(); + const chunks = ['A', 'B', 'C'].map((c) => c.repeat(100 * 1024)); + for (const c of chunks) pty.emitData(c); + + const b = makeBinding('interactive'); + const res = manager.attach(summary.id, b, 80, 24); + expect(res).toEqual({ ok: true, controlling: true }); + + expect(b.sendResync).toHaveBeenCalledTimes(1); + const payload = b.sendResync.mock.calls[0]![0]!; + const full = Buffer.from(chunks.join(''), 'ascii'); + expect(payload.length).toBe(REPLAY_TAIL_BYTES); + expect(payload.equals(full.subarray(full.length - REPLAY_TAIL_BYTES))).toBe(true); + // compteurs de flow control remis à zéro après le resync initial + expect(b.sentBytes).toBe(0); + expect(b.ackedBytes).toBe(0); + }); + + it('ring vide → resync avec payload vide', () => { + const { summary } = spawnBash(); + const b = makeBinding('interactive'); + manager.attach(summary.id, b, 80, 24); + expect(b.sendResync).toHaveBeenCalledTimes(1); + expect(b.sendResync.mock.calls[0]![0]!.length).toBe(0); + }); + + it('contrôle au premier interactif seulement, les observers ne comptent pas', () => { + const { summary, pty } = spawnBash(); + const obs = makeBinding('observer'); + const a = makeBinding('interactive'); + const b = makeBinding('interactive'); + + expect(manager.attach(summary.id, obs, 80, 24)).toEqual({ ok: true, controlling: false }); + expect(pty.resize).not.toHaveBeenCalled(); // un observer ne redimensionne pas + + expect(manager.attach(summary.id, a, 100, 30)).toEqual({ ok: true, controlling: true }); + expect(pty.resize).toHaveBeenCalledWith(100, 30); // le contrôleur impose sa taille + + expect(manager.attach(summary.id, b, 200, 50)).toEqual({ ok: true, controlling: false }); + expect(pty.resize).toHaveBeenCalledTimes(1); // pas de resize pour le non-contrôleur + expect(manager.get(summary.id)?.clients).toBe(3); + }); + }); + + describe('write / resize', () => { + it('observer read-only : write refusé', () => { + const { summary, pty } = spawnBash(); + const obs = makeBinding('observer'); + manager.attach(summary.id, obs, 80, 24); + expect(manager.write(summary.id, obs, 'ls\r')).toBe('not_controlling'); + expect(pty.write).not.toHaveBeenCalled(); + }); + + it('tout interactif peut écrire (même non contrôleur), session inconnue → gone', () => { + const { summary, pty } = spawnBash(); + const a = makeBinding('interactive'); + const b = makeBinding('interactive'); + manager.attach(summary.id, a, 80, 24); + manager.attach(summary.id, b, 80, 24); + expect(manager.write(summary.id, a, 'echo a\r')).toBe('ok'); + expect(manager.write(summary.id, b, 'echo b\r')).toBe('ok'); + expect(pty.write).toHaveBeenCalledTimes(2); + expect(manager.write('nope', a, 'x')).toBe('gone'); + }); + + it('resize ignoré pour les non-contrôleurs', () => { + const { summary, pty } = spawnBash(); + const a = makeBinding('interactive'); + const b = makeBinding('interactive'); + manager.attach(summary.id, a, 80, 24); + manager.attach(summary.id, b, 80, 24); + pty.resize.mockClear(); + manager.resize(summary.id, b, 50, 20); + expect(pty.resize).not.toHaveBeenCalled(); + manager.resize(summary.id, a, 90, 28); + expect(pty.resize).toHaveBeenCalledWith(90, 28); + }); + }); + + describe('flow control', () => { + it('pause() UNIQUEMENT quand tous les interactifs dépassent HIGH ; resume() quand le min repasse sous LOW', () => { + const { summary, pty } = spawnBash(); + const a = makeBinding('interactive'); + const b = makeBinding('interactive'); + manager.attach(summary.id, a, 80, 24); + manager.attach(summary.id, b, 80, 24); + + const chunk = 'x'.repeat(200 * 1024); // 200 Kio ASCII + pty.emitData(chunk); // a=200K, b=200K — sous HIGH (384K) + expect(pty.pause).not.toHaveBeenCalled(); + + manager.ack(summary.id, b, 200 * 1024); // b rattrape tout + pty.emitData(chunk); // a=400K > HIGH, b=200K < HIGH + expect(pty.pause).not.toHaveBeenCalled(); // un seul interactif au-dessus ne suffit pas + + pty.emitData(chunk); // a=600K, b=400K → TOUS au-dessus de HIGH + expect(a.sentBytes - a.ackedBytes).toBeGreaterThan(FLOW.HIGH_WATERMARK); + expect(b.sentBytes - b.ackedBytes).toBeGreaterThan(FLOW.HIGH_WATERMARK); + expect(pty.pause).toHaveBeenCalledTimes(1); + expect(pty.resume).not.toHaveBeenCalled(); + + manager.ack(summary.id, b, 600 * 1024); // outstanding b = 0 < LOW + expect(pty.resume).toHaveBeenCalledTimes(1); + expect(pty.pause).toHaveBeenCalledTimes(1); // pas de re-pause intempestive + }); + + it('les observers ne freinent jamais le PTY', () => { + const { summary, pty } = spawnBash(); + const a = makeBinding('interactive'); + const obs = makeBinding('observer'); + manager.attach(summary.id, a, 80, 24); + manager.attach(summary.id, obs, 80, 24); + + const chunk = 'y'.repeat(200 * 1024); + for (let i = 0; i < 3; i++) { + pty.emitData(chunk); + manager.ack(summary.id, a, a.sentBytes); // l'interactif suit le rythme + } + expect(obs.sentBytes - obs.ackedBytes).toBeGreaterThan(FLOW.HIGH_WATERMARK); + expect(pty.pause).not.toHaveBeenCalled(); + }); + + it('session sans interactif (observers seuls) : jamais de pause', () => { + const { summary, pty } = spawnBash(); + const obs = makeBinding('observer'); + manager.attach(summary.id, obs, 80, 24); + pty.emitData('z'.repeat(500 * 1024)); + expect(pty.pause).not.toHaveBeenCalled(); + }); + }); + + describe('client lagging', () => { + it('> LAGGING_BYTES sans ACK → flux coupé, puis resync au rattrapage', () => { + const { summary, pty } = spawnBash(); + const a = makeBinding('interactive'); + manager.attach(summary.id, a, 80, 24); + + const mib = 1024 * 1024; + const chunks = ['A', 'B', 'C', 'D'].map((c) => c.repeat(mib)); + + pty.emitData(chunks[0]!); // outstanding 1 Mio + pty.emitData(chunks[1]!); // outstanding 2 Mio — pas strictement > LAGGING_BYTES + expect(a.lagging).toBe(false); + expect(a.sendOutput).toHaveBeenCalledTimes(2); + + pty.emitData(chunks[2]!); // outstanding 3 Mio > 2 Mio → lagging + expect(a.lagging).toBe(true); + expect(a.sendOutput).toHaveBeenCalledTimes(3); // ce chunk est parti avant le marquage + // le seul interactif est lagging : le PTY ne doit pas rester en pause + expect(pty.resume).toHaveBeenCalled(); + + pty.emitData(chunks[3]!); // flux coupé pour le client lagging + expect(a.sendOutput).toHaveBeenCalledTimes(3); + + manager.ack(summary.id, a, 3 * mib); // rattrapage : outstanding 0 < LOW + expect(a.lagging).toBe(false); + expect(a.sendResync).toHaveBeenCalledTimes(2); // attach + rattrapage + const payload = a.sendResync.mock.calls[1]![0]!; + const full = Buffer.from(chunks.join(''), 'ascii'); + expect(payload.equals(full.subarray(full.length - REPLAY_TAIL_BYTES))).toBe(true); + expect(a.sentBytes).toBe(0); + expect(a.ackedBytes).toBe(0); + + // le flux reprend normalement après le resync + pty.emitData('tail'); + expect(a.sendOutput).toHaveBeenCalledTimes(4); + expect(a.sendOutput.mock.calls[3]![0]!.toString('ascii')).toBe('tail'); + }); + }); + + describe('detach et transfert de contrôle', () => { + it('detach du contrôleur → transfert au prochain interactif (onControlChanged)', () => { + const { summary } = spawnBash(); + const a = makeBinding('interactive'); + const b = makeBinding('interactive'); + const obs = makeBinding('observer'); + manager.attach(summary.id, a, 80, 24); + manager.attach(summary.id, obs, 80, 24); + manager.attach(summary.id, b, 80, 24); + expect(a.controlling).toBe(true); + expect(b.controlling).toBe(false); + + manager.detach(summary.id, a); + expect(b.controlling).toBe(true); + expect(b.onControlChanged).toHaveBeenCalledExactlyOnceWith(true); + expect(obs.onControlChanged).not.toHaveBeenCalled(); + expect(manager.get(summary.id)?.clients).toBe(2); + }); + + it('detach d’un non-contrôleur : aucun transfert', () => { + const { summary } = spawnBash(); + const a = makeBinding('interactive'); + const b = makeBinding('interactive'); + manager.attach(summary.id, a, 80, 24); + manager.attach(summary.id, b, 80, 24); + manager.detach(summary.id, b); + expect(a.controlling).toBe(true); + expect(a.onControlChanged).not.toHaveBeenCalled(); + }); + + it('detach du dernier interactif : un observer ne récupère jamais le contrôle', () => { + const { summary } = spawnBash(); + const a = makeBinding('interactive'); + const obs = makeBinding('observer'); + manager.attach(summary.id, a, 80, 24); + manager.attach(summary.id, obs, 80, 24); + manager.detach(summary.id, a); + expect(obs.controlling).toBe(false); + expect(obs.onControlChanged).not.toHaveBeenCalled(); + }); + + it('un nouvel interactif après detach du contrôleur reprend le contrôle', () => { + const { summary } = spawnBash(); + const a = makeBinding('interactive'); + manager.attach(summary.id, a, 80, 24); + manager.detach(summary.id, a); + const b = makeBinding('interactive'); + expect(manager.attach(summary.id, b, 80, 24)).toEqual({ ok: true, controlling: true }); + }); + }); + + describe('exit', () => { + it('onDetached(session_exit) pour tous, DB mise à jour, événements émis', () => { + const { summary, pty } = spawnBash(); + const a = makeBinding('interactive'); + const obs = makeBinding('observer'); + manager.attach(summary.id, a, 80, 24); + manager.attach(summary.id, obs, 80, 24); + + const exits: Array<{ sessionId: string; exitCode: number | null; signal: number | null }> = []; + const updates: SessionSummary[] = []; + manager.on('session_exit', (e) => exits.push(e)); + manager.on('session_update', (s) => updates.push(s)); + + pty.emitExit(0); + + expect(a.onDetached).toHaveBeenCalledExactlyOnceWith('session_exit'); + expect(obs.onDetached).toHaveBeenCalledExactlyOnceWith('session_exit'); + + expect(exits).toEqual([{ sessionId: summary.id, exitCode: 0, signal: null }]); + expect(updates).toHaveLength(1); + expect(updates[0]).toMatchObject({ id: summary.id, status: 'exited', live: false, exitCode: 0 }); + + const row = db.prepare('SELECT ended_at, exit_code FROM sessions WHERE id = ?').get(summary.id) as { ended_at: string | null; exit_code: number | null }; + expect(row.ended_at).not.toBeNull(); + expect(row.exit_code).toBe(0); + + expect(manager.get(summary.id)).toBeNull(); + const listed = manager.list().find((s) => s.id === summary.id); + expect(listed).toMatchObject({ status: 'exited', live: false, exitCode: 0 }); + }); + + it('exit avec signal : propagé dans session_exit', () => { + const { summary, pty } = spawnBash(); + const exits: Array<{ sessionId: string; exitCode: number | null; signal: number | null }> = []; + manager.on('session_exit', (e) => exits.push(e)); + pty.emitExit(1, 15); + expect(exits).toEqual([{ sessionId: summary.id, exitCode: 1, signal: 15 }]); + }); + + it('après exit : write → gone, attach → NOT_FOUND', () => { + const { summary, pty } = spawnBash(); + const a = makeBinding('interactive'); + manager.attach(summary.id, a, 80, 24); + pty.emitExit(0); + expect(manager.write(summary.id, a, 'x')).toBe('gone'); + expect(manager.attach(summary.id, makeBinding('interactive'), 80, 24)).toEqual({ ok: false, code: 'NOT_FOUND' }); + }); + }); +}); diff --git a/packages/server/test/ring-buffer.test.ts b/packages/server/test/ring-buffer.test.ts new file mode 100644 index 0000000..69564ed --- /dev/null +++ b/packages/server/test/ring-buffer.test.ts @@ -0,0 +1,247 @@ +import { describe, expect, it } from 'vitest'; +import { RingBuffer } from '../src/core/ring-buffer.js'; + +// Modèle de référence naïf : garde tout le flux et recalcule la fenêtre à la demande +class RefBuffer { + private data = Buffer.alloc(0); + constructor(readonly capacity: number) {} + write(chunk: Buffer): void { + this.data = Buffer.concat([this.data, chunk]); + } + get total(): number { + return this.data.length; + } + get windowStart(): number { + return Math.max(0, this.data.length - this.capacity); + } + tail(count: number): Buffer { + const window = this.data.subarray(this.windowStart); + return Buffer.from(window.subarray(Math.max(0, window.length - count))); + } + readFrom(offset: number): Buffer | null { + if (offset > this.total) return null; + if (offset === this.total) return Buffer.alloc(0); + if (offset < this.windowStart) return null; + return Buffer.from(this.data.subarray(offset)); + } +} + +function mulberry32(seed: number): () => number { + let a = seed >>> 0; + return () => { + a = (a + 0x6d2b79f5) >>> 0; + let t = a; + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + +const buf = (s: string): Buffer => Buffer.from(s, 'ascii'); + +describe('RingBuffer — écriture simple', () => { + it('écrit puis relit sans wrap', () => { + const ring = new RingBuffer(16); + ring.write(buf('hello')); + expect(ring.totalOffset).toBe(5); + expect(ring.windowStart).toBe(0); + expect(ring.tail(5).toString()).toBe('hello'); + expect(ring.tail(3).toString()).toBe('llo'); + expect(ring.tail(100).toString()).toBe('hello'); // borné à la fenêtre + expect(ring.tail(0).length).toBe(0); + }); + + it('écritures successives sans wrap', () => { + const ring = new RingBuffer(16); + ring.write(buf('abc')); + ring.write(buf('def')); + expect(ring.totalOffset).toBe(6); + expect(ring.tail(6).toString()).toBe('abcdef'); + }); + + it('chunk vide : no-op', () => { + const ring = new RingBuffer(8); + ring.write(buf('ab')); + ring.write(Buffer.alloc(0)); + expect(ring.totalOffset).toBe(2); + expect(ring.tail(8).toString()).toBe('ab'); + }); + + it('buffer vierge : tail vide, readFrom(0) vide', () => { + const ring = new RingBuffer(8); + expect(ring.totalOffset).toBe(0); + expect(ring.tail(8).length).toBe(0); + expect(ring.readFrom(0)?.length).toBe(0); + expect(ring.readFrom(1)).toBeNull(); + }); +}); + +describe('RingBuffer — wrap-around', () => { + it('wrap simple : le chunk chevauche la frontière du buffer', () => { + const ring = new RingBuffer(8); + ring.write(buf('01234')); // pos 0..5 + ring.write(buf('56789')); // 3 octets en queue, 2 au début + expect(ring.totalOffset).toBe(10); + expect(ring.windowStart).toBe(2); + expect(ring.tail(8).toString()).toBe('23456789'); + expect(ring.tail(4).toString()).toBe('6789'); + }); + + it('wrap-around multiple : la fenêtre reste les N derniers octets', () => { + const ring = new RingBuffer(8); + let stream = ''; + for (let i = 0; i < 10; i++) { + const chunk = String.fromCharCode(97 + i).repeat(3); // 'aaa', 'bbb'… + ring.write(buf(chunk)); + stream += chunk; + expect(ring.totalOffset).toBe(stream.length); + expect(ring.tail(8).toString()).toBe(stream.slice(Math.max(0, stream.length - 8))); + } + expect(ring.windowStart).toBe(30 - 8); + }); + + it('écriture qui finit exactement sur la frontière (tailSpace == chunk.length)', () => { + const ring = new RingBuffer(8); + ring.write(buf('abcd')); + ring.write(buf('efgh')); // remplit exactement jusqu'à la frontière + expect(ring.totalOffset).toBe(8); + expect(ring.tail(8).toString()).toBe('abcdefgh'); + ring.write(buf('ij')); // repart de la position 0 + expect(ring.tail(8).toString()).toBe('cdefghij'); + }); + + it('total multiple exact de la capacité (end === 0)', () => { + const ring = new RingBuffer(8); + ring.write(buf('abcdefgh')); + ring.write(buf('ijklmnop')); + expect(ring.totalOffset).toBe(16); + expect(ring.tail(8).toString()).toBe('ijklmnop'); + expect(ring.tail(3).toString()).toBe('nop'); + }); +}); + +describe('RingBuffer — chunk >= capacité', () => { + it('chunk == capacité sur un ring vierge', () => { + const ring = new RingBuffer(8); + ring.write(buf('abcdefgh')); + expect(ring.totalOffset).toBe(8); + expect(ring.windowStart).toBe(0); + expect(ring.tail(8).toString()).toBe('abcdefgh'); + }); + + it('chunk == 2x capacité sur un ring vierge (total reste aligné)', () => { + const ring = new RingBuffer(8); + ring.write(buf('0123456789abcdef')); + expect(ring.totalOffset).toBe(16); + expect(ring.windowStart).toBe(8); + expect(ring.tail(8).toString()).toBe('89abcdef'); + expect(ring.readFrom(8)?.toString()).toBe('89abcdef'); + expect(ring.readFrom(7)).toBeNull(); + }); + + // Invariant : l'octet k du flux vit à la position k % capacity, même quand un + // chunk dépasse la capacité (les octets sautés comptent dans l'offset). + it('chunk > capacité non aligné : la fenêtre reste ordonnée', () => { + const ring = new RingBuffer(8); + ring.write(buf('0123456789')); // 10 octets, total final 10 % 8 != 0 + expect(ring.totalOffset).toBe(10); + expect(ring.tail(8).toString()).toBe('23456789'); + }); + + it('chunk == capacité après un préfixe non aligné : fenêtre ordonnée', () => { + const ring = new RingBuffer(8); + ring.write(buf('abc')); + ring.write(buf('ABCDEFGH')); + expect(ring.tail(8).toString()).toBe('ABCDEFGH'); + }); +}); + +describe('RingBuffer — readFrom', () => { + function filled(): RingBuffer { + // total = 10, capacité 8 → fenêtre = offsets [2, 10) + const ring = new RingBuffer(8); + ring.write(buf('01234')); + ring.write(buf('56789')); + return ring; + } + + it('offset dans la fenêtre', () => { + const ring = filled(); + expect(ring.readFrom(5)?.toString()).toBe('56789'); + expect(ring.readFrom(9)?.toString()).toBe('9'); + }); + + it('offset == windowStart : toute la fenêtre', () => { + const ring = filled(); + expect(ring.readFrom(ring.windowStart)?.toString()).toBe('23456789'); + }); + + it('offset == total : buffer vide (rien de nouveau)', () => { + const ring = filled(); + const out = ring.readFrom(10); + expect(out).not.toBeNull(); + expect(out?.length).toBe(0); + }); + + it('offset sorti de fenêtre ou au-delà du total → null', () => { + const ring = filled(); + expect(ring.readFrom(1)).toBeNull(); // windowStart - 1 + expect(ring.readFrom(0)).toBeNull(); + expect(ring.readFrom(-1)).toBeNull(); + expect(ring.readFrom(11)).toBeNull(); // total + 1 + }); +}); + +describe('RingBuffer — totalOffset monotone', () => { + it('croît exactement de la taille de chaque chunk, jamais ne décroît', () => { + const ring = new RingBuffer(8); + const rand = mulberry32(0x5eed); + let expected = 0; + for (let i = 0; i < 200; i++) { + const size = Math.floor(rand() * 7); // 0..6 (< capacité) + ring.write(Buffer.alloc(size, 65 + (i % 26))); + expected += size; + expect(ring.totalOffset).toBe(expected); + expect(ring.windowStart).toBe(Math.max(0, expected - 8)); + } + }); +}); + +describe('RingBuffer — équivalence avec le modèle de référence (property test)', () => { + it('tail et readFrom identiques au modèle pour 500 écritures aléatoires (chunks < capacité)', () => { + const capacity = 64; + const ring = new RingBuffer(capacity); + const ref = new RefBuffer(capacity); + const rand = mulberry32(0xc0ffee); + for (let i = 0; i < 500; i++) { + const size = Math.floor(rand() * capacity); // 0..63 + const chunk = Buffer.alloc(size); + for (let j = 0; j < size; j++) chunk[j] = Math.floor(rand() * 256); + ring.write(chunk); + ref.write(chunk); + + expect(ring.totalOffset).toBe(ref.total); + expect(ring.windowStart).toBe(ref.windowStart); + for (const n of [0, 1, 7, capacity - 1, capacity, capacity + 1, 500]) { + expect(ring.tail(n).equals(ref.tail(n)), `tail(${n}) après ${i + 1} écritures`).toBe(true); + } + const offsets = [ + ref.windowStart - 1, + ref.windowStart, + ref.windowStart + Math.floor((ref.total - ref.windowStart) / 2), + ref.total - 1, + ref.total, + ref.total + 1, + ]; + for (const off of offsets) { + const a = ring.readFrom(off); + const b = ref.readFrom(off); + if (b === null) { + expect(a, `readFrom(${off}) après ${i + 1} écritures`).toBeNull(); + } else { + expect(a?.equals(b), `readFrom(${off}) après ${i + 1} écritures`).toBe(true); + } + } + } + }); +}); diff --git a/packages/server/tsconfig.tsbuildinfo b/packages/server/tsconfig.tsbuildinfo index 1ef9f47..07a954e 100644 --- a/packages/server/tsconfig.tsbuildinfo +++ b/packages/server/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/fast-uri/types/index.d.ts","../../node_modules/ajv/dist/compile/codegen/code.d.ts","../../node_modules/ajv/dist/compile/codegen/scope.d.ts","../../node_modules/ajv/dist/compile/codegen/index.d.ts","../../node_modules/ajv/dist/compile/rules.d.ts","../../node_modules/ajv/dist/compile/util.d.ts","../../node_modules/ajv/dist/compile/validate/subschema.d.ts","../../node_modules/ajv/dist/compile/errors.d.ts","../../node_modules/ajv/dist/compile/validate/index.d.ts","../../node_modules/ajv/dist/compile/validate/dataType.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/additionalItems.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/items2020.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/contains.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/dependencies.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/propertyNames.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/additionalProperties.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/not.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/anyOf.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/oneOf.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/if.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/index.d.ts","../../node_modules/ajv/dist/vocabularies/validation/limitNumber.d.ts","../../node_modules/ajv/dist/vocabularies/validation/multipleOf.d.ts","../../node_modules/ajv/dist/vocabularies/validation/pattern.d.ts","../../node_modules/ajv/dist/vocabularies/validation/required.d.ts","../../node_modules/ajv/dist/vocabularies/validation/uniqueItems.d.ts","../../node_modules/ajv/dist/vocabularies/validation/const.d.ts","../../node_modules/ajv/dist/vocabularies/validation/enum.d.ts","../../node_modules/ajv/dist/vocabularies/validation/index.d.ts","../../node_modules/ajv/dist/vocabularies/format/format.d.ts","../../node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedProperties.d.ts","../../node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedItems.d.ts","../../node_modules/ajv/dist/vocabularies/validation/dependentRequired.d.ts","../../node_modules/ajv/dist/vocabularies/discriminator/types.d.ts","../../node_modules/ajv/dist/vocabularies/discriminator/index.d.ts","../../node_modules/ajv/dist/vocabularies/errors.d.ts","../../node_modules/ajv/dist/types/json-schema.d.ts","../../node_modules/ajv/dist/types/jtd-schema.d.ts","../../node_modules/ajv/dist/runtime/validation_error.d.ts","../../node_modules/ajv/dist/compile/ref_error.d.ts","../../node_modules/ajv/dist/core.d.ts","../../node_modules/ajv/dist/compile/resolve.d.ts","../../node_modules/ajv/dist/compile/index.d.ts","../../node_modules/ajv/dist/types/index.d.ts","../../node_modules/ajv/dist/ajv.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/error.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/type.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/enum.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/elements.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/properties.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/discriminator.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/values.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/index.d.ts","../../node_modules/ajv/dist/jtd.d.ts","../../node_modules/@fastify/ajv-compiler/types/index.d.ts","../../node_modules/@fastify/error/types/index.d.ts","../../node_modules/fast-json-stringify/types/index.d.ts","../../node_modules/@fastify/fast-json-stringify-compiler/types/index.d.ts","../../node_modules/find-my-way/index.d.ts","../../node_modules/light-my-request/types/index.d.ts","../../node_modules/fastify/types/utils.d.ts","../../node_modules/fastify/types/schema.d.ts","../../node_modules/fastify/types/type-provider.d.ts","../../node_modules/fastify/types/reply.d.ts","../../node_modules/@types/node/compatibility/disposable.d.ts","../../node_modules/@types/node/compatibility/indexable.d.ts","../../node_modules/@types/node/compatibility/iterators.d.ts","../../node_modules/@types/node/compatibility/index.d.ts","../../node_modules/@types/node/globals.typedarray.d.ts","../../node_modules/@types/node/buffer.buffer.d.ts","../../node_modules/@types/node/globals.d.ts","../../node_modules/@types/node/web-globals/abortcontroller.d.ts","../../node_modules/@types/node/web-globals/domexception.d.ts","../../node_modules/@types/node/web-globals/events.d.ts","../../node_modules/buffer/index.d.ts","../../node_modules/undici-types/header.d.ts","../../node_modules/undici-types/readable.d.ts","../../node_modules/undici-types/file.d.ts","../../node_modules/undici-types/fetch.d.ts","../../node_modules/undici-types/formdata.d.ts","../../node_modules/undici-types/connector.d.ts","../../node_modules/undici-types/client.d.ts","../../node_modules/undici-types/errors.d.ts","../../node_modules/undici-types/dispatcher.d.ts","../../node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/undici-types/global-origin.d.ts","../../node_modules/undici-types/pool-stats.d.ts","../../node_modules/undici-types/pool.d.ts","../../node_modules/undici-types/handlers.d.ts","../../node_modules/undici-types/balanced-pool.d.ts","../../node_modules/undici-types/agent.d.ts","../../node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/undici-types/mock-agent.d.ts","../../node_modules/undici-types/mock-client.d.ts","../../node_modules/undici-types/mock-pool.d.ts","../../node_modules/undici-types/mock-errors.d.ts","../../node_modules/undici-types/proxy-agent.d.ts","../../node_modules/undici-types/env-http-proxy-agent.d.ts","../../node_modules/undici-types/retry-handler.d.ts","../../node_modules/undici-types/retry-agent.d.ts","../../node_modules/undici-types/api.d.ts","../../node_modules/undici-types/interceptors.d.ts","../../node_modules/undici-types/util.d.ts","../../node_modules/undici-types/cookies.d.ts","../../node_modules/undici-types/patch.d.ts","../../node_modules/undici-types/websocket.d.ts","../../node_modules/undici-types/eventsource.d.ts","../../node_modules/undici-types/filereader.d.ts","../../node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/undici-types/content-type.d.ts","../../node_modules/undici-types/cache.d.ts","../../node_modules/undici-types/index.d.ts","../../node_modules/@types/node/web-globals/fetch.d.ts","../../node_modules/@types/node/web-globals/navigator.d.ts","../../node_modules/@types/node/web-globals/storage.d.ts","../../node_modules/@types/node/assert.d.ts","../../node_modules/@types/node/assert/strict.d.ts","../../node_modules/@types/node/async_hooks.d.ts","../../node_modules/@types/node/buffer.d.ts","../../node_modules/@types/node/child_process.d.ts","../../node_modules/@types/node/cluster.d.ts","../../node_modules/@types/node/console.d.ts","../../node_modules/@types/node/constants.d.ts","../../node_modules/@types/node/crypto.d.ts","../../node_modules/@types/node/dgram.d.ts","../../node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/@types/node/dns.d.ts","../../node_modules/@types/node/dns/promises.d.ts","../../node_modules/@types/node/domain.d.ts","../../node_modules/@types/node/events.d.ts","../../node_modules/@types/node/fs.d.ts","../../node_modules/@types/node/fs/promises.d.ts","../../node_modules/@types/node/http.d.ts","../../node_modules/@types/node/http2.d.ts","../../node_modules/@types/node/https.d.ts","../../node_modules/@types/node/inspector.d.ts","../../node_modules/@types/node/inspector.generated.d.ts","../../node_modules/@types/node/module.d.ts","../../node_modules/@types/node/net.d.ts","../../node_modules/@types/node/os.d.ts","../../node_modules/@types/node/path.d.ts","../../node_modules/@types/node/perf_hooks.d.ts","../../node_modules/@types/node/process.d.ts","../../node_modules/@types/node/punycode.d.ts","../../node_modules/@types/node/querystring.d.ts","../../node_modules/@types/node/readline.d.ts","../../node_modules/@types/node/readline/promises.d.ts","../../node_modules/@types/node/repl.d.ts","../../node_modules/@types/node/sea.d.ts","../../node_modules/@types/node/sqlite.d.ts","../../node_modules/@types/node/stream.d.ts","../../node_modules/@types/node/stream/promises.d.ts","../../node_modules/@types/node/stream/consumers.d.ts","../../node_modules/@types/node/stream/web.d.ts","../../node_modules/@types/node/string_decoder.d.ts","../../node_modules/@types/node/test.d.ts","../../node_modules/@types/node/timers.d.ts","../../node_modules/@types/node/timers/promises.d.ts","../../node_modules/@types/node/tls.d.ts","../../node_modules/@types/node/trace_events.d.ts","../../node_modules/@types/node/tty.d.ts","../../node_modules/@types/node/url.d.ts","../../node_modules/@types/node/util.d.ts","../../node_modules/@types/node/v8.d.ts","../../node_modules/@types/node/vm.d.ts","../../node_modules/@types/node/wasi.d.ts","../../node_modules/@types/node/worker_threads.d.ts","../../node_modules/@types/node/zlib.d.ts","../../node_modules/@types/node/index.d.ts","../../node_modules/pino-std-serializers/index.d.ts","../../node_modules/sonic-boom/types/index.d.ts","../../node_modules/thread-stream/index.d.ts","../../node_modules/pino/pino.d.ts","../../node_modules/fastify/types/logger.d.ts","../../node_modules/fastify/types/plugin.d.ts","../../node_modules/fastify/types/register.d.ts","../../node_modules/fastify/types/instance.d.ts","../../node_modules/fastify/types/hooks.d.ts","../../node_modules/fastify/types/route.d.ts","../../node_modules/fastify/types/context.d.ts","../../node_modules/fastify/types/request.d.ts","../../node_modules/fastify/types/content-type-parser.d.ts","../../node_modules/fastify/types/errors.d.ts","../../node_modules/fastify/types/server-factory.d.ts","../../node_modules/fastify/fastify.d.ts","../../node_modules/@fastify/cookie/types/plugin.d.ts","../../node_modules/@types/ws/index.d.ts","../../node_modules/@fastify/websocket/types/index.d.ts","../../node_modules/@fastify/static/types/index.d.ts","./src/config.ts","./src/db/index.ts","./src/auth/service.ts","../../node_modules/@homebridge/node-pty-prebuilt-multiarch/typings/node-pty.d.ts","../shared/dist/protocol.d.ts","../shared/dist/api.d.ts","../shared/dist/index.d.ts","./src/core/ring-buffer.ts","./src/core/claude-launcher.ts","./src/core/pty-manager.ts","./src/routes/auth.ts","./src/routes/sessions.ts","../../node_modules/@types/ws/index.d.mts","./src/ws/gateway.ts","./src/app.ts","./src/index.ts"],"fileIdsList":[[102,106,115,131,180,197,198],[131,180,197,198,230,246,249,250,265],[131,180,197,198],[118,131,180,197,198],[131,180,192,197,198,230,246,247,249,265],[125,131,180,194,197,198,230,239,240,246,247,248,250,265],[131,177,178,180,197,198],[131,179,180,197,198],[180,197,198],[131,180,185,197,198,215],[131,180,181,186,191,197,198,200,212,223],[131,180,181,182,191,197,198,200],[126,127,128,131,180,197,198],[131,180,183,197,198,224],[131,180,184,185,192,197,198,201],[131,180,185,197,198,212,220],[131,180,186,188,191,197,198,200],[131,179,180,187,197,198],[131,180,188,189,197,198],[131,180,190,191,197,198],[131,179,180,191,197,198],[131,180,191,192,193,197,198,212,223],[131,180,191,192,193,197,198,207,212,215],[131,173,180,188,191,194,197,198,200,212,223],[131,180,191,192,194,195,197,198,200,212,220,223],[131,180,194,196,197,198,212,220,223],[129,130,131,132,133,134,135,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229],[131,180,191,197,198],[131,180,197,198,199,223],[131,180,188,191,197,198,200,212],[131,180,197,198,201],[131,180,197,198,202],[131,179,180,197,198,203],[131,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229],[131,180,197,198,205],[131,180,197,198,206],[131,180,191,197,198,207,208],[131,180,197,198,207,209,224,226],[131,180,192,197,198],[131,180,191,197,198,212,213,215],[131,180,197,198,214,215],[131,180,197,198,212,213],[131,180,197,198,215],[131,180,197,198,216],[131,177,180,197,198,212,217,223],[131,180,191,197,198,218,219],[131,180,197,198,218,219],[131,180,185,197,198,200,212,220],[131,180,197,198,221],[131,180,197,198,200,222],[131,180,194,197,198,206,223],[131,180,185,197,198,224],[131,180,197,198,212,225],[131,180,197,198,199,226],[131,180,197,198,227],[131,173,180,197,198],[131,173,180,191,193,197,198,203,212,215,223,225,226,228],[131,180,197,198,212,229],[131,180,191,194,196,197,198,200,212,220,223,229,230],[65,66,70,97,98,100,101,102,104,105,131,180,197,198],[63,64,131,180,197,198],[63,131,180,197,198],[65,105,131,180,197,198],[65,66,102,103,105,131,180,197,198],[105,131,180,197,198],[62,105,106,131,180,197,198],[65,66,104,105,131,180,197,198],[65,66,68,69,104,105,131,180,197,198],[65,66,67,104,105,131,180,197,198],[65,66,70,97,98,99,100,101,104,105,131,180,197,198],[65,70,99,100,101,102,104,105,114,131,180,197,198],[62,65,66,70,102,104,131,180,197,198],[70,105,131,180,197,198],[72,73,74,75,76,77,78,79,80,81,105,131,180,197,198],[95,105,131,180,197,198],[71,82,90,91,92,93,94,96,131,180,197,198],[95,105,107,131,180,197,198],[105,107,131,180,197,198],[105,108,109,110,111,112,113,131,180,197,198],[70,105,107,131,180,197,198],[75,105,131,180,197,198],[83,84,85,86,87,88,89,105,131,180,197,198],[106,131,180,197,198],[116,117,119,120,121,122,123,124,125,131,180,194,195,196,197,198,200,235,236,237,238,239,240,241,242,243,244,245],[122,123,124,131,180,197,198,240,242],[122,131,180,197,198,240],[117,131,180,197,198],[117,122,123,124,125,131,180,197,198,212,235,236,237,238,240,242],[117,120,121,122,123,124,125,131,180,194,197,198,200,235,237,239,240,242,243,246],[117,122,123,124,125,131,180,197,198,234,238,240,242],[122,124,131,180,197,198,235,238],[122,131,180,197,198,235,236,238,246],[122,123,124,131,180,197,198,235,238,240,242],[116,122,123,124,131,180,197,198,235,238,240,241],[117,120,122,123,124,125,131,180,197,198,235,238,239,241,242],[116,119,131,180,197,198,246],[122,131,180,194,195,196,197,198],[122,123,131,180,197,198,240],[131,180,194,195,196,197,198],[131,180,194,195,197,198],[131,180,194,197,198,212],[131,180,194,197,198,230],[131,180,191,197,198,228,231,232,233],[131,180,191,197,198,230],[131,180,191,197,198,228],[131,145,149,180,197,198,223],[131,145,180,197,198,212,223],[131,140,180,197,198],[131,142,145,180,197,198,220,223],[131,180,197,198,200,220],[131,180,197,198,230],[131,140,180,197,198,230],[131,142,145,180,197,198,200,223],[131,137,138,141,144,180,191,197,198,212,223],[131,145,152,180,197,198],[131,137,143,180,197,198],[131,145,166,167,180,197,198],[131,141,145,180,197,198,215,223,230],[131,166,180,197,198,230],[131,139,140,180,197,198,230],[131,145,180,197,198],[131,139,140,141,142,143,144,145,146,147,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,167,168,169,170,171,172,180,197,198],[131,145,160,180,197,198],[131,145,152,153,180,197,198],[131,143,145,153,154,180,197,198],[131,144,180,197,198],[131,137,140,145,180,197,198],[131,145,149,153,154,180,197,198],[131,149,180,197,198],[131,143,145,148,180,197,198,223],[131,137,142,145,152,180,197,198],[131,180,197,198,212],[131,140,145,166,180,197,198,228,230],[131,180,192,197,198,202,223,246,247,249,250,251,252,253,260,261,262,264],[131,180,185,197,198,252],[131,180,192,197,198,201,202,224],[131,180,181,197,198],[131,180,185,191,192,197,198,252,254,257,258,259],[131,180,197,198,211],[131,180,192,197,198,202,223,251,252,265],[131,180,197,198,246,253,257],[131,180,197,198,246,257,260],[131,180,197,198,246,257,260,263],[131,180,197,198,255],[131,180,197,198,255,256]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"df83c2a6c73228b625b0beb6669c7ee2a09c914637e2d35170723ad49c0f5cd4","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"84bcc7c6b06f4d643a55dc63b56be0c81d990f8d549b66ea615c553268774dc3","impliedFormat":1},{"version":"2d225e7bda2871c066a7079c88174340950fb604f624f2586d3ea27bb9e5f4ff","impliedFormat":1},{"version":"6a785f84e63234035e511817dd48ada756d984dd8f9344e56eb8b2bdcd8fd001","impliedFormat":1},{"version":"c1422d016f7df2ccd3594c06f2923199acd09898f2c42f50ea8159f1f856f618","impliedFormat":1},{"version":"2973b1b7857ca144251375b97f98474e9847a890331e27132d5a8b3aea9350a8","impliedFormat":1},{"version":"0eb6152d37c84d6119295493dfcc20c331c6fda1304a513d159cdaa599dcb78b","impliedFormat":1},{"version":"237df26f8c326ca00cd9d2deb40214a079749062156386b6d75bdcecc6988a6b","impliedFormat":1},{"version":"cd44995ee13d5d23df17a10213fed7b483fabfd5ea08f267ab52c07ce0b6b4da","impliedFormat":1},{"version":"58ce1486f851942bd2d3056b399079bc9cb978ec933fe9833ea417e33eab676e","impliedFormat":1},{"version":"7557d4d7f19f94341f4413575a3453ba7f6039c9591015bcf4282a8e75414043","impliedFormat":1},{"version":"a3b2cc16f3ce2d882eca44e1066f57a24751545f2a5e4a153d4de31b4cac9bb5","impliedFormat":1},{"version":"ac2b3b377d3068bfb6e1cb8889c99098f2c875955e2325315991882a74d92cc8","impliedFormat":1},{"version":"8deb39d89095469957f73bd194d11f01d9894b8c1f1e27fbf3f6e8122576b336","impliedFormat":1},{"version":"a38a9c41f433b608a0d37e645a31eecf7233ef3d3fffeb626988d3219f80e32f","impliedFormat":1},{"version":"8e1428dcba6a984489863935049893631170a37f9584c0479f06e1a5b1f04332","impliedFormat":1},{"version":"1fce9ecb87a2d3898941c60df617e52e50fb0c03c9b7b2ba8381972448327285","impliedFormat":1},{"version":"5ef0597b8238443908b2c4bf69149ed3894ac0ddd0515ac583d38c7595b151f1","impliedFormat":1},{"version":"ac52b775a80badff5f4ac329c5725a26bd5aaadd57afa7ad9e98b4844767312a","impliedFormat":1},{"version":"6ae5b4a63010c82bf2522b4ecfc29ffe6a8b0c5eea6b2b35120077e9ac54d7a1","impliedFormat":1},{"version":"dd7109c49f416f218915921d44f0f28975df78e04e437c62e1e1eb3be5e18a35","impliedFormat":1},{"version":"eee181112e420b345fc78422a6cc32385ede3d27e2eaf8b8c4ad8b2c29e3e52e","impliedFormat":1},{"version":"25fbe57c8ee3079e2201fe580578fab4f3a78881c98865b7c96233af00bf9624","impliedFormat":1},{"version":"62cc8477858487b4c4de7d7ae5e745a8ce0015c1592f398b63ee05d6e64ca295","impliedFormat":1},{"version":"cc2a9ec3cb10e4c0b8738b02c31798fad312d21ef20b6a2f5be1d077e9f5409d","impliedFormat":1},{"version":"4b4fadcda7d34034737598c07e2dca5d7e1e633cb3ba8dd4d2e6a7782b30b296","impliedFormat":1},{"version":"360fdc8829a51c5428636f1f83e7db36fef6c5a15ed4411b582d00a1c2bd6e97","impliedFormat":1},{"version":"1cf0d15e6ab1ecabbf329b906ae8543e6b8955133b7f6655f04d433e3a0597ab","impliedFormat":1},{"version":"7c9f98fe812643141502b30fb2b5ec56d16aaf94f98580276ae37b7924dd44a4","impliedFormat":1},{"version":"b3547893f24f59d0a644c52f55901b15a3fa1a115bc5ea9a582911469b9348b7","impliedFormat":1},{"version":"596e5b88b6ca8399076afcc22af6e6e0c4700c7cd1f420a78d637c3fb44a885e","impliedFormat":1},{"version":"adddf736e08132c7059ee572b128fdacb1c2650ace80d0f582e93d097ed4fbaf","impliedFormat":1},{"version":"d4cad9dc13e9c5348637170ddd5d95f7ed5fdfc856ddca40234fa55518bc99a6","impliedFormat":1},{"version":"d70675ba7ba7d02e52b7070a369957a70827e4b2bca2c1680c38a832e87b61fd","impliedFormat":1},{"version":"3be71f4ce8988a01e2f5368bdd58e1d60236baf511e4510ee9291c7b3729a27e","impliedFormat":1},{"version":"423d2ccc38e369a7527988d682fafc40267bcd6688a7473e59c5eea20a29b64f","impliedFormat":1},{"version":"2f9fde0868ed030277c678b435f63fcf03d27c04301299580a4017963cc04ce6","impliedFormat":1},{"version":"feeb73d48cc41c6dd23d17473521b0af877751504c30c18dc84267c8eeea429a","impliedFormat":1},{"version":"25f1159094dc0bf3a71313a74e0885426af21c5d6564a254004f2cadf9c5b052","impliedFormat":1},{"version":"cde493e09daad4bb29922fe633f760be9f0e8e2f39cdca999cce3b8690b5e13a","impliedFormat":1},{"version":"3d7f9eb12aface876f7b535cc89dcd416daf77f0b3573333f16ec0a70bcf902a","impliedFormat":1},{"version":"4304f640f7cb4724ea82441accb7c7607fa7207541182470d625adda99b2900b","impliedFormat":1},{"version":"e0205f04611bea8b5b82168065b8ef1476a8e96236201494eb8c785331c43118","impliedFormat":1},{"version":"62d26d8ba4fa15ab425c1b57a050ed76c5b0ecbffaa53f182110aa3a02405a07","impliedFormat":1},{"version":"9941cbf7ca695e95d588f5f1692ab040b078d44a95d231fa9a8f828186b7b77d","impliedFormat":1},{"version":"41b8775befd7ded7245a627e9f4de6110236688ce4c124d2d40c37bc1a3bfe05","impliedFormat":1},{"version":"a6613ee552418429af38391e37389036654a882c342a1b81f2711e8ddac597f2","impliedFormat":1},{"version":"da47cb979ae4a849f9b983f43ef34365b7050c4f5ae2ebf818195858774e1d67","impliedFormat":1},{"version":"ac3bcb82d7280fc313a967f311764258d18caf33db6d2b1a0243cde607ff01a0","impliedFormat":1},{"version":"c9b5632d6665177030428d02603aeac3e920d31ec83ac500b55d44c7da74bd84","impliedFormat":1},{"version":"46456824df16d60f243a7e386562b27bac838aaba66050b9bc0f31e1ab34c1f2","impliedFormat":1},{"version":"b91034069e217212d8dda6c92669ee9f180b4c36273b5244c3be2c657f9286c7","impliedFormat":1},{"version":"0697277dd829ac2610d68fe1b457c9e758105bb52d40e149d9c15e5e2fe6dca4","impliedFormat":1},{"version":"b0d06dbb409369169143ede5df1fb58b2fca8d44588e199bd624b6f6d966bf08","impliedFormat":1},{"version":"88dfdb2a44912a28aea3ebb657dc7fcec6ba59f7233005e3405824995b713dac","impliedFormat":1},{"version":"f38a45cbc0356dfb6455211e75ecd0f8aeaedbacd4cb8d6646c818d36a46d195","impliedFormat":1},{"version":"cc2d5d5687bdf9d7c49b6946b8769ac7abcbdcd1701d9bb9ca70a8bc1b003e8b","impliedFormat":1},{"version":"6f1fabd39b8c9a66a3232030a4b28ed4fb4f857dcffef0add3220dab4bbba77a","impliedFormat":1},{"version":"9c0623d67471ddc5b9d82b4e06252c746d54f7ae8ccff8701cd51c249f7e7694","impliedFormat":1},{"version":"71654f68a41362f16a2690ec215e583d3cfa913d49327e43c3c7f354022f67a3","impliedFormat":1},{"version":"f69b484edf398d636992757d587e7e38ea91844a66dbca9d682c9cf7858b77cf","impliedFormat":1},{"version":"37d852b3e6b30b974178674dbf2a7974a1ea4bbdbec26d0bdb8f34632cab94a2","impliedFormat":1},{"version":"c3ccf44723b474b5d7a87c53eed9711e81422f0e29b3e8f8f8d42775fa802243","impliedFormat":1},{"version":"faeaf9586ba4505448bd0fdb332509a7e3ecca5d8ae10208950a5d03e8b4ea28","impliedFormat":1},{"version":"c3a0295707fde0cb51ca784ecb799aa95ca71c40337b5ee4a81a8f7134dd7e92","impliedFormat":1},{"version":"6c7176368037af28cb72f2392010fa1cef295d6d6744bca8cfb54985f3a18c3e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"437e20f2ba32abaeb7985e0afe0002de1917bc74e949ba585e49feba65da6ca1","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"808069bba06b6768b62fd22429b53362e7af342da4a236ed2d2e1c89fcca3b4a","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e9c23ba78aabc2e0a27033f18737a6df754067731e69dc5f52823957d60a4b6","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b52476feb4a0cbcb25e5931b930fc73cb6643fb1a5060bf8a3dda0eeae5b4b68","affectsGlobalScope":true,"impliedFormat":1},{"version":"f9501cc13ce624c72b61f12b3963e84fad210fbdf0ffbc4590e08460a3f04eba","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0fa06ada475b910e2106c98c68b10483dc8811d0c14a8a8dd36efb2672485b29","impliedFormat":1},{"version":"33e5e9aba62c3193d10d1d33ae1fa75c46a1171cf76fef750777377d53b0303f","impliedFormat":1},{"version":"2b06b93fd01bcd49d1a6bd1f9b65ddcae6480b9a86e9061634d6f8e354c1468f","impliedFormat":1},{"version":"6a0cd27e5dc2cfbe039e731cf879d12b0e2dded06d1b1dedad07f7712de0d7f4","affectsGlobalScope":true,"impliedFormat":1},{"version":"13f5c844119c43e51ce777c509267f14d6aaf31eafb2c2b002ca35584cd13b29","impliedFormat":1},{"version":"e60477649d6ad21542bd2dc7e3d9ff6853d0797ba9f689ba2f6653818999c264","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"4c829ab315f57c5442c6667b53769975acbf92003a66aef19bce151987675bd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"b2ade7657e2db96d18315694789eff2ddd3d8aea7215b181f8a0b303277cc579","impliedFormat":1},{"version":"9855e02d837744303391e5623a531734443a5f8e6e8755e018c41d63ad797db2","impliedFormat":1},{"version":"4d631b81fa2f07a0e63a9a143d6a82c25c5f051298651a9b69176ba28930756d","impliedFormat":1},{"version":"836a356aae992ff3c28a0212e3eabcb76dd4b0cc06bcb9607aeef560661b860d","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"41670ee38943d9cbb4924e436f56fc19ee94232bc96108562de1a734af20dc2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"c906fb15bd2aabc9ed1e3f44eb6a8661199d6c320b3aa196b826121552cb3695","impliedFormat":1},{"version":"22295e8103f1d6d8ea4b5d6211e43421fe4564e34d0dd8e09e520e452d89e659","impliedFormat":1},{"version":"58647d85d0f722a1ce9de50955df60a7489f0593bf1a7015521efe901c06d770","impliedFormat":1},{"version":"9018e9493596e2d041fc531f5101a0d05f9d2e1311f4ae3c74345b1766158c08","impliedFormat":1},{"version":"a10f0e1854f3316d7ee437b79649e5a6ae3ae14ffe6322b02d4987071a95362e","impliedFormat":1},{"version":"e208f73ef6a980104304b0d2ca5f6bf1b85de6009d2c7e404028b875020fa8f2","impliedFormat":1},{"version":"d163b6bc2372b4f07260747cbc6c0a6405ab3fbcea3852305e98ac43ca59f5bc","impliedFormat":1},{"version":"e6fa9ad47c5f71ff733744a029d1dc472c618de53804eae08ffc243b936f87ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6f137d651076822d4fe884287e68fd61785a0d3d1fdb250a5059b691fa897db","impliedFormat":1},{"version":"24826ed94a78d5c64bd857570fdbd96229ad41b5cb654c08d75a9845e3ab7dde","impliedFormat":1},{"version":"8b479a130ccb62e98f11f136d3ac80f2984fdc07616516d29881f3061f2dd472","impliedFormat":1},{"version":"928af3d90454bf656a52a48679f199f64c1435247d6189d1caf4c68f2eaf921f","affectsGlobalScope":true,"impliedFormat":1},{"version":"bceb58df66ab8fb00170df20cd813978c5ab84be1d285710c4eb005d8e9d8efb","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"933921f0bb0ec12ef45d1062a1fc0f27635318f4d294e4d99de9a5493e618ca2","impliedFormat":1},{"version":"71a0f3ad612c123b57239a7749770017ecfe6b66411488000aba83e4546fde25","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"4f9d8ca0c417b67b69eeb54c7ca1bedd7b56034bb9bfd27c5d4f3bc4692daca7","impliedFormat":1},{"version":"814118df420c4e38fe5ae1b9a3bafb6e9c2aa40838e528cde908381867be6466","impliedFormat":1},{"version":"a3fc63c0d7b031693f665f5494412ba4b551fe644ededccc0ab5922401079c95","impliedFormat":1},{"version":"80523c00b8544a2000ae0143e4a90a00b47f99823eb7926c1e03c494216fc363","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"746911b62b329587939560deb5c036aca48aece03147b021fa680223255d5183","affectsGlobalScope":true,"impliedFormat":1},{"version":"18fd40412d102c5564136f29735e5d1c3b455b8a37f920da79561f1fde068208","impliedFormat":1},{"version":"c8d3e5a18ba35629954e48c4cc8f11dc88224650067a172685c736b27a34a4dc","impliedFormat":1},{"version":"f0be1b8078cd549d91f37c30c222c2a187ac1cf981d994fb476a1adc61387b14","affectsGlobalScope":true,"impliedFormat":1},{"version":"0aaed1d72199b01234152f7a60046bc947f1f37d78d182e9ae09c4289e06a592","impliedFormat":1},{"version":"2b55d426ff2b9087485e52ac4bc7cfafe1dc420fc76dad926cd46526567c501a","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"5b7aa3c4c1a5d81b411e8cb302b45507fea9358d3569196b27eb1a27ae3a90ef","affectsGlobalScope":true,"impliedFormat":1},{"version":"5987a903da92c7462e0b35704ce7da94d7fdc4b89a984871c0e2b87a8aae9e69","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea08a0345023ade2b47fbff5a76d0d0ed8bff10bc9d22b83f40858a8e941501c","impliedFormat":1},{"version":"47613031a5a31510831304405af561b0ffaedb734437c595256bb61a90f9311b","impliedFormat":1},{"version":"ae062ce7d9510060c5d7e7952ae379224fb3f8f2dd74e88959878af2057c143b","impliedFormat":1},{"version":"8a1a0d0a4a06a8d278947fcb66bf684f117bf147f89b06e50662d79a53be3e9f","affectsGlobalScope":true,"impliedFormat":1},{"version":"358765d5ea8afd285d4fd1532e78b88273f18cb3f87403a9b16fef61ac9fdcfe","impliedFormat":1},{"version":"9f55299850d4f0921e79b6bf344b47c420ce0f507b9dcf593e532b09ea7eeea1","impliedFormat":1},{"version":"4fe80f12b1d5189384a219095c2eabadbb389c2d3703aae7c5376dbaa56061df","impliedFormat":1},{"version":"9eb1d2dceae65d1c82fc6be7e9b6b19cf3ca93c364678611107362b6ad4d2d41","impliedFormat":1},{"version":"eb81f582c0e1ae21f004b39c268c68ad13222cd7e72f45080d6cdf9b994dfecf","impliedFormat":1},{"version":"e40f9840e4686053a5d42b39fddfd6774172a3d12fa61c1edb8c35b452a5e74a","impliedFormat":1},{"version":"c034433fe3ebd5391d0078de0193306591f61e421697a217cbeb92bd5e0f6ebc","impliedFormat":1},{"version":"479a021fec9328c8ab37f9372dcdc1022d79aeedde6febf03942b4456d1591c9","impliedFormat":1},{"version":"68d25a0614893e256a9e6fe18e2b125dbdad694381b57e63320de6a7518e47fc","impliedFormat":1},{"version":"49b89a0b4b3b3e18cbf726a9c608d622b4f6d49304076fc01c10f93833c70f09","impliedFormat":1},{"version":"4f62dc739ef3b99a93779255f85e51f02ce411d63cf294ee654258ff8c1978e4","impliedFormat":1},{"version":"3fa13ab73c7307a308bba2f9ca5cea92b74cbdae348a7ba1f5ac094fde7286c2","impliedFormat":1},{"version":"64102e00cb41de7f423608037d17dff83954904383e5c45f1054c2246cf5e184","impliedFormat":1},{"version":"5cf7ebe883e54b764af73548dbbef8887c640f1fd896f4d8b935b027d9989728","impliedFormat":1},{"version":"05e29a500e59cc5697947ee0fa9390e88ff008ec76be1f859152bda8ec01f13d","impliedFormat":1},{"version":"c89237cc87fc6e004290ae667babd0fa242e87c7de1cb49d4e5aba53391923f4","impliedFormat":1},{"version":"44b227ad122395768f07a8f1b84041b096220335b34ff7af3b8caa61731b294d","impliedFormat":1},{"version":"cfac258f114785ee4591fed69b045070da61d3ea03a9fabd750d682cc501253f","impliedFormat":1},{"version":"99dd60ecf4b17a5478ddc7a3a4e41500eba9b4fdbded73fa01e0ad525f7ff90d","impliedFormat":1},{"version":"1ba59c8bbeed2cb75b239bb12041582fa3e8ef32f8d0bd0ec802e38442d3f317","impliedFormat":1},{"version":"1bf1497c5e7ced001b52ed69657f472870edf3ef34641842046fc5cc47359fd0","impliedFormat":1},{"version":"36c1793e93c8a2dff0dcc67145b992e78fef0a67dd1f69d9f689b108a3598341","impliedFormat":1},{"version":"289302cd21b99f1b34e25812ed3119af56bab4740f10726c7a5a7db80df69e33","signature":"51cc114c75ade7761ead7dbeb917357947a820feb3c4c7589d6fc859804b117b","impliedFormat":99},{"version":"837f71558d52d6517b050cec1800f278a85fcff00dba7029801efc79bdd5e978","signature":"e1f8eff69d223ca308faac04131a51f40a811ad66005ea8553acd5b0a7884273","impliedFormat":99},{"version":"7fae4e5b823a86a5f5f4d4ab3689d6fdc8934afa67ddd3a5fa4792a7689ab146","signature":"b2389a3050e376a1e5e258f76d72c5c8baf0a80bc992c9ba65c263f9257c08a8","impliedFormat":99},{"version":"e2b0a3b2ab5fa5a3f31d8150ab3b93f29828131871a898379b117a16655888b0","impliedFormat":1},{"version":"155aa8c32f0815cb7f44246680859e2724428459727e6067d0d434a666b72e5b","impliedFormat":99},{"version":"0eaa5ca815f0d1f498a205737cc71a5259671ee1ffc96eb881ee721c2d60bf30","impliedFormat":99},{"version":"8b93a53483cf3bb55c754db9dffd3cc1e3d05f87fe7158aceb4b30f5521a14fc","impliedFormat":99},{"version":"912b5bd4dd0b17d42f18df923a05df4884b2ab3ec071d02f1ec3c8bf2adc75ae","signature":"1ddb6de962eac59d919fc52f07ce8b3c0af0c61da427b94b684d925c6ad6eb71","impliedFormat":99},{"version":"9c1e42f6390d8a66639fdead79e7cf2616f7708c2d8dba1dbf8db158ffa4d63c","signature":"d8396e56d160d98eb7f4fe538cb89f7db9345afa65563b3c7c1e030f6cee1bb5","impliedFormat":99},{"version":"caa98b91e3c64ab4f3d05e27940d556bae56cae78250ef2e623281f8028c4d09","signature":"b00d1b92b69461390eaecc1805d508328e6e1238c490bb4fc7d8f95dba9150c2","impliedFormat":99},{"version":"58089e4ae3cf5595f123796153c3852ab492073a464cf16875ca6f51dd2b89e7","signature":"9a427255affacd5f9393e9540bcdea7258ad97021bde8044493990e8dd2fd2ab","impliedFormat":99},{"version":"0aa5e375691619ba68ba72383a1641d5e8cb6781903b6cb714233a7a04f82787","signature":"d036d9eed1f11cdee9a3bb9a4ffe86cbeee21d03500f6c42de7219d53b660fa4","impliedFormat":99},{"version":"316f1486e15cbf7896425f0a16dfe12d447dd57cfb3244b8b119c77df870858f","impliedFormat":99},{"version":"303ab2a1b823b7ee483a89ab4ae9d720dc94a65886f6f0430577c63dfefb78f0","signature":"7f23a8041aff693c3a2cf7ff178e0f514a9dfa7d565e80fe5113ab60a575884e","impliedFormat":99},{"version":"4dd46bebc997d2d9de283ac20766fdfaaccb05baaab6e59bfd51ca0b0b1e3c57","signature":"3c903ff9ca7961c70214302108a0dd27044b7130b8d551ac891744fdb4bee60d","impliedFormat":99},{"version":"b0fef4d6b65da4b34876e3dc008d9b157c4f094a17855fcff92a18464c677179","signature":"43e818adf60173644896298637f47b01d5819b17eda46eaa32d0c7d64724d012","impliedFormat":99}],"root":[[251,253],[258,262],[264,266]],"options":{"composite":true,"declaration":true,"declarationMap":true,"esModuleInterop":true,"exactOptionalPropertyTypes":true,"module":199,"noImplicitOverride":true,"noUncheckedIndexedAccess":true,"outDir":"./dist","rootDir":"./src","skipLibCheck":true,"sourceMap":true,"strict":true,"target":10,"verbatimModuleSyntax":true},"referencedMap":[[116,1],[247,2],[117,3],[119,4],[250,5],[249,6],[254,3],[177,7],[178,7],[179,8],[131,9],[180,10],[181,11],[182,12],[126,3],[129,13],[127,3],[128,3],[183,14],[184,15],[185,16],[186,17],[187,18],[188,19],[189,19],[190,20],[191,21],[192,22],[193,23],[132,3],[130,3],[194,24],[195,25],[196,26],[230,27],[197,28],[198,3],[199,29],[200,30],[201,31],[202,32],[203,33],[204,34],[205,35],[206,36],[207,37],[208,37],[209,38],[210,3],[211,39],[212,40],[214,41],[213,42],[215,43],[216,44],[217,45],[218,46],[219,47],[220,48],[221,49],[222,50],[223,51],[224,52],[225,53],[226,54],[227,55],[133,3],[134,3],[135,3],[174,56],[175,3],[176,3],[228,57],[229,58],[263,59],[248,59],[106,60],[63,3],[65,61],[64,62],[69,63],[104,64],[101,65],[103,66],[66,65],[67,67],[71,67],[70,68],[68,69],[102,70],[115,71],[100,65],[105,72],[98,3],[99,3],[72,73],[77,65],[79,65],[74,65],[75,73],[81,65],[82,74],[73,65],[78,65],[80,65],[76,65],[96,75],[95,65],[97,76],[91,65],[112,77],[110,78],[109,65],[107,63],[114,79],[111,80],[108,78],[113,78],[93,65],[92,65],[88,65],[94,81],[89,65],[90,82],[83,65],[84,65],[85,65],[86,65],[87,65],[136,3],[118,83],[62,3],[246,84],[243,85],[241,86],[244,87],[239,88],[238,89],[235,90],[236,91],[237,92],[125,93],[242,94],[240,95],[123,96],[245,97],[124,98],[122,99],[120,100],[121,101],[231,102],[234,103],[232,104],[233,105],[60,3],[61,3],[12,3],[11,3],[2,3],[13,3],[14,3],[15,3],[16,3],[17,3],[18,3],[19,3],[20,3],[3,3],[21,3],[22,3],[4,3],[23,3],[27,3],[24,3],[25,3],[26,3],[28,3],[29,3],[30,3],[5,3],[31,3],[32,3],[33,3],[34,3],[6,3],[38,3],[35,3],[36,3],[37,3],[39,3],[7,3],[40,3],[45,3],[46,3],[41,3],[42,3],[43,3],[44,3],[8,3],[50,3],[47,3],[48,3],[49,3],[51,3],[9,3],[52,3],[53,3],[54,3],[56,3],[55,3],[57,3],[58,3],[10,3],[59,3],[1,3],[152,106],[162,107],[151,106],[172,108],[143,109],[142,110],[171,111],[165,112],[170,113],[145,114],[159,115],[144,116],[168,117],[140,118],[139,111],[169,119],[141,120],[146,121],[147,3],[150,121],[137,3],[173,122],[163,123],[154,124],[155,125],[157,126],[153,127],[156,128],[166,111],[148,129],[149,130],[158,131],[138,132],[161,123],[160,121],[164,3],[167,133],[265,134],[253,135],[251,136],[259,137],[260,138],[258,3],[252,139],[266,140],[261,141],[262,142],[264,143],[256,144],[257,145],[255,3]],"latestChangedDtsFile":"./dist/index.d.ts","version":"5.9.3"} \ No newline at end of file +{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/fast-uri/types/index.d.ts","../../node_modules/ajv/dist/compile/codegen/code.d.ts","../../node_modules/ajv/dist/compile/codegen/scope.d.ts","../../node_modules/ajv/dist/compile/codegen/index.d.ts","../../node_modules/ajv/dist/compile/rules.d.ts","../../node_modules/ajv/dist/compile/util.d.ts","../../node_modules/ajv/dist/compile/validate/subschema.d.ts","../../node_modules/ajv/dist/compile/errors.d.ts","../../node_modules/ajv/dist/compile/validate/index.d.ts","../../node_modules/ajv/dist/compile/validate/dataType.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/additionalItems.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/items2020.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/contains.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/dependencies.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/propertyNames.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/additionalProperties.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/not.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/anyOf.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/oneOf.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/if.d.ts","../../node_modules/ajv/dist/vocabularies/applicator/index.d.ts","../../node_modules/ajv/dist/vocabularies/validation/limitNumber.d.ts","../../node_modules/ajv/dist/vocabularies/validation/multipleOf.d.ts","../../node_modules/ajv/dist/vocabularies/validation/pattern.d.ts","../../node_modules/ajv/dist/vocabularies/validation/required.d.ts","../../node_modules/ajv/dist/vocabularies/validation/uniqueItems.d.ts","../../node_modules/ajv/dist/vocabularies/validation/const.d.ts","../../node_modules/ajv/dist/vocabularies/validation/enum.d.ts","../../node_modules/ajv/dist/vocabularies/validation/index.d.ts","../../node_modules/ajv/dist/vocabularies/format/format.d.ts","../../node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedProperties.d.ts","../../node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedItems.d.ts","../../node_modules/ajv/dist/vocabularies/validation/dependentRequired.d.ts","../../node_modules/ajv/dist/vocabularies/discriminator/types.d.ts","../../node_modules/ajv/dist/vocabularies/discriminator/index.d.ts","../../node_modules/ajv/dist/vocabularies/errors.d.ts","../../node_modules/ajv/dist/types/json-schema.d.ts","../../node_modules/ajv/dist/types/jtd-schema.d.ts","../../node_modules/ajv/dist/runtime/validation_error.d.ts","../../node_modules/ajv/dist/compile/ref_error.d.ts","../../node_modules/ajv/dist/core.d.ts","../../node_modules/ajv/dist/compile/resolve.d.ts","../../node_modules/ajv/dist/compile/index.d.ts","../../node_modules/ajv/dist/types/index.d.ts","../../node_modules/ajv/dist/ajv.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/error.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/type.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/enum.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/elements.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/properties.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/discriminator.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/values.d.ts","../../node_modules/ajv/dist/vocabularies/jtd/index.d.ts","../../node_modules/ajv/dist/jtd.d.ts","../../node_modules/@fastify/ajv-compiler/types/index.d.ts","../../node_modules/@fastify/error/types/index.d.ts","../../node_modules/fast-json-stringify/types/index.d.ts","../../node_modules/@fastify/fast-json-stringify-compiler/types/index.d.ts","../../node_modules/find-my-way/index.d.ts","../../node_modules/light-my-request/types/index.d.ts","../../node_modules/fastify/types/utils.d.ts","../../node_modules/fastify/types/schema.d.ts","../../node_modules/fastify/types/type-provider.d.ts","../../node_modules/fastify/types/reply.d.ts","../../node_modules/@types/node/compatibility/disposable.d.ts","../../node_modules/@types/node/compatibility/indexable.d.ts","../../node_modules/@types/node/compatibility/iterators.d.ts","../../node_modules/@types/node/compatibility/index.d.ts","../../node_modules/@types/node/globals.typedarray.d.ts","../../node_modules/@types/node/buffer.buffer.d.ts","../../node_modules/@types/node/globals.d.ts","../../node_modules/@types/node/web-globals/abortcontroller.d.ts","../../node_modules/@types/node/web-globals/domexception.d.ts","../../node_modules/@types/node/web-globals/events.d.ts","../../node_modules/buffer/index.d.ts","../../node_modules/undici-types/header.d.ts","../../node_modules/undici-types/readable.d.ts","../../node_modules/undici-types/file.d.ts","../../node_modules/undici-types/fetch.d.ts","../../node_modules/undici-types/formdata.d.ts","../../node_modules/undici-types/connector.d.ts","../../node_modules/undici-types/client.d.ts","../../node_modules/undici-types/errors.d.ts","../../node_modules/undici-types/dispatcher.d.ts","../../node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/undici-types/global-origin.d.ts","../../node_modules/undici-types/pool-stats.d.ts","../../node_modules/undici-types/pool.d.ts","../../node_modules/undici-types/handlers.d.ts","../../node_modules/undici-types/balanced-pool.d.ts","../../node_modules/undici-types/agent.d.ts","../../node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/undici-types/mock-agent.d.ts","../../node_modules/undici-types/mock-client.d.ts","../../node_modules/undici-types/mock-pool.d.ts","../../node_modules/undici-types/mock-errors.d.ts","../../node_modules/undici-types/proxy-agent.d.ts","../../node_modules/undici-types/env-http-proxy-agent.d.ts","../../node_modules/undici-types/retry-handler.d.ts","../../node_modules/undici-types/retry-agent.d.ts","../../node_modules/undici-types/api.d.ts","../../node_modules/undici-types/interceptors.d.ts","../../node_modules/undici-types/util.d.ts","../../node_modules/undici-types/cookies.d.ts","../../node_modules/undici-types/patch.d.ts","../../node_modules/undici-types/websocket.d.ts","../../node_modules/undici-types/eventsource.d.ts","../../node_modules/undici-types/filereader.d.ts","../../node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/undici-types/content-type.d.ts","../../node_modules/undici-types/cache.d.ts","../../node_modules/undici-types/index.d.ts","../../node_modules/@types/node/web-globals/fetch.d.ts","../../node_modules/@types/node/web-globals/navigator.d.ts","../../node_modules/@types/node/web-globals/storage.d.ts","../../node_modules/@types/node/assert.d.ts","../../node_modules/@types/node/assert/strict.d.ts","../../node_modules/@types/node/async_hooks.d.ts","../../node_modules/@types/node/buffer.d.ts","../../node_modules/@types/node/child_process.d.ts","../../node_modules/@types/node/cluster.d.ts","../../node_modules/@types/node/console.d.ts","../../node_modules/@types/node/constants.d.ts","../../node_modules/@types/node/crypto.d.ts","../../node_modules/@types/node/dgram.d.ts","../../node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/@types/node/dns.d.ts","../../node_modules/@types/node/dns/promises.d.ts","../../node_modules/@types/node/domain.d.ts","../../node_modules/@types/node/events.d.ts","../../node_modules/@types/node/fs.d.ts","../../node_modules/@types/node/fs/promises.d.ts","../../node_modules/@types/node/http.d.ts","../../node_modules/@types/node/http2.d.ts","../../node_modules/@types/node/https.d.ts","../../node_modules/@types/node/inspector.d.ts","../../node_modules/@types/node/inspector.generated.d.ts","../../node_modules/@types/node/module.d.ts","../../node_modules/@types/node/net.d.ts","../../node_modules/@types/node/os.d.ts","../../node_modules/@types/node/path.d.ts","../../node_modules/@types/node/perf_hooks.d.ts","../../node_modules/@types/node/process.d.ts","../../node_modules/@types/node/punycode.d.ts","../../node_modules/@types/node/querystring.d.ts","../../node_modules/@types/node/readline.d.ts","../../node_modules/@types/node/readline/promises.d.ts","../../node_modules/@types/node/repl.d.ts","../../node_modules/@types/node/sea.d.ts","../../node_modules/@types/node/sqlite.d.ts","../../node_modules/@types/node/stream.d.ts","../../node_modules/@types/node/stream/promises.d.ts","../../node_modules/@types/node/stream/consumers.d.ts","../../node_modules/@types/node/stream/web.d.ts","../../node_modules/@types/node/string_decoder.d.ts","../../node_modules/@types/node/test.d.ts","../../node_modules/@types/node/timers.d.ts","../../node_modules/@types/node/timers/promises.d.ts","../../node_modules/@types/node/tls.d.ts","../../node_modules/@types/node/trace_events.d.ts","../../node_modules/@types/node/tty.d.ts","../../node_modules/@types/node/url.d.ts","../../node_modules/@types/node/util.d.ts","../../node_modules/@types/node/v8.d.ts","../../node_modules/@types/node/vm.d.ts","../../node_modules/@types/node/wasi.d.ts","../../node_modules/@types/node/worker_threads.d.ts","../../node_modules/@types/node/zlib.d.ts","../../node_modules/@types/node/index.d.ts","../../node_modules/pino-std-serializers/index.d.ts","../../node_modules/sonic-boom/types/index.d.ts","../../node_modules/thread-stream/index.d.ts","../../node_modules/pino/pino.d.ts","../../node_modules/fastify/types/logger.d.ts","../../node_modules/fastify/types/plugin.d.ts","../../node_modules/fastify/types/register.d.ts","../../node_modules/fastify/types/instance.d.ts","../../node_modules/fastify/types/hooks.d.ts","../../node_modules/fastify/types/route.d.ts","../../node_modules/fastify/types/context.d.ts","../../node_modules/fastify/types/request.d.ts","../../node_modules/fastify/types/content-type-parser.d.ts","../../node_modules/fastify/types/errors.d.ts","../../node_modules/fastify/types/server-factory.d.ts","../../node_modules/fastify/fastify.d.ts","../../node_modules/@fastify/cookie/types/plugin.d.ts","../../node_modules/@types/ws/index.d.ts","../../node_modules/@fastify/websocket/types/index.d.ts","../../node_modules/@fastify/static/types/index.d.ts","./src/config.ts","./src/db/index.ts","./src/auth/service.ts","../../node_modules/@homebridge/node-pty-prebuilt-multiarch/typings/node-pty.d.ts","../shared/dist/protocol.d.ts","../shared/dist/api.d.ts","../shared/dist/index.d.ts","./src/core/ring-buffer.ts","./src/core/claude-launcher.ts","./src/core/pty-manager.ts","./src/routes/auth.ts","./src/routes/sessions.ts","../../node_modules/@types/ws/index.d.mts","./src/ws/gateway.ts","./src/app.ts","./src/index.ts"],"fileIdsList":[[102,106,115,131,180,197,198],[131,180,197,198,230,246,249,250,265],[131,180,197,198],[118,131,180,197,198],[131,180,192,197,198,230,246,247,249,265],[125,131,180,194,197,198,230,239,240,246,247,248,250,265],[131,177,178,180,197,198],[131,179,180,197,198],[180,197,198],[131,180,185,197,198,215],[131,180,181,186,191,197,198,200,212,223],[131,180,181,182,191,197,198,200],[126,127,128,131,180,197,198],[131,180,183,197,198,224],[131,180,184,185,192,197,198,201],[131,180,185,197,198,212,220],[131,180,186,188,191,197,198,200],[131,179,180,187,197,198],[131,180,188,189,197,198],[131,180,190,191,197,198],[131,179,180,191,197,198],[131,180,191,192,193,197,198,212,223],[131,180,191,192,193,197,198,207,212,215],[131,173,180,188,191,194,197,198,200,212,223],[131,180,191,192,194,195,197,198,200,212,220,223],[131,180,194,196,197,198,212,220,223],[129,130,131,132,133,134,135,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229],[131,180,191,197,198],[131,180,197,198,199,223],[131,180,188,191,197,198,200,212],[131,180,197,198,201],[131,180,197,198,202],[131,179,180,197,198,203],[131,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229],[131,180,197,198,205],[131,180,197,198,206],[131,180,191,197,198,207,208],[131,180,197,198,207,209,224,226],[131,180,192,197,198],[131,180,191,197,198,212,213,215],[131,180,197,198,214,215],[131,180,197,198,212,213],[131,180,197,198,215],[131,180,197,198,216],[131,177,180,197,198,212,217,223],[131,180,191,197,198,218,219],[131,180,197,198,218,219],[131,180,185,197,198,200,212,220],[131,180,197,198,221],[131,180,197,198,200,222],[131,180,194,197,198,206,223],[131,180,185,197,198,224],[131,180,197,198,212,225],[131,180,197,198,199,226],[131,180,197,198,227],[131,173,180,197,198],[131,173,180,191,193,197,198,203,212,215,223,225,226,228],[131,180,197,198,212,229],[131,180,191,194,196,197,198,200,212,220,223,229,230],[65,66,70,97,98,100,101,102,104,105,131,180,197,198],[63,64,131,180,197,198],[63,131,180,197,198],[65,105,131,180,197,198],[65,66,102,103,105,131,180,197,198],[105,131,180,197,198],[62,105,106,131,180,197,198],[65,66,104,105,131,180,197,198],[65,66,68,69,104,105,131,180,197,198],[65,66,67,104,105,131,180,197,198],[65,66,70,97,98,99,100,101,104,105,131,180,197,198],[65,70,99,100,101,102,104,105,114,131,180,197,198],[62,65,66,70,102,104,131,180,197,198],[70,105,131,180,197,198],[72,73,74,75,76,77,78,79,80,81,105,131,180,197,198],[95,105,131,180,197,198],[71,82,90,91,92,93,94,96,131,180,197,198],[95,105,107,131,180,197,198],[105,107,131,180,197,198],[105,108,109,110,111,112,113,131,180,197,198],[70,105,107,131,180,197,198],[75,105,131,180,197,198],[83,84,85,86,87,88,89,105,131,180,197,198],[106,131,180,197,198],[116,117,119,120,121,122,123,124,125,131,180,194,195,196,197,198,200,235,236,237,238,239,240,241,242,243,244,245],[122,123,124,131,180,197,198,240,242],[122,131,180,197,198,240],[117,131,180,197,198],[117,122,123,124,125,131,180,197,198,212,235,236,237,238,240,242],[117,120,121,122,123,124,125,131,180,194,197,198,200,235,237,239,240,242,243,246],[117,122,123,124,125,131,180,197,198,234,238,240,242],[122,124,131,180,197,198,235,238],[122,131,180,197,198,235,236,238,246],[122,123,124,131,180,197,198,235,238,240,242],[116,122,123,124,131,180,197,198,235,238,240,241],[117,120,122,123,124,125,131,180,197,198,235,238,239,241,242],[116,119,131,180,197,198,246],[122,131,180,194,195,196,197,198],[122,123,131,180,197,198,240],[131,180,194,195,196,197,198],[131,180,194,195,197,198],[131,180,194,197,198,212],[131,180,194,197,198,230],[131,180,191,197,198,228,231,232,233],[131,180,191,197,198,230],[131,180,191,197,198,228],[131,145,149,180,197,198,223],[131,145,180,197,198,212,223],[131,140,180,197,198],[131,142,145,180,197,198,220,223],[131,180,197,198,200,220],[131,180,197,198,230],[131,140,180,197,198,230],[131,142,145,180,197,198,200,223],[131,137,138,141,144,180,191,197,198,212,223],[131,145,152,180,197,198],[131,137,143,180,197,198],[131,145,166,167,180,197,198],[131,141,145,180,197,198,215,223,230],[131,166,180,197,198,230],[131,139,140,180,197,198,230],[131,145,180,197,198],[131,139,140,141,142,143,144,145,146,147,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,167,168,169,170,171,172,180,197,198],[131,145,160,180,197,198],[131,145,152,153,180,197,198],[131,143,145,153,154,180,197,198],[131,144,180,197,198],[131,137,140,145,180,197,198],[131,145,149,153,154,180,197,198],[131,149,180,197,198],[131,143,145,148,180,197,198,223],[131,137,142,145,152,180,197,198],[131,180,197,198,212],[131,140,145,166,180,197,198,228,230],[131,180,192,197,198,202,223,246,247,249,250,251,252,253,260,261,262,264],[131,180,185,197,198,252],[131,180,192,197,198,201,202,224],[131,180,181,197,198],[131,180,185,191,192,197,198,252,254,257,258,259],[131,180,197,198,211],[131,180,192,197,198,202,223,251,252,265],[131,180,197,198,246,253,257],[131,180,197,198,246,257,260],[131,180,197,198,246,257,260,263],[131,180,197,198,255],[131,180,197,198,255,256]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"df83c2a6c73228b625b0beb6669c7ee2a09c914637e2d35170723ad49c0f5cd4","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"84bcc7c6b06f4d643a55dc63b56be0c81d990f8d549b66ea615c553268774dc3","impliedFormat":1},{"version":"2d225e7bda2871c066a7079c88174340950fb604f624f2586d3ea27bb9e5f4ff","impliedFormat":1},{"version":"6a785f84e63234035e511817dd48ada756d984dd8f9344e56eb8b2bdcd8fd001","impliedFormat":1},{"version":"c1422d016f7df2ccd3594c06f2923199acd09898f2c42f50ea8159f1f856f618","impliedFormat":1},{"version":"2973b1b7857ca144251375b97f98474e9847a890331e27132d5a8b3aea9350a8","impliedFormat":1},{"version":"0eb6152d37c84d6119295493dfcc20c331c6fda1304a513d159cdaa599dcb78b","impliedFormat":1},{"version":"237df26f8c326ca00cd9d2deb40214a079749062156386b6d75bdcecc6988a6b","impliedFormat":1},{"version":"cd44995ee13d5d23df17a10213fed7b483fabfd5ea08f267ab52c07ce0b6b4da","impliedFormat":1},{"version":"58ce1486f851942bd2d3056b399079bc9cb978ec933fe9833ea417e33eab676e","impliedFormat":1},{"version":"7557d4d7f19f94341f4413575a3453ba7f6039c9591015bcf4282a8e75414043","impliedFormat":1},{"version":"a3b2cc16f3ce2d882eca44e1066f57a24751545f2a5e4a153d4de31b4cac9bb5","impliedFormat":1},{"version":"ac2b3b377d3068bfb6e1cb8889c99098f2c875955e2325315991882a74d92cc8","impliedFormat":1},{"version":"8deb39d89095469957f73bd194d11f01d9894b8c1f1e27fbf3f6e8122576b336","impliedFormat":1},{"version":"a38a9c41f433b608a0d37e645a31eecf7233ef3d3fffeb626988d3219f80e32f","impliedFormat":1},{"version":"8e1428dcba6a984489863935049893631170a37f9584c0479f06e1a5b1f04332","impliedFormat":1},{"version":"1fce9ecb87a2d3898941c60df617e52e50fb0c03c9b7b2ba8381972448327285","impliedFormat":1},{"version":"5ef0597b8238443908b2c4bf69149ed3894ac0ddd0515ac583d38c7595b151f1","impliedFormat":1},{"version":"ac52b775a80badff5f4ac329c5725a26bd5aaadd57afa7ad9e98b4844767312a","impliedFormat":1},{"version":"6ae5b4a63010c82bf2522b4ecfc29ffe6a8b0c5eea6b2b35120077e9ac54d7a1","impliedFormat":1},{"version":"dd7109c49f416f218915921d44f0f28975df78e04e437c62e1e1eb3be5e18a35","impliedFormat":1},{"version":"eee181112e420b345fc78422a6cc32385ede3d27e2eaf8b8c4ad8b2c29e3e52e","impliedFormat":1},{"version":"25fbe57c8ee3079e2201fe580578fab4f3a78881c98865b7c96233af00bf9624","impliedFormat":1},{"version":"62cc8477858487b4c4de7d7ae5e745a8ce0015c1592f398b63ee05d6e64ca295","impliedFormat":1},{"version":"cc2a9ec3cb10e4c0b8738b02c31798fad312d21ef20b6a2f5be1d077e9f5409d","impliedFormat":1},{"version":"4b4fadcda7d34034737598c07e2dca5d7e1e633cb3ba8dd4d2e6a7782b30b296","impliedFormat":1},{"version":"360fdc8829a51c5428636f1f83e7db36fef6c5a15ed4411b582d00a1c2bd6e97","impliedFormat":1},{"version":"1cf0d15e6ab1ecabbf329b906ae8543e6b8955133b7f6655f04d433e3a0597ab","impliedFormat":1},{"version":"7c9f98fe812643141502b30fb2b5ec56d16aaf94f98580276ae37b7924dd44a4","impliedFormat":1},{"version":"b3547893f24f59d0a644c52f55901b15a3fa1a115bc5ea9a582911469b9348b7","impliedFormat":1},{"version":"596e5b88b6ca8399076afcc22af6e6e0c4700c7cd1f420a78d637c3fb44a885e","impliedFormat":1},{"version":"adddf736e08132c7059ee572b128fdacb1c2650ace80d0f582e93d097ed4fbaf","impliedFormat":1},{"version":"d4cad9dc13e9c5348637170ddd5d95f7ed5fdfc856ddca40234fa55518bc99a6","impliedFormat":1},{"version":"d70675ba7ba7d02e52b7070a369957a70827e4b2bca2c1680c38a832e87b61fd","impliedFormat":1},{"version":"3be71f4ce8988a01e2f5368bdd58e1d60236baf511e4510ee9291c7b3729a27e","impliedFormat":1},{"version":"423d2ccc38e369a7527988d682fafc40267bcd6688a7473e59c5eea20a29b64f","impliedFormat":1},{"version":"2f9fde0868ed030277c678b435f63fcf03d27c04301299580a4017963cc04ce6","impliedFormat":1},{"version":"feeb73d48cc41c6dd23d17473521b0af877751504c30c18dc84267c8eeea429a","impliedFormat":1},{"version":"25f1159094dc0bf3a71313a74e0885426af21c5d6564a254004f2cadf9c5b052","impliedFormat":1},{"version":"cde493e09daad4bb29922fe633f760be9f0e8e2f39cdca999cce3b8690b5e13a","impliedFormat":1},{"version":"3d7f9eb12aface876f7b535cc89dcd416daf77f0b3573333f16ec0a70bcf902a","impliedFormat":1},{"version":"4304f640f7cb4724ea82441accb7c7607fa7207541182470d625adda99b2900b","impliedFormat":1},{"version":"e0205f04611bea8b5b82168065b8ef1476a8e96236201494eb8c785331c43118","impliedFormat":1},{"version":"62d26d8ba4fa15ab425c1b57a050ed76c5b0ecbffaa53f182110aa3a02405a07","impliedFormat":1},{"version":"9941cbf7ca695e95d588f5f1692ab040b078d44a95d231fa9a8f828186b7b77d","impliedFormat":1},{"version":"41b8775befd7ded7245a627e9f4de6110236688ce4c124d2d40c37bc1a3bfe05","impliedFormat":1},{"version":"a6613ee552418429af38391e37389036654a882c342a1b81f2711e8ddac597f2","impliedFormat":1},{"version":"da47cb979ae4a849f9b983f43ef34365b7050c4f5ae2ebf818195858774e1d67","impliedFormat":1},{"version":"ac3bcb82d7280fc313a967f311764258d18caf33db6d2b1a0243cde607ff01a0","impliedFormat":1},{"version":"c9b5632d6665177030428d02603aeac3e920d31ec83ac500b55d44c7da74bd84","impliedFormat":1},{"version":"46456824df16d60f243a7e386562b27bac838aaba66050b9bc0f31e1ab34c1f2","impliedFormat":1},{"version":"b91034069e217212d8dda6c92669ee9f180b4c36273b5244c3be2c657f9286c7","impliedFormat":1},{"version":"0697277dd829ac2610d68fe1b457c9e758105bb52d40e149d9c15e5e2fe6dca4","impliedFormat":1},{"version":"b0d06dbb409369169143ede5df1fb58b2fca8d44588e199bd624b6f6d966bf08","impliedFormat":1},{"version":"88dfdb2a44912a28aea3ebb657dc7fcec6ba59f7233005e3405824995b713dac","impliedFormat":1},{"version":"f38a45cbc0356dfb6455211e75ecd0f8aeaedbacd4cb8d6646c818d36a46d195","impliedFormat":1},{"version":"cc2d5d5687bdf9d7c49b6946b8769ac7abcbdcd1701d9bb9ca70a8bc1b003e8b","impliedFormat":1},{"version":"6f1fabd39b8c9a66a3232030a4b28ed4fb4f857dcffef0add3220dab4bbba77a","impliedFormat":1},{"version":"9c0623d67471ddc5b9d82b4e06252c746d54f7ae8ccff8701cd51c249f7e7694","impliedFormat":1},{"version":"71654f68a41362f16a2690ec215e583d3cfa913d49327e43c3c7f354022f67a3","impliedFormat":1},{"version":"f69b484edf398d636992757d587e7e38ea91844a66dbca9d682c9cf7858b77cf","impliedFormat":1},{"version":"37d852b3e6b30b974178674dbf2a7974a1ea4bbdbec26d0bdb8f34632cab94a2","impliedFormat":1},{"version":"c3ccf44723b474b5d7a87c53eed9711e81422f0e29b3e8f8f8d42775fa802243","impliedFormat":1},{"version":"faeaf9586ba4505448bd0fdb332509a7e3ecca5d8ae10208950a5d03e8b4ea28","impliedFormat":1},{"version":"c3a0295707fde0cb51ca784ecb799aa95ca71c40337b5ee4a81a8f7134dd7e92","impliedFormat":1},{"version":"6c7176368037af28cb72f2392010fa1cef295d6d6744bca8cfb54985f3a18c3e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"437e20f2ba32abaeb7985e0afe0002de1917bc74e949ba585e49feba65da6ca1","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"808069bba06b6768b62fd22429b53362e7af342da4a236ed2d2e1c89fcca3b4a","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e9c23ba78aabc2e0a27033f18737a6df754067731e69dc5f52823957d60a4b6","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b52476feb4a0cbcb25e5931b930fc73cb6643fb1a5060bf8a3dda0eeae5b4b68","affectsGlobalScope":true,"impliedFormat":1},{"version":"f9501cc13ce624c72b61f12b3963e84fad210fbdf0ffbc4590e08460a3f04eba","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0fa06ada475b910e2106c98c68b10483dc8811d0c14a8a8dd36efb2672485b29","impliedFormat":1},{"version":"33e5e9aba62c3193d10d1d33ae1fa75c46a1171cf76fef750777377d53b0303f","impliedFormat":1},{"version":"2b06b93fd01bcd49d1a6bd1f9b65ddcae6480b9a86e9061634d6f8e354c1468f","impliedFormat":1},{"version":"6a0cd27e5dc2cfbe039e731cf879d12b0e2dded06d1b1dedad07f7712de0d7f4","affectsGlobalScope":true,"impliedFormat":1},{"version":"13f5c844119c43e51ce777c509267f14d6aaf31eafb2c2b002ca35584cd13b29","impliedFormat":1},{"version":"e60477649d6ad21542bd2dc7e3d9ff6853d0797ba9f689ba2f6653818999c264","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"4c829ab315f57c5442c6667b53769975acbf92003a66aef19bce151987675bd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"b2ade7657e2db96d18315694789eff2ddd3d8aea7215b181f8a0b303277cc579","impliedFormat":1},{"version":"9855e02d837744303391e5623a531734443a5f8e6e8755e018c41d63ad797db2","impliedFormat":1},{"version":"4d631b81fa2f07a0e63a9a143d6a82c25c5f051298651a9b69176ba28930756d","impliedFormat":1},{"version":"836a356aae992ff3c28a0212e3eabcb76dd4b0cc06bcb9607aeef560661b860d","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"41670ee38943d9cbb4924e436f56fc19ee94232bc96108562de1a734af20dc2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"c906fb15bd2aabc9ed1e3f44eb6a8661199d6c320b3aa196b826121552cb3695","impliedFormat":1},{"version":"22295e8103f1d6d8ea4b5d6211e43421fe4564e34d0dd8e09e520e452d89e659","impliedFormat":1},{"version":"58647d85d0f722a1ce9de50955df60a7489f0593bf1a7015521efe901c06d770","impliedFormat":1},{"version":"9018e9493596e2d041fc531f5101a0d05f9d2e1311f4ae3c74345b1766158c08","impliedFormat":1},{"version":"a10f0e1854f3316d7ee437b79649e5a6ae3ae14ffe6322b02d4987071a95362e","impliedFormat":1},{"version":"e208f73ef6a980104304b0d2ca5f6bf1b85de6009d2c7e404028b875020fa8f2","impliedFormat":1},{"version":"d163b6bc2372b4f07260747cbc6c0a6405ab3fbcea3852305e98ac43ca59f5bc","impliedFormat":1},{"version":"e6fa9ad47c5f71ff733744a029d1dc472c618de53804eae08ffc243b936f87ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6f137d651076822d4fe884287e68fd61785a0d3d1fdb250a5059b691fa897db","impliedFormat":1},{"version":"24826ed94a78d5c64bd857570fdbd96229ad41b5cb654c08d75a9845e3ab7dde","impliedFormat":1},{"version":"8b479a130ccb62e98f11f136d3ac80f2984fdc07616516d29881f3061f2dd472","impliedFormat":1},{"version":"928af3d90454bf656a52a48679f199f64c1435247d6189d1caf4c68f2eaf921f","affectsGlobalScope":true,"impliedFormat":1},{"version":"bceb58df66ab8fb00170df20cd813978c5ab84be1d285710c4eb005d8e9d8efb","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"933921f0bb0ec12ef45d1062a1fc0f27635318f4d294e4d99de9a5493e618ca2","impliedFormat":1},{"version":"71a0f3ad612c123b57239a7749770017ecfe6b66411488000aba83e4546fde25","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"4f9d8ca0c417b67b69eeb54c7ca1bedd7b56034bb9bfd27c5d4f3bc4692daca7","impliedFormat":1},{"version":"814118df420c4e38fe5ae1b9a3bafb6e9c2aa40838e528cde908381867be6466","impliedFormat":1},{"version":"a3fc63c0d7b031693f665f5494412ba4b551fe644ededccc0ab5922401079c95","impliedFormat":1},{"version":"80523c00b8544a2000ae0143e4a90a00b47f99823eb7926c1e03c494216fc363","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"746911b62b329587939560deb5c036aca48aece03147b021fa680223255d5183","affectsGlobalScope":true,"impliedFormat":1},{"version":"18fd40412d102c5564136f29735e5d1c3b455b8a37f920da79561f1fde068208","impliedFormat":1},{"version":"c8d3e5a18ba35629954e48c4cc8f11dc88224650067a172685c736b27a34a4dc","impliedFormat":1},{"version":"f0be1b8078cd549d91f37c30c222c2a187ac1cf981d994fb476a1adc61387b14","affectsGlobalScope":true,"impliedFormat":1},{"version":"0aaed1d72199b01234152f7a60046bc947f1f37d78d182e9ae09c4289e06a592","impliedFormat":1},{"version":"2b55d426ff2b9087485e52ac4bc7cfafe1dc420fc76dad926cd46526567c501a","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"5b7aa3c4c1a5d81b411e8cb302b45507fea9358d3569196b27eb1a27ae3a90ef","affectsGlobalScope":true,"impliedFormat":1},{"version":"5987a903da92c7462e0b35704ce7da94d7fdc4b89a984871c0e2b87a8aae9e69","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea08a0345023ade2b47fbff5a76d0d0ed8bff10bc9d22b83f40858a8e941501c","impliedFormat":1},{"version":"47613031a5a31510831304405af561b0ffaedb734437c595256bb61a90f9311b","impliedFormat":1},{"version":"ae062ce7d9510060c5d7e7952ae379224fb3f8f2dd74e88959878af2057c143b","impliedFormat":1},{"version":"8a1a0d0a4a06a8d278947fcb66bf684f117bf147f89b06e50662d79a53be3e9f","affectsGlobalScope":true,"impliedFormat":1},{"version":"358765d5ea8afd285d4fd1532e78b88273f18cb3f87403a9b16fef61ac9fdcfe","impliedFormat":1},{"version":"9f55299850d4f0921e79b6bf344b47c420ce0f507b9dcf593e532b09ea7eeea1","impliedFormat":1},{"version":"4fe80f12b1d5189384a219095c2eabadbb389c2d3703aae7c5376dbaa56061df","impliedFormat":1},{"version":"9eb1d2dceae65d1c82fc6be7e9b6b19cf3ca93c364678611107362b6ad4d2d41","impliedFormat":1},{"version":"eb81f582c0e1ae21f004b39c268c68ad13222cd7e72f45080d6cdf9b994dfecf","impliedFormat":1},{"version":"e40f9840e4686053a5d42b39fddfd6774172a3d12fa61c1edb8c35b452a5e74a","impliedFormat":1},{"version":"c034433fe3ebd5391d0078de0193306591f61e421697a217cbeb92bd5e0f6ebc","impliedFormat":1},{"version":"479a021fec9328c8ab37f9372dcdc1022d79aeedde6febf03942b4456d1591c9","impliedFormat":1},{"version":"68d25a0614893e256a9e6fe18e2b125dbdad694381b57e63320de6a7518e47fc","impliedFormat":1},{"version":"49b89a0b4b3b3e18cbf726a9c608d622b4f6d49304076fc01c10f93833c70f09","impliedFormat":1},{"version":"4f62dc739ef3b99a93779255f85e51f02ce411d63cf294ee654258ff8c1978e4","impliedFormat":1},{"version":"3fa13ab73c7307a308bba2f9ca5cea92b74cbdae348a7ba1f5ac094fde7286c2","impliedFormat":1},{"version":"64102e00cb41de7f423608037d17dff83954904383e5c45f1054c2246cf5e184","impliedFormat":1},{"version":"5cf7ebe883e54b764af73548dbbef8887c640f1fd896f4d8b935b027d9989728","impliedFormat":1},{"version":"05e29a500e59cc5697947ee0fa9390e88ff008ec76be1f859152bda8ec01f13d","impliedFormat":1},{"version":"c89237cc87fc6e004290ae667babd0fa242e87c7de1cb49d4e5aba53391923f4","impliedFormat":1},{"version":"44b227ad122395768f07a8f1b84041b096220335b34ff7af3b8caa61731b294d","impliedFormat":1},{"version":"cfac258f114785ee4591fed69b045070da61d3ea03a9fabd750d682cc501253f","impliedFormat":1},{"version":"99dd60ecf4b17a5478ddc7a3a4e41500eba9b4fdbded73fa01e0ad525f7ff90d","impliedFormat":1},{"version":"1ba59c8bbeed2cb75b239bb12041582fa3e8ef32f8d0bd0ec802e38442d3f317","impliedFormat":1},{"version":"1bf1497c5e7ced001b52ed69657f472870edf3ef34641842046fc5cc47359fd0","impliedFormat":1},{"version":"36c1793e93c8a2dff0dcc67145b992e78fef0a67dd1f69d9f689b108a3598341","impliedFormat":1},{"version":"289302cd21b99f1b34e25812ed3119af56bab4740f10726c7a5a7db80df69e33","signature":"51cc114c75ade7761ead7dbeb917357947a820feb3c4c7589d6fc859804b117b","impliedFormat":99},{"version":"837f71558d52d6517b050cec1800f278a85fcff00dba7029801efc79bdd5e978","signature":"e1f8eff69d223ca308faac04131a51f40a811ad66005ea8553acd5b0a7884273","impliedFormat":99},{"version":"44374830e8530b42058a2b9a713f0e94b0171cd2887b53a19343ae80d6b0230b","signature":"b2389a3050e376a1e5e258f76d72c5c8baf0a80bc992c9ba65c263f9257c08a8","impliedFormat":99},{"version":"e2b0a3b2ab5fa5a3f31d8150ab3b93f29828131871a898379b117a16655888b0","impliedFormat":1},{"version":"155aa8c32f0815cb7f44246680859e2724428459727e6067d0d434a666b72e5b","impliedFormat":99},{"version":"0eaa5ca815f0d1f498a205737cc71a5259671ee1ffc96eb881ee721c2d60bf30","impliedFormat":99},{"version":"8b93a53483cf3bb55c754db9dffd3cc1e3d05f87fe7158aceb4b30f5521a14fc","impliedFormat":99},{"version":"94df841d67dcf657c26c5aa60fe3b3a9daca4bd156baae524d5a4ecd9e90b401","signature":"1ddb6de962eac59d919fc52f07ce8b3c0af0c61da427b94b684d925c6ad6eb71","impliedFormat":99},{"version":"9c1e42f6390d8a66639fdead79e7cf2616f7708c2d8dba1dbf8db158ffa4d63c","signature":"d8396e56d160d98eb7f4fe538cb89f7db9345afa65563b3c7c1e030f6cee1bb5","impliedFormat":99},{"version":"f38ed4bcd27c8054ccbfca5c93217ffc805f11c9421a9867851f8d77ea9525f1","signature":"b00d1b92b69461390eaecc1805d508328e6e1238c490bb4fc7d8f95dba9150c2","impliedFormat":99},{"version":"58089e4ae3cf5595f123796153c3852ab492073a464cf16875ca6f51dd2b89e7","signature":"9a427255affacd5f9393e9540bcdea7258ad97021bde8044493990e8dd2fd2ab","impliedFormat":99},{"version":"0aa5e375691619ba68ba72383a1641d5e8cb6781903b6cb714233a7a04f82787","signature":"d036d9eed1f11cdee9a3bb9a4ffe86cbeee21d03500f6c42de7219d53b660fa4","impliedFormat":99},{"version":"316f1486e15cbf7896425f0a16dfe12d447dd57cfb3244b8b119c77df870858f","impliedFormat":99},{"version":"303ab2a1b823b7ee483a89ab4ae9d720dc94a65886f6f0430577c63dfefb78f0","signature":"7f23a8041aff693c3a2cf7ff178e0f514a9dfa7d565e80fe5113ab60a575884e","impliedFormat":99},{"version":"c72437f83e5f35d3c6bc42889a22438fa8cb3f837d3a75e12bd45c7010f109e0","signature":"3c903ff9ca7961c70214302108a0dd27044b7130b8d551ac891744fdb4bee60d","impliedFormat":99},{"version":"b0fef4d6b65da4b34876e3dc008d9b157c4f094a17855fcff92a18464c677179","signature":"43e818adf60173644896298637f47b01d5819b17eda46eaa32d0c7d64724d012","impliedFormat":99}],"root":[[251,253],[258,262],[264,266]],"options":{"composite":true,"declaration":true,"declarationMap":true,"esModuleInterop":true,"exactOptionalPropertyTypes":true,"module":199,"noImplicitOverride":true,"noUncheckedIndexedAccess":true,"outDir":"./dist","rootDir":"./src","skipLibCheck":true,"sourceMap":true,"strict":true,"target":10,"verbatimModuleSyntax":true},"referencedMap":[[116,1],[247,2],[117,3],[119,4],[250,5],[249,6],[254,3],[177,7],[178,7],[179,8],[131,9],[180,10],[181,11],[182,12],[126,3],[129,13],[127,3],[128,3],[183,14],[184,15],[185,16],[186,17],[187,18],[188,19],[189,19],[190,20],[191,21],[192,22],[193,23],[132,3],[130,3],[194,24],[195,25],[196,26],[230,27],[197,28],[198,3],[199,29],[200,30],[201,31],[202,32],[203,33],[204,34],[205,35],[206,36],[207,37],[208,37],[209,38],[210,3],[211,39],[212,40],[214,41],[213,42],[215,43],[216,44],[217,45],[218,46],[219,47],[220,48],[221,49],[222,50],[223,51],[224,52],[225,53],[226,54],[227,55],[133,3],[134,3],[135,3],[174,56],[175,3],[176,3],[228,57],[229,58],[263,59],[248,59],[106,60],[63,3],[65,61],[64,62],[69,63],[104,64],[101,65],[103,66],[66,65],[67,67],[71,67],[70,68],[68,69],[102,70],[115,71],[100,65],[105,72],[98,3],[99,3],[72,73],[77,65],[79,65],[74,65],[75,73],[81,65],[82,74],[73,65],[78,65],[80,65],[76,65],[96,75],[95,65],[97,76],[91,65],[112,77],[110,78],[109,65],[107,63],[114,79],[111,80],[108,78],[113,78],[93,65],[92,65],[88,65],[94,81],[89,65],[90,82],[83,65],[84,65],[85,65],[86,65],[87,65],[136,3],[118,83],[62,3],[246,84],[243,85],[241,86],[244,87],[239,88],[238,89],[235,90],[236,91],[237,92],[125,93],[242,94],[240,95],[123,96],[245,97],[124,98],[122,99],[120,100],[121,101],[231,102],[234,103],[232,104],[233,105],[60,3],[61,3],[12,3],[11,3],[2,3],[13,3],[14,3],[15,3],[16,3],[17,3],[18,3],[19,3],[20,3],[3,3],[21,3],[22,3],[4,3],[23,3],[27,3],[24,3],[25,3],[26,3],[28,3],[29,3],[30,3],[5,3],[31,3],[32,3],[33,3],[34,3],[6,3],[38,3],[35,3],[36,3],[37,3],[39,3],[7,3],[40,3],[45,3],[46,3],[41,3],[42,3],[43,3],[44,3],[8,3],[50,3],[47,3],[48,3],[49,3],[51,3],[9,3],[52,3],[53,3],[54,3],[56,3],[55,3],[57,3],[58,3],[10,3],[59,3],[1,3],[152,106],[162,107],[151,106],[172,108],[143,109],[142,110],[171,111],[165,112],[170,113],[145,114],[159,115],[144,116],[168,117],[140,118],[139,111],[169,119],[141,120],[146,121],[147,3],[150,121],[137,3],[173,122],[163,123],[154,124],[155,125],[157,126],[153,127],[156,128],[166,111],[148,129],[149,130],[158,131],[138,132],[161,123],[160,121],[164,3],[167,133],[265,134],[253,135],[251,136],[259,137],[260,138],[258,3],[252,139],[266,140],[261,141],[262,142],[264,143],[256,144],[257,145],[255,3]],"latestChangedDtsFile":"./dist/index.d.ts","version":"5.9.3"} \ No newline at end of file diff --git a/packages/shared/src/protocol.ts b/packages/shared/src/protocol.ts index 3455aa4..3f4a069 100644 --- a/packages/shared/src/protocol.ts +++ b/packages/shared/src/protocol.ts @@ -34,9 +34,15 @@ export function decodeBinaryFrame(data: Uint8Array): { type: number; channel: nu } // ---- Flow control (pattern officiel xterm.js, watermarks du design) ---- +// INVARIANT anti-deadlock : ACK_EVERY_BYTES <= LOW_WATERMARK. Le client n'ACK +// qu'à réception de données ; si le serveur pouvait se mettre en pause avec un +// reliquat non-ACKé > LOW mais < pas d'ACK, plus aucun des deux ne progresserait. +// Avec ACK_EVERY <= LOW, une fois tout le flux en vol traité, le reliquat est +// < LOW et le serveur reprend toujours. (Complété côté client par un ACK +// traînant débouncé.) export const FLOW = { /** le client ACK tous les N octets réellement traités par xterm.write */ - ACK_EVERY_BYTES: 256 * 1024, + ACK_EVERY_BYTES: 64 * 1024, /** pause du PTY quand TOUS les clients interactifs dépassent HIGH */ HIGH_WATERMARK: 384 * 1024, /** reprise quand le min repasse sous LOW */ @@ -107,11 +113,12 @@ export function parseClientMessage(raw: string): ClientMessage | null { } if (typeof obj !== 'object' || obj === null || typeof (obj as { type?: unknown }).type !== 'string') return null; const m = obj as Record; - const isU32 = (v: unknown): v is number => typeof v === 'number' && Number.isInteger(v) && v >= 0 && v <= 0xffffffff; - const isDim = (v: unknown): v is number => typeof v === 'number' && Number.isInteger(v) && v >= 2 && v <= 1000; + const isU32 = (v: unknown): v is number => typeof v === 'number' && Number.isSafeInteger(v) && v >= 0 && v <= 0xffffffff; + const isCount = (v: unknown): v is number => typeof v === 'number' && Number.isSafeInteger(v) && v >= 0; + const isDim = (v: unknown): v is number => typeof v === 'number' && Number.isSafeInteger(v) && v >= 2 && v <= 1000; switch (m.type) { case 'hello': - return typeof m.protocol === 'number' ? { type: 'hello', protocol: m.protocol } : null; + return isCount(m.protocol) ? { type: 'hello', protocol: m.protocol } : null; case 'attach': return typeof m.sessionId === 'string' && (m.mode === 'interactive' || m.mode === 'observer') && isDim(m.cols) && isDim(m.rows) ? { type: 'attach', sessionId: m.sessionId, mode: m.mode, cols: m.cols, rows: m.rows } @@ -127,7 +134,7 @@ export function parseClientMessage(raw: string): ClientMessage | null { ? { type: 'resize', channel: m.channel, cols: m.cols, rows: m.rows } : null; case 'ack': - return isU32(m.channel) && typeof m.bytes === 'number' && m.bytes >= 0 + return isU32(m.channel) && isCount(m.bytes) ? { type: 'ack', channel: m.channel, bytes: m.bytes } : null; case 'sub': diff --git a/packages/shared/test/flow-invariants.test.ts b/packages/shared/test/flow-invariants.test.ts new file mode 100644 index 0000000..0d8a6d1 --- /dev/null +++ b/packages/shared/test/flow-invariants.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from 'vitest'; +import { FLOW, REPLAY_TAIL_BYTES } from '../src/index.js'; + +describe('invariants du flow control', () => { + // Le client n'ACK qu'à réception de données : si le serveur pouvait se mettre + // en pause avec un reliquat non-ACKé > LOW mais < pas d'ACK, plus rien ne + // progresserait (deadlock observé en acceptation P1 avec ACK_EVERY = 256 Kio). + it('ACK_EVERY_BYTES <= LOW_WATERMARK (anti-deadlock)', () => { + expect(FLOW.ACK_EVERY_BYTES).toBeLessThanOrEqual(FLOW.LOW_WATERMARK); + }); + + it('LOW < HIGH < LAGGING, tous strictement positifs', () => { + expect(FLOW.LOW_WATERMARK).toBeGreaterThan(0); + expect(FLOW.HIGH_WATERMARK).toBeGreaterThan(FLOW.LOW_WATERMARK); + expect(FLOW.LAGGING_BYTES).toBeGreaterThan(FLOW.HIGH_WATERMARK); + }); + + it('le replay tient dans la fenêtre lagging (resync ne re-déclenche pas lagging)', () => { + expect(REPLAY_TAIL_BYTES).toBeLessThan(FLOW.LAGGING_BYTES); + }); +}); diff --git a/packages/shared/test/protocol.test.ts b/packages/shared/test/protocol.test.ts new file mode 100644 index 0000000..f0d3d61 --- /dev/null +++ b/packages/shared/test/protocol.test.ts @@ -0,0 +1,241 @@ +import { describe, expect, it } from 'vitest'; +import { + BINARY_FRAME, + decodeBinaryFrame, + encodeBinaryFrame, + parseClientMessage, + type ClientMessage, +} from '../src/protocol.js'; + +// PRNG déterministe pour le fuzz (reproductible d'un run à l'autre) +function mulberry32(seed: number): () => number { + let a = seed >>> 0; + return () => { + a = (a + 0x6d2b79f5) >>> 0; + let t = a; + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + +const isU32 = (v: number): boolean => Number.isInteger(v) && v >= 0 && v <= 0xffffffff; +const isDim = (v: number): boolean => Number.isInteger(v) && v >= 2 && v <= 1000; + +// Invariants que tout message accepté DOIT respecter, quel que soit l'input +function assertInvariants(msg: ClientMessage): void { + switch (msg.type) { + case 'hello': + expect(typeof msg.protocol).toBe('number'); + break; + case 'attach': + expect(typeof msg.sessionId).toBe('string'); + expect(['interactive', 'observer']).toContain(msg.mode); + expect(isDim(msg.cols)).toBe(true); + expect(isDim(msg.rows)).toBe(true); + break; + case 'detach': + expect(isU32(msg.channel)).toBe(true); + break; + case 'stdin': + expect(isU32(msg.channel)).toBe(true); + expect(typeof msg.data).toBe('string'); + expect(msg.data.length).toBeLessThanOrEqual(65536); + break; + case 'resize': + expect(isU32(msg.channel)).toBe(true); + expect(isDim(msg.cols)).toBe(true); + expect(isDim(msg.rows)).toBe(true); + break; + case 'ack': + expect(isU32(msg.channel)).toBe(true); + expect(typeof msg.bytes).toBe('number'); + expect(msg.bytes).toBeGreaterThanOrEqual(0); + break; + case 'sub': + expect(msg.topics.every((t) => t === 'sessions')).toBe(true); + break; + case 'ping': + break; + } +} + +describe('parseClientMessage — cas valides', () => { + it('hello', () => { + expect(parseClientMessage('{"type":"hello","protocol":1}')).toEqual({ type: 'hello', protocol: 1 }); + }); + + it('attach avec dimensions aux bornes', () => { + const make = (cols: number, rows: number): string => + JSON.stringify({ type: 'attach', sessionId: 's1', mode: 'interactive', cols, rows }); + expect(parseClientMessage(make(2, 2))).toEqual({ type: 'attach', sessionId: 's1', mode: 'interactive', cols: 2, rows: 2 }); + expect(parseClientMessage(make(1000, 1000))).toEqual({ type: 'attach', sessionId: 's1', mode: 'interactive', cols: 1000, rows: 1000 }); + }); + + it('attach en mode observer', () => { + expect(parseClientMessage(JSON.stringify({ type: 'attach', sessionId: 's1', mode: 'observer', cols: 80, rows: 24 }))) + .toEqual({ type: 'attach', sessionId: 's1', mode: 'observer', cols: 80, rows: 24 }); + }); + + it('detach avec channel aux bornes u32', () => { + expect(parseClientMessage('{"type":"detach","channel":0}')).toEqual({ type: 'detach', channel: 0 }); + expect(parseClientMessage(`{"type":"detach","channel":${0xffffffff}}`)).toEqual({ type: 'detach', channel: 0xffffffff }); + }); + + it('stdin avec data à la taille max', () => { + const data = 'x'.repeat(65536); + expect(parseClientMessage(JSON.stringify({ type: 'stdin', channel: 1, data }))).toEqual({ type: 'stdin', channel: 1, data }); + }); + + it('resize', () => { + expect(parseClientMessage('{"type":"resize","channel":3,"cols":120,"rows":32}')) + .toEqual({ type: 'resize', channel: 3, cols: 120, rows: 32 }); + }); + + it('ack à zéro octet', () => { + expect(parseClientMessage('{"type":"ack","channel":1,"bytes":0}')).toEqual({ type: 'ack', channel: 1, bytes: 0 }); + }); + + it('sub avec topics sessions (et tableau vide accepté)', () => { + expect(parseClientMessage('{"type":"sub","topics":["sessions"]}')).toEqual({ type: 'sub', topics: ['sessions'] }); + expect(parseClientMessage('{"type":"sub","topics":[]}')).toEqual({ type: 'sub', topics: [] }); + }); + + it('ping (champs superflus ignorés)', () => { + expect(parseClientMessage('{"type":"ping","extra":42}')).toEqual({ type: 'ping' }); + }); +}); + +describe('parseClientMessage — cas malformés', () => { + it('JSON invalide ou non-objet', () => { + for (const raw of ['', 'not json', '{', '42', '"str"', 'null', 'true', '[]', '[1,2]']) { + expect(parseClientMessage(raw)).toBeNull(); + } + }); + + it('type absent, non-string ou inconnu', () => { + expect(parseClientMessage('{}')).toBeNull(); + expect(parseClientMessage('{"type":42}')).toBeNull(); + expect(parseClientMessage('{"type":null}')).toBeNull(); + expect(parseClientMessage('{"type":"unknown"}')).toBeNull(); + }); + + it('hello sans protocol ou protocol non numérique', () => { + expect(parseClientMessage('{"type":"hello"}')).toBeNull(); + expect(parseClientMessage('{"type":"hello","protocol":"1"}')).toBeNull(); + }); + + it('attach : dimensions hors bornes ou non entières', () => { + const make = (over: Record): string => + JSON.stringify({ type: 'attach', sessionId: 's1', mode: 'interactive', cols: 80, rows: 24, ...over }); + expect(parseClientMessage(make({ cols: 1 }))).toBeNull(); + expect(parseClientMessage(make({ cols: 1001 }))).toBeNull(); + expect(parseClientMessage(make({ rows: 0 }))).toBeNull(); + expect(parseClientMessage(make({ rows: 1001 }))).toBeNull(); + expect(parseClientMessage(make({ cols: 80.5 }))).toBeNull(); + expect(parseClientMessage(make({ cols: -80 }))).toBeNull(); + expect(parseClientMessage(make({ mode: 'admin' }))).toBeNull(); + expect(parseClientMessage(make({ sessionId: 42 }))).toBeNull(); + }); + + it('channel négatif, flottant ou > u32', () => { + expect(parseClientMessage('{"type":"detach","channel":-1}')).toBeNull(); + expect(parseClientMessage('{"type":"detach","channel":1.5}')).toBeNull(); + expect(parseClientMessage(`{"type":"detach","channel":${0x100000000}}`)).toBeNull(); + expect(parseClientMessage('{"type":"stdin","channel":-1,"data":"x"}')).toBeNull(); + expect(parseClientMessage('{"type":"ack","channel":-1,"bytes":0}')).toBeNull(); + }); + + it('stdin : data trop longue ou non-string', () => { + expect(parseClientMessage(JSON.stringify({ type: 'stdin', channel: 1, data: 'x'.repeat(65537) }))).toBeNull(); + expect(parseClientMessage('{"type":"stdin","channel":1,"data":42}')).toBeNull(); + expect(parseClientMessage('{"type":"stdin","channel":1}')).toBeNull(); + }); + + it('ack : bytes négatif ou non numérique', () => { + expect(parseClientMessage('{"type":"ack","channel":1,"bytes":-1}')).toBeNull(); + expect(parseClientMessage('{"type":"ack","channel":1,"bytes":"0"}')).toBeNull(); + }); + + it('sub : topics inconnu ou non-tableau', () => { + expect(parseClientMessage('{"type":"sub","topics":["other"]}')).toBeNull(); + expect(parseClientMessage('{"type":"sub","topics":["sessions","other"]}')).toBeNull(); + expect(parseClientMessage('{"type":"sub","topics":"sessions"}')).toBeNull(); + }); +}); + +describe('parseClientMessage — fuzz rapide', () => { + it('ne lève jamais et tout message accepté respecte les invariants', () => { + const rand = mulberry32(0xa5b0e7); + const types = ['hello', 'attach', 'detach', 'stdin', 'resize', 'ack', 'sub', 'ping', 'unknown', '', 'HELLO', 42, null]; + const values: unknown[] = [ + undefined, null, true, false, 0, -1, 1, 1.5, 2, 999, 1000, 1001, 0xffffffff, 0x100000000, + -0.0001, 1e21, '', 'x', '42', 'interactive', 'observer', 'sessions', {}, [], ['sessions'], + ['sessions', 'sessions'], ['sessions', 'other'], [42], 'a'.repeat(70000), + ]; + const pick = (arr: T[]): T => arr[Math.floor(rand() * arr.length)] as T; + for (let i = 0; i < 1000; i++) { + const obj: Record = { type: pick(types) }; + for (const key of ['protocol', 'sessionId', 'mode', 'cols', 'rows', 'channel', 'data', 'bytes', 'topics']) { + if (rand() < 0.7) obj[key] = pick(values); + } + const raw = JSON.stringify(obj); + let msg: ClientMessage | null = null; + expect(() => { + msg = parseClientMessage(raw); + }).not.toThrow(); + if (msg !== null) assertInvariants(msg); + } + }); +}); + +describe('encode/decodeBinaryFrame', () => { + it('round-trip payload vide', () => { + const frame = encodeBinaryFrame(BINARY_FRAME.OUTPUT, 0, new Uint8Array(0)); + expect(frame.byteLength).toBe(BINARY_FRAME.HEADER_BYTES); + const decoded = decodeBinaryFrame(frame); + expect(decoded.type).toBe(BINARY_FRAME.OUTPUT); + expect(decoded.channel).toBe(0); + expect(decoded.payload.byteLength).toBe(0); + }); + + it('round-trip gros payload (1 Mio) et channel max u32', () => { + const rand = mulberry32(0xdeadbe); + const payload = new Uint8Array(1024 * 1024); + for (let i = 0; i < payload.length; i++) payload[i] = Math.floor(rand() * 256); + const frame = encodeBinaryFrame(BINARY_FRAME.RESYNC, 0xffffffff, payload); + const decoded = decodeBinaryFrame(frame); + expect(decoded.type).toBe(BINARY_FRAME.RESYNC); + expect(decoded.channel).toBe(0xffffffff); + expect(Buffer.from(decoded.payload).equals(Buffer.from(payload))).toBe(true); + }); + + it('layout : type u8 puis channel u32 little-endian', () => { + const frame = encodeBinaryFrame(BINARY_FRAME.OUTPUT, 0x01020304, new Uint8Array([0xff])); + expect([...frame]).toEqual([0x01, 0x04, 0x03, 0x02, 0x01, 0xff]); + }); + + it('octets UTF-8 coupés en frontière de frame : le transport préserve les octets bruts', () => { + const text = Buffer.from('héllo 😀 wörld', 'utf8'); + const cut = 8; // coupe au milieu d'une séquence multi-octets + const part1 = new Uint8Array(text.subarray(0, cut)); + const part2 = new Uint8Array(text.subarray(cut)); + const d1 = decodeBinaryFrame(encodeBinaryFrame(BINARY_FRAME.OUTPUT, 7, part1)); + const d2 = decodeBinaryFrame(encodeBinaryFrame(BINARY_FRAME.OUTPUT, 7, part2)); + const reassembled = Buffer.concat([d1.payload, d2.payload]); + expect(reassembled.equals(text)).toBe(true); + expect(reassembled.toString('utf8')).toBe('héllo 😀 wörld'); + }); + + it('decode sur une vue avec byteOffset non nul (subarray d’un buffer plus large)', () => { + const payload = new Uint8Array([1, 2, 3]); + const frame = encodeBinaryFrame(BINARY_FRAME.OUTPUT, 42, payload); + const padded = new Uint8Array(frame.byteLength + 11); + padded.set(frame, 11); + const view = padded.subarray(11); + const decoded = decodeBinaryFrame(view); + expect(decoded.type).toBe(BINARY_FRAME.OUTPUT); + expect(decoded.channel).toBe(42); + expect([...decoded.payload]).toEqual([1, 2, 3]); + }); +}); diff --git a/packages/shared/tsconfig.tsbuildinfo b/packages/shared/tsconfig.tsbuildinfo index 9ab0d1d..68f838d 100644 --- a/packages/shared/tsconfig.tsbuildinfo +++ b/packages/shared/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","./src/protocol.ts","./src/api.ts","./src/index.ts","../../node_modules/@types/deep-eql/index.d.ts","../../node_modules/assertion-error/index.d.ts","../../node_modules/@types/chai/index.d.ts","../../node_modules/@types/estree/index.d.ts","../../node_modules/@types/node/compatibility/disposable.d.ts","../../node_modules/@types/node/compatibility/indexable.d.ts","../../node_modules/@types/node/compatibility/iterators.d.ts","../../node_modules/@types/node/compatibility/index.d.ts","../../node_modules/@types/node/globals.typedarray.d.ts","../../node_modules/@types/node/buffer.buffer.d.ts","../../node_modules/@types/node/globals.d.ts","../../node_modules/@types/node/web-globals/abortcontroller.d.ts","../../node_modules/@types/node/web-globals/domexception.d.ts","../../node_modules/@types/node/web-globals/events.d.ts","../../node_modules/buffer/index.d.ts","../../node_modules/undici-types/header.d.ts","../../node_modules/undici-types/readable.d.ts","../../node_modules/undici-types/file.d.ts","../../node_modules/undici-types/fetch.d.ts","../../node_modules/undici-types/formdata.d.ts","../../node_modules/undici-types/connector.d.ts","../../node_modules/undici-types/client.d.ts","../../node_modules/undici-types/errors.d.ts","../../node_modules/undici-types/dispatcher.d.ts","../../node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/undici-types/global-origin.d.ts","../../node_modules/undici-types/pool-stats.d.ts","../../node_modules/undici-types/pool.d.ts","../../node_modules/undici-types/handlers.d.ts","../../node_modules/undici-types/balanced-pool.d.ts","../../node_modules/undici-types/agent.d.ts","../../node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/undici-types/mock-agent.d.ts","../../node_modules/undici-types/mock-client.d.ts","../../node_modules/undici-types/mock-pool.d.ts","../../node_modules/undici-types/mock-errors.d.ts","../../node_modules/undici-types/proxy-agent.d.ts","../../node_modules/undici-types/env-http-proxy-agent.d.ts","../../node_modules/undici-types/retry-handler.d.ts","../../node_modules/undici-types/retry-agent.d.ts","../../node_modules/undici-types/api.d.ts","../../node_modules/undici-types/interceptors.d.ts","../../node_modules/undici-types/util.d.ts","../../node_modules/undici-types/cookies.d.ts","../../node_modules/undici-types/patch.d.ts","../../node_modules/undici-types/websocket.d.ts","../../node_modules/undici-types/eventsource.d.ts","../../node_modules/undici-types/filereader.d.ts","../../node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/undici-types/content-type.d.ts","../../node_modules/undici-types/cache.d.ts","../../node_modules/undici-types/index.d.ts","../../node_modules/@types/node/web-globals/fetch.d.ts","../../node_modules/@types/node/web-globals/navigator.d.ts","../../node_modules/@types/node/web-globals/storage.d.ts","../../node_modules/@types/node/assert.d.ts","../../node_modules/@types/node/assert/strict.d.ts","../../node_modules/@types/node/async_hooks.d.ts","../../node_modules/@types/node/buffer.d.ts","../../node_modules/@types/node/child_process.d.ts","../../node_modules/@types/node/cluster.d.ts","../../node_modules/@types/node/console.d.ts","../../node_modules/@types/node/constants.d.ts","../../node_modules/@types/node/crypto.d.ts","../../node_modules/@types/node/dgram.d.ts","../../node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/@types/node/dns.d.ts","../../node_modules/@types/node/dns/promises.d.ts","../../node_modules/@types/node/domain.d.ts","../../node_modules/@types/node/events.d.ts","../../node_modules/@types/node/fs.d.ts","../../node_modules/@types/node/fs/promises.d.ts","../../node_modules/@types/node/http.d.ts","../../node_modules/@types/node/http2.d.ts","../../node_modules/@types/node/https.d.ts","../../node_modules/@types/node/inspector.d.ts","../../node_modules/@types/node/inspector.generated.d.ts","../../node_modules/@types/node/module.d.ts","../../node_modules/@types/node/net.d.ts","../../node_modules/@types/node/os.d.ts","../../node_modules/@types/node/path.d.ts","../../node_modules/@types/node/perf_hooks.d.ts","../../node_modules/@types/node/process.d.ts","../../node_modules/@types/node/punycode.d.ts","../../node_modules/@types/node/querystring.d.ts","../../node_modules/@types/node/readline.d.ts","../../node_modules/@types/node/readline/promises.d.ts","../../node_modules/@types/node/repl.d.ts","../../node_modules/@types/node/sea.d.ts","../../node_modules/@types/node/sqlite.d.ts","../../node_modules/@types/node/stream.d.ts","../../node_modules/@types/node/stream/promises.d.ts","../../node_modules/@types/node/stream/consumers.d.ts","../../node_modules/@types/node/stream/web.d.ts","../../node_modules/@types/node/string_decoder.d.ts","../../node_modules/@types/node/test.d.ts","../../node_modules/@types/node/timers.d.ts","../../node_modules/@types/node/timers/promises.d.ts","../../node_modules/@types/node/tls.d.ts","../../node_modules/@types/node/trace_events.d.ts","../../node_modules/@types/node/tty.d.ts","../../node_modules/@types/node/url.d.ts","../../node_modules/@types/node/util.d.ts","../../node_modules/@types/node/v8.d.ts","../../node_modules/@types/node/vm.d.ts","../../node_modules/@types/node/wasi.d.ts","../../node_modules/@types/node/worker_threads.d.ts","../../node_modules/@types/node/zlib.d.ts","../../node_modules/@types/node/index.d.ts","../../node_modules/@types/ws/index.d.ts"],"fileIdsList":[[65,66,74,123,140,141],[74,123,140,141],[74,120,121,123,140,141],[74,122,123,140,141],[123,140,141],[74,123,128,140,141,158],[74,123,124,129,134,140,141,143,155,166],[74,123,124,125,134,140,141,143],[69,70,71,74,123,140,141],[74,123,126,140,141,167],[74,123,127,128,135,140,141,144],[74,123,128,140,141,155,163],[74,123,129,131,134,140,141,143],[74,122,123,130,140,141],[74,123,131,132,140,141],[74,123,133,134,140,141],[74,122,123,134,140,141],[74,123,134,135,136,140,141,155,166],[74,123,134,135,136,140,141,150,155,158],[74,116,123,131,134,137,140,141,143,155,166],[74,123,134,135,137,138,140,141,143,155,163,166],[74,123,137,139,140,141,155,163,166],[72,73,74,75,76,77,78,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172],[74,123,134,140,141],[74,123,140,141,142,166],[74,123,131,134,140,141,143,155],[74,123,140,141,144],[74,123,140,141,145],[74,122,123,140,141,146],[74,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172],[74,123,140,141,148],[74,123,140,141,149],[74,123,134,140,141,150,151],[74,123,140,141,150,152,167,169],[74,123,135,140,141],[74,123,134,140,141,155,156,158],[74,123,140,141,157,158],[74,123,140,141,155,156],[74,123,140,141,158],[74,123,140,141,159],[74,120,123,140,141,155,160,166],[74,123,134,140,141,161,162],[74,123,140,141,161,162],[74,123,128,140,141,143,155,163],[74,123,140,141,164],[74,123,140,141,143,165],[74,123,137,140,141,149,166],[74,123,128,140,141,167],[74,123,140,141,155,168],[74,123,140,141,142,169],[74,123,140,141,170],[74,116,123,140,141],[74,116,123,134,136,140,141,146,155,158,166,168,169,171],[74,123,140,141,155,172],[74,123,134,137,139,140,141,143,155,163,166,172,173],[74,88,92,123,140,141,166],[74,88,123,140,141,155,166],[74,83,123,140,141],[74,85,88,123,140,141,163,166],[74,123,140,141,143,163],[74,123,140,141,173],[74,83,123,140,141,173],[74,85,88,123,140,141,143,166],[74,80,81,84,87,123,134,140,141,155,166],[74,88,95,123,140,141],[74,80,86,123,140,141],[74,88,109,110,123,140,141],[74,84,88,123,140,141,158,166,173],[74,109,123,140,141,173],[74,82,83,123,140,141,173],[74,88,123,140,141],[74,82,83,84,85,86,87,88,89,90,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,110,111,112,113,114,115,123,140,141],[74,88,103,123,140,141],[74,88,95,96,123,140,141],[74,86,88,96,97,123,140,141],[74,87,123,140,141],[74,80,83,88,123,140,141],[74,88,92,96,97,123,140,141],[74,92,123,140,141],[74,86,88,91,123,140,141,166],[74,80,85,88,95,123,140,141],[74,123,140,141,155],[74,83,88,109,123,140,141,171,173],[62,74,123,140,141],[62,63,74,123,140,141]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"df83c2a6c73228b625b0beb6669c7ee2a09c914637e2d35170723ad49c0f5cd4","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"997f21102c89aff9cb1655113576e653940a290f1534d5e4957503c9052c9f9b","signature":"155aa8c32f0815cb7f44246680859e2724428459727e6067d0d434a666b72e5b","impliedFormat":99},{"version":"a12d76034419dd99ec4b201711bce3870bc140f3c1fa7a9eb9efbc22295cfa32","signature":"0eaa5ca815f0d1f498a205737cc71a5259671ee1ffc96eb881ee721c2d60bf30","impliedFormat":99},{"version":"8b93a53483cf3bb55c754db9dffd3cc1e3d05f87fe7158aceb4b30f5521a14fc","impliedFormat":99},{"version":"427fe2004642504828c1476d0af4270e6ad4db6de78c0b5da3e4c5ca95052a99","impliedFormat":1},{"version":"2eeffcee5c1661ddca53353929558037b8cf305ffb86a803512982f99bcab50d","impliedFormat":99},{"version":"9afb4cb864d297e4092a79ee2871b5d3143ea14153f62ef0bb04ede25f432030","affectsGlobalScope":true,"impliedFormat":99},{"version":"751764bb94219b4ce8f5475dc35d3de2e432fea01a0c9610cd7f69ad05e398c6","impliedFormat":1},{"version":"6c7176368037af28cb72f2392010fa1cef295d6d6744bca8cfb54985f3a18c3e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"437e20f2ba32abaeb7985e0afe0002de1917bc74e949ba585e49feba65da6ca1","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"808069bba06b6768b62fd22429b53362e7af342da4a236ed2d2e1c89fcca3b4a","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e9c23ba78aabc2e0a27033f18737a6df754067731e69dc5f52823957d60a4b6","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b52476feb4a0cbcb25e5931b930fc73cb6643fb1a5060bf8a3dda0eeae5b4b68","affectsGlobalScope":true,"impliedFormat":1},{"version":"f9501cc13ce624c72b61f12b3963e84fad210fbdf0ffbc4590e08460a3f04eba","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0fa06ada475b910e2106c98c68b10483dc8811d0c14a8a8dd36efb2672485b29","impliedFormat":1},{"version":"33e5e9aba62c3193d10d1d33ae1fa75c46a1171cf76fef750777377d53b0303f","impliedFormat":1},{"version":"2b06b93fd01bcd49d1a6bd1f9b65ddcae6480b9a86e9061634d6f8e354c1468f","impliedFormat":1},{"version":"6a0cd27e5dc2cfbe039e731cf879d12b0e2dded06d1b1dedad07f7712de0d7f4","affectsGlobalScope":true,"impliedFormat":1},{"version":"13f5c844119c43e51ce777c509267f14d6aaf31eafb2c2b002ca35584cd13b29","impliedFormat":1},{"version":"e60477649d6ad21542bd2dc7e3d9ff6853d0797ba9f689ba2f6653818999c264","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"4c829ab315f57c5442c6667b53769975acbf92003a66aef19bce151987675bd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"b2ade7657e2db96d18315694789eff2ddd3d8aea7215b181f8a0b303277cc579","impliedFormat":1},{"version":"9855e02d837744303391e5623a531734443a5f8e6e8755e018c41d63ad797db2","impliedFormat":1},{"version":"4d631b81fa2f07a0e63a9a143d6a82c25c5f051298651a9b69176ba28930756d","impliedFormat":1},{"version":"836a356aae992ff3c28a0212e3eabcb76dd4b0cc06bcb9607aeef560661b860d","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"41670ee38943d9cbb4924e436f56fc19ee94232bc96108562de1a734af20dc2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"c906fb15bd2aabc9ed1e3f44eb6a8661199d6c320b3aa196b826121552cb3695","impliedFormat":1},{"version":"22295e8103f1d6d8ea4b5d6211e43421fe4564e34d0dd8e09e520e452d89e659","impliedFormat":1},{"version":"58647d85d0f722a1ce9de50955df60a7489f0593bf1a7015521efe901c06d770","impliedFormat":1},{"version":"9018e9493596e2d041fc531f5101a0d05f9d2e1311f4ae3c74345b1766158c08","impliedFormat":1},{"version":"a10f0e1854f3316d7ee437b79649e5a6ae3ae14ffe6322b02d4987071a95362e","impliedFormat":1},{"version":"e208f73ef6a980104304b0d2ca5f6bf1b85de6009d2c7e404028b875020fa8f2","impliedFormat":1},{"version":"d163b6bc2372b4f07260747cbc6c0a6405ab3fbcea3852305e98ac43ca59f5bc","impliedFormat":1},{"version":"e6fa9ad47c5f71ff733744a029d1dc472c618de53804eae08ffc243b936f87ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6f137d651076822d4fe884287e68fd61785a0d3d1fdb250a5059b691fa897db","impliedFormat":1},{"version":"24826ed94a78d5c64bd857570fdbd96229ad41b5cb654c08d75a9845e3ab7dde","impliedFormat":1},{"version":"8b479a130ccb62e98f11f136d3ac80f2984fdc07616516d29881f3061f2dd472","impliedFormat":1},{"version":"928af3d90454bf656a52a48679f199f64c1435247d6189d1caf4c68f2eaf921f","affectsGlobalScope":true,"impliedFormat":1},{"version":"bceb58df66ab8fb00170df20cd813978c5ab84be1d285710c4eb005d8e9d8efb","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"933921f0bb0ec12ef45d1062a1fc0f27635318f4d294e4d99de9a5493e618ca2","impliedFormat":1},{"version":"71a0f3ad612c123b57239a7749770017ecfe6b66411488000aba83e4546fde25","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"4f9d8ca0c417b67b69eeb54c7ca1bedd7b56034bb9bfd27c5d4f3bc4692daca7","impliedFormat":1},{"version":"814118df420c4e38fe5ae1b9a3bafb6e9c2aa40838e528cde908381867be6466","impliedFormat":1},{"version":"a3fc63c0d7b031693f665f5494412ba4b551fe644ededccc0ab5922401079c95","impliedFormat":1},{"version":"80523c00b8544a2000ae0143e4a90a00b47f99823eb7926c1e03c494216fc363","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"746911b62b329587939560deb5c036aca48aece03147b021fa680223255d5183","affectsGlobalScope":true,"impliedFormat":1},{"version":"18fd40412d102c5564136f29735e5d1c3b455b8a37f920da79561f1fde068208","impliedFormat":1},{"version":"c8d3e5a18ba35629954e48c4cc8f11dc88224650067a172685c736b27a34a4dc","impliedFormat":1},{"version":"f0be1b8078cd549d91f37c30c222c2a187ac1cf981d994fb476a1adc61387b14","affectsGlobalScope":true,"impliedFormat":1},{"version":"0aaed1d72199b01234152f7a60046bc947f1f37d78d182e9ae09c4289e06a592","impliedFormat":1},{"version":"2b55d426ff2b9087485e52ac4bc7cfafe1dc420fc76dad926cd46526567c501a","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"5b7aa3c4c1a5d81b411e8cb302b45507fea9358d3569196b27eb1a27ae3a90ef","affectsGlobalScope":true,"impliedFormat":1},{"version":"5987a903da92c7462e0b35704ce7da94d7fdc4b89a984871c0e2b87a8aae9e69","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea08a0345023ade2b47fbff5a76d0d0ed8bff10bc9d22b83f40858a8e941501c","impliedFormat":1},{"version":"47613031a5a31510831304405af561b0ffaedb734437c595256bb61a90f9311b","impliedFormat":1},{"version":"ae062ce7d9510060c5d7e7952ae379224fb3f8f2dd74e88959878af2057c143b","impliedFormat":1},{"version":"8a1a0d0a4a06a8d278947fcb66bf684f117bf147f89b06e50662d79a53be3e9f","affectsGlobalScope":true,"impliedFormat":1},{"version":"358765d5ea8afd285d4fd1532e78b88273f18cb3f87403a9b16fef61ac9fdcfe","impliedFormat":1},{"version":"9f55299850d4f0921e79b6bf344b47c420ce0f507b9dcf593e532b09ea7eeea1","impliedFormat":1},{"version":"1ba59c8bbeed2cb75b239bb12041582fa3e8ef32f8d0bd0ec802e38442d3f317","impliedFormat":1}],"root":[[62,64]],"options":{"composite":true,"declaration":true,"declarationMap":true,"esModuleInterop":true,"exactOptionalPropertyTypes":true,"module":199,"noImplicitOverride":true,"noUncheckedIndexedAccess":true,"outDir":"./dist","rootDir":"./src","skipLibCheck":true,"sourceMap":true,"strict":true,"target":10,"verbatimModuleSyntax":true},"referencedMap":[[67,1],[65,2],[68,2],[120,3],[121,3],[122,4],[74,5],[123,6],[124,7],[125,8],[69,2],[72,9],[70,2],[71,2],[126,10],[127,11],[128,12],[129,13],[130,14],[131,15],[132,15],[133,16],[134,17],[135,18],[136,19],[75,2],[73,2],[137,20],[138,21],[139,22],[173,23],[140,24],[141,2],[142,25],[143,26],[144,27],[145,28],[146,29],[147,30],[148,31],[149,32],[150,33],[151,33],[152,34],[153,2],[154,35],[155,36],[157,37],[156,38],[158,39],[159,40],[160,41],[161,42],[162,43],[163,44],[164,45],[165,46],[166,47],[167,48],[168,49],[169,50],[170,51],[76,2],[77,2],[78,2],[117,52],[118,2],[119,2],[171,53],[172,54],[174,55],[66,2],[79,2],[60,2],[61,2],[12,2],[11,2],[2,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[20,2],[3,2],[21,2],[22,2],[4,2],[23,2],[27,2],[24,2],[25,2],[26,2],[28,2],[29,2],[30,2],[5,2],[31,2],[32,2],[33,2],[34,2],[6,2],[38,2],[35,2],[36,2],[37,2],[39,2],[7,2],[40,2],[45,2],[46,2],[41,2],[42,2],[43,2],[44,2],[8,2],[50,2],[47,2],[48,2],[49,2],[51,2],[9,2],[52,2],[53,2],[54,2],[56,2],[55,2],[57,2],[58,2],[10,2],[59,2],[1,2],[95,56],[105,57],[94,56],[115,58],[86,59],[85,60],[114,61],[108,62],[113,63],[88,64],[102,65],[87,66],[111,67],[83,68],[82,61],[112,69],[84,70],[89,71],[90,2],[93,71],[80,2],[116,72],[106,73],[97,74],[98,75],[100,76],[96,77],[99,78],[109,61],[91,79],[92,80],[101,81],[81,82],[104,73],[103,71],[107,2],[110,83],[63,84],[64,85],[62,2]],"latestChangedDtsFile":"./dist/index.d.ts","version":"5.9.3"} \ No newline at end of file +{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","./src/protocol.ts","./src/api.ts","./src/index.ts","../../node_modules/@types/deep-eql/index.d.ts","../../node_modules/assertion-error/index.d.ts","../../node_modules/@types/chai/index.d.ts","../../node_modules/@types/estree/index.d.ts","../../node_modules/@types/node/compatibility/disposable.d.ts","../../node_modules/@types/node/compatibility/indexable.d.ts","../../node_modules/@types/node/compatibility/iterators.d.ts","../../node_modules/@types/node/compatibility/index.d.ts","../../node_modules/@types/node/globals.typedarray.d.ts","../../node_modules/@types/node/buffer.buffer.d.ts","../../node_modules/@types/node/globals.d.ts","../../node_modules/@types/node/web-globals/abortcontroller.d.ts","../../node_modules/@types/node/web-globals/domexception.d.ts","../../node_modules/@types/node/web-globals/events.d.ts","../../node_modules/buffer/index.d.ts","../../node_modules/undici-types/header.d.ts","../../node_modules/undici-types/readable.d.ts","../../node_modules/undici-types/file.d.ts","../../node_modules/undici-types/fetch.d.ts","../../node_modules/undici-types/formdata.d.ts","../../node_modules/undici-types/connector.d.ts","../../node_modules/undici-types/client.d.ts","../../node_modules/undici-types/errors.d.ts","../../node_modules/undici-types/dispatcher.d.ts","../../node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/undici-types/global-origin.d.ts","../../node_modules/undici-types/pool-stats.d.ts","../../node_modules/undici-types/pool.d.ts","../../node_modules/undici-types/handlers.d.ts","../../node_modules/undici-types/balanced-pool.d.ts","../../node_modules/undici-types/agent.d.ts","../../node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/undici-types/mock-agent.d.ts","../../node_modules/undici-types/mock-client.d.ts","../../node_modules/undici-types/mock-pool.d.ts","../../node_modules/undici-types/mock-errors.d.ts","../../node_modules/undici-types/proxy-agent.d.ts","../../node_modules/undici-types/env-http-proxy-agent.d.ts","../../node_modules/undici-types/retry-handler.d.ts","../../node_modules/undici-types/retry-agent.d.ts","../../node_modules/undici-types/api.d.ts","../../node_modules/undici-types/interceptors.d.ts","../../node_modules/undici-types/util.d.ts","../../node_modules/undici-types/cookies.d.ts","../../node_modules/undici-types/patch.d.ts","../../node_modules/undici-types/websocket.d.ts","../../node_modules/undici-types/eventsource.d.ts","../../node_modules/undici-types/filereader.d.ts","../../node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/undici-types/content-type.d.ts","../../node_modules/undici-types/cache.d.ts","../../node_modules/undici-types/index.d.ts","../../node_modules/@types/node/web-globals/fetch.d.ts","../../node_modules/@types/node/web-globals/navigator.d.ts","../../node_modules/@types/node/web-globals/storage.d.ts","../../node_modules/@types/node/assert.d.ts","../../node_modules/@types/node/assert/strict.d.ts","../../node_modules/@types/node/async_hooks.d.ts","../../node_modules/@types/node/buffer.d.ts","../../node_modules/@types/node/child_process.d.ts","../../node_modules/@types/node/cluster.d.ts","../../node_modules/@types/node/console.d.ts","../../node_modules/@types/node/constants.d.ts","../../node_modules/@types/node/crypto.d.ts","../../node_modules/@types/node/dgram.d.ts","../../node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/@types/node/dns.d.ts","../../node_modules/@types/node/dns/promises.d.ts","../../node_modules/@types/node/domain.d.ts","../../node_modules/@types/node/events.d.ts","../../node_modules/@types/node/fs.d.ts","../../node_modules/@types/node/fs/promises.d.ts","../../node_modules/@types/node/http.d.ts","../../node_modules/@types/node/http2.d.ts","../../node_modules/@types/node/https.d.ts","../../node_modules/@types/node/inspector.d.ts","../../node_modules/@types/node/inspector.generated.d.ts","../../node_modules/@types/node/module.d.ts","../../node_modules/@types/node/net.d.ts","../../node_modules/@types/node/os.d.ts","../../node_modules/@types/node/path.d.ts","../../node_modules/@types/node/perf_hooks.d.ts","../../node_modules/@types/node/process.d.ts","../../node_modules/@types/node/punycode.d.ts","../../node_modules/@types/node/querystring.d.ts","../../node_modules/@types/node/readline.d.ts","../../node_modules/@types/node/readline/promises.d.ts","../../node_modules/@types/node/repl.d.ts","../../node_modules/@types/node/sea.d.ts","../../node_modules/@types/node/sqlite.d.ts","../../node_modules/@types/node/stream.d.ts","../../node_modules/@types/node/stream/promises.d.ts","../../node_modules/@types/node/stream/consumers.d.ts","../../node_modules/@types/node/stream/web.d.ts","../../node_modules/@types/node/string_decoder.d.ts","../../node_modules/@types/node/test.d.ts","../../node_modules/@types/node/timers.d.ts","../../node_modules/@types/node/timers/promises.d.ts","../../node_modules/@types/node/tls.d.ts","../../node_modules/@types/node/trace_events.d.ts","../../node_modules/@types/node/tty.d.ts","../../node_modules/@types/node/url.d.ts","../../node_modules/@types/node/util.d.ts","../../node_modules/@types/node/v8.d.ts","../../node_modules/@types/node/vm.d.ts","../../node_modules/@types/node/wasi.d.ts","../../node_modules/@types/node/worker_threads.d.ts","../../node_modules/@types/node/zlib.d.ts","../../node_modules/@types/node/index.d.ts","../../node_modules/@types/ws/index.d.ts"],"fileIdsList":[[65,66,74,123,140,141],[74,123,140,141],[74,120,121,123,140,141],[74,122,123,140,141],[123,140,141],[74,123,128,140,141,158],[74,123,124,129,134,140,141,143,155,166],[74,123,124,125,134,140,141,143],[69,70,71,74,123,140,141],[74,123,126,140,141,167],[74,123,127,128,135,140,141,144],[74,123,128,140,141,155,163],[74,123,129,131,134,140,141,143],[74,122,123,130,140,141],[74,123,131,132,140,141],[74,123,133,134,140,141],[74,122,123,134,140,141],[74,123,134,135,136,140,141,155,166],[74,123,134,135,136,140,141,150,155,158],[74,116,123,131,134,137,140,141,143,155,166],[74,123,134,135,137,138,140,141,143,155,163,166],[74,123,137,139,140,141,155,163,166],[72,73,74,75,76,77,78,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172],[74,123,134,140,141],[74,123,140,141,142,166],[74,123,131,134,140,141,143,155],[74,123,140,141,144],[74,123,140,141,145],[74,122,123,140,141,146],[74,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172],[74,123,140,141,148],[74,123,140,141,149],[74,123,134,140,141,150,151],[74,123,140,141,150,152,167,169],[74,123,135,140,141],[74,123,134,140,141,155,156,158],[74,123,140,141,157,158],[74,123,140,141,155,156],[74,123,140,141,158],[74,123,140,141,159],[74,120,123,140,141,155,160,166],[74,123,134,140,141,161,162],[74,123,140,141,161,162],[74,123,128,140,141,143,155,163],[74,123,140,141,164],[74,123,140,141,143,165],[74,123,137,140,141,149,166],[74,123,128,140,141,167],[74,123,140,141,155,168],[74,123,140,141,142,169],[74,123,140,141,170],[74,116,123,140,141],[74,116,123,134,136,140,141,146,155,158,166,168,169,171],[74,123,140,141,155,172],[74,123,134,137,139,140,141,143,155,163,166,172,173],[74,88,92,123,140,141,166],[74,88,123,140,141,155,166],[74,83,123,140,141],[74,85,88,123,140,141,163,166],[74,123,140,141,143,163],[74,123,140,141,173],[74,83,123,140,141,173],[74,85,88,123,140,141,143,166],[74,80,81,84,87,123,134,140,141,155,166],[74,88,95,123,140,141],[74,80,86,123,140,141],[74,88,109,110,123,140,141],[74,84,88,123,140,141,158,166,173],[74,109,123,140,141,173],[74,82,83,123,140,141,173],[74,88,123,140,141],[74,82,83,84,85,86,87,88,89,90,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,110,111,112,113,114,115,123,140,141],[74,88,103,123,140,141],[74,88,95,96,123,140,141],[74,86,88,96,97,123,140,141],[74,87,123,140,141],[74,80,83,88,123,140,141],[74,88,92,96,97,123,140,141],[74,92,123,140,141],[74,86,88,91,123,140,141,166],[74,80,85,88,95,123,140,141],[74,123,140,141,155],[74,83,88,109,123,140,141,171,173],[62,74,123,140,141],[62,63,74,123,140,141]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"df83c2a6c73228b625b0beb6669c7ee2a09c914637e2d35170723ad49c0f5cd4","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"cd3f497a20129961ff2798627df44b34b84b54b3f45f18d286a0398a379cbc17","signature":"155aa8c32f0815cb7f44246680859e2724428459727e6067d0d434a666b72e5b","impliedFormat":99},{"version":"a12d76034419dd99ec4b201711bce3870bc140f3c1fa7a9eb9efbc22295cfa32","signature":"0eaa5ca815f0d1f498a205737cc71a5259671ee1ffc96eb881ee721c2d60bf30","impliedFormat":99},{"version":"8b93a53483cf3bb55c754db9dffd3cc1e3d05f87fe7158aceb4b30f5521a14fc","impliedFormat":99},{"version":"427fe2004642504828c1476d0af4270e6ad4db6de78c0b5da3e4c5ca95052a99","impliedFormat":1},{"version":"2eeffcee5c1661ddca53353929558037b8cf305ffb86a803512982f99bcab50d","impliedFormat":99},{"version":"9afb4cb864d297e4092a79ee2871b5d3143ea14153f62ef0bb04ede25f432030","affectsGlobalScope":true,"impliedFormat":99},{"version":"751764bb94219b4ce8f5475dc35d3de2e432fea01a0c9610cd7f69ad05e398c6","impliedFormat":1},{"version":"6c7176368037af28cb72f2392010fa1cef295d6d6744bca8cfb54985f3a18c3e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"437e20f2ba32abaeb7985e0afe0002de1917bc74e949ba585e49feba65da6ca1","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"808069bba06b6768b62fd22429b53362e7af342da4a236ed2d2e1c89fcca3b4a","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e9c23ba78aabc2e0a27033f18737a6df754067731e69dc5f52823957d60a4b6","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b52476feb4a0cbcb25e5931b930fc73cb6643fb1a5060bf8a3dda0eeae5b4b68","affectsGlobalScope":true,"impliedFormat":1},{"version":"f9501cc13ce624c72b61f12b3963e84fad210fbdf0ffbc4590e08460a3f04eba","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0fa06ada475b910e2106c98c68b10483dc8811d0c14a8a8dd36efb2672485b29","impliedFormat":1},{"version":"33e5e9aba62c3193d10d1d33ae1fa75c46a1171cf76fef750777377d53b0303f","impliedFormat":1},{"version":"2b06b93fd01bcd49d1a6bd1f9b65ddcae6480b9a86e9061634d6f8e354c1468f","impliedFormat":1},{"version":"6a0cd27e5dc2cfbe039e731cf879d12b0e2dded06d1b1dedad07f7712de0d7f4","affectsGlobalScope":true,"impliedFormat":1},{"version":"13f5c844119c43e51ce777c509267f14d6aaf31eafb2c2b002ca35584cd13b29","impliedFormat":1},{"version":"e60477649d6ad21542bd2dc7e3d9ff6853d0797ba9f689ba2f6653818999c264","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"4c829ab315f57c5442c6667b53769975acbf92003a66aef19bce151987675bd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"b2ade7657e2db96d18315694789eff2ddd3d8aea7215b181f8a0b303277cc579","impliedFormat":1},{"version":"9855e02d837744303391e5623a531734443a5f8e6e8755e018c41d63ad797db2","impliedFormat":1},{"version":"4d631b81fa2f07a0e63a9a143d6a82c25c5f051298651a9b69176ba28930756d","impliedFormat":1},{"version":"836a356aae992ff3c28a0212e3eabcb76dd4b0cc06bcb9607aeef560661b860d","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"41670ee38943d9cbb4924e436f56fc19ee94232bc96108562de1a734af20dc2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"c906fb15bd2aabc9ed1e3f44eb6a8661199d6c320b3aa196b826121552cb3695","impliedFormat":1},{"version":"22295e8103f1d6d8ea4b5d6211e43421fe4564e34d0dd8e09e520e452d89e659","impliedFormat":1},{"version":"58647d85d0f722a1ce9de50955df60a7489f0593bf1a7015521efe901c06d770","impliedFormat":1},{"version":"9018e9493596e2d041fc531f5101a0d05f9d2e1311f4ae3c74345b1766158c08","impliedFormat":1},{"version":"a10f0e1854f3316d7ee437b79649e5a6ae3ae14ffe6322b02d4987071a95362e","impliedFormat":1},{"version":"e208f73ef6a980104304b0d2ca5f6bf1b85de6009d2c7e404028b875020fa8f2","impliedFormat":1},{"version":"d163b6bc2372b4f07260747cbc6c0a6405ab3fbcea3852305e98ac43ca59f5bc","impliedFormat":1},{"version":"e6fa9ad47c5f71ff733744a029d1dc472c618de53804eae08ffc243b936f87ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6f137d651076822d4fe884287e68fd61785a0d3d1fdb250a5059b691fa897db","impliedFormat":1},{"version":"24826ed94a78d5c64bd857570fdbd96229ad41b5cb654c08d75a9845e3ab7dde","impliedFormat":1},{"version":"8b479a130ccb62e98f11f136d3ac80f2984fdc07616516d29881f3061f2dd472","impliedFormat":1},{"version":"928af3d90454bf656a52a48679f199f64c1435247d6189d1caf4c68f2eaf921f","affectsGlobalScope":true,"impliedFormat":1},{"version":"bceb58df66ab8fb00170df20cd813978c5ab84be1d285710c4eb005d8e9d8efb","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"933921f0bb0ec12ef45d1062a1fc0f27635318f4d294e4d99de9a5493e618ca2","impliedFormat":1},{"version":"71a0f3ad612c123b57239a7749770017ecfe6b66411488000aba83e4546fde25","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"4f9d8ca0c417b67b69eeb54c7ca1bedd7b56034bb9bfd27c5d4f3bc4692daca7","impliedFormat":1},{"version":"814118df420c4e38fe5ae1b9a3bafb6e9c2aa40838e528cde908381867be6466","impliedFormat":1},{"version":"a3fc63c0d7b031693f665f5494412ba4b551fe644ededccc0ab5922401079c95","impliedFormat":1},{"version":"80523c00b8544a2000ae0143e4a90a00b47f99823eb7926c1e03c494216fc363","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"746911b62b329587939560deb5c036aca48aece03147b021fa680223255d5183","affectsGlobalScope":true,"impliedFormat":1},{"version":"18fd40412d102c5564136f29735e5d1c3b455b8a37f920da79561f1fde068208","impliedFormat":1},{"version":"c8d3e5a18ba35629954e48c4cc8f11dc88224650067a172685c736b27a34a4dc","impliedFormat":1},{"version":"f0be1b8078cd549d91f37c30c222c2a187ac1cf981d994fb476a1adc61387b14","affectsGlobalScope":true,"impliedFormat":1},{"version":"0aaed1d72199b01234152f7a60046bc947f1f37d78d182e9ae09c4289e06a592","impliedFormat":1},{"version":"2b55d426ff2b9087485e52ac4bc7cfafe1dc420fc76dad926cd46526567c501a","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"5b7aa3c4c1a5d81b411e8cb302b45507fea9358d3569196b27eb1a27ae3a90ef","affectsGlobalScope":true,"impliedFormat":1},{"version":"5987a903da92c7462e0b35704ce7da94d7fdc4b89a984871c0e2b87a8aae9e69","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea08a0345023ade2b47fbff5a76d0d0ed8bff10bc9d22b83f40858a8e941501c","impliedFormat":1},{"version":"47613031a5a31510831304405af561b0ffaedb734437c595256bb61a90f9311b","impliedFormat":1},{"version":"ae062ce7d9510060c5d7e7952ae379224fb3f8f2dd74e88959878af2057c143b","impliedFormat":1},{"version":"8a1a0d0a4a06a8d278947fcb66bf684f117bf147f89b06e50662d79a53be3e9f","affectsGlobalScope":true,"impliedFormat":1},{"version":"358765d5ea8afd285d4fd1532e78b88273f18cb3f87403a9b16fef61ac9fdcfe","impliedFormat":1},{"version":"9f55299850d4f0921e79b6bf344b47c420ce0f507b9dcf593e532b09ea7eeea1","impliedFormat":1},{"version":"1ba59c8bbeed2cb75b239bb12041582fa3e8ef32f8d0bd0ec802e38442d3f317","impliedFormat":1}],"root":[[62,64]],"options":{"composite":true,"declaration":true,"declarationMap":true,"esModuleInterop":true,"exactOptionalPropertyTypes":true,"module":199,"noImplicitOverride":true,"noUncheckedIndexedAccess":true,"outDir":"./dist","rootDir":"./src","skipLibCheck":true,"sourceMap":true,"strict":true,"target":10,"verbatimModuleSyntax":true},"referencedMap":[[67,1],[65,2],[68,2],[120,3],[121,3],[122,4],[74,5],[123,6],[124,7],[125,8],[69,2],[72,9],[70,2],[71,2],[126,10],[127,11],[128,12],[129,13],[130,14],[131,15],[132,15],[133,16],[134,17],[135,18],[136,19],[75,2],[73,2],[137,20],[138,21],[139,22],[173,23],[140,24],[141,2],[142,25],[143,26],[144,27],[145,28],[146,29],[147,30],[148,31],[149,32],[150,33],[151,33],[152,34],[153,2],[154,35],[155,36],[157,37],[156,38],[158,39],[159,40],[160,41],[161,42],[162,43],[163,44],[164,45],[165,46],[166,47],[167,48],[168,49],[169,50],[170,51],[76,2],[77,2],[78,2],[117,52],[118,2],[119,2],[171,53],[172,54],[174,55],[66,2],[79,2],[60,2],[61,2],[12,2],[11,2],[2,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[20,2],[3,2],[21,2],[22,2],[4,2],[23,2],[27,2],[24,2],[25,2],[26,2],[28,2],[29,2],[30,2],[5,2],[31,2],[32,2],[33,2],[34,2],[6,2],[38,2],[35,2],[36,2],[37,2],[39,2],[7,2],[40,2],[45,2],[46,2],[41,2],[42,2],[43,2],[44,2],[8,2],[50,2],[47,2],[48,2],[49,2],[51,2],[9,2],[52,2],[53,2],[54,2],[56,2],[55,2],[57,2],[58,2],[10,2],[59,2],[1,2],[95,56],[105,57],[94,56],[115,58],[86,59],[85,60],[114,61],[108,62],[113,63],[88,64],[102,65],[87,66],[111,67],[83,68],[82,61],[112,69],[84,70],[89,71],[90,2],[93,71],[80,2],[116,72],[106,73],[97,74],[98,75],[100,76],[96,77],[99,78],[109,61],[91,79],[92,80],[101,81],[81,82],[104,73],[103,71],[107,2],[110,83],[63,84],[64,85],[62,2]],"latestChangedDtsFile":"./dist/index.d.ts","version":"5.9.3"} \ No newline at end of file diff --git a/packages/web/index.html b/packages/web/index.html new file mode 100644 index 0000000..b60b18f --- /dev/null +++ b/packages/web/index.html @@ -0,0 +1,13 @@ + + + + + + + Arboretum + + +
+ + + diff --git a/packages/web/package.json b/packages/web/package.json new file mode 100644 index 0000000..d8d7814 --- /dev/null +++ b/packages/web/package.json @@ -0,0 +1,29 @@ +{ + "name": "@arboretum/web", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@arboretum/shared": "*", + "@xterm/addon-fit": "^0.11.0", + "@xterm/addon-webgl": "^0.19.0", + "@xterm/xterm": "^6.0.0", + "pinia": "^3.0.4", + "vue": "^3.5.38", + "vue-i18n": "^11.4.5", + "vue-router": "^4.6.4" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.3.0", + "@vitejs/plugin-vue": "^6.0.7", + "tailwindcss": "^4.3.0", + "typescript": "^5.7.0", + "vite": "^8.0.16", + "vue-tsc": "^3.3.4" + } +} diff --git a/packages/web/src/App.vue b/packages/web/src/App.vue new file mode 100644 index 0000000..563e28e --- /dev/null +++ b/packages/web/src/App.vue @@ -0,0 +1,20 @@ + + + diff --git a/packages/web/src/components/LanguageSwitcher.vue b/packages/web/src/components/LanguageSwitcher.vue new file mode 100644 index 0000000..5f3b8df --- /dev/null +++ b/packages/web/src/components/LanguageSwitcher.vue @@ -0,0 +1,22 @@ + + + diff --git a/packages/web/src/components/TerminalView.vue b/packages/web/src/components/TerminalView.vue new file mode 100644 index 0000000..89e1ad3 --- /dev/null +++ b/packages/web/src/components/TerminalView.vue @@ -0,0 +1,118 @@ + + + diff --git a/packages/web/src/i18n/en.ts b/packages/web/src/i18n/en.ts new file mode 100644 index 0000000..762d124 --- /dev/null +++ b/packages/web/src/i18n/en.ts @@ -0,0 +1,49 @@ +export default { + common: { + appName: 'Arboretum', + loading: 'Loading…', + cancel: 'Cancel', + logout: 'Log out', + language: 'Language', + }, + login: { + title: 'Sign in with your access token', + tokenLabel: 'Access token', + tokenPlaceholder: 'arb_…', + submit: 'Sign in', + submitting: 'Signing in…', + invalidToken: 'Invalid token', + rateLimited: 'Too many attempts — please wait before retrying', + genericError: 'Sign-in failed ({status})', + }, + sessions: { + title: 'Sessions', + refresh: 'Refresh', + empty: 'No sessions yet — start one below.', + newSession: 'New session', + cwdLabel: 'Working directory', + cwdPlaceholder: '/absolute/path/to/project', + commandLabel: 'Command', + creating: 'Creating…', + open: 'Open', + observe: 'Observe', + kill: 'Kill', + confirmKill: 'Confirm kill', + live: 'live', + clients: 'no client | 1 client | {n} clients', + exitCode: 'exit code {code}', + statusLabel: { + starting: 'starting', + running: 'running', + exited: 'exited', + }, + }, + terminal: { + observer: 'observer (read-only)', + sessionEnded: 'Session ended', + back: 'Sessions', + }, + ws: { + reconnecting: 'Connection lost — reconnecting…', + }, +}; diff --git a/packages/web/src/i18n/fr.ts b/packages/web/src/i18n/fr.ts new file mode 100644 index 0000000..4c75c46 --- /dev/null +++ b/packages/web/src/i18n/fr.ts @@ -0,0 +1,53 @@ +import type en from './en'; + +const fr: typeof en = { + common: { + appName: 'Arboretum', + loading: 'Chargement…', + cancel: 'Annuler', + logout: 'Se déconnecter', + language: 'Langue', + }, + login: { + title: 'Connectez-vous avec votre jeton d’accès', + tokenLabel: 'Jeton d’accès', + tokenPlaceholder: 'arb_…', + submit: 'Se connecter', + submitting: 'Connexion…', + invalidToken: 'Jeton invalide', + rateLimited: 'Trop de tentatives — patientez avant de réessayer', + genericError: 'Échec de la connexion ({status})', + }, + sessions: { + title: 'Sessions', + refresh: 'Rafraîchir', + empty: 'Aucune session — démarrez-en une ci-dessous.', + newSession: 'Nouvelle session', + cwdLabel: 'Répertoire de travail', + cwdPlaceholder: '/chemin/absolu/du/projet', + commandLabel: 'Commande', + creating: 'Création…', + open: 'Ouvrir', + observe: 'Observer', + kill: 'Tuer', + confirmKill: 'Confirmer', + live: 'en cours', + clients: 'aucun client | 1 client | {n} clients', + exitCode: 'code de sortie {code}', + statusLabel: { + starting: 'démarrage', + running: 'en cours', + exited: 'terminée', + }, + }, + terminal: { + observer: 'observateur (lecture seule)', + sessionEnded: 'Session terminée', + back: 'Sessions', + }, + ws: { + reconnecting: 'Connexion perdue — reconnexion…', + }, +}; + +export default fr; diff --git a/packages/web/src/i18n/index.ts b/packages/web/src/i18n/index.ts new file mode 100644 index 0000000..6c1ac9f --- /dev/null +++ b/packages/web/src/i18n/index.ts @@ -0,0 +1,24 @@ +import { createI18n } from 'vue-i18n'; +import en from './en'; +import fr from './fr'; + +export type AppLocale = 'en' | 'fr'; + +const STORAGE_KEY = 'arboretum.locale'; + +function initialLocale(): AppLocale { + const saved = localStorage.getItem(STORAGE_KEY); + return saved === 'fr' || saved === 'en' ? saved : 'en'; +} + +export const i18n = createI18n({ + legacy: false, + locale: initialLocale(), + fallbackLocale: 'en', + messages: { en, fr }, +}); + +export function setLocale(locale: AppLocale): void { + i18n.global.locale.value = locale; + localStorage.setItem(STORAGE_KEY, locale); +} diff --git a/packages/web/src/lib/api.ts b/packages/web/src/lib/api.ts new file mode 100644 index 0000000..c8efc13 --- /dev/null +++ b/packages/web/src/lib/api.ts @@ -0,0 +1,38 @@ +// Mini client REST : JSON, cookies de session, erreurs API normalisées. +import type { ApiError as ApiErrorBody } from '@arboretum/shared'; + +export class ApiError extends Error { + constructor( + readonly status: number, + readonly code: string, + message: string, + ) { + super(message); + this.name = 'ApiError'; + } +} + +async function request(path: string, method: string, body?: unknown): Promise { + const init: RequestInit = { method, credentials: 'same-origin' }; + if (body !== undefined) { + init.headers = { 'content-type': 'application/json' }; + init.body = JSON.stringify(body); + } + const res = await fetch(path, init); + if (!res.ok) { + let parsed: ApiErrorBody | null = null; + try { + parsed = (await res.json()) as ApiErrorBody; + } catch { + // réponse non-JSON : on retombe sur le statusText + } + throw new ApiError(res.status, parsed?.error.code ?? 'UNKNOWN', parsed?.error.message ?? res.statusText); + } + return (await res.json()) as T; +} + +export const api = { + get: (path: string): Promise => request(path, 'GET'), + post: (path: string, body?: unknown): Promise => request(path, 'POST', body), + delete: (path: string): Promise => request(path, 'DELETE'), +}; diff --git a/packages/web/src/lib/ws-client.ts b/packages/web/src/lib/ws-client.ts new file mode 100644 index 0000000..ea90289 --- /dev/null +++ b/packages/web/src/lib/ws-client.ts @@ -0,0 +1,370 @@ +// Client WebSocket multiplexé — UNE seule connexion pour toute l'app. +// Handshake hello/hello_ok, reconnexion avec backoff (ré-attache des terminaux +// ouverts + re-sub), corrélation FIFO des 'attached', flow control par ACK +// (cumul d'octets traités PAR CANAL, remis à zéro à chaque RESYNC). +import { ref, type Ref } from 'vue'; +import { + BINARY_FRAME, + FLOW, + PROTOCOL_VERSION, + decodeBinaryFrame, + type ClientMessage, + type ServerMessage, +} from '@arboretum/shared'; + +export type WsStatus = 'idle' | 'connecting' | 'open' | 'reconnecting'; + +export type DetachReason = 'client' | 'session_exit' | 'replaced'; + +/** Interface minimale attendue côté terminal (implémentée par xterm). */ +export interface TerminalSink { + /** le callback est invoqué quand xterm a réellement traité le chunk (base du flow control) */ + write(data: Uint8Array, callback?: () => void): void; + /** reset complet avant le replay d'une frame RESYNC */ + reset(): void; + onDetached(reason: DetachReason): void; + onControlChanged(controlling: boolean): void; +} + +export type SessionEvent = Extract; + +export interface AttachOptions { + sessionId: string; + mode: 'interactive' | 'observer'; + cols: number; + rows: number; + sink: TerminalSink; +} + +const BACKOFF_MIN_MS = 500; +const BACKOFF_MAX_MS = 10_000; + +export class Attachment { + /** canal courant — réassigné à chaque reconnexion, -1 tant que non attaché */ + channel = -1; + controlling = false; + closed = false; + cols: number; + rows: number; + /** époque de resync : invalide les callbacks d'écritures encore en vol après un reset */ + epoch = 0; + /** octets de payload réellement traités par le terminal depuis le dernier resync */ + processedBytes = 0; + /** dernier cumul envoyé en ack (repart à 0 à chaque resync, comme côté serveur) */ + lastAckBytes = 0; + /** ACK traînant : garantit l'envoi du reliquat même quand le flux s'arrête (anti-deadlock) */ + trailingAckTimer: ReturnType | null = null; + /** résolution du premier 'attached' de ce canal (corrélation FIFO) */ + pending: { resolve: (a: Attachment) => void; reject: (err: Error) => void } | null = null; + + constructor( + private readonly client: WsClient, + readonly sessionId: string, + readonly mode: 'interactive' | 'observer', + readonly sink: TerminalSink, + cols: number, + rows: number, + ) { + this.cols = cols; + this.rows = rows; + } + + sendStdin(data: string): void { + if (this.closed || this.channel < 0) return; + this.client.sendControl({ type: 'stdin', channel: this.channel, data }); + } + + /** mémorise toujours la taille (réutilisée à la ré-attache) ; n'envoie que si controlling */ + resize(cols: number, rows: number): void { + this.cols = cols; + this.rows = rows; + if (this.closed || this.channel < 0 || !this.controlling) return; + this.client.sendControl({ type: 'resize', channel: this.channel, cols, rows }); + } + + detach(): void { + if (this.closed) return; + this.closed = true; + this.pending = null; + this.client.releaseAttachment(this); + } +} + +export class WsClient { + readonly status: Ref = ref('idle'); + + private socket: WebSocket | null = null; + private ready = false; + private stopped = true; + private backoffMs = BACKOFF_MIN_MS; + private reconnectTimer: ReturnType | null = null; + /** terminaux ouverts, ré-attachés à chaque reconnexion (ordre d'insertion = ordre FIFO) */ + private readonly attachments = new Set(); + private readonly byChannel = new Map(); + /** FIFO des attaches en attente : le serveur répond aux 'attach' dans l'ordre */ + private awaitingAttached: Attachment[] = []; + private readonly sessionListeners = new Set<(e: SessionEvent) => void>(); + + connect(): void { + this.stopped = false; + if (this.socket || this.reconnectTimer) return; + if (this.status.value === 'idle') this.status.value = 'connecting'; + this.openSocket(); + } + + disconnect(): void { + this.stopped = true; + this.clearReconnectTimer(); + const socket = this.socket; + this.socket = null; + this.ready = false; + this.status.value = 'idle'; + for (const att of this.attachments) { + att.closed = true; + att.pending?.reject(new Error('disconnected')); + att.pending = null; + att.sink.onDetached('client'); + } + this.attachments.clear(); + this.byChannel.clear(); + this.awaitingAttached = []; + socket?.close(); + } + + /** résout sur le prochain 'attached' corrélé ; rejette si la session est introuvable/terminée */ + attach(opts: AttachOptions): Promise { + const att = new Attachment(this, opts.sessionId, opts.mode, opts.sink, opts.cols, opts.rows); + const promise = new Promise((resolve, reject) => { + att.pending = { resolve, reject }; + }); + this.attachments.add(att); + this.connect(); + if (this.ready) this.sendAttach(att); + return promise; + } + + subscribeSessions(listener: (e: SessionEvent) => void): () => void { + this.sessionListeners.add(listener); + this.connect(); + if (this.sessionListeners.size === 1) this.sendControl({ type: 'sub', topics: ['sessions'] }); + return () => { + this.sessionListeners.delete(listener); + if (this.sessionListeners.size === 0) this.sendControl({ type: 'sub', topics: [] }); + }; + } + + sendControl(msg: ClientMessage): void { + if (!this.ready || this.socket?.readyState !== WebSocket.OPEN) return; + this.socket.send(JSON.stringify(msg)); + } + + /** détache côté serveur et oublie l'attache (appelé par Attachment.detach) */ + releaseAttachment(att: Attachment): void { + this.attachments.delete(att); + if (att.channel >= 0) { + this.byChannel.delete(att.channel); + this.sendControl({ type: 'detach', channel: att.channel }); + att.channel = -1; + } + // si l'attache attend encore son 'attached', elle reste dans la FIFO pour + // préserver la corrélation : closed=true la fera détacher à la réception + } + + // ---- interne ---- + + private openSocket(): void { + const socket = new WebSocket(`${location.origin.replace(/^http/, 'ws')}/ws`); + socket.binaryType = 'arraybuffer'; + this.socket = socket; + socket.onopen = () => { + const hello: ClientMessage = { type: 'hello', protocol: PROTOCOL_VERSION }; + socket.send(JSON.stringify(hello)); + }; + socket.onmessage = (ev: MessageEvent) => this.handleMessage(ev); + socket.onclose = () => this.handleClose(socket); + socket.onerror = () => { + // onclose suit systématiquement : la reconnexion se joue là-bas + }; + } + + private handleClose(socket: WebSocket): void { + if (this.socket !== socket) return; + this.socket = null; + this.ready = false; + this.byChannel.clear(); + this.awaitingAttached = []; + for (const att of this.attachments) { + att.channel = -1; + att.epoch += 1; + } + if (this.stopped) { + this.status.value = 'idle'; + return; + } + this.status.value = 'reconnecting'; + this.scheduleReconnect(); + } + + private scheduleReconnect(): void { + this.clearReconnectTimer(); + this.reconnectTimer = setTimeout(() => { + this.reconnectTimer = null; + if (!this.stopped) this.openSocket(); + }, this.backoffMs); + this.backoffMs = Math.min(this.backoffMs * 2, BACKOFF_MAX_MS); + } + + private clearReconnectTimer(): void { + if (this.reconnectTimer !== null) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + } + + private sendAttach(att: Attachment): void { + this.awaitingAttached.push(att); + this.sendControl({ type: 'attach', sessionId: att.sessionId, mode: att.mode, cols: att.cols, rows: att.rows }); + } + + private handleMessage(ev: MessageEvent): void { + if (ev.data instanceof ArrayBuffer) { + this.handleBinary(new Uint8Array(ev.data)); + return; + } + let msg: ServerMessage; + try { + msg = JSON.parse(String(ev.data)) as ServerMessage; + } catch { + return; + } + this.handleServerMessage(msg); + } + + private handleBinary(data: Uint8Array): void { + if (data.byteLength < BINARY_FRAME.HEADER_BYTES) return; + const frame = decodeBinaryFrame(data); + const att = this.byChannel.get(frame.channel); + if (!att || att.closed) return; + + if (frame.type === BINARY_FRAME.RESYNC) { + // le serveur repart de sentBytes=0 après un resync : reset terminal, replay + // du ring, et compteurs d'octets remis à zéro (le payload du replay ne compte pas) + att.epoch += 1; + att.processedBytes = 0; + att.lastAckBytes = 0; + att.sink.reset(); + if (frame.payload.byteLength > 0) att.sink.write(frame.payload); + return; + } + if (frame.type !== BINARY_FRAME.OUTPUT) return; + + const epoch = att.epoch; + const length = frame.payload.byteLength; + att.sink.write(frame.payload, () => { + // compté seulement si aucun resync/reconnexion n'est passé entre temps + if (att.closed || att.epoch !== epoch || att.channel < 0) return; + att.processedBytes += length; + if (att.processedBytes - att.lastAckBytes >= FLOW.ACK_EVERY_BYTES) { + att.lastAckBytes = att.processedBytes; + this.sendControl({ type: 'ack', channel: att.channel, bytes: att.processedBytes }); + } + // ACK traînant débouncé : si le flux s'arrête avec un reliquat non-ACKé, + // le serveur ne doit jamais rester en pause faute d'ACK suivant. + if (att.trailingAckTimer) clearTimeout(att.trailingAckTimer); + att.trailingAckTimer = setTimeout(() => { + att.trailingAckTimer = null; + if (att.closed || att.epoch !== epoch || att.channel < 0) return; + if (att.processedBytes > att.lastAckBytes) { + att.lastAckBytes = att.processedBytes; + this.sendControl({ type: 'ack', channel: att.channel, bytes: att.processedBytes }); + } + }, 200); + }); + } + + private handleServerMessage(msg: ServerMessage): void { + switch (msg.type) { + case 'hello_ok': { + this.ready = true; + this.backoffMs = BACKOFF_MIN_MS; + this.status.value = 'open'; + if (this.sessionListeners.size > 0) this.sendControl({ type: 'sub', topics: ['sessions'] }); + // ré-attache des terminaux encore ouverts (nouveaux canaux côté serveur) + for (const att of this.attachments) { + if (!att.closed) this.sendAttach(att); + } + return; + } + case 'attached': { + const att = this.awaitingAttached.shift(); + if (!att) return; + if (att.closed) { + // détaché pendant l'attente : on libère le canal côté serveur + this.sendControl({ type: 'detach', channel: msg.channel }); + return; + } + const wasControlling = att.controlling; + att.channel = msg.channel; + att.controlling = msg.controlling; + this.byChannel.set(msg.channel, att); + if (att.pending) { + att.pending.resolve(att); + att.pending = null; + } else if (wasControlling !== msg.controlling) { + // ré-attache après reconnexion : le statut controlling peut avoir changé + att.sink.onControlChanged(msg.controlling); + } + return; + } + case 'detached': { + const att = this.byChannel.get(msg.channel); + if (!att) return; + this.byChannel.delete(msg.channel); + att.channel = -1; + if (msg.reason === 'client') return; + att.closed = true; + this.attachments.delete(att); + att.sink.onDetached(msg.reason); + return; + } + case 'control_changed': { + const att = this.byChannel.get(msg.channel); + if (!att || att.closed) return; + att.controlling = msg.controlling; + att.sink.onControlChanged(msg.controlling); + return; + } + case 'session_update': + case 'session_exit': { + for (const cb of this.sessionListeners) cb(msg); + return; + } + case 'error': { + if (msg.channel !== undefined) { + console.warn(`[ws] channel ${msg.channel}: ${msg.code} — ${msg.message}`); + return; + } + // un échec d'attach est la seule erreur sans canal corrélable à une requête + if ((msg.code === 'NOT_FOUND' || msg.code === 'SESSION_EXITED') && this.awaitingAttached.length > 0) { + const att = this.awaitingAttached.shift(); + if (!att) return; + this.attachments.delete(att); + if (att.pending) { + att.pending.reject(new Error(msg.code)); + att.pending = null; + } else if (!att.closed) { + att.closed = true; + att.sink.onDetached('session_exit'); + } + return; + } + console.warn(`[ws] ${msg.code} — ${msg.message}`); + return; + } + case 'pong': + return; + } + } +} + +export const wsClient = new WsClient(); diff --git a/packages/web/src/main.ts b/packages/web/src/main.ts new file mode 100644 index 0000000..7664ab4 --- /dev/null +++ b/packages/web/src/main.ts @@ -0,0 +1,8 @@ +import { createApp } from 'vue'; +import { createPinia } from 'pinia'; +import App from './App.vue'; +import { router } from './router'; +import { i18n } from './i18n'; +import './style.css'; + +createApp(App).use(createPinia()).use(router).use(i18n).mount('#app'); diff --git a/packages/web/src/router/index.ts b/packages/web/src/router/index.ts new file mode 100644 index 0000000..7ddf64b --- /dev/null +++ b/packages/web/src/router/index.ts @@ -0,0 +1,20 @@ +import { createRouter, createWebHistory } from 'vue-router'; +import { useAuthStore } from '../stores/auth'; + +export const router = createRouter({ + history: createWebHistory(), + routes: [ + { path: '/login', name: 'login', component: () => import('../views/LoginView.vue') }, + { path: '/', name: 'sessions', component: () => import('../views/SessionsListView.vue') }, + { path: '/sessions/:id', name: 'session', component: () => import('../views/SessionView.vue') }, + { path: '/:pathMatch(.*)*', redirect: '/' }, + ], +}); + +// Garde globale : tout sauf /login exige une session valide (GET /api/v1/auth/me, 401 → /login) +router.beforeEach(async (to) => { + const auth = useAuthStore(); + if (auth.authenticated === null) await auth.check(); + if (to.name === 'login') return auth.authenticated ? { name: 'sessions' } : true; + return auth.authenticated ? true : { name: 'login', query: { redirect: to.fullPath } }; +}); diff --git a/packages/web/src/stores/auth.ts b/packages/web/src/stores/auth.ts new file mode 100644 index 0000000..bd898e3 --- /dev/null +++ b/packages/web/src/stores/auth.ts @@ -0,0 +1,42 @@ +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import type { LoginResponse, MeResponse } from '@arboretum/shared'; +import { api } from '../lib/api'; +import { wsClient } from '../lib/ws-client'; + +export const useAuthStore = defineStore('auth', () => { + /** null = pas encore vérifié auprès du serveur */ + const authenticated = ref(null); + const tokenLabel = ref(null); + const serverVersion = ref(null); + + async function check(): Promise { + try { + const me = await api.get('/api/v1/auth/me'); + tokenLabel.value = me.tokenLabel; + serverVersion.value = me.serverVersion; + authenticated.value = true; + } catch { + authenticated.value = false; + } + return authenticated.value === true; + } + + async function login(token: string): Promise { + const res = await api.post('/api/v1/auth/login', { token }); + tokenLabel.value = res.label; + authenticated.value = true; + } + + async function logout(): Promise { + try { + await api.post<{ ok: true }>('/api/v1/auth/logout'); + } finally { + wsClient.disconnect(); + authenticated.value = false; + tokenLabel.value = null; + } + } + + return { authenticated, tokenLabel, serverVersion, check, login, logout }; +}); diff --git a/packages/web/src/stores/sessions.ts b/packages/web/src/stores/sessions.ts new file mode 100644 index 0000000..98dea77 --- /dev/null +++ b/packages/web/src/stores/sessions.ts @@ -0,0 +1,76 @@ +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import type { CreateSessionRequest, SessionResponse, SessionSummary, SessionsListResponse } from '@arboretum/shared'; +import { api } from '../lib/api'; +import { wsClient, type SessionEvent } from '../lib/ws-client'; + +export const useSessionsStore = defineStore('sessions', () => { + const sessions = ref([]); + const loading = ref(false); + const loadError = ref(null); + let unsubscribe: (() => void) | null = null; + + // vivantes d'abord, puis par date de création décroissante (même ordre que le serveur) + function sorted(list: SessionSummary[]): SessionSummary[] { + return [...list].sort((a, b) => Number(b.live) - Number(a.live) || b.createdAt.localeCompare(a.createdAt)); + } + + function upsert(session: SessionSummary): void { + const idx = sessions.value.findIndex((s) => s.id === session.id); + if (idx >= 0) sessions.value.splice(idx, 1, session); + else sessions.value.push(session); + sessions.value = sorted(sessions.value); + } + + function onEvent(e: SessionEvent): void { + if (e.type === 'session_update') { + upsert(e.session); + return; + } + const current = sessions.value.find((s) => s.id === e.sessionId); + if (!current) return; + upsert({ + ...current, + status: 'exited', + live: false, + exitCode: e.exitCode, + endedAt: new Date().toISOString(), + clients: 0, + }); + } + + async function fetchSessions(): Promise { + loading.value = true; + loadError.value = null; + try { + const res = await api.get('/api/v1/sessions'); + sessions.value = sorted(res.sessions); + } catch (err) { + loadError.value = err instanceof Error ? err.message : String(err); + } finally { + loading.value = false; + } + } + + function startRealtime(): void { + unsubscribe ??= wsClient.subscribeSessions(onEvent); + } + + function stopRealtime(): void { + unsubscribe?.(); + unsubscribe = null; + } + + async function createSession(cwd: string, command: NonNullable): Promise { + const body: CreateSessionRequest = { cwd, command }; + const res = await api.post('/api/v1/sessions', body); + upsert(res.session); + return res.session; + } + + async function killSession(id: string): Promise { + await api.delete<{ ok: true }>(`/api/v1/sessions/${id}`); + } + + return { sessions, loading, loadError, fetchSessions, startRealtime, stopRealtime, createSession, killSession }; +}); diff --git a/packages/web/src/style.css b/packages/web/src/style.css new file mode 100644 index 0000000..1cafe1a --- /dev/null +++ b/packages/web/src/style.css @@ -0,0 +1,31 @@ +@import 'tailwindcss'; + +html, +body, +#app { + height: 100%; +} + +body { + background-color: var(--color-zinc-950); + color: var(--color-zinc-200); + -webkit-font-smoothing: antialiased; +} + +@layer components { + .input { + @apply w-full rounded-md border border-zinc-700 bg-zinc-950 px-2.5 py-1.5 text-sm text-zinc-100 outline-none transition-colors placeholder:text-zinc-600 focus:border-zinc-400; + } + .btn { + @apply inline-flex items-center justify-center gap-1.5 rounded-md border border-zinc-700 bg-zinc-800 px-3 py-1.5 text-sm font-medium text-zinc-200 transition-colors hover:bg-zinc-700 disabled:cursor-not-allowed disabled:opacity-50; + } + .btn-primary { + @apply inline-flex items-center justify-center gap-1.5 rounded-md bg-emerald-600 px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-emerald-500 disabled:cursor-not-allowed disabled:opacity-50; + } + .btn-danger { + @apply inline-flex items-center justify-center gap-1.5 rounded-md border border-red-900 bg-red-950 px-3 py-1.5 text-sm font-medium text-red-300 transition-colors hover:bg-red-900 disabled:cursor-not-allowed disabled:opacity-50; + } + .badge { + @apply inline-flex items-center rounded px-1.5 py-0.5 text-[11px] font-medium; + } +} diff --git a/packages/web/src/views/LoginView.vue b/packages/web/src/views/LoginView.vue new file mode 100644 index 0000000..c9bf9df --- /dev/null +++ b/packages/web/src/views/LoginView.vue @@ -0,0 +1,64 @@ + + + diff --git a/packages/web/src/views/SessionView.vue b/packages/web/src/views/SessionView.vue new file mode 100644 index 0000000..e5a1c95 --- /dev/null +++ b/packages/web/src/views/SessionView.vue @@ -0,0 +1,40 @@ + + + diff --git a/packages/web/src/views/SessionsListView.vue b/packages/web/src/views/SessionsListView.vue new file mode 100644 index 0000000..0ebf00f --- /dev/null +++ b/packages/web/src/views/SessionsListView.vue @@ -0,0 +1,151 @@ + + + diff --git a/packages/web/tsconfig.json b/packages/web/tsconfig.json new file mode 100644 index 0000000..eed9011 --- /dev/null +++ b/packages/web/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": false, + "noEmit": true, + "declaration": false, + "declarationMap": false, + "sourceMap": false, + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2023", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + "useDefineForClassFields": true + }, + "include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts"] +} diff --git a/packages/web/vite.config.ts b/packages/web/vite.config.ts new file mode 100644 index 0000000..89e7f0d --- /dev/null +++ b/packages/web/vite.config.ts @@ -0,0 +1,29 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import tailwindcss from '@tailwindcss/vite'; + +const DAEMON_HTTP = 'http://127.0.0.1:7317'; +const DAEMON_WS = 'ws://127.0.0.1:7317'; + +export default defineConfig({ + plugins: [vue(), tailwindcss()], + server: { + proxy: { + // L'Origin est réécrite vers celle du daemon : son check Origin strict + // rejetterait sinon l'origine du serveur de dev Vite. + '/api': { + target: DAEMON_HTTP, + configure: (proxy) => { + proxy.on('proxyReq', (proxyReq) => proxyReq.setHeader('origin', DAEMON_HTTP)); + }, + }, + '/ws': { + target: DAEMON_WS, + ws: true, + configure: (proxy) => { + proxy.on('proxyReqWs', (proxyReq) => proxyReq.setHeader('origin', DAEMON_HTTP)); + }, + }, + }, + }, +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..48d2fbb --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + include: ['packages/*/test/**/*.test.ts'], + testTimeout: 10_000, + }, +});