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/seven-feet-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nodesecure/scanner": minor
---

Implement EventEmitter with two events on Payload class (Extractors)
30 changes: 30 additions & 0 deletions workspaces/scanner/docs/extractors.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export interface ManifestProbeExtractor<Defs> extends ProbeExtractor<Defs> {

> [!NOTE]
> generic `T` is defined as extending from `ProbeExtractor<any>[]`
The `Payload` class extends Node.js `EventEmitter`, allowing you to listen for extraction events and implement custom behavior during the extraction process.

### constructor(data: Scanner.Payload | Scanner.Payload[ "dependencies" ], probes: [ ...T ])

Expand Down Expand Up @@ -104,3 +105,32 @@ Runs the probes and deeply merges their results into a single record, for exampl
"probe2": "xxx"
}
```

### Events

Since `Payload` extends `EventEmitter`, you can listen for events that are emitted during the extraction process, the parameters signature of each listener is the same as the `next()` method of its corresponding probe.


### packument

Emitted for each dependency when processing at the packument level, the event signature is:

```ts
extractor.on('packument', (name: string, dependency: Scanner.Dependency) => {
// Handle packument-level processing
});
```

#### manifest

Emitted for each dependency version when processing at the manifest level, the event signature is:

```ts
extractor.on('manifest', (
spec: string,
depVersion: Scanner.DependencyVersion,
parent: { name: string, dependency: Scanner.Dependency }
) => {
// Handle manifest-level processing
});
```
8 changes: 7 additions & 1 deletion workspaces/scanner/src/extractors/payload.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Import Node.js Dependencies
import { EventEmitter } from "node:events";

// Import Third-party Dependencies
import type { Simplify } from "type-fest";
// @ts-ignore
Expand Down Expand Up @@ -48,7 +51,7 @@ export interface ManifestProbeExtractor<Defs> extends ProbeExtractor<Defs> {
): void;
}

export class Payload<T extends ProbeExtractor<any>[]> {
export class Payload<T extends ProbeExtractor<any>[]> extends EventEmitter {
private dependencies: Scanner.Payload["dependencies"];
private probes: Record<ProbeExtractorLevel, T>;
private cachedResult: ExtractProbeResult<T>;
Expand All @@ -57,6 +60,7 @@ export class Payload<T extends ProbeExtractor<any>[]> {
data: Scanner.Payload | Scanner.Payload["dependencies"],
probes: [...T]
) {
super();
this.dependencies = isNodesecurePayload(data) ?
data.dependencies :
data;
Expand All @@ -75,9 +79,11 @@ export class Payload<T extends ProbeExtractor<any>[]> {

for (const [name, dependency] of Object.entries(this.dependencies)) {
this.probes.packument.forEach((probe) => probe.next(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 });
}
}
}
Expand Down
42 changes: 42 additions & 0 deletions workspaces/scanner/test/extractors/payload.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,45 @@ describe("Extractors.Probes", () => {
);
});
});

describe("Events", () => {
it("should emits packument and manifest events", () => {
const vulnerabilitiesExtractor = new Extractors.Probes.VulnerabilitiesExtractor();
const licensesExtractor = new Extractors.Probes.LicensesExtractor();
type ManifestEvent = Parameters<typeof licensesExtractor.next>;
type PackumentEvent = Parameters<typeof vulnerabilitiesExtractor.next>;

const extractor = new Extractors.Payload(
expressNodesecurePayload,
[
licensesExtractor,
vulnerabilitiesExtractor
]
);

const manifestEvents: ManifestEvent[] = [];
const packumentEvents: PackumentEvent[] = [];

extractor.on("manifest", (...event: ManifestEvent) => {
manifestEvents.push(event);
});

extractor.on("packument", (...event: PackumentEvent) => {
packumentEvents.push(event);
});

const dependencies = Object.entries(expressNodesecurePayload.dependencies);

const expectedPackumentEvents = dependencies;
const expectedManifestEvents = dependencies.flatMap(([name, dependency]) => Object
.entries(dependency.versions)
.map(
([spec, depVersion]) => [spec, depVersion, { name, dependency }]
)
);

extractor.extract();
assert.deepEqual(packumentEvents, expectedPackumentEvents);
assert.deepEqual(manifestEvents, expectedManifestEvents);
});
});