diff --git a/.codacy.yml b/.codacy.yml new file mode 100644 index 0000000..19ba328 --- /dev/null +++ b/.codacy.yml @@ -0,0 +1,3 @@ +--- +exclude_paths: + - 'src/**/*.test.ts' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4ec93d6..e3637f2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,10 +23,8 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Install jq - uses: dcarbone/install-jq-action@v2 - name: Setup pnpm - uses: pnpm/action-setup@v3 + uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 - name: Setup Node.js uses: actions/setup-node@v4 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 79f872c..0bcc942 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup pnpm - uses: pnpm/action-setup@v3 + uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 - uses: actions/setup-node@v4 with: node-version: 'lts/*' @@ -36,7 +36,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup pnpm - uses: pnpm/action-setup@v3 + uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 - name: Set up Node.js ${{matrix.node}} uses: actions/setup-node@v4 with: @@ -55,7 +55,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup pnpm - uses: pnpm/action-setup@v3 + uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 - name: Set up Node.js uses: actions/setup-node@v4 with: @@ -74,6 +74,6 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install Deno - uses: denoland/setup-deno@v1 + uses: denoland/setup-deno@4606d5cc6fb3f673efd4f594850e3f4b3e9d29cd - name: Type-check the dependencies run: deno check src/index.ts diff --git a/.gitignore b/.gitignore index 4e0e823..852f77d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ node_modules/ # macOS .DS_Store + +# environment variables +.env* diff --git a/README.md b/README.md index 1839f38..a996ae8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # google-safe-browsing +[![JSR](https://jsr.io/badges/@hckhanh/google-safe-browsing)](https://jsr.io/@hckhanh/google-safe-browsing) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/be4f5f8019a743f3878358399c110a36)](https://app.codacy.com/gh/hckhanh/google-safe-browsing/dashboard) A JavaScript client for [Google Safe Browsing](https://safebrowsing.google.com) [API](https://developers.google.com/safe-browsing) @@ -9,19 +10,21 @@ A JavaScript client for [Google Safe Browsing](https://safebrowsing.google.com) - [Zero dependencies](https://jsr.io/@hckhanh/google-safe-browsing/dependencies) - Built-in support for Edge runtime - Typesafe with TypeScript -- Supports all Google Safe Browsing API v4 endpoints +- Supports some common cases - Fully documented ## APIs -### findThreadMatches +### threatMatches.find Finds the threat entries that match the Safe Browsing lists. ```ts -import { findThreadMatches } from '@hckhanh/google-safe-browsing' +import { GoogleSafeBrowsing } from '@hckhanh/google-safe-browsing' -const result = await findThreadMatches('apiKey', { +const client = new GoogleSafeBrowsing('apiKey') + +const result = await client.findThreatMatches({ client: { clientId: 'uniqueClientId', clientVersion: '1.0.0', @@ -44,5 +47,9 @@ const hasRisk = result.matches !== undefined && result.matches.length > 0 You can go to the [Releases](https://github.com/hckhanh/google-safe-browsing/releases) page to see the release notes. > [!NOTE] -> The Safe Browsing API is for non-commercial use only. If you need to use APIs to detect malicious URLs for commercial -> purposes - meaning 'for sale or revenue-generating purposes' - please refer to the [Web Risk API](https://github.com/hckhanh/google-web-risk). +> Enables client applications to check web resources (most commonly URLs) +> against Google-generated lists of unsafe web resources. +> The Safe Browsing APIs are for non-commercial use only. +> If you need to use APIs to detect malicious URLs for commercial purposes – +> meaning “for sale or revenue-generating purposes” – +> please refer to the [Web Risk API](https://github.com/hckhanh/google-web-risk). diff --git a/jsr.json b/jsr.json index 95c378a..8c867ab 100644 --- a/jsr.json +++ b/jsr.json @@ -3,6 +3,8 @@ "name": "@hckhanh/google-safe-browsing", "version": "0.0.0-development", "exports": "./src/index.ts", - "include": ["src/**", "README.md", "jsr.json", "LICENSE"], - "exclude": ["src/**/*.test.ts"] + "publish": { + "include": ["src/**", "README.md", "jsr.json", "LICENSE"], + "exclude": ["src/**/*.test.ts"] + } } diff --git a/package.json b/package.json index 57e467f..9feda8a 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,13 @@ "sideEffects": false, "scripts": { "test": "vitest related src/*.ts", - "test:prettier": "prettier --check ." + "test:prettier": "prettier --check .", + "test:types": "deno check src/index.ts" }, "author": { "name": "Khánh Hoàng", - "email": "hi@khanh.sh", - "url": "https://khanh.sh" + "email": "hi@khanh.id", + "url": "https://khanh.id" }, "license": "MIT", "keywords": [ @@ -24,11 +25,11 @@ "url" ], "devDependencies": { - "@edge-runtime/vm": "3.2.0", - "@types/node": "22.10.0", - "prettier": "3.2.5", - "typescript": "5.4.3", - "vitest": "1.3.1" + "@edge-runtime/vm": "4.0.4", + "@types/node": "22.10.1", + "prettier": "3.4.1", + "typescript": "5.7.2", + "vitest": "2.1.6" }, - "packageManager": "pnpm@9.14.3" + "packageManager": "pnpm@9.14.4" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3f9a34..8cebc55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,29 +9,29 @@ importers: .: devDependencies: '@edge-runtime/vm': - specifier: 3.2.0 - version: 3.2.0 + specifier: 4.0.4 + version: 4.0.4 '@types/node': - specifier: 22.10.0 - version: 22.10.0 + specifier: 22.10.1 + version: 22.10.1 prettier: - specifier: 3.2.5 - version: 3.2.5 + specifier: 3.4.1 + version: 3.4.1 typescript: - specifier: 5.4.3 - version: 5.4.3 + specifier: 5.7.2 + version: 5.7.2 vitest: - specifier: 1.3.1 - version: 1.3.1(@edge-runtime/vm@3.2.0)(@types/node@22.10.0) + specifier: 2.1.6 + version: 2.1.6(@edge-runtime/vm@4.0.4)(@types/node@22.10.1) packages: - '@edge-runtime/primitives@4.1.0': - resolution: {integrity: sha512-Vw0lbJ2lvRUqc7/soqygUX216Xb8T3WBZ987oywz6aJqRxcwSVWwr9e+Nqo2m9bxobA9mdbWNNoRY6S9eko1EQ==} + '@edge-runtime/primitives@5.1.1': + resolution: {integrity: sha512-osrHE4ObQ3XFkvd1sGBLkheV2mcHUqJI/Bum2AWA0R3U78h9lif3xZAdl6eLD/XnW4xhsdwjPUejLusXbjvI4Q==} engines: {node: '>=16'} - '@edge-runtime/vm@3.2.0': - resolution: {integrity: sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==} + '@edge-runtime/vm@4.0.4': + resolution: {integrity: sha512-LqPw+yaSPpCNnVZl5XoHQAySEzlnZiC9gReUuQHMh9GI03KKqwpVqWkIK1UfK116Yww7f2WZuAgnY/nhHwTsJA==} engines: {node: '>=16'} '@esbuild/aix-ppc64@0.19.12': @@ -172,12 +172,8 @@ packages: cpu: [x64] os: [win32] - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jridgewell/sourcemap-codec@1.4.15': - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} '@rollup/rollup-android-arm-eabi@4.12.0': resolution: {integrity: sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==} @@ -244,63 +240,59 @@ packages: cpu: [x64] os: [win32] - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - '@types/node@22.10.0': - resolution: {integrity: sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==} + '@types/node@22.10.1': + resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} - '@vitest/expect@1.3.1': - resolution: {integrity: sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==} + '@vitest/expect@2.1.6': + resolution: {integrity: sha512-9M1UR9CAmrhJOMoSwVnPh2rELPKhYo0m/CSgqw9PyStpxtkwhmdM6XYlXGKeYyERY1N6EIuzkQ7e3Lm1WKCoUg==} - '@vitest/runner@1.3.1': - resolution: {integrity: sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==} - - '@vitest/snapshot@1.3.1': - resolution: {integrity: sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==} + '@vitest/mocker@2.1.6': + resolution: {integrity: sha512-MHZp2Z+Q/A3am5oD4WSH04f9B0T7UvwEb+v5W0kCYMhtXGYbdyl2NUk1wdSMqGthmhpiThPDp/hEoVwu16+u1A==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true - '@vitest/spy@1.3.1': - resolution: {integrity: sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==} + '@vitest/pretty-format@2.1.6': + resolution: {integrity: sha512-exZyLcEnHgDMKc54TtHca4McV4sKT+NKAe9ix/yhd/qkYb/TP8HTyXRFDijV19qKqTZM0hPL4753zU/U8L/gAA==} - '@vitest/utils@1.3.1': - resolution: {integrity: sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==} + '@vitest/runner@2.1.6': + resolution: {integrity: sha512-SjkRGSFyrA82m5nz7To4CkRSEVWn/rwQISHoia/DB8c6IHIhaE/UNAo+7UfeaeJRE979XceGl00LNkIz09RFsA==} - acorn-walk@8.3.2: - resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} - engines: {node: '>=0.4.0'} + '@vitest/snapshot@2.1.6': + resolution: {integrity: sha512-5JTWHw8iS9l3v4/VSuthCndw1lN/hpPB+mlgn1BUhFbobeIUj1J1V/Bj2t2ovGEmkXLTckFjQddsxS5T6LuVWw==} - acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} - engines: {node: '>=0.4.0'} - hasBin: true + '@vitest/spy@2.1.6': + resolution: {integrity: sha512-oTFObV8bd4SDdRka5O+mSh5w9irgx5IetrD5i+OsUUsk/shsBoHifwCzy45SAORzAhtNiprUVaK3hSCCzZh1jQ==} - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} + '@vitest/utils@2.1.6': + resolution: {integrity: sha512-ixNkFy3k4vokOUTU2blIUvOgKq/N2PW8vKIjZZYsGJCMX69MRa9J2sKqX5hY/k5O5Gty3YJChepkqZ3KM9LyIQ==} - assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - chai@4.4.1: - resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} - engines: {node: '>=4'} - - check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} - debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -308,13 +300,12 @@ packages: supports-color: optional: true - deep-eql@4.1.3: - resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + es-module-lexer@1.5.4: + resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} esbuild@0.19.12: resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} @@ -324,136 +315,56 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + magic-string@0.30.14: + resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==} - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - js-tokens@8.0.3: - resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==} - - jsonc-parser@3.2.1: - resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} - - local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} - engines: {node: '>=14'} - - loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} - - magic-string@0.30.7: - resolution: {integrity: sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==} - engines: {node: '>=12'} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - - mlly@1.6.1: - resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==} - - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - - p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - pkg-types@1.0.3: - resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} - postcss@8.4.35: resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==} engines: {node: ^10 || ^12 || >=14} - prettier@3.2.5: - resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + prettier@3.4.1: + resolution: {integrity: sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==} engines: {node: '>=14'} hasBin: true - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - rollup@4.12.0: resolution: {integrity: sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} @@ -461,45 +372,38 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - strip-literal@2.0.0: - resolution: {integrity: sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==} + tinyexec@0.3.1: + resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} - tinybench@2.6.0: - resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==} + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} - tinypool@0.8.2: - resolution: {integrity: sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==} + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} - tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - - typescript@5.4.3: - resolution: {integrity: sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==} + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} engines: {node: '>=14.17'} hasBin: true - ufo@1.4.0: - resolution: {integrity: sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==} - undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - vite-node@1.3.1: - resolution: {integrity: sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==} - engines: {node: ^18.0.0 || >=20.0.0} + vite-node@2.1.6: + resolution: {integrity: sha512-DBfJY0n9JUwnyLxPSSUmEePT21j8JZp/sR9n+/gBwQU6DcQOioPdb8/pibWfXForbirSagZCilseYIwaL3f95A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true vite@5.1.4: @@ -530,15 +434,15 @@ packages: terser: optional: true - vitest@1.3.1: - resolution: {integrity: sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==} - engines: {node: ^18.0.0 || >=20.0.0} + vitest@2.1.6: + resolution: {integrity: sha512-isUCkvPL30J4c5O5hgONeFRsDmlw6kzFEdLQHLezmDdKQHy8Ke/B/dgdTMEgU0vm+iZ0TjW8GuK83DiahBoKWQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.3.1 - '@vitest/ui': 1.3.1 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 2.1.6 + '@vitest/ui': 2.1.6 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -555,27 +459,18 @@ packages: jsdom: optional: true - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - why-is-node-running@2.2.2: - resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true - yocto-queue@1.0.0: - resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} - engines: {node: '>=12.20'} - snapshots: - '@edge-runtime/primitives@4.1.0': {} + '@edge-runtime/primitives@5.1.1': {} - '@edge-runtime/vm@3.2.0': + '@edge-runtime/vm@4.0.4': dependencies: - '@edge-runtime/primitives': 4.1.0 + '@edge-runtime/primitives': 5.1.1 '@esbuild/aix-ppc64@0.19.12': optional: true @@ -646,11 +541,7 @@ snapshots: '@esbuild/win32-x64@0.19.12': optional: true - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.8 - - '@jridgewell/sourcemap-codec@1.4.15': {} + '@jridgewell/sourcemap-codec@1.5.0': {} '@rollup/rollup-android-arm-eabi@4.12.0': optional: true @@ -691,82 +582,73 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.12.0': optional: true - '@sinclair/typebox@0.27.8': {} - '@types/estree@1.0.5': {} - '@types/node@22.10.0': + '@types/node@22.10.1': dependencies: undici-types: 6.20.0 - '@vitest/expect@1.3.1': + '@vitest/expect@2.1.6': dependencies: - '@vitest/spy': 1.3.1 - '@vitest/utils': 1.3.1 - chai: 4.4.1 + '@vitest/spy': 2.1.6 + '@vitest/utils': 2.1.6 + chai: 5.1.2 + tinyrainbow: 1.2.0 - '@vitest/runner@1.3.1': + '@vitest/mocker@2.1.6(vite@5.1.4(@types/node@22.10.1))': dependencies: - '@vitest/utils': 1.3.1 - p-limit: 5.0.0 - pathe: 1.1.2 + '@vitest/spy': 2.1.6 + estree-walker: 3.0.3 + magic-string: 0.30.14 + optionalDependencies: + vite: 5.1.4(@types/node@22.10.1) - '@vitest/snapshot@1.3.1': + '@vitest/pretty-format@2.1.6': dependencies: - magic-string: 0.30.7 - pathe: 1.1.2 - pretty-format: 29.7.0 + tinyrainbow: 1.2.0 - '@vitest/spy@1.3.1': + '@vitest/runner@2.1.6': dependencies: - tinyspy: 2.2.1 + '@vitest/utils': 2.1.6 + pathe: 1.1.2 - '@vitest/utils@1.3.1': + '@vitest/snapshot@2.1.6': dependencies: - diff-sequences: 29.6.3 - estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 - - acorn-walk@8.3.2: {} + '@vitest/pretty-format': 2.1.6 + magic-string: 0.30.14 + pathe: 1.1.2 - acorn@8.11.3: {} + '@vitest/spy@2.1.6': + dependencies: + tinyspy: 3.0.2 - ansi-styles@5.2.0: {} + '@vitest/utils@2.1.6': + dependencies: + '@vitest/pretty-format': 2.1.6 + loupe: 3.1.2 + tinyrainbow: 1.2.0 - assertion-error@1.1.0: {} + assertion-error@2.0.1: {} cac@6.7.14: {} - chai@4.4.1: - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.3 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.0.8 - - check-error@1.0.3: + chai@5.1.2: dependencies: - get-func-name: 2.0.2 + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 - cross-spawn@7.0.3: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 + check-error@2.1.1: {} - debug@4.3.4: + debug@4.3.7: dependencies: - ms: 2.1.2 + ms: 2.1.3 - deep-eql@4.1.3: - dependencies: - type-detect: 4.0.8 + deep-eql@5.0.2: {} - diff-sequences@29.6.3: {} + es-module-lexer@1.5.4: {} esbuild@0.19.12: optionalDependencies: @@ -798,106 +680,34 @@ snapshots: dependencies: '@types/estree': 1.0.5 - execa@8.0.1: - dependencies: - cross-spawn: 7.0.3 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 + expect-type@1.1.0: {} fsevents@2.3.3: optional: true - get-func-name@2.0.2: {} - - get-stream@8.0.1: {} - - human-signals@5.0.0: {} - - is-stream@3.0.0: {} - - isexe@2.0.0: {} - - js-tokens@8.0.3: {} - - jsonc-parser@3.2.1: {} - - local-pkg@0.5.0: - dependencies: - mlly: 1.6.1 - pkg-types: 1.0.3 - - loupe@2.3.7: - dependencies: - get-func-name: 2.0.2 - - magic-string@0.30.7: - dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - - merge-stream@2.0.0: {} - - mimic-fn@4.0.0: {} + loupe@3.1.2: {} - mlly@1.6.1: + magic-string@0.30.14: dependencies: - acorn: 8.11.3 - pathe: 1.1.2 - pkg-types: 1.0.3 - ufo: 1.4.0 + '@jridgewell/sourcemap-codec': 1.5.0 - ms@2.1.2: {} + ms@2.1.3: {} nanoid@3.3.7: {} - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 - - p-limit@5.0.0: - dependencies: - yocto-queue: 1.0.0 - - path-key@3.1.1: {} - - path-key@4.0.0: {} - pathe@1.1.2: {} - pathval@1.1.1: {} + pathval@2.0.0: {} picocolors@1.0.0: {} - pkg-types@1.0.3: - dependencies: - jsonc-parser: 3.2.1 - mlly: 1.6.1 - pathe: 1.1.2 - postcss@8.4.35: dependencies: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.0.2 - prettier@3.2.5: {} - - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.2.0 - - react-is@18.2.0: {} + prettier@3.4.1: {} rollup@4.12.0: dependencies: @@ -918,49 +728,35 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.12.0 fsevents: 2.3.3 - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - siginfo@2.0.0: {} - signal-exit@4.1.0: {} - source-map-js@1.0.2: {} stackback@0.0.2: {} - std-env@3.7.0: {} - - strip-final-newline@3.0.0: {} - - strip-literal@2.0.0: - dependencies: - js-tokens: 8.0.3 + std-env@3.8.0: {} - tinybench@2.6.0: {} + tinybench@2.9.0: {} - tinypool@0.8.2: {} + tinyexec@0.3.1: {} - tinyspy@2.2.1: {} + tinypool@1.0.2: {} - type-detect@4.0.8: {} + tinyrainbow@1.2.0: {} - typescript@5.4.3: {} + tinyspy@3.0.2: {} - ufo@1.4.0: {} + typescript@5.7.2: {} undici-types@6.20.0: {} - vite-node@1.3.1(@types/node@22.10.0): + vite-node@2.1.6(@types/node@22.10.1): dependencies: cac: 6.7.14 - debug: 4.3.4 + debug: 4.3.7 + es-module-lexer: 1.5.4 pathe: 1.1.2 - picocolors: 1.0.0 - vite: 5.1.4(@types/node@22.10.0) + vite: 5.1.4(@types/node@22.10.1) transitivePeerDependencies: - '@types/node' - less @@ -971,56 +767,51 @@ snapshots: - supports-color - terser - vite@5.1.4(@types/node@22.10.0): + vite@5.1.4(@types/node@22.10.1): dependencies: esbuild: 0.19.12 postcss: 8.4.35 rollup: 4.12.0 optionalDependencies: - '@types/node': 22.10.0 + '@types/node': 22.10.1 fsevents: 2.3.3 - vitest@1.3.1(@edge-runtime/vm@3.2.0)(@types/node@22.10.0): - dependencies: - '@vitest/expect': 1.3.1 - '@vitest/runner': 1.3.1 - '@vitest/snapshot': 1.3.1 - '@vitest/spy': 1.3.1 - '@vitest/utils': 1.3.1 - acorn-walk: 8.3.2 - chai: 4.4.1 - debug: 4.3.4 - execa: 8.0.1 - local-pkg: 0.5.0 - magic-string: 0.30.7 + vitest@2.1.6(@edge-runtime/vm@4.0.4)(@types/node@22.10.1): + dependencies: + '@vitest/expect': 2.1.6 + '@vitest/mocker': 2.1.6(vite@5.1.4(@types/node@22.10.1)) + '@vitest/pretty-format': 2.1.6 + '@vitest/runner': 2.1.6 + '@vitest/snapshot': 2.1.6 + '@vitest/spy': 2.1.6 + '@vitest/utils': 2.1.6 + chai: 5.1.2 + debug: 4.3.7 + expect-type: 1.1.0 + magic-string: 0.30.14 pathe: 1.1.2 - picocolors: 1.0.0 - std-env: 3.7.0 - strip-literal: 2.0.0 - tinybench: 2.6.0 - tinypool: 0.8.2 - vite: 5.1.4(@types/node@22.10.0) - vite-node: 1.3.1(@types/node@22.10.0) - why-is-node-running: 2.2.2 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.1 + tinypool: 1.0.2 + tinyrainbow: 1.2.0 + vite: 5.1.4(@types/node@22.10.1) + vite-node: 2.1.6(@types/node@22.10.1) + why-is-node-running: 2.3.0 optionalDependencies: - '@edge-runtime/vm': 3.2.0 - '@types/node': 22.10.0 + '@edge-runtime/vm': 4.0.4 + '@types/node': 22.10.1 transitivePeerDependencies: - less - lightningcss + - msw - sass - stylus - sugarss - supports-color - terser - which@2.0.2: - dependencies: - isexe: 2.0.0 - - why-is-node-running@2.2.2: + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - - yocto-queue@1.0.0: {} diff --git a/src/lookup.test.ts b/src/GoogleSafeBrowsing.test.ts similarity index 67% rename from src/lookup.test.ts rename to src/GoogleSafeBrowsing.test.ts index f479d6c..52d895e 100644 --- a/src/lookup.test.ts +++ b/src/GoogleSafeBrowsing.test.ts @@ -1,15 +1,17 @@ import { describe, expect, it } from 'vitest' -import { findThreadMatches } from './lookup.ts' +import { GoogleSafeBrowsing } from './GoogleSafeBrowsing.js' -const apiKey = process.env.GOOGLE_SAFE_BROWSING_API_KEY as string +const client = new GoogleSafeBrowsing( + process.env.GOOGLE_SAFE_BROWSING_API_KEY as string, +) -describe('lookup', () => { - it('should work detect malicious link', () => { +describe('GoogleSafeBrowsing', () => { + it('should detect malicious link', async () => { expect( - findThreadMatches(apiKey, { + client.findThreatMatches({ client: { - clientId: 'khanh.sh', - clientVersion: '1.0.0', + clientId: 'khanh.id', + clientVersion: '2.0.0', }, threatInfo: { threatTypes: ['MALWARE', 'SOCIAL_ENGINEERING'], diff --git a/src/GoogleSafeBrowsing.ts b/src/GoogleSafeBrowsing.ts new file mode 100644 index 0000000..134890a --- /dev/null +++ b/src/GoogleSafeBrowsing.ts @@ -0,0 +1,126 @@ +import type { ClientInfo, ThreatInfo, ThreatMatch } from './types.ts' + +/** + * A [service endpoint]{@link https://cloud.google.com/apis/design/glossary#api_service_endpoint} + * is a base URL that specifies the network address of an API service. + * One service might have multiple service endpoints. + * This service has the following service endpoint, and all URIs below are relative to this service endpoint. + * + * @version 4 + */ +const DEFAULT_ENDPOINT = 'https://safebrowsing.googleapis.com/v4' + +/** + * The GoogleSafeBrowsing class provides methods to interact with the Google Safe Browsing API. + * It allows users to query the API to check URLs for potential threats such as malware or social engineering. + * + * @version 4 + */ +export class GoogleSafeBrowsing { + /** + * A string representing the API key required to authenticate and authorize requests + * to Google Safe Browsing API. + */ + private readonly apiKey: string + + /** + * Represents the endpoint URL of a network request or API call. + */ + private readonly endpoint: string + + /** + * Constructs an instance of the Class with the specified API key and optional endpoint. + * + * @param apiKey The API key used for authentication; must be a non-empty string. + * @param [endpoint=DEFAULT_ENDPOINT] The optional endpoint URL. + * @throws {Error} If the `apiKey` is an empty string or only contains whitespace. + */ + constructor(apiKey: string, endpoint: string = DEFAULT_ENDPOINT) { + if (!apiKey.trim()) { + throw new Error('API key is required') + } + + this.apiKey = apiKey + this.endpoint = endpoint + } + + /** + * Finds threat matches using Google Safe Browsing API. + * + * @example + * ```ts + * const client = new GoogleSafeBrowsing('apiKey') + * const result = await client.findThreatMatches({ + * client: { + * clientId: 'uniqueClientId', + * clientVersion: '1.0.0', + * }, + * threatInfo: { + * threatTypes: ['MALWARE', 'SOCIAL_ENGINEERING'], + * platformTypes: ['ALL_PLATFORMS'], + * threatEntryTypes: ['URL'], + * threatEntries: [ + * { url: 'http://malware.testing.google.test/testing/malware/' }, + * ], + * }, + * }) + * + * const hasRisk = result.matches !== undefined && result.matches.length > 0 + * ``` + * + * @param request The request object containing the parameters for finding threat matches. + * + * @return A promise that resolves to the response object containing the list of {@link ThreatMatch}. + */ + async findThreatMatches( + request: FindThreatMatchesRequest, + ): Promise { + const res = await fetch( + `${this.endpoint}/threatMatches:find?key=${this.apiKey}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept-Encoding': 'gzip, br', + }, + body: JSON.stringify(request), + }, + ) + + if (!res.ok) { + if (res.status === 429) { + throw new Error('Rate limit exceeded for Google Safe Browsing API') + } + + throw new Error(`API request failed with status ${res.status}`) + } + + return res.json() as Promise + } +} + +/** + * Represents a request to find threat matches. + * This type is used to encapsulate the necessary information + * for searching and identifying threats in specified lists and entries. + */ +export interface FindThreatMatchesRequest { + /** + * The client metadata. + */ + client: ClientInfo + /** + * The lists and entries to be checked for matches. + */ + threatInfo: ThreatInfo +} + +/** + * Represents the response from a find threat matches operation. + */ +export interface FindThreatMatchesResponse { + /** + * The threat list matches. If there is no threat, this field is omitted. + */ + matches?: ThreatMatch[] +} diff --git a/src/configs.ts b/src/configs.ts deleted file mode 100644 index b5b8677..0000000 --- a/src/configs.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * The base URL of the Google Safe Browsing API. The current version is v4. - */ -export const endpoint = 'https://safebrowsing.googleapis.com/v4' diff --git a/src/index.ts b/src/index.ts index 86d8f81..f9afe77 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ -export * from './lookup.ts' +export * from './GoogleSafeBrowsing.ts' export * from './types.ts' diff --git a/src/lookup.ts b/src/lookup.ts deleted file mode 100644 index 0479a99..0000000 --- a/src/lookup.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { ClientInfo, ThreatInfo, ThreatMatch } from './types.ts' -import { endpoint } from './configs.ts' - -/** - * The request object containing the parameters for finding thread matches. - */ -export type FindThreadMatchesRequest = { - /** - * The client metadata. - */ - client: ClientInfo - /** - * The lists and entries to be checked for matches. - */ - threatInfo: ThreatInfo -} - -/** - * The threat list matches. This may be empty if the client's threat list is empty. - */ -export type FindThreadMatchesResponse = { - /** - * The threat list matches. If there is no threat, this field is omitted. - */ - matches?: ThreatMatch[] -} - -/** - * Finds threat matches using Google Safe Browsing API. - * - * @example - * ```ts - * const result = await findThreadMatches('apiKey', { - * client: { - * clientId: 'uniqueClientId', - * clientVersion: '1.0.0', - * }, - * threatInfo: { - * threatTypes: ['MALWARE', 'SOCIAL_ENGINEERING'], - * platformTypes: ['ALL_PLATFORMS'], - * threatEntryTypes: ['URL'], - * threatEntries: [ - * { url: 'http://malware.testing.google.test/testing/malware/' }, - * ], - * }, - * }) - * - * const hasRisk = result.matches !== undefined && result.matches.length > 0 - * ``` - * - * @param apiKey The API key for accessing the Google Safe Browsing API. You should follow the instruction to get the API key at https://developers.google.com/safe-browsing/v4/get-started. - * @param request The request object containing the parameters for finding thread matches. - * - * @return A promise that resolves to the response object containing the list of {@link ThreatMatch}. - */ -export async function findThreadMatches( - apiKey: string, - request: FindThreadMatchesRequest, -): Promise { - const res = await fetch(`${endpoint}/threatMatches:find?key=${apiKey}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(request), - }) - - return res.json() -} diff --git a/src/types.ts b/src/types.ts index 0417a8b..f808619 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,13 @@ /** * Types of threats. * - * @see https://developers.google.com/safe-browsing/v4/reference/rest/v4/ThreatType + * * **THREAT_TYPE_UNSPECIFIED**: Unknown. + * * **MALWARE**: Malware threat type. + * * **SOCIAL_ENGINEERING**: Social engineering threat type. + * * **UNWANTED_SOFTWARE**: Unwanted software threat type. + * * **POTENTIALLY_HARMFUL_APPLICATION**: Potentially harmful application threat type. + * + * @see https://developers.google.com/safe-browsing/reference/rest/v4/ThreatType */ export type ThreatType = | 'THREAT_TYPE_UNSPECIFIED' @@ -13,7 +19,17 @@ export type ThreatType = /** * Types of platforms. * - * @see [PlatformType]{@link https://developers.google.com/safe-browsing/v4/reference/rest/v4/PlatformType} + * * **PLATFORM_TYPE_UNSPECIFIED**: Unknown platform. + * * **WINDOWS**: Threat posed to Windows. + * * **LINUX**: Threat posed to Linux. + * * **ANDROID**: Threat posed to Android. + * * **OSX**: Threat posed to OS X. + * * **IOS**: Threat posed to iOS. + * * **ANY_PLATFORM**: Threat posed to at least one of the defined platforms. + * * **ALL_PLATFORMS**: Threat posed to all defined platforms. + * * **CHROME**: Threat posed to Chrome. + * + * @see https://developers.google.com/safe-browsing/reference/rest/v4/PlatformType */ export type PlatformType = | 'PLATFORM_TYPE_UNSPECIFIED' @@ -27,9 +43,14 @@ export type PlatformType = | 'CHROME' /** - * Types of entries that pose threats. Threat lists are collections of entries of a single type. + * Types of entries that pose threats. + * Threat lists are collections of all types. + * + * * **THREAT_ENTRY_TYPE_UNSPECIFIED**: Unspecified. + * * **URL**: A URL. + * * **EXECUTABLE**: An executable program. * - * @see [ThreatEntryType]{@link https://developers.google.com/safe-browsing/v4/reference/rest/v4/ThreatEntryType} + * @see https://developers.google.com/safe-browsing/reference/rest/v4/ThreatEntryType */ export type ThreatEntryType = | 'THREAT_ENTRY_TYPE_UNSPECIFIED' @@ -39,7 +60,7 @@ export type ThreatEntryType = /** * An individual threat; for example, a malicious URL or its hash representation. Only one of these fields should be set. * - * @see [ThreatEntry]{@link https://developers.google.com/safe-browsing/v4/reference/rest/v4/ThreatEntry} + * @see https://developers.google.com/safe-browsing/reference/rest/v4/ThreatEntry */ export type ThreatEntry = | { @@ -57,8 +78,11 @@ export type ThreatEntry = } | { /** - * The digest of an executable in SHA256 format. The API supports both binary and hex digests. - * For JSON requests, digests are base64-encoded string. + * The digest of an executable in SHA256 format. + * The API supports both binary and hex digests. + * For JSON requests, digests are base64-encoded. + * + * A base64-encoded string. */ digest: string } @@ -66,9 +90,9 @@ export type ThreatEntry = /** * The client metadata associated with Safe Browsing API requests. * - * @see [ClientInfo]{@link https://developers.google.com/safe-browsing/v4/reference/rest/v4/ClientInfo} + * @see https://developers.google.com/safe-browsing/reference/rest/v4/ClientInfo */ -export type ClientInfo = { +export interface ClientInfo { /** * A client ID that (hopefully) uniquely identifies the client implementation of the Safe Browsing API. */ @@ -82,9 +106,9 @@ export type ClientInfo = { /** * The information regarding one or more threats that a client submits when checking for matches in threat lists. * - * @see [ThreatInfo]{@link https://developers.google.com/safe-browsing/v4/reference/rest/v4/ThreatInfo} + * @see https://developers.google.com/safe-browsing/reference/rest/v4/ThreatInfo */ -export type ThreatInfo = { +export interface ThreatInfo { /** * The threat types to be checked. */ @@ -106,26 +130,32 @@ export type ThreatInfo = { /** * A single metadata entry. * - * @see [MetadataEntry]{@link https://developers.google.com/safe-browsing/v4/reference/rest/v4/ThreatMatch#metadataentry} + * @see https://developers.google.com/safe-browsing/v4/reference/rest/v4/ThreatMatch#metadataentry */ -export type MetadataEntry = { +export interface MetadataEntry { /** - * The metadata entry key. For JSON requests, the key is base64-encoded string. + * The metadata entry key. + * For JSON requests, the key is base64-encoded. + * + * A base64-encoded string. */ key: string /** - * The metadata entry value. For JSON requests, the value is base64-encoded string. + * The metadata entry value. + * For JSON requests, the value is base64-encoded. + * + * A base64-encoded string. */ value: string } /** - * The metadata associated with a specific threat entry. The client is expected - * to know the metadata key/value pairs associated with each threat type. + * The metadata associated with a specific threat entry. + * The client is expected to know the metadata key/value pairs associated with each threat type. * - * @see [ThreatEntryMetadata]{@link https://developers.google.com/safe-browsing/v4/reference/rest/v4/ThreatMatch#threatentrymetadata} + * @see https://developers.google.com/safe-browsing/reference/rest/v4/ThreatMatch#threatentrymetadata */ -export type ThreatEntryMetadata = { +export interface ThreatEntryMetadata { /** * The metadata entries. */ @@ -135,9 +165,9 @@ export type ThreatEntryMetadata = { /** * A match when checking a threat entry in the Safe Browsing threat lists. * - * @see [ThreatMatch]{@link https://developers.google.com/safe-browsing/v4/reference/rest/v4/ThreatMatch} + * @see https://developers.google.com/safe-browsing/reference/rest/v4/ThreatMatch */ -export type ThreatMatch = { +export interface ThreatMatch { /** * The threat type matching this threat. */ @@ -161,7 +191,8 @@ export type ThreatMatch = { /** * The cache lifetime for the returned match. Clients must not cache this response for more than this duration to avoid false positives. * - * A duration in seconds with up to nine fractional digits, terminated by 's'. Example: "3.5s". + * A duration in seconds with up to nine fractional digits, ending with '**s**'. + * Example: "**3.5s**". */ cacheDuration: string }