@blizzard-api/client
Version:
A node.js client to integrate with the blizzard battle.net api.
295 lines (224 loc) • 7.65 kB
JavaScript
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