@camstack/system 1.0.2
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/dist/addon/addon-api-factory.d.ts +35 -0
- package/dist/addon-routes/addon-route-registry.d.ts +37 -0
- package/dist/addon-runner.js +599 -0
- package/dist/addon-runner.mjs +597 -0
- package/dist/auth/api-key-manager.d.ts +26 -0
- package/dist/auth/auth-manager.d.ts +109 -0
- package/dist/auth/parse-record.d.ts +18 -0
- package/dist/auth/scope-matcher.d.ts +7 -0
- package/dist/auth/scoped-token-manager.d.ts +40 -0
- package/dist/auth/totp-manager.d.ts +51 -0
- package/dist/auth/user-manager.d.ts +34 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.d.ts +53 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +259 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +251 -0
- package/dist/builtins/addon-pages-aggregator/dedupe-pages.d.ts +6 -0
- package/dist/builtins/addon-pages-aggregator/index.d.ts +1 -0
- package/dist/builtins/addon-pages-aggregator/index.js +8 -0
- package/dist/builtins/addon-pages-aggregator/index.mjs +2 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.d.ts +47 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +228 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +220 -0
- package/dist/builtins/addon-widgets-aggregator/index.d.ts +1 -0
- package/dist/builtins/addon-widgets-aggregator/index.js +8 -0
- package/dist/builtins/addon-widgets-aggregator/index.mjs +2 -0
- package/dist/builtins/alerts/alerts.addon.d.ts +81 -0
- package/dist/builtins/alerts/alerts.addon.js +601 -0
- package/dist/builtins/alerts/alerts.addon.mjs +595 -0
- package/dist/builtins/alerts/index.d.ts +1 -0
- package/dist/builtins/alerts/index.js +4 -0
- package/dist/builtins/alerts/index.mjs +2 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +147 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2229 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2220 -0
- package/dist/builtins/backup-orchestrator/cron-helpers.d.ts +23 -0
- package/dist/builtins/backup-orchestrator/destination-policy.d.ts +72 -0
- package/dist/builtins/backup-orchestrator/download-helpers.d.ts +12 -0
- package/dist/builtins/backup-orchestrator/index.d.ts +2 -0
- package/dist/builtins/backup-orchestrator/index.js +8 -0
- package/dist/builtins/backup-orchestrator/index.mjs +2 -0
- package/dist/builtins/backup-orchestrator/manifest-store.d.ts +77 -0
- package/dist/builtins/console-logging/console-destination.d.ts +13 -0
- package/dist/builtins/console-logging/console-logging.addon.d.ts +25 -0
- package/dist/builtins/console-logging/index.d.ts +3 -0
- package/dist/builtins/console-logging/index.js +104 -0
- package/dist/builtins/console-logging/index.mjs +95 -0
- package/dist/builtins/device-manager/device-config-contribution.d.ts +32 -0
- package/dist/builtins/device-manager/device-event-propagator.d.ts +26 -0
- package/dist/builtins/device-manager/device-link-overlay.d.ts +23 -0
- package/dist/builtins/device-manager/device-link-resolver.d.ts +15 -0
- package/dist/builtins/device-manager/device-manager.addon.d.ts +452 -0
- package/dist/builtins/device-manager/device-manager.addon.js +3299 -0
- package/dist/builtins/device-manager/device-manager.addon.mjs +3292 -0
- package/dist/builtins/device-manager/index.d.ts +2 -0
- package/dist/builtins/device-manager/index.js +8 -0
- package/dist/builtins/device-manager/index.mjs +2 -0
- package/dist/builtins/hub-forwarder/hub-forwarder-destination.d.ts +44 -0
- package/dist/builtins/hub-forwarder/hub-forwarder.addon.d.ts +15 -0
- package/dist/builtins/hub-forwarder/index.d.ts +3 -0
- package/dist/builtins/hub-forwarder/index.js +154 -0
- package/dist/builtins/hub-forwarder/index.mjs +145 -0
- package/dist/builtins/local-auth/auth-schema.d.ts +26 -0
- package/dist/builtins/local-auth/index.d.ts +1 -0
- package/dist/builtins/local-auth/index.js +4 -0
- package/dist/builtins/local-auth/index.mjs +2 -0
- package/dist/builtins/local-auth/local-auth.addon.d.ts +18 -0
- package/dist/builtins/local-auth/local-auth.addon.js +8094 -0
- package/dist/builtins/local-auth/local-auth.addon.mjs +8063 -0
- package/dist/builtins/local-auth/oauth-grants.d.ts +45 -0
- package/dist/builtins/local-auth/oauth-session-manager.d.ts +50 -0
- package/dist/builtins/local-network/index.d.ts +2 -0
- package/dist/builtins/local-network/index.js +10 -0
- package/dist/builtins/local-network/index.mjs +2 -0
- package/dist/builtins/local-network/local-network.addon.d.ts +150 -0
- package/dist/builtins/local-network/local-network.addon.js +489 -0
- package/dist/builtins/local-network/local-network.addon.mjs +477 -0
- package/dist/builtins/native-metrics/index.d.ts +2 -0
- package/dist/builtins/native-metrics/native-metrics-provider.d.ts +48 -0
- package/dist/builtins/native-metrics/native-metrics.addon.d.ts +73 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js +922 -0
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +914 -0
- package/dist/builtins/platform-probe/hardware-decode-accel-probe.d.ts +37 -0
- package/dist/builtins/platform-probe/hardware-encoder-probe.d.ts +13 -0
- package/dist/builtins/platform-probe/index.d.ts +22 -0
- package/dist/builtins/platform-probe/index.js +834 -0
- package/dist/builtins/platform-probe/index.mjs +822 -0
- package/dist/builtins/platform-probe/inference-config-resolver.d.ts +29 -0
- package/dist/builtins/platform-probe/intel-accelerators.d.ts +11 -0
- package/dist/builtins/platform-probe/platform-scorer.d.ts +30 -0
- package/dist/builtins/platform-probe/runtime-packages.d.ts +6 -0
- package/dist/builtins/remote-access-orchestrator/enabled-providers-reconcile.d.ts +96 -0
- package/dist/builtins/remote-access-orchestrator/index.d.ts +1 -0
- package/dist/builtins/remote-access-orchestrator/index.js +8 -0
- package/dist/builtins/remote-access-orchestrator/index.mjs +2 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.d.ts +40 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +214 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +208 -0
- package/dist/builtins/shared/settle-sources.d.ts +22 -0
- package/dist/builtins/snapshot/index.d.ts +2 -0
- package/dist/builtins/snapshot/index.js +494 -0
- package/dist/builtins/snapshot/index.mjs +488 -0
- package/dist/builtins/snapshot/snapshot.addon.d.ts +120 -0
- package/dist/builtins/sqlite-storage/config-store.d.ts +8 -0
- package/dist/builtins/sqlite-storage/device-store.d.ts +23 -0
- package/dist/builtins/sqlite-storage/filesystem-browse-provider.d.ts +25 -0
- package/dist/builtins/sqlite-storage/filesystem-storage-provider.d.ts +83 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.ts +32 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +396 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +388 -0
- package/dist/builtins/sqlite-storage/index.d.ts +8 -0
- package/dist/builtins/sqlite-storage/index.js +62 -0
- package/dist/builtins/sqlite-storage/index.mjs +49 -0
- package/dist/builtins/sqlite-storage/integration-registry.d.ts +27 -0
- package/dist/builtins/sqlite-storage/path-guard.d.ts +4 -0
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +102 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +14 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +644 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +636 -0
- package/dist/builtins/storage-orchestrator/index.d.ts +6 -0
- package/dist/builtins/storage-orchestrator/index.js +10 -0
- package/dist/builtins/storage-orchestrator/index.mjs +2 -0
- package/dist/builtins/storage-orchestrator/location-store.d.ts +49 -0
- package/dist/builtins/storage-orchestrator/provider-discovery.d.ts +10 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +103 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1138 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1128 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +236 -0
- package/dist/builtins/storage-orchestrator/storage-pressure-manager.d.ts +38 -0
- package/dist/builtins/system-backup/system-backup.service.d.ts +137 -0
- package/dist/builtins/system-config/index.d.ts +1 -0
- package/dist/builtins/system-config/index.js +8 -0
- package/dist/builtins/system-config/index.mjs +2 -0
- package/dist/builtins/system-config/system-config.addon.d.ts +10 -0
- package/dist/builtins/system-config/system-config.addon.js +232 -0
- package/dist/builtins/system-config/system-config.addon.mjs +226 -0
- package/dist/builtins/winston-logging/index.d.ts +3 -0
- package/dist/builtins/winston-logging/index.js +156 -0
- package/dist/builtins/winston-logging/index.mjs +144 -0
- package/dist/builtins/winston-logging/winston-destination.d.ts +21 -0
- package/dist/builtins/winston-logging/winston-logging.addon.d.ts +19 -0
- package/dist/chunk-CNf5ZN-e.mjs +37 -0
- package/dist/chunk-Cek0wNdY.js +64 -0
- package/dist/download/model-download-service.d.ts +41 -0
- package/dist/download/model-downloader.d.ts +31 -0
- package/dist/events/event-bus.d.ts +10 -0
- package/dist/events/system-event-bus.d.ts +14 -0
- package/dist/feature/feature-manager.d.ts +11 -0
- package/dist/formatter-B7qW8bPJ.mjs +162 -0
- package/dist/formatter-DqAKDlvN.js +167 -0
- package/dist/http/authenticated-file-server.d.ts +53 -0
- package/dist/http/data-plane-registry.d.ts +23 -0
- package/dist/http/file-data-plane.d.ts +10 -0
- package/dist/http/reverse-proxy.d.ts +15 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +93485 -0
- package/dist/index.mjs +93179 -0
- package/dist/intel-accelerators-Gg0P5mnl.js +20 -0
- package/dist/intel-accelerators-hGgpZ0pX.mjs +19 -0
- package/dist/kernel/addon-class-resolver.d.ts +4 -0
- package/dist/kernel/addon-engine-manager.d.ts +22 -0
- package/dist/kernel/addon-health-monitor.d.ts +154 -0
- package/dist/kernel/addon-installer.d.ts +208 -0
- package/dist/kernel/addon-loader.d.ts +106 -0
- package/dist/kernel/addon-manifest.d.ts +77 -0
- package/dist/kernel/capability-handle.d.ts +46 -0
- package/dist/kernel/capability-registry.d.ts +412 -0
- package/dist/kernel/config-manager.d.ts +212 -0
- package/dist/kernel/config-schema.d.ts +93 -0
- package/dist/kernel/custom-action-registry.d.ts +23 -0
- package/dist/kernel/deps/addon-deps-manager.d.ts +19 -0
- package/dist/kernel/deps/manifest-native-deps.d.ts +25 -0
- package/dist/kernel/deps/manifest-python-deps.d.ts +20 -0
- package/dist/kernel/device-registry.d.ts +29 -0
- package/dist/kernel/fs-utils.d.ts +41 -0
- package/dist/kernel/hwaccel/hwaccel-resolver.d.ts +19 -0
- package/dist/kernel/hwaccel/hwaccel-service.d.ts +4 -0
- package/dist/kernel/index.d.ts +74 -0
- package/dist/kernel/infra-capabilities.d.ts +13 -0
- package/dist/kernel/moleculer/addon-context-factory.d.ts +91 -0
- package/dist/kernel/moleculer/addon-data-plane-facility.d.ts +19 -0
- package/dist/kernel/moleculer/addon-runner.d.ts +1 -0
- package/dist/kernel/moleculer/addon-service-factory.d.ts +50 -0
- package/dist/kernel/moleculer/broker-factory.d.ts +50 -0
- package/dist/kernel/moleculer/cap-usage-registry.d.ts +46 -0
- package/dist/kernel/moleculer/capabilities-access.d.ts +21 -0
- package/dist/kernel/moleculer/child-addon-call-dispatch.d.ts +46 -0
- package/dist/kernel/moleculer/child-cap-dispatch.d.ts +20 -0
- package/dist/kernel/moleculer/cluster-secret.d.ts +15 -0
- package/dist/kernel/moleculer/core-cap-service.d.ts +50 -0
- package/dist/kernel/moleculer/crash-supervisor.d.ts +50 -0
- package/dist/kernel/moleculer/device-cap-proxy.d.ts +79 -0
- package/dist/kernel/moleculer/event-bus-core.d.ts +53 -0
- package/dist/kernel/moleculer/event-bus.d.ts +53 -0
- package/dist/kernel/moleculer/hub-log-forwarder.d.ts +36 -0
- package/dist/kernel/moleculer/hub-service.d.ts +35 -0
- package/dist/kernel/moleculer/node-registry.d.ts +126 -0
- package/dist/kernel/moleculer/process-context.d.ts +4 -0
- package/dist/kernel/moleculer/process-service.d.ts +72 -0
- package/dist/kernel/moleculer/provider-registry.d.ts +28 -0
- package/dist/kernel/moleculer/readiness-context.d.ts +62 -0
- package/dist/kernel/moleculer/readiness-service.d.ts +7 -0
- package/dist/kernel/moleculer/register-node-client.d.ts +35 -0
- package/dist/kernel/moleculer/remote-logger.d.ts +43 -0
- package/dist/kernel/moleculer/resilient-cap-call.d.ts +28 -0
- package/dist/kernel/moleculer/stream-probe-service.d.ts +9 -0
- package/dist/kernel/moleculer/trpc-links.d.ts +189 -0
- package/dist/kernel/moleculer/typed-array-serde.d.ts +25 -0
- package/dist/kernel/moleculer/worker-device-restore.d.ts +10 -0
- package/dist/kernel/provider-kind-drift.d.ts +12 -0
- package/dist/kernel/restart-coordinator.d.ts +90 -0
- package/dist/kernel/storage-location-registry.d.ts +40 -0
- package/dist/kernel/transport/cap-action-name.d.ts +100 -0
- package/dist/kernel/transport/cap-route-resolver.d.ts +148 -0
- package/dist/kernel/transport/cap-route.d.ts +148 -0
- package/dist/kernel/transport/child-cap-protocol.d.ts +136 -0
- package/dist/kernel/transport/create-local-transport.d.ts +7 -0
- package/dist/kernel/transport/frame-codec.d.ts +7 -0
- package/dist/kernel/transport/index.d.ts +27 -0
- package/dist/kernel/transport/local-child-client.d.ts +136 -0
- package/dist/kernel/transport/local-child-registry.d.ts +179 -0
- package/dist/kernel/transport/local-endpoint-path.d.ts +6 -0
- package/dist/kernel/transport/local-transport.d.ts +46 -0
- package/dist/kernel/transport/parent-unowned-call.d.ts +75 -0
- package/dist/kernel/transport/socket-channel.d.ts +27 -0
- package/dist/kernel/transport/uds-event-bridge.d.ts +36 -0
- package/dist/kernel/transport/uds-event-bus.d.ts +22 -0
- package/dist/kernel/transport/uds-local-transport.d.ts +18 -0
- package/dist/kernel/transport/uds-log-ingest.d.ts +28 -0
- package/dist/kernel/transport/uds-logger.d.ts +44 -0
- package/dist/kernel/utils/ring-buffer.d.ts +15 -0
- package/dist/kernel/workspace-detect.d.ts +9 -0
- package/dist/lifecycle/lifecycle-state-machine.d.ts +28 -0
- package/dist/logging/formatter.d.ts +30 -0
- package/dist/logging/log-manager.d.ts +54 -0
- package/dist/logging/log-ring-buffer.d.ts +47 -0
- package/dist/logging/partitioned-log-buffer.d.ts +35 -0
- package/dist/logging/scoped-logger.d.ts +17 -0
- package/dist/main-DNnMW7Z2.js +9983 -0
- package/dist/main-rtjOwPBR.mjs +9976 -0
- package/dist/manifest-python-deps-D1DbAQEv.js +6724 -0
- package/dist/manifest-python-deps-DZsKTbs1.mjs +6315 -0
- package/dist/network/network-quality.d.ts +11 -0
- package/dist/notification/notification-service.d.ts +37 -0
- package/dist/notification/toast-service.d.ts +22 -0
- package/dist/pipeline/engine-manager-resolver.d.ts +15 -0
- package/dist/pipeline/pipeline-runner.d.ts +8 -0
- package/dist/pipeline/pipeline-validator.d.ts +13 -0
- package/dist/process/resource-monitor.d.ts +11 -0
- package/dist/python/python-env-manager.d.ts +12 -0
- package/dist/repl/interfaces.d.ts +31 -0
- package/dist/repl/repl-engine.d.ts +8 -0
- package/dist/resource-monitor-ClDGFyf6.mjs +57 -0
- package/dist/resource-monitor-IIEanuJt.js +74 -0
- package/dist/settle-sources-Bhsy57y-.js +38 -0
- package/dist/settle-sources-CDtNC8ub.mjs +33 -0
- package/dist/storage/fs-storage-backend.d.ts +40 -0
- package/dist/storage/storage-location-manager.d.ts +23 -0
- package/dist/storage/storage-manager.d.ts +83 -0
- package/dist/tar-BgAEMRBR.js +5434 -0
- package/dist/tar-ByMOPNM0.mjs +5429 -0
- package/dist/tls/cert-manager.d.ts +26 -0
- package/dist/tls/index.d.ts +1 -0
- package/package.json +343 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { TokenPayload, IScopedLogger, TokenScope } from '@camstack/types';
|
|
2
|
+
export type { TokenPayload };
|
|
3
|
+
export type AuthConfigReader = {
|
|
4
|
+
get<T>(path: string): T;
|
|
5
|
+
update(section: string, data: Record<string, unknown>): void;
|
|
6
|
+
};
|
|
7
|
+
export declare class AuthManager {
|
|
8
|
+
private readonly config;
|
|
9
|
+
private readonly jwtSecret;
|
|
10
|
+
private readonly logger;
|
|
11
|
+
constructor(config: AuthConfigReader, logger?: IScopedLogger);
|
|
12
|
+
signToken(payload: Omit<TokenPayload, 'iat' | 'exp'>): string;
|
|
13
|
+
verifyToken(token: string): TokenPayload;
|
|
14
|
+
hashPassword(password: string): Promise<string>;
|
|
15
|
+
comparePassword(password: string, hash: string): Promise<boolean>;
|
|
16
|
+
generateApiKey(): {
|
|
17
|
+
token: string;
|
|
18
|
+
hash: string;
|
|
19
|
+
prefix: string;
|
|
20
|
+
};
|
|
21
|
+
validateApiKey(token: string, storedHash: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Create a service token for agent/worker authentication.
|
|
24
|
+
* Used when forking workers or when agents register.
|
|
25
|
+
*/
|
|
26
|
+
createServiceToken(opts: {
|
|
27
|
+
readonly agentId: string;
|
|
28
|
+
readonly expiresIn?: string;
|
|
29
|
+
}): string;
|
|
30
|
+
/**
|
|
31
|
+
* Mint a short-lived HMAC-signed bridge token used by SSO-style auth
|
|
32
|
+
* providers (OIDC, SAML, magic-link, …) to hand the post-callback
|
|
33
|
+
* claims to `/api/auth/sso/finish` without trusting unsigned query
|
|
34
|
+
* parameters. The endpoint verifies the token signature with the same
|
|
35
|
+
* `jwtSecret` and only then mints the user session JWT. This closes
|
|
36
|
+
* the privilege-escalation gap where any client could craft a
|
|
37
|
+
* `?isAdmin=1` link to `/sso/finish` and become admin.
|
|
38
|
+
*
|
|
39
|
+
* The payload carries `kind: 'sso-bridge'` so `verifySsoBridgeToken`
|
|
40
|
+
* can reject session tokens reused as bridge tokens (and vice versa).
|
|
41
|
+
* TTL defaults to 5 minutes — long enough to survive the IdP
|
|
42
|
+
* redirect bounce, short enough that a leaked URL stops being useful
|
|
43
|
+
* before the operator notices.
|
|
44
|
+
*/
|
|
45
|
+
signSsoBridgeToken(payload: SsoBridgeClaims, ttlSec?: number): string;
|
|
46
|
+
/**
|
|
47
|
+
* Verify + decode a bridge token minted by `signSsoBridgeToken`.
|
|
48
|
+
* Returns `null` for invalid signature, expired token, or wrong
|
|
49
|
+
* `kind` claim. Never throws — the consumer ( `/sso/finish`) treats
|
|
50
|
+
* `null` as 400 Bad Request.
|
|
51
|
+
*/
|
|
52
|
+
verifySsoBridgeToken(token: string): SsoBridgeClaims | null;
|
|
53
|
+
/**
|
|
54
|
+
* Mint a TOTP challenge token bridging the two-step login flow:
|
|
55
|
+
*
|
|
56
|
+
* 1. POST /login → password OK + user has 2FA → returns
|
|
57
|
+
* `{requiresTotp: true, challengeToken: '...'}` (this token).
|
|
58
|
+
* 2. POST /login/totp-verify → operator types 6-digit code →
|
|
59
|
+
* server verifies challenge token + code → mints the real
|
|
60
|
+
* session JWT.
|
|
61
|
+
*
|
|
62
|
+
* The token carries `kind: 'totp-challenge'` so it can't be confused
|
|
63
|
+
* with a regular session token, plus the `userId` + `username` +
|
|
64
|
+
* `isAdmin` claims that will end up in the final session if the code
|
|
65
|
+
* verifies. TTL defaults to 5 minutes — long enough to type the code,
|
|
66
|
+
* short enough that an abandoned login can't be picked up later.
|
|
67
|
+
*/
|
|
68
|
+
signTotpChallengeToken(payload: TotpChallengeClaims, ttlSec?: number): string;
|
|
69
|
+
/**
|
|
70
|
+
* Verify + decode a TOTP challenge token. Returns `null` for any
|
|
71
|
+
* failure (invalid signature, expired, wrong `kind`, missing
|
|
72
|
+
* claims). The caller MUST also verify the 6-digit code against
|
|
73
|
+
* `totpManager.verify(userId, code)` before minting the real
|
|
74
|
+
* session — this method validates the bridge transport only.
|
|
75
|
+
*/
|
|
76
|
+
verifyTotpChallengeToken(token: string): TotpChallengeClaims | null;
|
|
77
|
+
}
|
|
78
|
+
export interface SsoBridgeClaims {
|
|
79
|
+
readonly userId: string;
|
|
80
|
+
readonly username: string;
|
|
81
|
+
readonly isAdmin: boolean;
|
|
82
|
+
readonly provider: string;
|
|
83
|
+
readonly email?: string;
|
|
84
|
+
readonly displayName?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Public HTTPS URL of the hub that minted the token. Carried verbatim
|
|
87
|
+
* through sign/verify; consumed by cloud-mode OAuth proxies (e.g. the
|
|
88
|
+
* Alexa Lambda) which need to know which hub to forward to without
|
|
89
|
+
* keeping per-user routing state. Optional — only the cloud-mode
|
|
90
|
+
* issuers set it.
|
|
91
|
+
*/
|
|
92
|
+
readonly hubUrl?: string;
|
|
93
|
+
/** Permission scopes baked into the token by the OAuth account-linking
|
|
94
|
+
* grant. Absent on ordinary SSO-login bridge tokens. */
|
|
95
|
+
readonly scopes?: readonly TokenScope[];
|
|
96
|
+
/** OAuth authorization-code binding — set only on `oauth-code` tokens. */
|
|
97
|
+
readonly redirectUri?: string;
|
|
98
|
+
readonly integrationId?: string;
|
|
99
|
+
/** JWT ID — unique per issued code; consumed-set enforces single-use. */
|
|
100
|
+
readonly jti?: string;
|
|
101
|
+
/** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
|
|
102
|
+
* tokens so the verify path can check the session is not revoked. */
|
|
103
|
+
readonly sessionId?: string;
|
|
104
|
+
}
|
|
105
|
+
export interface TotpChallengeClaims {
|
|
106
|
+
readonly userId: string;
|
|
107
|
+
readonly username: string;
|
|
108
|
+
readonly isAdmin: boolean;
|
|
109
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap a Zod schema parse with an actionable error message. The
|
|
3
|
+
* settings-store regression that broke local-auth in May 2026 surfaced
|
|
4
|
+
* as an opaque Zod error listing every required field as `undefined`
|
|
5
|
+
* — confusing because the data IS in the DB, just exposed through the
|
|
6
|
+
* wrong shape by `queryDeclared`. This helper makes the failure mode
|
|
7
|
+
* obvious: it explicitly mentions the kind, the offending value's top-
|
|
8
|
+
* level keys, and a hint about the most common cause.
|
|
9
|
+
*
|
|
10
|
+
* `schema` is typed loosely (any with a `parse` method) to avoid the
|
|
11
|
+
* generic ZodTypeAny variance error across Zod major versions when this
|
|
12
|
+
* helper is reused by callers that import schemas from `@camstack/types`.
|
|
13
|
+
*/
|
|
14
|
+
interface ZodLikeSchema<T> {
|
|
15
|
+
parse(data: unknown): T;
|
|
16
|
+
}
|
|
17
|
+
export declare function parseRecord<T>(kind: string, schema: ZodLikeSchema<T>, data: unknown): T;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { TokenScope, MethodAccess } from '@camstack/types';
|
|
2
|
+
/**
|
|
3
|
+
* True if the scope set grants `access` on device-scoped capabilities.
|
|
4
|
+
* A `category:device` grant covers every device cap (the broad grant).
|
|
5
|
+
* Mirrors the OR-semantics of the existing token scope matcher.
|
|
6
|
+
*/
|
|
7
|
+
export declare function scopesAllowDeviceCap(scopes: readonly TokenScope[], access: MethodAccess): boolean;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ScopedToken, TokenScope, SettingsStoreClient } from '@camstack/types';
|
|
2
|
+
/**
|
|
3
|
+
* Manages scoped API tokens with restricted addon/route/capability access.
|
|
4
|
+
*/
|
|
5
|
+
export declare class ScopedTokenManager {
|
|
6
|
+
private readonly store;
|
|
7
|
+
constructor(store: SettingsStoreClient);
|
|
8
|
+
create(userId: string, name: string, scopes: TokenScope[], expiresAt?: number): Promise<{
|
|
9
|
+
token: string;
|
|
10
|
+
record: ScopedToken;
|
|
11
|
+
}>;
|
|
12
|
+
validate(rawToken: string): Promise<ScopedToken | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Coarse scope match — checks only `addonId` / `capability` (no
|
|
15
|
+
* `route-prefix` after the caps-only migration). Used by addon-route
|
|
16
|
+
* HTTP handlers that need a quick allow/deny before the per-method
|
|
17
|
+
* `access` check kicks in via `METHOD_ACCESS_MAP` in
|
|
18
|
+
* `protectedProcedure`.
|
|
19
|
+
*/
|
|
20
|
+
matchesScope(token: ScopedToken, addonId?: string, capability?: string): boolean;
|
|
21
|
+
revoke(tokenId: string): Promise<void>;
|
|
22
|
+
listForUser(userId: string): Promise<ScopedToken[]>;
|
|
23
|
+
updateLastUsed(tokenId: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* One-shot migration: drop tokens whose owner can't be resolved.
|
|
26
|
+
*
|
|
27
|
+
* Two ways a token can end up orphan:
|
|
28
|
+
* • Pre-fix tokens minted via the CLI were owned by the literal
|
|
29
|
+
* string `"system"` (provider hardcode, fixed 2026-05-11).
|
|
30
|
+
* • A user gets deleted but their tokens were not cascade-revoked.
|
|
31
|
+
*
|
|
32
|
+
* Either way the UI's `listScopedTokens({ userId: u.id })` never
|
|
33
|
+
* returns them, so the operator can't revoke through the normal flow.
|
|
34
|
+
* We sweep both classes by passing the live user-id set in.
|
|
35
|
+
*
|
|
36
|
+
* Idempotent: if no orphans exist the method is a no-op.
|
|
37
|
+
* Returns the number of tokens removed.
|
|
38
|
+
*/
|
|
39
|
+
cleanupOrphans(validUserIds: ReadonlySet<string>): Promise<number>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { SettingsStoreClient } from '@camstack/types';
|
|
2
|
+
export interface TotpSetupResult {
|
|
3
|
+
readonly secret: string;
|
|
4
|
+
readonly otpauthUrl: string;
|
|
5
|
+
}
|
|
6
|
+
export interface TotpStatus {
|
|
7
|
+
readonly enabled: boolean;
|
|
8
|
+
readonly confirmedAt: number | null;
|
|
9
|
+
}
|
|
10
|
+
export declare class TotpManager {
|
|
11
|
+
private readonly store;
|
|
12
|
+
constructor(store: SettingsStoreClient, opts?: {
|
|
13
|
+
/** ±N 30-second windows tolerated. Default 1 = ±30 s clock skew. */
|
|
14
|
+
readonly window?: number;
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Begin enrollment. Always generates a fresh secret — calling `setup`
|
|
18
|
+
* a second time on the same user replaces any pending half-enrollment
|
|
19
|
+
* (the user must rescan the QR / re-enter the secret).
|
|
20
|
+
*
|
|
21
|
+
* The user identity (`label`) is what an authenticator displays as
|
|
22
|
+
* the account name; we use the supplied `username` for human-readable
|
|
23
|
+
* recognition. `ISSUER` is the CamStack brand.
|
|
24
|
+
*/
|
|
25
|
+
setup(userId: string, username: string): Promise<TotpSetupResult>;
|
|
26
|
+
/**
|
|
27
|
+
* Confirm enrollment by checking a code from the authenticator
|
|
28
|
+
* against the pending secret. On success, flips `confirmedAt` so
|
|
29
|
+
* `verify()` starts accepting codes. Idempotent on already-confirmed
|
|
30
|
+
* rows: re-confirming a valid code just succeeds with no state
|
|
31
|
+
* change.
|
|
32
|
+
*/
|
|
33
|
+
confirm(userId: string, code: string): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Remove enrollment. Idempotent — no error if the user had no row.
|
|
36
|
+
*/
|
|
37
|
+
disable(userId: string): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Verify a code against the confirmed secret. Returns `false` when:
|
|
40
|
+
* - The user has no row (not set up).
|
|
41
|
+
* - The row is pending (`confirmedAt === null`).
|
|
42
|
+
* - The code doesn't match within the configured window.
|
|
43
|
+
*/
|
|
44
|
+
verify(userId: string, code: string): Promise<boolean>;
|
|
45
|
+
/**
|
|
46
|
+
* Read the user's enrollment status. Pending half-enrollments are
|
|
47
|
+
* reported as `enabled: false` — only a confirmed row counts.
|
|
48
|
+
*/
|
|
49
|
+
getStatus(userId: string): Promise<TotpStatus>;
|
|
50
|
+
private find;
|
|
51
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AuthManager } from './auth-manager.js';
|
|
2
|
+
import { UserRecord, TokenScope, SettingsStoreClient } from '@camstack/types';
|
|
3
|
+
export type { UserRecord };
|
|
4
|
+
interface CreateUserInput {
|
|
5
|
+
username: string;
|
|
6
|
+
password: string;
|
|
7
|
+
isAdmin?: boolean;
|
|
8
|
+
allowedProviders?: string[] | '*';
|
|
9
|
+
allowedDevices?: Record<string, string[] | '*'>;
|
|
10
|
+
scopes?: TokenScope[];
|
|
11
|
+
}
|
|
12
|
+
type UpdatableUserFields = Partial<Pick<UserRecord, 'isAdmin' | 'allowedProviders' | 'allowedDevices' | 'scopes'>>;
|
|
13
|
+
export interface UserStorageAccess {
|
|
14
|
+
getStore(): SettingsStoreClient;
|
|
15
|
+
}
|
|
16
|
+
export interface UserConfigReader {
|
|
17
|
+
get<T>(path: string): T;
|
|
18
|
+
}
|
|
19
|
+
export declare class UserManager {
|
|
20
|
+
private readonly storageAccess;
|
|
21
|
+
private readonly auth;
|
|
22
|
+
private readonly config;
|
|
23
|
+
constructor(storageAccess: UserStorageAccess, auth: AuthManager, config: UserConfigReader);
|
|
24
|
+
private get store();
|
|
25
|
+
create(input: CreateUserInput): Promise<UserRecord>;
|
|
26
|
+
findByUsername(username: string): Promise<UserRecord | null>;
|
|
27
|
+
findById(id: string): Promise<UserRecord | null>;
|
|
28
|
+
validateCredentials(username: string, password: string): Promise<UserRecord | null>;
|
|
29
|
+
listAll(): Promise<Omit<UserRecord, 'passwordHash'>[]>;
|
|
30
|
+
update(id: string, data: UpdatableUserFields): Promise<void>;
|
|
31
|
+
delete(id: string): Promise<void>;
|
|
32
|
+
resetPassword(id: string, newPassword: string): Promise<void>;
|
|
33
|
+
ensureAdminExists(): Promise<void>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { BaseAddon, ProviderRegistration } from '@camstack/types';
|
|
2
|
+
export declare class AddonPagesAggregatorAddon extends BaseAddon {
|
|
3
|
+
readonly id = "addon-pages-aggregator";
|
|
4
|
+
private resolvedPaths;
|
|
5
|
+
/**
|
|
6
|
+
* Last successful `listPages()` snapshot per source. Used as the
|
|
7
|
+
* "stale-but-valid" fallback when a source transiently fails — drops
|
|
8
|
+
* happen often enough during boot (Moleculer service-discovery
|
|
9
|
+
* window) that swallowing the error and returning empty leaves the
|
|
10
|
+
* sidebar with nothing for several seconds. Keeping the previous
|
|
11
|
+
* good entry means a flake is invisible to the operator.
|
|
12
|
+
*/
|
|
13
|
+
private readonly lastGood;
|
|
14
|
+
/** In-flight retry guards keyed by sourceId. Avoids double-scheduling. */
|
|
15
|
+
private readonly retryTimers;
|
|
16
|
+
constructor();
|
|
17
|
+
protected onInitialize(): Promise<ProviderRegistration[]>;
|
|
18
|
+
protected onShutdown(): Promise<void>;
|
|
19
|
+
private aggregate;
|
|
20
|
+
/**
|
|
21
|
+
* Schedule background re-call of `listPages()` on a source that just
|
|
22
|
+
* failed. Walks `RETRY_BACKOFF_MS` from index 0 — each successful
|
|
23
|
+
* call updates the cache and emits `AddonPageReady` so the admin UI
|
|
24
|
+
* invalidates its query. Stops on first success or after the schedule
|
|
25
|
+
* is exhausted.
|
|
26
|
+
*/
|
|
27
|
+
private scheduleRetry;
|
|
28
|
+
private retrySource;
|
|
29
|
+
/**
|
|
30
|
+
* Build `/api/addon-pages/<addonId>/<bundle>?v=<mtime>`. Falls back
|
|
31
|
+
* to `Date.now()` when the bundle path can't be stat'd (remote addon
|
|
32
|
+
* with no local file, addon not yet on disk, etc.) — the browser
|
|
33
|
+
* just gets a fresh URL on each call instead of cache-friendly mtime.
|
|
34
|
+
*/
|
|
35
|
+
private makeBundleUrl;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the local filesystem path for an addon's bundle. Mirrors
|
|
38
|
+
* `AddonPagesService.resolveBundle` (server-side), but without the
|
|
39
|
+
* existence / traversal checks — those still live on the server's
|
|
40
|
+
* static file route. We only need a stat-able path here for the
|
|
41
|
+
* `mtime` cache-buster.
|
|
42
|
+
*/
|
|
43
|
+
private resolveBundlePath;
|
|
44
|
+
/**
|
|
45
|
+
* Read `server.dataPath` from the cluster yml-backed sections (same
|
|
46
|
+
* source the server uses at boot), then derive the addons directory
|
|
47
|
+
* from it. Falls back to `camstack-data/addons` when no settings API
|
|
48
|
+
* is available — agents and isolated tests don't see this addon, so
|
|
49
|
+
* the fallback is purely defensive.
|
|
50
|
+
*/
|
|
51
|
+
private resolvePaths;
|
|
52
|
+
}
|
|
53
|
+
export default AddonPagesAggregatorAddon;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
+
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
7
|
+
let node_fs = require("node:fs");
|
|
8
|
+
node_fs = require_chunk.__toESM(node_fs);
|
|
9
|
+
let node_path = require("node:path");
|
|
10
|
+
node_path = require_chunk.__toESM(node_path);
|
|
11
|
+
let _camstack_types = require("@camstack/types");
|
|
12
|
+
let node_crypto = require("node:crypto");
|
|
13
|
+
//#region src/builtins/addon-pages-aggregator/dedupe-pages.ts
|
|
14
|
+
/** Stable identity for an addon page, independent of which node reported it. */
|
|
15
|
+
function pageKey(info) {
|
|
16
|
+
return `${info.addonId}::${info.page.id}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Return a new list with duplicate `(addonId, page.id)` entries removed,
|
|
20
|
+
* preserving first-seen order. Pure — never mutates the input.
|
|
21
|
+
*/
|
|
22
|
+
function dedupePages(pages) {
|
|
23
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24
|
+
const out = [];
|
|
25
|
+
for (const info of pages) {
|
|
26
|
+
const key = pageKey(info);
|
|
27
|
+
if (seen.has(key)) continue;
|
|
28
|
+
seen.add(key);
|
|
29
|
+
out.push(info);
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.ts
|
|
35
|
+
/**
|
|
36
|
+
* Addon Pages Aggregator — hub-local builtin that owns the singleton
|
|
37
|
+
* `addon-pages` cap.
|
|
38
|
+
*
|
|
39
|
+
* Walks every registered `addon-pages-source` (collection) provider and
|
|
40
|
+
* emits an enriched `AddonPageInfo[]` list with versioned `bundleUrl`s
|
|
41
|
+
* pointing at `/api/addon-pages/<addonId>/<bundle>?v=<mtime>`. The
|
|
42
|
+
* filesystem `mtime` cache-buster lets the browser pick up addon
|
|
43
|
+
* rebuilds without manual reload — same scheme the original
|
|
44
|
+
* hand-written `AddonPagesService.listPages` used before this split.
|
|
45
|
+
*
|
|
46
|
+
* The static file endpoint (`/api/addon-pages/:addonId/*`) is still
|
|
47
|
+
* served by `AddonPagesService.resolveBundle()` on the server side; this
|
|
48
|
+
* addon only owns the listing surface.
|
|
49
|
+
*
|
|
50
|
+
* Why a builtin rather than a cap-providers helper:
|
|
51
|
+
* - The aggregator is conceptually the "addon-pages provider" — addons
|
|
52
|
+
* own caps, not the server. Living in `@camstack/system/builtins` keeps
|
|
53
|
+
* the surface symmetrical with `system-config`, `local-auth`, etc.
|
|
54
|
+
* - Lets the aggregator subscribe to source readiness without piping a
|
|
55
|
+
* CapabilityRegistry handle through `RouterServices`.
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* Backoff schedule (ms) used to retry sources that failed during a
|
|
59
|
+
* `listPages()` round-trip — typically because the cap was just
|
|
60
|
+
* registered (provider connected via Moleculer) but the worker-side
|
|
61
|
+
* action registration hadn't propagated yet, so `Service '...listPages'
|
|
62
|
+
* is not found on '<node>'` raced ahead of the call.
|
|
63
|
+
*
|
|
64
|
+
* We schedule one retry per entry; if every retry fails we stop
|
|
65
|
+
* (the next aggregate() run will pick the source up again). On
|
|
66
|
+
* success we re-emit `AddonPageReady` so admin-ui invalidates its
|
|
67
|
+
* `addonPages.listPages` query and the sidebar populates without a
|
|
68
|
+
* page reload.
|
|
69
|
+
*/
|
|
70
|
+
var RETRY_BACKOFF_MS = [
|
|
71
|
+
500,
|
|
72
|
+
1500,
|
|
73
|
+
4e3
|
|
74
|
+
];
|
|
75
|
+
/**
|
|
76
|
+
* Per-source ceiling for a single `listPages()` round-trip. A healthy
|
|
77
|
+
* source answers in well under this (hub-local UDS <100ms, a reachable
|
|
78
|
+
* remote agent <1s). A source that's unreachable — e.g. an agent-hosted
|
|
79
|
+
* addon whose cap routes through a Moleculer `waitForServices` that waits
|
|
80
|
+
* its full 30s discovery timeout for a node that will never appear — must
|
|
81
|
+
* NOT block the whole listing: we time it out fast, serve its cached
|
|
82
|
+
* snapshot, and let the background retry recover it. Combined with the
|
|
83
|
+
* parallel fan-out below, one slow/dead source can no longer stall the
|
|
84
|
+
* sidebar query (which otherwise hangs long enough to trip the WS
|
|
85
|
+
* keepalive → "server unreachable").
|
|
86
|
+
*/
|
|
87
|
+
var PER_SOURCE_TIMEOUT_MS = 2500;
|
|
88
|
+
var AddonPagesAggregatorAddon = class extends _camstack_types.BaseAddon {
|
|
89
|
+
id = "addon-pages-aggregator";
|
|
90
|
+
resolvedPaths = null;
|
|
91
|
+
/**
|
|
92
|
+
* Last successful `listPages()` snapshot per source. Used as the
|
|
93
|
+
* "stale-but-valid" fallback when a source transiently fails — drops
|
|
94
|
+
* happen often enough during boot (Moleculer service-discovery
|
|
95
|
+
* window) that swallowing the error and returning empty leaves the
|
|
96
|
+
* sidebar with nothing for several seconds. Keeping the previous
|
|
97
|
+
* good entry means a flake is invisible to the operator.
|
|
98
|
+
*/
|
|
99
|
+
lastGood = /* @__PURE__ */ new Map();
|
|
100
|
+
/** In-flight retry guards keyed by sourceId. Avoids double-scheduling. */
|
|
101
|
+
retryTimers = /* @__PURE__ */ new Map();
|
|
102
|
+
constructor() {
|
|
103
|
+
super({});
|
|
104
|
+
}
|
|
105
|
+
async onInitialize() {
|
|
106
|
+
this.resolvedPaths = await this.resolvePaths();
|
|
107
|
+
const provider = { listPages: async () => this.aggregate() };
|
|
108
|
+
this.ctx.logger.info("Initialized — aggregating addon-pages-source providers");
|
|
109
|
+
return [{
|
|
110
|
+
capability: _camstack_types.addonPagesCapability,
|
|
111
|
+
provider
|
|
112
|
+
}];
|
|
113
|
+
}
|
|
114
|
+
async onShutdown() {
|
|
115
|
+
for (const t of this.retryTimers.values()) clearTimeout(t);
|
|
116
|
+
this.retryTimers.clear();
|
|
117
|
+
this.lastGood.clear();
|
|
118
|
+
}
|
|
119
|
+
async aggregate() {
|
|
120
|
+
const sources = this.capabilities?.getCollection("addon-pages-source") ?? [];
|
|
121
|
+
const out = [];
|
|
122
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
123
|
+
const settled = await require_settle_sources.settleSourcesWithTimeout(sources.map((source) => [source.id, () => Promise.resolve(source.listPages())]), PER_SOURCE_TIMEOUT_MS);
|
|
124
|
+
for (const [sourceId, result] of settled) {
|
|
125
|
+
seenIds.add(sourceId);
|
|
126
|
+
if (result.ok) {
|
|
127
|
+
const enriched = result.value.map((page) => ({
|
|
128
|
+
addonId: sourceId,
|
|
129
|
+
page,
|
|
130
|
+
bundleUrl: this.makeBundleUrl(sourceId, page.bundle)
|
|
131
|
+
}));
|
|
132
|
+
for (const item of enriched) out.push(item);
|
|
133
|
+
this.lastGood.set(sourceId, enriched);
|
|
134
|
+
} else {
|
|
135
|
+
this.ctx.logger.warn("addon-pages-source provider failed", { meta: {
|
|
136
|
+
sourceId,
|
|
137
|
+
error: result.error.message
|
|
138
|
+
} });
|
|
139
|
+
const cached = this.lastGood.get(sourceId);
|
|
140
|
+
if (cached !== void 0) {
|
|
141
|
+
for (const item of cached) out.push(item);
|
|
142
|
+
this.ctx.logger.info("addon-pages-source falling back to cached snapshot", { meta: {
|
|
143
|
+
sourceId,
|
|
144
|
+
cachedPages: cached.length
|
|
145
|
+
} });
|
|
146
|
+
}
|
|
147
|
+
this.scheduleRetry(sourceId);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
for (const cachedId of this.lastGood.keys()) if (!seenIds.has(cachedId)) this.lastGood.delete(cachedId);
|
|
151
|
+
return dedupePages(out);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Schedule background re-call of `listPages()` on a source that just
|
|
155
|
+
* failed. Walks `RETRY_BACKOFF_MS` from index 0 — each successful
|
|
156
|
+
* call updates the cache and emits `AddonPageReady` so the admin UI
|
|
157
|
+
* invalidates its query. Stops on first success or after the schedule
|
|
158
|
+
* is exhausted.
|
|
159
|
+
*/
|
|
160
|
+
scheduleRetry(sourceId, attempt = 0) {
|
|
161
|
+
if (attempt >= RETRY_BACKOFF_MS.length) return;
|
|
162
|
+
if (this.retryTimers.has(sourceId)) return;
|
|
163
|
+
const delayMs = RETRY_BACKOFF_MS[attempt] ?? RETRY_BACKOFF_MS[RETRY_BACKOFF_MS.length - 1];
|
|
164
|
+
const timer = setTimeout(() => {
|
|
165
|
+
this.retryTimers.delete(sourceId);
|
|
166
|
+
this.retrySource(sourceId, attempt);
|
|
167
|
+
}, delayMs);
|
|
168
|
+
this.retryTimers.set(sourceId, timer);
|
|
169
|
+
}
|
|
170
|
+
async retrySource(sourceId, attempt) {
|
|
171
|
+
const source = (this.capabilities?.getCollection("addon-pages-source") ?? []).find((s) => s.id === sourceId);
|
|
172
|
+
if (!source) return;
|
|
173
|
+
try {
|
|
174
|
+
const enriched = (await Promise.resolve(source.listPages())).map((page) => ({
|
|
175
|
+
addonId: source.id,
|
|
176
|
+
page,
|
|
177
|
+
bundleUrl: this.makeBundleUrl(source.id, page.bundle)
|
|
178
|
+
}));
|
|
179
|
+
this.lastGood.set(source.id, enriched);
|
|
180
|
+
this.ctx.logger.info("addon-pages-source recovered after retry", { meta: {
|
|
181
|
+
sourceId,
|
|
182
|
+
attempt: attempt + 1,
|
|
183
|
+
pages: enriched.length
|
|
184
|
+
} });
|
|
185
|
+
this.ctx.eventBus.emit({
|
|
186
|
+
id: (0, node_crypto.randomUUID)(),
|
|
187
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
188
|
+
source: {
|
|
189
|
+
type: "addon",
|
|
190
|
+
id: this.id
|
|
191
|
+
},
|
|
192
|
+
category: _camstack_types.EventCategory.AddonPageReady,
|
|
193
|
+
data: {
|
|
194
|
+
addonId: sourceId,
|
|
195
|
+
recovered: true
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
} catch (err) {
|
|
199
|
+
this.ctx.logger.debug("addon-pages-source retry failed", { meta: {
|
|
200
|
+
sourceId,
|
|
201
|
+
attempt: attempt + 1,
|
|
202
|
+
error: (0, _camstack_types.errMsg)(err)
|
|
203
|
+
} });
|
|
204
|
+
this.scheduleRetry(sourceId, attempt + 1);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Build `/api/addon-pages/<addonId>/<bundle>?v=<mtime>`. Falls back
|
|
209
|
+
* to `Date.now()` when the bundle path can't be stat'd (remote addon
|
|
210
|
+
* with no local file, addon not yet on disk, etc.) — the browser
|
|
211
|
+
* just gets a fresh URL on each call instead of cache-friendly mtime.
|
|
212
|
+
*/
|
|
213
|
+
makeBundleUrl(addonId, bundle) {
|
|
214
|
+
const bundlePath = this.resolveBundlePath(addonId, bundle);
|
|
215
|
+
let mtime = Date.now();
|
|
216
|
+
if (bundlePath !== null) try {
|
|
217
|
+
mtime = node_fs.statSync(bundlePath).mtimeMs;
|
|
218
|
+
} catch {}
|
|
219
|
+
return `/api/addon-pages/${addonId}/${bundle}?v=${Math.floor(mtime)}`;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Resolve the local filesystem path for an addon's bundle. Mirrors
|
|
223
|
+
* `AddonPagesService.resolveBundle` (server-side), but without the
|
|
224
|
+
* existence / traversal checks — those still live on the server's
|
|
225
|
+
* static file route. We only need a stat-able path here for the
|
|
226
|
+
* `mtime` cache-buster.
|
|
227
|
+
*/
|
|
228
|
+
resolveBundlePath(addonId, bundle) {
|
|
229
|
+
const paths = this.resolvedPaths;
|
|
230
|
+
if (!paths) return null;
|
|
231
|
+
const addonDistPath = node_path.join(paths.addonsDir, "@camstack", `addon-${addonId}`, "dist");
|
|
232
|
+
const resolvedBase = node_path.resolve(addonDistPath);
|
|
233
|
+
const resolvedFile = node_path.resolve(addonDistPath, bundle);
|
|
234
|
+
if (!resolvedFile.startsWith(resolvedBase + node_path.sep) && resolvedFile !== resolvedBase) return null;
|
|
235
|
+
return resolvedFile;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Read `server.dataPath` from the cluster yml-backed sections (same
|
|
239
|
+
* source the server uses at boot), then derive the addons directory
|
|
240
|
+
* from it. Falls back to `camstack-data/addons` when no settings API
|
|
241
|
+
* is available — agents and isolated tests don't see this addon, so
|
|
242
|
+
* the fallback is purely defensive.
|
|
243
|
+
*/
|
|
244
|
+
async resolvePaths() {
|
|
245
|
+
const fallback = { addonsDir: node_path.resolve("camstack-data", "addons") };
|
|
246
|
+
if (!this.ctx.settings) return fallback;
|
|
247
|
+
try {
|
|
248
|
+
const server = await this.ctx.settings.getSection("server");
|
|
249
|
+
const dataPath = typeof server["dataPath"] === "string" && server["dataPath"] ? server["dataPath"] : "camstack-data";
|
|
250
|
+
return { addonsDir: node_path.resolve(dataPath, "addons") };
|
|
251
|
+
} catch (err) {
|
|
252
|
+
this.ctx.logger.debug("Failed to read server.dataPath — falling back", { meta: { error: (0, _camstack_types.errMsg)(err) } });
|
|
253
|
+
return fallback;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
//#endregion
|
|
258
|
+
exports.AddonPagesAggregatorAddon = AddonPagesAggregatorAddon;
|
|
259
|
+
exports.default = AddonPagesAggregatorAddon;
|