Skip to content
Merged
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/fluffy-puffin-flock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nodesecure/scanner": patch
---

fix(extractor): improve error handling for event listener
54 changes: 40 additions & 14 deletions workspaces/scanner/src/extractors/payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,12 @@ export class Payload<T extends ProbeExtractor<any>[]> 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 });
}
}
}
Expand All @@ -106,7 +106,38 @@ export class Payload<T extends ProbeExtractor<any>[]> extends EventTarget {
) as unknown as MergedExtractProbeResult<T>;
}

emit<T extends ProbeExtractorLevel>(
on<T extends ProbeExtractorLevel>(
e: T,
listener: ExtractorListener<T>
): this {
const wrappedListener = (event: Event) => {
const customEvent = event as CustomEvent<ExtractorCallbackParams<T>>;
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<Error>;
try {
listener(customErrorEvent.detail);
}
catch (error) {
console.warn("Something went wrong in error listener", { cause: error });
}
}
this.addEventListener("error", wrappedListener);
}

#emit<T extends ProbeExtractorLevel>(
event: T,
...extractionDetails: unknown[]
) {
Expand All @@ -116,17 +147,12 @@ export class Payload<T extends ProbeExtractor<any>[]> extends EventTarget {
this.dispatchEvent(customEvent);
}

on<T extends ProbeExtractorLevel>(
e: T,
listener: ExtractorListener<T>
): this {
function wrappedListener(event: Event) {
const customEvent = event as CustomEvent<ExtractorCallbackParams<T>>;
listener(...customEvent.detail);
}
this.addEventListener(e, wrappedListener);

return this;
#emitError(e: Error
) {
const customErrorEvent = new CustomEvent("error", {
detail: e
});
this.dispatchEvent(customErrorEvent);
}
}

Expand Down
25 changes: 25 additions & 0 deletions workspaces/scanner/test/extractors/payload.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down