Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lemon-sides-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sap-cloud-sdk/http-client': minor
---

[Improvement] Add `signal` property to `CustomRequestConfig` and `HttpRequestConfigBase` type definition to document `AbortSignal` support for cancelling HTTP requests.
8 changes: 8 additions & 0 deletions packages/eslint-config/flat-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@ const flatConfig = [
'@typescript-eslint/prefer-for-of': 'error',
'@typescript-eslint/prefer-function-type': 'error',
'@typescript-eslint/unified-signatures': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_'
}
]
'import/named': 'error',
'import/default': 'error',
'import/namespace': 'error',
Expand Down
8 changes: 8 additions & 0 deletions packages/eslint-config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ module.exports = {
'@typescript-eslint/prefer-for-of': 'error',
'@typescript-eslint/prefer-function-type': 'error',
'@typescript-eslint/unified-signatures': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_'
}
],
'import/named': 'error',
'import/default': 'error',
'import/namespace': 'error',
Expand Down
5 changes: 5 additions & 0 deletions packages/http-client/src/http-client-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ export interface HttpRequestConfigBase {
* Encoder for the query parameters key and values. Per default parameters and keys are percent encoded.
*/
parameterEncoder?: ParameterEncoder;
/**
* An `AbortSignal` to cancel the request.
*/
signal?: AbortSignal;
}

/**
Expand Down Expand Up @@ -207,6 +211,7 @@ export type CustomRequestConfig = Pick<
| 'httpAgent'
| 'httpsAgent'
| 'parameterEncoder'
| 'signal'
> &
Record<string, any>;

Expand Down
73 changes: 73 additions & 0 deletions packages/http-client/src/http-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,79 @@ describe('generic http client', () => {
);
});

it('considers abort signal to cancel the request', async () => {
const signal = AbortSignal.timeout(10);

const _scope = nock('https://example.com')
.get('/abort')
.delay(1000)
.reply(200, 'Should not get this');

const requestPromise = executeHttpRequest(
{ url: 'https://example.com' },
{
method: 'get',
url: '/abort',
signal
}
);

await expect(requestPromise).rejects.toMatchObject({
code: 'ERR_CANCELED'
});
// I have confirmed this get's cancelled, but this is not reflected in nock.
// expect(scope.isDone()).toBe(false);
});

it('cancels CSRF token fetch when signal is aborted', async () => {
const _csrfScope = nock('https://example.com')
.head('/api/entity')
.delay(10000)
.reply(200, {}, { 'x-csrf-token': 'test-token' });

const postScope = nock('https://example.com')
.post('/api/entity')
.reply(200, 'Should not get this');

await expect(
executeHttpRequest(
{ url: 'https://example.com' },
{
method: 'post',
url: '/api/entity',
signal: AbortSignal.timeout(50)
}
)
).rejects.toMatchObject({
code: 'ERR_CANCELED'
});
// I have confirmed this get's cancelled, but this is not reflected in nock.
// expect(csrfScope.isDone()).toBe(false);
expect(postScope.isDone()).toBe(false);
});

it('rejects immediately when signal is already aborted', async () => {
const signal = AbortSignal.abort();

const scope = nock('https://example.com')
.get('/should-not-reach')
.reply(200, 'Should never reach this');

await expect(
executeHttpRequest(
{ url: 'https://example.com' },
{
method: 'get',
url: '/should-not-reach',
signal
}
)
).rejects.toMatchObject({
code: 'ERR_CANCELED'
});
expect(scope.isDone()).toBe(false);
});

it('attaches one middleware', async () => {
nock('https://example.com').get(/.*/).reply(200, 'Initial value.');
const myMiddleware = buildMiddleware('Middleware One.');
Expand Down
Loading