From 48767adce9a197a88e7d11f0c6ac71abd597f5c3 Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Fri, 22 Sep 2023 18:42:50 +0200 Subject: [PATCH] feat: use built-in fetch when available --- lib/build/framework/fastify/index.d.ts | 11 ++-- lib/build/framework/utils.d.ts | 2 +- lib/build/querier.js | 55 +++---------------- lib/build/recipe/dashboard/api/analytics.js | 4 +- .../recipe/thirdparty/providers/github.js | 6 +- .../recipe/thirdparty/providers/gitlab.js | 3 +- .../recipe/thirdparty/providers/utils.js | 6 +- lib/build/utils.d.ts | 1 + lib/build/utils.js | 11 +++- lib/ts/querier.ts | 22 ++++---- lib/ts/recipe/dashboard/api/analytics.ts | 4 +- lib/ts/recipe/thirdparty/providers/github.ts | 6 +- lib/ts/recipe/thirdparty/providers/gitlab.ts | 3 +- lib/ts/recipe/thirdparty/providers/utils.ts | 6 +- lib/ts/utils.ts | 11 +++- 15 files changed, 64 insertions(+), 87 deletions(-) diff --git a/lib/build/framework/fastify/index.d.ts b/lib/build/framework/fastify/index.d.ts index 3cca3a1a2..fb2d3fe21 100644 --- a/lib/build/framework/fastify/index.d.ts +++ b/lib/build/framework/fastify/index.d.ts @@ -1,18 +1,21 @@ // @ts-nocheck /// export type { SessionRequest } from "./framework"; -export declare const plugin: import("fastify").FastifyPluginCallback, import("http").Server>; +export declare const plugin: import("fastify").FastifyPluginCallback< + Record, + import("fastify").RawServerDefault +>; export declare const errorHandler: () => ( err: any, req: import("fastify").FastifyRequest< import("fastify/types/route").RouteGenericInterface, - import("http").Server, + import("fastify").RawServerDefault, import("http").IncomingMessage >, res: import("fastify").FastifyReply< - import("http").Server, + import("fastify").RawServerDefault, import("http").IncomingMessage, - import("http").ServerResponse, + import("http").ServerResponse, import("fastify/types/route").RouteGenericInterface, unknown > diff --git a/lib/build/framework/utils.d.ts b/lib/build/framework/utils.d.ts index 636a97724..123ddf23c 100644 --- a/lib/build/framework/utils.d.ts +++ b/lib/build/framework/utils.d.ts @@ -45,7 +45,7 @@ export declare function setCookieForServerResponse( expires: number, path: string, sameSite: "strict" | "lax" | "none" -): ServerResponse; +): ServerResponse; export declare function getCookieValueToSetInHeader( prev: string | string[] | undefined, val: string | string[], diff --git a/lib/build/querier.js b/lib/build/querier.js index 6546e051b..9daa21bb8 100644 --- a/lib/build/querier.js +++ b/lib/build/querier.js @@ -1,40 +1,4 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; - }, - }); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; - }; var __importDefault = (this && this.__importDefault) || function (mod) { @@ -56,7 +20,6 @@ exports.Querier = void 0; * License for the specific language governing permissions and limitations * under the License. */ -const cross_fetch_1 = __importStar(require("cross-fetch")); const utils_1 = require("./utils"); const version_1 = require("./version"); const normalisedURLPath_1 = __importDefault(require("./normalisedURLPath")); @@ -84,7 +47,7 @@ class Querier { "api-key": Querier.apiKey, }; } - let response = await cross_fetch_1.default(url, { + let response = await utils_1.doFetch(url, { method: "GET", headers, }); @@ -129,7 +92,7 @@ class Querier { if (path.isARecipePath() && this.rIdToCore !== undefined) { headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); } - return cross_fetch_1.default(url, { + return utils_1.doFetch(url, { method: "POST", body: body !== undefined ? JSON.stringify(body) : undefined, headers, @@ -157,7 +120,7 @@ class Querier { const finalURL = new URL(url); const searchParams = new URLSearchParams(params); finalURL.search = searchParams.toString(); - return cross_fetch_1.default(finalURL.toString(), { + return utils_1.doFetch(finalURL.toString(), { method: "DELETE", body: body !== undefined ? JSON.stringify(body) : undefined, headers, @@ -187,7 +150,7 @@ class Querier { Object.entries(params).filter(([_, value]) => value !== undefined) ); finalURL.search = searchParams.toString(); - return await cross_fetch_1.default(finalURL.toString(), { + return utils_1.doFetch(finalURL.toString(), { method: "GET", headers, }); @@ -215,7 +178,7 @@ class Querier { Object.entries(params).filter(([_, value]) => value !== undefined) ); finalURL.search = searchParams.toString(); - return await cross_fetch_1.default(finalURL.toString(), { + return utils_1.doFetch(finalURL.toString(), { method: "GET", headers, }); @@ -238,7 +201,7 @@ class Querier { if (path.isARecipePath() && this.rIdToCore !== undefined) { headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); } - return cross_fetch_1.default(url, { + return utils_1.doFetch(url, { method: "PUT", body: body !== undefined ? JSON.stringify(body) : undefined, headers, @@ -297,9 +260,9 @@ class Querier { err.message.includes("ECONNREFUSED") || err.code === "ECONNREFUSED") ) { - return await this.sendRequestHelper(path, method, requestFunc, numberOfTries - 1, retryInfoMap); + return this.sendRequestHelper(path, method, requestFunc, numberOfTries - 1, retryInfoMap); } - if (err instanceof cross_fetch_1.Response) { + if ("status" in err && "text" in err) { if (err.status === constants_1.RATE_LIMIT_STATUS_CODE) { const retriesLeft = retryInfoMap[url]; if (retriesLeft > 0) { @@ -307,7 +270,7 @@ class Querier { const attemptsMade = maxRetries - retriesLeft; const delay = 10 + 250 * attemptsMade; await new Promise((resolve) => setTimeout(resolve, delay)); - return await this.sendRequestHelper(path, method, requestFunc, numberOfTries, retryInfoMap); + return this.sendRequestHelper(path, method, requestFunc, numberOfTries, retryInfoMap); } } throw new Error( diff --git a/lib/build/recipe/dashboard/api/analytics.js b/lib/build/recipe/dashboard/api/analytics.js index f14cc300d..63615acd5 100644 --- a/lib/build/recipe/dashboard/api/analytics.js +++ b/lib/build/recipe/dashboard/api/analytics.js @@ -24,7 +24,7 @@ const querier_1 = require("../../../querier"); const normalisedURLPath_1 = __importDefault(require("../../../normalisedURLPath")); const version_1 = require("../../../version"); const error_1 = __importDefault(require("../../../error")); -const cross_fetch_1 = __importDefault(require("cross-fetch")); +const utils_1 = require("../../../utils"); async function analyticsPost(_, ___, options, __) { // If telemetry is disabled, dont send any event if (!supertokens_1.default.getInstanceOrThrowError().telemetryEnabled) { @@ -73,7 +73,7 @@ async function analyticsPost(_, ___, options, __) { dashboardVersion, }; try { - await cross_fetch_1.default("https://api.supertokens.com/0/st/telemetry", { + await utils_1.doFetch("https://api.supertokens.com/0/st/telemetry", { method: "POST", body: JSON.stringify(data), headers: { diff --git a/lib/build/recipe/thirdparty/providers/github.js b/lib/build/recipe/thirdparty/providers/github.js index e7fd91dbd..fe6f1585a 100644 --- a/lib/build/recipe/thirdparty/providers/github.js +++ b/lib/build/recipe/thirdparty/providers/github.js @@ -19,7 +19,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); * License for the specific language governing permissions and limitations * under the License. */ -const cross_fetch_1 = __importDefault(require("cross-fetch")); +const utils_1 = require("../../../utils"); const custom_1 = __importDefault(require("./custom")); function getSupertokensUserInfoFromRawUserInfoResponseForGithub(rawUserInfoResponse) { if (rawUserInfoResponse.fromUserInfoAPI === undefined) { @@ -69,13 +69,13 @@ function Github(input) { Accept: "application/vnd.github.v3+json", }; const rawResponse = {}; - const emailInfoResp = await cross_fetch_1.default("https://api.github.com/user/emails", { headers }); + const emailInfoResp = await utils_1.doFetch("https://api.github.com/user/emails", { headers }); if (emailInfoResp.status >= 400) { throw new Error(`Getting userInfo failed with ${emailInfoResp.status}: ${await emailInfoResp.text()}`); } const emailInfo = await emailInfoResp.json(); rawResponse.emails = emailInfo; - const userInfoResp = await cross_fetch_1.default("https://api.github.com/user", { headers }); + const userInfoResp = await utils_1.doFetch("https://api.github.com/user", { headers }); if (userInfoResp.status >= 400) { throw new Error(`Getting userInfo failed with ${userInfoResp.status}: ${await userInfoResp.text()}`); } diff --git a/lib/build/recipe/thirdparty/providers/gitlab.js b/lib/build/recipe/thirdparty/providers/gitlab.js index a7f121c5d..456cb51b7 100644 --- a/lib/build/recipe/thirdparty/providers/gitlab.js +++ b/lib/build/recipe/thirdparty/providers/gitlab.js @@ -20,7 +20,6 @@ var __importDefault = }; Object.defineProperty(exports, "__esModule", { value: true }); const custom_1 = __importDefault(require("./custom")); -// import fetch from "cross-fetch"; // import NormalisedURLDomain from "../../../normalisedURLDomain"; function Gitlab(input) { if (input.config.name === undefined) { @@ -96,7 +95,7 @@ exports.default = Gitlab; // }) { // let accessToken = accessTokenAPIResponse.access_token; // let authHeader = `Bearer ${accessToken}`; -// let response = await fetch(baseUrl + "/api/v4/user", { +// let response = await doFetch(baseUrl + "/api/v4/user", { // method: "get", // headers: { // Authorization: authHeader, diff --git a/lib/build/recipe/thirdparty/providers/utils.js b/lib/build/recipe/thirdparty/providers/utils.js index 7e5896f2c..061daaead 100644 --- a/lib/build/recipe/thirdparty/providers/utils.js +++ b/lib/build/recipe/thirdparty/providers/utils.js @@ -42,11 +42,11 @@ var __importDefault = }; Object.defineProperty(exports, "__esModule", { value: true }); exports.discoverOIDCEndpoints = exports.verifyIdTokenFromJWKSEndpointAndGetPayload = exports.doPostRequest = exports.doGetRequest = void 0; -const cross_fetch_1 = __importDefault(require("cross-fetch")); const jose = __importStar(require("jose")); const normalisedURLDomain_1 = __importDefault(require("../../../normalisedURLDomain")); const normalisedURLPath_1 = __importDefault(require("../../../normalisedURLPath")); const logger_1 = require("../../../logger"); +const utils_1 = require("../../../utils"); async function doGetRequest(url, queryParams, headers) { logger_1.logDebugMessage( `GET request to ${url}, with query params ${JSON.stringify(queryParams)} and headers ${JSON.stringify(headers)}` @@ -56,7 +56,7 @@ async function doGetRequest(url, queryParams, headers) { } const finalURL = new URL(url); finalURL.search = new URLSearchParams(queryParams).toString(); - let response = await cross_fetch_1.default(finalURL.toString(), { + let response = await utils_1.doFetch(finalURL.toString(), { headers: headers, }); if (response.status >= 400) { @@ -80,7 +80,7 @@ async function doPostRequest(url, params, headers) { `POST request to ${url}, with params ${JSON.stringify(params)} and headers ${JSON.stringify(headers)}` ); const body = new URLSearchParams(params).toString(); - let response = await cross_fetch_1.default(url, { + let response = await utils_1.doFetch(url, { method: "POST", body, headers, diff --git a/lib/build/utils.d.ts b/lib/build/utils.d.ts index 6d796ecce..b7b2b17ed 100644 --- a/lib/build/utils.d.ts +++ b/lib/build/utils.d.ts @@ -3,6 +3,7 @@ import type { AppInfo, NormalisedAppinfo, HTTPMethod, JSONObject } from "./types import type { BaseRequest, BaseResponse } from "./framework"; import { User } from "./user"; import { SessionContainer } from "./recipe/session"; +export declare const doFetch: typeof fetch; export declare function getLargestVersionFromIntersection(v1: string[], v2: string[]): string | undefined; export declare function maxVersion(version1: string, version2: string): string; export declare function normaliseInputAppInfoOrThrowError(appInfo: AppInfo): NormalisedAppinfo; diff --git a/lib/build/utils.js b/lib/build/utils.js index b210e2623..a39a8c09c 100644 --- a/lib/build/utils.js +++ b/lib/build/utils.js @@ -41,13 +41,20 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.normaliseEmail = exports.postWithFetch = exports.getFromObjectCaseInsensitive = exports.getTopLevelDomainForSameSiteResolution = exports.setRequestInUserContextIfNotDefined = exports.makeDefaultUserContextFromAPI = exports.humaniseMilliseconds = exports.frontendHasInterceptor = exports.getRidFromHeader = exports.doesRequestSupportFDI = exports.getBackwardsCompatibleUserInfo = exports.isAnIpAddress = exports.send200Response = exports.sendNon200Response = exports.sendNon200ResponseWithMessage = exports.normaliseHttpMethod = exports.normaliseInputAppInfoOrThrowError = exports.maxVersion = exports.getLargestVersionFromIntersection = void 0; +exports.normaliseEmail = exports.postWithFetch = exports.getFromObjectCaseInsensitive = exports.getTopLevelDomainForSameSiteResolution = exports.setRequestInUserContextIfNotDefined = exports.makeDefaultUserContextFromAPI = exports.humaniseMilliseconds = exports.frontendHasInterceptor = exports.getRidFromHeader = exports.doesRequestSupportFDI = exports.getBackwardsCompatibleUserInfo = exports.isAnIpAddress = exports.send200Response = exports.sendNon200Response = exports.sendNon200ResponseWithMessage = exports.normaliseHttpMethod = exports.normaliseInputAppInfoOrThrowError = exports.maxVersion = exports.getLargestVersionFromIntersection = exports.doFetch = void 0; const psl = __importStar(require("psl")); const normalisedURLDomain_1 = __importDefault(require("./normalisedURLDomain")); const normalisedURLPath_1 = __importDefault(require("./normalisedURLPath")); const logger_1 = require("./logger"); const constants_1 = require("./constants"); const cross_fetch_1 = __importDefault(require("cross-fetch")); +const doFetch = (...args) => { + if (typeof fetch !== "undefined") { + return fetch(...args); + } + return cross_fetch_1.default(...args); +}; +exports.doFetch = doFetch; function getLargestVersionFromIntersection(v1, v2) { let intersection = v1.filter((value) => v2.indexOf(value) !== -1); if (intersection.length === 0) { @@ -286,7 +293,7 @@ async function postWithFetch(url, headers, body, { successLog, errorLogHeader }) let error; let resp; try { - const fetchResp = await cross_fetch_1.default(url, { + const fetchResp = await exports.doFetch(url, { method: "POST", body: JSON.stringify(body), headers, diff --git a/lib/ts/querier.ts b/lib/ts/querier.ts index a4ba3b334..59ed5f95e 100644 --- a/lib/ts/querier.ts +++ b/lib/ts/querier.ts @@ -12,9 +12,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import fetch, { Response } from "cross-fetch"; - -import { getLargestVersionFromIntersection } from "./utils"; +import { doFetch, getLargestVersionFromIntersection } from "./utils"; import { cdiSupported } from "./version"; import NormalisedURLDomain from "./normalisedURLDomain"; import NormalisedURLPath from "./normalisedURLPath"; @@ -58,7 +56,7 @@ export class Querier { "api-key": Querier.apiKey, }; } - let response = await fetch(url, { + let response = await doFetch(url, { method: "GET", headers, }); @@ -132,7 +130,7 @@ export class Querier { rid: this.rIdToCore, }; } - return fetch(url, { + return doFetch(url, { method: "POST", body: body !== undefined ? JSON.stringify(body) : undefined, headers, @@ -167,7 +165,7 @@ export class Querier { const searchParams = new URLSearchParams(params); finalURL.search = searchParams.toString(); - return fetch(finalURL.toString(), { + return doFetch(finalURL.toString(), { method: "DELETE", body: body !== undefined ? JSON.stringify(body) : undefined, headers, @@ -206,7 +204,7 @@ export class Querier { Object.entries(params).filter(([_, value]) => value !== undefined) as string[][] ); finalURL.search = searchParams.toString(); - return await fetch(finalURL.toString(), { + return doFetch(finalURL.toString(), { method: "GET", headers, }); @@ -243,7 +241,7 @@ export class Querier { Object.entries(params).filter(([_, value]) => value !== undefined) as string[][] ); finalURL.search = searchParams.toString(); - return await fetch(finalURL.toString(), { + return doFetch(finalURL.toString(), { method: "GET", headers, }); @@ -273,7 +271,7 @@ export class Querier { }; } - return fetch(url, { + return doFetch(url, { method: "PUT", body: body !== undefined ? JSON.stringify(body) : undefined, headers, @@ -350,10 +348,10 @@ export class Querier { err.message.includes("ECONNREFUSED") || err.code === "ECONNREFUSED") ) { - return await this.sendRequestHelper(path, method, requestFunc, numberOfTries - 1, retryInfoMap); + return this.sendRequestHelper(path, method, requestFunc, numberOfTries - 1, retryInfoMap); } - if (err instanceof Response) { + if ("status" in err && "text" in err) { if (err.status === RATE_LIMIT_STATUS_CODE) { const retriesLeft = retryInfoMap[url]; @@ -365,7 +363,7 @@ export class Querier { await new Promise((resolve) => setTimeout(resolve, delay)); - return await this.sendRequestHelper(path, method, requestFunc, numberOfTries, retryInfoMap); + return this.sendRequestHelper(path, method, requestFunc, numberOfTries, retryInfoMap); } } diff --git a/lib/ts/recipe/dashboard/api/analytics.ts b/lib/ts/recipe/dashboard/api/analytics.ts index 010237300..4a58e46e1 100644 --- a/lib/ts/recipe/dashboard/api/analytics.ts +++ b/lib/ts/recipe/dashboard/api/analytics.ts @@ -19,7 +19,7 @@ import { Querier } from "../../../querier"; import NormalisedURLPath from "../../../normalisedURLPath"; import { version as SDKVersion } from "../../../version"; import STError from "../../../error"; -import fetch from "cross-fetch"; +import { doFetch } from "../../../utils"; export type Response = { status: "OK"; @@ -85,7 +85,7 @@ export default async function analyticsPost( }; try { - await fetch("https://api.supertokens.com/0/st/telemetry", { + await doFetch("https://api.supertokens.com/0/st/telemetry", { method: "POST", body: JSON.stringify(data), headers: { diff --git a/lib/ts/recipe/thirdparty/providers/github.ts b/lib/ts/recipe/thirdparty/providers/github.ts index cc733154f..d33b9717c 100644 --- a/lib/ts/recipe/thirdparty/providers/github.ts +++ b/lib/ts/recipe/thirdparty/providers/github.ts @@ -12,7 +12,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import fetch from "cross-fetch"; +import { doFetch } from "../../../utils"; import { ProviderInput, TypeProvider, UserInfo } from "../types"; import NewProvider from "./custom"; @@ -79,14 +79,14 @@ export default function Github(input: ProviderInput): TypeProvider { }; const rawResponse: { [key: string]: any } = {}; - const emailInfoResp = await fetch("https://api.github.com/user/emails", { headers }); + const emailInfoResp = await doFetch("https://api.github.com/user/emails", { headers }); if (emailInfoResp.status >= 400) { throw new Error(`Getting userInfo failed with ${emailInfoResp.status}: ${await emailInfoResp.text()}`); } const emailInfo = await emailInfoResp.json(); rawResponse.emails = emailInfo; - const userInfoResp = await fetch("https://api.github.com/user", { headers }); + const userInfoResp = await doFetch("https://api.github.com/user", { headers }); if (userInfoResp.status >= 400) { throw new Error(`Getting userInfo failed with ${userInfoResp.status}: ${await userInfoResp.text()}`); } diff --git a/lib/ts/recipe/thirdparty/providers/gitlab.ts b/lib/ts/recipe/thirdparty/providers/gitlab.ts index ef8552386..a390be591 100644 --- a/lib/ts/recipe/thirdparty/providers/gitlab.ts +++ b/lib/ts/recipe/thirdparty/providers/gitlab.ts @@ -15,7 +15,6 @@ import { TypeProvider, ProviderInput } from "../types"; import NewProvider from "./custom"; -// import fetch from "cross-fetch"; // import NormalisedURLDomain from "../../../normalisedURLDomain"; export default function Gitlab(input: ProviderInput): TypeProvider { @@ -102,7 +101,7 @@ export default function Gitlab(input: ProviderInput): TypeProvider { // }) { // let accessToken = accessTokenAPIResponse.access_token; // let authHeader = `Bearer ${accessToken}`; -// let response = await fetch(baseUrl + "/api/v4/user", { +// let response = await doFetch(baseUrl + "/api/v4/user", { // method: "get", // headers: { // Authorization: authHeader, diff --git a/lib/ts/recipe/thirdparty/providers/utils.ts b/lib/ts/recipe/thirdparty/providers/utils.ts index 28dcf6e26..4a2ebb98b 100644 --- a/lib/ts/recipe/thirdparty/providers/utils.ts +++ b/lib/ts/recipe/thirdparty/providers/utils.ts @@ -1,10 +1,10 @@ -import fetch from "cross-fetch"; import * as jose from "jose"; import { ProviderConfigForClientType } from "../types"; import NormalisedURLDomain from "../../../normalisedURLDomain"; import NormalisedURLPath from "../../../normalisedURLPath"; import { logDebugMessage } from "../../../logger"; +import { doFetch } from "../../../utils"; export async function doGetRequest( url: string, @@ -22,7 +22,7 @@ export async function doGetRequest( } const finalURL = new URL(url); finalURL.search = new URLSearchParams(queryParams).toString(); - let response = await fetch(finalURL.toString(), { + let response = await doFetch(finalURL.toString(), { headers: headers, }); @@ -53,7 +53,7 @@ export async function doPostRequest( ); const body = new URLSearchParams(params).toString(); - let response = await fetch(url, { + let response = await doFetch(url, { method: "POST", body, headers, diff --git a/lib/ts/utils.ts b/lib/ts/utils.ts index 3ccb32f2c..294ec88e0 100644 --- a/lib/ts/utils.ts +++ b/lib/ts/utils.ts @@ -6,10 +6,17 @@ import NormalisedURLPath from "./normalisedURLPath"; import type { BaseRequest, BaseResponse } from "./framework"; import { logDebugMessage } from "./logger"; import { HEADER_FDI, HEADER_RID } from "./constants"; -import fetch from "cross-fetch"; +import crossFetch from "cross-fetch"; import { User } from "./user"; import { SessionContainer } from "./recipe/session"; +export const doFetch: typeof fetch = (...args) => { + if (typeof fetch !== "undefined") { + return fetch(...args); + } + return crossFetch(...args); +}; + export function getLargestVersionFromIntersection(v1: string[], v2: string[]): string | undefined { let intersection = v1.filter((value) => v2.indexOf(value) !== -1); if (intersection.length === 0) { @@ -276,7 +283,7 @@ export async function postWithFetch( let error; let resp: { status: number; body: any }; try { - const fetchResp = await fetch(url, { + const fetchResp = await doFetch(url, { method: "POST", body: JSON.stringify(body), headers,