diff --git a/.changeset/fluffy-puffin-flock.md b/.changeset/fluffy-puffin-flock.md new file mode 100644 index 00000000..5fca6c9e --- /dev/null +++ b/.changeset/fluffy-puffin-flock.md @@ -0,0 +1,5 @@ +--- +"@nodesecure/scanner": patch +--- + +fix(extractor): improve error handling for event listener \ No newline at end of file diff --git a/workspaces/scanner/src/extractors/payload.ts b/workspaces/scanner/src/extractors/payload.ts index bdce25f1..5178684e 100644 --- a/workspaces/scanner/src/extractors/payload.ts +++ b/workspaces/scanner/src/extractors/payload.ts @@ -82,12 +82,12 @@ export class Payload[]> extends EventTarget { for (const [name, dependency] of Object.entries(this.dependencies)) { this.probes.packument.forEach((probe) => probe.next(name, dependency)); - this.emit("packument", name, dependency); + this.#emit("packument", name, dependency); if (this.probes.manifest.length > 0) { for (const [spec, depVersion] of Object.entries(dependency.versions)) { this.probes.manifest.forEach((probe) => probe.next(spec, depVersion, { name, dependency })); - this.emit("manifest", spec, depVersion, { name, dependency }); + this.#emit("manifest", spec, depVersion, { name, dependency }); } } } @@ -106,7 +106,38 @@ export class Payload[]> extends EventTarget { ) as unknown as MergedExtractProbeResult; } - emit( + on( + e: T, + listener: ExtractorListener + ): this { + const wrappedListener = (event: Event) => { + const customEvent = event as CustomEvent>; + try { + listener(...customEvent.detail); + } + catch (error) { + this.#emitError(new Error(`An error occured during ${e} event`, { cause: error })); + } + }; + this.addEventListener(e, wrappedListener); + + return this; + } + + onError(listener: (e: Error) => void) { + function wrappedListener(event: Event) { + const customErrorEvent = event as CustomEvent; + try { + listener(customErrorEvent.detail); + } + catch (error) { + console.warn("Something went wrong in error listener", { cause: error }); + } + } + this.addEventListener("error", wrappedListener); + } + + #emit( event: T, ...extractionDetails: unknown[] ) { @@ -116,17 +147,12 @@ export class Payload[]> extends EventTarget { this.dispatchEvent(customEvent); } - on( - e: T, - listener: ExtractorListener - ): this { - function wrappedListener(event: Event) { - const customEvent = event as CustomEvent>; - listener(...customEvent.detail); - } - this.addEventListener(e, wrappedListener); - - return this; + #emitError(e: Error + ) { + const customErrorEvent = new CustomEvent("error", { + detail: e + }); + this.dispatchEvent(customErrorEvent); } } diff --git a/workspaces/scanner/test/extractors/payload.spec.ts b/workspaces/scanner/test/extractors/payload.spec.ts index c94f3663..992f2647 100644 --- a/workspaces/scanner/test/extractors/payload.spec.ts +++ b/workspaces/scanner/test/extractors/payload.spec.ts @@ -281,6 +281,31 @@ describe("Extractors.Payload events", () => { assert.deepEqual(packumentEvents, expectedPackumentEvents); assert.deepEqual(manifestEvents, expectedManifestEvents); }); + + it("should emit error when extraction listener goes wrong", () => { + const extractor = new Extractors.Payload( + expressNodesecurePayload, + [ + new Extractors.Probes.Licenses() + ] + ); + + const error = new Error("Listener error"); + extractor.on("packument", () => { + throw error; + }); + + const packumentListenerErrors: Error[] = []; + + extractor.onError((error) => { + packumentListenerErrors.push(error.cause as Error); + }); + + extractor.extract(); + + const expectedPackumentListenerErrors = packumentListenerErrors.map(() => error); + assert.deepEqual(packumentListenerErrors, expectedPackumentListenerErrors); + }); }); describe("Extractors.Callbacks", () => {