From 17625dea4788723933e50cc9391b26a8b3a18740 Mon Sep 17 00:00:00 2001 From: Joe Hanley Date: Thu, 25 Jun 2026 15:32:47 -0700 Subject: [PATCH] feat: migrate API client and codebase to native fetch --- npm-shrinkwrap.json | 257 ++++++++++-------- package.json | 4 +- src/apiv2.ts | 132 ++++++--- src/apphosting/backend.ts | 1 - src/database/import.ts | 6 +- src/dataconnect/webhook.ts | 4 +- .../functions/runtimes/discovery/index.ts | 5 +- src/deploy/functions/runtimes/node/index.ts | 1 - src/deploy/functions/runtimes/python/index.ts | 1 - src/deploy/hosting/uploader.ts | 3 +- src/downloadUtils.ts | 4 +- src/emulator/auth/operations.ts | 22 +- src/emulator/taskQueue.ts | 10 +- src/extensions/extensionsHelper.ts | 8 +- src/hosting/initMiddleware.ts | 2 +- src/hosting/proxy.ts | 61 +++-- src/profiler.ts | 1 - src/track.ts | 1 - 18 files changed, 324 insertions(+), 199 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 0a875757285..72a9646e2b8 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -71,6 +71,7 @@ "tcp-port-used": "^1.0.2", "tmp": "^0.2.3", "triple-beam": "^1.3.0", + "undici": "^6.19.0", "universal-analytics": "^0.5.3", "update-notifier-cjs": "^5.1.6", "winston": "^3.0.0", @@ -436,6 +437,15 @@ "node": ">=16.12.0" } }, + "node_modules/@astrojs/telemetry/node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@astrojs/telemetry/node_modules/ci-info": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", @@ -501,6 +511,18 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/@astrojs/telemetry/node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dev": true, + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/@astrojs/webapi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@astrojs/webapi/-/webapi-2.1.0.tgz", @@ -5169,7 +5191,6 @@ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*", "form-data": "^4.0.4" @@ -10815,6 +10836,25 @@ } } }, + "node_modules/gaxios/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/gaxios/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/gaxios/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/gcp-metadata": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", @@ -13244,7 +13284,7 @@ "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true }, "node_modules/json5": { @@ -13660,12 +13700,6 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, - "node_modules/lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", - "dev": true - }, "node_modules/lodash.snakecase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", @@ -15601,14 +15635,13 @@ } }, "node_modules/nock": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.0.5.tgz", - "integrity": "sha512-1ILZl0zfFm2G4TIeJFW0iHknxr2NyA+aGCMTjDVUsBY4CkMRispF1pfIYkTRdAR/3Bg+UzdEuK0B6HczMQZcCg==", + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", + "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==", "dev": true, "dependencies": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", - "lodash.set": "^4.3.2", "propagate": "^2.0.0" }, "engines": { @@ -15616,12 +15649,12 @@ } }, "node_modules/nock/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -15633,9 +15666,9 @@ } }, "node_modules/nock/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "node_modules/node-domexception": { @@ -15703,6 +15736,25 @@ "node": "4.x || >=6.0.0" } }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -17695,6 +17747,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "engines": { "node": ">=6" } @@ -20231,17 +20284,6 @@ "lodash": "^4.17.10" } }, - "node_modules/tr46": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", - "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -20605,15 +20647,11 @@ "license": "MIT" }, "node_modules/undici": { - "version": "5.21.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.21.2.tgz", - "integrity": "sha512-f6pTQ9RF4DQtwoWSaC42P/NKlUjvezVvd9r155ohqkwFNRyBKM3f3pcty3ouusefNRyM25XhIQEbeQ46sZDJfQ==", - "dev": true, - "dependencies": { - "busboy": "^1.6.0" - }, + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", + "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", "engines": { - "node": ">=12.18" + "node": ">=18.17" } }, "node_modules/undici-types": { @@ -21353,14 +21391,6 @@ "node": ">= 8" } }, - "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==", - "engines": { - "node": ">=12" - } - }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -21384,18 +21414,6 @@ "node": ">=0.8.0" } }, - "node_modules/whatwg-url": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", - "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", - "dependencies": { - "tr46": "^5.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", @@ -22114,6 +22132,12 @@ "which-pm-runs": "^1.1.0" }, "dependencies": { + "@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true + }, "ci-info": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", @@ -22151,6 +22175,15 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true + }, + "undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dev": true, + "requires": { + "@fastify/busboy": "^2.0.0" + } } } }, @@ -29677,7 +29710,26 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "requires": { - "whatwg-url": "^14.0.0" + "whatwg-url": "^5.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } } } @@ -31465,7 +31517,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true }, "json5": { @@ -31823,12 +31875,6 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, - "lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", - "dev": true - }, "lodash.snakecase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", @@ -33173,30 +33219,29 @@ } }, "nock": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.0.5.tgz", - "integrity": "sha512-1ILZl0zfFm2G4TIeJFW0iHknxr2NyA+aGCMTjDVUsBY4CkMRispF1pfIYkTRdAR/3Bg+UzdEuK0B6HczMQZcCg==", + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", + "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==", "dev": true, "requires": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", - "lodash.set": "^4.3.2", "propagate": "^2.0.0" }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true } } @@ -33222,7 +33267,28 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "requires": { - "whatwg-url": "^14.0.0" + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } } }, "node-fetch-h2": { @@ -34707,7 +34773,8 @@ "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true }, "punycode.js": { "version": "2.3.1", @@ -36606,14 +36673,6 @@ "lodash": "^4.17.10" } }, - "tr46": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", - "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", - "requires": { - "punycode": "^2.3.1" - } - }, "trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -36872,13 +36931,9 @@ "dev": true }, "undici": { - "version": "5.21.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.21.2.tgz", - "integrity": "sha512-f6pTQ9RF4DQtwoWSaC42P/NKlUjvezVvd9r155ohqkwFNRyBKM3f3pcty3ouusefNRyM25XhIQEbeQ46sZDJfQ==", - "dev": true, - "requires": { - "busboy": "^1.6.0" - } + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", + "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==" }, "undici-types": { "version": "7.16.0", @@ -37392,11 +37447,6 @@ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" }, - "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==" - }, "websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -37414,15 +37464,6 @@ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, - "whatwg-url": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", - "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", - "requires": { - "tr46": "^5.0.0", - "webidl-conversions": "^7.0.0" - } - }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", diff --git a/package.json b/package.json index c8bf741a081..1c6a0d94950 100644 --- a/package.json +++ b/package.json @@ -170,6 +170,7 @@ "triple-beam": "^1.3.0", "universal-analytics": "^0.5.3", "update-notifier-cjs": "^5.1.6", + "undici": "^6.19.0", "winston": "^3.0.0", "winston-transport": "^4.4.0", "ws": "^7.5.10", @@ -270,9 +271,6 @@ "ajv-formats": "3.0.1", "ajv": "^8.17.1" }, - "node-fetch": { - "whatwg-url": "^14.0.0" - }, "hono": "^4.11.4", "@tootallnate/once": "^3.0.1", "protobufjs": "^7.4.0", diff --git a/src/apiv2.ts b/src/apiv2.ts index a478afa264d..7f50898df0f 100644 --- a/src/apiv2.ts +++ b/src/apiv2.ts @@ -1,10 +1,7 @@ -import { AbortSignal } from "abort-controller"; import { URL, URLSearchParams } from "url"; import { Readable } from "stream"; -import { ProxyAgent } from "proxy-agent"; +import { ProxyAgent, request } from "undici"; import * as retry from "retry"; -import AbortController from "abort-controller"; -import fetch, { HeadersInit, Response, RequestInit, Headers } from "node-fetch"; import * as http from "http"; import * as https from "https"; import util from "util"; @@ -38,7 +35,6 @@ const GOOG_QUOTA_USER_HEADER = "x-goog-quota-user"; // Header for specifying a quota project. See https://cloud.google.com/apis/docs/system-parameters#project-header export const GOOG_USER_PROJECT_HEADER = "x-goog-user-project"; -const GOOGLE_CLOUD_QUOTA_PROJECT = process.env.GOOGLE_CLOUD_QUOTA_PROJECT; export const CLI_OAUTH_PROJECT_NUMBER = "563584335869"; export type HttpMethod = @@ -150,13 +146,16 @@ export async function getAccessToken(): Promise { } function proxyURIFromEnv(): string | undefined { - return ( + const uri = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || - undefined - ); + undefined; + if (uri === "undefined" || uri === "null") { + return undefined; + } + return uri; } // Some networks (and recent Node.js security releases) interact badly with @@ -198,6 +197,12 @@ export type ClientOptions = { auth?: boolean; }; +interface FetchOptions extends RequestInit { + dispatcher?: ProxyAgent; + duplex?: "half"; + agent?: http.Agent | https.Agent | ((parsedUrl: URL) => http.Agent | https.Agent); +} + export class Client { constructor(private opts: ClientOptions) { if (this.opts.auth === undefined) { @@ -357,10 +362,10 @@ export class Client { } if ( !reqOptions.ignoreQuotaProject && - GOOGLE_CLOUD_QUOTA_PROJECT && - GOOGLE_CLOUD_QUOTA_PROJECT !== "" + process.env.GOOGLE_CLOUD_QUOTA_PROJECT && + process.env.GOOGLE_CLOUD_QUOTA_PROJECT !== "" ) { - reqOptions.headers.set(GOOG_USER_PROJECT_HEADER, GOOGLE_CLOUD_QUOTA_PROJECT); + reqOptions.headers.set(GOOG_USER_PROJECT_HEADER, process.env.GOOGLE_CLOUD_QUOTA_PROJECT); } return reqOptions; } @@ -409,24 +414,19 @@ export class Client { } } - const fetchOptions: RequestInit = { + const fetchOptions: FetchOptions = { headers: options.headers, method: options.method, redirect: options.redirect, - compress: options.compress, }; - if (proxyURIFromEnv()) { - fetchOptions.agent = new ProxyAgent(); + const proxyURI = proxyURIFromEnv(); + if (proxyURI) { + fetchOptions.dispatcher = new ProxyAgent({ uri: proxyURI }); } if (options.signal) { - const signal = options.signal as any; - signal.reason = ""; - signal.throwIfAborted = () => { - throw new FirebaseError("Aborted"); - }; - fetchOptions.signal = signal; + fetchOptions.signal = options.signal; } let reqTimeout: NodeJS.Timeout | undefined; @@ -435,12 +435,7 @@ export class Client { reqTimeout = setTimeout(() => { controller.abort(); }, options.timeout); - const signal = controller.signal as any; - signal.reason = ""; - signal.throwIfAborted = () => { - throw new FirebaseError("Aborted"); - }; - fetchOptions.signal = signal; + fetchOptions.signal = controller.signal; } // A request can only be safely retried if its body can be sent again. @@ -449,11 +444,12 @@ export class Client { // Raw streams cannot be replayed and therefore disable the keep-alive retry. let bodyReplayable = true; if (typeof options.body === "string" || Buffer.isBuffer(options.body)) { - fetchOptions.body = options.body; + fetchOptions.body = options.body as any; } else if (options.body instanceof FormData) { - fetchOptions.body = options.body.getBuffer(); + fetchOptions.body = options.body.getBuffer() as any; } else if (isStream(options.body)) { - fetchOptions.body = options.body; + fetchOptions.body = options.body as any; + fetchOptions.duplex = "half"; bodyReplayable = false; } else if (options.body !== undefined) { fetchOptions.body = JSON.stringify(options.body); @@ -493,9 +489,38 @@ export class Client { } this.logRequest(options); try { - res = await fetch(fetchURL, fetchOptions); + if (options.compress === false) { + const undiciOptions: any = { + method: fetchOptions.method, + headers: {}, + decompress: false, + }; + if (fetchOptions.dispatcher) { + undiciOptions.dispatcher = fetchOptions.dispatcher; + } + if (fetchOptions.signal) { + undiciOptions.signal = fetchOptions.signal; + } + if (fetchOptions.body) { + undiciOptions.body = fetchOptions.body; + } + if (fetchOptions.headers) { + for (const [key, value] of (fetchOptions.headers as any).entries()) { + undiciOptions.headers[key] = value; + } + } + const undiciRes = await request(fetchURL, undiciOptions); + res = new UndiciResponseCompat(undiciRes) as any; + } else { + res = await fetch(fetchURL, fetchOptions); + } } catch (thrown: any) { - const err = thrown instanceof Error ? thrown : new Error(thrown); + const err = + thrown && typeof thrown === "object" && thrown.cause instanceof Error + ? thrown.cause + : thrown instanceof Error + ? thrown + : new Error(thrown); logger.debug( `*** [apiv2] error from fetch(${fetchURL}, ${JSON.stringify(fetchOptions)}): ${err}`, ); @@ -532,7 +557,17 @@ export class Client { } else if (options.responseType === "xml") { body = (await res.text()) as unknown as ResT; } else if (options.responseType === "stream") { - body = res.body as unknown as ResT; + if (res.body) { + if (typeof (res.body as any).getReader === "function") { + body = Readable.fromWeb(res.body as any) as unknown as ResT; + } else { + body = res.body as unknown as ResT; + } + } else { + const emptyStream = new Readable(); + emptyStream.push(null); + body = emptyStream as unknown as ResT; + } } else { throw new FirebaseError(`Unable to interpret response. Please set responseType.`, { exit: 2, @@ -650,6 +685,37 @@ function isLocalInsecureRequest(urlPrefix: string): boolean { return u.protocol === "http:"; } +class UndiciResponseCompat { + readonly status: number; + readonly headers: Headers; + readonly body: any; + readonly ok: boolean; + + constructor(private undiciRes: any) { + this.status = undiciRes.statusCode; + this.ok = undiciRes.statusCode >= 200 && undiciRes.statusCode < 300; + this.headers = new Headers(); + for (const [key, value] of Object.entries(undiciRes.headers)) { + if (Array.isArray(value)) { + for (const v of value) { + this.headers.append(key, v); + } + } else if (value !== undefined) { + this.headers.set(key, String(value)); + } + } + this.body = undiciRes.body; + } + + async text(): Promise { + if (!this.body) { + return ""; + } + const { streamToString } = require("./utils"); + return streamToString(this.body); + } +} + function bodyToString(body: unknown): string { if (isStream(body)) { // Don't attempt to read any stream type, in case the caller needs it. diff --git a/src/apphosting/backend.ts b/src/apphosting/backend.ts index 74097355659..2653d6b2283 100644 --- a/src/apphosting/backend.ts +++ b/src/apphosting/backend.ts @@ -25,7 +25,6 @@ import { DeepOmit } from "../metaprogramming"; import { webApps } from "./app"; import { GitRepositoryLink } from "../gcp/devConnect"; import * as ora from "ora"; -import fetch from "node-fetch"; import { orchestrateRollout } from "./rollout"; import * as fuzzy from "fuzzy"; import { isEnabled } from "../experiments"; diff --git a/src/database/import.ts b/src/database/import.ts index 73355744506..804c850085f 100644 --- a/src/database/import.ts +++ b/src/database/import.ts @@ -6,7 +6,6 @@ import * as StreamObject from "stream-json/streamers/StreamObject"; import { URL } from "url"; import { Client, ClientResponse } from "../apiv2"; -import { FetchError } from "node-fetch"; import { FirebaseError } from "../error"; import { pLimit, Limit } from "../utils"; @@ -240,8 +239,9 @@ export default class DatabaseImporter { } catch (err: any) { const isTimeoutErr = err instanceof FirebaseError && - err.original instanceof FetchError && - err.original.code === "ETIMEDOUT"; + (err.original?.name === "AbortError" || + (err.original as any)?.code === "ETIMEDOUT" || + (err.original as any)?.cause?.code === "ETIMEDOUT"); if (isTimeoutErr) { // RTDB connection timeouts are transient and can be retried await new Promise((res) => setTimeout(res, this.nonFatalRetryTimeout)); diff --git a/src/dataconnect/webhook.ts b/src/dataconnect/webhook.ts index b194a0f9abf..31c3ff9c677 100644 --- a/src/dataconnect/webhook.ts +++ b/src/dataconnect/webhook.ts @@ -2,9 +2,7 @@ * Webhook send API used to notify VSCode of states within */ -import fetch from "node-fetch"; import { logger } from "../logger"; -import { AbortSignal } from "node-fetch/externals"; export enum VSCODE_MESSAGE { EMULATORS_STARTED = "EMULATORS_STARTED", @@ -33,7 +31,7 @@ export async function sendVSCodeMessage(body: WebhookBody) { "x-mantle-admin": "all", }, body: jsonBody, - signal: AbortSignal.timeout(3000) as unknown as AbortSignal, // necessary due to https://github.com/node-fetch/node-fetch/issues/1652 + signal: AbortSignal.timeout(3000), }); } catch (e) { logger.debug( diff --git a/src/deploy/functions/runtimes/discovery/index.ts b/src/deploy/functions/runtimes/discovery/index.ts index eac941dee12..28f1380b83a 100644 --- a/src/deploy/functions/runtimes/discovery/index.ts +++ b/src/deploy/functions/runtimes/discovery/index.ts @@ -1,4 +1,3 @@ -import fetch, { Response } from "node-fetch"; import * as fs from "fs"; import * as path from "path"; import * as yaml from "yaml"; @@ -97,9 +96,11 @@ export async function detectFromPort( res = await Promise.race([fetch(url), timedOut]); break; } catch (err: any) { + const realErr = err?.cause || err; if ( err?.name === "FetchError" || - ["ECONNREFUSED", "ECONNRESET", "ETIMEDOUT"].includes(err?.code) + realErr?.name === "FetchError" || + ["ECONNREFUSED", "ECONNRESET", "ETIMEDOUT"].includes(realErr?.code) ) { continue; } diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index 03bc651d4c9..17f843f924f 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -4,7 +4,6 @@ import * as path from "path"; import * as portfinder from "portfinder"; import * as semver from "semver"; import * as spawn from "cross-spawn"; -import fetch from "node-fetch"; import { ChildProcess } from "child_process"; import { FirebaseError } from "../../../../error"; diff --git a/src/deploy/functions/runtimes/python/index.ts b/src/deploy/functions/runtimes/python/index.ts index b78104336e9..0bd3d343a8e 100644 --- a/src/deploy/functions/runtimes/python/index.ts +++ b/src/deploy/functions/runtimes/python/index.ts @@ -1,6 +1,5 @@ import * as fs from "fs"; import * as path from "path"; -import fetch from "node-fetch"; import { promisify } from "util"; import * as portfinder from "portfinder"; diff --git a/src/deploy/hosting/uploader.ts b/src/deploy/hosting/uploader.ts index 3cf1061afb3..38219c24d42 100644 --- a/src/deploy/hosting/uploader.ts +++ b/src/deploy/hosting/uploader.ts @@ -1,5 +1,4 @@ import { size } from "lodash"; -import AbortController from "abort-controller"; import * as clc from "colorette"; import * as crypto from "crypto"; import * as fs from "fs"; @@ -250,7 +249,7 @@ export class Uploader { logger.debug( `[hosting][upload] ${this.hashMap[toUpload]} (${toUpload}) HTTP ERROR ${ res.status - }: headers=${JSON.stringify(res.response.headers.raw())} ${errorMessage}`, + }: headers=${JSON.stringify(Object.fromEntries((res.response.headers as any).entries()))} ${errorMessage}`, ); throw new Error(`Unexpected error while uploading file: ${errorMessage}`); } diff --git a/src/downloadUtils.ts b/src/downloadUtils.ts index 0c9ded143b5..3d0b2406489 100644 --- a/src/downloadUtils.ts +++ b/src/downloadUtils.ts @@ -28,7 +28,9 @@ export async function downloadToTmp(remoteUrl: string, auth = false): Promise { - throw new FirebaseError("Aborted"); - }; + try { + if (!("reason" in signal)) { + signal.reason = ""; + } + } catch (e) { + // Ignore if read-only + } + try { + if (!("throwIfAborted" in signal)) { + signal.throwIfAborted = () => { + throw new FirebaseError("Aborted"); + }; + } + } catch (e) { + // Ignore if read-only + } const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, diff --git a/src/emulator/taskQueue.ts b/src/emulator/taskQueue.ts index 58d4de0c6e9..4d7de6cab8d 100644 --- a/src/emulator/taskQueue.ts +++ b/src/emulator/taskQueue.ts @@ -1,9 +1,6 @@ -import AbortController from "abort-controller"; import { EmulatorLogger } from "./emulatorLogger"; import { RetryConfig, Task, TaskQueueConfig } from "./tasksEmulator"; import { Emulators } from "./types"; -import { FirebaseError } from "../error"; -import fetch from "node-fetch"; class Node { public data: T; @@ -294,16 +291,11 @@ export class TaskQueue { headers["X-CloudTasks-TaskPreviousResponse"] = `${emulatedTask.metadata.previousResponse}`; } const controller = new AbortController(); - const signal = controller.signal as any; - signal.reason = ""; - signal.throwIfAborted = () => { - throw new FirebaseError("Aborted"); - }; const request = fetch(emulatedTask.task.httpRequest.url, { method: "POST", headers: headers, body: JSON.stringify(emulatedTask.task.httpRequest.body), - signal: signal, + signal: controller.signal, }); const dispatchDeadline = emulatedTask.task.dispatchDeadline; diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 396f318ef55..d42f7543216 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -3,8 +3,8 @@ import * as ora from "ora"; import * as semver from "semver"; import * as tmp from "tmp"; import * as fs from "fs-extra"; -import fetch from "node-fetch"; import * as path from "path"; +import { Readable } from "stream"; import { marked } from "marked"; import { markedTerminal } from "marked-terminal"; @@ -751,8 +751,10 @@ async function fetchExtensionSource( )}. Please check that the repo is public and that the source ref is valid.`; try { const response = await fetch(archiveUri); - if (response.ok) { - await response.body.pipe(createUnzipTransform(tempDirectory.name)).promise(); + if (response.ok && response.body) { + await Readable.fromWeb(response.body as any) + .pipe(createUnzipTransform(tempDirectory.name)) + .promise(); } } catch (err) { throw new FirebaseError(archiveErrorMessage); diff --git a/src/hosting/initMiddleware.ts b/src/hosting/initMiddleware.ts index d2ae03625c1..afc6710dd9d 100644 --- a/src/hosting/initMiddleware.ts +++ b/src/hosting/initMiddleware.ts @@ -41,7 +41,7 @@ export function initMiddleware( if (sdkRes.status === 404) { return next(); } - for (const [key, value] of Object.entries(sdkRes.response.headers.raw())) { + for (const [key, value] of (sdkRes.response.headers as any).entries()) { res.setHeader(key, value); } sdkRes.body.pipe(res); diff --git a/src/hosting/proxy.ts b/src/hosting/proxy.ts index 11f619dfce7..8c771940508 100644 --- a/src/hosting/proxy.ts +++ b/src/hosting/proxy.ts @@ -1,5 +1,4 @@ import { capitalize, includes } from "lodash"; -import { FetchError, Headers } from "node-fetch"; import { IncomingMessage, ServerResponse } from "http"; import { PassThrough } from "stream"; import { Request, RequestHandler, Response } from "express"; @@ -106,51 +105,64 @@ export function proxyRequestHandler( compress: false, }); } catch (err: any) { + logger.error("[PROXY ERROR]", err); const isAbortError = err instanceof FirebaseError && err.original?.name.includes("AbortError"); const isTimeoutError = err instanceof FirebaseError && - err.original instanceof FetchError && - err.original.code === "ETIMEDOUT"; + ((err.original as any)?.code === "ETIMEDOUT" || + (err.original as any)?.cause?.code === "ETIMEDOUT"); const isSocketTimeoutError = err instanceof FirebaseError && - err.original instanceof FetchError && - err.original.code === "ESOCKETTIMEDOUT"; + ((err.original as any)?.code === "ESOCKETTIMEDOUT" || + (err.original as any)?.cause?.code === "ESOCKETTIMEDOUT"); if (isAbortError || isTimeoutError || isSocketTimeoutError) { res.statusCode = 504; return res.end("Timed out waiting for function to respond.\n"); } res.statusCode = 500; return res.end(`An internal error occurred while proxying for ${rewriteIdentifier}\n`); + } finally { + if (passThrough) { + passThrough.resume(); + } + } + + const resHeaders: Record = {}; + for (const [key, value] of (proxyRes.response.headers as any).entries()) { + resHeaders[key.toLowerCase()] = value; } if (proxyRes.status === 404) { - // x-cascade is not a string[]. - const cascade = proxyRes.response.headers.get("x-cascade"); - if (options.forceCascade || (cascade && cascade.toUpperCase() === "PASS")) { + const cascade = resHeaders["x-cascade"]; + if ( + options.forceCascade || + (typeof cascade === "string" && cascade.toUpperCase() === "PASS") + ) { return next(); } } // default to private cache - if (!proxyRes.response.headers.get("cache-control")) { - proxyRes.response.headers.set("cache-control", "private"); + if (!resHeaders["cache-control"]) { + resHeaders["cache-control"] = "private"; } // don't allow cookies to be set on non-private cached responses - const cc = proxyRes.response.headers.get("cache-control"); - if (cc && !cc.includes("private")) { - proxyRes.response.headers.delete("set-cookie"); + const cc = resHeaders["cache-control"]; + if (typeof cc === "string" && !cc.includes("private")) { + delete resHeaders["set-cookie"]; } - proxyRes.response.headers.set("vary", makeVary(proxyRes.response.headers.get("vary"))); + const vary = resHeaders["vary"]; + resHeaders["vary"] = makeVary(typeof vary === "string" ? vary : null); // Fix the location header that `node-fetch` attempts to helpfully fix: // https://github.com/node-fetch/node-fetch/blob/4abbfd231f4bce7dbe65e060a6323fc6917fd6d9/src/index.js#L117-L120 // Filed a bug in `node-fetch` to either document the change or fix it: // https://github.com/node-fetch/node-fetch/issues/1086 - const location = proxyRes.response.headers.get("location"); - if (location) { + const location = resHeaders["location"]; + if (typeof location === "string" && location) { // If parsing the URL fails, it may be because the location header // isn't a helpeful resolved URL (if node-fetch changes behavior). This // try is a preventative measure to ensure such a change shouldn't break @@ -161,7 +173,7 @@ export function proxyRequestHandler( // "fixed" header is the same as the origin of the outbound request. if (locationURL.origin === u.origin) { const unborkedLocation = location.replace(locationURL.origin, ""); - proxyRes.response.headers.set("location", unborkedLocation); + resHeaders["location"] = unborkedLocation; } } catch (e: any) { logger.debug( @@ -170,11 +182,20 @@ export function proxyRequestHandler( } } - for (const [key, value] of Object.entries(proxyRes.response.headers.raw())) { - res.setHeader(key, value as string[]); + for (const key of Object.keys(resHeaders)) { + const value = resHeaders[key]; + if (key === "set-cookie") { + if (typeof (proxyRes.response.headers as any).getSetCookie === "function") { + res.setHeader(key, (proxyRes.response.headers as any).getSetCookie()); + } else { + res.setHeader(key, value); + } + } else { + res.setHeader(key, value); + } } res.statusCode = proxyRes.status; - proxyRes.response.body.pipe(res); + proxyRes.body.pipe(res); }; } diff --git a/src/profiler.ts b/src/profiler.ts index a7df6d28568..7f560e319d0 100644 --- a/src/profiler.ts +++ b/src/profiler.ts @@ -2,7 +2,6 @@ import * as fs from "fs"; import * as ora from "ora"; import * as readline from "readline"; import * as tmp from "tmp"; -import AbortController from "abort-controller"; import { Client } from "./apiv2"; import { realtimeOriginOrEmulatorOrCustomUrl } from "./database/api"; diff --git a/src/track.ts b/src/track.ts index cf858abc6e4..68d88d9d9d6 100644 --- a/src/track.ts +++ b/src/track.ts @@ -1,4 +1,3 @@ -import fetch from "node-fetch"; import { randomUUID } from "crypto"; import { getGlobalDefaultAccount } from "./auth";