Skip to content

Commit

Permalink
fix(RequestHandler): handle invalid json/html response (#136)
Browse files Browse the repository at this point in the history
  • Loading branch information
csuvajit committed Nov 19, 2022
1 parent 77903f2 commit d5f0443
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 34 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -4,3 +4,4 @@ node_modules/
.idea/
dist/
.vercel
test/
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "clashofclans.js",
"version": "2.9.1",
"version": "2.9.2",
"description": "JavaScript library for interacting with the Clash of Clans API",
"author": "SUVAJIT <suvajit.me@gmail.com>",
"license": "MIT",
Expand Down
72 changes: 41 additions & 31 deletions src/rest/RequestHandler.ts
@@ -1,5 +1,5 @@
import https from 'https';
import fetch from 'node-fetch';
import fetch, { FetchError } from 'node-fetch';
import { HTTPError, PrivateWarLogError } from './HTTPError';
import { QueueThrottler, BatchThrottler } from './Throttler';
import { Response, RequestOptions, LoginOptions, Store, RequestHandlerOptions } from '../types';
Expand Down Expand Up @@ -71,40 +71,50 @@ export class RequestHandler {
}

private async exec<T>(path: string, options: RequestOptions = {}, retries = 0): Promise<Response<T>> {
const res = await fetch(`${this.baseURL}${path}`, {
agent,
body: options.body,
method: options.method,
timeout: options.restRequestTimeout ?? this.restRequestTimeout,
headers: { 'Authorization': `Bearer ${this._key}`, 'Content-Type': 'application/json' }
}).catch(() => null);

const data = await res?.json().catch(() => null);
if (!res && retries < (options.retryLimit ?? this.retryLimit)) return this.exec<T>(path, options, ++retries);

if (
this.creds &&
res?.status === 403 &&
data?.reason === 'accessDenied.invalidIp' &&
retries < (options.retryLimit ?? this.retryLimit)
) {
const keys = await this.reValidateKeys().then(() => () => this.login());
if (keys.length) return this.exec<T>(path, options, ++retries);
}
try {
const res = await fetch(`${this.baseURL}${path}`, {
agent,
body: options.body,
method: options.method,
timeout: options.restRequestTimeout ?? this.restRequestTimeout,
headers: { 'Authorization': `Bearer ${this._key}`, 'Content-Type': 'application/json' }
});

if (res.status === 504 && retries < (options.retryLimit ?? this.retryLimit)) {
return await this.exec<T>(path, options, ++retries);
}
const data = await res.json();

if (
this.creds &&
res.status === 403 &&
data.reason === 'accessDenied.invalidIp' &&
retries < (options.retryLimit ?? this.retryLimit)
) {
const keys = await this.reValidateKeys().then(() => () => this.login());
if (keys.length) return await this.exec<T>(path, options, ++retries);
}

const maxAge = Number(res?.headers.get('cache-control')?.split('=')?.[1] ?? 0) * 1000;
const maxAge = Number(res.headers.get('cache-control')?.split('=')?.[1] ?? 0) * 1000;

if (res?.status === 403 && !data?.message && this.rejectIfNotValid) {
throw new HTTPError(PrivateWarLogError, res.status, path, maxAge);
}
if (!res?.ok && this.rejectIfNotValid) {
throw new HTTPError(data, res?.status ?? 504, path, maxAge, options.method);
}
if (res.status === 403 && !data?.message && this.rejectIfNotValid) {
throw new HTTPError(PrivateWarLogError, res.status, path, maxAge);
}
if (!res.ok && this.rejectIfNotValid) {
throw new HTTPError(data, res.status, path, maxAge, options.method);
}

if (this.cached && maxAge > 0 && options.cache !== false && res?.ok) {
await this.cached.set(path, { data, ttl: Date.now() + maxAge, status: res.status }, maxAge);
if (this.cached && maxAge > 0 && options.cache !== false && res.ok) {
await this.cached.set(path, { data, ttl: Date.now() + maxAge, status: res.status }, maxAge);
}
return { data, maxAge, status: res.status, path, ok: res.status === 200 };
} catch (error) {
if (error instanceof FetchError && error.type === 'request-timeout' && retries < (options.retryLimit ?? this.retryLimit)) {
return this.exec<T>(path, options, ++retries);
}
if (this.rejectIfNotValid) throw error;
return { data: { message: (error as FetchError).message } as unknown as T, maxAge: 0, status: 500, path, ok: false };
}
return { data, maxAge, status: res?.status ?? 504, path, ok: res?.status === 200 };
}

public async init(options: LoginOptions) {
Expand Down

1 comment on commit d5f0443

@csuvajit
Copy link
Contributor Author

@csuvajit csuvajit commented on d5f0443 Nov 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for clashofclans-js ready!

✅ Preview
https://clashofclans-1ekst4kwh-csuvajit.vercel.app

Built with commit d5f0443.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.