diff --git a/source/add-listener.md b/source/add-listener.md new file mode 100644 index 0000000..735ce14 --- /dev/null +++ b/source/add-listener.md @@ -0,0 +1,34 @@ +# add-listener + +Utility method to add listeners to events and remove them when the signal is aborted. + +Currently this **requires** a `signal`. Without a `signal`, you should just use the native `addListener` method as this has no advantages over it. + +```js +import {addListener} from 'webext-events'; + +addListener(chrome.tabs.onCreated, (tab) => { + console.log('Hurray, a new tab was created') +}, {signal: AbortSignal.timeout(1000)}); +``` + +> [!NOTE] +> Background workers are unloaded and the status of `signal`s created within them is reset. Dealing with this is outside the responsibility of this library. + +## Compatibility + +- Any browser + +## Permissions + +- No special permissions + +## Context + +- Any context + +## Related + +- [abort-utils](https://github.com/fregante/abort-utils) - Utility functions to use and combine `AbortSignal` and `AbortController` with Promises. + +## [Main page ⏎](../readme.md) diff --git a/source/add-listener.test.ts b/source/add-listener.test.ts new file mode 100644 index 0000000..e50d3e6 --- /dev/null +++ b/source/add-listener.test.ts @@ -0,0 +1,30 @@ +import { + describe, it, vi, expect, expectTypeOf, +} from 'vitest'; +import {addListener} from './add-listener.js'; + +describe('addListener', () => { + it('should remove the listener when the signal is aborted', () => { + const event = { + addListener: vi.fn(), + removeListener: vi.fn(), + }; + const listener = vi.fn(); + const controller = new AbortController(); + addListener(event, listener, {signal: controller.signal}); + + expect(event.addListener).toHaveBeenCalledWith(listener); + + controller.abort(); + + expect(event.removeListener).toHaveBeenCalledWith(listener); + }); + + it('should have the correct types', () => { + addListener(chrome.tabs.onMoved, (tabId, tab) => { + expectTypeOf(tabId).toEqualTypeOf(); + expectTypeOf(tab).toEqualTypeOf(); + }, {signal: AbortSignal.timeout(1000)}); + }); +}); + diff --git a/source/add-listener.ts b/source/add-listener.ts new file mode 100644 index 0000000..dee3b8d --- /dev/null +++ b/source/add-listener.ts @@ -0,0 +1,26 @@ +type AnyFunction = (...parameters: any[]) => void; + +type RemovableEvent unknown> = { + removeListener(callback: T): void; + addListener(callback: T): void; +}; + +export function addListener>( + event: Event, + listener: (...parameters: Parameters[0]>) => void, + { + signal, + }: { + signal: AbortSignal; + }, +): void { + if (signal?.aborted) { + return; + } + + event.addListener(listener); + + signal.addEventListener('abort', () => { + event.removeListener(listener); + }, {once: true}); +} diff --git a/source/index.ts b/source/index.ts index 54b682b..9ea7896 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,3 +1,4 @@ export * from './on-context-invalidated.js'; export * from './on-extension-start.js'; export * from './one-event.js'; +export * from './add-listener.js'; diff --git a/source/on-context-invalidated.md b/source/on-context-invalidated.md index 87be57b..5e1c91f 100644 --- a/source/on-context-invalidated.md +++ b/source/on-context-invalidated.md @@ -34,3 +34,5 @@ fetch('/api', {signal: onContextInvalidated.signal}) - any context Some contexts like the background page/worker and standalone tabs will be closed by the browser automatically so the event doesn't apply there. + +## [Main page ⏎](../readme.md) diff --git a/source/on-extension-start.md b/source/on-extension-start.md index f683122..809c802 100644 --- a/source/on-extension-start.md +++ b/source/on-extension-start.md @@ -35,3 +35,5 @@ onExtensionStart.addListener(listener); - background worker - background page - event page (not in Chrome) + +## [Main page ⏎](../readme.md) diff --git a/source/one-event.md b/source/one-event.md index b1acf40..c94d518 100644 --- a/source/one-event.md +++ b/source/one-event.md @@ -57,3 +57,5 @@ if (timeout.aborted) { ## Related - [one-event](https://github.com/fregante/one-event) - The same thing, but for regular browser events. + +## [Main page ⏎](../readme.md)