diff --git a/eslint.config.js b/eslint.config.js index 2e6d63d..ec00836 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -23,6 +23,10 @@ export default antfu({ name: ['*', 'bytes'], message: 'Request/Blob/Response/... .bytes is not widely supported, use readAsBuffer instead', }, + { + name: ['*', 'throwIfAborted'], + message: 'React Native\'s AbortSignal polyfill has no throwIfAborted, use throwIfAborted from @standard-server/shared instead', + }, ], }, }, { diff --git a/packages/peer/src/client.ts b/packages/peer/src/client.ts index e6a4d12..b273bb1 100644 --- a/packages/peer/src/client.ts +++ b/packages/peer/src/client.ts @@ -2,7 +2,7 @@ import type { StandardBody, StandardLazyResponse, StandardRequest } from '@stand import type { Queue } from '@standard-server/shared' import type { ClientPeerSendMessage, PeerEventStreamMessage, PeerOctetStreamMessage, ServerPeerSendMessage } from './types' import { cancelStandardBody } from '@standard-server/core' -import { AbortError, hasAnyDefinedValue, isAsyncIteratorObject, SequentialIdGenerator } from '@standard-server/shared' +import { AbortError, hasAnyDefinedValue, isAsyncIteratorObject, SequentialIdGenerator, throwIfAborted } from '@standard-server/shared' import { encodeAtomicStandardBody, toStandardBody } from './body' import { EventStreamTransmitter } from './event-stream' import { OctetStreamTransmitter } from './octet-stream' @@ -32,7 +32,7 @@ export class ClientPeer { request(request: StandardRequest): Promise { return new Promise((resolve, reject) => { const signal = request.signal - signal?.throwIfAborted() + throwIfAborted(signal) const id = this.idGenerator.generate() const state: ClientPeerRequestStateInternal = { resolve, reject } @@ -69,7 +69,7 @@ export class ClientPeer { const encodedAtomicBody = await encodeAtomicStandardBody(request.body, request.headers) // signal can be aborted during encode - request.signal?.throwIfAborted() + throwIfAborted(request.signal) // the peer can be closed during encode if (this.requests.get(id) !== state) { diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 268349c..e7d1f23 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -8,6 +8,7 @@ export * from './object' export * from './promise' export * from './proxy' export * from './queue' +export { throwIfAborted } from './signal' export * from './time' export * from './types' export * from './uri' diff --git a/packages/shared/src/signal.test.ts b/packages/shared/src/signal.test.ts index 93feebc..07d17b4 100644 --- a/packages/shared/src/signal.test.ts +++ b/packages/shared/src/signal.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest' -import { anyAbortSignal } from './signal' +import { anyAbortSignal, throwIfAborted } from './signal' /** * Helper to temporarily disable AbortSignal.any to force fallback implementation. @@ -105,3 +105,18 @@ describe('anyAbortSignal', () => { }) }) }) + +describe('throwIfAborted', () => { + it('works on a signal without throwIfAborted (React Native polyfill)', () => { + const controller = new AbortController() + Object.defineProperty(controller.signal, 'throwIfAborted', { value: undefined }) + + expect(() => throwIfAborted(undefined)).not.toThrow() + expect(() => throwIfAborted(controller.signal)).not.toThrow() + + const reason = new Error('aborted') + controller.abort(reason) + + expect(() => throwIfAborted(controller.signal)).toThrow(reason) + }) +}) diff --git a/packages/shared/src/signal.ts b/packages/shared/src/signal.ts index 5f41fc4..b9f59c3 100644 --- a/packages/shared/src/signal.ts +++ b/packages/shared/src/signal.ts @@ -43,3 +43,13 @@ export function anyAbortSignal(...signals: (AbortSignal | undefined)[]): AbortSi return controller.signal } + +/** + * Throws the signal's abort reason if it is aborted, like `signal.throwIfAborted()`. + * React Native's AbortSignal polyfill has no `throwIfAborted`, so use this instead. + */ +export function throwIfAborted(signal: AbortSignal | undefined | null): void { + if (signal?.aborted) { + throw signal.reason + } +} diff --git a/packages/shared/src/time.ts b/packages/shared/src/time.ts index 7c4f197..fbfa313 100644 --- a/packages/shared/src/time.ts +++ b/packages/shared/src/time.ts @@ -1,3 +1,5 @@ +import { throwIfAborted } from './signal' + export interface SleepOptions { signal?: AbortSignal | undefined } @@ -7,10 +9,7 @@ export interface SleepOptions { */ export function sleep(ms: number, { signal }: SleepOptions = {}): Promise { return new Promise((resolve, reject) => { - if (signal?.aborted) { - reject(signal.reason) - return - } + throwIfAborted(signal) let abortListener: (() => void) | null = null diff --git a/tests/client-server.node-http2.ts b/tests/client-server.node-http2.ts index de1117d..e5d00fb 100644 --- a/tests/client-server.node-http2.ts +++ b/tests/client-server.node-http2.ts @@ -5,6 +5,7 @@ import * as http2 from 'node:http2' import { Readable } from 'node:stream' import { toFetchBody, toFetchHeaders, toStandardLazyResponse } from '@standard-server/fetch' import { sendStandardResponse, toStandardLazyRequest } from '@standard-server/node' +import { throwIfAborted } from '@standard-server/shared' import { generateTlsCert } from './tls' export interface NodeHttp2ClientServerTestOptions { @@ -57,7 +58,7 @@ export function createNodeHttp2ClientServerTest(options: NodeHttp2ClientServerTe }) const request: ClientServerTest['request'] = vi.fn(async (standardRequest) => { - standardRequest.signal?.throwIfAborted() + throwIfAborted(standardRequest.signal) const { session, port } = await serverReady diff --git a/tests/client-server.node-https.ts b/tests/client-server.node-https.ts index 724662a..b57ad88 100644 --- a/tests/client-server.node-https.ts +++ b/tests/client-server.node-https.ts @@ -5,6 +5,7 @@ import * as https from 'node:https' import { Readable } from 'node:stream' import { toFetchBody, toFetchHeaders, toStandardLazyResponse } from '@standard-server/fetch' import { sendStandardResponse, toStandardLazyRequest } from '@standard-server/node' +import { throwIfAborted } from '@standard-server/shared' import { generateTlsCert } from './tls' export function createNodeHttpsClientServerTest(): ClientServerTest { @@ -39,7 +40,7 @@ export function createNodeHttpsClientServerTest(): ClientServerTest { }) const request: ClientServerTest['request'] = vi.fn(async (standardRequest) => { - standardRequest.signal?.throwIfAborted() + throwIfAborted(standardRequest.signal) const { agent, port } = await serverReady