diff --git a/README.md b/README.md index 19c7687..f173dee 100644 --- a/README.md +++ b/README.md @@ -130,9 +130,11 @@ const amo = new Amo("mydomain.amocrm.ru", auth_object, options_object); - `request_delay: number` (ms) - amo backend limits you to _7 reqs/sec_, so the client manages with it by performing requests sequentially with delay (_150ms_ by default). You could set your own delay number (or zero, if you want). -- `on_token: (new_token: OAuth) => void | Promise` - callback, that will be called on _new token_ event (during +- `on_token?: (new_token: OAuth) => void | Promise` - callback, that will be called on _new token_ event (during receiving from a code or refreshing). Lib manages the auth/token stuff for you, but it is strongly recommended to store the new token persistently somewhere you want (fs, db) to provide it on the next app start. +- `on_error?: (error: Error) => void | Promise;` - default error handler. If provided, it will be called instead + of throwing errors. Request lifycycle will not be interrupted and you receive `null` as a response. --- @@ -295,6 +297,18 @@ try { } ``` +Also you could use default non-intercepted error handler passed with the `options` to client constructor: + +```ts +const amo = new Amo("mydomain.amocrm.ru", auth_object, { + on_error: (err) => console.error("Amo emits error", err); +}); +const lead = amo.lead.getLeadById(6969); +if (lead) { + // do logic +} +``` + # Contribution Pull request, issues and feedback are very welcome. Code style is formatted with deno fmt. diff --git a/src/core/rest-client.ts b/src/core/rest-client.ts index 13f7aaa..b15ac9e 100644 --- a/src/core/rest-client.ts +++ b/src/core/rest-client.ts @@ -34,22 +34,18 @@ export class RestClient { } private async authorization(value: OAuthCode | OAuthRefresh): Promise { - try { - const res = await this.queue.push(fetch, `${this.url_base}/oauth2/access_token`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(value), - }); - if (res.ok === false) { - throw new AuthError(res.body ? await res.json() : "Empty"); - } - const data = (await res.json()) as OAuth; - this._token = { ...data, expires_at: Date.now() + (data.expires_in * 1000) }; - if (this.options?.on_token !== undefined) { - this.options.on_token(this._token); - } - } catch (err) { - throw err; + const res = await this.queue.push(fetch, `${this.url_base}/oauth2/access_token`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(value), + }); + if (res.ok === false) { + throw new AuthError(res.body ? await res.json() : "Empty"); + } + const data = (await res.json()) as OAuth; + this._token = { ...data, expires_at: Date.now() + (data.expires_in * 1000) }; + if (this.options?.on_token !== undefined) { + this.options.on_token(this._token); } } @@ -80,21 +76,30 @@ export class RestClient { } async request(method: HttpMethod, init: RequestInit): Promise { - await this.checkToken(); - const target = `${init.url_base ?? this.url_base}${init?.url}${init.query ? "?" + init.query : ""}`; - - const res = await this.queue.push(fetch, target, { - method: method, - headers: { - "Authorization": `${this._token?.token_type} ${this._token?.access_token}`, - "Content-Type": "application/json", - ...init.headers, - }, - body: init.payload ? JSON.stringify(init.payload) : undefined, - }); + try { + await this.checkToken(); + const target = `${init.url_base ?? this.url_base}${init?.url}${init.query ? "?" + init.query : ""}`; + + const res = await this.queue.push(fetch, target, { + method: method, + headers: { + "Authorization": `${this._token?.token_type} ${this._token?.access_token}`, + "Content-Type": "application/json", + ...init.headers, + }, + body: init.payload ? JSON.stringify(init.payload) : undefined, + }); - await this.checkError(res, method); - return res.body ? (await res.json()) as T : null as T; + await this.checkError(res, method); + return res.body ? (await res.json()) as T : null as T; + } catch (err) { + if (this.options?.on_error) { + this.options.on_error(err); + return null as T; + } else { + throw err; + } + } } get(init: RequestInit): Promise { diff --git a/src/typings/lib.ts b/src/typings/lib.ts index 5205d6c..1925d66 100644 --- a/src/typings/lib.ts +++ b/src/typings/lib.ts @@ -1,9 +1,15 @@ +import { AuthError } from "../errors/auth.ts"; +import { NoContentError } from "../errors/no-content.ts"; +import { HttpError } from "../errors/http.ts"; +import { WebhookError } from "../errors/webhook.ts"; +import { ApiError } from "../errors/api.ts"; import { OAuth } from "./auth.ts"; import { JSONValue } from "./utility.ts"; export type Options = { request_delay?: number; on_token?: (token: OAuth) => void | Promise; + on_error?: (error: Error | AuthError | ApiError | NoContentError | HttpError | WebhookError) => void | Promise; }; export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; diff --git a/tests/errors.test.ts b/tests/errors.test.ts index ddff2d8..1002d00 100644 --- a/tests/errors.test.ts +++ b/tests/errors.test.ts @@ -121,3 +121,18 @@ Deno.test("should return AuthError", async () => { await sleep(200); }); + +Deno.test("should return AuthError to error handler", async () => { + mf.mock("POST@/oauth2/access_token", () => { + return new Response(null, { status: 403 }); + }); + + const amo = new Amo("mydomain.amocrm.ru", { ...auth, ...token, expires_at: 0 }, { + on_token: (new_token) => console.log("New token obtained", new_token), + on_error: (err) => assertInstanceOf(err, AuthError), + }); + + await amo.lead.getLeadById(69); + + await sleep(200); +});