From 968c5b628307e8f11ef765568f27662cf9cdc9fd Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 11 Aug 2026 11:16:09 -0500 Subject: [PATCH 1/2] Remove the MQTT subsystem Ingest has not processed a packet since 2024-07-15. The topic parser sliced at a fixed offset (`topic.substring(8)`), which only matched the pre-2.3 layout, so every region-prefixed topic fell through to "Unknown topic". With no connect or error handler on the client, a broker that never matched looked identical to one that was working. Meanwhile GET /mqtt and the gatewayStream RPC served the Gateway table unauthenticated, latitude and longitude included at full precision. Those coordinates were never the gateway's own position: the row is keyed on packet.gatewayId, but the coordinates came from the POSITION_APP payload of the relayed packet, whose origin is packet.packet.from. Each row held the last known position of some other node the gateway happened to forward -- nodes whose owners never chose to publish anything -- and there is no retention logic anywhere in the repo. Rather than repair ingest and resume collecting that data, remove the subsystem: - delete the ingest, the /mqtt route and the gatewayStream service - drop the Gateway and Channel models, the only two in the schema, and with them the ConnectRPC layer and the gateway protobufs that existed solely to serve them - remove nine now-unused dependencies Two migrations run on deploy: the first nulls the stored coordinates, the second drops both tables. The purge is redundant once the drop succeeds and is kept deliberately, because Prisma halts on the first failing migration -- if the DROP fails, the coordinates have already been cleared. Prisma is retained for this release only, as the mechanism that runs `prisma migrate deploy` from the start script; removing it in the same change would mean the drop never reaches production. The Gateway and Channel models stay in the schema alongside it, because `prisma generate` exits 1 on a model-less schema and the build runs it -- so for one release the schema deliberately describes two tables that no longer exist. Nothing reads them; no code imports the generated client. Follow-up steps are noted in schema.prisma. Also replaces the hardcoded gist in the updater route with UPDATER_MANIFEST_URL, and returns 502 rather than throwing out of the handler when the manifest source is unreachable. --- .env.example | 5 +- buf.yaml | 11 - package.json | 8 - pnpm-lock.yaml | 535 ------------------ .../migration.sql | 14 + .../migration.sql | 13 + prisma/schema.prisma | 18 + protobufs/gateway/v1/gateway.proto | 34 -- protobufs/gateway/v1/gateway_service.proto | 14 - src/index.ts | 16 - src/lib/index.ts | 2 - src/lib/mqtt.ts | 191 ------- src/lib/prisma.ts | 5 - src/routes/index.ts | 1 - src/routes/mqtt.ts | 23 - src/routes/updater.ts | 43 +- src/services/gateway.ts | 56 -- src/services/index.ts | 1 - src/utils/yieldFromEvent.ts | 27 - 19 files changed, 84 insertions(+), 933 deletions(-) delete mode 100644 buf.yaml create mode 100644 prisma/migrations/20260811000000_purge_gateway_coordinates/migration.sql create mode 100644 prisma/migrations/20260811000100_drop_mqtt_tables/migration.sql delete mode 100644 protobufs/gateway/v1/gateway.proto delete mode 100644 protobufs/gateway/v1/gateway_service.proto delete mode 100644 src/lib/mqtt.ts delete mode 100644 src/lib/prisma.ts delete mode 100644 src/routes/mqtt.ts delete mode 100644 src/services/gateway.ts delete mode 100644 src/services/index.ts delete mode 100644 src/utils/yieldFromEvent.ts diff --git a/.env.example b/.env.example index 0a7ccb0..e8b9d64 100644 --- a/.env.example +++ b/.env.example @@ -6,7 +6,4 @@ GH_APP_CLIENT_ID= GH_APP_CLIENT_SECRET= REDIS_URL= DATABASE_URL= -MQTT_URL= -MQTT_USERNAME= -MQTT_PASSWORD= -MQTT_ROOT_TOPIC= \ No newline at end of file +UPDATER_MANIFEST_URL= \ No newline at end of file diff --git a/buf.yaml b/buf.yaml deleted file mode 100644 index 8e0e29c..0000000 --- a/buf.yaml +++ /dev/null @@ -1,11 +0,0 @@ -version: v1 -name: buf.build/meshtastic/api -deps: [] -build: - excludes: [] -breaking: - use: - - FILE -lint: - use: - - MINIMAL \ No newline at end of file diff --git a/package.json b/package.json index 62531d9..4ca32e7 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,6 @@ "build": "prisma generate && tsc" }, "dependencies": { - "@buf/meshtastic_api.bufbuild_es": "1.7.2-20240110092216-3a147af14302.1", - "@buf/meshtastic_api.connectrpc_es": "1.3.0-20240110092216-3a147af14302.1", - "@bufbuild/protobuf": "^1.7.2", - "@connectrpc/connect": "^1.3.0", - "@connectrpc/connect-express": "^1.3.0", - "@meshtastic/js": "2.2.23-0", "@octokit/auth-app": "^6.0.3", "@prisma/client": "5.22.0", "@tinyhttp/app": "2.2.3", @@ -25,11 +19,9 @@ "@tinyhttp/favicon": "^3.0.0", "@tinyhttp/logger": "^2.0.0", "add": "^2.0.6", - "mqtt": "^5.3.5", "octokit": "^3.1.2", "pnpm": "^8.15.3", "redis": "^4.6.13", - "sub-events": "^1.9.0", "ts-pattern": "^5.0.8", "typescript": "^5.3.3" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 28bc99e..410e15d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,24 +8,6 @@ importers: .: dependencies: - '@buf/meshtastic_api.bufbuild_es': - specifier: 1.7.2-20240110092216-3a147af14302.1 - version: 1.7.2-20240110092216-3a147af14302.1(@bufbuild/protobuf@1.7.2) - '@buf/meshtastic_api.connectrpc_es': - specifier: 1.3.0-20240110092216-3a147af14302.1 - version: 1.3.0-20240110092216-3a147af14302.1(@bufbuild/protobuf@1.7.2)(@connectrpc/connect@1.3.0(@bufbuild/protobuf@1.7.2)) - '@bufbuild/protobuf': - specifier: ^1.7.2 - version: 1.7.2 - '@connectrpc/connect': - specifier: ^1.3.0 - version: 1.3.0(@bufbuild/protobuf@1.7.2) - '@connectrpc/connect-express': - specifier: ^1.3.0 - version: 1.3.0(@bufbuild/protobuf@1.7.2)(@connectrpc/connect-node@1.3.0(@bufbuild/protobuf@1.7.2)(@connectrpc/connect@1.3.0(@bufbuild/protobuf@1.7.2)))(@connectrpc/connect@1.3.0(@bufbuild/protobuf@1.7.2)) - '@meshtastic/js': - specifier: 2.2.23-0 - version: 2.2.23-0(buffer@6.0.3) '@octokit/auth-app': specifier: ^6.0.3 version: 6.0.3 @@ -50,9 +32,6 @@ importers: add: specifier: ^2.0.6 version: 2.0.6 - mqtt: - specifier: ^5.3.5 - version: 5.3.5 octokit: specifier: ^3.1.2 version: 3.1.2 @@ -62,9 +41,6 @@ importers: redis: specifier: ^4.6.13 version: 4.6.13 - sub-events: - specifier: ^1.9.0 - version: 1.9.0 ts-pattern: specifier: ^5.0.8 version: 5.0.8 @@ -90,10 +66,6 @@ importers: packages: - '@babel/runtime@7.23.9': - resolution: {integrity: sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==} - engines: {node: '>=6.9.0'} - '@biomejs/biome@2.1.3': resolution: {integrity: sha512-KE/tegvJIxTkl7gJbGWSgun7G6X/n2M6C35COT6ctYrAy7SiPyNvi6JtoQERVK/VRbttZfgGq96j2bFmhmnH4w==} engines: {node: '>=14.21.3'} @@ -147,21 +119,6 @@ packages: cpu: [x64] os: [win32] - '@buf/meshtastic_api.bufbuild_es@1.6.0-20240110092216-3a147af14302.1': - resolution: {tarball: https://buf.build/gen/npm/v1/@buf/meshtastic_api.bufbuild_es/-/meshtastic_api.bufbuild_es-1.6.0-20240110092216-3a147af14302.1.tgz} - peerDependencies: - '@bufbuild/protobuf': ^1.6.0 - - '@buf/meshtastic_api.bufbuild_es@1.7.2-20240110092216-3a147af14302.1': - resolution: {tarball: https://buf.build/gen/npm/v1/@buf/meshtastic_api.bufbuild_es/-/meshtastic_api.bufbuild_es-1.7.2-20240110092216-3a147af14302.1.tgz} - peerDependencies: - '@bufbuild/protobuf': ^1.7.2 - - '@buf/meshtastic_api.connectrpc_es@1.3.0-20240110092216-3a147af14302.1': - resolution: {tarball: https://buf.build/gen/npm/v1/@buf/meshtastic_api.connectrpc_es/-/meshtastic_api.connectrpc_es-1.3.0-20240110092216-3a147af14302.1.tgz} - peerDependencies: - '@connectrpc/connect': ^1.3.0 - '@buf/meshtastic_protobufs.bufbuild_es@1.7.2-20240216123215-6b07c41c68c9.1': resolution: {tarball: https://buf.build/gen/npm/v1/@buf/meshtastic_protobufs.bufbuild_es/-/meshtastic_protobufs.bufbuild_es-1.7.2-20240216123215-6b07c41c68c9.1.tgz} peerDependencies: @@ -170,26 +127,6 @@ packages: '@bufbuild/protobuf@1.7.2': resolution: {integrity: sha512-i5GE2Dk5ekdlK1TR7SugY4LWRrKSfb5T1Qn4unpIMbfxoeGKERKQ59HG3iYewacGD10SR7UzevfPnh6my4tNmQ==} - '@connectrpc/connect-express@1.3.0': - resolution: {integrity: sha512-6wbaQheD9cb4DnU1PvgVQdB1XPfA0bhlA0V0ZKx6oJJnTgGEYBzPrQztmqs5XW36/r+qJRfMgaVzZfX8MLafgA==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@bufbuild/protobuf': ^1.4.2 - '@connectrpc/connect': 1.3.0 - '@connectrpc/connect-node': 1.3.0 - - '@connectrpc/connect-node@1.3.0': - resolution: {integrity: sha512-2fV/z/8MUFOkTn2Gbm7T/qvRfkpt/D/w7ykYqACZRH6VNG/faY4lH2wUZiNkwv9tzTrECKOJFyPsaGs3nRYK3w==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@bufbuild/protobuf': ^1.4.2 - '@connectrpc/connect': 1.3.0 - - '@connectrpc/connect@1.3.0': - resolution: {integrity: sha512-kTeWxJnLLtxKc2ZSDN0rIBgwfP8RwcLknthX4AKlIAmN9ZC4gGnCbwp+3BKcP/WH5c8zGBAWqSY3zeqCM+ah7w==} - peerDependencies: - '@bufbuild/protobuf': ^1.4.2 - '@esbuild/aix-ppc64@0.19.12': resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} engines: {node: '>=12'} @@ -328,14 +265,6 @@ packages: cpu: [x64] os: [win32] - '@fastify/busboy@2.1.1': - resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} - engines: {node: '>=14'} - - '@meshtastic/js@2.2.23-0': - resolution: {integrity: sha512-67L2xcgqIa+INuZBuMNikCNBLWvtOmGJIMDOsZx84pw6MLYAKAoX93SG0qKOvWHfsDyThBxjHzm2lAje8xC9/g==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - '@octokit/app@14.0.2': resolution: {integrity: sha512-NCSCktSx+XmjuSUVn2dLfqQ9WIYePGP95SDJs4I9cn/0ZkeXcPkaoCLl64Us3dRKL2ozC7hArwze5Eu+/qt1tg==} engines: {node: '>= 18'} @@ -583,58 +512,15 @@ packages: '@types/aws-lambda@8.10.130': resolution: {integrity: sha512-HxTfLeGvD1wTJqIGwcBCpNmHKenja+We1e0cuzeIDFfbEj3ixnlTInyPR/81zAe0Ss/Ip12rFK6XNeMLVucOSg==} - '@types/body-parser@1.19.5': - resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} - '@types/btoa-lite@1.0.2': resolution: {integrity: sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==} - '@types/connect@3.4.38': - resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - - '@types/express-serve-static-core@4.17.41': - resolution: {integrity: sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==} - - '@types/express@4.17.21': - resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} - - '@types/http-errors@2.0.4': - resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} - '@types/jsonwebtoken@9.0.5': resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} - '@types/mime@1.3.5': - resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - - '@types/mime@3.0.4': - resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==} - '@types/node@20.11.19': resolution: {integrity: sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==} - '@types/qs@6.9.11': - resolution: {integrity: sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==} - - '@types/range-parser@1.2.7': - resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - - '@types/readable-stream@4.0.10': - resolution: {integrity: sha512-AbUKBjcC8SHmImNi4yK2bbjogQlkFSg7shZCcicxPQapniOlajG8GCc39lvXzCWX4lLRRs7DM3VAeSlqmEVZUA==} - - '@types/send@0.17.4': - resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} - - '@types/serve-static@1.15.5': - resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==} - - '@types/ws@8.5.10': - resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} - - abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - add@2.0.6: resolution: {integrity: sha512-j5QzrmsokwWWp6kUcJQySpbG+xfOBqqKnup3OIk1pz+kB/80SLorZ9V8zHFLO92Lcd+hbvq8bT+zOGoPkmBV0Q==} @@ -642,15 +528,9 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - before-after-hook@2.2.3: resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} - bl@6.0.10: - resolution: {integrity: sha512-F14DFhDZfxtVm2FY0k9kG2lWAwzZkO9+jX3Ytuoy/V0E1/5LBuBzzQHXAjqpxXEDIpmTPZZf5GVIGPQcLxFpaA==} - bottleneck@2.19.5: resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} @@ -660,12 +540,6 @@ packages: buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -677,34 +551,9 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - commist@3.2.0: - resolution: {integrity: sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==} - - concat-stream@2.0.0: - resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} - engines: {'0': node >= 6.0} - - crc@4.3.2: - resolution: {integrity: sha512-uGDHf4KLLh2zsHa8D8hIQ1H/HtFQhyHrc0uhHBcoKGol/Xnb+MPYfUMw7cvON6ze/GUESTudKayDcJC5HnJv1A==} - engines: {node: '>=12'} - peerDependencies: - buffer: '>=6.0.3' - peerDependenciesMeta: - buffer: - optional: true - dayjs@1.11.10: resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} - debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - deprecation@2.3.1: resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} @@ -728,18 +577,6 @@ packages: engines: {node: '>=12'} hasBin: true - event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - - fast-unique-numbers@8.0.13: - resolution: {integrity: sha512-7OnTFAVPefgw2eBJ1xj2PGGR9FwYzSUso9decayHgCDX4sJkHLdcsYTytTg+tYv+wKF3U8gJuSBz2jJpQV4u/g==} - engines: {node: '>=16.1.0'} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -756,29 +593,17 @@ packages: resolution: {integrity: sha512-B9zCFt3jH8g09LR1vHL4pcAn8yMEtlSlOUdQemzHMRKMImNIhhszdeosYFfNW0WXKQtXIlWB+O4owHJKvEJYaA==} engines: {node: '>=12.22.0'} - help-me@5.0.0: - resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} - http-status-emojis@2.2.0: resolution: {integrity: sha512-ompKtgwpx8ff0hsbpIB7oE4ax1LXoHmftsHHStMELX56ivG3GhofTX8ZHWlUaFKfGjcGjw6G3rPk7dJRXMmbbg==} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ipaddr.js@2.1.0: resolution: {integrity: sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==} engines: {node: '>= 10'} - js-sdsl@4.3.0: - resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} - jsonwebtoken@9.0.2: resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} engines: {node: '>=12', npm: '>=6'} @@ -814,10 +639,6 @@ packages: resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==} engines: {node: 14 || >=16.14} - lru-cache@10.2.0: - resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} - engines: {node: 14 || >=16.14} - lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -827,20 +648,6 @@ packages: engines: {node: '>=16'} hasBin: true - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - mqtt-packet@9.0.0: - resolution: {integrity: sha512-8v+HkX+fwbodsWAZIZTI074XIoxVBOmPeggQuDFCGg1SqNcC+uoRMWu7J6QlJPqIUIJXmjNYYHxBBLr1Y/Df4w==} - - mqtt@5.3.5: - resolution: {integrity: sha512-xd7qt/LEM721U6yHQcqjlaAKXL1Fsqf/MXq6C2WPi/6OXK2jdSzL1eZ7ZUgjea7IY2yFLRWD5LNdT1mL0arPoA==} - engines: {node: '>=16.0.0'} - hasBin: true - - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -848,9 +655,6 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} - number-allocator@1.0.14: - resolution: {integrity: sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==} - octokit@3.1.2: resolution: {integrity: sha512-MG5qmrTL5y8KYwFgE1A4JWmgfQBaIETE/lOlfwNYx1QOtCQHGVxkRJmdUJltFc1HVn73d61TlMhMyNTOtMl+ng==} engines: {node: '>= 18'} @@ -868,43 +672,16 @@ packages: engines: {node: '>=16.13'} hasBin: true - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - readable-stream@4.5.2: - resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - redis@4.6.13: resolution: {integrity: sha512-MHgkS4B+sPjCXpf+HfdetBwbRz6vCtsceTmw1pHNYJAsYxrfpOP6dz+piJWGos8wqG7qb3vj/Rrc5qOlmInUuA==} - regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - regexparam@2.0.2: resolution: {integrity: sha512-A1PeDEYMrkLrfyOwv2jwihXbo9qxdGD3atBYQA9JJgreAx8/7rC6IUkWOw2NQlOxLp2wL0ifQbh1HuidDfYA6w==} engines: {node: '>=8'} - reinterval@1.1.0: - resolution: {integrity: sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==} - resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - rfdc@1.3.1: - resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==} - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -913,35 +690,14 @@ packages: engines: {node: '>=10'} hasBin: true - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - sub-events@1.9.0: - resolution: {integrity: sha512-dnFBayilG9Ku0k/lNs1Y7WV4kv91+ovCoeBV3uIYrY49DylvBb6z9d9ED2ctcrvX2YlReFalpCgJNtSgmrOaJg==} - engines: {node: '>=10.0.0'} - ts-pattern@5.0.8: resolution: {integrity: sha512-aafbuAQOTEeWmA7wtcL94w6I89EgLD7F+IlWkr596wYxeb0oveWDO5dQpv85YP0CGbxXT/qXBIeV6IYLcoZ2uA==} - tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - - tslog@4.9.2: - resolution: {integrity: sha512-wBM+LRJoNl34Bdu8mYEFxpvmOUedpNUwMNQB/NcuPIZKwdDde6xLHUev3bBjXQU7gdurX++X/YE7gLH8eXYsiQ==} - engines: {node: '>=16'} - tsx@4.7.1: resolution: {integrity: sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==} engines: {node: '>=18.0.0'} hasBin: true - typedarray@0.0.6: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typescript@5.3.3: resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} engines: {node: '>=14.17'} @@ -950,52 +706,20 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici@5.29.0: - resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} - engines: {node: '>=14.0'} - universal-github-app-jwt@1.1.2: resolution: {integrity: sha512-t1iB2FmLFE+yyJY9+3wMx0ejB+MQpEVkH0gQv7dR6FZyltyq+ZZO0uDpbopxhrZ3SLEO4dCEkIujOMldEQ2iOA==} universal-user-agent@6.0.1: resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - worker-timers-broker@6.1.1: - resolution: {integrity: sha512-CTlDnkXAewtYvw5gOwVIc6UuIPcNHJrqWxBMhZbCWOmadvl20nPs9beAsXlaTEwW3G2KBpuKiSgkhBkhl3mxDA==} - - worker-timers-worker@7.0.65: - resolution: {integrity: sha512-Dl4nGONr8A8Fr+vQnH7Ee+o2iB480S1fBcyJYqnMyMwGRVyQZLZU+o91vbMvU1vHqiryRQmjXzzMYlh86wx+YQ==} - - worker-timers@7.1.1: - resolution: {integrity: sha512-axtq83GwPqYwkQmQmei2abQ9cT7oSwmLw4lQCZ9VmMH9g4t4kuEF1Gw+tdnIJGHCiZ2QPDnr/+307bYx6tynLA==} - wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.20.1: - resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} snapshots: - '@babel/runtime@7.23.9': - dependencies: - regenerator-runtime: 0.14.1 - '@biomejs/biome@2.1.3': optionalDependencies: '@biomejs/cli-darwin-arm64': 2.1.3 @@ -1031,44 +755,12 @@ snapshots: '@biomejs/cli-win32-x64@2.1.3': optional: true - '@buf/meshtastic_api.bufbuild_es@1.6.0-20240110092216-3a147af14302.1(@bufbuild/protobuf@1.7.2)': - dependencies: - '@bufbuild/protobuf': 1.7.2 - - '@buf/meshtastic_api.bufbuild_es@1.7.2-20240110092216-3a147af14302.1(@bufbuild/protobuf@1.7.2)': - dependencies: - '@bufbuild/protobuf': 1.7.2 - - '@buf/meshtastic_api.connectrpc_es@1.3.0-20240110092216-3a147af14302.1(@bufbuild/protobuf@1.7.2)(@connectrpc/connect@1.3.0(@bufbuild/protobuf@1.7.2))': - dependencies: - '@buf/meshtastic_api.bufbuild_es': 1.6.0-20240110092216-3a147af14302.1(@bufbuild/protobuf@1.7.2) - '@connectrpc/connect': 1.3.0(@bufbuild/protobuf@1.7.2) - transitivePeerDependencies: - - '@bufbuild/protobuf' - '@buf/meshtastic_protobufs.bufbuild_es@1.7.2-20240216123215-6b07c41c68c9.1(@bufbuild/protobuf@1.7.2)': dependencies: '@bufbuild/protobuf': 1.7.2 '@bufbuild/protobuf@1.7.2': {} - '@connectrpc/connect-express@1.3.0(@bufbuild/protobuf@1.7.2)(@connectrpc/connect-node@1.3.0(@bufbuild/protobuf@1.7.2)(@connectrpc/connect@1.3.0(@bufbuild/protobuf@1.7.2)))(@connectrpc/connect@1.3.0(@bufbuild/protobuf@1.7.2))': - dependencies: - '@bufbuild/protobuf': 1.7.2 - '@connectrpc/connect': 1.3.0(@bufbuild/protobuf@1.7.2) - '@connectrpc/connect-node': 1.3.0(@bufbuild/protobuf@1.7.2)(@connectrpc/connect@1.3.0(@bufbuild/protobuf@1.7.2)) - '@types/express': 4.17.21 - - '@connectrpc/connect-node@1.3.0(@bufbuild/protobuf@1.7.2)(@connectrpc/connect@1.3.0(@bufbuild/protobuf@1.7.2))': - dependencies: - '@bufbuild/protobuf': 1.7.2 - '@connectrpc/connect': 1.3.0(@bufbuild/protobuf@1.7.2) - undici: 5.29.0 - - '@connectrpc/connect@1.3.0(@bufbuild/protobuf@1.7.2)': - dependencies: - '@bufbuild/protobuf': 1.7.2 - '@esbuild/aix-ppc64@0.19.12': optional: true @@ -1138,16 +830,6 @@ snapshots: '@esbuild/win32-x64@0.19.12': optional: true - '@fastify/busboy@2.1.1': {} - - '@meshtastic/js@2.2.23-0(buffer@6.0.3)': - dependencies: - crc: 4.3.2(buffer@6.0.3) - sub-events: 1.9.0 - tslog: 4.9.2 - transitivePeerDependencies: - - buffer - '@octokit/app@14.0.2': dependencies: '@octokit/auth-app': 6.0.3 @@ -1443,73 +1125,16 @@ snapshots: '@types/aws-lambda@8.10.130': {} - '@types/body-parser@1.19.5': - dependencies: - '@types/connect': 3.4.38 - '@types/node': 20.11.19 - '@types/btoa-lite@1.0.2': {} - '@types/connect@3.4.38': - dependencies: - '@types/node': 20.11.19 - - '@types/express-serve-static-core@4.17.41': - dependencies: - '@types/node': 20.11.19 - '@types/qs': 6.9.11 - '@types/range-parser': 1.2.7 - '@types/send': 0.17.4 - - '@types/express@4.17.21': - dependencies: - '@types/body-parser': 1.19.5 - '@types/express-serve-static-core': 4.17.41 - '@types/qs': 6.9.11 - '@types/serve-static': 1.15.5 - - '@types/http-errors@2.0.4': {} - '@types/jsonwebtoken@9.0.5': dependencies: '@types/node': 20.11.19 - '@types/mime@1.3.5': {} - - '@types/mime@3.0.4': {} - '@types/node@20.11.19': dependencies: undici-types: 5.26.5 - '@types/qs@6.9.11': {} - - '@types/range-parser@1.2.7': {} - - '@types/readable-stream@4.0.10': - dependencies: - '@types/node': 20.11.19 - safe-buffer: 5.1.2 - - '@types/send@0.17.4': - dependencies: - '@types/mime': 1.3.5 - '@types/node': 20.11.19 - - '@types/serve-static@1.15.5': - dependencies: - '@types/http-errors': 2.0.4 - '@types/mime': 3.0.4 - '@types/node': 20.11.19 - - '@types/ws@8.5.10': - dependencies: - '@types/node': 20.11.19 - - abort-controller@3.0.0: - dependencies: - event-target-shim: 5.0.1 - add@2.0.6: {} aggregate-error@3.1.0: @@ -1517,54 +1142,22 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 - base64-js@1.5.1: {} - before-after-hook@2.2.3: {} - bl@6.0.10: - dependencies: - buffer: 6.0.3 - inherits: 2.0.4 - readable-stream: 4.5.2 - bottleneck@2.19.5: {} btoa-lite@1.0.0: {} buffer-equal-constant-time@1.0.1: {} - buffer-from@1.1.2: {} - - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - clean-stack@2.2.0: {} cluster-key-slot@1.1.2: {} colorette@2.0.20: {} - commist@3.2.0: {} - - concat-stream@2.0.0: - dependencies: - buffer-from: 1.1.2 - inherits: 2.0.4 - readable-stream: 3.6.2 - typedarray: 0.0.6 - - crc@4.3.2(buffer@6.0.3): - optionalDependencies: - buffer: 6.0.3 - dayjs@1.11.10: {} - debug@4.3.4: - dependencies: - ms: 2.1.2 - deprecation@2.3.1: {} ecdsa-sig-formatter@1.0.11: @@ -1603,15 +1196,6 @@ snapshots: '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 - event-target-shim@5.0.1: {} - - events@3.3.0: {} - - fast-unique-numbers@8.0.13: - dependencies: - '@babel/runtime': 7.23.9 - tslib: 2.6.2 - fsevents@2.3.3: optional: true @@ -1623,20 +1207,12 @@ snapshots: header-range-parser@1.1.3: {} - help-me@5.0.0: {} - http-status-emojis@2.2.0: {} - ieee754@1.2.1: {} - indent-string@4.0.0: {} - inherits@2.0.4: {} - ipaddr.js@2.1.0: {} - js-sdsl@4.3.0: {} - jsonwebtoken@9.0.2: dependencies: jws: 3.2.2 @@ -1677,60 +1253,16 @@ snapshots: lru-cache@10.1.0: {} - lru-cache@10.2.0: {} - lru-cache@6.0.0: dependencies: yallist: 4.0.0 mime@4.0.1: {} - minimist@1.2.8: {} - - mqtt-packet@9.0.0: - dependencies: - bl: 6.0.10 - debug: 4.3.4 - process-nextick-args: 2.0.1 - transitivePeerDependencies: - - supports-color - - mqtt@5.3.5: - dependencies: - '@types/readable-stream': 4.0.10 - '@types/ws': 8.5.10 - commist: 3.2.0 - concat-stream: 2.0.0 - debug: 4.3.4 - help-me: 5.0.0 - lru-cache: 10.2.0 - minimist: 1.2.8 - mqtt-packet: 9.0.0 - number-allocator: 1.0.14 - readable-stream: 4.5.2 - reinterval: 1.1.0 - rfdc: 1.3.1 - split2: 4.2.0 - worker-timers: 7.1.1 - ws: 8.20.1 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - ms@2.1.2: {} - ms@2.1.3: {} negotiator@0.6.3: {} - number-allocator@1.0.14: - dependencies: - debug: 4.3.4 - js-sdsl: 4.3.0 - transitivePeerDependencies: - - supports-color - octokit@3.1.2: dependencies: '@octokit/app': 14.0.2 @@ -1756,24 +1288,6 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - process-nextick-args@2.0.1: {} - - process@0.11.10: {} - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - readable-stream@4.5.2: - dependencies: - abort-controller: 3.0.0 - buffer: 6.0.3 - events: 3.3.0 - process: 0.11.10 - string_decoder: 1.3.0 - redis@4.6.13: dependencies: '@redis/bloom': 1.2.0(@redis/client@1.5.14) @@ -1783,38 +1297,18 @@ snapshots: '@redis/search': 1.1.6(@redis/client@1.5.14) '@redis/time-series': 1.0.5(@redis/client@1.5.14) - regenerator-runtime@0.14.1: {} - regexparam@2.0.2: {} - reinterval@1.1.0: {} - resolve-pkg-maps@1.0.0: {} - rfdc@1.3.1: {} - - safe-buffer@5.1.2: {} - safe-buffer@5.2.1: {} semver@7.5.4: dependencies: lru-cache: 6.0.0 - split2@4.2.0: {} - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - sub-events@1.9.0: {} - ts-pattern@5.0.8: {} - tslib@2.6.2: {} - - tslog@4.9.2: {} - tsx@4.7.1: dependencies: esbuild: 0.19.12 @@ -1822,16 +1316,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - typedarray@0.0.6: {} - typescript@5.3.3: {} undici-types@5.26.5: {} - undici@5.29.0: - dependencies: - '@fastify/busboy': 2.1.1 - universal-github-app-jwt@1.1.2: dependencies: '@types/jsonwebtoken': 9.0.5 @@ -1839,29 +1327,6 @@ snapshots: universal-user-agent@6.0.1: {} - util-deprecate@1.0.2: {} - - worker-timers-broker@6.1.1: - dependencies: - '@babel/runtime': 7.23.9 - fast-unique-numbers: 8.0.13 - tslib: 2.6.2 - worker-timers-worker: 7.0.65 - - worker-timers-worker@7.0.65: - dependencies: - '@babel/runtime': 7.23.9 - tslib: 2.6.2 - - worker-timers@7.1.1: - dependencies: - '@babel/runtime': 7.23.9 - tslib: 2.6.2 - worker-timers-broker: 6.1.1 - worker-timers-worker: 7.0.65 - wrappy@1.0.2: {} - ws@8.20.1: {} - yallist@4.0.0: {} diff --git a/prisma/migrations/20260811000000_purge_gateway_coordinates/migration.sql b/prisma/migrations/20260811000000_purge_gateway_coordinates/migration.sql new file mode 100644 index 0000000..94e4da0 --- /dev/null +++ b/prisma/migrations/20260811000000_purge_gateway_coordinates/migration.sql @@ -0,0 +1,14 @@ +-- Purge stored gateway coordinates. +-- +-- These were never the gateway's own position. The row was keyed on +-- packet.gatewayId, but the coordinates came from the POSITION_APP payload of +-- the packet being relayed, whose origin is packet.packet.from -- so each row +-- held the last known position of some *other* node that the gateway happened +-- to forward, at full precision (degrees * 1e7, ~1cm). Every row was served +-- publicly and unauthenticated by GET /mqtt and the gatewayStream RPC. +-- +-- The following migration drops these tables outright, which makes this +-- statement redundant on a successful deploy. It is kept deliberately: Prisma +-- applies migrations in order and halts on the first failure, so if the DROP +-- fails for any reason the coordinates have already been cleared. +UPDATE "Gateway" SET "latitude" = NULL, "longitude" = NULL; diff --git a/prisma/migrations/20260811000100_drop_mqtt_tables/migration.sql b/prisma/migrations/20260811000100_drop_mqtt_tables/migration.sql new file mode 100644 index 0000000..14af201 --- /dev/null +++ b/prisma/migrations/20260811000100_drop_mqtt_tables/migration.sql @@ -0,0 +1,13 @@ +-- Drop the MQTT-derived tables. +-- +-- The MQTT ingest, the GET /mqtt endpoint and the gatewayStream RPC have all +-- been removed, so nothing writes or reads these tables. They were the only two +-- models in the schema. +-- +-- This is irreversible. It destroys the stored coordinates for good -- which is +-- the point, see the preceding migration -- and takes the per-channel message +-- counters with them. +-- +-- Channel is dropped first: it carries the foreign key to Gateway. +DROP TABLE IF EXISTS "Channel"; +DROP TABLE IF EXISTS "Gateway"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ccf58d6..8b6fa2e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,6 +1,24 @@ // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema +// The MQTT subsystem that read and wrote these models is gone, and the +// migrations in this directory drop both tables on deploy. The models are kept +// here for one release regardless, because `prisma generate` exits 1 on a schema +// with no models and this project's build runs it. Emptying the schema in the +// same change would break the build, and the drop would then never reach +// production. +// +// So between this deploy and the follow-up, the schema deliberately describes +// two tables that no longer exist. Nothing reads them -- no code imports the +// generated client. Do not run `prisma migrate dev` against a deployed database +// in that window: it would read the missing tables as drift and try to recreate +// them. +// +// FOLLOW-UP, once the drop migration has been applied: delete this file and the +// migrations directory, drop the `prisma` and `@prisma/client` dependencies and +// the `prisma generate` / `prisma migrate deploy` steps from package.json, and +// remove DATABASE_URL from the environment. + generator client { provider = "prisma-client-js" } diff --git a/protobufs/gateway/v1/gateway.proto b/protobufs/gateway/v1/gateway.proto deleted file mode 100644 index 767dc1d..0000000 --- a/protobufs/gateway/v1/gateway.proto +++ /dev/null @@ -1,34 +0,0 @@ -syntax = "proto3"; - -package meshtastic.api.gateway.v1; - -import "google/protobuf/timestamp.proto"; - -message Transaction { - enum Type { - CREATE = 0; - UPDATE = 1; - DELETE = 2; - } - - Type type = 1; -} - -message Gateway { - string id = 1; - sfixed32 latitude = 2; - sfixed32 longitude = 3; - google.protobuf.Timestamp created_at = 4; - google.protobuf.Timestamp updated_at = 5; - - repeated Channel channels = 6; -} - -message Channel { - string id = 1; - string name = 2; - bool encrypted = 3; - int32 messages_count = 4; - google.protobuf.Timestamp created_at = 5; - google.protobuf.Timestamp updated_at = 6; -} diff --git a/protobufs/gateway/v1/gateway_service.proto b/protobufs/gateway/v1/gateway_service.proto deleted file mode 100644 index b8fe7bb..0000000 --- a/protobufs/gateway/v1/gateway_service.proto +++ /dev/null @@ -1,14 +0,0 @@ -syntax = "proto3"; - -package meshtastic.api.gateway.v1; - -import "google/protobuf/empty.proto"; -import "protobufs/gateway/v1/gateway.proto"; - -service GatewayService { - rpc GatewayStream(google.protobuf.Empty) returns (stream GatewayStreamResponse) {} -} - -message GatewayStreamResponse { - Gateway gateway = 1; -} diff --git a/src/index.ts b/src/index.ts index e486fc6..fed438a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,26 +1,20 @@ -import { GatewayService } from "@buf/meshtastic_api.connectrpc_es/protobufs/gateway/v1/gateway_service_connect.js"; -import { expressConnectMiddleware } from "@connectrpc/connect-express"; import { App } from "@tinyhttp/app"; import { cors } from "@tinyhttp/cors"; import { config } from "@tinyhttp/dotenv"; import { favicon } from "@tinyhttp/favicon"; import { logger } from "@tinyhttp/logger"; -import { RegisterMqttClient } from "./lib/index.js"; import { DeviceLinksRoutes, EventFirmwareIconRoutes, EventFirmwareRoutes, FirmwareRoutes, GithubRoutes, - MqttRoutes, ResourceRoutes, UpdaterRoutes, } from "./routes/index.js"; -import { Gateway } from "./services/index.js"; export const app = new App(); config(); -RegisterMqttClient(); app .use(logger()) .use(favicon("static/favicon.ico")) @@ -67,15 +61,6 @@ app res.setHeader("content-type", "application/octet-stream"); res.send(data.body); }); - }) - .use(async (req, res, next) => { - return expressConnectMiddleware({ - routes: (router) => { - router.service(GatewayService, new Gateway()); - }, - connect: true, - // @ts-ignore - })(req, res, next); }); /** @@ -88,6 +73,5 @@ DeviceLinksRoutes(); EventFirmwareRoutes(); EventFirmwareIconRoutes(); UpdaterRoutes(); -MqttRoutes(); app.listen(Number.parseInt(process.env.PORT ?? "4000")); diff --git a/src/lib/index.ts b/src/lib/index.ts index 66d2a79..09bc526 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,5 +1,3 @@ export * as GitHub from "./github.js"; -export { RegisterMqttClient } from "./mqtt.js"; -export { prisma } from "./prisma.js"; export { redis } from "./redis.js"; export * as Hardware from "./resource.js"; diff --git a/src/lib/mqtt.ts b/src/lib/mqtt.ts deleted file mode 100644 index 5569573..0000000 --- a/src/lib/mqtt.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { Protobuf } from "@meshtastic/js"; -import MQTT from "mqtt"; -import { prisma, redis } from "./index.js"; - -const mqtt = MQTT.connect(process.env.MQTT_URL as string, { - username: process.env.MQTT_USERNAME, - password: process.env.MQTT_PASSWORD, -}); - -export const RegisterMqttClient = () => { - const queue = new MqttQueue(); - // Subscribe to all topics - mqtt.subscribe(process.env.MQTT_ROOT_TOPIC as string); - - mqtt.on("message", (topic, payload) => { - // Split topic into parts - const topicParts = topic.substring(8).split("/"); - - if (topicParts.length === 2) { - // Standard channel message - try { - const decoded = Protobuf.Mqtt.ServiceEnvelope.fromBinary(payload); - queue.push(decoded); - } catch (error) { - console.error(error, topic, payload); - } - } else { - // Likely stat message - console.log("Unknown topic", topic); - } - }); -}; - -interface QueueItem { - id: string; - latitude?: number; - longitude?: number; - channels: { - name: string; - encrypted: boolean; - messages: number; - }[]; -} - -class MqttQueue { - private queue: QueueItem[]; - private isWorking: boolean; - private lastWorked: Date; - - constructor() { - this.queue = []; - this.isWorking = false; - this.lastWorked = new Date(); - } - - private textDecoder = new TextDecoder(); - - public push(packet: Protobuf.Mqtt.ServiceEnvelope) { - if (packet.packet?.payloadVariant.case === "decoded") { - const data = packet.packet?.payloadVariant.value; - if (data.portnum === Protobuf.Portnums.PortNum.POSITION_APP) { - const position = Protobuf.Mesh.Position.fromBinary(data.payload); - if (position.latitudeI !== 0 && position.longitudeI !== 0) { - this.queue.push({ - id: packet.gatewayId, - latitude: position.latitudeI, - longitude: position.longitudeI, - channels: [], - }); - } - } else if (data.portnum === Protobuf.Portnums.PortNum.TEXT_MESSAGE_APP) { - //cache in redis - redis.set( - `mqttMessage-${packet.gatewayId}-${ - packet.channelId - }-${new Date().getTime()}`, - JSON.stringify({ - from: packet.packet.from, - to: packet.packet.to, - message: this.textDecoder.decode(data.payload), - }), - { - EX: 60 * 60 * 12, // 12 hours - }, - ); - } else { - //update counter - } - } - - // Push to queue, batch updates - const gatewayExists = this.queue.find( - (item: QueueItem) => item.id === packet.gatewayId, - ); - - if (gatewayExists) { - const channelExists = gatewayExists.channels.find( - (channel) => channel.name === packet.channelId, - ); - - if (channelExists) { - channelExists.messages += 1; - } else { - gatewayExists.channels.push({ - name: packet.channelId, - encrypted: packet.packet?.payloadVariant.case === "encrypted", - messages: 1, - }); - } - } else { - this.queue.push({ - id: packet.gatewayId, - channels: [ - { - name: packet.channelId, - encrypted: packet.packet?.payloadVariant.case === "encrypted", - messages: 1, - }, - ], - }); - } - - // Check if we should process (every 10 seconds) - if (!this.isWorking && this.lastWorked.getTime() + 10000 < Date.now()) { - this.process(); - } - } - - public async process() { - this.isWorking = true; - this.lastWorked = new Date(Date.now()); - //iterate over current queue size, shift off items and process - - const toProcess = this.queue.splice(0, Math.min(this.queue.length, 10)); - - for (const gateway of toProcess) { - for (const channel of gateway.channels) { - console.log( - "Queue:", - this.queue.length, - "Processing", - gateway.id, - channel.name, - ); - - await prisma.channel.upsert({ - create: { - name: channel.name, - encrypted: channel.encrypted, - messages: channel.messages, - gateway: { - connectOrCreate: { - create: { - id: gateway.id, - }, - where: { - id: gateway.id, - }, - }, - }, - }, - update: { - messages: { - increment: channel.messages, - }, - }, - where: { - channelIdentifier: { - gatewayId: gateway.id, - name: channel.name, - }, - }, - }); - } - - // Update gateway position - if (gateway.latitude && gateway.longitude) { - await prisma.gateway.update({ - where: { - id: gateway.id, - }, - data: { - latitude: gateway.latitude, - longitude: gateway.longitude, - }, - }); - } - } - this.isWorking = false; - } -} diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts deleted file mode 100644 index e19b369..0000000 --- a/src/lib/prisma.ts +++ /dev/null @@ -1,5 +0,0 @@ -import Prisma from "@prisma/client"; - -const { PrismaClient } = Prisma; - -export const prisma = new PrismaClient(); diff --git a/src/routes/index.ts b/src/routes/index.ts index 482c8a4..eba3e3b 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -3,6 +3,5 @@ export { EventFirmwareRoutes } from "./eventFirmware.js"; export { EventFirmwareIconRoutes } from "./eventFirmwareIcon.js"; export { FirmwareRoutes } from "./firmware.js"; export { GithubRoutes } from "./github.js"; -export { MqttRoutes } from "./mqtt.js"; export { ResourceRoutes } from "./resource.js"; export { UpdaterRoutes } from "./updater.js"; diff --git a/src/routes/mqtt.ts b/src/routes/mqtt.ts deleted file mode 100644 index 094b1db..0000000 --- a/src/routes/mqtt.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { app } from "../index.js"; -import { prisma, redis } from "../lib/index.js"; - -export const MqttRoutes = () => { - return app.get("/mqtt", async (_req, res) => { - const mqttCache = await redis.get("mqttCache"); - - if (mqttCache) { - res.send(JSON.parse(mqttCache)); - } else { - const data = await prisma.gateway.findMany({ - include: { - channels: true, - }, - }); - - redis.set("mqttCache", JSON.stringify(data), { - EX: 1, - }); - res.send(data); - } - }); -}; diff --git a/src/routes/updater.ts b/src/routes/updater.ts index b733afe..0d8e0d4 100644 --- a/src/routes/updater.ts +++ b/src/routes/updater.ts @@ -2,6 +2,15 @@ import { app } from "../index.js"; export type SupportedApps = "meshtastic-desktop-flasher"; +// A production endpoint should not depend on one contributor's personal gist. +// The gist remains the default so existing clients keep working, but the source +// is overridable via UPDATER_MANIFEST_URL and should be pointed at infrastructure +// the project controls. +const DEFAULT_MANIFEST_URL = + "https://gist.githubusercontent.com/ajmcquilkin/4bdf1a679f070e74da61c64132aa431d/raw/manifests.json"; + +const manifestUrl = process.env.UPDATER_MANIFEST_URL || DEFAULT_MANIFEST_URL; + export const UpdaterRoutes = () => { return app .get("/updater", (_req, res) => { @@ -42,11 +51,35 @@ export const UpdaterRoutes = () => { // } // const gistContent = await fetch(rawUrl); - const gistContent = await fetch( - "https://gist.githubusercontent.com/ajmcquilkin/4bdf1a679f070e74da61c64132aa431d/raw/manifests.json", - ); - const parsedGistContent = (await gistContent.json()) as object[]; - const mostRecentManifest = parsedGistContent[0]; + let parsedManifests: unknown; + + try { + const manifestResponse = await fetch(manifestUrl); + + if (!manifestResponse.ok) { + console.error( + "[updater] manifest fetch returned", + manifestResponse.status, + manifestUrl, + ); + return res + .status(502) + .send("Error fetching manifests, please contact a developer."); + } + + parsedManifests = await manifestResponse.json(); + } catch (error) { + // An unreachable or non-JSON manifest source previously threw out of the + // handler rather than returning a response. + console.error("[updater] manifest fetch failed", manifestUrl, error); + return res + .status(502) + .send("Error fetching manifests, please contact a developer."); + } + + const mostRecentManifest = Array.isArray(parsedManifests) + ? (parsedManifests[0] as object | undefined) + : undefined; if (!mostRecentManifest) { return res diff --git a/src/services/gateway.ts b/src/services/gateway.ts deleted file mode 100644 index 9bf95be..0000000 --- a/src/services/gateway.ts +++ /dev/null @@ -1,56 +0,0 @@ -import {} from "@buf/meshtastic_api.bufbuild_es/protobufs/gateway/v1/gateway_pb.js"; -import { GatewayStreamResponse } from "@buf/meshtastic_api.bufbuild_es/protobufs/gateway/v1/gateway_service_pb.js"; -import type { GatewayService } from "@buf/meshtastic_api.connectrpc_es/protobufs/gateway/v1/gateway_service_connect.js"; -import { Timestamp } from "@bufbuild/protobuf"; -import type { ServiceImpl } from "@connectrpc/connect"; -import type { Channel } from "@prisma/client"; -import { prisma } from "../lib/index.js"; - -export class Gateway implements ServiceImpl { - public async *gatewayStream(): AsyncGenerator { - const gateways = await prisma.gateway.findMany({ - include: { - channels: true, - }, - where: { - latitude: { - not: { - equals: null, - }, - }, - }, - }); - - for (const gateway of gateways) { - //delay for 1 second - await new Promise((resolve) => setTimeout(resolve, 100)); - yield new GatewayStreamResponse({ - gateway: { - id: gateway.id, - latitude: gateway.latitude ?? undefined, - longitude: gateway.longitude ?? undefined, - createdAt: Timestamp.fromDate(gateway.createdAt), - updatedAt: Timestamp.fromDate(gateway.updatedAt), - channels: gateway.channels.map((channel: Channel) => { - return { - id: channel.id, - name: channel.name, - encrypted: channel.encrypted, - messagesCount: channel.messages, - createdAt: Timestamp.fromDate(channel.createdAt), - updatedAt: Timestamp.fromDate(channel.updatedAt), - }; - }), - }, - }); - } - - // while (true) { - // yield* yieldFromEvent(this.events.gateway, (data) => { - // return new GatewayStreamResponse({ - // gateway: data, - // }); - // }); - // } - } -} diff --git a/src/services/index.ts b/src/services/index.ts deleted file mode 100644 index e39dc18..0000000 --- a/src/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { Gateway } from "./gateway.js"; diff --git a/src/utils/yieldFromEvent.ts b/src/utils/yieldFromEvent.ts deleted file mode 100644 index c45d005..0000000 --- a/src/utils/yieldFromEvent.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { SubEvent } from "sub-events"; - -export function yieldFromEvent( - event: SubEvent, - generator: (data: T) => K, -): AsyncGenerator { - return { - next: async () => { - const value = await new Promise((resolve) => { - event.subscribe((e) => { - resolve(generator(e)); - }); - }); - - return { value, done: false }; - }, - async return() { - return await Promise.resolve({ value: undefined, done: true }); - }, - throw(error) { - return Promise.reject(error); - }, - [Symbol.asyncIterator]() { - return this; - }, - }; -} From 3c4d02e01786d9a706431a03fe603cc7b86dbdeb Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 11 Aug 2026 11:56:58 -0500 Subject: [PATCH 2/2] Harden the updater manifest fetch Three issues in the manifest handling, from review: - The fetch had no timeout, so a manifest source that accepted the connection and then stalled left the updater request pending indefinitely. It now aborts after 10s, and the rejection maps to 502 like any other upstream failure. - The non-ok branch returned without consuming the response body, which holds the connection open until it is garbage collected. Cancel it before returning. - `parsedManifests[0] as object | undefined` asserted a shape rather than checking it, so an array whose first element was a string passed the truthiness guard and was served to clients with a 200. The payload is now validated at runtime, and every shape this endpoint cannot use -- non-array JSON, an empty array, a non-object first element -- returns 502 rather than 500, since the fault lies with the manifest source rather than with this service. Verified against a stub manifest source: a valid array returns 200 with the first manifest; a string first element, a null first element, a non-array object, an empty array, a non-JSON body and an upstream 404 all return 502; a stalling source returns 502 after the 10s timeout. --- src/routes/updater.ts | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/routes/updater.ts b/src/routes/updater.ts index 0d8e0d4..a7ab5b9 100644 --- a/src/routes/updater.ts +++ b/src/routes/updater.ts @@ -11,6 +11,17 @@ const DEFAULT_MANIFEST_URL = const manifestUrl = process.env.UPDATER_MANIFEST_URL || DEFAULT_MANIFEST_URL; +// A manifest source that accepts the connection and then stalls would otherwise +// leave the updater request pending indefinitely. The abort rejects the fetch, +// which the catch below maps to 502 like any other upstream failure. +const MANIFEST_FETCH_TIMEOUT_MS = 10_000; + +// The payload is whatever the manifest source chose to serve, so it is checked +// at runtime rather than asserted. `typeof null === "object"`, and arrays are +// objects too, so both are excluded explicitly. +const isManifest = (value: unknown): value is object => + typeof value === "object" && value !== null && !Array.isArray(value); + export const UpdaterRoutes = () => { return app .get("/updater", (_req, res) => { @@ -54,7 +65,9 @@ export const UpdaterRoutes = () => { let parsedManifests: unknown; try { - const manifestResponse = await fetch(manifestUrl); + const manifestResponse = await fetch(manifestUrl, { + signal: AbortSignal.timeout(MANIFEST_FETCH_TIMEOUT_MS), + }); if (!manifestResponse.ok) { console.error( @@ -62,6 +75,9 @@ export const UpdaterRoutes = () => { manifestResponse.status, manifestUrl, ); + // Release the connection: an unread body is not returned to the pool + // until it is consumed or cancelled. + await manifestResponse.body?.cancel().catch(() => {}); return res .status(502) .send("Error fetching manifests, please contact a developer."); @@ -78,12 +94,20 @@ export const UpdaterRoutes = () => { } const mostRecentManifest = Array.isArray(parsedManifests) - ? (parsedManifests[0] as object | undefined) + ? parsedManifests[0] : undefined; - if (!mostRecentManifest) { + // Non-array JSON, an empty array and a first element that is not a + // manifest object are all the manifest source serving something this + // endpoint cannot use, so they are reported as upstream failures rather + // than as a fault in this service. + if (!isManifest(mostRecentManifest)) { + console.error( + "[updater] manifest source returned an unusable payload", + manifestUrl, + ); return res - .status(500) + .status(502) .send( "Error finding most recent manifest, please contact a developer.", );