@blizzard-api/client 2.3.3 → 3.0.1

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 CHANGED
@@ -2,7 +2,6 @@ import { Locales, Origins, Resource, ResourceResponse, getBlizzardApi } from "@b
2
2
  import { Options } from "ky";
3
3
 
4
4
  //#region src/client/types.d.ts
5
-
6
5
  /**
7
6
  * An access token response from the Blizzard API.
8
7
  * @see https://develop.battle.net/documentation/guides/using-oauth
@@ -36,14 +35,6 @@ interface AccessTokenRequestArguments {
36
35
  origin?: Origins;
37
36
  secret?: string;
38
37
  }
39
- type AxiosCompatability<T> = T & {
40
- /**
41
- * @deprecated
42
- * This property is only here for backward compatibility, it will be removed in the next major version.
43
- * All data should be accessed directly from the response object instead of through this property.
44
- */
45
- data: T;
46
- };
47
38
  /**
48
39
  * A client configuration object.
49
40
  * @example
@@ -135,7 +126,7 @@ declare class BlizzardApiClient {
135
126
  * // => 86399
136
127
  * // => 'client-id'
137
128
  */
138
- getAccessToken: (options?: AccessTokenRequestArguments) => Promise<AxiosCompatability<AccessToken>>;
129
+ getAccessToken: (options?: AccessTokenRequestArguments) => Promise<AccessToken>;
139
130
  /**
140
131
  * Set the access token.
141
132
  * @param token The access token.
@@ -154,7 +145,7 @@ declare class BlizzardApiClient {
154
145
  * // => 86399
155
146
  * // => 'client-id'
156
147
  */
157
- refreshAccessToken: (options?: AccessTokenRequestArguments) => Promise<AxiosCompatability<AccessToken>>;
148
+ refreshAccessToken: (options?: AccessTokenRequestArguments) => Promise<AccessToken>;
158
149
  /**
159
150
  * Validate an access token.
160
151
  * @param options The validate access token arguments. See {@link ValidateAccessTokenArguments}.
@@ -164,7 +155,7 @@ declare class BlizzardApiClient {
164
155
  * console.log(response.client_id);
165
156
  * // => 'client-id'
166
157
  */
167
- validateAccessToken: (options?: ValidateAccessTokenArguments) => Promise<AxiosCompatability<ValidateAccessTokenResponse>>;
158
+ validateAccessToken: (options?: ValidateAccessTokenArguments) => Promise<ValidateAccessTokenResponse>;
168
159
  /**
169
160
  * Get the request configuration.
170
161
  * @param resource The resource to fetch. See {@link Resource}.
@@ -196,7 +187,7 @@ declare class BlizzardApiClient {
196
187
  * @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.
197
188
  * @returns The response from the Blizzard API. See {@link ResourceResponse}.
198
189
  */
199
- sendRequest<T, Protected extends boolean = false>(resource: Resource<T, object, Protected>, options?: Partial<ClientOptions>, headers?: Record<string, string>): ResourceResponse<AxiosCompatability<T>>;
190
+ sendRequest<T, Protected extends boolean = false>(resource: Resource<T, object, Protected>, options?: Partial<ClientOptions>, headers?: Record<string, string>): ResourceResponse<T>;
200
191
  }
201
192
  //#endregion
202
193
  //#region src/client/create-client.d.ts
