Skip to content

Commit

Permalink
feat(sdk): add abort plugin SDK (#1776)
Browse files Browse the repository at this point in the history
## Proposed change

Add plugin to simplify request canceling
  • Loading branch information
kpanot committed May 15, 2024
2 parents 016fa1a + 120f824 commit 62276a6
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/@ama-sdk/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Please refer to the [ama-sdk-schematics](../schematics/README.md) package for ge

## Available plugins

- [abort](https://github.com/AmadeusITGroup/otter/tree/main/packages/%40ama-sdk/core/src/plugins/abort)
- [additional-params](https://github.com/AmadeusITGroup/otter/tree/main/packages/%40ama-sdk/core/src/plugins/additional-params)
- [api-configuration-override](https://github.com/AmadeusITGroup/otter/tree/main/packages/%40ama-sdk/core/src/plugins/api-configuration-override)
- [api-key](https://github.com/AmadeusITGroup/otter/tree/main/packages/%40ama-sdk/core/src/plugins/api-key)
Expand Down
91 changes: 91 additions & 0 deletions packages/@ama-sdk/core/src/plugins/abort/abort.fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { FetchCall, FetchPlugin, FetchPluginContext, RequestOptions } from '../core';

interface AbortCallbackParameters {
/** URL targeted */
url: string;

/** Fetch call options */
options: RequestInit | RequestOptions;

// TODO Now supported for all the modern browsers - should become mandatory in @ama-sdk/core@11.0
/** Abort controller to abort fetch call */
controller?: AbortController;
}

const isPromise = (result: boolean | void | Promise<void> | Promise<boolean>): result is (Promise<void> | Promise<boolean>) => {
if (typeof result !== 'object') {
return false;
}

return true;
};

/**
* Abort callback
* Returns `true` to abort a request (or access directly to the controller to cancel fetch request)
* @example Immediate abort on URL match
* ```typescript
* const abortCondition: AbortCallback = ({url}) => url.endsWith('pet');
*
* const client = new ApiFetchClient(
* {
* basePath: 'https://petstore3.swagger.io/api/v3',
* fetchPlugins: [new AbortFetch(abortCondition)]
* }
* );
* ```
* @example Abort on external event
* ```typescript
* import { firstValueFrom } from 'rxjs';
* import { myObservable } from 'somewhere';
*
* const abortCondition: AbortCallback = ((observable: any) => () => firstValueFrom(observable).then((value) => !!value))(myObservable);
*
* const client = new ApiFetchClient(
* {
* basePath: 'https://petstore3.swagger.io/api/v3',
* fetchPlugins: [new AbortFetch(abortCondition)]
* }
* );
* ```
*
* @example Abort on Timeout
* ```typescript
* const abortCondition: AbortCallback = ({controller}) => { setTimeout(() => controller?.abort(), 3000); };
*
* const client = new ApiFetchClient(
* {
* basePath: 'https://petstore3.swagger.io/api/v3',
* fetchPlugins: [new AbortFetch(abortCondition)]
* }
* );
* ```
*/
export type AbortCallback = (controller?: AbortCallbackParameters) => void | boolean | Promise<void> | Promise<boolean>;

/** Plugin to abort a Fetch request */
export class AbortFetch implements FetchPlugin {

/**
* Abort Fetch plugin
* @param abortCallback Condition that should be passed to start the call
*/
constructor(public abortCallback: AbortCallback) {
}


/** @inheritDoc */
public load(context: FetchPluginContext) {
return {
transform: (fetchCall: FetchCall) => {
const abortCallbackResult = this.abortCallback();
if (isPromise(abortCallbackResult)) {
void abortCallbackResult.then((res) => res && context.controller?.abort());
} else if (abortCallbackResult) {
context.controller?.abort();
}
return fetchCall;
}
};
}
}
44 changes: 44 additions & 0 deletions packages/@ama-sdk/core/src/plugins/abort/abort.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { AbortFetch } from './abort.fetch';

describe('Abort Plugin', () => {

it('should trigger the callback', async () => {
const fn = jest.fn();
const plugin = new AbortFetch(fn);

const runner = plugin.load({} as any);
await runner.transform(Promise.resolve() as Promise<any>);

expect(fn).toHaveBeenCalled();
});

it('should trigger abort signal if true', async () => {
const defaultContext = {
controller: {
abort: jest.fn()
}
};
const fn = jest.fn().mockResolvedValue(true);
const plugin = new AbortFetch(fn);

const runner = plugin.load(defaultContext as any);
await runner.transform(Promise.resolve() as Promise<any>);

expect(defaultContext.controller.abort).toHaveBeenCalled();
});

it('should not trigger abort signal if false', async () => {
const defaultContext = {
controller: {
abort: jest.fn()
}
};
const fn = jest.fn().mockResolvedValue(false);
const plugin = new AbortFetch(fn);

const runner = plugin.load(defaultContext as any);
await runner.transform(Promise.resolve() as Promise<any>);

expect(defaultContext.controller.abort).not.toHaveBeenCalled();
});
});
1 change: 1 addition & 0 deletions packages/@ama-sdk/core/src/plugins/abort/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './abort.fetch';
59 changes: 59 additions & 0 deletions packages/@ama-sdk/core/src/plugins/abort/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
## Abort

Plugin to abort a Fetch call.

### Usage examples

### Immediate abort on URL match

```typescript
import { AbortFetch, type AbortCallback } from '@ama-sdk/core';

const abortCondition: AbortCallback = ({url}) => url.endsWith('pet');

const client = new ApiFetchClient(
{
basePath: 'https://petstore3.swagger.io/api/v3',
fetchPlugins: [new AbortFetch(abortCondition)]
}
);
```

### Abort on external event

```typescript
import { AbortFetch, type AbortCallback } from '@ama-sdk/core';
import { firstValueFrom } from 'rxjs';
import { myObservable } from 'somewhere';

const abortCondition: AbortCallback = ((observable: any) => () => firstValueFrom(observable).then((value) => !!value))(myObservable);

const client = new ApiFetchClient(
{
basePath: 'https://petstore3.swagger.io/api/v3',
fetchPlugins: [new AbortFetch(abortCondition)]
}
);
```

### Abort on Timeout

```typescript
import { AbortFetch, type AbortCallback } from '@ama-sdk/core';

const abortCondition: AbortCallback = ({controller}) => { setTimeout(() => controller?.abort(), 3000); };

const client = new ApiFetchClient(
{
basePath: 'https://petstore3.swagger.io/api/v3',
fetchPlugins: [new AbortFetch(abortCondition)]
}
);
```

> [!WARN]
> We recommend to use the [Timeout plugin](https://github.com/AmadeusITGroup/otter/tree/main/packages/%40ama-sdk/core/src/plugins/timeout) to implement more easily and properly a request timeout.
### Type of plugins

- Fetch plugin: [AbortFetch](./abort.fetch.ts)
2 changes: 1 addition & 1 deletion packages/@ama-sdk/core/src/plugins/core/fetch-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface FetchPluginContext extends PluginContext {
/** Api Client processing the call the the API */
apiClient: ApiClient;

// TODO Now supported for all the modern browsers - should become mandatory in @ama-sdk/core@10.0
// TODO Now supported for all the modern browsers - should become mandatory in @ama-sdk/core@11.0
/** Abort controller to abort fetch call */
controller?: AbortController;
}
Expand Down
5 changes: 3 additions & 2 deletions packages/@ama-sdk/core/src/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
export * from './abort/index';
export * from './additional-params/index';
export * from './api-configuration-override/index';
export * from './api-key/index';
export * from './bot-protection-fingerprint/index';
export * from './client-facts/index';
export * from './concurrent/index';
export * from './core/index';
export * from './client-facts/index';
export * from './custom-info/index';
export * from './exception/index';
export * from './fetch-cache/index';
Expand All @@ -20,6 +21,6 @@ export * from './reviver/index';
export * from './session-id/index';
export * from './si-token/index';
export * from './simple-api-key-authentication/index';
export * from './timeout/index';
export * from './url-rewrite/index';
export * from './wait-for/index';
export * from './timeout/index';

0 comments on commit 62276a6

Please sign in to comment.