From ec7b4e1d2a720d8e15e0f25aef67dc8f0dd02011 Mon Sep 17 00:00:00 2001 From: kwburns-kong Date: Sun, 18 Oct 2026 22:27:30 -0400 Subject: [PATCH] chore(deps): bump major dependencies (#10587) * chore: bumped zod@3.25.76 to zod@4.6.5 Zod 4 treats a required z.undefined() object key as non-optional, so absent keys in the RequestCollectionSchema union discriminators failed. Wrap those keys in .optional() to keep v3 semantics. Update the deep-strict auth test to expect the unrecognized_keys issue that zod 4 reports for the strict empty-object fallback branch. * chore: bumped jsdom@25.0.1 to jsdom@30.1.2 - Clears @asamuzakjp/css-color@3.2.0 advisory (AIKIDO-2026-199800); css-color now 7.1.4. - No source changes needed: none of the jsdom 26-30 breaking changes hit the repo's jsdom tests. * chore: bumped @bufbuild/protobuf@1.10.1 to @bufbuild/protobuf@2.16.0 Moves the gRPC stack to connect v2 alongside protobuf v2 (connect-node and connect 1.7.0 -> 2.2.0). This clears the transitive undici 5.29.0 and @fastify/busboy 2.1.1 under connect-node 1.x. Source changes: - grpc.ts: createPromiseClient is removed in v2, so the reflection service is now built from a FileDescriptorProto via fromJson and createFileRegistry (proto3.makeMessageType is removed). The unary call goes through transport.unary directly because createClient cannot type a runtime-built descriptor. - grpc.ts: toBinary takes the schema (FileDescriptorSetSchema from wkt). - grpc.test.ts: mock transport uses the v2 unary signature (no service argument, method.output, fromJsonString). * chore: regenerate insomnia JSON schema for dependency bumps * test: add grpc reflection regression tests for connect v2 * test: add smoke tests for buf reflection API * fix: don't auto-send gRPC reflection API key on request open * fix: drop non-standard starts_with format from published schema --- package-lock.json | 534 +++++++++++------- .../fixtures/grpc-reflection-api.yaml | 46 ++ .../server/grpc-reflection-api.ts | 155 +++++ packages/insomnia-smoke-test/server/index.ts | 2 + .../tests/smoke/grpc-reflection-api.test.ts | 39 ++ packages/insomnia/package.json | 8 +- .../ipc/__tests__/grpc-reflection-buf.test.ts | 182 ++++++ .../src/main/ipc/__tests__/grpc.test.ts | 11 +- packages/insomnia/src/main/ipc/grpc.ts | 114 ++-- .../ui/components/panes/grpc-request-pane.tsx | 4 +- 10 files changed, 832 insertions(+), 263 deletions(-) create mode 100644 packages/insomnia-smoke-test/fixtures/grpc-reflection-api.yaml create mode 100644 packages/insomnia-smoke-test/server/grpc-reflection-api.ts create mode 100644 packages/insomnia-smoke-test/tests/smoke/grpc-reflection-api.test.ts create mode 100644 packages/insomnia/src/main/ipc/__tests__/grpc-reflection-buf.test.ts diff --git a/package-lock.json b/package-lock.json index a88aa81594..938ebb59b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -123,23 +123,72 @@ } }, "node_modules/@asamuzakjp/css-color": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-7.1.6.tgz", + "integrity": "sha512-jZJUZX0mR6JqnjQGb79yTYbR5WXmACmcLoFSgZk+7W+0qMJ8jWGRp9ncYvE1ihwL3dwbmfeCe6/O0rRwWTko4g==", "license": "MIT", "dependencies": { - "@csstools/css-calc": "^2.1.3", - "@csstools/css-color-parser": "^3.0.9", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" + "@csstools/css-calc": "^3.4.3", + "@csstools/css-color-parser": "^4.2.6", + "@csstools/css-parser-algorithms": "^4.0.2", + "@csstools/css-tokenizer": "^4.0.2", + "lru-cache": "^11.5.3" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-10.0.3.tgz", + "integrity": "sha512-3+777rEPsOOjoy5/sRmIzHnoRDaLpm3XWY2dmWn/N7f/XwDG0bcpnFaKL3X9FYJUbNwLmpcJC1bUYexJRxsFlw==", + "license": "MIT", + "dependencies": { + "bidi-js": "^1.1.0", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.3" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" }, "node_modules/@asyncapi/specs": { "version": "6.11.1", @@ -1127,53 +1176,41 @@ "node": ">=6.9.0" } }, - "node_modules/@bufbuild/protobuf": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.1.tgz", - "integrity": "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==", - "license": "(Apache-2.0 AND BSD-3-Clause)" - }, - "node_modules/@connectrpc/connect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-1.7.0.tgz", - "integrity": "sha512-iNKdJRi69YP3mq6AePRT8F/HrxWCewrhxnLMNm0vpqXAR8biwzRtO6Hjx80C6UvtKJ5sFmffQT7I4Baecz389w==", - "license": "Apache-2.0", - "peerDependencies": { - "@bufbuild/protobuf": "^1.10.0" - } - }, - "node_modules/@connectrpc/connect-node": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-1.7.0.tgz", - "integrity": "sha512-6vaPIkG/NyhxlYgytLoR9KYbPhczEboFB2OYWkA9qvUz1K7efXfeGrlRxoLtpa+r8VxyIOw73w5ktNe743nD+A==", - "license": "Apache-2.0", - "dependencies": { - "undici": "^5.28.4" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@bufbuild/protobuf": "^1.10.0", - "@connectrpc/connect": "1.7.0" - } - }, - "node_modules/@connectrpc/connect-node/node_modules/undici": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", - "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "node_modules/@bramus/specificity": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.3.tgz", + "integrity": "sha512-0GEz8qJWokkUVYQYRukmkSHIFNoBH8QTreeHrAFbvz1XFYc3Y+puhMs8PQ5x8GNugiC2l5jvDBXKLbBIByeSBw==", "license": "MIT", "dependencies": { - "@fastify/busboy": "^2.0.0" + "css-tree": "^3.2.1" }, - "engines": { - "node": ">=14.0" + "bin": { + "specificity": "bin/cli.js" } }, + "node_modules/@bramus/specificity/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/@bramus/specificity/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.2.tgz", + "integrity": "sha512-grhRy3OKmniaAEKXMjua5z/EODX0MSqBGjunw8+j/3HQjOnahs2AGhvEOIYVUWcU6ScApbhLhVrQTX8XqrMrow==", "funding": [ { "type": "github", @@ -1186,13 +1223,13 @@ ], "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.4.3.tgz", + "integrity": "sha512-iex20d8CHVkyvg6B7UKV7uHnI2Bqo9g+EFfT9E0y+GvTvhZ/DwONJ+9aKb1dlqm0ZiGsL5RXjp0fCoJYnkeDjA==", "funding": [ { "type": "github", @@ -1205,17 +1242,17 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.2", + "@csstools/css-tokenizer": "^4.0.2" } }, "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.6.tgz", + "integrity": "sha512-iiPQ3iRWwnJkeEn6RIu6SJPr7hYrLz6XZ9s/QZl+2/LI5KQVjpl2fdmDSZKuD4xP6GMmMPgHFFXg6k1Wkz0Trg==", "funding": [ { "type": "github", @@ -1228,21 +1265,21 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" + "@csstools/color-helpers": "^6.1.2", + "@csstools/css-calc": "^3.4.3" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.2", + "@csstools/css-tokenizer": "^4.0.2" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.2.tgz", + "integrity": "sha512-40cSKyMvK+tq4qz6Awrlye2WGuOKt3FwPgtGg6KTfbHOWNw+Rk1rzbAtZnZ6IBhsY491HLRnDXwoyBAijmmILA==", "funding": [ { "type": "github", @@ -1255,16 +1292,16 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-tokenizer": "^4.0.2" } }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.2.tgz", + "integrity": "sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==", "funding": [ { "type": "github", @@ -1277,7 +1314,7 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@customerio/cdp-analytics-browser": { @@ -2349,6 +2386,23 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@exodus/bytes": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.16.0.tgz", + "integrity": "sha512-IcpW84uEn3N7ETtNZMlxKhfl6Pec8rUNGOTBtWbK1FKhJxIFAptZyVrvVRVBimAJxJCgc3PxepxkdWWG4DVzfA==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@exodus/schemasafe": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", @@ -2371,15 +2425,6 @@ "npm": ">=10" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/@formatjs/ecma402-abstract": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz", @@ -3288,7 +3333,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -10250,6 +10295,15 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/bidi-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.1.0.tgz", + "integrity": "sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -11628,25 +11682,6 @@ "node": ">=4" } }, - "node_modules/cssstyle": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", - "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", - "license": "MIT", - "dependencies": { - "@asamuzakjp/css-color": "^3.2.0", - "rrweb-cssom": "^0.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/cssstyle/node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "license": "MIT" - }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -11693,16 +11728,25 @@ } }, "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-8.0.0.tgz", + "integrity": "sha512-JQUCCK+/CAGbGGx618PlFZMXH81Jtj7t73CHv9dd/zVWvxgGT2zLFRjnKb+Ng46GIwcy+f7f88qd4duqllK+WQ==", "license": "MIT", "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^17.1.2" }, "engines": { - "node": ">=18" + "node": "^22.14.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "license": "MIT", + "engines": { + "node": ">=20" } }, "node_modules/data-view-buffer": { @@ -15431,15 +15475,15 @@ } }, "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-7.0.0.tgz", + "integrity": "sha512-UikN5yr7xsCDAq87Or5or0PAlD3HJJOKVzM05az588WnpDJ4Ux7a2A53Qi6gofGg2/EtvF/H4hCi/TXfCW4Y6w==", "license": "MIT", "dependencies": { - "whatwg-encoding": "^3.1.1" + "@exodus/bytes": "^1.15.1" }, "engines": { - "node": ">=18" + "node": "^22.13.0 || >=24.0.0" } }, "node_modules/html-entities": { @@ -16896,38 +16940,37 @@ "license": "MIT" }, "node_modules/jsdom": { - "version": "25.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", - "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "version": "30.1.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.3.tgz", + "integrity": "sha512-hQMUPvRgN7KxsfIytDQji7bimuJeRQa6Qo5+iuiZQk5CafeRxLIA10/cG5YCBFNpD9RO3oqIiN4CO1Y44kxqXg==", "license": "MIT", "dependencies": { - "cssstyle": "^4.1.0", - "data-urls": "^5.0.0", - "decimal.js": "^10.4.3", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", + "@asamuzakjp/css-color": "^7.1.3", + "@asamuzakjp/dom-selector": "^10.0.3", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.15", + "@exodus/bytes": "^1.16.0", + "css-tree": "^3.2.1", + "data-urls": "^8.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^7.0.0", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.12", - "parse5": "^7.1.2", - "rrweb-cssom": "^0.7.1", + "lru-cache": "^11.5.3", + "parse5": "^8.0.1", "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^5.0.0", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0", - "ws": "^8.18.0", + "tough-cookie": "^6.0.2", + "undici": "^8.12.1", + "w3c-xmlserializer": "^6.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^17.2.0", "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=18" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" }, "peerDependencies": { - "canvas": "^2.11.2" + "canvas": "^3.2.3" }, "peerDependenciesMeta": { "canvas": { @@ -16935,34 +16978,112 @@ } } }, - "node_modules/jsdom/node_modules/form-data": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", - "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "node_modules/jsdom/node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.15.tgz", + "integrity": "sha512-J0u7HkVl2nzSlhsiTOp4AmwcUQ3D+mGEEKfBy/7To5/y7F2OHwyLrXfrhR0SMgr4p5Lo+eaMVSeai24zUcBIxA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.4", - "mime-types": "^2.1.35" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">= 6" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/jsdom/node_modules/entities": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.1.0.tgz", + "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/jsdom/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/jsdom/node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", "license": "BSD-3-Clause", "dependencies": { - "tldts": "^6.1.32" + "tldts": "^7.0.5" }, "engines": { "node": ">=16" } }, + "node_modules/jsdom/node_modules/undici": { + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz", + "integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==", + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/jsdom/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/jsep": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", @@ -19696,12 +19817,6 @@ "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", "license": "MIT" }, - "node_modules/nwsapi": { - "version": "2.2.23", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", - "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", - "license": "MIT" - }, "node_modules/oas-kit-common": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", @@ -22124,12 +22239,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/rrweb-cssom": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", - "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", - "license": "MIT" - }, "node_modules/rtl-css-js": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz", @@ -23719,12 +23828,6 @@ "node": ">= 6" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "license": "MIT" - }, "node_modules/tailwind-merge": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", @@ -24047,21 +24150,21 @@ } }, "node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "version": "7.4.18", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.18.tgz", + "integrity": "sha512-somxA8xKAUzlAxl0TX/jMSYIbRNFIVGnI96pVGJPeMc5BsHmfVXn6mOJT6U++ELnl04eGfzpfcEuZe8WiASkXg==", "license": "MIT", "dependencies": { - "tldts-core": "^6.1.86" + "tldts-core": "^7.4.18" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "version": "7.4.18", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.18.tgz", + "integrity": "sha512-KI2PKvCmvQI/xD6ypPqkU0i7oxNXR/O58RLRaHR4svsgVUQ2JE/XC8BtMbG6PSehD27I8sZJRcC1ASnCy6lp3g==", "license": "MIT" }, "node_modules/tmp": { @@ -24151,15 +24254,15 @@ } }, "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-7.0.0.tgz", + "integrity": "sha512-BcDSDW+SIGWVOns1Zm6Yg91q9AEj7eaID0QDY9oIqJD1A8dscNwMcl9Sms2DeuWCNov1X0/oXanHGYQBSLwZsg==", "license": "MIT", "dependencies": { "punycode": "^2.3.1" }, "engines": { - "node": ">=18" + "node": "^22.14.0 || >=24.0.0" } }, "node_modules/truncate-utf8-bytes": { @@ -25571,15 +25674,15 @@ "license": "MIT" }, "node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-6.0.0.tgz", + "integrity": "sha512-4Nsy8K5Tr6SPDH9jhKJOHf7ChDrc1zufZTVSF7x72hwuEXBqxqk9G6cK+K2NRUtB3iELRJqjXb4JPDMBjMTl2Q==", "license": "MIT", "dependencies": { "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=18" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, "node_modules/watskeburt": { @@ -25627,12 +25730,12 @@ "license": "0BSD" }, "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "license": "BSD-2-Clause", "engines": { - "node": ">=12" + "node": ">=20" } }, "node_modules/whatwg-encoding": { @@ -25658,16 +25761,17 @@ } }, "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.3.0.tgz", + "integrity": "sha512-KwOAJrpf+GTgBvcBQJcP38sMF0DsQKdwtG+TxM2pW44P6E5k0hDbWrDmyuJDIF0Uef0aQq0ZoJs+5oI+k8ddhg==", "license": "MIT", "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" + "@exodus/bytes": "^1.16.0", + "tr46": "^7.0.0", + "webidl-conversions": "^8.0.1" }, "engines": { - "node": ">=18" + "node": "^22.14.0 || >=24.0.0" } }, "node_modules/which": { @@ -26184,9 +26288,9 @@ "dependencies": { "@apideck/better-ajv-errors": "^0.3.6", "@apidevtools/swagger-parser": "12.1.0", - "@bufbuild/protobuf": "^1.10.0", - "@connectrpc/connect": "^1.6.1", - "@connectrpc/connect-node": "^1.6.1", + "@bufbuild/protobuf": "^2.16.0", + "@connectrpc/connect": "^2.2.0", + "@connectrpc/connect-node": "^2.2.0", "@customerio/cdp-analytics-browser": "^0.5.9", "@faker-js/faker": "^10.6.0", "@fortawesome/fontawesome-svg-core": "^6.7.2", @@ -26258,7 +26362,7 @@ "isbot": "^5", "isomorphic-git": "1.25.7", "js-yaml": "^4.1.0", - "jsdom": "^25.0.1", + "jsdom": "^30.1.2", "jshint": "^2.13.6", "json-bigint": "^1.0.0", "json-order": "^1.1.3", @@ -26817,6 +26921,34 @@ "@types/node-forge": "^1.3.14" } }, + "packages/insomnia/node_modules/@bufbuild/protobuf": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.16.0.tgz", + "integrity": "sha512-FWa0sPlqYGJgpTs6OxBRcL/AW4JT0OIO+W1ajerluoTVh8CV0B7XM1qsNCRgnkSCkRCgOICKHRQlY17jrsm0+Q==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "packages/insomnia/node_modules/@connectrpc/connect": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.2.0.tgz", + "integrity": "sha512-M63OQ1ZFcPUm85CuewJ9G9CMV8LjzGR9935xOnXNQGSazAbwMb3x2W7vXBYM1zqrP7XFD8uxp/dUAhirclgwnQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^2.7.0" + } + }, + "packages/insomnia/node_modules/@connectrpc/connect-node": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-2.2.0.tgz", + "integrity": "sha512-E6uUO6e11WdynAuBjuN+0WpHjqxEM9BNDdfDIJHcn49Esg/WxSIbn2y1KFt9XL96IJi01qdBbvubcdpBz1X3Zw==", + "license": "Apache-2.0", + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@bufbuild/protobuf": "^2.7.0", + "@connectrpc/connect": "2.2.0" + } + }, "packages/insomnia/node_modules/@testing-library/dom": { "version": "10.4.1", "dev": true, diff --git a/packages/insomnia-smoke-test/fixtures/grpc-reflection-api.yaml b/packages/insomnia-smoke-test/fixtures/grpc-reflection-api.yaml new file mode 100644 index 0000000000..130b63b463 --- /dev/null +++ b/packages/insomnia-smoke-test/fixtures/grpc-reflection-api.yaml @@ -0,0 +1,46 @@ +type: collection.insomnia.rest/5.0 +schema_version: "5.1" +name: gRPC Reflection API +meta: + id: wrk_grpc_reflection_api_smoke + created: 1736278634829 + modified: 1736278634829 +collection: + - url: localhost:50051 + name: ReflectionApiUnary + meta: + id: greq_grpc_reflection_api_smoke + created: 1638200268776 + modified: 1669986544274 + isPrivate: false + sortKey: -1638200268776 + body: + text: |- + { + "latitude":"409146138", + "longitude":"-746188906" + } + reflectionApi: + enabled: true + url: http://localhost:4010 + apiKey: smoke-reflection-key + module: smoke-module + - url: localhost:50051 + name: ReflectionApiInvalidKey + meta: + id: greq_grpc_reflection_api_invalid_key_smoke + created: 1638200268776 + modified: 1669986544274 + isPrivate: false + sortKey: -1638200268775 + body: + text: |- + { + "latitude":"409146138", + "longitude":"-746188906" + } + reflectionApi: + enabled: true + url: http://localhost:4010 + apiKey: wrong-key + module: smoke-module diff --git a/packages/insomnia-smoke-test/server/grpc-reflection-api.ts b/packages/insomnia-smoke-test/server/grpc-reflection-api.ts new file mode 100644 index 0000000000..660a752872 --- /dev/null +++ b/packages/insomnia-smoke-test/server/grpc-reflection-api.ts @@ -0,0 +1,155 @@ +import express from 'express'; + +// Mock of the buf.build reflection API (Connect unary) used by getMethodsFromReflectionServer. +// The Insomnia Connect client sends application/proto by default, so responses are encoded to match the +// request content type (binary protobuf or JSON). +const ROUTE_GUIDE_FILE = { + name: 'route_guide.proto', + package: 'routeguide', + syntax: 'proto3', + messageType: [ + { + name: 'Point', + field: [ + { name: 'latitude', number: 1, label: 'LABEL_OPTIONAL', type: 'TYPE_INT32', jsonName: 'latitude' }, + { name: 'longitude', number: 2, label: 'LABEL_OPTIONAL', type: 'TYPE_INT32', jsonName: 'longitude' }, + ], + }, + { + name: 'Feature', + field: [ + { name: 'name', number: 1, label: 'LABEL_OPTIONAL', type: 'TYPE_STRING', jsonName: 'name' }, + { + name: 'location', + number: 2, + label: 'LABEL_OPTIONAL', + type: 'TYPE_MESSAGE', + typeName: '.routeguide.Point', + jsonName: 'location', + }, + ], + }, + ], + service: [ + { + name: 'RouteGuide', + method: [{ name: 'GetFeature', inputType: '.routeguide.Point', outputType: '.routeguide.Feature' }], + }, + ], +}; + +// Protobuf wire encoding for the descriptor subset above (field numbers from descriptor.proto). +const LABEL_VALUES: Record = { LABEL_OPTIONAL: 1, LABEL_REPEATED: 3 }; +const TYPE_VALUES: Record = { TYPE_INT32: 5, TYPE_STRING: 9, TYPE_MESSAGE: 11 }; + +const encodeVarint = (value: number): Buffer => { + const bytes: number[] = []; + let rest = value; + while (rest > 0x7f) { + bytes.push((rest & 0x7f) | 0x80); + rest >>>= 7; + } + bytes.push(rest); + return Buffer.from(bytes); +}; +const encodeTag = (fieldNumber: number, wireType: number) => encodeVarint((fieldNumber << 3) | wireType); +const encodeString = (fieldNumber: number, value: string) => { + const bytes = Buffer.from(value, 'utf8'); + return Buffer.concat([encodeTag(fieldNumber, 2), encodeVarint(bytes.length), bytes]); +}; +const encodeInt = (fieldNumber: number, value: number) => + Buffer.concat([encodeTag(fieldNumber, 0), encodeVarint(value)]); +const encodeMessage = (fieldNumber: number, body: Buffer) => + Buffer.concat([encodeTag(fieldNumber, 2), encodeVarint(body.length), body]); + +type FileDescriptor = typeof ROUTE_GUIDE_FILE; + +const encodeFileDescriptor = (file: FileDescriptor) => + Buffer.concat([ + encodeString(1, file.name), + encodeString(2, file.package), + ...file.messageType.map(message => + encodeMessage( + 4, + Buffer.concat([ + encodeString(1, message.name), + ...message.field.map(field => + encodeMessage( + 2, + Buffer.concat([ + encodeString(1, field.name), + encodeInt(3, field.number), + encodeInt(4, LABEL_VALUES[field.label]), + encodeInt(5, TYPE_VALUES[field.type]), + ...('typeName' in field ? [encodeString(6, field.typeName)] : []), + encodeString(10, field.jsonName), + ]), + ), + ), + ]), + ), + ), + ...file.service.map(service => + encodeMessage( + 6, + Buffer.concat([ + encodeString(1, service.name), + ...service.method.map(method => + encodeMessage( + 2, + Buffer.concat([ + encodeString(1, method.name), + encodeString(2, method.inputType), + encodeString(3, method.outputType), + ]), + ), + ), + ]), + ), + ), + encodeString(12, file.syntax), + ]); + +// GetFileDescriptorSetResponse { FileDescriptorSet file_descriptor_set = 1 } where FileDescriptorSet { repeated FileDescriptorProto file = 1 } +const encodeResponse = () => encodeMessage(1, encodeMessage(1, encodeFileDescriptor(ROUTE_GUIDE_FILE))); + +// The Connect client only sets `module` (field 1, string) on the wire. +const decodeModuleFromProto = (body: Buffer): string | undefined => { + if (body[0] !== 0x0a) { + return undefined; + } + const length = body[1]; + return body.subarray(2, 2 + length).toString('utf8'); +}; + +const router = express.Router(); + +router.post( + '/buf.reflect.v1beta1.FileDescriptorSetService/GetFileDescriptorSet', + express.raw({ type: '*/*' }), + (req, res) => { + if (req.headers.authorization !== 'Bearer smoke-reflection-key') { + res.status(401).json({ code: 'unauthenticated', message: 'invalid api key' }); + return; + } + + const body = Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0); + const isJson = (req.headers['content-type'] ?? '').includes('json'); + const moduleName = isJson + ? (JSON.parse(body.toString('utf8') || '{}') as { module?: string }).module + : decodeModuleFromProto(body); + + if (moduleName !== 'smoke-module') { + res.status(404).json({ code: 'not_found', message: 'module not found' }); + return; + } + + if (isJson) { + res.status(200).json({ fileDescriptorSet: { file: [ROUTE_GUIDE_FILE] } }); + return; + } + res.status(200).type('application/proto').send(encodeResponse()); + }, +); + +export default router; diff --git a/packages/insomnia-smoke-test/server/index.ts b/packages/insomnia-smoke-test/server/index.ts index 8eddb4699b..6ae000876a 100644 --- a/packages/insomnia-smoke-test/server/index.ts +++ b/packages/insomnia-smoke-test/server/index.ts @@ -29,6 +29,7 @@ import githubApi from './github-api'; import gitlabApi from './gitlab-api'; import { schema } from './graphql'; import { startGRPCServer } from './grpc'; +import grpcReflectionApi from './grpc-reflection-api'; import insomniaApi from './insomnia-api'; import { mtlsRouter } from './mtls'; import { oauthRoutes } from './oauth'; @@ -109,6 +110,7 @@ app.get('/cookies', (_req, res) => { app.use('/file', express.static('fixtures/files')); app.use('/auth/basic', basicAuthRouter); app.use('/protected', mtlsRouter); +app.use(grpcReflectionApi); githubApi(app); gitlabApi(app); diff --git a/packages/insomnia-smoke-test/tests/smoke/grpc-reflection-api.test.ts b/packages/insomnia-smoke-test/tests/smoke/grpc-reflection-api.test.ts new file mode 100644 index 0000000000..4948a1b11a --- /dev/null +++ b/packages/insomnia-smoke-test/tests/smoke/grpc-reflection-api.test.ts @@ -0,0 +1,39 @@ +import { expect } from '@playwright/test'; + +import { loadFixture } from '../../playwright/paths'; +import { test } from '../../playwright/test'; + +test('can load methods through the buf reflection API', async ({ app, page, insomnia }) => { + test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms'); + + const text = await loadFixture('grpc-reflection-api.yaml'); + await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); + + await page.getByLabel('Import').click(); + await page.locator('[data-test-id="import-from-clipboard"]').click(); + await page.getByRole('button', { name: 'Scan' }).click(); + await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); + + await insomnia.navigationSidebar.clickRequestOrFolder('ReflectionApiUnary'); + await page.getByTestId('button-server-reflection').click(); + + await page.getByRole('button', { name: 'Select Method' }).click(); + await expect.soft(page.getByRole('option', { name: 'RouteGuide/GetFeature' })).toBeVisible(); +}); + +test('shows an error for an invalid reflection api key', async ({ app, page, insomnia }) => { + test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms'); + + const text = await loadFixture('grpc-reflection-api.yaml'); + await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); + + await page.getByLabel('Import').click(); + await page.locator('[data-test-id="import-from-clipboard"]').click(); + await page.getByRole('button', { name: 'Scan' }).click(); + await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); + + await insomnia.navigationSidebar.clickRequestOrFolder('ReflectionApiInvalidKey'); + await page.getByTestId('button-server-reflection').click(); + + await expect.soft(page.getByRole('dialog')).toContainText('Invalid reflection server api key'); +}); diff --git a/packages/insomnia/package.json b/packages/insomnia/package.json index 7c40272918..1a835b1b11 100644 --- a/packages/insomnia/package.json +++ b/packages/insomnia/package.json @@ -51,9 +51,9 @@ "dependencies": { "@apideck/better-ajv-errors": "^0.3.6", "@apidevtools/swagger-parser": "12.1.0", - "@bufbuild/protobuf": "^1.10.0", - "@connectrpc/connect": "^1.6.1", - "@connectrpc/connect-node": "^1.6.1", + "@bufbuild/protobuf": "^2.16.0", + "@connectrpc/connect": "^2.2.0", + "@connectrpc/connect-node": "^2.2.0", "@customerio/cdp-analytics-browser": "^0.5.9", "@faker-js/faker": "^10.6.0", "@fortawesome/fontawesome-svg-core": "^6.7.2", @@ -125,7 +125,7 @@ "isbot": "^5", "isomorphic-git": "1.25.7", "js-yaml": "^4.1.0", - "jsdom": "^25.0.1", + "jsdom": "^30.1.2", "jshint": "^2.13.6", "json-bigint": "^1.0.0", "json-order": "^1.1.3", diff --git a/packages/insomnia/src/main/ipc/__tests__/grpc-reflection-buf.test.ts b/packages/insomnia/src/main/ipc/__tests__/grpc-reflection-buf.test.ts new file mode 100644 index 0000000000..9f432ff342 --- /dev/null +++ b/packages/insomnia/src/main/ipc/__tests__/grpc-reflection-buf.test.ts @@ -0,0 +1,182 @@ +import { type DescMessage, type DescMethodUnary, fromJson, type JsonObject, type MessageInitShape } from '@bufbuild/protobuf'; +import { Code, ConnectError, type UnaryResponse } from '@connectrpc/connect'; +import { createConnectTransport } from '@connectrpc/connect-node'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { version } from '../../../../package.json'; +import { loadMethodsFromReflection } from '../grpc'; + +vi.mock('@connectrpc/connect-node'); +vi.mock('../../../network/grpc/write-proto-file.node'); + +const reflectionApi = { + enabled: true, + apiKey: 'TEST_KEY', + url: 'https://buf.build', + module: 'buf.build/acme/greeter', +}; + +const fileDescriptorSetJson: JsonObject = { + file: [ + { + name: 'acme/greeter/v1/greeter.proto', + package: 'acme.greeter.v1', + syntax: 'proto3', + messageType: [ + { + name: 'Msg', + field: [{ name: 'text', number: 1, label: 'LABEL_OPTIONAL', type: 'TYPE_STRING', jsonName: 'text' }], + }, + ], + service: [ + { + name: 'GreeterService', + method: [ + { name: 'Unary', inputType: '.acme.greeter.v1.Msg', outputType: '.acme.greeter.v1.Msg' }, + { + name: 'ClientStream', + inputType: '.acme.greeter.v1.Msg', + outputType: '.acme.greeter.v1.Msg', + clientStreaming: true, + }, + { + name: 'ServerStream', + inputType: '.acme.greeter.v1.Msg', + outputType: '.acme.greeter.v1.Msg', + serverStreaming: true, + }, + { + name: 'BidiStream', + inputType: '.acme.greeter.v1.Msg', + outputType: '.acme.greeter.v1.Msg', + clientStreaming: true, + serverStreaming: true, + }, + ], + }, + ], + }, + ], +}; + +// Installs a fake Connect transport whose unary() is the given mock. +const useTransport = (unary: ReturnType) => { + vi.mocked(createConnectTransport).mockReturnValue({ unary } as unknown as ReturnType); +}; + +const loadBuf = (options: { apiKey?: string; disableUserAgentHeader?: boolean } = {}) => + loadMethodsFromReflection({ + url: 'foo.com', + metadata: [], + rejectUnauthorized: true, + disableUserAgentHeader: options.disableUserAgentHeader, + reflectionApi: { ...reflectionApi, apiKey: options.apiKey ?? reflectionApi.apiKey }, + }); + +const headersOf = (unary: ReturnType) => new Headers(unary.mock.calls[0][3] as HeadersInit); + +describe('loadMethodsFromReflection (buf reflection api)', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('error mapping', () => { + it('maps Unauthenticated to an invalid api key error', async () => { + useTransport(vi.fn().mockRejectedValue(new ConnectError('denied', Code.Unauthenticated))); + + await expect(loadBuf()).rejects.toThrow('Invalid reflection server api key'); + }); + + it('maps NotFound to a module access error', async () => { + useTransport(vi.fn().mockRejectedValue(new ConnectError('missing', Code.NotFound))); + + await expect(loadBuf()).rejects.toThrow( + "The reflection server api key doesn't have access to the module or the module does not exists", + ); + }); + + it('rethrows other Connect errors unchanged', async () => { + const error = new ConnectError('boom', Code.Internal); + useTransport(vi.fn().mockRejectedValue(error)); + + await expect(loadBuf()).rejects.toBe(error); + }); + + it('rethrows non-Connect errors unchanged', async () => { + const error = new Error('network down'); + useTransport(vi.fn().mockRejectedValue(error)); + + await expect(loadBuf()).rejects.toBe(error); + }); + }); + + describe('request headers', () => { + it('sends the Insomnia User-Agent and Bearer auth when enabled', async () => { + const unary = vi.fn().mockRejectedValue(new ConnectError('stop', Code.Internal)); + useTransport(unary); + + await expect(loadBuf()).rejects.toBeDefined(); + + expect(headersOf(unary).get('User-Agent')).toBe(`insomnia/${version}`); + expect(headersOf(unary).get('Authorization')).toBe('Bearer TEST_KEY'); + }); + + it('omits the User-Agent header when disabled', async () => { + const unary = vi.fn().mockRejectedValue(new ConnectError('stop', Code.Internal)); + useTransport(unary); + + await expect(loadBuf({ disableUserAgentHeader: true })).rejects.toBeDefined(); + + expect(headersOf(unary).has('User-Agent')).toBe(false); + expect(headersOf(unary).get('Authorization')).toBe('Bearer TEST_KEY'); + }); + + it('omits the Authorization header when no api key is set', async () => { + const unary = vi.fn().mockRejectedValue(new ConnectError('stop', Code.Internal)); + useTransport(unary); + + await expect(loadBuf({ apiKey: '' })).rejects.toBeDefined(); + + expect(headersOf(unary).has('Authorization')).toBe(false); + }); + }); + + describe('response handling', () => { + it('returns no methods when the response has no file descriptor set', async () => { + useTransport( + vi.fn( + async (method: DescMethodUnary): Promise => + ({ message: fromJson(method.output, {}) }) as unknown as UnaryResponse, + ), + ); + + await expect(loadBuf()).resolves.toStrictEqual([]); + }); + + it('maps unary, client, server and bidi methods from the file descriptor set', async () => { + useTransport( + vi.fn( + async ( + method: DescMethodUnary, + _signal: AbortSignal | undefined, + _timeoutMs: number | undefined, + _header: HeadersInit | undefined, + _input: MessageInitShape, + ): Promise => + ({ + message: fromJson(method.output, { fileDescriptorSet: fileDescriptorSetJson }), + }) as unknown as UnaryResponse, + ), + ); + + const methods = await loadBuf(); + + expect(methods.map(({ fullPath, type }) => ({ fullPath, type }))).toStrictEqual([ + { fullPath: '/acme.greeter.v1.GreeterService/Unary', type: 'unary' }, + { fullPath: '/acme.greeter.v1.GreeterService/ClientStream', type: 'client' }, + { fullPath: '/acme.greeter.v1.GreeterService/ServerStream', type: 'server' }, + { fullPath: '/acme.greeter.v1.GreeterService/BidiStream', type: 'bidi' }, + ]); + }); + }); +}); diff --git a/packages/insomnia/src/main/ipc/__tests__/grpc.test.ts b/packages/insomnia/src/main/ipc/__tests__/grpc.test.ts index 00ab43131d..5079bc3208 100644 --- a/packages/insomnia/src/main/ipc/__tests__/grpc.test.ts +++ b/packages/insomnia/src/main/ipc/__tests__/grpc.test.ts @@ -1,5 +1,5 @@ // @ts-nocheck -import type { AnyMessage, MethodInfo, PartialMessage, ServiceType } from '@bufbuild/protobuf'; +import { type DescMessage, type DescMethodUnary, fromJsonString, type MessageInitShape } from '@bufbuild/protobuf'; import type { UnaryResponse } from '@connectrpc/connect'; import { createConnectTransport } from '@connectrpc/connect-node'; import * as grpcReflection from 'grpc-reflection-js'; @@ -193,23 +193,22 @@ describe('loadMethodsFromReflection', () => { expect(options.baseUrl).toStrictEqual('https://buf.build'); return { async unary( - service: ServiceType, - method: MethodInfo, + method: DescMethodUnary, _: AbortSignal | undefined, __: number | undefined, header: HeadersInit | undefined, - input: PartialMessage, + input: MessageInitShape, ): Promise { expect(new Headers(header).get('Authorization')).toStrictEqual('Bearer TEST_KEY'); expect(input).toStrictEqual({ module: 'buf.build/connectrpc/eliza' }); return { - service: service, method: method, header: new Headers(), trailer: new Headers(), stream: false, // Output of running `buf curl https://buf.build/buf.reflect.v1beta1.FileDescriptorSetService/GetFileDescriptorSet --data '{"module": "buf.build/connectrpc/eliza"}' --schema buf.build/bufbuild/reflect -H 'Authorization: Bearer buf-token'` - message: method.O.fromJsonString( + message: fromJsonString( + method.output, '{"fileDescriptorSet":{"file":[{"name":"connectrpc/eliza/v1/eliza.proto","package":"connectrpc.eliza.v1","messageType":[{"name":"SayRequest","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"SayResponse","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"ConverseRequest","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"ConverseResponse","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"IntroduceRequest","field":[{"name":"name","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"name"}]},{"name":"IntroduceResponse","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]}],"service":[{"name":"ElizaService","method":[{"name":"Say","inputType":".connectrpc.eliza.v1.SayRequest","outputType":".connectrpc.eliza.v1.SayResponse","options":{"idempotencyLevel":"NO_SIDE_EFFECTS"}},{"name":"Converse","inputType":".connectrpc.eliza.v1.ConverseRequest","outputType":".connectrpc.eliza.v1.ConverseResponse","options":{},"clientStreaming":true,"serverStreaming":true},{"name":"Introduce","inputType":".connectrpc.eliza.v1.IntroduceRequest","outputType":".connectrpc.eliza.v1.IntroduceResponse","options":{},"serverStreaming":true}]}],"syntax":"proto3"}]},"version":"233fca715f49425581ec0a1b660be886"}', ), }; diff --git a/packages/insomnia/src/main/ipc/grpc.ts b/packages/insomnia/src/main/ipc/grpc.ts index 42ff33c73e..1385d00a3d 100644 --- a/packages/insomnia/src/main/ipc/grpc.ts +++ b/packages/insomnia/src/main/ipc/grpc.ts @@ -1,10 +1,11 @@ +import { createFileRegistry, type DescMethodUnary, fromJson, toBinary } from '@bufbuild/protobuf'; import { - FileDescriptorSet as ProtobufEsFileDescriptorSet, - MethodIdempotency, - MethodKind, - proto3, -} from '@bufbuild/protobuf'; -import { Code, ConnectError, createPromiseClient } from '@connectrpc/connect'; + file_google_protobuf_descriptor, + FileDescriptorProtoSchema, + type FileDescriptorSet, + FileDescriptorSetSchema, +} from '@bufbuild/protobuf/wkt'; +import { Code, ConnectError } from '@connectrpc/connect'; import { createConnectTransport } from '@connectrpc/connect-node'; import { type Call, @@ -132,65 +133,76 @@ const getMethodsFromReflectionServer = async ( disableUserAgentHeader: boolean, ): Promise => { const { url, module, apiKey } = reflectionApi; - const GetFileDescriptorSetRequest = proto3.makeMessageType('buf.reflect.v1beta1.GetFileDescriptorSetRequest', () => [ - { no: 1, name: 'module', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { no: 2, name: 'version', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { - no: 3, - name: 'symbols', - kind: 'scalar', - T: 9 /* ScalarType.STRING */, - repeated: true, - }, - ]); - const GetFileDescriptorSetResponse = proto3.makeMessageType( - 'buf.reflect.v1beta1.GetFileDescriptorSetResponse', - () => [ - { - no: 1, - name: 'file_descriptor_set', - kind: 'message', - T: ProtobufEsFileDescriptorSet, - }, - { no: 2, name: 'version', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - ], + const reflectionRegistry = createFileRegistry( + fromJson(FileDescriptorProtoSchema, { + name: 'buf/reflect/v1beta1/file_descriptor_set_service.proto', + package: 'buf.reflect.v1beta1', + syntax: 'proto3', + dependency: ['google/protobuf/descriptor.proto'], + messageType: [ + { + name: 'GetFileDescriptorSetRequest', + field: [ + { name: 'module', number: 1, label: 'LABEL_OPTIONAL', type: 'TYPE_STRING', jsonName: 'module' }, + { name: 'version', number: 2, label: 'LABEL_OPTIONAL', type: 'TYPE_STRING', jsonName: 'version' }, + { name: 'symbols', number: 3, label: 'LABEL_REPEATED', type: 'TYPE_STRING', jsonName: 'symbols' }, + ], + }, + { + name: 'GetFileDescriptorSetResponse', + field: [ + { + name: 'file_descriptor_set', + number: 1, + label: 'LABEL_OPTIONAL', + type: 'TYPE_MESSAGE', + typeName: '.google.protobuf.FileDescriptorSet', + jsonName: 'fileDescriptorSet', + }, + { name: 'version', number: 2, label: 'LABEL_OPTIONAL', type: 'TYPE_STRING', jsonName: 'version' }, + ], + }, + ], + service: [ + { + name: 'FileDescriptorSetService', + method: [ + { + name: 'GetFileDescriptorSet', + inputType: '.buf.reflect.v1beta1.GetFileDescriptorSetRequest', + outputType: '.buf.reflect.v1beta1.GetFileDescriptorSetResponse', + options: { idempotencyLevel: 'NO_SIDE_EFFECTS' }, + }, + ], + }, + ], + }), + importName => (importName === 'google/protobuf/descriptor.proto' ? file_google_protobuf_descriptor : undefined), ); - const FileDescriptorSetService = { - typeName: 'buf.reflect.v1beta1.FileDescriptorSetService', - methods: { - getFileDescriptorSet: { - name: 'GetFileDescriptorSet', - I: GetFileDescriptorSetRequest, - O: GetFileDescriptorSetResponse, - kind: MethodKind.Unary, - idempotency: MethodIdempotency.NoSideEffects, - }, - }, - } as const; + const FileDescriptorSetService = reflectionRegistry.getService('buf.reflect.v1beta1.FileDescriptorSetService'); + invariant(FileDescriptorSetService, 'FileDescriptorSetService is missing from the reflection descriptor'); const transport = createConnectTransport({ baseUrl: url, httpVersion: '1.1', }); - const client = createPromiseClient(FileDescriptorSetService, transport); + const getFileDescriptorSetMethod = FileDescriptorSetService.method.getFileDescriptorSet; + invariant(getFileDescriptorSetMethod.methodKind === 'unary', 'GetFileDescriptorSet must be a unary method'); const headers: HeadersInit = { ...(disableUserAgentHeader ? {} : { 'User-Agent': `insomnia/${version}` }), ...(apiKey === '' ? {} : { Authorization: `Bearer ${apiKey}` }), }; try { - const res = await client.getFileDescriptorSet( - { - module, - }, - { - headers, - }, - ); + // Calls the transport directly: createClient() cannot type a descriptor built at runtime. + const res = await transport.unary(getFileDescriptorSetMethod as DescMethodUnary, undefined, undefined, headers, { + module, + }); + const response = res.message as { fileDescriptorSet?: FileDescriptorSet }; const methodDefs: MethodDefs[] = []; - if (res.fileDescriptorSet === undefined) { + if (response.fileDescriptorSet === undefined) { return []; } const packageDefinition = protoLoader.loadFileDescriptorSetFromBuffer( - Buffer.from(res.fileDescriptorSet.toBinary()), + Buffer.from(toBinary(FileDescriptorSetSchema, response.fileDescriptorSet)), ); for (const definition of Object.values(packageDefinition)) { const serviceDefinition = asServiceDefinition(definition); diff --git a/packages/insomnia/src/ui/components/panes/grpc-request-pane.tsx b/packages/insomnia/src/ui/components/panes/grpc-request-pane.tsx index aaf8a6a4ef..099f3e3f31 100644 --- a/packages/insomnia/src/ui/components/panes/grpc-request-pane.tsx +++ b/packages/insomnia/src/ui/components/panes/grpc-request-pane.tsx @@ -86,7 +86,9 @@ export const GrpcRequestPane: FunctionComponent = ({ grpcState, setGrpcSt console.log(`[gRPC] loading proto file methods pf=${activeRequest.protoFileId}`); const methods = await window.main.grpc.loadMethods(activeRequest.protoFileId); setGrpcState({ ...grpcState, methods }); - } else if (activeRequest.url && activeRequest.reflectionApi) { + } else if (activeRequest.url && activeRequest.reflectionApi && !activeRequest.reflectionApi.enabled) { + // Intentionally skipped when the buf reflection API is enabled: loading would send the API key on pane open. + // In that case methods are only loaded when the user clicks the reflection refresh button. const requestGroups = ( await db.withAncestors(activeRequest, [models.requestGroup.type]) ).filter(isRequestGroup);