diff --git a/Dockerfile b/Dockerfile index fa3ecdc..3229e86 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,20 +10,14 @@ # ── Stage 1: Install Node.js dependencies ────────────────── FROM node:20-slim AS builder -# git is required by npm to clone the @keysat/licensing-client git+https -# dependency. Stripped from the final image (only used in this builder stage). -# The url.insteadOf rewrites force npm/git to use https for github.com even -# when npm's git resolver tries ssh first — there's no ssh client or key in -# this container. -RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \ - && rm -rf /var/lib/apt/lists/* \ - && git config --global --add url."https://github.com/".insteadOf "ssh://git@github.com/" \ - && git config --global --add url."https://github.com/".insteadOf "git@github.com:" \ - && git config --global --add url."https://github.com/".insteadOf "git://github.com/" - +# @keysat/licensing-client is a private git repo, so we vendor its built +# output into vendor/ and reference it via a file: dep. That removes any +# need for git or credentials in the build container. +WORKDIR /app +COPY vendor/keysat-licensing-client /app/vendor/keysat-licensing-client WORKDIR /app/server COPY server/package.json server/package-lock.json* ./ -RUN npm ci --production --ignore-scripts 2>/dev/null || npm install --production --ignore-scripts +RUN npm ci --omit=dev --ignore-scripts 2>/dev/null || npm install --omit=dev --ignore-scripts # ── Stage 2: Final runtime image ─────────────────────────── FROM node:20-slim AS runner diff --git a/package.json b/package.json index bfb5757..0b65712 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "recap-startos", + "name": "youtube-summarizer-startos", "private": true, "scripts": { "build": "rm -rf ./javascript && ncc build startos/index.ts -o ./javascript", diff --git a/server/package-lock.json b/server/package-lock.json index 2be5409..7a79280 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,15 +9,26 @@ "version": "1.0.0", "dependencies": { "@google/genai": "^1.41.0", - "@keysat/licensing-client": "git+https://github.com/keysat-xyz/keysat-client-ts.git", + "@keysat/licensing-client": "file:../vendor/keysat-licensing-client", "cors": "^2.8.5", "express": "^4.21.0" } }, + "../vendor/keysat-licensing-client": { + "name": "@keysat/licensing-client", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@noble/ed25519": "^2.0.0", + "@noble/hashes": "^1.3.3" + }, + "devDependencies": {}, + "engines": { + "node": ">=18" + } + }, "node_modules/@google/genai": { "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", - "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -39,66 +50,27 @@ } }, "node_modules/@keysat/licensing-client": { - "version": "0.1.0", - "resolved": "git+ssh://git@github.com/keysat-xyz/keysat-client-ts.git#61a6518ac80b08d672a53c5be3a31cf2064cd0ff", - "license": "MIT", - "dependencies": { - "@noble/ed25519": "^2.0.0", - "@noble/hashes": "^1.3.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@noble/ed25519": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-2.3.0.tgz", - "integrity": "sha512-M7dvXL2B92/M7dw9+gzuydL8qn/jiqNHaoR3Q+cb1q1GHV7uwE17WCyFMG+Y+TZb5izcaXk5TdJRrDUxHXL78A==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } + "resolved": "../vendor/keysat-licensing-client", + "link": true }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/base64": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.1", @@ -107,38 +79,26 @@ }, "node_modules/@protobufjs/float": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/inquire": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", - "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/path": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/pool": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", - "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", "license": "BSD-3-Clause" }, "node_modules/@types/node": { "version": "25.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.2.tgz", - "integrity": "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==", "license": "MIT", "dependencies": { "undici-types": "~7.19.0" @@ -146,14 +106,10 @@ }, "node_modules/@types/retry": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "license": "MIT" }, "node_modules/accepts": { "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", "dependencies": { "mime-types": "~2.1.34", @@ -165,8 +121,6 @@ }, "node_modules/agent-base": { "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", "engines": { "node": ">= 14" @@ -174,14 +128,10 @@ }, "node_modules/array-flatten": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "funding": [ { "type": "github", @@ -200,8 +150,6 @@ }, "node_modules/bignumber.js": { "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", "license": "MIT", "engines": { "node": "*" @@ -209,8 +157,6 @@ }, "node_modules/body-parser": { "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -233,8 +179,6 @@ }, "node_modules/body-parser/node_modules/qs": { "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -248,14 +192,10 @@ }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, "node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -263,8 +203,6 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -276,8 +214,6 @@ }, "node_modules/call-bound": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -292,8 +228,6 @@ }, "node_modules/content-disposition": { "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -304,8 +238,6 @@ }, "node_modules/content-type": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -313,8 +245,6 @@ }, "node_modules/cookie": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -322,14 +252,10 @@ }, "node_modules/cookie-signature": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, "node_modules/cors": { "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -345,8 +271,6 @@ }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "license": "MIT", "engines": { "node": ">= 12" @@ -354,8 +278,6 @@ }, "node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -363,8 +285,6 @@ }, "node_modules/depd": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -372,8 +292,6 @@ }, "node_modules/destroy": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "license": "MIT", "engines": { "node": ">= 0.8", @@ -382,8 +300,6 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -396,8 +312,6 @@ }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" @@ -405,14 +319,10 @@ }, "node_modules/ee-first": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -420,8 +330,6 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -429,8 +337,6 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -438,8 +344,6 @@ }, "node_modules/es-object-atoms": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -450,14 +354,10 @@ }, "node_modules/escape-html": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, "node_modules/etag": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -465,8 +365,6 @@ }, "node_modules/express": { "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -511,14 +409,10 @@ }, "node_modules/extend": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, "node_modules/fetch-blob": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "funding": [ { "type": "github", @@ -540,8 +434,6 @@ }, "node_modules/finalhandler": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -558,8 +450,6 @@ }, "node_modules/formdata-polyfill": { "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "license": "MIT", "dependencies": { "fetch-blob": "^3.1.2" @@ -570,8 +460,6 @@ }, "node_modules/forwarded": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -579,8 +467,6 @@ }, "node_modules/fresh": { "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -588,8 +474,6 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -597,8 +481,6 @@ }, "node_modules/gaxios": { "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", @@ -611,8 +493,6 @@ }, "node_modules/gcp-metadata": { "version": "8.1.2", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", - "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", "license": "Apache-2.0", "dependencies": { "gaxios": "^7.0.0", @@ -625,8 +505,6 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -649,8 +527,6 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -662,8 +538,6 @@ }, "node_modules/google-auth-library": { "version": "10.6.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", - "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", @@ -679,8 +553,6 @@ }, "node_modules/google-logging-utils": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", - "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", "license": "Apache-2.0", "engines": { "node": ">=14" @@ -688,8 +560,6 @@ }, "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -700,8 +570,6 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -712,8 +580,6 @@ }, "node_modules/hasown": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -724,8 +590,6 @@ }, "node_modules/http-errors": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { "depd": "~2.0.0", @@ -744,8 +608,6 @@ }, "node_modules/https-proxy-agent": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", "dependencies": { "agent-base": "^7.1.2", @@ -757,8 +619,6 @@ }, "node_modules/https-proxy-agent/node_modules/debug": { "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -774,14 +634,10 @@ }, "node_modules/https-proxy-agent/node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -792,14 +648,10 @@ }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, "node_modules/ipaddr.js": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -807,8 +659,6 @@ }, "node_modules/json-bigint": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", "license": "MIT", "dependencies": { "bignumber.js": "^9.0.0" @@ -816,8 +666,6 @@ }, "node_modules/jwa": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", "dependencies": { "buffer-equal-constant-time": "^1.0.1", @@ -827,8 +675,6 @@ }, "node_modules/jws": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", "dependencies": { "jwa": "^2.0.1", @@ -837,14 +683,10 @@ }, "node_modules/long": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -852,8 +694,6 @@ }, "node_modules/media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -861,8 +701,6 @@ }, "node_modules/merge-descriptors": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -870,8 +708,6 @@ }, "node_modules/methods": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -879,8 +715,6 @@ }, "node_modules/mime": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "license": "MIT", "bin": { "mime": "cli.js" @@ -891,8 +725,6 @@ }, "node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -900,8 +732,6 @@ }, "node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -912,14 +742,10 @@ }, "node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -927,9 +753,6 @@ }, "node_modules/node-domexception": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", "funding": [ { "type": "github", @@ -947,8 +770,6 @@ }, "node_modules/node-fetch": { "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "license": "MIT", "dependencies": { "data-uri-to-buffer": "^4.0.0", @@ -965,8 +786,6 @@ }, "node_modules/object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -974,8 +793,6 @@ }, "node_modules/object-inspect": { "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -986,8 +803,6 @@ }, "node_modules/on-finished": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -998,8 +813,6 @@ }, "node_modules/p-retry": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", "license": "MIT", "dependencies": { "@types/retry": "0.12.0", @@ -1011,8 +824,6 @@ }, "node_modules/parseurl": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -1020,14 +831,10 @@ }, "node_modules/path-to-regexp": { "version": "0.1.13", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", - "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, "node_modules/protobufjs": { "version": "7.5.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.6.tgz", - "integrity": "sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -1050,8 +857,6 @@ }, "node_modules/proxy-addr": { "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -1063,8 +868,6 @@ }, "node_modules/qs": { "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -1078,8 +881,6 @@ }, "node_modules/range-parser": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1087,8 +888,6 @@ }, "node_modules/raw-body": { "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -1102,8 +901,6 @@ }, "node_modules/retry": { "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "license": "MIT", "engines": { "node": ">= 4" @@ -1111,8 +908,6 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -1131,14 +926,10 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, "node_modules/send": { "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -1161,14 +952,10 @@ }, "node_modules/send/node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/serve-static": { "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", @@ -1182,14 +969,10 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, "node_modules/side-channel": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -1207,8 +990,6 @@ }, "node_modules/side-channel-list": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -1223,8 +1004,6 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -1241,8 +1020,6 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -1260,8 +1037,6 @@ }, "node_modules/statuses": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -1269,8 +1044,6 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", "engines": { "node": ">=0.6" @@ -1278,8 +1051,6 @@ }, "node_modules/type-is": { "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "license": "MIT", "dependencies": { "media-typer": "0.3.0", @@ -1291,14 +1062,10 @@ }, "node_modules/undici-types": { "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "license": "MIT" }, "node_modules/unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -1306,8 +1073,6 @@ }, "node_modules/utils-merge": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", "engines": { "node": ">= 0.4.0" @@ -1315,8 +1080,6 @@ }, "node_modules/vary": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -1324,8 +1087,6 @@ }, "node_modules/web-streams-polyfill": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "license": "MIT", "engines": { "node": ">= 8" @@ -1333,8 +1094,6 @@ }, "node_modules/ws": { "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/server/package.json b/server/package.json index 753326b..6516136 100644 --- a/server/package.json +++ b/server/package.json @@ -1,5 +1,5 @@ { - "name": "recap-server", + "name": "youtube-summarizer-server", "version": "1.0.0", "type": "module", "scripts": { @@ -8,7 +8,7 @@ }, "dependencies": { "@google/genai": "^1.41.0", - "@keysat/licensing-client": "git+https://github.com/keysat-xyz/keysat-client-ts.git", + "@keysat/licensing-client": "file:../vendor/keysat-licensing-client", "cors": "^2.8.5", "express": "^4.21.0" } diff --git a/vendor/keysat-licensing-client/LICENSE b/vendor/keysat-licensing-client/LICENSE new file mode 100644 index 0000000..1b63053 --- /dev/null +++ b/vendor/keysat-licensing-client/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Keysat + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/keysat-licensing-client/README.md b/vendor/keysat-licensing-client/README.md new file mode 100644 index 0000000..11220be --- /dev/null +++ b/vendor/keysat-licensing-client/README.md @@ -0,0 +1,71 @@ +# @keysat/licensing-client + +TypeScript / JavaScript client for [`Keysat`](https://github.com/keysat-xyz/keysat) — a self-hosted Bitcoin-paid software licensing server that runs on Start9. + +Works in modern browsers and Node 18+. No native dependencies; signature verification is done in pure JS via [`@noble/ed25519`](https://github.com/paulmillr/noble-ed25519). + +## What you get + +- **Offline verification**: check a license key with just the issuing server's public key. No network. +- **Online validation**: live revocation check and fingerprint binding via the service's `/v1/validate` endpoint. +- **Purchase flow**: kick off a BTCPay checkout and poll for the issued key. + +## Install + +```bash +npm install @keysat/licensing-client +``` + +## 5-line offline check + +```ts +import { Verifier, PublicKey } from '@keysat/licensing-client' + +const verifier = new Verifier(PublicKey.fromPem(ISSUER_PUBKEY_PEM)) +const ok = verifier.verify(keyFromUser) +console.log('licensed for product', ok.productId) +``` + +That's the whole integration. Embed your public key as a string at build time (e.g. Vite's `?raw` import, webpack raw-loader, or just a `const`). If the verifier returns without throwing, the key is real and was issued by you. + +## 10-line online check (with revocation + fingerprint) + +```ts +import { Client } from '@keysat/licensing-client' + +const client = new Client('https://license.example.com') +const result = await client.validate(keyFromUser, 'my-product', machineFingerprint) +if (!result.ok) { + console.error('rejected:', result.reason) + process.exit(1) +} +``` + +The server enforces revocation live and does trust-on-first-use fingerprint binding, so the same key used from a second machine gets rejected. + +## Purchase flow + +```ts +const session = await client.startPurchase('my-product') +console.log('pay at:', session.checkoutUrl) +const key = await client.waitForLicense(session.invoiceId) +console.log('got license:', key) +``` + +`waitForLicense` polls until the BTCPay invoice settles and the service issues a key. It throws if the invoice expires or becomes invalid. + +## Browser usage + +Everything here works in the browser too. Drop the library into your React/Svelte/Vue app and run offline verification client-side — no server call needed for the common case. + +```ts +// Vite: import the PEM as a raw string at build time +import issuerPem from './issuer.pub?raw' +import { Verifier, PublicKey } from '@keysat/licensing-client' + +const verifier = new Verifier(PublicKey.fromPem(issuerPem)) +``` + +## License + +MIT. diff --git a/vendor/keysat-licensing-client/dist/index.cjs b/vendor/keysat-licensing-client/dist/index.cjs new file mode 100644 index 0000000..c594a7b --- /dev/null +++ b/vendor/keysat-licensing-client/dist/index.cjs @@ -0,0 +1,553 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/index.ts +var index_exports = {}; +__export(index_exports, { + Client: () => Client, + FLAG_FINGERPRINT_BOUND: () => FLAG_FINGERPRINT_BOUND, + FLAG_TRIAL: () => FLAG_TRIAL, + KEY_PREFIX: () => KEY_PREFIX, + KEY_VERSION: () => KEY_VERSION, + KEY_VERSION_V1: () => KEY_VERSION_V1, + KEY_VERSION_V2: () => KEY_VERSION_V2, + LicensingError: () => LicensingError, + PublicKey: () => PublicKey, + Verifier: () => Verifier, + hasEntitlement: () => hasEntitlement, + hashFingerprint: () => hashFingerprint, + isExpiredAt: () => isExpiredAt, + parseLicenseKey: () => parseLicenseKey +}); +module.exports = __toCommonJS(index_exports); + +// src/verify.ts +var ed = __toESM(require("@noble/ed25519"), 1); +var import_sha512 = require("@noble/hashes/sha512"); + +// src/errors.ts +var LicensingError = class extends Error { + /** + * Machine-readable reason code. Common values: + * `"bad_format"`, `"bad_encoding"`, `"bad_version"`, `"bad_signature"`, + * `"expired"`, `"server_error"`, `"http_error"`, `"other"`. + */ + code; + constructor(code, message) { + super(message); + this.name = "LicensingError"; + this.code = code; + } +}; + +// src/base32.ts +var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; +var DECODE_TABLE = (() => { + const t = {}; + for (let i = 0; i < ALPHABET.length; i++) t[ALPHABET[i]] = i; + return t; +})(); +function decodeBase32NoPad(input) { + const up = input.toUpperCase(); + const out = new Uint8Array(Math.floor(up.length * 5 / 8)); + let bits = 0; + let value = 0; + let outPos = 0; + for (let i = 0; i < up.length; i++) { + const ch = up[i]; + const v = DECODE_TABLE[ch]; + if (v === void 0) { + throw new LicensingError("bad_encoding", `invalid base32 character '${ch}'`); + } + value = value << 5 | v; + bits += 5; + if (bits >= 8) { + bits -= 8; + out[outPos++] = value >> bits & 255; + } + } + return out.subarray(0, outPos); +} + +// src/key.ts +var KEY_PREFIX = "LIC1"; +var KEY_VERSION_V1 = 1; +var KEY_VERSION_V2 = 2; +var KEY_VERSION = KEY_VERSION_V2; +var FLAG_FINGERPRINT_BOUND = 1; +var FLAG_TRIAL = 2; +var PAYLOAD_V1_LEN = 74; +var PAYLOAD_V2_HEAD_LEN = 83; +var SIGNATURE_LEN = 64; +function isExpiredAt(payload, nowUnixSeconds) { + return payload.expiresAt !== 0 && nowUnixSeconds >= payload.expiresAt; +} +function hasEntitlement(payload, slug) { + return payload.entitlements.includes(slug); +} +function parseLicenseKey(raw) { + const trimmed = raw.trim(); + const firstDash = trimmed.indexOf("-"); + if (firstDash < 0) throw new LicensingError("bad_format", "key is missing prefix delimiter"); + const prefix = trimmed.slice(0, firstDash); + if (prefix !== KEY_PREFIX) throw new LicensingError("bad_format", `unknown key prefix '${prefix}'`); + const body = trimmed.slice(firstDash + 1); + const lastDash = body.lastIndexOf("-"); + if (lastDash < 0) throw new LicensingError("bad_format", "key is missing signature delimiter"); + const payloadB32 = body.slice(0, lastDash); + const signatureB32 = body.slice(lastDash + 1); + const payloadBytes = decodeBase32NoPad(payloadB32); + const signature = decodeBase32NoPad(signatureB32); + if (signature.length !== SIGNATURE_LEN) { + throw new LicensingError( + "bad_format", + `signature is ${signature.length} bytes; expected ${SIGNATURE_LEN}` + ); + } + if (payloadBytes.length < 1) { + throw new LicensingError("bad_format", "empty payload"); + } + const version = payloadBytes[0]; + let payload; + switch (version) { + case KEY_VERSION_V1: + payload = parseV1(payloadBytes); + break; + case KEY_VERSION_V2: + payload = parseV2(payloadBytes); + break; + default: + throw new LicensingError("bad_version", `unsupported key version ${version}`); + } + return { + payload, + signedBytes: payloadBytes, + signature + }; +} +function parseV1(payloadBytes) { + if (payloadBytes.length !== PAYLOAD_V1_LEN) { + throw new LicensingError( + "bad_format", + `v1 payload is ${payloadBytes.length} bytes; expected ${PAYLOAD_V1_LEN}` + ); + } + const flags = payloadBytes[1]; + const productId = payloadBytes.slice(2, 18); + const licenseId = payloadBytes.slice(18, 34); + const issuedAt = readBigEndianI64(payloadBytes, 34); + const fingerprintHash = payloadBytes.slice(42, 74); + return { + version: KEY_VERSION_V1, + flags, + productId, + licenseId, + issuedAt, + expiresAt: 0, + fingerprintHash, + entitlements: [], + productUuid: uuidString(productId), + licenseUuid: uuidString(licenseId), + isFingerprintBound: (flags & FLAG_FINGERPRINT_BOUND) !== 0, + isTrial: (flags & FLAG_TRIAL) !== 0 + }; +} +function parseV2(payloadBytes) { + if (payloadBytes.length < PAYLOAD_V2_HEAD_LEN) { + throw new LicensingError( + "bad_format", + `v2 payload is ${payloadBytes.length} bytes; expected >= ${PAYLOAD_V2_HEAD_LEN}` + ); + } + const flags = payloadBytes[1]; + const productId = payloadBytes.slice(2, 18); + const licenseId = payloadBytes.slice(18, 34); + const issuedAt = readBigEndianI64(payloadBytes, 34); + const expiresAt = readBigEndianI64(payloadBytes, 42); + const fingerprintHash = payloadBytes.slice(50, 82); + const numEntitlements = payloadBytes[82]; + const entitlements = []; + let cursor = PAYLOAD_V2_HEAD_LEN; + const decoder = new TextDecoder("utf-8", { fatal: true }); + for (let i = 0; i < numEntitlements; i++) { + if (cursor >= payloadBytes.length) { + throw new LicensingError("bad_format", "truncated entitlement list"); + } + const len = payloadBytes[cursor]; + cursor += 1; + if (cursor + len > payloadBytes.length) { + throw new LicensingError("bad_format", "truncated entitlement"); + } + try { + entitlements.push(decoder.decode(payloadBytes.slice(cursor, cursor + len))); + } catch { + throw new LicensingError("bad_format", "entitlement not utf-8"); + } + cursor += len; + } + if (cursor !== payloadBytes.length) { + throw new LicensingError("bad_format", "trailing bytes in payload"); + } + return { + version: KEY_VERSION_V2, + flags, + productId, + licenseId, + issuedAt, + expiresAt, + fingerprintHash, + entitlements, + productUuid: uuidString(productId), + licenseUuid: uuidString(licenseId), + isFingerprintBound: (flags & FLAG_FINGERPRINT_BOUND) !== 0, + isTrial: (flags & FLAG_TRIAL) !== 0 + }; +} +function readBigEndianI64(buf, offset) { + const view = new DataView(buf.buffer, buf.byteOffset + offset, 8); + const hi = view.getInt32(0, false); + const lo = view.getUint32(4, false); + return hi * 2 ** 32 + lo; +} +function uuidString(b) { + const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join(""); + return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`; +} + +// src/fingerprint.ts +var import_sha256 = require("@noble/hashes/sha256"); +function hashFingerprint(raw) { + return (0, import_sha256.sha256)(new TextEncoder().encode(raw)); +} + +// src/verify.ts +ed.etc.sha512Sync = (...m) => (0, import_sha512.sha512)(ed.etc.concatBytes(...m)); +var Verifier = class { + pubkey; + constructor(pubkey) { + this.pubkey = pubkey; + } + /** Verify a license key string. Throws on any failure. */ + verify(keyStr) { + const key = parseLicenseKey(keyStr); + const ok = ed.verify(key.signature, key.signedBytes, this.pubkey.raw); + if (!ok) throw new LicensingError("bad_signature", "signature did not verify"); + return { + payload: key.payload, + licenseId: key.payload.licenseUuid, + productId: key.payload.productUuid + }; + } + /** + * Verify AND enforce that, if the key is fingerprint-bound, the given + * fingerprint matches. If the key is not bound, the fingerprint is + * ignored. Throws on any failure. + */ + verifyWithFingerprint(keyStr, fingerprint) { + const result = this.verify(keyStr); + if (result.payload.isFingerprintBound) { + const expected = hashFingerprint(fingerprint); + const stored = result.payload.fingerprintHash; + if (!equalBytes(expected, stored)) { + throw new LicensingError("bad_signature", "fingerprint does not match bound key"); + } + } + return result; + } + /** + * Verify a key and additionally reject it with an `expired` error if + * `nowUnixSeconds` is at or past its `expiresAt`. Perpetual keys + * (`expiresAt === 0`) are accepted regardless of `nowUnixSeconds`. This is + * offline-only — no grace window logic; use `Client.validate` for that. + */ + verifyWithTime(keyStr, nowUnixSeconds) { + const result = this.verify(keyStr); + if (isExpiredAt(result.payload, nowUnixSeconds)) { + throw new LicensingError("expired", "license has expired"); + } + return result; + } +}; +function equalBytes(a, b) { + if (a.length !== b.length) return false; + let diff = 0; + for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i]; + return diff === 0; +} + +// src/online.ts +var Client = class { + base; + constructor(baseUrl) { + this.base = baseUrl.replace(/\/+$/, ""); + } + /** The normalized base URL this client is pinned to. */ + baseUrl() { + return this.base; + } + /** Fetch the server's PEM-encoded public key. */ + async fetchPubkeyPem() { + const data = await this.get("/v1/pubkey"); + return data.public_key_pem; + } + /** + * Server-authoritative validation. Returns the full response including + * expiry / entitlements / seat fields introduced in v2. + * + * Two-argument form kept for call-site compatibility with earlier SDK + * versions; pass an options object for the full set of fields. + */ + async validate(key, productSlugOrOptions, fingerprint) { + const opts = typeof productSlugOrOptions === "string" ? { productSlug: productSlugOrOptions, fingerprint } : productSlugOrOptions ?? {}; + const raw = await this.post("/v1/validate", { + key, + product_slug: opts.productSlug, + fingerprint: opts.fingerprint, + hostname: opts.hostname, + platform: opts.platform + }); + return this.toValidateResponse(raw); + } + /** Lightweight heartbeat. Server updates `last_heartbeat_at`. */ + async heartbeat(key, fingerprint) { + const raw = await this.post("/v1/machines/heartbeat", { + key, + fingerprint + }); + return this.toMachineResponse(raw); + } + /** Explicitly activate a seat for the given fingerprint. */ + async activate(key, fingerprint, opts = {}) { + const raw = await this.post("/v1/machines/activate", { + key, + fingerprint, + hostname: opts.hostname, + platform: opts.platform + }); + return this.toMachineResponse(raw); + } + /** Free a seat held by the given fingerprint. */ + async deactivate(key, fingerprint, reason) { + const raw = await this.post("/v1/machines/deactivate", { + key, + fingerprint, + reason + }); + return this.toMachineResponse(raw); + } + /** Start a purchase. Returns the checkout URL and invoice id. */ + async startPurchase(productSlug, opts = {}) { + const raw = await this.post("/v1/purchase", { + product: productSlug, + buyer_email: opts.buyerEmail, + buyer_note: opts.buyerNote, + redirect_url: opts.redirectUrl, + code: opts.code + }); + return { + invoiceId: raw.invoice_id, + btcpayInvoiceId: raw.btcpay_invoice_id, + checkoutUrl: raw.checkout_url, + amountSats: raw.amount_sats, + pollUrl: raw.poll_url + }; + } + /** + * Redeem a `free_license` code: bypass BTCPay entirely and receive the + * signed license key directly. Throws if the code is unknown / disabled + * / expired / wrong product / not a free_license code, or if the cap + * has been reached. + */ + async redeemFreeLicense(productSlug, code, opts = {}) { + const raw = await this.post("/v1/redeem", { + product: productSlug, + code, + buyer_email: opts.buyerEmail, + buyer_note: opts.buyerNote + }); + return { + licenseId: raw.license_id, + licenseKey: raw.license_key, + invoiceId: raw.invoice_id, + redemptionId: raw.redemption_id + }; + } + /** Poll a purchase by its invoice id. */ + async pollPurchase(invoiceId) { + const raw = await this.get( + `/v1/purchase/${encodeURIComponent(invoiceId)}` + ); + return { + invoiceId: raw.invoice_id, + status: raw.status, + productId: raw.product_id, + amountSats: raw.amount_sats, + licenseKey: raw.license_key ?? void 0, + licenseId: raw.license_id ?? void 0 + }; + } + /** + * Convenience: open the checkout, poll until a license key is issued, + * then return it. Suitable for CLI usage or for an app UI that shows a + * spinner while the buyer pays. + */ + async waitForLicense(invoiceId, options = {}) { + const interval = options.intervalMs ?? 5e3; + const deadline = options.timeoutMs ? Date.now() + options.timeoutMs : Infinity; + while (true) { + const poll = await this.pollPurchase(invoiceId); + if (poll.licenseKey) return poll.licenseKey; + if (poll.status === "expired" || poll.status === "invalid") { + throw new LicensingError("server_error", `invoice ended in status ${poll.status}`); + } + if (Date.now() > deadline) { + throw new LicensingError("server_error", "timed out waiting for license issuance"); + } + await sleep(interval); + } + } + // --- internals --- + toValidateResponse(raw) { + const entitlements = Array.isArray(raw.entitlements) ? raw.entitlements.filter((x) => typeof x === "string") : void 0; + return { + ok: !!raw.ok, + reason: raw.reason, + licenseId: raw.license_id, + productId: raw.product_id, + productSlug: raw.product_slug, + issuedAt: raw.issued_at, + expiresAt: raw.expires_at, + graceUntil: raw.grace_until, + inGracePeriod: raw.in_grace_period, + isTrial: raw.is_trial, + entitlements, + status: raw.status, + machineId: raw.machine_id, + maxMachines: raw.max_machines + }; + } + toMachineResponse(raw) { + return { + ok: !!raw.ok, + reason: raw.reason, + machineId: raw.machine_id, + activeCount: raw.active_count, + maxMachines: raw.max_machines + }; + } + async get(path) { + return this.request(path, { method: "GET" }); + } + async post(path, body) { + return this.request(path, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body) + }); + } + async request(path, init) { + let resp; + try { + resp = await fetch(`${this.base}${path}`, init); + } catch (e) { + throw new LicensingError("http_error", e instanceof Error ? e.message : String(e)); + } + const text = await resp.text(); + if (!resp.ok) { + throw new LicensingError("server_error", `HTTP ${resp.status}: ${text}`); + } + try { + return JSON.parse(text); + } catch { + throw new LicensingError("server_error", `non-JSON response: ${text}`); + } + } +}; +function sleep(ms) { + return new Promise((res) => setTimeout(res, ms)); +} + +// src/pubkey.ts +var PublicKey = class _PublicKey { + /** Raw 32-byte Ed25519 public key material. */ + raw; + constructor(raw) { + if (raw.length !== 32) { + throw new LicensingError( + "bad_format", + `public key must be 32 bytes; got ${raw.length}` + ); + } + this.raw = raw; + } + /** Parse a PEM blob as emitted by the service. */ + static fromPem(pem) { + const stripped = pem.replace(/-----BEGIN [^-]+-----/g, "").replace(/-----END [^-]+-----/g, "").replace(/\s+/g, ""); + if (!stripped) { + throw new LicensingError("bad_format", "empty PEM input"); + } + const der = base64Decode(stripped); + if (der.length < 32) { + throw new LicensingError("bad_format", "PEM body too short to contain a public key"); + } + const raw = der.slice(der.length - 32); + return new _PublicKey(raw); + } + /** Construct from raw bytes (no PEM envelope). */ + static fromBytes(bytes) { + return new _PublicKey(bytes); + } +}; +function base64Decode(b64) { + const nodeBuffer = globalThis.Buffer; + if (nodeBuffer) { + return new Uint8Array(nodeBuffer.from(b64, "base64")); + } + const bin = atob(b64); + const out = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i); + return out; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + Client, + FLAG_FINGERPRINT_BOUND, + FLAG_TRIAL, + KEY_PREFIX, + KEY_VERSION, + KEY_VERSION_V1, + KEY_VERSION_V2, + LicensingError, + PublicKey, + Verifier, + hasEntitlement, + hashFingerprint, + isExpiredAt, + parseLicenseKey +}); diff --git a/vendor/keysat-licensing-client/dist/index.d.cts b/vendor/keysat-licensing-client/dist/index.d.cts new file mode 100644 index 0000000..959b5aa --- /dev/null +++ b/vendor/keysat-licensing-client/dist/index.d.cts @@ -0,0 +1,306 @@ +/** + * License-key parsing. Matches the service's wire format exactly. + * + * ## Wire format + * + * A key string looks like `LIC1--`. Both halves + * are Crockford base32 (no padding) of the raw bytes. + * + * ### v1 payload (74 bytes, fixed) + * + * ```text + * offset size field + * 0 1 version = 1 + * 1 1 flags + * 2 16 product_id (UUID bytes) + * 18 16 license_id (UUID bytes) + * 34 8 issued_at (i64 unix seconds, big-endian) + * 42 32 fingerprint_hash (SHA-256, or all-zero) + * ``` + * + * ### v2 payload (83 bytes + variable entitlements) + * + * ```text + * offset size field + * 0 1 version = 2 + * 1 1 flags + * 2 16 product_id + * 18 16 license_id + * 34 8 issued_at + * 42 8 expires_at (i64, 0 = perpetual) + * 50 32 fingerprint_hash + * 82 1 num_entitlements (u8) + * 83 * entitlements — each: [u8 len][len utf-8 bytes] + * ``` + * + * Clients verifying a v1 key treat `expiresAt` as 0 and `entitlements` as + * empty, so application code can branch on flags / fields uniformly. + */ +declare const KEY_PREFIX = "LIC1"; +/** v1 format identifier. */ +declare const KEY_VERSION_V1 = 1; +/** v2 format identifier. */ +declare const KEY_VERSION_V2 = 2; +/** Highest format version this client understands. */ +declare const KEY_VERSION = 2; +/** Set when the key is bound to a specific machine fingerprint hash. */ +declare const FLAG_FINGERPRINT_BOUND = 1; +/** Set on trial keys. */ +declare const FLAG_TRIAL = 2; +/** Decoded fields of the signed payload. */ +interface LicensePayload { + /** Format version (1 or 2). */ + version: number; + /** Feature flags. */ + flags: number; + /** Raw 16-byte product id (UUID). */ + productId: Uint8Array; + /** Raw 16-byte license id (UUID). */ + licenseId: Uint8Array; + /** Unix seconds issued. */ + issuedAt: number; + /** Unix seconds expiry; `0` for perpetual. Always `0` on v1 keys. */ + expiresAt: number; + /** SHA-256 hash of the bound machine fingerprint, or all-zero. */ + fingerprintHash: Uint8Array; + /** Entitlement slugs granted by this license. Empty on v1 keys. */ + entitlements: string[]; + /** Product UUID in canonical string form. */ + productUuid: string; + /** License UUID in canonical string form. */ + licenseUuid: string; + /** True if the key is fingerprint-bound. */ + isFingerprintBound: boolean; + /** True if the key is flagged as a trial. */ + isTrial: boolean; +} +/** A parsed (not yet verified) license key. */ +interface LicenseKey { + payload: LicensePayload; + /** + * Raw payload bytes (what the server signed over). Length is 74 on v1, + * `>= 83` on v2. + */ + signedBytes: Uint8Array; + /** Raw 64-byte signature. */ + signature: Uint8Array; +} +/** True if `nowUnixSeconds` is at or after the key's `expiresAt`. */ +declare function isExpiredAt(payload: LicensePayload, nowUnixSeconds: number): boolean; +/** True if the license grants the given entitlement slug. */ +declare function hasEntitlement(payload: LicensePayload, slug: string): boolean; +/** Parse a `LIC1-...-...` string. Does NOT verify. */ +declare function parseLicenseKey(raw: string): LicenseKey; + +/** + * Issuer public key. Accepts either raw 32-byte Ed25519 key material or a + * PEM-encoded SubjectPublicKeyInfo blob (which is what the service returns + * from `/v1/pubkey`). + */ +/** Parsed Ed25519 public key, ready for signature verification. */ +declare class PublicKey { + /** Raw 32-byte Ed25519 public key material. */ + readonly raw: Uint8Array; + constructor(raw: Uint8Array); + /** Parse a PEM blob as emitted by the service. */ + static fromPem(pem: string): PublicKey; + /** Construct from raw bytes (no PEM envelope). */ + static fromBytes(bytes: Uint8Array): PublicKey; +} + +/** Offline Ed25519 signature verification. */ + +interface VerifyOk { + /** Parsed payload fields. */ + payload: LicensePayload; + /** License UUID as a canonical string. */ + licenseId: string; + /** Product UUID as a canonical string. */ + productId: string; +} +/** Verifies license keys against a single issuing server's public key. */ +declare class Verifier { + private pubkey; + constructor(pubkey: PublicKey); + /** Verify a license key string. Throws on any failure. */ + verify(keyStr: string): VerifyOk; + /** + * Verify AND enforce that, if the key is fingerprint-bound, the given + * fingerprint matches. If the key is not bound, the fingerprint is + * ignored. Throws on any failure. + */ + verifyWithFingerprint(keyStr: string, fingerprint: string): VerifyOk; + /** + * Verify a key and additionally reject it with an `expired` error if + * `nowUnixSeconds` is at or past its `expiresAt`. Perpetual keys + * (`expiresAt === 0`) are accepted regardless of `nowUnixSeconds`. This is + * offline-only — no grace window logic; use `Client.validate` for that. + */ + verifyWithTime(keyStr: string, nowUnixSeconds: number): VerifyOk; +} + +/** + * Online operations against a running `licensing-service` instance. + * + * All methods use the global `fetch` available in Node 18+ and every modern + * browser. No additional runtime required. + */ +interface ValidateResponse { + ok: boolean; + /** + * Machine-readable reason on failure. One of: + * `bad_format`, `bad_signature`, `not_found`, `revoked`, `suspended`, + * `expired`, `product_mismatch`, `fingerprint_mismatch`, + * `too_many_machines`, `rate_limited`, `invalid_state`. + */ + reason?: string; + licenseId?: string; + productId?: string; + productSlug?: string; + issuedAt?: string; + /** Expiry timestamp (RFC 3339) if the license has one. */ + expiresAt?: string; + /** End of the grace window (RFC 3339) when in a grace period. */ + graceUntil?: string; + /** True when the key is past `expiresAt` but still inside the grace window. */ + inGracePeriod?: boolean; + /** True if this license is flagged as a trial. */ + isTrial?: boolean; + /** Entitlement slugs granted by the license. */ + entitlements?: string[]; + /** License status string: `active`, `suspended`, `revoked`. */ + status?: string; + /** Machine id created or matched by this call (when fingerprint was sent). */ + machineId?: string; + /** Seat cap: `0` unlimited, `1` single-seat, `n` n-seat. */ + maxMachines?: number; +} +interface ValidateOptions { + /** Product slug the caller expects the key to cover. */ + productSlug?: string; + /** Raw machine fingerprint; enables seat binding / cap enforcement. */ + fingerprint?: string; + /** Client-supplied hostname, stored against the machine row on activation. */ + hostname?: string; + /** Client-supplied platform descriptor, e.g. `'linux-x86_64'`. */ + platform?: string; +} +interface MachineResponse { + ok: boolean; + reason?: string; + machineId?: string; + activeCount?: number; + maxMachines?: number; +} +interface PurchaseSession { + /** Our internal invoice id — use with `pollPurchase`. */ + invoiceId: string; + /** BTCPay's invoice id (opaque). */ + btcpayInvoiceId: string; + /** URL to open in the buyer's browser to pay. */ + checkoutUrl: string; + /** Price in satoshis. */ + amountSats: number; + /** Where the service recommends polling. */ + pollUrl: string; +} +interface PollResponse { + invoiceId: string; + /** `pending | settled | expired | invalid`. */ + status: string; + productId: string; + amountSats: number; + /** Populated once the license has been issued. */ + licenseKey?: string; + licenseId?: string; +} +interface StartPurchaseOptions { + /** Optional email for the receipt. */ + buyerEmail?: string; + /** Optional URL the buyer should be returned to after payment. */ + redirectUrl?: string; + /** Optional discount / referral code. */ + code?: string; + /** Optional buyer note recorded on the invoice (admin-visible). */ + buyerNote?: string; +} +interface RedeemFreeOptions { + /** Optional email recorded on the synthetic invoice + license. */ + buyerEmail?: string; + /** Optional buyer note. */ + buyerNote?: string; +} +interface RedeemFreeResponse { + licenseId: string; + /** The fully-signed license key, ready for offline verification. */ + licenseKey: string; + invoiceId: string; + redemptionId: string; +} +/** An HTTP client pinned to one licensing-service base URL. */ +declare class Client { + private base; + constructor(baseUrl: string); + /** The normalized base URL this client is pinned to. */ + baseUrl(): string; + /** Fetch the server's PEM-encoded public key. */ + fetchPubkeyPem(): Promise; + /** + * Server-authoritative validation. Returns the full response including + * expiry / entitlements / seat fields introduced in v2. + * + * Two-argument form kept for call-site compatibility with earlier SDK + * versions; pass an options object for the full set of fields. + */ + validate(key: string, productSlugOrOptions?: string | ValidateOptions, fingerprint?: string): Promise; + /** Lightweight heartbeat. Server updates `last_heartbeat_at`. */ + heartbeat(key: string, fingerprint: string): Promise; + /** Explicitly activate a seat for the given fingerprint. */ + activate(key: string, fingerprint: string, opts?: { + hostname?: string; + platform?: string; + }): Promise; + /** Free a seat held by the given fingerprint. */ + deactivate(key: string, fingerprint: string, reason?: string): Promise; + /** Start a purchase. Returns the checkout URL and invoice id. */ + startPurchase(productSlug: string, opts?: StartPurchaseOptions): Promise; + /** + * Redeem a `free_license` code: bypass BTCPay entirely and receive the + * signed license key directly. Throws if the code is unknown / disabled + * / expired / wrong product / not a free_license code, or if the cap + * has been reached. + */ + redeemFreeLicense(productSlug: string, code: string, opts?: RedeemFreeOptions): Promise; + /** Poll a purchase by its invoice id. */ + pollPurchase(invoiceId: string): Promise; + /** + * Convenience: open the checkout, poll until a license key is issued, + * then return it. Suitable for CLI usage or for an app UI that shows a + * spinner while the buyer pays. + */ + waitForLicense(invoiceId: string, options?: { + intervalMs?: number; + timeoutMs?: number; + }): Promise; + private toValidateResponse; + private toMachineResponse; + private get; + private post; + private request; +} + +/** Hash a raw fingerprint string to the 32-byte form embedded in keys. */ +declare function hashFingerprint(raw: string): Uint8Array; + +/** All errors thrown by this library inherit from `LicensingError`. */ +declare class LicensingError extends Error { + /** + * Machine-readable reason code. Common values: + * `"bad_format"`, `"bad_encoding"`, `"bad_version"`, `"bad_signature"`, + * `"expired"`, `"server_error"`, `"http_error"`, `"other"`. + */ + readonly code: string; + constructor(code: string, message: string); +} + +export { Client, FLAG_FINGERPRINT_BOUND, FLAG_TRIAL, KEY_PREFIX, KEY_VERSION, KEY_VERSION_V1, KEY_VERSION_V2, type LicenseKey, type LicensePayload, LicensingError, type MachineResponse, type PollResponse, PublicKey, type PurchaseSession, type RedeemFreeOptions, type RedeemFreeResponse, type StartPurchaseOptions, type ValidateOptions, type ValidateResponse, Verifier, type VerifyOk, hasEntitlement, hashFingerprint, isExpiredAt, parseLicenseKey }; diff --git a/vendor/keysat-licensing-client/dist/index.d.ts b/vendor/keysat-licensing-client/dist/index.d.ts new file mode 100644 index 0000000..959b5aa --- /dev/null +++ b/vendor/keysat-licensing-client/dist/index.d.ts @@ -0,0 +1,306 @@ +/** + * License-key parsing. Matches the service's wire format exactly. + * + * ## Wire format + * + * A key string looks like `LIC1--`. Both halves + * are Crockford base32 (no padding) of the raw bytes. + * + * ### v1 payload (74 bytes, fixed) + * + * ```text + * offset size field + * 0 1 version = 1 + * 1 1 flags + * 2 16 product_id (UUID bytes) + * 18 16 license_id (UUID bytes) + * 34 8 issued_at (i64 unix seconds, big-endian) + * 42 32 fingerprint_hash (SHA-256, or all-zero) + * ``` + * + * ### v2 payload (83 bytes + variable entitlements) + * + * ```text + * offset size field + * 0 1 version = 2 + * 1 1 flags + * 2 16 product_id + * 18 16 license_id + * 34 8 issued_at + * 42 8 expires_at (i64, 0 = perpetual) + * 50 32 fingerprint_hash + * 82 1 num_entitlements (u8) + * 83 * entitlements — each: [u8 len][len utf-8 bytes] + * ``` + * + * Clients verifying a v1 key treat `expiresAt` as 0 and `entitlements` as + * empty, so application code can branch on flags / fields uniformly. + */ +declare const KEY_PREFIX = "LIC1"; +/** v1 format identifier. */ +declare const KEY_VERSION_V1 = 1; +/** v2 format identifier. */ +declare const KEY_VERSION_V2 = 2; +/** Highest format version this client understands. */ +declare const KEY_VERSION = 2; +/** Set when the key is bound to a specific machine fingerprint hash. */ +declare const FLAG_FINGERPRINT_BOUND = 1; +/** Set on trial keys. */ +declare const FLAG_TRIAL = 2; +/** Decoded fields of the signed payload. */ +interface LicensePayload { + /** Format version (1 or 2). */ + version: number; + /** Feature flags. */ + flags: number; + /** Raw 16-byte product id (UUID). */ + productId: Uint8Array; + /** Raw 16-byte license id (UUID). */ + licenseId: Uint8Array; + /** Unix seconds issued. */ + issuedAt: number; + /** Unix seconds expiry; `0` for perpetual. Always `0` on v1 keys. */ + expiresAt: number; + /** SHA-256 hash of the bound machine fingerprint, or all-zero. */ + fingerprintHash: Uint8Array; + /** Entitlement slugs granted by this license. Empty on v1 keys. */ + entitlements: string[]; + /** Product UUID in canonical string form. */ + productUuid: string; + /** License UUID in canonical string form. */ + licenseUuid: string; + /** True if the key is fingerprint-bound. */ + isFingerprintBound: boolean; + /** True if the key is flagged as a trial. */ + isTrial: boolean; +} +/** A parsed (not yet verified) license key. */ +interface LicenseKey { + payload: LicensePayload; + /** + * Raw payload bytes (what the server signed over). Length is 74 on v1, + * `>= 83` on v2. + */ + signedBytes: Uint8Array; + /** Raw 64-byte signature. */ + signature: Uint8Array; +} +/** True if `nowUnixSeconds` is at or after the key's `expiresAt`. */ +declare function isExpiredAt(payload: LicensePayload, nowUnixSeconds: number): boolean; +/** True if the license grants the given entitlement slug. */ +declare function hasEntitlement(payload: LicensePayload, slug: string): boolean; +/** Parse a `LIC1-...-...` string. Does NOT verify. */ +declare function parseLicenseKey(raw: string): LicenseKey; + +/** + * Issuer public key. Accepts either raw 32-byte Ed25519 key material or a + * PEM-encoded SubjectPublicKeyInfo blob (which is what the service returns + * from `/v1/pubkey`). + */ +/** Parsed Ed25519 public key, ready for signature verification. */ +declare class PublicKey { + /** Raw 32-byte Ed25519 public key material. */ + readonly raw: Uint8Array; + constructor(raw: Uint8Array); + /** Parse a PEM blob as emitted by the service. */ + static fromPem(pem: string): PublicKey; + /** Construct from raw bytes (no PEM envelope). */ + static fromBytes(bytes: Uint8Array): PublicKey; +} + +/** Offline Ed25519 signature verification. */ + +interface VerifyOk { + /** Parsed payload fields. */ + payload: LicensePayload; + /** License UUID as a canonical string. */ + licenseId: string; + /** Product UUID as a canonical string. */ + productId: string; +} +/** Verifies license keys against a single issuing server's public key. */ +declare class Verifier { + private pubkey; + constructor(pubkey: PublicKey); + /** Verify a license key string. Throws on any failure. */ + verify(keyStr: string): VerifyOk; + /** + * Verify AND enforce that, if the key is fingerprint-bound, the given + * fingerprint matches. If the key is not bound, the fingerprint is + * ignored. Throws on any failure. + */ + verifyWithFingerprint(keyStr: string, fingerprint: string): VerifyOk; + /** + * Verify a key and additionally reject it with an `expired` error if + * `nowUnixSeconds` is at or past its `expiresAt`. Perpetual keys + * (`expiresAt === 0`) are accepted regardless of `nowUnixSeconds`. This is + * offline-only — no grace window logic; use `Client.validate` for that. + */ + verifyWithTime(keyStr: string, nowUnixSeconds: number): VerifyOk; +} + +/** + * Online operations against a running `licensing-service` instance. + * + * All methods use the global `fetch` available in Node 18+ and every modern + * browser. No additional runtime required. + */ +interface ValidateResponse { + ok: boolean; + /** + * Machine-readable reason on failure. One of: + * `bad_format`, `bad_signature`, `not_found`, `revoked`, `suspended`, + * `expired`, `product_mismatch`, `fingerprint_mismatch`, + * `too_many_machines`, `rate_limited`, `invalid_state`. + */ + reason?: string; + licenseId?: string; + productId?: string; + productSlug?: string; + issuedAt?: string; + /** Expiry timestamp (RFC 3339) if the license has one. */ + expiresAt?: string; + /** End of the grace window (RFC 3339) when in a grace period. */ + graceUntil?: string; + /** True when the key is past `expiresAt` but still inside the grace window. */ + inGracePeriod?: boolean; + /** True if this license is flagged as a trial. */ + isTrial?: boolean; + /** Entitlement slugs granted by the license. */ + entitlements?: string[]; + /** License status string: `active`, `suspended`, `revoked`. */ + status?: string; + /** Machine id created or matched by this call (when fingerprint was sent). */ + machineId?: string; + /** Seat cap: `0` unlimited, `1` single-seat, `n` n-seat. */ + maxMachines?: number; +} +interface ValidateOptions { + /** Product slug the caller expects the key to cover. */ + productSlug?: string; + /** Raw machine fingerprint; enables seat binding / cap enforcement. */ + fingerprint?: string; + /** Client-supplied hostname, stored against the machine row on activation. */ + hostname?: string; + /** Client-supplied platform descriptor, e.g. `'linux-x86_64'`. */ + platform?: string; +} +interface MachineResponse { + ok: boolean; + reason?: string; + machineId?: string; + activeCount?: number; + maxMachines?: number; +} +interface PurchaseSession { + /** Our internal invoice id — use with `pollPurchase`. */ + invoiceId: string; + /** BTCPay's invoice id (opaque). */ + btcpayInvoiceId: string; + /** URL to open in the buyer's browser to pay. */ + checkoutUrl: string; + /** Price in satoshis. */ + amountSats: number; + /** Where the service recommends polling. */ + pollUrl: string; +} +interface PollResponse { + invoiceId: string; + /** `pending | settled | expired | invalid`. */ + status: string; + productId: string; + amountSats: number; + /** Populated once the license has been issued. */ + licenseKey?: string; + licenseId?: string; +} +interface StartPurchaseOptions { + /** Optional email for the receipt. */ + buyerEmail?: string; + /** Optional URL the buyer should be returned to after payment. */ + redirectUrl?: string; + /** Optional discount / referral code. */ + code?: string; + /** Optional buyer note recorded on the invoice (admin-visible). */ + buyerNote?: string; +} +interface RedeemFreeOptions { + /** Optional email recorded on the synthetic invoice + license. */ + buyerEmail?: string; + /** Optional buyer note. */ + buyerNote?: string; +} +interface RedeemFreeResponse { + licenseId: string; + /** The fully-signed license key, ready for offline verification. */ + licenseKey: string; + invoiceId: string; + redemptionId: string; +} +/** An HTTP client pinned to one licensing-service base URL. */ +declare class Client { + private base; + constructor(baseUrl: string); + /** The normalized base URL this client is pinned to. */ + baseUrl(): string; + /** Fetch the server's PEM-encoded public key. */ + fetchPubkeyPem(): Promise; + /** + * Server-authoritative validation. Returns the full response including + * expiry / entitlements / seat fields introduced in v2. + * + * Two-argument form kept for call-site compatibility with earlier SDK + * versions; pass an options object for the full set of fields. + */ + validate(key: string, productSlugOrOptions?: string | ValidateOptions, fingerprint?: string): Promise; + /** Lightweight heartbeat. Server updates `last_heartbeat_at`. */ + heartbeat(key: string, fingerprint: string): Promise; + /** Explicitly activate a seat for the given fingerprint. */ + activate(key: string, fingerprint: string, opts?: { + hostname?: string; + platform?: string; + }): Promise; + /** Free a seat held by the given fingerprint. */ + deactivate(key: string, fingerprint: string, reason?: string): Promise; + /** Start a purchase. Returns the checkout URL and invoice id. */ + startPurchase(productSlug: string, opts?: StartPurchaseOptions): Promise; + /** + * Redeem a `free_license` code: bypass BTCPay entirely and receive the + * signed license key directly. Throws if the code is unknown / disabled + * / expired / wrong product / not a free_license code, or if the cap + * has been reached. + */ + redeemFreeLicense(productSlug: string, code: string, opts?: RedeemFreeOptions): Promise; + /** Poll a purchase by its invoice id. */ + pollPurchase(invoiceId: string): Promise; + /** + * Convenience: open the checkout, poll until a license key is issued, + * then return it. Suitable for CLI usage or for an app UI that shows a + * spinner while the buyer pays. + */ + waitForLicense(invoiceId: string, options?: { + intervalMs?: number; + timeoutMs?: number; + }): Promise; + private toValidateResponse; + private toMachineResponse; + private get; + private post; + private request; +} + +/** Hash a raw fingerprint string to the 32-byte form embedded in keys. */ +declare function hashFingerprint(raw: string): Uint8Array; + +/** All errors thrown by this library inherit from `LicensingError`. */ +declare class LicensingError extends Error { + /** + * Machine-readable reason code. Common values: + * `"bad_format"`, `"bad_encoding"`, `"bad_version"`, `"bad_signature"`, + * `"expired"`, `"server_error"`, `"http_error"`, `"other"`. + */ + readonly code: string; + constructor(code: string, message: string); +} + +export { Client, FLAG_FINGERPRINT_BOUND, FLAG_TRIAL, KEY_PREFIX, KEY_VERSION, KEY_VERSION_V1, KEY_VERSION_V2, type LicenseKey, type LicensePayload, LicensingError, type MachineResponse, type PollResponse, PublicKey, type PurchaseSession, type RedeemFreeOptions, type RedeemFreeResponse, type StartPurchaseOptions, type ValidateOptions, type ValidateResponse, Verifier, type VerifyOk, hasEntitlement, hashFingerprint, isExpiredAt, parseLicenseKey }; diff --git a/vendor/keysat-licensing-client/dist/index.js b/vendor/keysat-licensing-client/dist/index.js new file mode 100644 index 0000000..f24377c --- /dev/null +++ b/vendor/keysat-licensing-client/dist/index.js @@ -0,0 +1,503 @@ +// src/verify.ts +import * as ed from "@noble/ed25519"; +import { sha512 } from "@noble/hashes/sha512"; + +// src/errors.ts +var LicensingError = class extends Error { + /** + * Machine-readable reason code. Common values: + * `"bad_format"`, `"bad_encoding"`, `"bad_version"`, `"bad_signature"`, + * `"expired"`, `"server_error"`, `"http_error"`, `"other"`. + */ + code; + constructor(code, message) { + super(message); + this.name = "LicensingError"; + this.code = code; + } +}; + +// src/base32.ts +var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; +var DECODE_TABLE = (() => { + const t = {}; + for (let i = 0; i < ALPHABET.length; i++) t[ALPHABET[i]] = i; + return t; +})(); +function decodeBase32NoPad(input) { + const up = input.toUpperCase(); + const out = new Uint8Array(Math.floor(up.length * 5 / 8)); + let bits = 0; + let value = 0; + let outPos = 0; + for (let i = 0; i < up.length; i++) { + const ch = up[i]; + const v = DECODE_TABLE[ch]; + if (v === void 0) { + throw new LicensingError("bad_encoding", `invalid base32 character '${ch}'`); + } + value = value << 5 | v; + bits += 5; + if (bits >= 8) { + bits -= 8; + out[outPos++] = value >> bits & 255; + } + } + return out.subarray(0, outPos); +} + +// src/key.ts +var KEY_PREFIX = "LIC1"; +var KEY_VERSION_V1 = 1; +var KEY_VERSION_V2 = 2; +var KEY_VERSION = KEY_VERSION_V2; +var FLAG_FINGERPRINT_BOUND = 1; +var FLAG_TRIAL = 2; +var PAYLOAD_V1_LEN = 74; +var PAYLOAD_V2_HEAD_LEN = 83; +var SIGNATURE_LEN = 64; +function isExpiredAt(payload, nowUnixSeconds) { + return payload.expiresAt !== 0 && nowUnixSeconds >= payload.expiresAt; +} +function hasEntitlement(payload, slug) { + return payload.entitlements.includes(slug); +} +function parseLicenseKey(raw) { + const trimmed = raw.trim(); + const firstDash = trimmed.indexOf("-"); + if (firstDash < 0) throw new LicensingError("bad_format", "key is missing prefix delimiter"); + const prefix = trimmed.slice(0, firstDash); + if (prefix !== KEY_PREFIX) throw new LicensingError("bad_format", `unknown key prefix '${prefix}'`); + const body = trimmed.slice(firstDash + 1); + const lastDash = body.lastIndexOf("-"); + if (lastDash < 0) throw new LicensingError("bad_format", "key is missing signature delimiter"); + const payloadB32 = body.slice(0, lastDash); + const signatureB32 = body.slice(lastDash + 1); + const payloadBytes = decodeBase32NoPad(payloadB32); + const signature = decodeBase32NoPad(signatureB32); + if (signature.length !== SIGNATURE_LEN) { + throw new LicensingError( + "bad_format", + `signature is ${signature.length} bytes; expected ${SIGNATURE_LEN}` + ); + } + if (payloadBytes.length < 1) { + throw new LicensingError("bad_format", "empty payload"); + } + const version = payloadBytes[0]; + let payload; + switch (version) { + case KEY_VERSION_V1: + payload = parseV1(payloadBytes); + break; + case KEY_VERSION_V2: + payload = parseV2(payloadBytes); + break; + default: + throw new LicensingError("bad_version", `unsupported key version ${version}`); + } + return { + payload, + signedBytes: payloadBytes, + signature + }; +} +function parseV1(payloadBytes) { + if (payloadBytes.length !== PAYLOAD_V1_LEN) { + throw new LicensingError( + "bad_format", + `v1 payload is ${payloadBytes.length} bytes; expected ${PAYLOAD_V1_LEN}` + ); + } + const flags = payloadBytes[1]; + const productId = payloadBytes.slice(2, 18); + const licenseId = payloadBytes.slice(18, 34); + const issuedAt = readBigEndianI64(payloadBytes, 34); + const fingerprintHash = payloadBytes.slice(42, 74); + return { + version: KEY_VERSION_V1, + flags, + productId, + licenseId, + issuedAt, + expiresAt: 0, + fingerprintHash, + entitlements: [], + productUuid: uuidString(productId), + licenseUuid: uuidString(licenseId), + isFingerprintBound: (flags & FLAG_FINGERPRINT_BOUND) !== 0, + isTrial: (flags & FLAG_TRIAL) !== 0 + }; +} +function parseV2(payloadBytes) { + if (payloadBytes.length < PAYLOAD_V2_HEAD_LEN) { + throw new LicensingError( + "bad_format", + `v2 payload is ${payloadBytes.length} bytes; expected >= ${PAYLOAD_V2_HEAD_LEN}` + ); + } + const flags = payloadBytes[1]; + const productId = payloadBytes.slice(2, 18); + const licenseId = payloadBytes.slice(18, 34); + const issuedAt = readBigEndianI64(payloadBytes, 34); + const expiresAt = readBigEndianI64(payloadBytes, 42); + const fingerprintHash = payloadBytes.slice(50, 82); + const numEntitlements = payloadBytes[82]; + const entitlements = []; + let cursor = PAYLOAD_V2_HEAD_LEN; + const decoder = new TextDecoder("utf-8", { fatal: true }); + for (let i = 0; i < numEntitlements; i++) { + if (cursor >= payloadBytes.length) { + throw new LicensingError("bad_format", "truncated entitlement list"); + } + const len = payloadBytes[cursor]; + cursor += 1; + if (cursor + len > payloadBytes.length) { + throw new LicensingError("bad_format", "truncated entitlement"); + } + try { + entitlements.push(decoder.decode(payloadBytes.slice(cursor, cursor + len))); + } catch { + throw new LicensingError("bad_format", "entitlement not utf-8"); + } + cursor += len; + } + if (cursor !== payloadBytes.length) { + throw new LicensingError("bad_format", "trailing bytes in payload"); + } + return { + version: KEY_VERSION_V2, + flags, + productId, + licenseId, + issuedAt, + expiresAt, + fingerprintHash, + entitlements, + productUuid: uuidString(productId), + licenseUuid: uuidString(licenseId), + isFingerprintBound: (flags & FLAG_FINGERPRINT_BOUND) !== 0, + isTrial: (flags & FLAG_TRIAL) !== 0 + }; +} +function readBigEndianI64(buf, offset) { + const view = new DataView(buf.buffer, buf.byteOffset + offset, 8); + const hi = view.getInt32(0, false); + const lo = view.getUint32(4, false); + return hi * 2 ** 32 + lo; +} +function uuidString(b) { + const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join(""); + return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`; +} + +// src/fingerprint.ts +import { sha256 } from "@noble/hashes/sha256"; +function hashFingerprint(raw) { + return sha256(new TextEncoder().encode(raw)); +} + +// src/verify.ts +ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m)); +var Verifier = class { + pubkey; + constructor(pubkey) { + this.pubkey = pubkey; + } + /** Verify a license key string. Throws on any failure. */ + verify(keyStr) { + const key = parseLicenseKey(keyStr); + const ok = ed.verify(key.signature, key.signedBytes, this.pubkey.raw); + if (!ok) throw new LicensingError("bad_signature", "signature did not verify"); + return { + payload: key.payload, + licenseId: key.payload.licenseUuid, + productId: key.payload.productUuid + }; + } + /** + * Verify AND enforce that, if the key is fingerprint-bound, the given + * fingerprint matches. If the key is not bound, the fingerprint is + * ignored. Throws on any failure. + */ + verifyWithFingerprint(keyStr, fingerprint) { + const result = this.verify(keyStr); + if (result.payload.isFingerprintBound) { + const expected = hashFingerprint(fingerprint); + const stored = result.payload.fingerprintHash; + if (!equalBytes(expected, stored)) { + throw new LicensingError("bad_signature", "fingerprint does not match bound key"); + } + } + return result; + } + /** + * Verify a key and additionally reject it with an `expired` error if + * `nowUnixSeconds` is at or past its `expiresAt`. Perpetual keys + * (`expiresAt === 0`) are accepted regardless of `nowUnixSeconds`. This is + * offline-only — no grace window logic; use `Client.validate` for that. + */ + verifyWithTime(keyStr, nowUnixSeconds) { + const result = this.verify(keyStr); + if (isExpiredAt(result.payload, nowUnixSeconds)) { + throw new LicensingError("expired", "license has expired"); + } + return result; + } +}; +function equalBytes(a, b) { + if (a.length !== b.length) return false; + let diff = 0; + for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i]; + return diff === 0; +} + +// src/online.ts +var Client = class { + base; + constructor(baseUrl) { + this.base = baseUrl.replace(/\/+$/, ""); + } + /** The normalized base URL this client is pinned to. */ + baseUrl() { + return this.base; + } + /** Fetch the server's PEM-encoded public key. */ + async fetchPubkeyPem() { + const data = await this.get("/v1/pubkey"); + return data.public_key_pem; + } + /** + * Server-authoritative validation. Returns the full response including + * expiry / entitlements / seat fields introduced in v2. + * + * Two-argument form kept for call-site compatibility with earlier SDK + * versions; pass an options object for the full set of fields. + */ + async validate(key, productSlugOrOptions, fingerprint) { + const opts = typeof productSlugOrOptions === "string" ? { productSlug: productSlugOrOptions, fingerprint } : productSlugOrOptions ?? {}; + const raw = await this.post("/v1/validate", { + key, + product_slug: opts.productSlug, + fingerprint: opts.fingerprint, + hostname: opts.hostname, + platform: opts.platform + }); + return this.toValidateResponse(raw); + } + /** Lightweight heartbeat. Server updates `last_heartbeat_at`. */ + async heartbeat(key, fingerprint) { + const raw = await this.post("/v1/machines/heartbeat", { + key, + fingerprint + }); + return this.toMachineResponse(raw); + } + /** Explicitly activate a seat for the given fingerprint. */ + async activate(key, fingerprint, opts = {}) { + const raw = await this.post("/v1/machines/activate", { + key, + fingerprint, + hostname: opts.hostname, + platform: opts.platform + }); + return this.toMachineResponse(raw); + } + /** Free a seat held by the given fingerprint. */ + async deactivate(key, fingerprint, reason) { + const raw = await this.post("/v1/machines/deactivate", { + key, + fingerprint, + reason + }); + return this.toMachineResponse(raw); + } + /** Start a purchase. Returns the checkout URL and invoice id. */ + async startPurchase(productSlug, opts = {}) { + const raw = await this.post("/v1/purchase", { + product: productSlug, + buyer_email: opts.buyerEmail, + buyer_note: opts.buyerNote, + redirect_url: opts.redirectUrl, + code: opts.code + }); + return { + invoiceId: raw.invoice_id, + btcpayInvoiceId: raw.btcpay_invoice_id, + checkoutUrl: raw.checkout_url, + amountSats: raw.amount_sats, + pollUrl: raw.poll_url + }; + } + /** + * Redeem a `free_license` code: bypass BTCPay entirely and receive the + * signed license key directly. Throws if the code is unknown / disabled + * / expired / wrong product / not a free_license code, or if the cap + * has been reached. + */ + async redeemFreeLicense(productSlug, code, opts = {}) { + const raw = await this.post("/v1/redeem", { + product: productSlug, + code, + buyer_email: opts.buyerEmail, + buyer_note: opts.buyerNote + }); + return { + licenseId: raw.license_id, + licenseKey: raw.license_key, + invoiceId: raw.invoice_id, + redemptionId: raw.redemption_id + }; + } + /** Poll a purchase by its invoice id. */ + async pollPurchase(invoiceId) { + const raw = await this.get( + `/v1/purchase/${encodeURIComponent(invoiceId)}` + ); + return { + invoiceId: raw.invoice_id, + status: raw.status, + productId: raw.product_id, + amountSats: raw.amount_sats, + licenseKey: raw.license_key ?? void 0, + licenseId: raw.license_id ?? void 0 + }; + } + /** + * Convenience: open the checkout, poll until a license key is issued, + * then return it. Suitable for CLI usage or for an app UI that shows a + * spinner while the buyer pays. + */ + async waitForLicense(invoiceId, options = {}) { + const interval = options.intervalMs ?? 5e3; + const deadline = options.timeoutMs ? Date.now() + options.timeoutMs : Infinity; + while (true) { + const poll = await this.pollPurchase(invoiceId); + if (poll.licenseKey) return poll.licenseKey; + if (poll.status === "expired" || poll.status === "invalid") { + throw new LicensingError("server_error", `invoice ended in status ${poll.status}`); + } + if (Date.now() > deadline) { + throw new LicensingError("server_error", "timed out waiting for license issuance"); + } + await sleep(interval); + } + } + // --- internals --- + toValidateResponse(raw) { + const entitlements = Array.isArray(raw.entitlements) ? raw.entitlements.filter((x) => typeof x === "string") : void 0; + return { + ok: !!raw.ok, + reason: raw.reason, + licenseId: raw.license_id, + productId: raw.product_id, + productSlug: raw.product_slug, + issuedAt: raw.issued_at, + expiresAt: raw.expires_at, + graceUntil: raw.grace_until, + inGracePeriod: raw.in_grace_period, + isTrial: raw.is_trial, + entitlements, + status: raw.status, + machineId: raw.machine_id, + maxMachines: raw.max_machines + }; + } + toMachineResponse(raw) { + return { + ok: !!raw.ok, + reason: raw.reason, + machineId: raw.machine_id, + activeCount: raw.active_count, + maxMachines: raw.max_machines + }; + } + async get(path) { + return this.request(path, { method: "GET" }); + } + async post(path, body) { + return this.request(path, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body) + }); + } + async request(path, init) { + let resp; + try { + resp = await fetch(`${this.base}${path}`, init); + } catch (e) { + throw new LicensingError("http_error", e instanceof Error ? e.message : String(e)); + } + const text = await resp.text(); + if (!resp.ok) { + throw new LicensingError("server_error", `HTTP ${resp.status}: ${text}`); + } + try { + return JSON.parse(text); + } catch { + throw new LicensingError("server_error", `non-JSON response: ${text}`); + } + } +}; +function sleep(ms) { + return new Promise((res) => setTimeout(res, ms)); +} + +// src/pubkey.ts +var PublicKey = class _PublicKey { + /** Raw 32-byte Ed25519 public key material. */ + raw; + constructor(raw) { + if (raw.length !== 32) { + throw new LicensingError( + "bad_format", + `public key must be 32 bytes; got ${raw.length}` + ); + } + this.raw = raw; + } + /** Parse a PEM blob as emitted by the service. */ + static fromPem(pem) { + const stripped = pem.replace(/-----BEGIN [^-]+-----/g, "").replace(/-----END [^-]+-----/g, "").replace(/\s+/g, ""); + if (!stripped) { + throw new LicensingError("bad_format", "empty PEM input"); + } + const der = base64Decode(stripped); + if (der.length < 32) { + throw new LicensingError("bad_format", "PEM body too short to contain a public key"); + } + const raw = der.slice(der.length - 32); + return new _PublicKey(raw); + } + /** Construct from raw bytes (no PEM envelope). */ + static fromBytes(bytes) { + return new _PublicKey(bytes); + } +}; +function base64Decode(b64) { + const nodeBuffer = globalThis.Buffer; + if (nodeBuffer) { + return new Uint8Array(nodeBuffer.from(b64, "base64")); + } + const bin = atob(b64); + const out = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i); + return out; +} +export { + Client, + FLAG_FINGERPRINT_BOUND, + FLAG_TRIAL, + KEY_PREFIX, + KEY_VERSION, + KEY_VERSION_V1, + KEY_VERSION_V2, + LicensingError, + PublicKey, + Verifier, + hasEntitlement, + hashFingerprint, + isExpiredAt, + parseLicenseKey +}; diff --git a/vendor/keysat-licensing-client/package.json b/vendor/keysat-licensing-client/package.json new file mode 100644 index 0000000..1322f0f --- /dev/null +++ b/vendor/keysat-licensing-client/package.json @@ -0,0 +1,33 @@ +{ + "name": "@keysat/licensing-client", + "version": "0.1.0", + "description": "Client library for Keysat. Verifies signed license keys offline and wraps the HTTP API for purchase and revocation checks.", + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": ["dist", "README.md", "LICENSE"], + "scripts": {}, + "keywords": [ + "bitcoin", + "licensing", + "btcpay", + "start9", + "ed25519" + ], + "repository": "https://github.com/keysat-xyz/keysat-client-ts", + "license": "MIT", + "engines": { "node": ">=18" }, + "dependencies": { + "@noble/ed25519": "^2.0.0", + "@noble/hashes": "^1.3.3" + }, + "devDependencies": {} +}