UNPKG

@blizzard-api/client

Version:

A node.js client to integrate with the blizzard battle.net api.

295 lines (224 loc) 7.65 kB
import { setTimeout } from "node:timers"; import { stringify } from "node:querystring"; import { getBlizzardApi } from "@blizzard-api/core"; import ky from "ky"; //#region src/client/client.ts /** * A Blizzard API client. * @classdesc A client to interact with the Blizzard API. * @example * const client = new BlizzardApiClient({ * key: 'client', * secret: 'secret', * origin: 'eu', * locale: 'en_GB', * token: 'access' * }); */ var BlizzardApiClient = class { defaults; ky; constructor(options) { const { locale, origin } = getBlizzardApi(options.origin, options.locale); this.defaults = { key: options.key, locale, origin, secret: options.secret, token: options.token }; this.ky = ky.create(options.kyOptions); } /** * Get an access token. * @param options The access token request arguments. See {@link AccessTokenRequestArguments}. * @returns The access token. See {@link AccessToken}. * @example * const response = await client.getAccessToken(); * const { access_token, token_type, expires_in, sub } = response; * console.log(access_token, token_type, expires_in, sub); * // => 'access' * // => 'bearer' * // => 86399 * // => 'client-id' */ getAccessToken = async (options) => { const { key, origin, secret } = { ...this.defaults, ...options }; const basicAuth = Buffer.from(`${key}:${secret}`).toString("base64"); const response = await this.ky.post(`https://${origin}.battle.net/oauth/token`, { headers: { Authorization: `Basic ${basicAuth}`, "Content-Type": "application/json" }, searchParams: { grant_type: "client_credentials" } }).json(); return { data: response, ...response }; }; /** * Set the access token. * @param token The access token. */ setAccessToken = (token) => { this.defaults.token = token; }; /** * Refresh the access token. * @param options The access token request arguments. See {@link AccessTokenRequestArguments}. * @returns The access token. See {@link AccessToken}. * @example * const response = await client.refreshAccessToken(); * const { access_token, token_type, expires_in, sub } = response; * console.log(access_token, token_type, expires_in, sub); * // => 'access' * // => 'bearer' * // => 86399 * // => 'client-id' */ refreshAccessToken = async (options) => { const response = await this.getAccessToken(options); this.setAccessToken(response.access_token); return response; }; /** * Validate an access token. * @param options The validate access token arguments. See {@link ValidateAccessTokenArguments}. * @returns The response from the Blizzard API. See {@link ValidateAccessTokenResponse}. * @example * const response = await client.validateAccessToken({ token: 'access' }); * console.log(response.client_id); * // => 'client-id' */ validateAccessToken = async (options) => { const { origin, token } = { ...this.defaults, ...options }; if (!token) throw new Error("No token has been set previously or been passed to the validateAccessToken method."); const response = await this.ky.post(`https://${origin}.battle.net/oauth/check_token`, { body: stringify({ token }), headers: { "Content-Type": "application/x-www-form-urlencoded" } }).json(); return { data: response, ...response }; }; /** * Get the request configuration. * @param resource The resource to fetch. See {@link Resource}. * @param options Client options. See {@link ClientOptions}. * @param headers Additional headers to include in the request. This is deprecated and should be passed into the kyOptions as part of the client options instead. * @returns The request configuration. */ getRequestConfig(resource, options, headers) { const config = { ...this.defaults, ...options }; const endpoint = getBlizzardApi(config.origin, config.locale); const namespace = resource.namespace ? { "Battlenet-Namespace": `${resource.namespace}-${endpoint.origin}` } : void 0; const parameters = resource.parameters; if (parameters) { for (const key of Object.keys(parameters)) if (parameters[key] === void 0) delete parameters[key]; } return { headers: { ...headers, ...namespace, Authorization: `Bearer ${config.token}`, "Content-Type": "application/json" }, searchParams: { ...parameters, locale: endpoint.locale } }; } /** * Get the request URL. * @param resource The resource to fetch. See {@link Resource}. * @param options Client options. See {@link ClientOptions}. * @returns The request URL. */ getRequestUrl(resource, options) { const config = { ...this.defaults, ...options }; const endpoint = getBlizzardApi(config.origin, config.locale); const backslashSeparator = resource.path.startsWith("/") ? "" : "/"; return `${endpoint.hostname}${backslashSeparator}${resource.path}`; } /** * Send a request to the Blizzard API. * @param resource The resource to fetch. See {@link Resource}. * @param options Client options. See {@link ClientOptions}. * @param headers Additional headers to include in the request. This is deprecated and should be passed into the kyOptions as part of the client options instead. * @returns The response from the Blizzard API. See {@link ResourceResponse}. */ async sendRequest(resource, options, headers) { const url = this.getRequestUrl(resource, options); const config = this.getRequestConfig(resource, options, headers); const response = await this.ky.get(url, { ...options?.kyOptions, headers: { ...config.headers, ...options?.kyOptions?.headers }, searchParams: { ...config.searchParams, ...Object.entries(options?.kyOptions?.searchParams ?? {}) } }); const data = await response.json(); return { data, ...data }; } }; //#endregion //#region src/client/create-client.ts const getTokenExpiration = (expiresIn) => expiresIn * 1e3 - 6e4; /** * Create a new Blizzard API client. * @param options Client options, see {@link ClientOptions} & https://develop.battle.net/documentation/guides/getting-started * @param onTokenRefresh Callback function to handle token refresh. If set to `true`, the client will automatically refresh the token. If set to `false`, the client will not refresh the token. If set to a function, the function will be called with the new token. * @returns A new Blizzard API client. */ const createBlizzardApiClient = async (options, onTokenRefresh = true) => { const { key, secret, token } = options; if (!key) throw new Error(`Client missing 'key' parameter`); if (!secret) throw new Error(`Client missing 'secret' parameter`); const client = new BlizzardApiClient(options); const refreshToken = async () => { const response = await client.getAccessToken(); client.setAccessToken(response.access_token); if (typeof onTokenRefresh === "function") onTokenRefresh?.(response); const timeout = setTimeout(() => void refreshToken(), getTokenExpiration(response.expires_in)); timeout.unref(); }; if (!onTokenRefresh) return client; if (token) try { const validatedToken = await client.validateAccessToken({ token }); const expiry = getTokenExpiration(validatedToken.exp); if (expiry - Date.now() < 6e4) await refreshToken(); else { const timeout = setTimeout(() => void refreshToken(), expiry - Date.now()); timeout.unref(); } } catch { await refreshToken(); } else await refreshToken(); return client; }; //#endregion export { createBlizzardApiClient, createBlizzardApiClient as default }; //# sourceMappingURL=index.js.map