@blizzard-api/client 4.0.0 → 5.0.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/README.md +61 -0
- package/dist/index.d.ts +32 -35
- package/dist/index.js +84 -63
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -37,6 +37,67 @@ console.log(response);
|
|
|
37
37
|
^ typeof AuctionHouseCommoditiesResponse
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
## Client options
|
|
41
|
+
|
|
42
|
+
`createBlizzardApiClient` accepts a `ClientOptions` object:
|
|
43
|
+
|
|
44
|
+
| Option | Type | Required | Description |
|
|
45
|
+
| ----------- | ----------- | -------- | --------------------------------------------------------------------------------------------------- |
|
|
46
|
+
| `key` | `string` | Yes | Your Blizzard API client ID. |
|
|
47
|
+
| `secret` | `string` | Yes | Your Blizzard API client secret. |
|
|
48
|
+
| `origin` | `Origins` | Yes | The region to talk to, e.g. `'eu'`, `'us'`, `'kr'`, `'tw'`, `'cn'`. |
|
|
49
|
+
| `locale` | `Locales` | No | The locale to request responses in, e.g. `'en_GB'`. Defaults to a sensible locale for the `origin`. |
|
|
50
|
+
| `token` | `string` | No | A pre-existing access token. If supplied it is validated and reused when still valid. |
|
|
51
|
+
| `kyOptions` | `KyOptions` | No | Options forwarded to the underlying [`ky`](https://github.com/sindresorhus/ky) instance. |
|
|
52
|
+
|
|
53
|
+
### Automatic token refresh
|
|
54
|
+
|
|
55
|
+
`createBlizzardApiClient` takes an optional second argument, `onTokenRefresh`, that controls how access tokens are managed:
|
|
56
|
+
|
|
57
|
+
- `true` (default) — the client obtains a token on creation and automatically schedules a refresh shortly before it expires.
|
|
58
|
+
- `false` — the client is returned without obtaining or scheduling any token. You are responsible for calling `getAccessToken`/`setAccessToken` yourself.
|
|
59
|
+
- `(token: AccessToken) => void` — behaves like `true`, but your callback is invoked with each newly obtained token (useful for persisting it).
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const client = await createBlizzardApiClient({ key, secret, origin: 'eu' }, (token) => {
|
|
63
|
+
console.log('Refreshed token', token.access_token);
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If a `token` is provided in the options, it is validated first. A still-valid token is reused and a refresh is scheduled for when it nears expiry; an expired or invalid token is refreshed immediately. Refresh timers are `unref`'d, so they never keep your process alive.
|
|
68
|
+
|
|
69
|
+
## Sending requests
|
|
70
|
+
|
|
71
|
+
Pass a resource from one of the game packages to `sendRequest`. The response type is inferred from the resource, and per-request overrides can be supplied:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { wow } from '@blizzard-api/wow';
|
|
75
|
+
|
|
76
|
+
const response = await client.sendRequest(wow.commodities(), {
|
|
77
|
+
locale: 'de_DE',
|
|
78
|
+
// Any other ClientOptions field can be overridden per request,
|
|
79
|
+
// including kyOptions for retries, timeouts, hooks, etc.
|
|
80
|
+
kyOptions: { timeout: 10_000 },
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`sendRequest` resolves to `undefined` for endpoints that return `204 No Content` or an empty body.
|
|
85
|
+
|
|
86
|
+
## API
|
|
87
|
+
|
|
88
|
+
The client returned by `createBlizzardApiClient` (an instance of `BlizzardApiClient`) exposes:
|
|
89
|
+
|
|
90
|
+
| Member | Description |
|
|
91
|
+
| -------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
92
|
+
| `defaults` | The resolved default `key`, `secret`, `origin`, `locale`, and current `token`. |
|
|
93
|
+
| `sendRequest(resource, options?)` | Send a request for a resource and return its typed response (or `undefined` for empty responses). |
|
|
94
|
+
| `getAccessToken(options?)` | Request a new access token using the client-credentials flow. |
|
|
95
|
+
| `refreshAccessToken(options?)` | Request a new access token and store it on the client. |
|
|
96
|
+
| `validateAccessToken(options?)` | Validate an access token against Blizzard's OAuth endpoint. |
|
|
97
|
+
| `setAccessToken(token)` | Manually set the access token used for subsequent requests. |
|
|
98
|
+
| `getRequestUrl(resource, options?)` | Build the request URL for a resource without sending it. |
|
|
99
|
+
| `getRequestConfig(resource, options?)` | Build the headers and search params for a resource without sending it. |
|
|
100
|
+
|
|
40
101
|
## Authentication
|
|
41
102
|
|
|
42
103
|
Please refer to the [battle.net documentation](https://develop.battle.net/documentation/guides/getting-started) for guides on how to obtain Blizzard API credentials.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Locales, Origins, Resource, getBlizzardApi } from "@blizzard-api/core";
|
|
2
2
|
import { Options } from "ky";
|
|
3
|
-
|
|
4
3
|
//#region src/client/types.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* An access token response from the Blizzard API.
|
|
@@ -127,43 +126,13 @@ declare class BlizzardApiClient {
|
|
|
127
126
|
* // => 'client-id'
|
|
128
127
|
*/
|
|
129
128
|
getAccessToken: (options?: AccessTokenRequestArguments) => Promise<AccessToken>;
|
|
130
|
-
/**
|
|
131
|
-
* Set the access token.
|
|
132
|
-
* @param token The access token.
|
|
133
|
-
*/
|
|
134
|
-
setAccessToken: (token: string) => void;
|
|
135
|
-
/**
|
|
136
|
-
* Refresh the access token.
|
|
137
|
-
* @param options The access token request arguments. See {@link AccessTokenRequestArguments}.
|
|
138
|
-
* @returns The access token. See {@link AccessToken}.
|
|
139
|
-
* @example
|
|
140
|
-
* const response = await client.refreshAccessToken();
|
|
141
|
-
* const { access_token, token_type, expires_in, sub } = response;
|
|
142
|
-
* console.log(access_token, token_type, expires_in, sub);
|
|
143
|
-
* // => 'access'
|
|
144
|
-
* // => 'bearer'
|
|
145
|
-
* // => 86399
|
|
146
|
-
* // => 'client-id'
|
|
147
|
-
*/
|
|
148
|
-
refreshAccessToken: (options?: AccessTokenRequestArguments) => Promise<AccessToken>;
|
|
149
|
-
/**
|
|
150
|
-
* Validate an access token.
|
|
151
|
-
* @param options The validate access token arguments. See {@link ValidateAccessTokenArguments}.
|
|
152
|
-
* @returns The response from the Blizzard API. See {@link ValidateAccessTokenResponse}.
|
|
153
|
-
* @example
|
|
154
|
-
* const response = await client.validateAccessToken({ token: 'access' });
|
|
155
|
-
* console.log(response.client_id);
|
|
156
|
-
* // => 'client-id'
|
|
157
|
-
*/
|
|
158
|
-
validateAccessToken: (options?: ValidateAccessTokenArguments) => Promise<ValidateAccessTokenResponse>;
|
|
159
129
|
/**
|
|
160
130
|
* Get the request configuration.
|
|
161
131
|
* @param resource The resource to fetch. See {@link Resource}.
|
|
162
132
|
* @param options Client options. See {@link ClientOptions}.
|
|
163
|
-
* @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.
|
|
164
133
|
* @returns The request configuration.
|
|
165
134
|
*/
|
|
166
|
-
getRequestConfig<T, Protected extends boolean = false>(resource: Resource<T, object, Protected>, options?: Partial<ClientOptions
|
|
135
|
+
getRequestConfig<T, Protected extends boolean = false>(resource: Resource<T, object, Protected>, options?: Partial<ClientOptions>): {
|
|
167
136
|
headers: Record<string, string> & {
|
|
168
137
|
Authorization: `Bearer ${string}`;
|
|
169
138
|
'Battlenet-Namespace'?: string;
|
|
@@ -180,14 +149,42 @@ declare class BlizzardApiClient {
|
|
|
180
149
|
* @returns The request URL.
|
|
181
150
|
*/
|
|
182
151
|
getRequestUrl<T, Protected extends boolean = false>(resource: Resource<T, object, Protected>, options?: Partial<ClientOptions>): string;
|
|
152
|
+
/**
|
|
153
|
+
* Refresh the access token.
|
|
154
|
+
* @param options The access token request arguments. See {@link AccessTokenRequestArguments}.
|
|
155
|
+
* @returns The access token. See {@link AccessToken}.
|
|
156
|
+
* @example
|
|
157
|
+
* const response = await client.refreshAccessToken();
|
|
158
|
+
* const { access_token, token_type, expires_in, sub } = response;
|
|
159
|
+
* console.log(access_token, token_type, expires_in, sub);
|
|
160
|
+
* // => 'access'
|
|
161
|
+
* // => 'bearer'
|
|
162
|
+
* // => 86399
|
|
163
|
+
* // => 'client-id'
|
|
164
|
+
*/
|
|
165
|
+
refreshAccessToken: (options?: AccessTokenRequestArguments) => Promise<AccessToken>;
|
|
183
166
|
/**
|
|
184
167
|
* Send a request to the Blizzard API.
|
|
185
168
|
* @param resource The resource to fetch. See {@link Resource}.
|
|
186
169
|
* @param options Client options. See {@link ClientOptions}.
|
|
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.
|
|
188
170
|
* @returns The response from the Blizzard API.
|
|
189
171
|
*/
|
|
190
|
-
sendRequest<T, Protected extends boolean = false>(resource: Resource<T, object, Protected>, options?: Partial<ClientOptions
|
|
172
|
+
sendRequest<T, Protected extends boolean = false>(resource: Resource<T, object, Protected>, options?: Partial<ClientOptions>): Promise<T | undefined>;
|
|
173
|
+
/**
|
|
174
|
+
* Set the access token.
|
|
175
|
+
* @param token The access token.
|
|
176
|
+
*/
|
|
177
|
+
setAccessToken: (token: string) => void;
|
|
178
|
+
/**
|
|
179
|
+
* Validate an access token.
|
|
180
|
+
* @param options The validate access token arguments. See {@link ValidateAccessTokenArguments}.
|
|
181
|
+
* @returns The response from the Blizzard API. See {@link ValidateAccessTokenResponse}.
|
|
182
|
+
* @example
|
|
183
|
+
* const response = await client.validateAccessToken({ token: 'access' });
|
|
184
|
+
* console.log(response.client_id);
|
|
185
|
+
* // => 'client-id'
|
|
186
|
+
*/
|
|
187
|
+
validateAccessToken: (options?: ValidateAccessTokenArguments) => Promise<ValidateAccessTokenResponse>;
|
|
191
188
|
}
|
|
192
189
|
//#endregion
|
|
193
190
|
//#region src/client/create-client.d.ts
|
|
@@ -199,5 +196,5 @@ declare class BlizzardApiClient {
|
|
|
199
196
|
*/
|
|
200
197
|
declare const createBlizzardApiClient: (options: ClientOptions, onTokenRefresh?: ((token: AccessToken) => void) | boolean) => Promise<BlizzardApiClient>;
|
|
201
198
|
//#endregion
|
|
202
|
-
export { AccessToken, AccessTokenRequestArguments, ClientOptions, ValidateAccessTokenArguments, ValidateAccessTokenResponse, createBlizzardApiClient, createBlizzardApiClient as default };
|
|
199
|
+
export { type AccessToken, type AccessTokenRequestArguments, type ClientOptions, type ValidateAccessTokenArguments, type ValidateAccessTokenResponse, createBlizzardApiClient, createBlizzardApiClient as default };
|
|
203
200
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -57,71 +57,24 @@ var BlizzardApiClient = class {
|
|
|
57
57
|
}).json();
|
|
58
58
|
};
|
|
59
59
|
/**
|
|
60
|
-
* Set the access token.
|
|
61
|
-
* @param token The access token.
|
|
62
|
-
*/
|
|
63
|
-
setAccessToken = (token) => {
|
|
64
|
-
this.defaults.token = token;
|
|
65
|
-
};
|
|
66
|
-
/**
|
|
67
|
-
* Refresh the access token.
|
|
68
|
-
* @param options The access token request arguments. See {@link AccessTokenRequestArguments}.
|
|
69
|
-
* @returns The access token. See {@link AccessToken}.
|
|
70
|
-
* @example
|
|
71
|
-
* const response = await client.refreshAccessToken();
|
|
72
|
-
* const { access_token, token_type, expires_in, sub } = response;
|
|
73
|
-
* console.log(access_token, token_type, expires_in, sub);
|
|
74
|
-
* // => 'access'
|
|
75
|
-
* // => 'bearer'
|
|
76
|
-
* // => 86399
|
|
77
|
-
* // => 'client-id'
|
|
78
|
-
*/
|
|
79
|
-
refreshAccessToken = async (options) => {
|
|
80
|
-
const response = await this.getAccessToken(options);
|
|
81
|
-
this.setAccessToken(response.access_token);
|
|
82
|
-
return response;
|
|
83
|
-
};
|
|
84
|
-
/**
|
|
85
|
-
* Validate an access token.
|
|
86
|
-
* @param options The validate access token arguments. See {@link ValidateAccessTokenArguments}.
|
|
87
|
-
* @returns The response from the Blizzard API. See {@link ValidateAccessTokenResponse}.
|
|
88
|
-
* @example
|
|
89
|
-
* const response = await client.validateAccessToken({ token: 'access' });
|
|
90
|
-
* console.log(response.client_id);
|
|
91
|
-
* // => 'client-id'
|
|
92
|
-
*/
|
|
93
|
-
validateAccessToken = async (options) => {
|
|
94
|
-
const { origin, token } = {
|
|
95
|
-
...this.defaults,
|
|
96
|
-
...options
|
|
97
|
-
};
|
|
98
|
-
if (!token) throw new Error("No token has been set previously or been passed to the validateAccessToken method.");
|
|
99
|
-
return await this.ky.post(`https://${origin}.battle.net/oauth/check_token`, {
|
|
100
|
-
body: stringify({ token }),
|
|
101
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" }
|
|
102
|
-
}).json();
|
|
103
|
-
};
|
|
104
|
-
/**
|
|
105
60
|
* Get the request configuration.
|
|
106
61
|
* @param resource The resource to fetch. See {@link Resource}.
|
|
107
62
|
* @param options Client options. See {@link ClientOptions}.
|
|
108
|
-
* @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.
|
|
109
63
|
* @returns The request configuration.
|
|
110
64
|
*/
|
|
111
|
-
getRequestConfig(resource, options
|
|
65
|
+
getRequestConfig(resource, options) {
|
|
112
66
|
const config = {
|
|
113
67
|
...this.defaults,
|
|
114
68
|
...options
|
|
115
69
|
};
|
|
116
70
|
const endpoint = getBlizzardApi(config.origin, config.locale);
|
|
117
71
|
const namespace = resource.namespace ? { "Battlenet-Namespace": `${resource.namespace}-${endpoint.origin}` } : void 0;
|
|
118
|
-
const parameters =
|
|
119
|
-
if (parameters) {
|
|
120
|
-
for (const key of Object.
|
|
72
|
+
const parameters = {};
|
|
73
|
+
if (resource.parameters) {
|
|
74
|
+
for (const [key, value] of Object.entries(resource.parameters)) if (value !== void 0) parameters[key] = value;
|
|
121
75
|
}
|
|
122
76
|
return {
|
|
123
77
|
headers: {
|
|
124
|
-
...headers,
|
|
125
78
|
...namespace,
|
|
126
79
|
Authorization: `Bearer ${config.token}`,
|
|
127
80
|
"Content-Type": "application/json"
|
|
@@ -148,15 +101,32 @@ var BlizzardApiClient = class {
|
|
|
148
101
|
return `${endpoint.hostname}${backslashSeparator}${resource.path}`;
|
|
149
102
|
}
|
|
150
103
|
/**
|
|
104
|
+
* Refresh the access token.
|
|
105
|
+
* @param options The access token request arguments. See {@link AccessTokenRequestArguments}.
|
|
106
|
+
* @returns The access token. See {@link AccessToken}.
|
|
107
|
+
* @example
|
|
108
|
+
* const response = await client.refreshAccessToken();
|
|
109
|
+
* const { access_token, token_type, expires_in, sub } = response;
|
|
110
|
+
* console.log(access_token, token_type, expires_in, sub);
|
|
111
|
+
* // => 'access'
|
|
112
|
+
* // => 'bearer'
|
|
113
|
+
* // => 86399
|
|
114
|
+
* // => 'client-id'
|
|
115
|
+
*/
|
|
116
|
+
refreshAccessToken = async (options) => {
|
|
117
|
+
const response = await this.getAccessToken(options);
|
|
118
|
+
this.setAccessToken(response.access_token);
|
|
119
|
+
return response;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
151
122
|
* Send a request to the Blizzard API.
|
|
152
123
|
* @param resource The resource to fetch. See {@link Resource}.
|
|
153
124
|
* @param options Client options. See {@link ClientOptions}.
|
|
154
|
-
* @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.
|
|
155
125
|
* @returns The response from the Blizzard API.
|
|
156
126
|
*/
|
|
157
|
-
async sendRequest(resource, options
|
|
127
|
+
async sendRequest(resource, options) {
|
|
158
128
|
const url = new URL(this.getRequestUrl(resource, options));
|
|
159
|
-
const config = this.getRequestConfig(resource, options
|
|
129
|
+
const config = this.getRequestConfig(resource, options);
|
|
160
130
|
for (const [key, value] of Object.entries(config.searchParams)) url.searchParams.set(key, String(value));
|
|
161
131
|
const response = await this.ky.get(url, {
|
|
162
132
|
...options?.kyOptions,
|
|
@@ -165,13 +135,61 @@ var BlizzardApiClient = class {
|
|
|
165
135
|
...options?.kyOptions?.headers
|
|
166
136
|
}
|
|
167
137
|
});
|
|
168
|
-
if (response.status === 204 || response.headers.get("Content-Length") === "0") return
|
|
138
|
+
if (response.status === 204 || response.headers.get("Content-Length") === "0") return;
|
|
169
139
|
return await response.json();
|
|
170
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Set the access token.
|
|
143
|
+
* @param token The access token.
|
|
144
|
+
*/
|
|
145
|
+
setAccessToken = (token) => {
|
|
146
|
+
this.defaults.token = token;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Validate an access token.
|
|
150
|
+
* @param options The validate access token arguments. See {@link ValidateAccessTokenArguments}.
|
|
151
|
+
* @returns The response from the Blizzard API. See {@link ValidateAccessTokenResponse}.
|
|
152
|
+
* @example
|
|
153
|
+
* const response = await client.validateAccessToken({ token: 'access' });
|
|
154
|
+
* console.log(response.client_id);
|
|
155
|
+
* // => 'client-id'
|
|
156
|
+
*/
|
|
157
|
+
validateAccessToken = async (options) => {
|
|
158
|
+
const { origin, token } = {
|
|
159
|
+
...this.defaults,
|
|
160
|
+
...options
|
|
161
|
+
};
|
|
162
|
+
if (!token) throw new Error("No token has been set previously or been passed to the validateAccessToken method.");
|
|
163
|
+
return await this.ky.post(`https://${origin}.battle.net/oauth/check_token`, {
|
|
164
|
+
body: stringify({ token }),
|
|
165
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" }
|
|
166
|
+
}).json();
|
|
167
|
+
};
|
|
171
168
|
};
|
|
172
169
|
//#endregion
|
|
173
170
|
//#region src/client/create-client.ts
|
|
174
|
-
|
|
171
|
+
/**
|
|
172
|
+
* How long before a token's actual expiry it should be refreshed, in milliseconds.
|
|
173
|
+
* This buffer avoids using a token that could expire mid-request.
|
|
174
|
+
*/
|
|
175
|
+
const REFRESH_BUFFER_MS = 6e4;
|
|
176
|
+
/**
|
|
177
|
+
* Calculate the delay (in milliseconds) to wait before refreshing a token, based on the
|
|
178
|
+
* token's remaining lifetime.
|
|
179
|
+
* @param lifetimeInSeconds The token's lifetime in seconds, as returned in the `expires_in`
|
|
180
|
+
* field when requesting a new access token.
|
|
181
|
+
* @returns The delay in milliseconds before the token should be refreshed.
|
|
182
|
+
*/
|
|
183
|
+
const getRefreshDelayFromLifetime = (lifetimeInSeconds) => lifetimeInSeconds * 1e3 - REFRESH_BUFFER_MS;
|
|
184
|
+
/**
|
|
185
|
+
* Calculate the delay (in milliseconds) to wait before refreshing a token, based on the
|
|
186
|
+
* token's absolute expiry time.
|
|
187
|
+
* @param expiryInSeconds The token's expiry as a Unix timestamp in seconds, as returned in the
|
|
188
|
+
* `exp` field when validating an existing access token.
|
|
189
|
+
* @returns The delay in milliseconds before the token should be refreshed. May be negative if the
|
|
190
|
+
* token is already within its refresh buffer or expired.
|
|
191
|
+
*/
|
|
192
|
+
const getRefreshDelayFromExpiry = (expiryInSeconds) => expiryInSeconds * 1e3 - REFRESH_BUFFER_MS - Date.now();
|
|
175
193
|
/**
|
|
176
194
|
* Create a new Blizzard API client.
|
|
177
195
|
* @param options Client options, see {@link ClientOptions} & https://develop.battle.net/documentation/guides/getting-started
|
|
@@ -183,17 +201,20 @@ const createBlizzardApiClient = async (options, onTokenRefresh = true) => {
|
|
|
183
201
|
if (!key) throw new Error(`Client missing 'key' parameter`);
|
|
184
202
|
if (!secret) throw new Error(`Client missing 'secret' parameter`);
|
|
185
203
|
const client = new BlizzardApiClient(options);
|
|
204
|
+
const scheduleRefresh = (delayInMilliseconds) => {
|
|
205
|
+
setTimeout(() => void refreshToken(), delayInMilliseconds).unref();
|
|
206
|
+
};
|
|
186
207
|
const refreshToken = async () => {
|
|
187
|
-
const response = await client.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
setTimeout(() => void refreshToken(), getTokenExpiration(response.expires_in)).unref();
|
|
208
|
+
const response = await client.refreshAccessToken();
|
|
209
|
+
if (typeof onTokenRefresh === "function") onTokenRefresh(response);
|
|
210
|
+
scheduleRefresh(getRefreshDelayFromLifetime(response.expires_in));
|
|
191
211
|
};
|
|
192
212
|
if (!onTokenRefresh) return client;
|
|
193
213
|
if (token) try {
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
214
|
+
const validatedToken = await client.validateAccessToken({ token });
|
|
215
|
+
const refreshDelay = getRefreshDelayFromExpiry(validatedToken.exp);
|
|
216
|
+
if (refreshDelay < REFRESH_BUFFER_MS) await refreshToken();
|
|
217
|
+
else scheduleRefresh(refreshDelay);
|
|
197
218
|
} catch {
|
|
198
219
|
await refreshToken();
|
|
199
220
|
}
|
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 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.\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 ): Promise<T> {\r\n const url = new URL(this.getRequestUrl(resource, options));\r\n const config = this.getRequestConfig(resource, options, headers);\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 {} as T;\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":";;;;;;;;;;;;;;;;;AAwBA,IAAa,oBAAb,MAA+B;CAC7B;CAQA;CAEA,YAAY,SAAwB;EAClC,MAAM,EAAE,QAAQ,WAAW,eAAe,QAAQ,QAAQ,QAAQ,OAAO;AACzE,OAAK,WAAW;GACd,KAAK,QAAQ;GACL;GACA;GACR,QAAQ,QAAQ;GAChB,OAAO,QAAQ;GAChB;AACD,OAAK,KAAK,GAAG,OAAO,QAAQ,UAAU;;;;;;;;;;;;;;;CAgBxC,iBAAwB,OAAO,YAAgE;EAC7F,MAAM,EAAE,KAAK,QAAQ,WAAW;GAAE,GAAG,KAAK;GAAU,GAAG;GAAS;EAChE,MAAM,YAAY,OAAO,KAAK,GAAG,IAAI,GAAG,SAAS,CAAC,SAAS,SAAS;AAapE,SAAO,MAZgB,KAAK,GACzB,KAAkB,WAAW,OAAO,0BAA0B;GAC7D,SAAS;IACP,eAAe,SAAS;IACxB,gBAAgB;IACjB;GACD,cAAc,EACZ,YAAY,sBACb;GACF,CAAC,CACD,MAAM;;;;;;CASX,kBAAyB,UAAwB;AAC/C,OAAK,SAAS,QAAQ;;;;;;;;;;;;;;;CAgBxB,qBAA4B,OAAO,YAAgE;EACjG,MAAM,WAAW,MAAM,KAAK,eAAe,QAAQ;AACnD,OAAK,eAAe,SAAS,aAAa;AAC1C,SAAO;;;;;;;;;;;CAYT,sBAA6B,OAAO,YAAiF;EACnH,MAAM,EAAE,QAAQ,UAAU;GAAE,GAAG,KAAK;GAAU,GAAG;GAAS;AAE1D,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,qFAAqF;AAYvG,SAAO,MATgB,KAAK,GACzB,KAAkC,WAAW,OAAO,gCAAgC;GACnF,MAAM,UAAU,EAAE,OAAO,CAAC;GAC1B,SAAS,EACP,gBAAgB,qCACjB;GACF,CAAC,CACD,MAAM;;;;;;;;;CAYX,iBACE,UACA,SACA,SAQA;EACA,MAAM,SAAS;GAAE,GAAG,KAAK;GAAU,GAAG;GAAS;EAC/C,MAAM,WAAW,eAAe,OAAO,QAAQ,OAAO,OAAO;EAE7D,MAAM,YAAY,SAAS,YACvB,EAAE,uBAAuB,GAAG,SAAS,UAAU,GAAG,SAAS,UAAU,GACrE,KAAA;EAEJ,MAAM,aAAa,SAAS;AAC5B,MAAI;QACG,MAAM,OAAO,OAAO,KAAK,WAAW,CACvC,KAAI,WAAW,SAAS,KAAA,EACtB,QAAO,WAAW;;AAKxB,SAAO;GACL,SAAS;IACP,GAAG;IACH,GAAG;IACH,eAAe,UAAU,OAAO;IAChC,gBAAgB;IACjB;GACD,cAAc;IACZ,GAAG;IACH,QAAQ,SAAS;IAClB;GACF;;;;;;;;CASH,cACE,UACA,SACA;EACA,MAAM,SAAS;GAAE,GAAG,KAAK;GAAU,GAAG;GAAS;EAC/C,MAAM,WAAW,eAAe,OAAO,QAAQ,OAAO,OAAO;EAE7D,MAAM,qBAAqB,SAAS,KAAK,WAAW,IAAI,GAAG,KAAK;AAEhE,SAAO,GAAG,SAAS,WAAW,qBAAqB,SAAS;;;;;;;;;CAU9D,MAAa,YACX,UACA,SACA,SACY;EACZ,MAAM,MAAM,IAAI,IAAI,KAAK,cAAc,UAAU,QAAQ,CAAC;EAC1D,MAAM,SAAS,KAAK,iBAAiB,UAAU,SAAS,QAAQ;AAEhE,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,aAAa,CAC5D,KAAI,aAAa,IAAI,KAAK,OAAO,MAAM,CAAC;EAG1C,MAAM,WAAW,MAAM,KAAK,GAAG,IAAO,KAAK;GACzC,GAAG,SAAS;GACZ,SAAS;IACP,GAAG,OAAO;IACV,GAAG,SAAS,WAAW;IACxB;GACF,CAAC;AAMF,MAAI,SAAS,WAAW,OAAO,SAAS,QAAQ,IAAI,iBAAiB,KAAK,IACxE,QAAO,EAAE;AAIX,SAAO,MAFY,SAAS,MAAM;;;;;ACrOtC,MAAM,sBAAsB,cAAsB,YAAY,MAAO;;;;;;;AAQrE,MAAa,0BAA0B,OACrC,SACA,iBAA2D,SAC5B;CAC/B,MAAM,EAAE,KAAK,QAAQ,UAAU;AAC/B,KAAI,CAAC,IACH,OAAM,IAAI,MAAM,iCAAiC;AAEnD,KAAI,CAAC,OACH,OAAM,IAAI,MAAM,oCAAoC;CAGtD,MAAM,SAAS,IAAI,kBAAkB,QAAQ;CAE7C,MAAM,eAAe,YAAY;EAC/B,MAAM,WAAW,MAAM,OAAO,gBAAgB;AAE9C,SAAO,eAAe,SAAS,aAAa;AAE5C,MAAI,OAAO,mBAAmB,WAC5B,kBAAiB,SAAS;AAIZ,mBAAiB,KAAK,cAAc,EAAE,mBAAmB,SAAS,WAAW,CACtF,CAAC,OAAO;;AAIjB,KAAI,CAAC,eACH,QAAO;AAGT,KAAI,MACF,KAAI;EAGF,MAAM,SAAS,oBAAmB,MADL,OAAO,oBAAoB,EAAE,OAAO,CAAC,EACjB,IAAI;AAErD,MAAI,SAAS,KAAK,KAAK,GAAG,IACxB,OAAM,cAAc;MAGJ,kBAAiB,KAAK,cAAc,EAAE,SAAS,KAAK,KAAK,CAElE,CAAC,OAAO;SAEX;AAEN,QAAM,cAAc;;KAItB,OAAM,cAAc;AAGtB,QAAO"}
|
|
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 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 * 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 * 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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blizzard-api/client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Putro",
|
|
6
6
|
"description": "A node.js client to integrate with the blizzard battle.net api.",
|
|
@@ -47,15 +47,15 @@
|
|
|
47
47
|
"ky": "2.0.2"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@blizzard-api/core": "4.
|
|
50
|
+
"@blizzard-api/core": "4.1.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@blizzard-api/classic-wow": "4.0.
|
|
54
|
-
"@blizzard-api/
|
|
55
|
-
"@blizzard-api/
|
|
56
|
-
"@blizzard-api/
|
|
57
|
-
"@blizzard-api/sc2": "3.0.
|
|
58
|
-
"@blizzard-api/wow": "4.0.
|
|
53
|
+
"@blizzard-api/classic-wow": "4.0.3",
|
|
54
|
+
"@blizzard-api/d3": "3.0.1",
|
|
55
|
+
"@blizzard-api/hs": "3.0.1",
|
|
56
|
+
"@blizzard-api/core": "4.1.0",
|
|
57
|
+
"@blizzard-api/sc2": "3.0.1",
|
|
58
|
+
"@blizzard-api/wow": "4.0.6"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "tsdown",
|