UNPKG

@gw2api/fetch

Version:

Tiny wrapper around fetch that returns type-safe responses

88 lines (87 loc) 4.35 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; export function fetchGw2Api() { return __awaiter(this, arguments, void 0, function* (...[endpoint, options]) { var _a; const url = new URL(endpoint, 'https://api.guildwars2.com/'); if (options.schema) { url.searchParams.set('v', options.schema); } if (hasLanguage(options)) { url.searchParams.set('lang', options.language); } if (hasAccessToken(options)) { url.searchParams.set('access_token', options.accessToken); } // build request let request = new Request(url, { // The GW2 API never uses redirects, so we want to error if we encounter one. // We use `manual` instead of `error` here so we can throw our own `Gw2ApiError` with the response attached redirect: 'manual', // set signal and cache from options signal: options.signal, cache: options.cache }); // if there is a onRequest handler registered, let it modify the request if (options.onRequest) { request = yield options.onRequest(request); if (!(request instanceof Request)) { throw new Error(`onRequest has to return a Request`); } } // call the API const response = yield fetch(request); // call onResponse handler yield ((_a = options.onResponse) === null || _a === void 0 ? void 0 : _a.call(options, response)); // check if the response is json (`application/json; charset=utf-8`) const isJson = response.headers.get('content-type').startsWith('application/json'); // censor access token in url to not leak it in error messages const erroredUrl = hasAccessToken(options) ? url.toString().replace(options.accessToken, '***') : url.toString(); // check if the response is an error if (!response.ok) { // if the response is JSON, it might have more details in the `text` prop if (isJson) { const error = yield response.json(); if (typeof error === 'object' && 'text' in error && typeof error.text === 'string') { throw new Gw2ApiError(`The GW2 API call to '${erroredUrl}' returned ${response.status} ${response.statusText}: ${error.text}.`, response); } } // otherwise just throw error with the status code throw new Gw2ApiError(`The GW2 API call to '${erroredUrl}' returned ${response.status} ${response.statusText}.`, response); } // if the response is not JSON, throw an error if (!isJson) { throw new Gw2ApiError(`The GW2 API call to '${erroredUrl}' did not respond with a JSON response`, response); } // parse json const json = yield response.json(); // check that json is not `["v1", "v2"]` which sometimes happens for authenticated endpoints if (url.toString() !== 'https://api.guildwars2.com/' && Array.isArray(json) && json.length === 2 && json[0] === 'v1' && json[1] === 'v2') { throw new Gw2ApiError(`The GW2 API call to '${erroredUrl}' did returned an invalid response (["v1", "v2"])`, response); } // TODO: catch more errors return json; }); } export class Gw2ApiError extends Error { constructor(message, response) { super(message); this.response = response; this.name = 'Gw2ApiError'; } } function hasLanguage(options) { return 'language' in options; } function hasAccessToken(options) { return 'accessToken' in options; }