Skip to content

Commit

Permalink
feat(cancellation): Prevent external cancellation (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
JoseLion authored Nov 12, 2022
1 parent 98ce269 commit 40959f4
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 23 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
},
"scripts": {
"build": "tsc -p tsconfig.prod.json",
"check": "yarn compile && yarn lint && yarn test",
"check": "yarn compile && yarn lint && yarn test --forbid-only",
"compile": "tsc",
"lint": "tslint -p tsconfig.json -c tslint.json \"**/*.ts\"",
"release": "semantic-release",
Expand All @@ -46,6 +46,7 @@
"semantic-release": "^19.0.5",
"sinon": "^14.0.2",
"ts-node": "^10.9.1",
"tslib": "^2.4.1",
"tslint": "^6.1.3",
"tslint-eslint-rules": "^5.4.0",
"typescript": "^4.8.4"
Expand Down
52 changes: 41 additions & 11 deletions src/lib/RxjsAxios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,11 @@ export class RxjsAxios {
public request<T, R extends AxiosResponse<T> = AxiosResponse<T>, D = unknown>(
config: AxiosRequestConfig<D>,
): Observable<R> {
const reqConfig = this.validateConfig(config);
const { controller, signal } = this.makeCancellable();

return observify(
() => this.axios.request<T, R, D>({ ...config, signal }),
() => this.axios.request<T, R, D>({ ...reqConfig, signal }),
controller,
);
}
Expand All @@ -85,10 +86,11 @@ export class RxjsAxios {
url: string,
config?: AxiosRequestConfig<D>,
): Observable<R> {
const reqConfig = this.validateConfig(config);
const { controller, signal } = this.makeCancellable();

return observify(
() => this.axios.get<T, R, D>(url, { ...config, signal }),
() => this.axios.get<T, R, D>(url, { ...reqConfig, signal }),
controller,
);
}
Expand All @@ -97,10 +99,11 @@ export class RxjsAxios {
url: string,
config?: AxiosRequestConfig<D>,
): Observable<R> {
const reqConfig = this.validateConfig(config);
const { controller, signal } = this.makeCancellable();

return observify(
() => this.axios.delete<T, R, D>(url, { ...config, signal }),
() => this.axios.delete<T, R, D>(url, { ...reqConfig, signal }),
controller,
);
}
Expand All @@ -109,10 +112,11 @@ export class RxjsAxios {
url: string,
config?: AxiosRequestConfig<D>,
): Observable<R> {
const reqConfig = this.validateConfig(config);
const { controller, signal } = this.makeCancellable();

return observify(
() => this.axios.head<T, R, D>(url, { ...config, signal }),
() => this.axios.head<T, R, D>(url, { ...reqConfig, signal }),
controller,
);
}
Expand All @@ -121,10 +125,11 @@ export class RxjsAxios {
url: string,
config?: AxiosRequestConfig<D>,
): Observable<R> {
const reqConfig = this.validateConfig(config);
const { controller, signal } = this.makeCancellable();

return observify(
() => this.axios.options<T, R, D>(url, { ...config, signal }),
() => this.axios.options<T, R, D>(url, { ...reqConfig, signal }),
controller,
);
}
Expand All @@ -134,10 +139,11 @@ export class RxjsAxios {
data?: D,
config?: AxiosRequestConfig<D>,
): Observable<R> {
const reqConfig = this.validateConfig(config);
const { controller, signal } = this.makeCancellable();

return observify(
() => this.axios.post<T, R, D>(url, data, { ...config, signal }),
() => this.axios.post<T, R, D>(url, data, { ...reqConfig, signal }),
controller,
);
}
Expand All @@ -147,10 +153,11 @@ export class RxjsAxios {
data?: D,
config?: AxiosRequestConfig<D>,
): Observable<R> {
const reqConfig = this.validateConfig(config);
const { controller, signal } = this.makeCancellable();

return observify(
() => this.axios.put<T, R, D>(url, data, { ...config, signal }),
() => this.axios.put<T, R, D>(url, data, { ...reqConfig, signal }),
controller,
);
}
Expand All @@ -160,10 +167,11 @@ export class RxjsAxios {
data?: D,
config?: AxiosRequestConfig<D>,
): Observable<R> {
const reqConfig = this.validateConfig(config);
const { controller, signal } = this.makeCancellable();

return observify(
() => this.axios.patch<T, R, D>(url, data, { ...config, signal }),
() => this.axios.patch<T, R, D>(url, data, { ...reqConfig, signal }),
controller,
);
}
Expand All @@ -173,10 +181,11 @@ export class RxjsAxios {
data?: D,
config?: AxiosRequestConfig<D>,
): Observable<R> {
const reqConfig = this.validateConfig(config);
const { controller, signal } = this.makeCancellable();

return observify(
() => this.axios.postForm<T, R, D>(url, data, { ...config, signal }),
() => this.axios.postForm<T, R, D>(url, data, { ...reqConfig, signal }),
controller,
);
}
Expand All @@ -186,10 +195,11 @@ export class RxjsAxios {
data?: D,
config?: AxiosRequestConfig<D>,
): Observable<R> {
const reqConfig = this.validateConfig(config);
const { controller, signal } = this.makeCancellable();

return observify(
() => this.axios.putForm<T, R, D>(url, data, { ...config, signal }),
() => this.axios.putForm<T, R, D>(url, data, { ...reqConfig, signal }),
controller,
);
}
Expand All @@ -199,14 +209,34 @@ export class RxjsAxios {
data?: D,
config?: AxiosRequestConfig<D>,
): Observable<R> {
const reqConfig = this.validateConfig(config);
const { controller, signal } = this.makeCancellable();

return observify(
() => this.axios.patchForm<T, R, D>(url, data, { ...config, signal }),
() => this.axios.patchForm<T, R, D>(url, data, { ...reqConfig, signal }),
controller,
);
}

private validateConfig<D>(config?: AxiosRequestConfig<D>): AxiosRequestConfig<D> | undefined {
if (config !== undefined) {
const insteadMsg = "Instead, unsubscribe from the observable to cancel the request.";
const { cancelToken, signal, ...rest } = config;

if (cancelToken !== undefined) {
console.warn(`Use of "cancelToken" is deprecated by Axios and has no effect on rxjs-axios. ${insteadMsg}`);
}

if (signal !== undefined) {
console.warn(`Use of "signal" has no effect on rxjs-axios. ${insteadMsg}`);
}

return rest;
}

return config;
}

private makeCancellable(): Abortable {
const controller = new AbortController();
const signal = controller.signal;
Expand Down
18 changes: 9 additions & 9 deletions test/lib/RxjsAxios.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, TypeFactories } from "@stackbuilders/assertive-ts";
import otherAxios from "axios";
import originalAxios from "axios";
import FormData from "form-data";
import { Observable } from "rxjs";
import Sinon from "sinon";
Expand All @@ -10,11 +10,11 @@ import { RxjsAxios } from "../../src/lib/RxjsAxios";
describe("[Unit] RxjsAxios.test.ts", () => {
describe(".of", () => {
it("creates an RxjsAxios instance from another Axios instance", () => {
const rxjsAxios = RxjsAxios.of(otherAxios);
const rxjsAxios = RxjsAxios.of(originalAxios);

expect(Object(rxjsAxios))
.asType(TypeFactories.object())
.toContainEntry(["axios", otherAxios]);
.toContainEntry(["axios", originalAxios]);
});
});

Expand All @@ -28,7 +28,7 @@ describe("[Unit] RxjsAxios.test.ts", () => {

describe(".isAxiosError", () => {
it("calls the same Axios method", () => {
const spy = Sinon.spy(otherAxios, "isAxiosError");
const spy = Sinon.spy(originalAxios, "isAxiosError");

RxjsAxios.isAxiosError("foo");

Expand All @@ -38,7 +38,7 @@ describe("[Unit] RxjsAxios.test.ts", () => {

describe(".isCancel", () => {
it("calls the same Axios method", () => {
const spy = Sinon.spy(otherAxios, "isCancel");
const spy = Sinon.spy(originalAxios, "isCancel");

RxjsAxios.isCancel("foo");

Expand All @@ -48,7 +48,7 @@ describe("[Unit] RxjsAxios.test.ts", () => {

describe(".toFormData", () => {
it("calls the same Axios method", () => {
const spy = Sinon.spy(otherAxios, "toFormData");
const spy = Sinon.spy(originalAxios, "toFormData");

RxjsAxios.toFormData({ });

Expand All @@ -58,7 +58,7 @@ describe("[Unit] RxjsAxios.test.ts", () => {

describe(".formToJSON", () => {
it("calls the same Axios method", () => {
const spy = Sinon.spy(otherAxios, "formToJSON");
const spy = Sinon.spy(originalAxios, "formToJSON");
const formData = new FormData();

RxjsAxios.formToJSON(formData);
Expand All @@ -77,9 +77,9 @@ describe("[Unit] RxjsAxios.test.ts", () => {

describe("#interceptors", () => {
it("returns the instance interceptors", () => {
const rxjsAxios = RxjsAxios.of(otherAxios);
const rxjsAxios = RxjsAxios.of(originalAxios);

expect(rxjsAxios.interceptors).toBeSame(otherAxios.interceptors);
expect(rxjsAxios.interceptors).toBeSame(originalAxios.interceptors);
});
});

Expand Down
2 changes: 1 addition & 1 deletion tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"linebreak-style": [true, "LF"],
"max-line-length": [true, 120],
"member-access": [true, "check-constructor"],
"no-console": true,
"no-console": [true, "log"],
"no-duplicate-imports": true,
"no-namespace": false,
"no-trailing-whitespace": true,
Expand Down
3 changes: 2 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4742,6 +4742,7 @@ __metadata:
semantic-release: ^19.0.5
sinon: ^14.0.2
ts-node: ^10.9.1
tslib: ^2.4.1
tslint: ^6.1.3
tslint-eslint-rules: ^5.4.0
typescript: ^4.8.4
Expand Down Expand Up @@ -5390,7 +5391,7 @@ __metadata:
languageName: node
linkType: hard

"tslib@npm:^2.1.0":
"tslib@npm:^2.1.0, tslib@npm:^2.4.1":
version: 2.4.1
resolution: "tslib@npm:2.4.1"
checksum: 19480d6e0313292bd6505d4efe096a6b31c70e21cf08b5febf4da62e95c265c8f571f7b36fcc3d1a17e068032f59c269fab3459d6cd3ed6949eafecf64315fca
Expand Down

0 comments on commit 40959f4

Please sign in to comment.