clashofclans.js 2.5.2-dev.09bde37 → 2.6.0-dev.3d6d64a
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/CHANGELOG.md +7 -0
- package/dist/client/Client.js +4 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/rest/HTTPError.js +1 -1
- package/dist/rest/RequestHandler.d.ts +1 -0
- package/dist/rest/RequestHandler.js +14 -8
- package/dist/struct/ClanWarLeagueGroup.js +1 -0
- package/dist/types/api.d.ts +1 -1
- package/dist/types/lib.d.ts +8 -3
- package/dist/util/Store.d.ts +28 -0
- package/dist/util/Store.js +43 -0
- package/package.json +1 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## 2.6.0
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Replaced Keyv with customizable cache store ([#99](https://github.com/clashperk/clashofclans.js/pull/99))
|
|
10
|
+
- Guide for [Internal Caching](https://clashofclans.js.org/guide/internal-caching)
|
|
11
|
+
|
|
5
12
|
## 2.5.2 (2022-01-23)
|
|
6
13
|
|
|
7
14
|
### Bug Fixes
|
package/dist/client/Client.js
CHANGED
|
@@ -150,7 +150,10 @@ class Client extends events_1.EventEmitter {
|
|
|
150
150
|
}
|
|
151
151
|
/** Get info about clan war league. */
|
|
152
152
|
async getClanWarLeagueGroup(clanTag, options) {
|
|
153
|
-
const { data } = await this.rest.getClanWarLeagueGroup(clanTag, options);
|
|
153
|
+
const { data, status, path, maxAge } = await this.rest.getClanWarLeagueGroup(clanTag, options);
|
|
154
|
+
if (data.state === 'notInWar') {
|
|
155
|
+
throw new HTTPError_1.HTTPError(HTTPError_1.NotInWarError, status, path, maxAge);
|
|
156
|
+
}
|
|
154
157
|
return new struct_1.ClanWarLeagueGroup(this, data);
|
|
155
158
|
}
|
|
156
159
|
/** Get info about a CWL round by WarTag. */
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/rest/HTTPError.js
CHANGED
|
@@ -11,7 +11,7 @@ const messages = {
|
|
|
11
11
|
403: 'Access denied, either because of missing/incorrect credentials or used API token does not grant access to the requested resource.'
|
|
12
12
|
};
|
|
13
13
|
const reasons = {
|
|
14
|
-
503: '
|
|
14
|
+
503: 'inMaintenance',
|
|
15
15
|
429: 'requestThrottled',
|
|
16
16
|
400: 'badRequest',
|
|
17
17
|
403: 'accessDenied',
|
|
@@ -18,6 +18,7 @@ export declare class RequestHandler {
|
|
|
18
18
|
private get _keys();
|
|
19
19
|
private get _key();
|
|
20
20
|
setKeys(keys: string[]): this;
|
|
21
|
+
private get creds();
|
|
21
22
|
request<T>(path: string, options?: RequestOptions): Promise<Response<T>>;
|
|
22
23
|
private exec;
|
|
23
24
|
init(options: LoginOptions): Promise<string[]>;
|
|
@@ -18,9 +18,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
18
18
|
exports.RequestHandler = void 0;
|
|
19
19
|
const Constants_1 = require("../util/Constants");
|
|
20
20
|
const HTTPError_1 = require("./HTTPError");
|
|
21
|
+
const Store_1 = require("../util/Store");
|
|
21
22
|
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
22
23
|
const https_1 = __importDefault(require("https"));
|
|
23
|
-
const keyv_1 = __importDefault(require("keyv"));
|
|
24
24
|
const IP_REGEX = /\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}/g;
|
|
25
25
|
const agent = new https_1.default.Agent({ keepAlive: true });
|
|
26
26
|
/** Represents a Request Handler. */
|
|
@@ -33,10 +33,10 @@ class RequestHandler {
|
|
|
33
33
|
this.baseURL = options?.baseURL ?? Constants_1.API_BASE_URL;
|
|
34
34
|
this.restRequestTimeout = options?.restRequestTimeout ?? 0;
|
|
35
35
|
this.rejectIfNotValid = options?.rejectIfNotValid ?? true;
|
|
36
|
-
if (options?.cache
|
|
36
|
+
if (typeof options?.cache === 'object')
|
|
37
37
|
this.cached = options.cache;
|
|
38
38
|
else
|
|
39
|
-
this.cached = options?.cache ? new
|
|
39
|
+
this.cached = options?.cache === true ? new Store_1.CacheStore() : null;
|
|
40
40
|
}
|
|
41
41
|
get _keys() {
|
|
42
42
|
return Array.isArray(this.keys) ? this.keys : [this.keys];
|
|
@@ -50,6 +50,9 @@ class RequestHandler {
|
|
|
50
50
|
this.keys = keys;
|
|
51
51
|
return this;
|
|
52
52
|
}
|
|
53
|
+
get creds() {
|
|
54
|
+
return Boolean(this.email && this.password);
|
|
55
|
+
}
|
|
53
56
|
async request(path, options = {}) {
|
|
54
57
|
const cached = (await this.cached?.get(path)) ?? null;
|
|
55
58
|
if (cached && options.force !== true) {
|
|
@@ -76,8 +79,11 @@ class RequestHandler {
|
|
|
76
79
|
const data = await res?.json().catch(() => null);
|
|
77
80
|
if (!res && retries < (options.retryLimit ?? this.retryLimit))
|
|
78
81
|
return this.exec(path, options, ++retries);
|
|
79
|
-
if (
|
|
80
|
-
|
|
82
|
+
if (this.creds &&
|
|
83
|
+
res?.status === 403 &&
|
|
84
|
+
data?.reason === 'accessDenied.invalidIp' &&
|
|
85
|
+
retries < (options.retryLimit ?? this.retryLimit)) {
|
|
86
|
+
const keys = await this.reValidateKeys().then(() => () => this.login());
|
|
81
87
|
if (keys.length)
|
|
82
88
|
return this.exec(path, options, ++retries);
|
|
83
89
|
}
|
|
@@ -114,7 +120,7 @@ class RequestHandler {
|
|
|
114
120
|
if (res?.status === 403) {
|
|
115
121
|
const index = this.keys.indexOf(key);
|
|
116
122
|
this.keys.splice(index, 1);
|
|
117
|
-
process.emitWarning(`
|
|
123
|
+
process.emitWarning(`Key #${index + 1} is no longer valid. Removed from the key list.`);
|
|
118
124
|
}
|
|
119
125
|
}
|
|
120
126
|
}
|
|
@@ -145,14 +151,14 @@ class RequestHandler {
|
|
|
145
151
|
// Get all available keys from the developer site.
|
|
146
152
|
const keys = (data.keys ?? []);
|
|
147
153
|
// Revoke keys for specified key name but not matching current IP address.
|
|
148
|
-
for (const key of keys.filter((key) => key.name === this.keyName && !key.cidrRanges
|
|
154
|
+
for (const key of keys.filter((key) => key.name === this.keyName && !key.cidrRanges?.includes(ip))) {
|
|
149
155
|
if (!(await this.revokeKey(key.id, cookie)))
|
|
150
156
|
continue;
|
|
151
157
|
const index = keys.findIndex(({ id }) => id === key.id);
|
|
152
158
|
keys.splice(index, 1);
|
|
153
159
|
}
|
|
154
160
|
// Filter keys for current IP address and specified key name.
|
|
155
|
-
for (const key of keys.filter((key) => key.name === this.keyName && key.cidrRanges
|
|
161
|
+
for (const key of keys.filter((key) => key.name === this.keyName && key.cidrRanges?.includes(ip))) {
|
|
156
162
|
if (this.keys.length >= this.keyCount)
|
|
157
163
|
break;
|
|
158
164
|
if (!this.keys.includes(key.key))
|
|
@@ -41,6 +41,7 @@ exports.ClanWarLeagueRound = ClanWarLeagueRound;
|
|
|
41
41
|
class ClanWarLeagueGroup {
|
|
42
42
|
constructor(client, data) {
|
|
43
43
|
this.client = client;
|
|
44
|
+
// @ts-expect-error
|
|
44
45
|
this.state = data.state;
|
|
45
46
|
this.season = data.season;
|
|
46
47
|
this.clans = data.clans.map((clan) => new ClanWarLeagueClan(client, clan));
|
package/dist/types/api.d.ts
CHANGED
|
@@ -139,7 +139,7 @@ export interface APIClanWarLog {
|
|
|
139
139
|
}
|
|
140
140
|
/** /clans/{clanTag}/currentwar/leaguegroup */
|
|
141
141
|
export interface APIClanWarLeagueGroup {
|
|
142
|
-
state: 'preparation' | 'inWar' | 'ended';
|
|
142
|
+
state: 'notInWar' | 'preparation' | 'inWar' | 'ended';
|
|
143
143
|
season: string;
|
|
144
144
|
clans: APIClanWarLeagueClan[];
|
|
145
145
|
rounds: APIClanWarLeagueRound[];
|
package/dist/types/lib.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { QueueThrottler, BatchThrottler } from '../rest/Throttler';
|
|
2
|
-
|
|
2
|
+
export interface Store<T = any> {
|
|
3
|
+
set(key: string, value: T, ttl?: number): boolean | Promise<boolean>;
|
|
4
|
+
get(key: string): T | null | Promise<T | null>;
|
|
5
|
+
delete(key: string): boolean | Promise<boolean>;
|
|
6
|
+
clear(): void | Promise<void>;
|
|
7
|
+
}
|
|
3
8
|
/** Options for a Client. */
|
|
4
9
|
export interface ClientOptions {
|
|
5
10
|
/** Keys from Clash of Clans API developer site. */
|
|
@@ -17,7 +22,7 @@ export interface ClientOptions {
|
|
|
17
22
|
* const client = new Client({ cache: true });
|
|
18
23
|
* ```
|
|
19
24
|
*/
|
|
20
|
-
cache?: boolean |
|
|
25
|
+
cache?: boolean | Store;
|
|
21
26
|
/** Time to wait before cancelling a REST request, in milliseconds. */
|
|
22
27
|
restRequestTimeout?: number;
|
|
23
28
|
/**
|
|
@@ -62,7 +67,7 @@ export interface OverrideOptions {
|
|
|
62
67
|
/** Whether to skip the cache check and request the API. */
|
|
63
68
|
force?: boolean;
|
|
64
69
|
/** How many times to retry on 5XX errors. */
|
|
65
|
-
retryLimit?:
|
|
70
|
+
retryLimit?: number;
|
|
66
71
|
/** Whether to ignore throttlers. */
|
|
67
72
|
ignoreRateLimit?: boolean;
|
|
68
73
|
/** Time to wait before cancelling a REST request, in milliseconds. */
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Store } from '../types';
|
|
2
|
+
export interface CacheOptions {
|
|
3
|
+
/**
|
|
4
|
+
* How frequently to remove data from cache that are older than the lifetime/ttl (in milliseconds, 0 for never)
|
|
5
|
+
*
|
|
6
|
+
* To prevent high CPU usage, set a higher value (>= 30 seconds)
|
|
7
|
+
*
|
|
8
|
+
* @default 120000 (2 minutes)
|
|
9
|
+
*/
|
|
10
|
+
sweepInterval?: number;
|
|
11
|
+
/**
|
|
12
|
+
* How long a data should stay in the cache until it is considered sweepable (in milliseconds, 0 for forever)
|
|
13
|
+
*
|
|
14
|
+
* @default 0
|
|
15
|
+
*/
|
|
16
|
+
ttl?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare class CacheStore<T = any> implements Store<T> {
|
|
19
|
+
private readonly ttl;
|
|
20
|
+
private readonly sweepInterval?;
|
|
21
|
+
private readonly store;
|
|
22
|
+
constructor(options?: CacheOptions);
|
|
23
|
+
private _sweep;
|
|
24
|
+
set(key: string, value: T, ttl?: number): boolean;
|
|
25
|
+
get(key: string): T | null;
|
|
26
|
+
delete(key: string): boolean;
|
|
27
|
+
clear(): void;
|
|
28
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CacheStore = void 0;
|
|
4
|
+
class CacheStore {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.store = new Map();
|
|
7
|
+
this.ttl = options?.ttl ?? 0;
|
|
8
|
+
this.sweepInterval = options?.sweepInterval ?? 2 * 60 * 1000;
|
|
9
|
+
if (this.sweepInterval > 0)
|
|
10
|
+
this._sweep(); // sweep expired cache
|
|
11
|
+
}
|
|
12
|
+
_sweep() {
|
|
13
|
+
setInterval(() => {
|
|
14
|
+
for (const cache of this.store.values()) {
|
|
15
|
+
if (cache.expires > 0 && Date.now() > cache.expires) {
|
|
16
|
+
this.store.delete(cache.key);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}, Math.max(this.sweepInterval, 30 * 1000));
|
|
20
|
+
}
|
|
21
|
+
set(key, value, ttl = 0) {
|
|
22
|
+
const expires = ttl > 0 ? Date.now() + ttl : this.ttl > 0 ? Date.now() + this.ttl : 0;
|
|
23
|
+
this.store.set(key, { value, expires, key });
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
get(key) {
|
|
27
|
+
const data = this.store.get(key);
|
|
28
|
+
if (!data)
|
|
29
|
+
return null;
|
|
30
|
+
if (data.expires > 0 && Date.now() > data.expires) {
|
|
31
|
+
this.store.delete(key);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return data.value;
|
|
35
|
+
}
|
|
36
|
+
delete(key) {
|
|
37
|
+
return this.store.delete(key);
|
|
38
|
+
}
|
|
39
|
+
clear() {
|
|
40
|
+
return this.store.clear();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.CacheStore = CacheStore;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clashofclans.js",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0-dev.3d6d64a",
|
|
4
4
|
"description": "JavaScript library for interacting with the Clash of Clans API",
|
|
5
5
|
"author": "SUVAJIT <suvajit.me@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -93,11 +93,9 @@
|
|
|
93
93
|
}
|
|
94
94
|
},
|
|
95
95
|
"dependencies": {
|
|
96
|
-
"keyv": "^4.0.4",
|
|
97
96
|
"node-fetch": "^2.6.7"
|
|
98
97
|
},
|
|
99
98
|
"devDependencies": {
|
|
100
|
-
"@types/keyv": "^3.1.3",
|
|
101
99
|
"@types/node": "^16.10.3",
|
|
102
100
|
"@types/node-fetch": "^2.5.12",
|
|
103
101
|
"@typescript-eslint/eslint-plugin": "^5.4.0",
|