@camstack/server 0.2.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{src/agent-status-page.ts → dist/agent-status-page.js} +30 -45
- package/dist/api/addon-upload.js +441 -0
- package/dist/api/addons-custom.router.js +91 -0
- package/dist/api/auth-whoami.js +55 -0
- package/dist/api/bridge-addons.router.js +109 -0
- package/dist/api/capabilities.router.js +229 -0
- package/dist/api/core/addon-settings.router.js +117 -0
- package/dist/api/core/agents.router.js +73 -0
- package/dist/api/core/auth.router.js +286 -0
- package/dist/api/core/bulk-update-coordinator.js +229 -0
- package/dist/api/core/cap-providers.js +1124 -0
- package/dist/api/core/capabilities.router.js +138 -0
- package/dist/api/core/collection-preference.js +17 -0
- package/dist/api/core/event-bus-proxy.router.js +45 -0
- package/dist/api/core/hwaccel.router.js +91 -0
- package/dist/api/core/live-events.router.js +61 -0
- package/dist/api/core/logs.router.js +172 -0
- package/dist/api/core/notifications.router.js +67 -0
- package/dist/api/core/repl.router.js +35 -0
- package/dist/api/core/settings-backend.router.js +121 -0
- package/dist/api/core/stream-probe.router.js +58 -0
- package/dist/api/core/system-events.router.js +100 -0
- package/dist/api/health/health.routes.js +68 -0
- package/{src/api/oauth2/consent-page.ts → dist/api/oauth2/consent-page.js} +11 -20
- package/dist/api/oauth2/oauth2-routes.js +219 -0
- package/dist/api/trpc/cap-mount-helpers.js +194 -0
- package/dist/api/trpc/cap-route-error-formatter.js +133 -0
- package/dist/api/trpc/client-ip.js +147 -0
- package/dist/api/trpc/core-cap-bridge.js +115 -0
- package/dist/api/trpc/generated-cap-mounts.js +388 -0
- package/dist/api/trpc/generated-cap-routers.js +7635 -0
- package/dist/api/trpc/scope-access.js +93 -0
- package/dist/api/trpc/trpc.context.js +184 -0
- package/dist/api/trpc/trpc.middleware.js +139 -0
- package/dist/api/trpc/trpc.router.js +188 -0
- package/dist/auth/session-cookie.js +47 -0
- package/dist/boot/boot-config.js +241 -0
- package/dist/boot/integration-id-backfill.js +76 -0
- package/dist/boot/post-boot.service.js +85 -0
- package/dist/core/addon/addon-call-gateway.js +99 -0
- package/dist/core/addon/addon-package.service.js +1560 -0
- package/dist/core/addon/addon-registry.service.js +2739 -0
- package/{src/core/addon/addon-row-manifest.ts → dist/core/addon/addon-row-manifest.js} +5 -5
- package/dist/core/addon/addon-search.service.js +62 -0
- package/dist/core/addon/addon-settings-provider.js +102 -0
- package/dist/core/addon/addon.tokens.js +5 -0
- package/dist/core/addon-bridge/addon-bridge.service.js +145 -0
- package/dist/core/addon-pages/addon-pages.service.js +107 -0
- package/dist/core/addon-widgets/addon-widgets.service.js +120 -0
- package/dist/core/agent/agent-registry.service.js +477 -0
- package/dist/core/auth/auth.service.js +10 -0
- package/dist/core/capability/capability.service.js +58 -0
- package/dist/core/config/config.schema.js +7 -0
- package/dist/core/config/config.service.js +10 -0
- package/dist/core/events/event-bus.service.js +83 -0
- package/dist/core/feature/feature.service.js +10 -0
- package/dist/core/lifecycle/lifecycle-state-machine.js +6 -0
- package/dist/core/logging/log-ring-buffer.js +6 -0
- package/dist/core/logging/logging.service.js +130 -0
- package/dist/core/logging/scoped-logger.js +6 -0
- package/dist/core/moleculer/cap-call-fn.js +50 -0
- package/dist/core/moleculer/cap-route-authority.js +122 -0
- package/dist/core/moleculer/moleculer.service.js +898 -0
- package/dist/core/network/network-quality.service.js +7 -0
- package/dist/core/notification/notification-wrapper.service.js +33 -0
- package/dist/core/notification/toast-wrapper.service.js +25 -0
- package/dist/core/provider/provider.tokens.js +4 -0
- package/dist/core/repl/repl-engine.service.js +140 -0
- package/dist/core/storage/fs-storage-backend.js +6 -0
- package/dist/core/storage/storage-location-manager.js +6 -0
- package/dist/core/storage/storage.service.js +7 -0
- package/dist/core/streaming/stream-probe.service.js +209 -0
- package/dist/core/topology/topology-emitter.service.js +106 -0
- package/dist/launcher.js +325 -0
- package/dist/main.js +1098 -0
- package/dist/manual-boot.js +227 -0
- package/package.json +5 -1
- package/src/__tests__/addon-install-e2e.test.ts +0 -74
- package/src/__tests__/addon-pages-e2e.test.ts +0 -200
- package/src/__tests__/addon-route-session.test.ts +0 -17
- package/src/__tests__/addon-settings-router.spec.ts +0 -67
- package/src/__tests__/addon-upload.spec.ts +0 -475
- package/src/__tests__/agent-registry.spec.ts +0 -179
- package/src/__tests__/agent-status-page.spec.ts +0 -82
- package/src/__tests__/auth-session-cookie.test.ts +0 -48
- package/src/__tests__/bulk-update-coordinator.spec.ts +0 -303
- package/src/__tests__/cap-ownership-authority.spec.ts +0 -431
- package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +0 -206
- package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +0 -37
- package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +0 -110
- package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +0 -292
- package/src/__tests__/cap-providers-bulk-update.spec.ts +0 -408
- package/src/__tests__/cap-route-adapter.spec.ts +0 -302
- package/src/__tests__/cap-routers/_meta.spec.ts +0 -199
- package/src/__tests__/cap-routers/addon-settings.router.spec.ts +0 -115
- package/src/__tests__/cap-routers/broker-routing.router.spec.ts +0 -177
- package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +0 -125
- package/src/__tests__/cap-routers/capabilities-node.spec.ts +0 -68
- package/src/__tests__/cap-routers/device-link-overlay.spec.ts +0 -137
- package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +0 -194
- package/src/__tests__/cap-routers/harness.ts +0 -163
- package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +0 -133
- package/src/__tests__/cap-routers/null-provider-guard.spec.ts +0 -64
- package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +0 -159
- package/src/__tests__/cap-routers/settings-store.router.spec.ts +0 -291
- package/src/__tests__/capability-e2e.test.ts +0 -384
- package/src/__tests__/cli-e2e.test.ts +0 -150
- package/src/__tests__/core-cap-bridge.spec.ts +0 -91
- package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +0 -40
- package/src/__tests__/device-settings-contribution-dispatch.spec.ts +0 -280
- package/src/__tests__/embedded-deps-e2e.test.ts +0 -125
- package/src/__tests__/event-bus-proxy-router.spec.ts +0 -75
- package/src/__tests__/fixtures/mock-analysis-addon-a.ts +0 -37
- package/src/__tests__/fixtures/mock-analysis-addon-b.ts +0 -37
- package/src/__tests__/fixtures/mock-log-addon.ts +0 -37
- package/src/__tests__/fixtures/mock-storage-addon.ts +0 -40
- package/src/__tests__/framework-allowlist.spec.ts +0 -96
- package/src/__tests__/framework-installer-defer-restart.spec.ts +0 -165
- package/src/__tests__/https-e2e.test.ts +0 -124
- package/src/__tests__/lifecycle-e2e.test.ts +0 -189
- package/src/__tests__/live-events-subscription.spec.ts +0 -149
- package/src/__tests__/moleculer/uds-readiness.spec.ts +0 -150
- package/src/__tests__/moleculer/uds-topology.spec.ts +0 -418
- package/src/__tests__/moleculer/uds-unowned-call.spec.ts +0 -383
- package/src/__tests__/moleculer-register-node-idempotency.spec.ts +0 -273
- package/src/__tests__/native-cap-route.spec.ts +0 -427
- package/src/__tests__/oauth2-account-linking.spec.ts +0 -867
- package/src/__tests__/post-boot-restart.spec.ts +0 -161
- package/src/__tests__/singleton-contention.test.ts +0 -499
- package/src/__tests__/streaming-diagnostic.test.ts +0 -615
- package/src/__tests__/streaming-scale.test.ts +0 -314
- package/src/__tests__/uds-addon-call-wiring.spec.ts +0 -242
- package/src/__tests__/uds-log-ingest.spec.ts +0 -183
- package/src/api/__tests__/addons-custom.spec.ts +0 -148
- package/src/api/__tests__/capabilities.router.test.ts +0 -56
- package/src/api/addon-upload.ts +0 -529
- package/src/api/addons-custom.router.ts +0 -101
- package/src/api/auth-whoami.ts +0 -101
- package/src/api/bridge-addons.router.ts +0 -122
- package/src/api/capabilities.router.ts +0 -265
- package/src/api/core/__tests__/auth-router-totp.spec.ts +0 -297
- package/src/api/core/__tests__/integration-markers.spec.ts +0 -10
- package/src/api/core/addon-settings.router.ts +0 -127
- package/src/api/core/agents.router.ts +0 -86
- package/src/api/core/auth.router.ts +0 -322
- package/src/api/core/bulk-update-coordinator.ts +0 -305
- package/src/api/core/cap-providers.ts +0 -1339
- package/src/api/core/capabilities.router.ts +0 -149
- package/src/api/core/collection-preference.ts +0 -40
- package/src/api/core/event-bus-proxy.router.ts +0 -45
- package/src/api/core/hwaccel.router.ts +0 -108
- package/src/api/core/live-events.router.ts +0 -67
- package/src/api/core/logs.router.ts +0 -195
- package/src/api/core/notifications.router.ts +0 -66
- package/src/api/core/repl.router.ts +0 -39
- package/src/api/core/settings-backend.router.ts +0 -140
- package/src/api/core/stream-probe.router.ts +0 -57
- package/src/api/core/system-events.router.ts +0 -125
- package/src/api/health/health.routes.ts +0 -117
- package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +0 -62
- package/src/api/oauth2/oauth2-routes.ts +0 -281
- package/src/api/trpc/__tests__/client-ip.spec.ts +0 -146
- package/src/api/trpc/__tests__/scope-access-device.spec.ts +0 -268
- package/src/api/trpc/__tests__/scope-access.spec.ts +0 -102
- package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +0 -136
- package/src/api/trpc/cap-mount-helpers.ts +0 -245
- package/src/api/trpc/cap-route-error-formatter.ts +0 -171
- package/src/api/trpc/client-ip.ts +0 -147
- package/src/api/trpc/core-cap-bridge.ts +0 -154
- package/src/api/trpc/generated-cap-mounts.ts +0 -1240
- package/src/api/trpc/generated-cap-routers.ts +0 -11523
- package/src/api/trpc/scope-access.ts +0 -110
- package/src/api/trpc/trpc.context.ts +0 -258
- package/src/api/trpc/trpc.middleware.ts +0 -146
- package/src/api/trpc/trpc.router.ts +0 -389
- package/src/auth/session-cookie.ts +0 -54
- package/src/boot/__tests__/integration-id-backfill.spec.ts +0 -131
- package/src/boot/boot-config.ts +0 -259
- package/src/boot/integration-id-backfill.ts +0 -109
- package/src/boot/post-boot.service.ts +0 -105
- package/src/core/addon/__tests__/addon-registry-capability.test.ts +0 -62
- package/src/core/addon/__tests__/addon-row-manifest.spec.ts +0 -62
- package/src/core/addon/addon-call-gateway.ts +0 -171
- package/src/core/addon/addon-package.service.ts +0 -1787
- package/src/core/addon/addon-registry.service.ts +0 -3130
- package/src/core/addon/addon-search.service.ts +0 -91
- package/src/core/addon/addon-settings-provider.ts +0 -220
- package/src/core/addon/addon.tokens.ts +0 -2
- package/src/core/addon-bridge/addon-bridge.service.ts +0 -130
- package/src/core/addon-pages/addon-pages.service.spec.ts +0 -117
- package/src/core/addon-pages/addon-pages.service.ts +0 -82
- package/src/core/addon-widgets/addon-widgets.service.ts +0 -95
- package/src/core/agent/agent-registry.service.ts +0 -529
- package/src/core/auth/auth.service.spec.ts +0 -86
- package/src/core/auth/auth.service.ts +0 -8
- package/src/core/capability/capability.service.ts +0 -66
- package/src/core/config/config.schema.ts +0 -3
- package/src/core/config/config.service.spec.ts +0 -175
- package/src/core/config/config.service.ts +0 -7
- package/src/core/events/event-bus.service.spec.ts +0 -235
- package/src/core/events/event-bus.service.ts +0 -89
- package/src/core/feature/feature.service.spec.ts +0 -99
- package/src/core/feature/feature.service.ts +0 -8
- package/src/core/lifecycle/lifecycle-state-machine.spec.ts +0 -166
- package/src/core/lifecycle/lifecycle-state-machine.ts +0 -3
- package/src/core/logging/log-ring-buffer.ts +0 -3
- package/src/core/logging/logging.service.spec.ts +0 -287
- package/src/core/logging/logging.service.ts +0 -143
- package/src/core/logging/scoped-logger.ts +0 -3
- package/src/core/moleculer/cap-call-fn.spec.ts +0 -173
- package/src/core/moleculer/cap-call-fn.ts +0 -107
- package/src/core/moleculer/cap-route-authority.ts +0 -194
- package/src/core/moleculer/moleculer.service.ts +0 -1072
- package/src/core/network/network-quality.service.spec.ts +0 -53
- package/src/core/network/network-quality.service.ts +0 -5
- package/src/core/notification/notification-wrapper.service.ts +0 -34
- package/src/core/notification/toast-wrapper.service.ts +0 -27
- package/src/core/provider/provider.tokens.ts +0 -1
- package/src/core/repl/repl-engine.service.spec.ts +0 -444
- package/src/core/repl/repl-engine.service.ts +0 -155
- package/src/core/storage/fs-storage-backend.spec.ts +0 -70
- package/src/core/storage/fs-storage-backend.ts +0 -3
- package/src/core/storage/storage-location-manager.spec.ts +0 -130
- package/src/core/storage/storage-location-manager.ts +0 -3
- package/src/core/storage/storage.service.spec.ts +0 -73
- package/src/core/storage/storage.service.ts +0 -3
- package/src/core/streaming/stream-probe.service.ts +0 -221
- package/src/core/topology/topology-emitter.service.ts +0 -105
- package/src/launcher.ts +0 -314
- package/src/main.ts +0 -1245
- package/src/manual-boot.ts +0 -301
- package/tsconfig.build.json +0 -8
- package/tsconfig.json +0 -33
- package/vitest.config.ts +0 -26
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.overlayDeclaration = overlayDeclaration;
|
|
1
4
|
/**
|
|
2
5
|
* Overlay the fresh on-disk `declaration` onto a live addon instance's
|
|
3
6
|
* `manifest` when building the rows returned by `listAddons()`.
|
|
@@ -21,9 +24,6 @@
|
|
|
21
24
|
* values), so the spread overlays only the keys the declaration actually
|
|
22
25
|
* defines; the instance manifest fills any gaps.
|
|
23
26
|
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
declaration: Partial<M> | undefined,
|
|
27
|
-
): M {
|
|
28
|
-
return { ...instanceManifest, ...declaration }
|
|
27
|
+
function overlayDeclaration(instanceManifest, declaration) {
|
|
28
|
+
return { ...instanceManifest, ...declaration };
|
|
29
29
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AddonSearchService = void 0;
|
|
4
|
+
const types_1 = require("@camstack/types");
|
|
5
|
+
class AddonSearchService {
|
|
6
|
+
loggingService;
|
|
7
|
+
cache = null;
|
|
8
|
+
CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
9
|
+
constructor(loggingService) {
|
|
10
|
+
this.loggingService = loggingService;
|
|
11
|
+
}
|
|
12
|
+
async searchAddons(query) {
|
|
13
|
+
const npmResults = await this.fetchFromNpm();
|
|
14
|
+
// Filter by additional query if provided
|
|
15
|
+
let filtered = npmResults;
|
|
16
|
+
if (query) {
|
|
17
|
+
const q = query.toLowerCase();
|
|
18
|
+
filtered = npmResults.filter((r) => r.name.toLowerCase().includes(q) ||
|
|
19
|
+
r.description?.toLowerCase().includes(q) ||
|
|
20
|
+
r.keywords?.some((k) => k.toLowerCase().includes(q)));
|
|
21
|
+
}
|
|
22
|
+
// Return without install status — the caller can merge with installed list
|
|
23
|
+
return filtered.map((r) => ({
|
|
24
|
+
name: r.name,
|
|
25
|
+
version: r.version,
|
|
26
|
+
description: r.description ?? '',
|
|
27
|
+
keywords: r.keywords ?? [],
|
|
28
|
+
publishedAt: r.date ?? '',
|
|
29
|
+
author: r.publisher?.username ?? '',
|
|
30
|
+
installed: false, // caller will enrich this
|
|
31
|
+
installedVersion: undefined,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
async fetchFromNpm() {
|
|
35
|
+
// Return from cache if still fresh
|
|
36
|
+
if (this.cache && Date.now() - this.cache.timestamp < this.CACHE_TTL_MS) {
|
|
37
|
+
return this.cache.results;
|
|
38
|
+
}
|
|
39
|
+
// npm registry v1 search: packages with BOTH "camstack" and "addon" keywords
|
|
40
|
+
const url = 'https://registry.npmjs.org/-/v1/search?text=keywords:camstack+keywords:addon&size=250';
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch(url, {
|
|
43
|
+
headers: { Accept: 'application/json' },
|
|
44
|
+
signal: AbortSignal.timeout(10000),
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new Error(`npm search failed: ${response.status}`);
|
|
48
|
+
}
|
|
49
|
+
const data = (await response.json());
|
|
50
|
+
const results = data.objects.map((o) => o.package);
|
|
51
|
+
this.cache = { results, timestamp: Date.now() };
|
|
52
|
+
return results;
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
const logger = this.loggingService.createLogger('addon-search');
|
|
56
|
+
logger.warn('npm search failed', { meta: { error: (0, types_1.errMsg)(err) } });
|
|
57
|
+
// Stale cache is better than nothing on transient network failure
|
|
58
|
+
return this.cache?.results ?? [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.AddonSearchService = AddonSearchService;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAddonSettingsProvider = createAddonSettingsProvider;
|
|
4
|
+
function reshapeForOutput(schema) {
|
|
5
|
+
return {
|
|
6
|
+
tabs: schema.tabs
|
|
7
|
+
? schema.tabs.map((t) => ({ id: t.id, label: t.label, icon: t.icon, order: t.order }))
|
|
8
|
+
: undefined,
|
|
9
|
+
sections: schema.sections.map((s) => ({
|
|
10
|
+
id: s.id,
|
|
11
|
+
title: s.title,
|
|
12
|
+
description: s.description,
|
|
13
|
+
style: s.style,
|
|
14
|
+
defaultCollapsed: s.defaultCollapsed,
|
|
15
|
+
columns: s.columns,
|
|
16
|
+
tab: s.tab,
|
|
17
|
+
order: s.order,
|
|
18
|
+
fields: [...s.fields],
|
|
19
|
+
})),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* True for Moleculer's `ServiceNotFoundError` — raised when a
|
|
24
|
+
* `<addonId>.settings.*` action is called before the owning forked
|
|
25
|
+
* addon has registered its service (still booting, or wedged in
|
|
26
|
+
* `onInitialize`). Matched by `name` so we don't depend on Moleculer's
|
|
27
|
+
* error class being importable here.
|
|
28
|
+
*/
|
|
29
|
+
function isServiceNotFoundError(err) {
|
|
30
|
+
return err instanceof Error && err.name === 'ServiceNotFoundError';
|
|
31
|
+
}
|
|
32
|
+
function createAddonSettingsProvider(deps) {
|
|
33
|
+
const { getAddon, gateway } = deps;
|
|
34
|
+
// Forked READ — route through the shared gateway (UDS hub-local-child OR
|
|
35
|
+
// Moleculer remote agent). Degrades to null when the forked addon can't
|
|
36
|
+
// answer yet (mid-boot / missing service) so the panel shows its empty state
|
|
37
|
+
// instead of bubbling a 500.
|
|
38
|
+
async function forkedGet(addonId, method, args, nodeId) {
|
|
39
|
+
try {
|
|
40
|
+
const result = await gateway.callForked(addonId, { target: 'settings', method, args }, nodeId);
|
|
41
|
+
return result ? reshapeForOutput(result) : null;
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (isServiceNotFoundError(err))
|
|
45
|
+
return null;
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Forked UPDATE — route through the shared gateway; returns the ack.
|
|
50
|
+
async function forkedUpdate(addonId, method, args, nodeId) {
|
|
51
|
+
await gateway.callForked(addonId, { target: 'settings', method, args }, nodeId);
|
|
52
|
+
return { success: true };
|
|
53
|
+
}
|
|
54
|
+
// ── Provider implementation ──────────────────────────────────────
|
|
55
|
+
return {
|
|
56
|
+
async getGlobalSettings(input) {
|
|
57
|
+
if (gateway.isInProcess(input.addonId, input.nodeId)) {
|
|
58
|
+
const addon = getAddon(input.addonId);
|
|
59
|
+
if (!addon || typeof addon.getGlobalSettings !== 'function')
|
|
60
|
+
return null;
|
|
61
|
+
const result = await addon.getGlobalSettings(input.overlay, input.cap);
|
|
62
|
+
return result ? reshapeForOutput(result) : null;
|
|
63
|
+
}
|
|
64
|
+
return forkedGet(input.addonId, 'getGlobalSettings', {
|
|
65
|
+
...(input.overlay ? { overlay: input.overlay } : {}),
|
|
66
|
+
...(input.cap ? { cap: input.cap } : {}),
|
|
67
|
+
}, input.nodeId);
|
|
68
|
+
},
|
|
69
|
+
async updateGlobalSettings(input) {
|
|
70
|
+
if (gateway.isInProcess(input.addonId, input.nodeId)) {
|
|
71
|
+
const addon = getAddon(input.addonId);
|
|
72
|
+
if (!addon || typeof addon.updateGlobalSettings !== 'function') {
|
|
73
|
+
throw new Error(`Addon "${input.addonId}" does not implement updateGlobalSettings`);
|
|
74
|
+
}
|
|
75
|
+
await addon.updateGlobalSettings(input.patch);
|
|
76
|
+
return { success: true };
|
|
77
|
+
}
|
|
78
|
+
return forkedUpdate(input.addonId, 'updateGlobalSettings', { patch: input.patch }, input.nodeId);
|
|
79
|
+
},
|
|
80
|
+
async getDeviceSettings(input) {
|
|
81
|
+
if (gateway.isInProcess(input.addonId, input.nodeId)) {
|
|
82
|
+
const addon = getAddon(input.addonId);
|
|
83
|
+
if (!addon || typeof addon.getDeviceSettings !== 'function')
|
|
84
|
+
return null;
|
|
85
|
+
const result = await addon.getDeviceSettings(input.deviceId);
|
|
86
|
+
return result ? reshapeForOutput(result) : null;
|
|
87
|
+
}
|
|
88
|
+
return forkedGet(input.addonId, 'getDeviceSettings', { deviceId: input.deviceId }, input.nodeId);
|
|
89
|
+
},
|
|
90
|
+
async updateDeviceSettings(input) {
|
|
91
|
+
if (gateway.isInProcess(input.addonId, input.nodeId)) {
|
|
92
|
+
const addon = getAddon(input.addonId);
|
|
93
|
+
if (!addon || typeof addon.updateDeviceSettings !== 'function') {
|
|
94
|
+
throw new Error(`Addon "${input.addonId}" does not implement updateDeviceSettings`);
|
|
95
|
+
}
|
|
96
|
+
await addon.updateDeviceSettings(input.deviceId, input.patch);
|
|
97
|
+
return { success: true };
|
|
98
|
+
}
|
|
99
|
+
return forkedUpdate(input.addonId, 'updateDeviceSettings', { deviceId: input.deviceId, patch: input.patch }, input.nodeId);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AddonBridgeService = void 0;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const types_1 = require("@camstack/types");
|
|
39
|
+
/**
|
|
40
|
+
* AddonBridgeService — slim package-management surface.
|
|
41
|
+
*
|
|
42
|
+
* Historically this service also cached per-camera pipeline configs + per-
|
|
43
|
+
* addon config records + hosted an in-process `PipelineRunner` that consumers
|
|
44
|
+
* called via `processFrame` / `processMotionFrame`. Every piece of that
|
|
45
|
+
* plumbing was dead post commit 2c (the runtime scheduler moved to
|
|
46
|
+
* `addon-pipeline-runner`) and the pipeline page redesign finishes the job
|
|
47
|
+
* by deleting the cache, the AddonEngineManager wiring, the bridge-pipeline
|
|
48
|
+
* tRPC router, and the PipelineWiringModule that bolted the legacy addon
|
|
49
|
+
* resolver onto this service.
|
|
50
|
+
*
|
|
51
|
+
* What's left is ONLY the addon package management surface: the installer
|
|
52
|
+
* + the loader + `reloadPackages` + `listAvailableAddons`. These are still
|
|
53
|
+
* used by:
|
|
54
|
+
* - `server/backend/src/api/addon-upload.ts` (multipart upload → install
|
|
55
|
+
* → loader reload)
|
|
56
|
+
* - `server/backend/src/api/bridge-addons.router.ts` (install/uninstall
|
|
57
|
+
* package lifecycle, addon list via the loader)
|
|
58
|
+
*/
|
|
59
|
+
class AddonBridgeService {
|
|
60
|
+
loggingService;
|
|
61
|
+
logger;
|
|
62
|
+
loader;
|
|
63
|
+
installer = null;
|
|
64
|
+
/** Whether the bridge initialised successfully */
|
|
65
|
+
available = false;
|
|
66
|
+
constructor(loggingService) {
|
|
67
|
+
this.loggingService = loggingService;
|
|
68
|
+
this.logger = this.loggingService.createLogger('AddonBridge');
|
|
69
|
+
// Initialize installer eagerly in constructor (no async needed).
|
|
70
|
+
// This ensures install/uninstall works even before onModuleInit completes.
|
|
71
|
+
try {
|
|
72
|
+
const kernel = require('@camstack/kernel');
|
|
73
|
+
const dataDir = process.env.CAMSTACK_DATA ?? 'camstack-data';
|
|
74
|
+
const addonsDir = path.resolve(dataDir, 'addons');
|
|
75
|
+
const workspacePackagesDir = kernel.detectWorkspacePackagesDir(__dirname);
|
|
76
|
+
this.installer = new kernel.AddonInstaller({
|
|
77
|
+
addonsDir,
|
|
78
|
+
workspacePackagesDir: workspacePackagesDir ?? undefined,
|
|
79
|
+
});
|
|
80
|
+
kernel.ensureDir(addonsDir);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const msg = (0, types_1.errMsg)(error);
|
|
84
|
+
this.logger.warn('Installer init failed', { meta: { error: msg } });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async onModuleInit() {
|
|
88
|
+
this.logger.info('Initializing addon bridge (package management only)...');
|
|
89
|
+
try {
|
|
90
|
+
const kernel = await Promise.resolve().then(() => __importStar(require('@camstack/kernel')));
|
|
91
|
+
this.loader = new kernel.AddonLoader(this.logger.child('AddonLoader'));
|
|
92
|
+
const dataDir = process.env.CAMSTACK_DATA ?? 'camstack-data';
|
|
93
|
+
const addonsDir = path.resolve(dataDir, 'addons');
|
|
94
|
+
await this.loader.loadFromDirectory(addonsDir);
|
|
95
|
+
this.available = true;
|
|
96
|
+
this.logger.info('Addon bridge initialized', {
|
|
97
|
+
meta: { count: this.loader.listAddons().length },
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
const msg = (0, types_1.errMsg)(error);
|
|
102
|
+
this.logger.warn('Addon bridge loader failed — install/uninstall still available', {
|
|
103
|
+
meta: { error: msg },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async onModuleDestroy() {
|
|
108
|
+
this.logger.info('Addon bridge shut down');
|
|
109
|
+
}
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Public API
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
/** Whether the bridge is ready for use */
|
|
114
|
+
isAvailable() {
|
|
115
|
+
return this.available;
|
|
116
|
+
}
|
|
117
|
+
/** Return the underlying AddonLoader (for querying / introspection) */
|
|
118
|
+
getLoader() {
|
|
119
|
+
return this.loader;
|
|
120
|
+
}
|
|
121
|
+
/** List IDs of all successfully loaded addons */
|
|
122
|
+
listAvailableAddons() {
|
|
123
|
+
if (!this.available)
|
|
124
|
+
return [];
|
|
125
|
+
return this.loader.listAddons().map((a) => a.declaration.id);
|
|
126
|
+
}
|
|
127
|
+
/** Return the AddonInstaller instance (may be null if bridge failed to init) */
|
|
128
|
+
getInstaller() {
|
|
129
|
+
return this.installer;
|
|
130
|
+
}
|
|
131
|
+
/** Re-discover addons from the addons directory (call after install/uninstall) */
|
|
132
|
+
async reloadPackages() {
|
|
133
|
+
if (!this.available) {
|
|
134
|
+
this.logger.warn('reloadPackages called but bridge is unavailable — skipping');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const kernel = await Promise.resolve().then(() => __importStar(require('@camstack/kernel')));
|
|
138
|
+
this.loader = new kernel.AddonLoader(this.logger.child('AddonLoader'));
|
|
139
|
+
const dataDir = process.env.CAMSTACK_DATA ?? 'camstack-data';
|
|
140
|
+
const addonsDir = path.resolve(dataDir, 'addons');
|
|
141
|
+
await this.loader.loadFromDirectory(addonsDir);
|
|
142
|
+
this.logger.info('Reloaded', { meta: { count: this.loader.listAddons().length } });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.AddonBridgeService = AddonBridgeService;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AddonPagesService = void 0;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
/**
|
|
40
|
+
* AddonPagesService — server-side helper that backs the static file
|
|
41
|
+
* route `/api/addon-pages/:addonId/*` (registered in `main.ts`).
|
|
42
|
+
*
|
|
43
|
+
* The public listing surface (`addonPages.listPages` on the AppRouter)
|
|
44
|
+
* has been split out of this class — it now lives in the
|
|
45
|
+
* `addon-pages-aggregator` builtin (`@camstack/core/builtins/...`)
|
|
46
|
+
* which walks every `addon-pages-source` collection provider and
|
|
47
|
+
* stamps versioned `bundleUrl`s. The split lets both ends flow through
|
|
48
|
+
* codegen instead of relying on a hand-written wrapper.
|
|
49
|
+
*
|
|
50
|
+
* What stays here: filesystem path resolution + traversal protection
|
|
51
|
+
* for the static file route. Both rely on `CapabilityService` to
|
|
52
|
+
* enumerate registered page providers (so unknown / unregistered
|
|
53
|
+
* addons can't be probed via path traversal tricks) and on
|
|
54
|
+
* `ConfigService` to locate the addons directory.
|
|
55
|
+
*/
|
|
56
|
+
class AddonPagesService {
|
|
57
|
+
loggingService;
|
|
58
|
+
configService;
|
|
59
|
+
caps;
|
|
60
|
+
logger;
|
|
61
|
+
constructor(loggingService, configService, caps) {
|
|
62
|
+
this.loggingService = loggingService;
|
|
63
|
+
this.configService = configService;
|
|
64
|
+
this.caps = caps;
|
|
65
|
+
this.logger = this.loggingService.createLogger('AddonPagesService');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the filesystem path to an addon's page bundle file.
|
|
69
|
+
* Returns null if the addon is not registered, the file doesn't exist,
|
|
70
|
+
* or the path would escape the addon directory (path traversal protection).
|
|
71
|
+
*/
|
|
72
|
+
resolveBundle(addonId, filePath) {
|
|
73
|
+
// Check if the addon is a registered page provider via the
|
|
74
|
+
// collection cap. `addon-pages-source` is the new home for the raw
|
|
75
|
+
// per-provider declarations (the public `addon-pages` cap is the
|
|
76
|
+
// aggregated singleton — different shape, different surface).
|
|
77
|
+
const providers = this.caps.getCollection('addon-pages-source');
|
|
78
|
+
const isRegistered = providers.some((p) => p.id === addonId);
|
|
79
|
+
if (!isRegistered) {
|
|
80
|
+
this.logger.warn('Bundle resolve failed: addon not registered as page provider', {
|
|
81
|
+
tags: { addonId },
|
|
82
|
+
});
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const addonsDir = this.resolveAddonsDir();
|
|
86
|
+
// Addon packages are at addons/@camstack/addon-<id>/dist/
|
|
87
|
+
const addonDistPath = path.join(addonsDir, '@camstack', `addon-${addonId}`, 'dist');
|
|
88
|
+
const resolvedBase = path.resolve(addonDistPath);
|
|
89
|
+
const resolvedFile = path.resolve(addonDistPath, filePath);
|
|
90
|
+
// Path traversal protection
|
|
91
|
+
if (!resolvedFile.startsWith(resolvedBase + path.sep) && resolvedFile !== resolvedBase) {
|
|
92
|
+
this.logger.warn('Path traversal denied for addon', { tags: { addonId }, meta: { filePath } });
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
if (!fs.existsSync(resolvedFile)) {
|
|
96
|
+
this.logger.debug('Bundle file not found', { meta: { resolvedFile } });
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return resolvedFile;
|
|
100
|
+
}
|
|
101
|
+
/** Resolve the addons directory from config */
|
|
102
|
+
resolveAddonsDir() {
|
|
103
|
+
const dataPath = this.configService.get('server.dataPath') ?? 'camstack-data';
|
|
104
|
+
return path.resolve(dataPath, 'addons');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.AddonPagesService = AddonPagesService;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AddonWidgetsService = void 0;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
/**
|
|
40
|
+
* Strip the `@<nodeId>` suffix the CapabilityBridge appends to
|
|
41
|
+
* collection-provider registry keys for cross-node addons. The widget
|
|
42
|
+
* static-file route always receives the bare manifest id.
|
|
43
|
+
*/
|
|
44
|
+
function stripNodeSuffix(registryKey) {
|
|
45
|
+
const at = registryKey.indexOf('@');
|
|
46
|
+
return at === -1 ? registryKey : registryKey.slice(0, at);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* AddonWidgetsService — server-side helper that backs the static file
|
|
50
|
+
* route `/api/addon-widgets/:addonId/*` (registered in `main.ts`).
|
|
51
|
+
*
|
|
52
|
+
* Mirrors `AddonPagesService` exactly. The public listing surface
|
|
53
|
+
* (`addonWidgets.listWidgets` on the AppRouter) lives in the
|
|
54
|
+
* `addon-widgets-aggregator` builtin which walks every
|
|
55
|
+
* `addon-widgets-source` collection provider and stamps versioned
|
|
56
|
+
* `bundleUrl`s. Both ends flow through codegen.
|
|
57
|
+
*
|
|
58
|
+
* What stays here: filesystem path resolution + traversal protection
|
|
59
|
+
* for the static file route. The registered-provider check uses
|
|
60
|
+
* `CapabilityService` (so unknown / unregistered addons can't be
|
|
61
|
+
* probed via path traversal tricks); the on-disk path comes from
|
|
62
|
+
* `AddonRegistryService.getAddonInstallPath()` so bundled addons
|
|
63
|
+
* (where one npm package ships multiple addon entries under
|
|
64
|
+
* `dist/<entryId>/`) resolve to the right sub-folder instead of the
|
|
65
|
+
* pre-bundle `addons/@camstack/addon-<id>/dist/` layout.
|
|
66
|
+
*/
|
|
67
|
+
class AddonWidgetsService {
|
|
68
|
+
loggingService;
|
|
69
|
+
caps;
|
|
70
|
+
registry;
|
|
71
|
+
logger;
|
|
72
|
+
constructor(loggingService, caps, registry) {
|
|
73
|
+
this.loggingService = loggingService;
|
|
74
|
+
this.caps = caps;
|
|
75
|
+
this.registry = registry;
|
|
76
|
+
this.logger = this.loggingService.createLogger('AddonWidgetsService');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the filesystem path to an addon's widget bundle file.
|
|
80
|
+
* Returns null if the addon is not registered, the file doesn't exist,
|
|
81
|
+
* or the path would escape the addon directory (path traversal protection).
|
|
82
|
+
*/
|
|
83
|
+
resolveBundle(addonId, filePath) {
|
|
84
|
+
// The collection cap doesn't carry an `id` on its provider, so we
|
|
85
|
+
// attribute via `getCollectionEntries` (returns `[addonId, provider]`).
|
|
86
|
+
//
|
|
87
|
+
// For cross-node addons the CapabilityBridge registers the provider
|
|
88
|
+
// under a node-qualified key `<addonId>@<nodeId>` (see
|
|
89
|
+
// `moleculer.service.ts`). The bundle is hub-resident and keyed by
|
|
90
|
+
// the bare manifest id, so the route always carries the bare
|
|
91
|
+
// `addonId` — match it against each registry key with the
|
|
92
|
+
// `@<nodeId>` suffix stripped.
|
|
93
|
+
const entries = this.caps.getCollectionEntries('addon-widgets-source');
|
|
94
|
+
const isRegistered = entries.some(([id]) => stripNodeSuffix(id) === addonId);
|
|
95
|
+
if (!isRegistered) {
|
|
96
|
+
this.logger.warn('Bundle resolve failed: addon not registered as widget provider', {
|
|
97
|
+
tags: { addonId },
|
|
98
|
+
});
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const installPath = this.registry.getAddonInstallPath(addonId);
|
|
102
|
+
if (!installPath) {
|
|
103
|
+
this.logger.warn('Bundle resolve failed: addon install path not found', { tags: { addonId } });
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const resolvedBase = path.resolve(installPath.distDir);
|
|
107
|
+
const resolvedFile = path.resolve(installPath.distDir, filePath);
|
|
108
|
+
// Path traversal protection
|
|
109
|
+
if (!resolvedFile.startsWith(resolvedBase + path.sep) && resolvedFile !== resolvedBase) {
|
|
110
|
+
this.logger.warn('Path traversal denied for addon', { tags: { addonId }, meta: { filePath } });
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
if (!fs.existsSync(resolvedFile)) {
|
|
114
|
+
this.logger.debug('Bundle file not found', { meta: { resolvedFile } });
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return resolvedFile;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
exports.AddonWidgetsService = AddonWidgetsService;
|