@blizzard-api/client 5.2.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +4 -0
- package/dist/index.js +56 -13
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -50,7 +50,9 @@ interface ClientOptions {
|
|
|
50
50
|
key: string;
|
|
51
51
|
kyOptions?: Options;
|
|
52
52
|
locale?: Locales;
|
|
53
|
+
maxAttempts?: number;
|
|
53
54
|
origin: Origins;
|
|
55
|
+
retryOnRateLimit?: boolean;
|
|
54
56
|
secret: string;
|
|
55
57
|
token?: string;
|
|
56
58
|
}
|
|
@@ -106,7 +108,9 @@ declare class BlizzardApiClient {
|
|
|
106
108
|
defaults: {
|
|
107
109
|
key: string;
|
|
108
110
|
locale: Locales;
|
|
111
|
+
maxAttempts: number;
|
|
109
112
|
origin: Origins;
|
|
113
|
+
retryOnRateLimit: boolean;
|
|
110
114
|
secret: string;
|
|
111
115
|
token?: string;
|
|
112
116
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,36 @@
|
|
|
1
|
-
import { setTimeout } from "node:timers";
|
|
1
|
+
import { setTimeout as setTimeout$1 } from "node:timers";
|
|
2
2
|
import { stringify } from "node:querystring";
|
|
3
3
|
import { getBlizzardApi } from "@blizzard-api/core";
|
|
4
4
|
import ky from "ky";
|
|
5
|
+
//#region src/client/utilities.ts
|
|
6
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
const getRateLimitDelay = (attempt, response) => {
|
|
8
|
+
const retryAfterHeader = response?.headers.get("Retry-After");
|
|
9
|
+
if (retryAfterHeader) {
|
|
10
|
+
const retryAfterSeconds = Math.trunc(Number(retryAfterHeader));
|
|
11
|
+
if (!Number.isNaN(retryAfterSeconds)) return retryAfterSeconds * 1e3;
|
|
12
|
+
}
|
|
13
|
+
return Math.min(250 * 2 ** attempt, 5e3) + Math.random() * 250;
|
|
14
|
+
};
|
|
15
|
+
const withRateLimitRetry = async (request, { maxAttempts = 3, retryOnRateLimit = true } = {}) => {
|
|
16
|
+
const retryAttempts = Math.max(1, Math.trunc(maxAttempts || 3));
|
|
17
|
+
let attempt = 0;
|
|
18
|
+
while (true) try {
|
|
19
|
+
return await request();
|
|
20
|
+
} catch (error) {
|
|
21
|
+
const response = error?.response;
|
|
22
|
+
if (!retryOnRateLimit || response?.status !== 429 || attempt >= retryAttempts - 1) throw error;
|
|
23
|
+
const delayMs = getRateLimitDelay(attempt, response);
|
|
24
|
+
attempt += 1;
|
|
25
|
+
await sleep(delayMs);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const validateClientOptions = (options) => {
|
|
29
|
+
if (!options.key) throw new Error(`Client missing 'key' parameter`);
|
|
30
|
+
if (!options.secret) throw new Error(`Client missing 'secret' parameter`);
|
|
31
|
+
if (!options.origin) throw new Error(`Client missing 'origin' parameter`);
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
5
34
|
//#region src/client/client.ts
|
|
6
35
|
/**
|
|
7
36
|
* A Blizzard API client.
|
|
@@ -19,11 +48,14 @@ var BlizzardApiClient = class {
|
|
|
19
48
|
defaults;
|
|
20
49
|
ky;
|
|
21
50
|
constructor(options) {
|
|
51
|
+
validateClientOptions(options);
|
|
22
52
|
const { locale, origin } = getBlizzardApi(options.origin, options.locale);
|
|
23
53
|
this.defaults = {
|
|
24
54
|
key: options.key,
|
|
25
55
|
locale,
|
|
56
|
+
maxAttempts: Math.max(1, options.maxAttempts ?? 3),
|
|
26
57
|
origin,
|
|
58
|
+
retryOnRateLimit: options.retryOnRateLimit ?? true,
|
|
27
59
|
secret: options.secret,
|
|
28
60
|
token: options.token
|
|
29
61
|
};
|
|
@@ -47,18 +79,21 @@ var BlizzardApiClient = class {
|
|
|
47
79
|
* // => 'client-id'
|
|
48
80
|
*/
|
|
49
81
|
getAccessToken = async (options) => {
|
|
50
|
-
const { key,
|
|
82
|
+
const { key, secret } = {
|
|
51
83
|
...this.defaults,
|
|
52
84
|
...options
|
|
53
85
|
};
|
|
54
86
|
const basicAuth = Buffer.from(`${key}:${secret}`).toString("base64");
|
|
55
|
-
return
|
|
87
|
+
return withRateLimitRetry(async () => this.ky.post(`https://oauth.battle.net/token`, {
|
|
56
88
|
headers: {
|
|
57
89
|
Authorization: `Basic ${basicAuth}`,
|
|
58
90
|
"Content-Type": "application/json"
|
|
59
91
|
},
|
|
60
92
|
searchParams: { grant_type: "client_credentials" }
|
|
61
|
-
}).json()
|
|
93
|
+
}).json(), {
|
|
94
|
+
maxAttempts: this.defaults.maxAttempts,
|
|
95
|
+
retryOnRateLimit: this.defaults.retryOnRateLimit
|
|
96
|
+
});
|
|
62
97
|
};
|
|
63
98
|
/**
|
|
64
99
|
* Get the request configuration.
|
|
@@ -71,6 +106,7 @@ var BlizzardApiClient = class {
|
|
|
71
106
|
...this.defaults,
|
|
72
107
|
...options
|
|
73
108
|
};
|
|
109
|
+
if (!config.token) throw new Error("No token has been set for this client.");
|
|
74
110
|
const endpoint = getBlizzardApi(config.origin, config.locale);
|
|
75
111
|
const namespace = resource.namespace ? { "Battlenet-Namespace": `${resource.namespace}-${endpoint.origin}` } : void 0;
|
|
76
112
|
const parameters = {};
|
|
@@ -132,15 +168,20 @@ var BlizzardApiClient = class {
|
|
|
132
168
|
const url = new URL(this.getRequestUrl(resource, options));
|
|
133
169
|
const config = this.getRequestConfig(resource, options);
|
|
134
170
|
for (const [key, value] of Object.entries(config.searchParams)) url.searchParams.set(key, String(value));
|
|
135
|
-
const response = await this.ky.get(url, {
|
|
171
|
+
const response = await withRateLimitRetry(async () => this.ky.get(url, {
|
|
136
172
|
...options?.kyOptions,
|
|
137
173
|
headers: {
|
|
138
174
|
...config.headers,
|
|
139
175
|
...options?.kyOptions?.headers
|
|
140
176
|
}
|
|
177
|
+
}), {
|
|
178
|
+
maxAttempts: this.defaults.maxAttempts,
|
|
179
|
+
retryOnRateLimit: this.defaults.retryOnRateLimit
|
|
141
180
|
});
|
|
142
181
|
if (response.status === 204 || response.headers.get("Content-Length") === "0") return;
|
|
143
|
-
|
|
182
|
+
const text = await response.text();
|
|
183
|
+
if (!text.trim()) return;
|
|
184
|
+
return JSON.parse(text);
|
|
144
185
|
}
|
|
145
186
|
/**
|
|
146
187
|
* Set the access token.
|
|
@@ -164,10 +205,13 @@ var BlizzardApiClient = class {
|
|
|
164
205
|
...options
|
|
165
206
|
};
|
|
166
207
|
if (!token) throw new Error("No token has been set previously or been passed to the validateAccessToken method.");
|
|
167
|
-
return
|
|
208
|
+
return withRateLimitRetry(async () => this.ky.post(`https://${origin}.battle.net/oauth/check_token`, {
|
|
168
209
|
body: stringify({ token }),
|
|
169
210
|
headers: { "Content-Type": "application/x-www-form-urlencoded" }
|
|
170
|
-
}).json()
|
|
211
|
+
}).json(), {
|
|
212
|
+
maxAttempts: this.defaults.maxAttempts,
|
|
213
|
+
retryOnRateLimit: this.defaults.retryOnRateLimit
|
|
214
|
+
});
|
|
171
215
|
};
|
|
172
216
|
};
|
|
173
217
|
//#endregion
|
|
@@ -201,12 +245,11 @@ const getRefreshDelayFromExpiry = (expiryInSeconds) => expiryInSeconds * 1e3 - R
|
|
|
201
245
|
* @returns A new Blizzard API client.
|
|
202
246
|
*/
|
|
203
247
|
const createBlizzardApiClient = async (options, onTokenRefresh = true) => {
|
|
204
|
-
const {
|
|
205
|
-
|
|
206
|
-
if (!secret) throw new Error(`Client missing 'secret' parameter`);
|
|
248
|
+
const { token } = options;
|
|
249
|
+
validateClientOptions(options);
|
|
207
250
|
const client = new BlizzardApiClient(options);
|
|
208
251
|
const scheduleRefresh = (delayInMilliseconds) => {
|
|
209
|
-
setTimeout(() => void refreshToken(), delayInMilliseconds).unref();
|
|
252
|
+
setTimeout$1(() => void refreshToken(), delayInMilliseconds).unref();
|
|
210
253
|
};
|
|
211
254
|
const refreshToken = async () => {
|
|
212
255
|
const response = await client.refreshAccessToken();
|
|
@@ -217,7 +260,7 @@ const createBlizzardApiClient = async (options, onTokenRefresh = true) => {
|
|
|
217
260
|
if (token) try {
|
|
218
261
|
const validatedToken = await client.validateAccessToken({ token });
|
|
219
262
|
const refreshDelay = getRefreshDelayFromExpiry(validatedToken.exp);
|
|
220
|
-
if (refreshDelay
|
|
263
|
+
if (refreshDelay <= 0) await refreshToken();
|
|
221
264
|
else scheduleRefresh(refreshDelay);
|
|
222
265
|
} catch {
|
|
223
266
|
await refreshToken();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/client/client.ts","../src/client/create-client.ts"],"sourcesContent":["import { stringify } from 'node:querystring';\r\nimport { getBlizzardApi } from '@blizzard-api/core';\r\nimport type { Locales, Origins, Resource } from '@blizzard-api/core';\r\nimport ky from 'ky';\r\nimport type {\r\n AccessToken,\r\n AccessTokenRequestArguments,\r\n ClientOptions,\r\n ValidateAccessTokenArguments,\r\n ValidateAccessTokenResponse,\r\n} from './types';\r\n\r\n/**\r\n * A Blizzard API client.\r\n * @classdesc A client to interact with the Blizzard API.\r\n * @example\r\n * const client = new BlizzardApiClient({\r\n * key: 'client',\r\n * secret: 'secret',\r\n * origin: 'eu',\r\n * locale: 'en_GB',\r\n * token: 'access'\r\n * });\r\n */\r\nexport class BlizzardApiClient {\r\n public defaults: {\r\n key: string;\r\n locale: Locales;\r\n origin: Origins;\r\n secret: string;\r\n token?: string;\r\n };\r\n\r\n private ky;\r\n\r\n constructor(options: ClientOptions) {\r\n const { locale, origin } = getBlizzardApi(options.origin, options.locale);\r\n this.defaults = {\r\n key: options.key,\r\n locale: locale,\r\n origin: origin,\r\n secret: options.secret,\r\n token: options.token,\r\n };\r\n const kyOptions = {\r\n timeout: 15_000,\r\n ...options.kyOptions,\r\n };\r\n this.ky = ky.create(kyOptions);\r\n }\r\n\r\n /**\r\n * Get an access token.\r\n * @param options The access token request arguments. See {@link AccessTokenRequestArguments}.\r\n * @returns The access token. See {@link AccessToken}.\r\n * @example\r\n * const response = await client.getAccessToken();\r\n * const { access_token, token_type, expires_in, sub } = response;\r\n * console.log(access_token, token_type, expires_in, sub);\r\n * // => 'access'\r\n * // => 'bearer'\r\n * // => 86399\r\n * // => 'client-id'\r\n */\r\n public getAccessToken = async (options?: AccessTokenRequestArguments): Promise<AccessToken> => {\r\n const { key, origin, secret } = { ...this.defaults, ...options };\r\n const basicAuth = Buffer.from(`${key}:${secret}`).toString('base64');\r\n const response = await this.ky\r\n .post<AccessToken>(`https://${origin}.battle.net/oauth/token`, {\r\n headers: {\r\n Authorization: `Basic ${basicAuth}`,\r\n 'Content-Type': 'application/json',\r\n },\r\n searchParams: {\r\n grant_type: 'client_credentials',\r\n },\r\n })\r\n .json();\r\n\r\n return response;\r\n };\r\n\r\n /**\r\n * Get the request configuration.\r\n * @param resource The resource to fetch. See {@link Resource}.\r\n * @param options Client options. See {@link ClientOptions}.\r\n * @returns The request configuration.\r\n */\r\n public getRequestConfig<T, Protected extends boolean = false>(\r\n resource: Resource<T, object, Protected>,\r\n options?: Partial<ClientOptions>,\r\n ): {\r\n headers: Record<string, string> & {\r\n Authorization: `Bearer ${string}`;\r\n 'Battlenet-Namespace'?: string;\r\n 'Content-Type': 'application/json';\r\n };\r\n searchParams: Record<string, unknown> & { locale: ReturnType<typeof getBlizzardApi>['locale'] };\r\n } {\r\n const config = { ...this.defaults, ...options };\r\n const endpoint = getBlizzardApi(config.origin, config.locale);\r\n\r\n const namespace = resource.namespace\r\n ? { 'Battlenet-Namespace': `${resource.namespace}-${endpoint.origin}` }\r\n : undefined;\r\n\r\n // Build a new object with the defined parameters so we never mutate the\r\n // caller's resource, which may be reused across requests.\r\n const parameters: Record<string, unknown> = {};\r\n if (resource.parameters) {\r\n for (const [key, value] of Object.entries(resource.parameters as Record<string, unknown>)) {\r\n if (value !== undefined) {\r\n parameters[key] = value;\r\n }\r\n }\r\n }\r\n\r\n return {\r\n headers: {\r\n ...namespace,\r\n Authorization: `Bearer ${config.token}`,\r\n 'Content-Type': 'application/json',\r\n },\r\n searchParams: {\r\n ...parameters,\r\n locale: endpoint.locale,\r\n },\r\n };\r\n }\r\n\r\n /**\r\n * Get the request URL.\r\n * @param resource The resource to fetch. See {@link Resource}.\r\n * @param options Client options. See {@link ClientOptions}.\r\n * @returns The request URL.\r\n */\r\n public getRequestUrl<T, Protected extends boolean = false>(\r\n resource: Resource<T, object, Protected>,\r\n options?: Partial<ClientOptions>,\r\n ) {\r\n const config = { ...this.defaults, ...options };\r\n const endpoint = getBlizzardApi(config.origin, config.locale);\r\n\r\n const backslashSeparator = resource.path.startsWith('/') ? '' : '/';\r\n\r\n return `${endpoint.hostname}${backslashSeparator}${resource.path}`;\r\n }\r\n\r\n /**\r\n * Refresh the access token.\r\n * @param options The access token request arguments. See {@link AccessTokenRequestArguments}.\r\n * @returns The access token. See {@link AccessToken}.\r\n * @example\r\n * const response = await client.refreshAccessToken();\r\n * const { access_token, token_type, expires_in, sub } = response;\r\n * console.log(access_token, token_type, expires_in, sub);\r\n * // => 'access'\r\n * // => 'bearer'\r\n * // => 86399\r\n * // => 'client-id'\r\n */\r\n public refreshAccessToken = async (options?: AccessTokenRequestArguments): Promise<AccessToken> => {\r\n const response = await this.getAccessToken(options);\r\n this.setAccessToken(response.access_token);\r\n return response;\r\n };\r\n\r\n /**\r\n * Send a request to the Blizzard API.\r\n * @param resource The resource to fetch. See {@link Resource}.\r\n * @param options Client options. See {@link ClientOptions}.\r\n * @returns The response from the Blizzard API.\r\n */\r\n public async sendRequest<T, Protected extends boolean = false>(\r\n resource: Resource<T, object, Protected>,\r\n options?: Partial<ClientOptions>,\r\n ): Promise<T | undefined> {\r\n const url = new URL(this.getRequestUrl(resource, options));\r\n const config = this.getRequestConfig(resource, options);\r\n\r\n for (const [key, value] of Object.entries(config.searchParams)) {\r\n url.searchParams.set(key, String(value));\r\n }\r\n\r\n const response = await this.ky.get<T>(url, {\r\n ...options?.kyOptions,\r\n headers: {\r\n ...config.headers,\r\n ...options?.kyOptions?.headers,\r\n },\r\n });\r\n\r\n // Some endpoints return a 204 status code with no content,\r\n // while others return a 200 status code with an empty body.\r\n // To handle both cases, we check for a 204 status code or a Content-Length header of 0\r\n // before attempting to parse the response as JSON.\r\n if (response.status === 204 || response.headers.get('Content-Length') === '0') {\r\n return;\r\n }\r\n const data = await response.json();\r\n\r\n return data;\r\n }\r\n\r\n /**\r\n * Set the access token.\r\n * @param token The access token.\r\n */\r\n public setAccessToken = (token: string): void => {\r\n this.defaults.token = token;\r\n };\r\n\r\n /**\r\n * Validate an access token.\r\n * @param options The validate access token arguments. See {@link ValidateAccessTokenArguments}.\r\n * @returns The response from the Blizzard API. See {@link ValidateAccessTokenResponse}.\r\n * @example\r\n * const response = await client.validateAccessToken({ token: 'access' });\r\n * console.log(response.client_id);\r\n * // => 'client-id'\r\n */\r\n public validateAccessToken = async (options?: ValidateAccessTokenArguments): Promise<ValidateAccessTokenResponse> => {\r\n const { origin, token } = { ...this.defaults, ...options };\r\n\r\n if (!token) {\r\n throw new Error('No token has been set previously or been passed to the validateAccessToken method.');\r\n }\r\n\r\n const response = await this.ky\r\n .post<ValidateAccessTokenResponse>(`https://${origin}.battle.net/oauth/check_token`, {\r\n body: stringify({ token }),\r\n headers: {\r\n 'Content-Type': 'application/x-www-form-urlencoded',\r\n },\r\n })\r\n .json();\r\n\r\n return response;\r\n };\r\n}\r\n","//We have to explicitly import setTimeout because of https://github.com/electron/electron/issues/21162#issuecomment-554792447\r\nimport { setTimeout } from 'node:timers';\r\nimport { BlizzardApiClient } from './client';\r\nimport type { AccessToken, ClientOptions } from './types';\r\n\r\n/**\r\n * How long before a token's actual expiry it should be refreshed, in milliseconds.\r\n * This buffer avoids using a token that could expire mid-request.\r\n */\r\nconst REFRESH_BUFFER_MS = 60_000;\r\n\r\n/**\r\n * Calculate the delay (in milliseconds) to wait before refreshing a token, based on the\r\n * token's remaining lifetime.\r\n * @param lifetimeInSeconds The token's lifetime in seconds, as returned in the `expires_in`\r\n * field when requesting a new access token.\r\n * @returns The delay in milliseconds before the token should be refreshed.\r\n */\r\nconst getRefreshDelayFromLifetime = (lifetimeInSeconds: number): number => lifetimeInSeconds * 1000 - REFRESH_BUFFER_MS;\r\n\r\n/**\r\n * Calculate the delay (in milliseconds) to wait before refreshing a token, based on the\r\n * token's absolute expiry time.\r\n * @param expiryInSeconds The token's expiry as a Unix timestamp in seconds, as returned in the\r\n * `exp` field when validating an existing access token.\r\n * @returns The delay in milliseconds before the token should be refreshed. May be negative if the\r\n * token is already within its refresh buffer or expired.\r\n */\r\nconst getRefreshDelayFromExpiry = (expiryInSeconds: number): number =>\r\n expiryInSeconds * 1000 - REFRESH_BUFFER_MS - Date.now();\r\n\r\n/**\r\n * Create a new Blizzard API client.\r\n * @param options Client options, see {@link ClientOptions} & https://develop.battle.net/documentation/guides/getting-started\r\n * @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.\r\n * @returns A new Blizzard API client.\r\n */\r\nexport const createBlizzardApiClient = async (\r\n options: ClientOptions,\r\n onTokenRefresh: ((token: AccessToken) => void) | boolean = true,\r\n): Promise<BlizzardApiClient> => {\r\n const { key, secret, token } = options;\r\n if (!key) {\r\n throw new Error(`Client missing 'key' parameter`);\r\n }\r\n if (!secret) {\r\n throw new Error(`Client missing 'secret' parameter`);\r\n }\r\n\r\n const client = new BlizzardApiClient(options);\r\n\r\n //Schedule a token refresh after the given delay, without keeping the process alive.\r\n const scheduleRefresh = (delayInMilliseconds: number) => {\r\n const timeout = setTimeout(() => void refreshToken(), delayInMilliseconds);\r\n //Unref the timeout so the process can exit while a refresh is pending.\r\n timeout.unref();\r\n };\r\n\r\n const refreshToken = async () => {\r\n const response = await client.refreshAccessToken();\r\n\r\n if (typeof onTokenRefresh === 'function') {\r\n onTokenRefresh(response);\r\n }\r\n\r\n scheduleRefresh(getRefreshDelayFromLifetime(response.expires_in));\r\n };\r\n\r\n //If tokenRefresh is false, return the client without refreshing the token\r\n if (!onTokenRefresh) {\r\n return client;\r\n }\r\n\r\n if (token) {\r\n try {\r\n //If token is set, validate the token\r\n const validatedToken = await client.validateAccessToken({ token });\r\n const refreshDelay = getRefreshDelayFromExpiry(validatedToken.exp);\r\n //If the token is already within its refresh buffer, refresh it now\r\n if (refreshDelay < REFRESH_BUFFER_MS) {\r\n await refreshToken();\r\n } else {\r\n //Otherwise schedule a refresh for when the token nears expiry\r\n scheduleRefresh(refreshDelay);\r\n }\r\n } catch {\r\n //If token is invalid, refresh the token\r\n await refreshToken();\r\n }\r\n } else {\r\n //If token is not set, refresh the token\r\n await refreshToken();\r\n }\r\n\r\n return client;\r\n};\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["setTimeout"],"sources":["../src/client/utilities.ts","../src/client/client.ts","../src/client/create-client.ts"],"sourcesContent":["import type { ClientOptions } from './types';\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\nexport interface WithRateLimitRetryOptions {\n maxAttempts?: number;\n retryOnRateLimit?: boolean;\n}\n\nconst getRateLimitDelay = (attempt: number, response?: Pick<Response, 'headers'>): number => {\n const retryAfterHeader = response?.headers.get('Retry-After');\n if (retryAfterHeader) {\n const retryAfterSeconds = Math.trunc(Number(retryAfterHeader));\n if (!Number.isNaN(retryAfterSeconds)) {\n return retryAfterSeconds * 1000;\n }\n }\n\n // eslint-disable-next-line sonarjs/pseudo-random\n return Math.min(250 * 2 ** attempt, 5000) + Math.random() * 250;\n};\n\nexport const withRateLimitRetry = async <T>(\n request: () => Promise<T>,\n { maxAttempts = 3, retryOnRateLimit = true }: WithRateLimitRetryOptions = {},\n): Promise<T> => {\n const retryAttempts = Math.max(1, Math.trunc(maxAttempts || 3));\n let attempt = 0;\n\n while (true) {\n try {\n return await request();\n } catch (error: unknown) {\n const response = (error as { response?: Response })?.response;\n if (!retryOnRateLimit || response?.status !== 429 || attempt >= retryAttempts - 1) {\n throw error;\n }\n\n const delayMs = getRateLimitDelay(attempt, response);\n attempt += 1;\n await sleep(delayMs);\n }\n }\n};\n\nexport const validateClientOptions = (options: ClientOptions): void => {\n if (!options.key) {\n throw new Error(`Client missing 'key' parameter`);\n }\n if (!options.secret) {\n throw new Error(`Client missing 'secret' parameter`);\n }\n if (!options.origin) {\n throw new Error(`Client missing 'origin' parameter`);\n }\n};\n","import { stringify } from 'node:querystring';\r\nimport { getBlizzardApi } from '@blizzard-api/core';\r\nimport type { Locales, Origins, Resource } from '@blizzard-api/core';\r\nimport ky from 'ky';\r\nimport type {\r\n AccessToken,\r\n AccessTokenRequestArguments,\r\n ClientOptions,\r\n ValidateAccessTokenArguments,\r\n ValidateAccessTokenResponse,\r\n} from './types';\r\nimport { validateClientOptions, withRateLimitRetry } from './utilities';\r\n\r\n/**\r\n * A Blizzard API client.\r\n * @classdesc A client to interact with the Blizzard API.\r\n * @example\r\n * const client = new BlizzardApiClient({\r\n * key: 'client',\r\n * secret: 'secret',\r\n * origin: 'eu',\r\n * locale: 'en_GB',\r\n * token: 'access'\r\n * });\r\n */\r\nexport class BlizzardApiClient {\r\n public defaults: {\r\n key: string;\r\n locale: Locales;\r\n maxAttempts: number;\r\n origin: Origins;\r\n retryOnRateLimit: boolean;\r\n secret: string;\r\n token?: string;\r\n };\r\n\r\n private ky;\r\n\r\n constructor(options: ClientOptions) {\r\n validateClientOptions(options);\r\n\r\n const { locale, origin } = getBlizzardApi(options.origin, options.locale);\r\n this.defaults = {\r\n key: options.key,\r\n locale: locale,\r\n maxAttempts: Math.max(1, options.maxAttempts ?? 3),\r\n origin: origin,\r\n retryOnRateLimit: options.retryOnRateLimit ?? true,\r\n secret: options.secret,\r\n token: options.token,\r\n };\r\n const kyOptions = {\r\n timeout: 15_000,\r\n ...options.kyOptions,\r\n };\r\n this.ky = ky.create(kyOptions);\r\n }\r\n\r\n /**\r\n * Get an access token.\r\n * @param options The access token request arguments. See {@link AccessTokenRequestArguments}.\r\n * @returns The access token. See {@link AccessToken}.\r\n * @example\r\n * const response = await client.getAccessToken();\r\n * const { access_token, token_type, expires_in, sub } = response;\r\n * console.log(access_token, token_type, expires_in, sub);\r\n * // => 'access'\r\n * // => 'bearer'\r\n * // => 86399\r\n * // => 'client-id'\r\n */\r\n public getAccessToken = async (options?: AccessTokenRequestArguments): Promise<AccessToken> => {\r\n const { key, secret } = { ...this.defaults, ...options };\r\n const basicAuth = Buffer.from(`${key}:${secret}`).toString('base64');\r\n\r\n return withRateLimitRetry(\r\n async () =>\r\n this.ky\r\n .post<AccessToken>(`https://oauth.battle.net/token`, {\r\n headers: {\r\n Authorization: `Basic ${basicAuth}`,\r\n 'Content-Type': 'application/json',\r\n },\r\n searchParams: {\r\n grant_type: 'client_credentials',\r\n },\r\n })\r\n .json(),\r\n {\r\n maxAttempts: this.defaults.maxAttempts,\r\n retryOnRateLimit: this.defaults.retryOnRateLimit,\r\n },\r\n );\r\n };\r\n\r\n /**\r\n * Get the request configuration.\r\n * @param resource The resource to fetch. See {@link Resource}.\r\n * @param options Client options. See {@link ClientOptions}.\r\n * @returns The request configuration.\r\n */\r\n public getRequestConfig<T, Protected extends boolean = false>(\r\n resource: Resource<T, object, Protected>,\r\n options?: Partial<ClientOptions>,\r\n ): {\r\n headers: Record<string, string> & {\r\n Authorization: `Bearer ${string}`;\r\n 'Battlenet-Namespace'?: string;\r\n 'Content-Type': 'application/json';\r\n };\r\n searchParams: Record<string, unknown> & { locale: ReturnType<typeof getBlizzardApi>['locale'] };\r\n } {\r\n const config = { ...this.defaults, ...options };\r\n if (!config.token) {\r\n throw new Error('No token has been set for this client.');\r\n }\r\n\r\n const endpoint = getBlizzardApi(config.origin, config.locale);\r\n\r\n const namespace = resource.namespace\r\n ? { 'Battlenet-Namespace': `${resource.namespace}-${endpoint.origin}` }\r\n : undefined;\r\n\r\n // Build a new object with the defined parameters so we never mutate the\r\n // caller's resource, which may be reused across requests.\r\n const parameters: Record<string, unknown> = {};\r\n if (resource.parameters) {\r\n for (const [key, value] of Object.entries(resource.parameters as Record<string, unknown>)) {\r\n if (value !== undefined) {\r\n parameters[key] = value;\r\n }\r\n }\r\n }\r\n\r\n return {\r\n headers: {\r\n ...namespace,\r\n Authorization: `Bearer ${config.token}`,\r\n 'Content-Type': 'application/json',\r\n },\r\n searchParams: {\r\n ...parameters,\r\n locale: endpoint.locale,\r\n },\r\n };\r\n }\r\n\r\n /**\r\n * Get the request URL.\r\n * @param resource The resource to fetch. See {@link Resource}.\r\n * @param options Client options. See {@link ClientOptions}.\r\n * @returns The request URL.\r\n */\r\n public getRequestUrl<T, Protected extends boolean = false>(\r\n resource: Resource<T, object, Protected>,\r\n options?: Partial<ClientOptions>,\r\n ) {\r\n const config = { ...this.defaults, ...options };\r\n const endpoint = getBlizzardApi(config.origin, config.locale);\r\n\r\n const backslashSeparator = resource.path.startsWith('/') ? '' : '/';\r\n\r\n return `${endpoint.hostname}${backslashSeparator}${resource.path}`;\r\n }\r\n\r\n /**\r\n * Refresh the access token.\r\n * @param options The access token request arguments. See {@link AccessTokenRequestArguments}.\r\n * @returns The access token. See {@link AccessToken}.\r\n * @example\r\n * const response = await client.refreshAccessToken();\r\n * const { access_token, token_type, expires_in, sub } = response;\r\n * console.log(access_token, token_type, expires_in, sub);\r\n * // => 'access'\r\n * // => 'bearer'\r\n * // => 86399\r\n * // => 'client-id'\r\n */\r\n public refreshAccessToken = async (options?: AccessTokenRequestArguments): Promise<AccessToken> => {\r\n const response = await this.getAccessToken(options);\r\n this.setAccessToken(response.access_token);\r\n return response;\r\n };\r\n\r\n /**\r\n * Send a request to the Blizzard API.\r\n * @param resource The resource to fetch. See {@link Resource}.\r\n * @param options Client options. See {@link ClientOptions}.\r\n * @returns The response from the Blizzard API.\r\n */\r\n public async sendRequest<T, Protected extends boolean = false>(\r\n resource: Resource<T, object, Protected>,\r\n options?: Partial<ClientOptions>,\r\n ): Promise<T | undefined> {\r\n const url = new URL(this.getRequestUrl(resource, options));\r\n const config = this.getRequestConfig(resource, options);\r\n\r\n for (const [key, value] of Object.entries(config.searchParams)) {\r\n url.searchParams.set(key, String(value));\r\n }\r\n\r\n const response = await withRateLimitRetry(\r\n async () =>\r\n this.ky.get<T>(url, {\r\n ...options?.kyOptions,\r\n headers: {\r\n ...config.headers,\r\n ...options?.kyOptions?.headers,\r\n },\r\n }),\r\n {\r\n maxAttempts: this.defaults.maxAttempts,\r\n retryOnRateLimit: this.defaults.retryOnRateLimit,\r\n },\r\n );\r\n\r\n // Some endpoints return a 204 status code with no content,\r\n // while others return a 200 status code with an empty body.\r\n // We must check for empty responses before trying to parse JSON, otherwise\r\n // a valid empty payload can be treated as invalid JSON and crash the client.\r\n if (response.status === 204 || response.headers.get('Content-Length') === '0') {\r\n return;\r\n }\r\n\r\n const text = await response.text();\r\n if (!text.trim()) {\r\n return;\r\n }\r\n\r\n return JSON.parse(text) as T;\r\n }\r\n\r\n /**\r\n * Set the access token.\r\n * @param token The access token.\r\n */\r\n public setAccessToken = (token: string): void => {\r\n this.defaults.token = token;\r\n };\r\n\r\n /**\r\n * Validate an access token.\r\n * @param options The validate access token arguments. See {@link ValidateAccessTokenArguments}.\r\n * @returns The response from the Blizzard API. See {@link ValidateAccessTokenResponse}.\r\n * @example\r\n * const response = await client.validateAccessToken({ token: 'access' });\r\n * console.log(response.client_id);\r\n * // => 'client-id'\r\n */\r\n public validateAccessToken = async (options?: ValidateAccessTokenArguments): Promise<ValidateAccessTokenResponse> => {\r\n const { origin, token } = { ...this.defaults, ...options };\r\n\r\n if (!token) {\r\n throw new Error('No token has been set previously or been passed to the validateAccessToken method.');\r\n }\r\n\r\n return withRateLimitRetry(\r\n async () =>\r\n this.ky\r\n .post<ValidateAccessTokenResponse>(`https://${origin}.battle.net/oauth/check_token`, {\r\n body: stringify({ token }),\r\n headers: {\r\n 'Content-Type': 'application/x-www-form-urlencoded',\r\n },\r\n })\r\n .json(),\r\n {\r\n maxAttempts: this.defaults.maxAttempts,\r\n retryOnRateLimit: this.defaults.retryOnRateLimit,\r\n },\r\n );\r\n };\r\n}\r\n","//We have to explicitly import setTimeout because of https://github.com/electron/electron/issues/21162#issuecomment-554792447\r\nimport { setTimeout } from 'node:timers';\r\nimport { BlizzardApiClient } from './client';\r\nimport type { AccessToken, ClientOptions } from './types';\r\nimport { validateClientOptions } from './utilities';\r\n\r\n/**\r\n * How long before a token's actual expiry it should be refreshed, in milliseconds.\r\n * This buffer avoids using a token that could expire mid-request.\r\n */\r\nconst REFRESH_BUFFER_MS = 60_000;\r\n\r\n/**\r\n * Calculate the delay (in milliseconds) to wait before refreshing a token, based on the\r\n * token's remaining lifetime.\r\n * @param lifetimeInSeconds The token's lifetime in seconds, as returned in the `expires_in`\r\n * field when requesting a new access token.\r\n * @returns The delay in milliseconds before the token should be refreshed.\r\n */\r\nconst getRefreshDelayFromLifetime = (lifetimeInSeconds: number): number => lifetimeInSeconds * 1000 - REFRESH_BUFFER_MS;\r\n\r\n/**\r\n * Calculate the delay (in milliseconds) to wait before refreshing a token, based on the\r\n * token's absolute expiry time.\r\n * @param expiryInSeconds The token's expiry as a Unix timestamp in seconds, as returned in the\r\n * `exp` field when validating an existing access token.\r\n * @returns The delay in milliseconds before the token should be refreshed. May be negative if the\r\n * token is already within its refresh buffer or expired.\r\n */\r\nconst getRefreshDelayFromExpiry = (expiryInSeconds: number): number =>\r\n expiryInSeconds * 1000 - REFRESH_BUFFER_MS - Date.now();\r\n\r\n/**\r\n * Create a new Blizzard API client.\r\n * @param options Client options, see {@link ClientOptions} & https://develop.battle.net/documentation/guides/getting-started\r\n * @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.\r\n * @returns A new Blizzard API client.\r\n */\r\nexport const createBlizzardApiClient = async (\r\n options: ClientOptions,\r\n onTokenRefresh: ((token: AccessToken) => void) | boolean = true,\r\n): Promise<BlizzardApiClient> => {\r\n const { token } = options;\r\n validateClientOptions(options);\r\n\r\n const client = new BlizzardApiClient(options);\r\n\r\n //Schedule a token refresh after the given delay, without keeping the process alive.\r\n const scheduleRefresh = (delayInMilliseconds: number) => {\r\n const timeout = setTimeout(() => void refreshToken(), delayInMilliseconds);\r\n //Unref the timeout so the process can exit while a refresh is pending.\r\n timeout.unref();\r\n };\r\n\r\n const refreshToken = async () => {\r\n const response = await client.refreshAccessToken();\r\n\r\n if (typeof onTokenRefresh === 'function') {\r\n onTokenRefresh(response);\r\n }\r\n\r\n scheduleRefresh(getRefreshDelayFromLifetime(response.expires_in));\r\n };\r\n\r\n //If tokenRefresh is false, return the client without refreshing the token\r\n if (!onTokenRefresh) {\r\n return client;\r\n }\r\n\r\n if (token) {\r\n try {\r\n //If token is set, validate the token\r\n const validatedToken = await client.validateAccessToken({ token });\r\n const refreshDelay = getRefreshDelayFromExpiry(validatedToken.exp);\r\n //If the token is already within its refresh buffer, refresh it now.\r\n if (refreshDelay <= 0) {\r\n await refreshToken();\r\n } else {\r\n //Otherwise schedule a refresh for when the token nears expiry.\r\n scheduleRefresh(refreshDelay);\r\n }\r\n } catch {\r\n //If token is invalid, refresh the token\r\n await refreshToken();\r\n }\r\n } else {\r\n //If token is not set, refresh the token\r\n await refreshToken();\r\n }\r\n\r\n return client;\r\n};\r\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blizzard-api/client",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Putro",
|
|
6
6
|
"description": "A node.js client to integrate with the blizzard battle.net api.",
|
|
@@ -45,17 +45,17 @@
|
|
|
45
45
|
"hearthstone"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"ky": "2.0
|
|
48
|
+
"ky": "2.1.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"@blizzard-api/core": "4.1.2"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@blizzard-api/classic-wow": "4.1.0",
|
|
55
54
|
"@blizzard-api/core": "4.1.2",
|
|
56
|
-
"@blizzard-api/
|
|
57
|
-
"@blizzard-api/hs": "3.1.1",
|
|
55
|
+
"@blizzard-api/hs": "3.1.2",
|
|
58
56
|
"@blizzard-api/sc2": "3.1.2",
|
|
57
|
+
"@blizzard-api/d3": "3.1.1",
|
|
58
|
+
"@blizzard-api/classic-wow": "4.1.1",
|
|
59
59
|
"@blizzard-api/wow": "4.1.0"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|