Plugin System
StreetJS ships a formal, dependency-free plugin system built on node:crypto. It
covers the full in-process lifecycle of a plugin — registration, signature and
integrity verification, capability/permission metadata, dependency and version
resolution, lifecycle orchestration, and discovery. The network install flow
(fetch + extract from a registry) lives separately in PluginInstaller.
All symbols are exported from streetjs.
Concepts
A plugin is a subclass of PluginModule with name, version, and optional
onInstall / onLoad / onUnload hooks. A PluginManifest describes its
capabilities, requested permissions, and dependencies:
1
2
3
4
5
6
7
8
9
interface PluginManifest {
name: string;
version: string;
capabilities?: string[]; // discovery tags, e.g. ['payments']
permissions?: PluginPermission[]; // 'middleware'|'events'|'net'|'fs'|'db'|'secrets'
dependencies?: Record<string, string>; // name → semver range
checksum?: string; // SHA-256 of canonical body
signature?: string; // base64 Ed25519 over checksum
}
Signing & verifying manifests
Manifests are signed with an Ed25519 key. Integrity is a SHA-256 over a deterministic, key-sorted body; authenticity is an Ed25519 signature over that checksum. Verification is offline and constant against tampering.
1
2
3
4
5
6
7
8
import { generateKeyPairSync } from 'node:crypto';
import { signManifest, verifyManifest } from 'streetjs';
const { publicKey, privateKey } = generateKeyPairSync('ed25519');
const signed = signManifest({ name: 'pay', version: '1.0.0', capabilities: ['payments'] }, privateKey);
verifyManifest(signed, publicKey); // true
verifyManifest({ ...signed, capabilities: ['payments', 'evil'] }, publicKey); // false (tampered)
Hosting plugins
PluginHost grants a set of permissions and (optionally) a public key. When a
public key is configured, registration requires a valid signature.
1
2
3
4
5
6
7
8
9
10
11
import { PluginHost, PluginModule } from 'streetjs';
class StripePlugin extends PluginModule {
readonly name = 'stripe';
readonly version = '1.0.0';
async onLoad(app) { app.use(async (ctx, next) => { /* ... */ await next(); }); }
}
const host = new PluginHost({ grantedPermissions: ['middleware', 'net'], publicKey });
host.register(new StripePlugin(), signedManifest);
await host.enable('stripe');
Permissions
A plugin can only load if every permission in its manifest is granted by the
host. The sandbox passed to onLoad is gated too: calling app.use(...)
without the middleware permission, or app.on(...) without events, throws
PluginPermissionError. Pass grantedPermissions: '*' to grant all.
Dependencies & versions
enable(name) resolves dependencies first, in dependency order, validating that
each is registered and its version satisfies the declared range. Supported
ranges: exact (1.2.3), caret (^1.2.3), tilde (~1.2.3), comparators
(>=, >, <=, <), and any (*). Missing dependencies, version conflicts,
and dependency cycles raise PluginDependencyError.
1
2
3
host.register(new Base(), { name: 'base', version: '1.2.0' });
host.register(new Feature(), { name: 'feature', version: '1.0.0', dependencies: { base: '^1.0.0' } });
await host.enable('feature'); // enables base first, then feature
Lifecycle
| Method | Behaviour |
|---|---|
register(plugin, manifest) |
Validates identity + signature; state → registered. |
enable(name) |
Checks permissions/deps; runs onInstall once, then onLoad; state → enabled. |
disable(name) |
Runs onUnload; refuses if an enabled plugin still depends on it; state → disabled. |
remove(name) |
Removes from the host; requires the plugin be disabled first. |
enable is idempotent (no duplicate onInstall/onLoad).
Discovery
1
2
3
4
5
host.list(); // all registered names
host.has('stripe'); // boolean
host.state('stripe'); // 'registered' | 'enabled' | 'disabled'
host.findByCapability('payments');// names exposing a capability
host.middlewaresOf('stripe'); // middlewares an enabled plugin contributed
Verification
packages/core/src/tests/plugin-host.test.ts covers semver matching, real
Ed25519 sign/verify (including tamper and wrong-key rejection), signature
enforcement on registration, permission gating (including the sandbox),
dependency ordering + version conflicts + cycle detection, idempotent enable,
discovery, and disable/remove safety.
1
2
3
cd packages/core
npx tsc
node --test dist/src/tests/plugin-host.test.js