@blizzard-api/client
Version:
A node.js client to integrate with the blizzard battle.net api.
206 lines (203 loc) • 6.85 kB
JavaScript
import { setTimeout } from 'node:timers';
import { stringify } from 'node:querystring';
import { getBlizzardApi } from '@blizzard-api/core';
import ky from 'ky';
// src/client/create-client.ts
var BlizzardApiClient = class {
defaults;
ky = ky.create();
constructor(options) {
const { locale, origin } = getBlizzardApi(options.origin, options.locale);
this.defaults = {
key: options.key,
locale,
origin,
secret: options.secret,
token: options.token
};
}
/**
* 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.
* @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: {
locale: endpoint.locale,
...resource.parameters
}
};
}
/**
* 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.
* @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, config).json();
return {
data: response,
...response
};
}
};
// src/client/create-client.ts
var getTokenExpiration = (expiresIn) => expiresIn * 1e3 - 6e4;
var 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;
};
export { createBlizzardApiClient, createBlizzardApiClient as default };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map