@@ -208,5 +199,5 @@ declare class BlizzardApiClient {
208
199
  */
209
200
  declare const createBlizzardApiClient: (options: ClientOptions, onTokenRefresh?: ((token: AccessToken) => void) | boolean) => Promise<BlizzardApiClient>;
210
201
  //#endregion
211
- export { AccessToken, AccessTokenRequestArguments, AxiosCompatability, ClientOptions, ValidateAccessTokenArguments, ValidateAccessTokenResponse, createBlizzardApiClient, createBlizzardApiClient as default };
202
+ export { AccessToken, AccessTokenRequestArguments, ClientOptions, ValidateAccessTokenArguments, ValidateAccessTokenResponse, createBlizzardApiClient, createBlizzardApiClient as default };
212
203
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -49,17 +49,13 @@ var BlizzardApiClient = class {
49
49
  ...options
50
50
  };
51
51
  const basicAuth = Buffer.from(`${key}:${secret}`).toString("base64");
52
- const response = await this.ky.post(`https://${origin}.battle.net/oauth/token`, {
52
+ return await this.ky.post(`https://${origin}.battle.net/oauth/token`, {
53
53
  headers: {
54
54
  Authorization: `Basic ${basicAuth}`,
55
55
  "Content-Type": "application/json"
56
56
  },
57
57
  searchParams: { grant_type: "client_credentials" }
58
58
  }).json();
59
- return {
60
- data: response,
61
- ...response
62
- };
63
59
  };
64
60
  /**
65
61
  * Set the access token.
@@ -101,14 +97,10 @@ var BlizzardApiClient = class {
101
97
  ...options
102
98
  };
103
99
  if (!token) throw new Error("No token has been set previously or been passed to the validateAccessToken method.");
104
- const response = await this.ky.post(`https://${origin}.battle.net/oauth/check_token`, {
100
+ return await this.ky.post(`https://${origin}.battle.net/oauth/check_token`, {
105
101
  body: stringify({ token }),
106
102
  headers: { "Content-Type": "application/x-www-form-urlencoded" }
107
103
  }).json();
108
- return {
109
- data: response,
110
- ...response
111
- };
112
104
  };
113
105
  /**
114
106
  * Get the request configuration.
@@ -166,21 +158,21 @@ var BlizzardApiClient = class {
166
158
  async sendRequest(resource, options, headers) {
167
159
  const url = this.getRequestUrl(resource, options);
168
160
  const config = this.getRequestConfig(resource, options, headers);
169
- const data = await (await this.ky.get(url, {
161
+ const kyHeaders = {};
162
+ for (const value of Object.entries(options?.kyOptions?.headers ?? {})) Object.assign(kyHeaders, value);
163
+ const kySearchParameters = {};
164
+ for (const value of Object.entries(options?.kyOptions?.searchParams ?? {})) Object.assign(kySearchParameters, value);
165
+ return await (await this.ky.get(url, {
170
166
  ...options?.kyOptions,
171
167
  headers: {
172
168
  ...config.headers,
173
- ...options?.kyOptions?.headers
169
+ ...kyHeaders
174
170
  },
175
171
  searchParams: {
176
172
  ...config.searchParams,
177
- ...Object.entries(options?.kyOptions?.searchParams ?? {})
173
+ ...kySearchParameters
178
174
  }
179
175
  })).json();
180
- return {
181
- data,
182
- ...data
183
- };
184
176
  }
185
177
  };
186
178
 
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, ResourceResponse } from '@blizzard-api/core';\r\nimport ky from 'ky';\r\nimport type {\r\n AccessToken,\r\n AccessTokenRequestArguments,\r\n AxiosCompatability,\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 this.ky = ky.create(options.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<AxiosCompatability<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 {\r\n data: response,\r\n ...response,\r\n };\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 * 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 (\r\n options?: AccessTokenRequestArguments,\r\n ): Promise<AxiosCompatability<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 * 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 (\r\n options?: ValidateAccessTokenArguments,\r\n ): Promise<AxiosCompatability<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 {\r\n data: response,\r\n ...response,\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 * @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.\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 headers?: Record<string, string>,\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 const parameters = resource.parameters as Record<string, unknown>;\r\n if (parameters) {\r\n for (const key of Object.keys(parameters)) {\r\n if (parameters[key] === undefined) {\r\n delete parameters[key];\r\n }\r\n }\r\n }\r\n\r\n return {\r\n headers: {\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 * 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 * @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.\r\n * @returns The response from the Blizzard API. See {@link ResourceResponse}.\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 headers?: Record<string, string>,\r\n ): ResourceResponse<AxiosCompatability<T>> {\r\n const url = this.getRequestUrl(resource, options);\r\n const config = this.getRequestConfig(resource, options, headers);\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 searchParams: { ...config.searchParams, ...Object.entries(options?.kyOptions?.searchParams ?? {}) },\r\n });\r\n const data = await response.json();\r\n\r\n return {\r\n data,\r\n ...data,\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\n\r\nconst getTokenExpiration = (expiresIn: number) => expiresIn * 1000 - 60_000;\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 const refreshToken = async () => {\r\n const response = await client.getAccessToken();\r\n\r\n client.setAccessToken(response.access_token);\r\n\r\n if (typeof onTokenRefresh === 'function') {\r\n onTokenRefresh?.(response);\r\n }\r\n\r\n //Schedule a refresh of the token\r\n const timeout = setTimeout(() => void refreshToken(), getTokenExpiration(response.expires_in));\r\n timeout.unref();\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 expiry = getTokenExpiration(validatedToken.exp);\r\n //If token is expiring in less than 60 seconds, refresh the token\r\n if (expiry - Date.now() < 60_000) {\r\n await refreshToken();\r\n } else {\r\n //If token is not expiring, schedule a refresh\r\n const timeout = setTimeout(() => void refreshToken(), expiry - Date.now());\r\n //Unref the timeout so the process can exit\r\n timeout.unref();\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":[],"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, ResourceResponse } 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 this.ky = ky.create(options.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 * 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 * 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 * 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 /**\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 * @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.\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 headers?: Record<string, string>,\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 const parameters = resource.parameters as Record<string, unknown>;\r\n if (parameters) {\r\n for (const key of Object.keys(parameters)) {\r\n if (parameters[key] === undefined) {\r\n delete parameters[key];\r\n }\r\n }\r\n }\r\n\r\n return {\r\n headers: {\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 * 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 * @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.\r\n * @returns The response from the Blizzard API. See {@link ResourceResponse}.\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 headers?: Record<string, string>,\r\n ): ResourceResponse<T> {\r\n const url = this.getRequestUrl(resource, options);\r\n const config = this.getRequestConfig(resource, options, headers);\r\n\r\n const kyHeaders = {};\r\n for (const value of Object.entries(options?.kyOptions?.headers ?? {})) {\r\n Object.assign(kyHeaders, value);\r\n }\r\n\r\n const kySearchParameters = {};\r\n for (const value of Object.entries(options?.kyOptions?.searchParams ?? {})) {\r\n Object.assign(kySearchParameters, 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 ...kyHeaders,\r\n },\r\n searchParams: { ...config.searchParams, ...kySearchParameters },\r\n });\r\n const data = await response.json();\r\n\r\n return data;\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\nconst getTokenExpiration = (expiresIn: number) => expiresIn * 1000 - 60_000;\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 const refreshToken = async () => {\r\n const response = await client.getAccessToken();\r\n\r\n client.setAccessToken(response.access_token);\r\n\r\n if (typeof onTokenRefresh === 'function') {\r\n onTokenRefresh?.(response);\r\n }\r\n\r\n //Schedule a refresh of the token\r\n const timeout = setTimeout(() => void refreshToken(), getTokenExpiration(response.expires_in));\r\n timeout.unref();\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 expiry = getTokenExpiration(validatedToken.exp);\r\n //If token is expiring in less than 60 seconds, refresh the token\r\n if (expiry - Date.now() < 60_000) {\r\n await refreshToken();\r\n } else {\r\n //If token is not expiring, schedule a refresh\r\n const timeout = setTimeout(() => void refreshToken(), expiry - Date.now());\r\n //Unref the timeout so the process can exit\r\n timeout.unref();\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,13 +1,13 @@
1
1
  {
2
2
  "name": "@blizzard-api/client",
3
- "version": "2.3.3",
3
+ "version": "3.0.1",
4
4
  "license": "MIT",
5
5
  "author": "Putro",
6
6
  "description": "A node.js client to integrate with the blizzard battle.net api.",
7
7
  "repository": "https://github.com/Pewtro/blizzard-api/tree/main/packages/client",
8
8
  "type": "module",
9
9
  "engines": {
10
- "node": "^18.18 || >=20.9"
10
+ "node": "^20.19.0 || ^22.13.0 || >=24"
11
11
  },
12
12
  "module": "./dist/index.js",
13
13
  "types": "./dist/index.d.ts",
@@ -40,18 +40,18 @@
40
40
  "hearthstone"
41
41
  ],
42
42
  "dependencies": {
43
- "ky": "1.14.0"
43
+ "ky": "1.14.3"
44
44
  },
45
45
  "peerDependencies": {
46
- "@blizzard-api/core": "2.1.1"
46
+ "@blizzard-api/core": "3.0.1"
47
47
  },
48
48
  "devDependencies": {
49
- "@blizzard-api/core": "2.1.1",
50
- "@blizzard-api/d3": "1.0.5",
51
- "@blizzard-api/hs": "1.0.5",
52
- "@blizzard-api/classic-wow": "2.1.3",
53
- "@blizzard-api/sc2": "1.0.5",
54
- "@blizzard-api/wow": "2.1.0"
49
+ "@blizzard-api/classic-wow": "3.0.1",
50
+ "@blizzard-api/core": "3.0.1",
51
+ "@blizzard-api/d3": "2.0.1",
52
+ "@blizzard-api/hs": "2.0.1",
53
+ "@blizzard-api/sc2": "2.0.1",
54
+ "@blizzard-api/wow": "3.0.1"
55
55
  },
56
56
  "scripts": {
57
57
  "build": "tsdown",