@camstack/server 1.2.593 → 1.2.595
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/agent/agent-http.js +5 -2
- package/dist/agent/agent-service.js +9 -9
- package/dist/agent/main.js +10 -3
- package/dist/api/core/cap-providers.js +2 -2
- package/dist/api/trpc/generated-cap-routers.js +18 -0
- package/dist/closure-patches/patch-reach.json +6 -2
- package/dist/core/addon/addon-registry.service.js +63 -8
- package/dist/core/agent/agent-registry.service.js +3 -3
- package/dist/core/agent/cluster-node-history-store.js +85 -26
- package/dist/core/auth/record-kinds.js +38 -0
- package/dist/core/auth/share-token.service.js +136 -47
- package/dist/core/moleculer/cap-call-fn.js +38 -1
- package/dist/core/moleculer/moleculer.service.js +35 -39
- package/dist/main.js +1 -0
- package/dist/manual-boot.js +4 -2
- package/package.json +13 -13
package/dist/agent/agent-http.js
CHANGED
|
@@ -184,8 +184,11 @@ function writeConfigFile(configPath, data) {
|
|
|
184
184
|
/** Live Moleculer registry snapshot — reused by agent-bootstrap's Option A. */
|
|
185
185
|
function getRegistryNodes(broker) {
|
|
186
186
|
try {
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
return broker.registry.getNodeList({ onlyAvailable: true }).map((node) => ({
|
|
188
|
+
id: node.id,
|
|
189
|
+
...(node.hostname != null ? { hostname: node.hostname } : {}),
|
|
190
|
+
...(node.ipList != null ? { ipList: node.ipList } : {}),
|
|
191
|
+
}));
|
|
189
192
|
}
|
|
190
193
|
catch {
|
|
191
194
|
return [];
|
|
@@ -202,8 +202,7 @@ function readDeployedAddonIds(addonDir) {
|
|
|
202
202
|
}
|
|
203
203
|
function isHubConnected(broker) {
|
|
204
204
|
try {
|
|
205
|
-
const
|
|
206
|
-
const nodes = registry?.getNodeList?.({ onlyAvailable: true }) ?? [];
|
|
205
|
+
const nodes = broker.registry.getNodeList({ onlyAvailable: true });
|
|
207
206
|
return nodes.some((n) => n.id === 'hub');
|
|
208
207
|
}
|
|
209
208
|
catch {
|
|
@@ -367,8 +366,7 @@ function createAgentService(deps) {
|
|
|
367
366
|
},
|
|
368
367
|
deploy: {
|
|
369
368
|
handler: async (ctx) => {
|
|
370
|
-
const {
|
|
371
|
-
const { addonId, source, bundle } = params;
|
|
369
|
+
const { addonId, source, bundle } = ctx.params;
|
|
372
370
|
// execFile (no shell) instead of execSync — addonId comes from RPC
|
|
373
371
|
// params and was previously interpolated into a shell command, which
|
|
374
372
|
// is a command-injection vector. argv form doesn't go through a shell
|
|
@@ -469,7 +467,7 @@ function createAgentService(deps) {
|
|
|
469
467
|
*/
|
|
470
468
|
stageDevUploads: {
|
|
471
469
|
handler: async (ctx) => {
|
|
472
|
-
const
|
|
470
|
+
const params = ctx.params;
|
|
473
471
|
const seam = {
|
|
474
472
|
dataDir: deps.dataDir,
|
|
475
473
|
fetchBundle: (s) => (0, fetch_bundle_from_hub_js_1.fetchBundleFromHub)(s),
|
|
@@ -487,7 +485,7 @@ function createAgentService(deps) {
|
|
|
487
485
|
*/
|
|
488
486
|
distributeModel: {
|
|
489
487
|
handler: async (ctx) => {
|
|
490
|
-
const
|
|
488
|
+
const params = ctx.params;
|
|
491
489
|
const { execFile } = await Promise.resolve().then(() => __importStar(require('node:child_process')));
|
|
492
490
|
const { promisify } = await Promise.resolve().then(() => __importStar(require('node:util')));
|
|
493
491
|
const execFileAsync = promisify(execFile);
|
|
@@ -526,12 +524,13 @@ function createAgentService(deps) {
|
|
|
526
524
|
undeploy: {
|
|
527
525
|
handler: async (ctx) => {
|
|
528
526
|
const { params, broker } = ctx;
|
|
527
|
+
const bkr = (0, system_1.asBrokerLike)(broker);
|
|
529
528
|
return (0, agent_undeploy_js_1.undeployFromAgent)({
|
|
530
529
|
addonsDir: deps.addonsDir,
|
|
531
530
|
loadedAddons: deps.loadedAddons,
|
|
532
531
|
bootstrapPackages: deps.bootstrapPackages ?? DEFAULT_AGENT_BOOTSTRAP_PACKAGES,
|
|
533
|
-
listRunnersByAddon: (addonId) =>
|
|
534
|
-
stopRunner: (name) =>
|
|
532
|
+
listRunnersByAddon: (addonId) => bkr.call('$process.listByAddon', { addonId }, { nodeID: broker.nodeID, timeout: AGENT_PROCESS_RESTART_TIMEOUT_MS }),
|
|
533
|
+
stopRunner: (name) => bkr.call('$process.stop', { name }, { nodeID: broker.nodeID, timeout: AGENT_PROCESS_RESTART_TIMEOUT_MS }),
|
|
535
534
|
destroyService: (addonId) => broker.destroyService(addonId),
|
|
536
535
|
unregisterProvider: (capName, addonId) => deps.unregisterProvider?.(capName, addonId),
|
|
537
536
|
respawnSurvivors: deps.reloadDeployedAddons,
|
|
@@ -566,6 +565,7 @@ function createAgentService(deps) {
|
|
|
566
565
|
restart: {
|
|
567
566
|
handler: async (ctx) => {
|
|
568
567
|
const { params, broker } = ctx;
|
|
568
|
+
const bkr = (0, system_1.asBrokerLike)(broker);
|
|
569
569
|
const { addonId } = params;
|
|
570
570
|
// Delegate to THIS agent's own `$process` service — the SAME primitive
|
|
571
571
|
// the hub uses for a local forked addon (AddonRegistryService.restartAddon
|
|
@@ -575,7 +575,7 @@ function createAgentService(deps) {
|
|
|
575
575
|
// Moleculer node evicted, and the addon's caps re-registered — identical
|
|
576
576
|
// semantics to a hub-local restart. Pin to the agent's own node so the
|
|
577
577
|
// call always hits THIS agent's `$process`, never the hub's.
|
|
578
|
-
const result = await
|
|
578
|
+
const result = await bkr.call('$process.restart', { name: addonId }, { nodeID: broker.nodeID, timeout: AGENT_PROCESS_RESTART_TIMEOUT_MS });
|
|
579
579
|
if (!result.success) {
|
|
580
580
|
// Fail loudly so the hub's `nodes.restartAddon` surfaces the real
|
|
581
581
|
// error instead of the old fire-and-forget phantom success.
|
package/dist/agent/main.js
CHANGED
|
@@ -279,8 +279,7 @@ async function startAgent(configPath) {
|
|
|
279
279
|
// Fall through to live registry-derived state (connected/unconfigured/disconnected).
|
|
280
280
|
// getRegistryNodes-equivalent inline: broker.registry.getNodeList
|
|
281
281
|
try {
|
|
282
|
-
const
|
|
283
|
-
const nodes = registry?.getNodeList?.({ onlyAvailable: true }) ?? [];
|
|
282
|
+
const nodes = broker.registry.getNodeList({ onlyAvailable: true });
|
|
284
283
|
if (nodes.some((n) => n.id === 'hub'))
|
|
285
284
|
return 'connected';
|
|
286
285
|
}
|
|
@@ -840,12 +839,20 @@ async function startAgent(configPath) {
|
|
|
840
839
|
// Wired after broker.start() so the broker event bus is operational.
|
|
841
840
|
const agentReadinessRegistry = (0, system_1.getOrInitReadinessRegistry)(broker, agentBrokerEventBus, consoleLogger);
|
|
842
841
|
agentUdsRegistry.onReadinessSnapshotRequest(() => agentReadinessRegistry.getSnapshotForTransport());
|
|
842
|
+
// D8 reverse leg: expose THIS agent's readiness snapshot so the hub can
|
|
843
|
+
// snapshot-reconcile agent-origin records on every (re)connect and
|
|
844
|
+
// periodically. Without this, the hub learns agent readiness ONLY from
|
|
845
|
+
// lossy cross-node `system.ready-state` broadcasts — a lost `ready`
|
|
846
|
+
// (2026-07-17 incident) left the hub's authoritative registry stuck on
|
|
847
|
+
// the synthesized `down` forever. Registered under
|
|
848
|
+
// `$agent-readiness` (NOT `$readiness`) so the agent's own unqualified
|
|
849
|
+
// `$readiness.getSnapshot` hub-hydrate call is never shadowed locally.
|
|
843
850
|
broker.createService((0, system_1.createReadinessServiceForRegistry)(agentReadinessRegistry, system_1.AGENT_READINESS_SERVICE_NAME));
|
|
844
851
|
}
|
|
845
852
|
// ── HTTP server (Fastify) — status API + process management + UI ──
|
|
846
853
|
// Started early (before addon boot) so the status page is reachable
|
|
847
854
|
// even while addons are still loading.
|
|
848
|
-
const getBrokerFn = (
|
|
855
|
+
const getBrokerFn = () => broker;
|
|
849
856
|
// ── Update controls for the agent UI ──────────────────────────────
|
|
850
857
|
// Framework: the LOCAL `AgentUpdateService` — `npm view` + `npm install`
|
|
851
858
|
// straight from the registry, no hub in the path, so an isolated node can
|
|
@@ -488,7 +488,7 @@ function createNodeRootPackageLookup(moleculer, serverUpdate) {
|
|
|
488
488
|
};
|
|
489
489
|
}
|
|
490
490
|
function buildNodesProvider(agentRegistry, moleculer, addonRegistry, getNodeRootPackage, logger) {
|
|
491
|
-
const broker = moleculer.broker;
|
|
491
|
+
const broker = (0, system_1.asBrokerLike)(moleculer.broker);
|
|
492
492
|
return {
|
|
493
493
|
topology: async () => computeTopology(agentRegistry, addonRegistry, getNodeRootPackage, (nodeId) => moleculer.isNodeRegistered(nodeId)),
|
|
494
494
|
directory: async () => agentRegistry.listNodeDirectory(),
|
|
@@ -1462,7 +1462,7 @@ function buildUninstallSeam(ar, ps, broker, agentRegistry) {
|
|
|
1462
1462
|
};
|
|
1463
1463
|
}
|
|
1464
1464
|
function buildAddonsProvider(ar, ps, ls, moleculer, configService, ctx, agentRegistry) {
|
|
1465
|
-
const broker = moleculer.broker;
|
|
1465
|
+
const broker = (0, system_1.asBrokerLike)(moleculer.broker);
|
|
1466
1466
|
const uninstallSeam = buildUninstallSeam(ar, ps, broker, agentRegistry);
|
|
1467
1467
|
const frameworkAllowSet = new Set([addon_package_service_js_1.SYSTEM_PACKAGE]);
|
|
1468
1468
|
// Process-wide runner built once at boot (manual-boot wires its deps from the
|
|
@@ -10868,6 +10868,24 @@ function createCapRouter_storageAdmin(getProvider, createRemoteProxy) {
|
|
|
10868
10868
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
10869
10869
|
return p.adoptRecords(methodInput);
|
|
10870
10870
|
}),
|
|
10871
|
+
readLegacyCollection: trpc_middleware_js_1.adminProcedure
|
|
10872
|
+
.input(types_124.storageAdminCapability.methods.readLegacyCollection.input.loose())
|
|
10873
|
+
.output(types_124.storageAdminCapability.methods.readLegacyCollection.output)
|
|
10874
|
+
.query(async ({ input, ctx }) => {
|
|
10875
|
+
const { nodeId, ...methodInput } = input;
|
|
10876
|
+
const p = resolveProvider('storage-admin', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
10877
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
10878
|
+
return p.readLegacyCollection(methodInput);
|
|
10879
|
+
}),
|
|
10880
|
+
isAdoptedLegacyTable: trpc_middleware_js_1.adminProcedure
|
|
10881
|
+
.input(types_124.storageAdminCapability.methods.isAdoptedLegacyTable.input.loose())
|
|
10882
|
+
.output(types_124.storageAdminCapability.methods.isAdoptedLegacyTable.output)
|
|
10883
|
+
.query(async ({ input, ctx }) => {
|
|
10884
|
+
const { nodeId, ...methodInput } = input;
|
|
10885
|
+
const p = resolveProvider('storage-admin', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
10886
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
10887
|
+
return p.isAdoptedLegacyTable(methodInput);
|
|
10888
|
+
}),
|
|
10871
10889
|
});
|
|
10872
10890
|
}
|
|
10873
10891
|
function createCapRouter_storageEvictable(getProvider, createRemoteProxy) {
|
|
@@ -61,8 +61,12 @@
|
|
|
61
61
|
],
|
|
62
62
|
"lib/webrtc/src/media/rtpSender.js": ["RTP_HISTORY_SIZE = 4096"],
|
|
63
63
|
"lib/rtp/src/rtp/rtp.js": ["camstack: D868 — SRTP output arena"],
|
|
64
|
-
"lib/rtp/src/srtp/cipher/ctr.js": [
|
|
65
|
-
|
|
64
|
+
"lib/rtp/src/srtp/cipher/ctr.js": [
|
|
65
|
+
"camstack: D868 — header, ciphertext and tag in ONE arena slice"
|
|
66
|
+
],
|
|
67
|
+
"lib/rtp/src/srtp/cipher/gcm.js": [
|
|
68
|
+
"camstack: D868 — header, ciphertext and tag in ONE arena slice"
|
|
69
|
+
]
|
|
66
70
|
},
|
|
67
71
|
"declaredBy": [
|
|
68
72
|
"packages/addon-pipeline",
|
|
@@ -53,6 +53,7 @@ const addon_settings_provider_js_1 = require("./addon-settings-provider.js");
|
|
|
53
53
|
const device_meta_mirror_js_1 = require("./device-meta-mirror.js");
|
|
54
54
|
const forked_custom_actions_js_1 = require("./forked-custom-actions.js");
|
|
55
55
|
const integration_visibility_js_1 = require("./integration-visibility.js");
|
|
56
|
+
const record_kinds_js_1 = require("../auth/record-kinds.js");
|
|
56
57
|
const materialize_route_path_js_1 = require("./materialize-route-path.js");
|
|
57
58
|
const package_dir_utils_1 = require("./package-dir-utils");
|
|
58
59
|
const prune_misplaced_addons_js_1 = require("./prune-misplaced-addons.js");
|
|
@@ -96,6 +97,12 @@ function addonUpdatePayload(fromVersion, toVersion) {
|
|
|
96
97
|
toVersion,
|
|
97
98
|
};
|
|
98
99
|
}
|
|
100
|
+
function isAddonDeclaration(value) {
|
|
101
|
+
if (typeof value !== 'object' || value === null)
|
|
102
|
+
return false;
|
|
103
|
+
const id = Reflect.get(value, 'id');
|
|
104
|
+
return typeof id === 'string' && id.length > 0;
|
|
105
|
+
}
|
|
99
106
|
/**
|
|
100
107
|
* Type predicate: true when an `ISettingsBackend` also satisfies the
|
|
101
108
|
* sync `ISettingsStore` contract (system/addon/device key-value ops)
|
|
@@ -360,6 +367,9 @@ class AddonRegistryService {
|
|
|
360
367
|
activeStorageProvider = null;
|
|
361
368
|
activeSettingsBackend = null;
|
|
362
369
|
integrationRegistry = null;
|
|
370
|
+
clusterNodeHistoryStore = null;
|
|
371
|
+
clusterAgentRecords = null;
|
|
372
|
+
shareTokenService = null;
|
|
363
373
|
// Device architecture — in-memory registry of live IDevice instances.
|
|
364
374
|
// Persistence is owned by the `device-manager` capability addon.
|
|
365
375
|
deviceRegistry = new system_1.DeviceRegistry();
|
|
@@ -375,7 +385,7 @@ class AddonRegistryService {
|
|
|
375
385
|
* `this.broker.call(...)` / `this.broker.nodeID` lint clean.
|
|
376
386
|
*/
|
|
377
387
|
get broker() {
|
|
378
|
-
return this.moleculer.broker;
|
|
388
|
+
return (0, system_1.asBrokerLike)(this.moleculer.broker);
|
|
379
389
|
}
|
|
380
390
|
constructor(loggingService, eventBusService, configService, storageService, capabilityService, moleculer, streamProbe) {
|
|
381
391
|
this.loggingService = loggingService;
|
|
@@ -1334,6 +1344,43 @@ class AddonRegistryService {
|
|
|
1334
1344
|
loadCollectionPreferences() {
|
|
1335
1345
|
// TODO: implement CapabilityRegistry.loadDisabledProviders() to restore persisted preferences
|
|
1336
1346
|
}
|
|
1347
|
+
/** Durable cluster-node history (A1) — bind records kind after orphan declare. */
|
|
1348
|
+
setClusterNodeHistoryStore(store) {
|
|
1349
|
+
this.clusterNodeHistoryStore = store;
|
|
1350
|
+
this.bindClusterNodeHistoryRecordsKind();
|
|
1351
|
+
}
|
|
1352
|
+
bindClusterNodeHistoryRecordsKind() {
|
|
1353
|
+
const clusterStore = this.clusterNodeHistoryStore;
|
|
1354
|
+
const records = this.clusterAgentRecords;
|
|
1355
|
+
if (clusterStore === null || records === null)
|
|
1356
|
+
return;
|
|
1357
|
+
const kind = records.kind(system_1.clusterNodeHistoryRecordKind);
|
|
1358
|
+
clusterStore.bindRecordsKind(() => kind);
|
|
1359
|
+
}
|
|
1360
|
+
setShareTokenService(service) {
|
|
1361
|
+
this.shareTokenService = service;
|
|
1362
|
+
}
|
|
1363
|
+
async wireHubMainOrphanRecords(store) {
|
|
1364
|
+
const recordsCap = this.capabilityRegistry.getSingleton('records');
|
|
1365
|
+
if (recordsCap === null || recordsCap === undefined)
|
|
1366
|
+
return;
|
|
1367
|
+
const integrationRecords = this.recordsForOwner('integration-registry', this.secretsForOwner('integration-registry'));
|
|
1368
|
+
await integrationRecords.declare(system_1.integrationRegistryRecordKinds);
|
|
1369
|
+
this.integrationRegistry = new system_1.IntegrationRegistry(store, system_1.IntegrationRegistry.recordStoresOf(integrationRecords));
|
|
1370
|
+
await this.integrationRegistry.initialize();
|
|
1371
|
+
this.logger.info('IntegrationRegistry initialized (records)', { meta: { phase: 'v2' } });
|
|
1372
|
+
const clusterRecords = this.recordsForOwner('cluster-agent', this.secretsForOwner('cluster-agent'));
|
|
1373
|
+
await clusterRecords.declare(system_1.clusterAgentRecordKinds);
|
|
1374
|
+
this.clusterAgentRecords = clusterRecords;
|
|
1375
|
+
this.bindClusterNodeHistoryRecordsKind();
|
|
1376
|
+
const shareTokens = this.shareTokenService;
|
|
1377
|
+
if (shareTokens !== null) {
|
|
1378
|
+
const coreRecords = this.recordsForOwner('core-auth', this.secretsForOwner('core-auth'));
|
|
1379
|
+
await coreRecords.declare(record_kinds_js_1.coreAuthRecordKinds);
|
|
1380
|
+
const shareKind = coreRecords.kind(record_kinds_js_1.coreAuthRecordKinds['share-view-tokens']);
|
|
1381
|
+
shareTokens.bindShareTokensKind(() => shareKind);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1337
1384
|
/**
|
|
1338
1385
|
* Returns the IntegrationRegistry filtered to exclude orphaned integrations
|
|
1339
1386
|
* (where the addon is not currently installed). Orphaned data stays in DB
|
|
@@ -2210,11 +2257,12 @@ class AddonRegistryService {
|
|
|
2210
2257
|
...(bundleRaw.icon !== undefined ? { icon: bundleRaw.icon } : {}),
|
|
2211
2258
|
}
|
|
2212
2259
|
: undefined;
|
|
2213
|
-
for (const
|
|
2214
|
-
|
|
2215
|
-
const name = typeof decl['name'] === 'string' ? decl['name'] : id;
|
|
2216
|
-
if (!id)
|
|
2260
|
+
for (const rawDecl of addons) {
|
|
2261
|
+
if (!isAddonDeclaration(rawDecl))
|
|
2217
2262
|
continue;
|
|
2263
|
+
const decl = rawDecl;
|
|
2264
|
+
const id = decl.id;
|
|
2265
|
+
const name = typeof decl.name === 'string' ? decl.name : id;
|
|
2218
2266
|
const manifest = {
|
|
2219
2267
|
...decl,
|
|
2220
2268
|
id,
|
|
@@ -2550,9 +2598,16 @@ class AddonRegistryService {
|
|
|
2550
2598
|
registry: this.capabilityRegistry,
|
|
2551
2599
|
logger: this.logger,
|
|
2552
2600
|
}, store);
|
|
2553
|
-
this.
|
|
2554
|
-
|
|
2555
|
-
|
|
2601
|
+
void this.wireHubMainOrphanRecords(store).catch((err) => {
|
|
2602
|
+
this.logger.warn('hub-main records wiring failed (best-effort)', {
|
|
2603
|
+
meta: { error: (0, types_1.errMsg)(err) },
|
|
2604
|
+
});
|
|
2605
|
+
this.integrationRegistry = new system_1.IntegrationRegistry(store);
|
|
2606
|
+
void this.integrationRegistry.initialize().then(() => {
|
|
2607
|
+
this.logger.info('IntegrationRegistry initialized (legacy)', {
|
|
2608
|
+
meta: { phase: 'v2' },
|
|
2609
|
+
});
|
|
2610
|
+
});
|
|
2556
2611
|
});
|
|
2557
2612
|
this.loadCollectionPreferences();
|
|
2558
2613
|
// DeviceStore/ConfigStore are owned by the `device-persistence`
|
|
@@ -526,7 +526,7 @@ class AgentRegistryService {
|
|
|
526
526
|
// connected when the listener registers. Classifies each as
|
|
527
527
|
// agent or worker — same logic as the live handlers above.
|
|
528
528
|
try {
|
|
529
|
-
const registry = this.
|
|
529
|
+
const registry = this.nodeCatalogRegistry();
|
|
530
530
|
const existing = registry?.getNodeList?.({ onlyAvailable: true }) ?? [];
|
|
531
531
|
for (const { id } of existing) {
|
|
532
532
|
const kind = classifyNode(id);
|
|
@@ -571,7 +571,7 @@ class AgentRegistryService {
|
|
|
571
571
|
* must not abort the pass or hub boot.
|
|
572
572
|
*/
|
|
573
573
|
async reconcileConnectedAgents() {
|
|
574
|
-
const registry = this.
|
|
574
|
+
const registry = this.nodeCatalogRegistry();
|
|
575
575
|
const nodes = registry?.getNodeList?.({ onlyAvailable: true }) ?? [];
|
|
576
576
|
const agentIds = nodes
|
|
577
577
|
.map((n) => n.id)
|
|
@@ -968,7 +968,7 @@ class AgentRegistryService {
|
|
|
968
968
|
return (0, node_directory_1.toNodeDirectory)(this.listNodeLiveness(), history);
|
|
969
969
|
}
|
|
970
970
|
async listNodes() {
|
|
971
|
-
const registry = this.
|
|
971
|
+
const registry = this.nodeCatalogRegistry();
|
|
972
972
|
// `onlyAvailable: false` intentionally includes nodes Moleculer still
|
|
973
973
|
// remembers as disconnected (`available: false`) — the same registry
|
|
974
974
|
// read `listNodeLiveness()` uses for `/health`. This surfaces recently
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ClusterNodeHistoryStore = void 0;
|
|
4
|
+
const system_1 = require("@camstack/system");
|
|
4
5
|
const zod_1 = require("zod");
|
|
5
6
|
const types_1 = require("@camstack/types");
|
|
6
7
|
const DescriptorSchema = zod_1.z.object({
|
|
@@ -83,9 +84,15 @@ class ClusterNodeHistoryStore {
|
|
|
83
84
|
resolveSettingsStore;
|
|
84
85
|
logger;
|
|
85
86
|
declared = false;
|
|
86
|
-
|
|
87
|
+
resolveKind;
|
|
88
|
+
constructor(resolveSettingsStore, logger, resolveKind = () => null) {
|
|
87
89
|
this.resolveSettingsStore = resolveSettingsStore;
|
|
88
90
|
this.logger = logger;
|
|
91
|
+
this.resolveKind = resolveKind;
|
|
92
|
+
}
|
|
93
|
+
/** Hub wiring after `records.forOwner('cluster-agent').declare(...)`. */
|
|
94
|
+
bindRecordsKind(resolver) {
|
|
95
|
+
this.resolveKind = resolver;
|
|
89
96
|
}
|
|
90
97
|
/**
|
|
91
98
|
* Idempotently declare the typed collection. Returns the resolved provider
|
|
@@ -93,6 +100,8 @@ class ClusterNodeHistoryStore {
|
|
|
93
100
|
* unreachable) — callers treat null as "skip, best-effort".
|
|
94
101
|
*/
|
|
95
102
|
async ensureDeclared() {
|
|
103
|
+
if (this.resolveKind() !== null)
|
|
104
|
+
return this.resolveSettingsStore();
|
|
96
105
|
const store = this.resolveSettingsStore();
|
|
97
106
|
if (!store)
|
|
98
107
|
return null;
|
|
@@ -143,16 +152,12 @@ class ClusterNodeHistoryStore {
|
|
|
143
152
|
// Carry the package roster forward. `listNodes()` does not resolve it
|
|
144
153
|
// (only the reconcile does), so writing this descriptor blind would erase
|
|
145
154
|
// the back-fill's intended set on the very next topology poll.
|
|
146
|
-
const existing = await this.
|
|
155
|
+
const existing = await this.readStored(descriptor.id);
|
|
147
156
|
const packages = descriptor.packages ?? existing?.descriptor.packages;
|
|
148
157
|
const next = packages === undefined ? descriptor : { ...descriptor, packages };
|
|
149
158
|
if (existing !== null && !this.worthWriting(existing, next))
|
|
150
159
|
return;
|
|
151
|
-
await
|
|
152
|
-
collection: COLLECTION,
|
|
153
|
-
key: descriptor.id,
|
|
154
|
-
value: { lastActive: Date.now(), descriptor: next },
|
|
155
|
-
});
|
|
160
|
+
await this.writeStored(descriptor.id, Date.now(), next);
|
|
156
161
|
}
|
|
157
162
|
catch (err) {
|
|
158
163
|
this.logger.warn('snapshot upsert failed (best-effort)', {
|
|
@@ -172,13 +177,48 @@ class ClusterNodeHistoryStore {
|
|
|
172
177
|
return !descriptorsEqual(existing.descriptor, next);
|
|
173
178
|
}
|
|
174
179
|
/** Read one node's row, validated. `null` when absent or malformed. */
|
|
175
|
-
async
|
|
180
|
+
async readStored(nodeId) {
|
|
181
|
+
const kind = this.resolveKind();
|
|
182
|
+
if (kind !== null) {
|
|
183
|
+
const row = await kind.get(nodeId);
|
|
184
|
+
if (row === null)
|
|
185
|
+
return null;
|
|
186
|
+
const parsed = RowDataSchema.safeParse({
|
|
187
|
+
lastActive: row.at ?? 0,
|
|
188
|
+
descriptor: row.data.descriptor,
|
|
189
|
+
});
|
|
190
|
+
return parsed.success ? parsed.data : null;
|
|
191
|
+
}
|
|
192
|
+
const store = this.resolveSettingsStore();
|
|
193
|
+
if (!store)
|
|
194
|
+
return null;
|
|
195
|
+
return this.readRowLegacy(store, nodeId);
|
|
196
|
+
}
|
|
197
|
+
async readRowLegacy(store, nodeId) {
|
|
176
198
|
const raw = await store.get({ collection: COLLECTION, key: nodeId });
|
|
177
199
|
if (raw === null || raw === undefined)
|
|
178
200
|
return null;
|
|
179
201
|
const parsed = RowDataSchema.safeParse(raw);
|
|
180
202
|
return parsed.success ? parsed.data : null;
|
|
181
203
|
}
|
|
204
|
+
async writeStored(nodeId, lastActive, descriptor) {
|
|
205
|
+
const kind = this.resolveKind();
|
|
206
|
+
if (kind !== null) {
|
|
207
|
+
const body = system_1.ClusterNodeHistoryBodySchema.safeParse({ descriptor });
|
|
208
|
+
if (!body.success)
|
|
209
|
+
return;
|
|
210
|
+
await kind.put({ id: nodeId, at: lastActive, data: body.data });
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const store = this.resolveSettingsStore();
|
|
214
|
+
if (!store)
|
|
215
|
+
return;
|
|
216
|
+
await store.set({
|
|
217
|
+
collection: COLLECTION,
|
|
218
|
+
key: nodeId,
|
|
219
|
+
value: { lastActive, descriptor },
|
|
220
|
+
});
|
|
221
|
+
}
|
|
182
222
|
/** A placeholder descriptor for a node the topology pass has not seen yet. */
|
|
183
223
|
static placeholderDescriptor(nodeId) {
|
|
184
224
|
return {
|
|
@@ -206,7 +246,7 @@ class ClusterNodeHistoryStore {
|
|
|
206
246
|
if (!store)
|
|
207
247
|
return [];
|
|
208
248
|
try {
|
|
209
|
-
return (await this.
|
|
249
|
+
return (await this.readStored(nodeId))?.descriptor.packages ?? [];
|
|
210
250
|
}
|
|
211
251
|
catch (err) {
|
|
212
252
|
this.logger.warn('getPackages failed (best-effort)', {
|
|
@@ -231,7 +271,7 @@ class ClusterNodeHistoryStore {
|
|
|
231
271
|
if (!store)
|
|
232
272
|
return;
|
|
233
273
|
try {
|
|
234
|
-
const existing = await this.
|
|
274
|
+
const existing = await this.readStored(nodeId);
|
|
235
275
|
const merged = new Map((existing?.descriptor.packages ?? []).map((p) => [p.name, p]));
|
|
236
276
|
// The node's current declaration wins on version — it is the authority on
|
|
237
277
|
// what it is actually running.
|
|
@@ -241,11 +281,7 @@ class ClusterNodeHistoryStore {
|
|
|
241
281
|
...(existing?.descriptor ?? ClusterNodeHistoryStore.placeholderDescriptor(nodeId)),
|
|
242
282
|
packages: [...merged.values()],
|
|
243
283
|
};
|
|
244
|
-
await
|
|
245
|
-
collection: COLLECTION,
|
|
246
|
-
key: nodeId,
|
|
247
|
-
value: { lastActive: existing?.lastActive ?? Date.now(), descriptor },
|
|
248
|
-
});
|
|
284
|
+
await this.writeStored(nodeId, existing?.lastActive ?? Date.now(), descriptor);
|
|
249
285
|
}
|
|
250
286
|
catch (err) {
|
|
251
287
|
this.logger.warn('recordPackages failed (best-effort)', {
|
|
@@ -266,7 +302,7 @@ class ClusterNodeHistoryStore {
|
|
|
266
302
|
if (!store)
|
|
267
303
|
return;
|
|
268
304
|
try {
|
|
269
|
-
const existing = await this.
|
|
305
|
+
const existing = await this.readStored(nodeId);
|
|
270
306
|
if (existing === null)
|
|
271
307
|
return;
|
|
272
308
|
const current = existing.descriptor.packages;
|
|
@@ -276,13 +312,9 @@ class ClusterNodeHistoryStore {
|
|
|
276
312
|
const remaining = current.filter((p) => !drop.has(p.name));
|
|
277
313
|
if (remaining.length === current.length)
|
|
278
314
|
return;
|
|
279
|
-
await
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
value: {
|
|
283
|
-
lastActive: existing.lastActive,
|
|
284
|
-
descriptor: { ...existing.descriptor, packages: remaining },
|
|
285
|
-
},
|
|
315
|
+
await this.writeStored(nodeId, existing.lastActive, {
|
|
316
|
+
...existing.descriptor,
|
|
317
|
+
packages: remaining,
|
|
286
318
|
});
|
|
287
319
|
this.logger.info('pruned node package record', {
|
|
288
320
|
tags: { nodeId },
|
|
@@ -306,6 +338,18 @@ class ClusterNodeHistoryStore {
|
|
|
306
338
|
if (!store)
|
|
307
339
|
return;
|
|
308
340
|
try {
|
|
341
|
+
const existing = await this.readStored(nodeId);
|
|
342
|
+
if (existing === null)
|
|
343
|
+
return;
|
|
344
|
+
const kind = this.resolveKind();
|
|
345
|
+
if (kind !== null) {
|
|
346
|
+
await kind.put({
|
|
347
|
+
id: nodeId,
|
|
348
|
+
at: Date.now(),
|
|
349
|
+
data: { descriptor: existing.descriptor },
|
|
350
|
+
});
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
309
353
|
await store.update({
|
|
310
354
|
collection: COLLECTION,
|
|
311
355
|
id: nodeId,
|
|
@@ -335,8 +379,17 @@ class ClusterNodeHistoryStore {
|
|
|
335
379
|
if (!store)
|
|
336
380
|
return null;
|
|
337
381
|
try {
|
|
338
|
-
const
|
|
382
|
+
const kind = this.resolveKind();
|
|
339
383
|
const result = [];
|
|
384
|
+
const rows = kind !== null
|
|
385
|
+
? (await (0, system_1.readAllRows)(kind)).map((row) => ({
|
|
386
|
+
id: row.id,
|
|
387
|
+
data: { lastActive: row.at ?? 0, descriptor: row.data.descriptor },
|
|
388
|
+
}))
|
|
389
|
+
: (await store.query({ collection: COLLECTION })).map((row) => ({
|
|
390
|
+
id: row.id,
|
|
391
|
+
data: row.data,
|
|
392
|
+
}));
|
|
340
393
|
for (const row of rows) {
|
|
341
394
|
const parsed = RowDataSchema.safeParse(row.data);
|
|
342
395
|
if (!parsed.success) {
|
|
@@ -370,8 +423,14 @@ class ClusterNodeHistoryStore {
|
|
|
370
423
|
// Read first: `delete` on this provider is idempotent and cannot tell a
|
|
371
424
|
// removal from a no-op, and "there was nothing to remove" is the answer
|
|
372
425
|
// that stops an operator repeating a working action forever.
|
|
373
|
-
const existed = (await this.
|
|
374
|
-
|
|
426
|
+
const existed = (await this.readStored(nodeId)) !== null;
|
|
427
|
+
const kind = this.resolveKind();
|
|
428
|
+
if (kind !== null) {
|
|
429
|
+
await kind.delete(nodeId);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
await store.delete({ collection: COLLECTION, key: nodeId });
|
|
433
|
+
}
|
|
375
434
|
if (!existed) {
|
|
376
435
|
this.logger.info('cluster node had no history row to forget', { tags: { nodeId } });
|
|
377
436
|
return 'not-found';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.coreAuthRecordKinds = exports.SHARE_VIEW_TOKENS_LEGACY = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Record kinds owned by `core-auth` (hub-resident, phase 6 / §2.8).
|
|
6
|
+
*/
|
|
7
|
+
const types_1 = require("@camstack/types");
|
|
8
|
+
const zod_1 = require("zod");
|
|
9
|
+
const ShareTokenScopeSchema = zod_1.z.object({
|
|
10
|
+
kind: zod_1.z.enum(types_1.SHARE_VIEW_KINDS),
|
|
11
|
+
deviceIds: zod_1.z.array(zod_1.z.number().int().nonnegative()).min(1).max(64),
|
|
12
|
+
talk: zod_1.z.boolean().optional(),
|
|
13
|
+
});
|
|
14
|
+
exports.SHARE_VIEW_TOKENS_LEGACY = 'share_view_tokens';
|
|
15
|
+
const ShareViewTokenBodySchema = zod_1.z.object({
|
|
16
|
+
userId: zod_1.z.string(),
|
|
17
|
+
tokenHash: zod_1.z.string(),
|
|
18
|
+
tokenPrefix: zod_1.z.string(),
|
|
19
|
+
scope: ShareTokenScopeSchema,
|
|
20
|
+
createdAt: zod_1.z.number(),
|
|
21
|
+
expiresAt: zod_1.z.number().nullable(),
|
|
22
|
+
lastUsedAt: zod_1.z.number().nullish(),
|
|
23
|
+
});
|
|
24
|
+
exports.coreAuthRecordKinds = (0, types_1.defineRecordKinds)({
|
|
25
|
+
'share-view-tokens': {
|
|
26
|
+
schema: ShareViewTokenBodySchema,
|
|
27
|
+
class: 'config',
|
|
28
|
+
writeTrigger: 'operator mints a share link; validate touches lastUsedAt',
|
|
29
|
+
device: 'none',
|
|
30
|
+
at: 'none',
|
|
31
|
+
indexes: [
|
|
32
|
+
{ name: 'hash', fields: ['tokenHash'], unique: true },
|
|
33
|
+
{ name: 'user', fields: ['userId'] },
|
|
34
|
+
],
|
|
35
|
+
retention: { none: 'revoke only; expiry enforced at validate' },
|
|
36
|
+
adoptsFrom: exports.SHARE_VIEW_TOKENS_LEGACY,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -61,6 +61,7 @@ exports.ShareTokenService = exports.ShareTokenRecordSchema = exports.ShareTokenS
|
|
|
61
61
|
const crypto = __importStar(require("node:crypto"));
|
|
62
62
|
const zod_1 = require("zod");
|
|
63
63
|
const types_1 = require("@camstack/types");
|
|
64
|
+
const record_kinds_js_1 = require("./record-kinds.js");
|
|
64
65
|
/**
|
|
65
66
|
* @durable class=config owner=core-auth
|
|
66
67
|
* write="a user mints a share link; and a best-effort lastUsedAt touch on every successful validation"
|
|
@@ -153,6 +154,29 @@ function parseShareToken(data) {
|
|
|
153
154
|
const parsed = exports.ShareTokenRecordSchema.safeParse(data);
|
|
154
155
|
return parsed.success ? parsed.data : null;
|
|
155
156
|
}
|
|
157
|
+
function recordToBody(record) {
|
|
158
|
+
const { id: _id, ...body } = record;
|
|
159
|
+
return body;
|
|
160
|
+
}
|
|
161
|
+
function rowToRecord(id, body) {
|
|
162
|
+
return exports.ShareTokenRecordSchema.safeParse({ id, ...body }).data ?? null;
|
|
163
|
+
}
|
|
164
|
+
async function readAllShareTokenRows(kind, where) {
|
|
165
|
+
const out = [];
|
|
166
|
+
let cursor;
|
|
167
|
+
for (let page = 0; page < 100; page += 1) {
|
|
168
|
+
const answer = await kind.query({
|
|
169
|
+
...(where === undefined ? {} : { where }),
|
|
170
|
+
limit: 1000,
|
|
171
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
172
|
+
});
|
|
173
|
+
out.push(...answer.records);
|
|
174
|
+
if (answer.nextCursor === null)
|
|
175
|
+
return out;
|
|
176
|
+
cursor = answer.nextCursor;
|
|
177
|
+
}
|
|
178
|
+
throw new Error('share-view-tokens: row count exceeds pagination bound');
|
|
179
|
+
}
|
|
156
180
|
class ShareTokenService {
|
|
157
181
|
getStore;
|
|
158
182
|
logger;
|
|
@@ -162,9 +186,59 @@ class ShareTokenService {
|
|
|
162
186
|
* the sqlite builtin registers after this service is constructed).
|
|
163
187
|
*/
|
|
164
188
|
collectionDeclared = false;
|
|
165
|
-
|
|
189
|
+
resolveKind;
|
|
190
|
+
constructor(getStore, logger = null, resolveKind = () => null) {
|
|
166
191
|
this.getStore = getStore;
|
|
167
192
|
this.logger = logger;
|
|
193
|
+
this.resolveKind = resolveKind;
|
|
194
|
+
}
|
|
195
|
+
/** Hub wiring after `records.forOwner('core-auth').declare(...)`. */
|
|
196
|
+
bindShareTokensKind(resolver) {
|
|
197
|
+
this.resolveKind = resolver;
|
|
198
|
+
this.collectionDeclared = true;
|
|
199
|
+
}
|
|
200
|
+
kind() {
|
|
201
|
+
return this.resolveKind();
|
|
202
|
+
}
|
|
203
|
+
async findById(id) {
|
|
204
|
+
const kind = this.kind();
|
|
205
|
+
if (kind !== null) {
|
|
206
|
+
const row = await kind.get(id);
|
|
207
|
+
return row === null ? null : rowToRecord(row.id, row.data);
|
|
208
|
+
}
|
|
209
|
+
const results = await (await this.store()).query({
|
|
210
|
+
collection: SHARE_TOKENS_COLLECTION,
|
|
211
|
+
filter: { where: { id } },
|
|
212
|
+
});
|
|
213
|
+
const first = results[0];
|
|
214
|
+
return first === undefined ? null : parseShareToken(first.data);
|
|
215
|
+
}
|
|
216
|
+
async persistRecord(record) {
|
|
217
|
+
const kind = this.kind();
|
|
218
|
+
if (kind !== null) {
|
|
219
|
+
const parsed = record_kinds_js_1.coreAuthRecordKinds['share-view-tokens'].schema.safeParse(recordToBody(record));
|
|
220
|
+
if (!parsed.success)
|
|
221
|
+
throw new Error(parsed.error.message);
|
|
222
|
+
await kind.put({
|
|
223
|
+
id: record.id,
|
|
224
|
+
expiresAt: record.expiresAt ?? undefined,
|
|
225
|
+
data: parsed.data,
|
|
226
|
+
});
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
await (await this.store()).update({
|
|
230
|
+
collection: SHARE_TOKENS_COLLECTION,
|
|
231
|
+
id: record.id,
|
|
232
|
+
data: { ...record },
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
async deleteById(id) {
|
|
236
|
+
const kind = this.kind();
|
|
237
|
+
if (kind !== null) {
|
|
238
|
+
await kind.delete(id);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
await (await this.store()).delete({ collection: SHARE_TOKENS_COLLECTION, key: id });
|
|
168
242
|
}
|
|
169
243
|
/**
|
|
170
244
|
* Resolve the backend AND ensure `share_view_tokens` is declared before
|
|
@@ -174,6 +248,13 @@ class ShareTokenService {
|
|
|
174
248
|
* rows keep working with no migration.
|
|
175
249
|
*/
|
|
176
250
|
async store() {
|
|
251
|
+
if (this.kind() !== null) {
|
|
252
|
+
const store = this.getStore();
|
|
253
|
+
if (!store) {
|
|
254
|
+
throw new Error('Share tokens unavailable — settings backend not ready');
|
|
255
|
+
}
|
|
256
|
+
return store;
|
|
257
|
+
}
|
|
177
258
|
const store = this.getStore();
|
|
178
259
|
if (!store) {
|
|
179
260
|
throw new Error('Share tokens unavailable — settings backend not ready');
|
|
@@ -219,10 +300,23 @@ class ShareTokenService {
|
|
|
219
300
|
createdAt: now,
|
|
220
301
|
expiresAt: ttlSec === 'never' ? null : now + ttlSec * 1000,
|
|
221
302
|
};
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
303
|
+
const kind = this.kind();
|
|
304
|
+
if (kind !== null) {
|
|
305
|
+
const parsed = record_kinds_js_1.coreAuthRecordKinds['share-view-tokens'].schema.safeParse(recordToBody(record));
|
|
306
|
+
if (!parsed.success)
|
|
307
|
+
throw new Error(parsed.error.message);
|
|
308
|
+
await kind.put({
|
|
309
|
+
id: record.id,
|
|
310
|
+
expiresAt: record.expiresAt ?? undefined,
|
|
311
|
+
data: parsed.data,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
await (await this.store()).insert({
|
|
316
|
+
collection: SHARE_TOKENS_COLLECTION,
|
|
317
|
+
record: { id: record.id, data: { ...record } },
|
|
318
|
+
});
|
|
319
|
+
}
|
|
226
320
|
this.logger?.info('Share token minted', {
|
|
227
321
|
meta: {
|
|
228
322
|
id: record.id,
|
|
@@ -243,15 +337,22 @@ class ShareTokenService {
|
|
|
243
337
|
if (!rawToken.startsWith(exports.SHARE_TOKEN_PREFIX))
|
|
244
338
|
return null;
|
|
245
339
|
const tokenHash = crypto.createHash('sha256').update(rawToken).digest('hex');
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
340
|
+
const kind = this.kind();
|
|
341
|
+
let record = null;
|
|
342
|
+
if (kind !== null) {
|
|
343
|
+
const page = await kind.query({ where: { tokenHash }, limit: 1 });
|
|
344
|
+
const row = page.records[0];
|
|
345
|
+
record = row === undefined ? null : rowToRecord(row.id, row.data);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
const results = await (await this.store()).query({
|
|
349
|
+
collection: SHARE_TOKENS_COLLECTION,
|
|
350
|
+
filter: { where: { tokenHash } },
|
|
351
|
+
});
|
|
352
|
+
const first = results[0];
|
|
353
|
+
record = first === undefined ? null : parseShareToken(first.data);
|
|
354
|
+
}
|
|
355
|
+
if (record === null)
|
|
255
356
|
return null;
|
|
256
357
|
// `expiresAt: null` = never expires (explicit mint-time choice);
|
|
257
358
|
// revocation remains the kill switch for those.
|
|
@@ -275,16 +376,8 @@ class ShareTokenService {
|
|
|
275
376
|
throw new Error(`Share token TTL out of bounds — got ${input.ttlSec}s, allowed ` +
|
|
276
377
|
`[${exports.SHARE_TOKEN_TTL_MIN_SEC}s, ${exports.SHARE_TOKEN_TTL_MAX_SEC}s]`);
|
|
277
378
|
}
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
filter: { where: { id: input.id } },
|
|
281
|
-
});
|
|
282
|
-
const first = results[0];
|
|
283
|
-
if (!first) {
|
|
284
|
-
throw (0, types_1.userError)('auth.share-token-not-found', { id: input.id }, `Share token not found: ${input.id}`);
|
|
285
|
-
}
|
|
286
|
-
const record = parseShareToken(first.data);
|
|
287
|
-
if (!record) {
|
|
379
|
+
const record = await this.findById(input.id);
|
|
380
|
+
if (record === null) {
|
|
288
381
|
throw (0, types_1.userError)('auth.share-token-not-found', { id: input.id }, `Share token not found: ${input.id}`);
|
|
289
382
|
}
|
|
290
383
|
if (!input.callerIsAdmin && record.userId !== input.callerUserId) {
|
|
@@ -301,11 +394,7 @@ class ShareTokenService {
|
|
|
301
394
|
...record,
|
|
302
395
|
expiresAt: now + input.ttlSec * 1000,
|
|
303
396
|
};
|
|
304
|
-
await
|
|
305
|
-
collection: SHARE_TOKENS_COLLECTION,
|
|
306
|
-
id: record.id,
|
|
307
|
-
data: { ...updated },
|
|
308
|
-
});
|
|
397
|
+
await this.persistRecord(updated);
|
|
309
398
|
this.logger?.info('Share token extended', {
|
|
310
399
|
meta: {
|
|
311
400
|
id: updated.id,
|
|
@@ -321,23 +410,13 @@ class ShareTokenService {
|
|
|
321
410
|
* permission mismatch so the router surfaces a FORBIDDEN.
|
|
322
411
|
*/
|
|
323
412
|
async revoke(input) {
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
filter: { where: { id: input.id } },
|
|
327
|
-
});
|
|
328
|
-
const first = results[0];
|
|
329
|
-
if (!first)
|
|
413
|
+
const record = await this.findById(input.id);
|
|
414
|
+
if (record === null)
|
|
330
415
|
return false;
|
|
331
|
-
const record = parseShareToken(first.data);
|
|
332
|
-
if (!record) {
|
|
333
|
-
// Corrupt record — delete it regardless (it can never validate).
|
|
334
|
-
await (await this.store()).delete({ collection: SHARE_TOKENS_COLLECTION, key: input.id });
|
|
335
|
-
return true;
|
|
336
|
-
}
|
|
337
416
|
if (!input.callerIsAdmin && record.userId !== input.callerUserId) {
|
|
338
417
|
throw (0, types_1.userError)('auth.share-owner-only-revoke', undefined, 'Only the token owner or an admin can revoke a share token');
|
|
339
418
|
}
|
|
340
|
-
await
|
|
419
|
+
await this.deleteById(input.id);
|
|
341
420
|
this.logger?.info('Share token revoked', {
|
|
342
421
|
meta: { id: record.id, byUserId: input.callerUserId },
|
|
343
422
|
});
|
|
@@ -345,6 +424,13 @@ class ShareTokenService {
|
|
|
345
424
|
}
|
|
346
425
|
/** Every share token minted by `userId`, expired ones included. */
|
|
347
426
|
async listForUser(userId) {
|
|
427
|
+
const kind = this.kind();
|
|
428
|
+
if (kind !== null) {
|
|
429
|
+
const rows = await readAllShareTokenRows(kind, { userId });
|
|
430
|
+
return rows
|
|
431
|
+
.map((row) => rowToRecord(row.id, row.data))
|
|
432
|
+
.filter((r) => r !== null);
|
|
433
|
+
}
|
|
348
434
|
const results = await (await this.store()).query({
|
|
349
435
|
collection: SHARE_TOKENS_COLLECTION,
|
|
350
436
|
filter: { where: { userId } },
|
|
@@ -353,6 +439,13 @@ class ShareTokenService {
|
|
|
353
439
|
}
|
|
354
440
|
/** All share tokens (admin listing). */
|
|
355
441
|
async listAll() {
|
|
442
|
+
const kind = this.kind();
|
|
443
|
+
if (kind !== null) {
|
|
444
|
+
const rows = await readAllShareTokenRows(kind);
|
|
445
|
+
return rows
|
|
446
|
+
.map((row) => rowToRecord(row.id, row.data))
|
|
447
|
+
.filter((r) => r !== null);
|
|
448
|
+
}
|
|
356
449
|
const results = await (await this.store()).query({
|
|
357
450
|
collection: SHARE_TOKENS_COLLECTION,
|
|
358
451
|
filter: {},
|
|
@@ -360,11 +453,7 @@ class ShareTokenService {
|
|
|
360
453
|
return results.map((r) => parseShareToken(r.data)).filter((r) => r !== null);
|
|
361
454
|
}
|
|
362
455
|
async touchLastUsed(record) {
|
|
363
|
-
await (
|
|
364
|
-
collection: SHARE_TOKENS_COLLECTION,
|
|
365
|
-
id: record.id,
|
|
366
|
-
data: { ...record, lastUsedAt: Date.now() },
|
|
367
|
-
});
|
|
456
|
+
await this.persistRecord({ ...record, lastUsedAt: Date.now() });
|
|
368
457
|
}
|
|
369
458
|
}
|
|
370
459
|
exports.ShareTokenService = ShareTokenService;
|
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildCapCallFn = buildCapCallFn;
|
|
4
|
+
/**
|
|
5
|
+
* cap-call-fn — the per-(cap, node) dispatcher behind every CapabilityRegistry
|
|
6
|
+
* provider proxy the hub builds in `applyNodeManifest`.
|
|
7
|
+
*
|
|
8
|
+
* Extracted from `MoleculerService` so its routing branches are unit-testable
|
|
9
|
+
* in isolation (the closure that lived inline could only be exercised by
|
|
10
|
+
* standing up a full broker). It closes a UDS-migration gap: the inline
|
|
11
|
+
* version hand-rolled a `broker.call` for any non-hub-local provider, so an
|
|
12
|
+
* AGENT-hosted addon cap (a UDS child of the agent, NOT a Moleculer service)
|
|
13
|
+
* resolved to a Moleculer node that does not exist → `waitForServices` waited
|
|
14
|
+
* its full 30s discovery timeout → "Services waiting is timed out". The fix
|
|
15
|
+
* routes everything that isn't a hub-local child through the unified
|
|
16
|
+
* `CapRouteResolver`, which classifies an agent node as `agent-child-forward`
|
|
17
|
+
* (hub → agent over Moleculer → agent's UDS child) and a direct remote as
|
|
18
|
+
* `remote-moleculer`.
|
|
19
|
+
*/
|
|
20
|
+
const types_1 = require("@camstack/types");
|
|
4
21
|
/** Extract a numeric `deviceId` routing hint out of arbitrary method args. */
|
|
5
22
|
function extractDeviceId(params) {
|
|
6
23
|
if (params === null || typeof params !== 'object')
|
|
@@ -8,6 +25,19 @@ function extractDeviceId(params) {
|
|
|
8
25
|
const raw = Reflect.get(params, 'deviceId');
|
|
9
26
|
return typeof raw === 'number' ? raw : undefined;
|
|
10
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Hub in-process builtins state `callerAddonId` in args (see
|
|
30
|
+
* `AddonRegistryService.inProcessRecordsTransport`). A registry proxy routes
|
|
31
|
+
* over UDS; without lifting this to {@link CapCallInput.callerAddonId} the
|
|
32
|
+
* child strips the claim (`stampCallerAddonId` with no wire stamp) and records
|
|
33
|
+
* refuses `no-caller`.
|
|
34
|
+
*/
|
|
35
|
+
function extractStatedCallerAddonId(params) {
|
|
36
|
+
if (params === null || typeof params !== 'object' || Array.isArray(params))
|
|
37
|
+
return undefined;
|
|
38
|
+
const raw = Reflect.get(params, 'callerAddonId');
|
|
39
|
+
return typeof raw === 'string' && raw.length > 0 ? raw : undefined;
|
|
40
|
+
}
|
|
11
41
|
/**
|
|
12
42
|
* Build the `CallFn` (`(method, params, targetNodeId?) => Promise`) for one
|
|
13
43
|
* registered provider. See module docs for the routing rationale.
|
|
@@ -25,12 +55,16 @@ function buildCapCallFn(deps) {
|
|
|
25
55
|
const registry = deps.getLocalChildRegistry();
|
|
26
56
|
if (registry !== null && registry.childProvides(deps.udsChildId, deps.capName, deviceId)) {
|
|
27
57
|
deps.onUdsRoute?.(deps.capName);
|
|
58
|
+
const statedCaller = types_1.CALLER_STAMPED_CAPABILITIES.includes(deps.capName)
|
|
59
|
+
? extractStatedCallerAddonId(params)
|
|
60
|
+
: undefined;
|
|
28
61
|
const input = {
|
|
29
62
|
capName: deps.capName,
|
|
30
63
|
method,
|
|
31
64
|
args: params,
|
|
32
65
|
...(deviceId !== undefined ? { deviceId } : {}),
|
|
33
66
|
...(deps.addonId !== undefined ? { addonId: deps.addonId } : {}),
|
|
67
|
+
...(statedCaller !== undefined ? { callerAddonId: statedCaller } : {}),
|
|
34
68
|
};
|
|
35
69
|
return registry.callCapOnChild(deps.udsChildId, input);
|
|
36
70
|
}
|
|
@@ -45,11 +79,14 @@ function buildCapCallFn(deps) {
|
|
|
45
79
|
// there is a harmless no-op.
|
|
46
80
|
const resolver = deps.getResolver();
|
|
47
81
|
if (resolver !== null) {
|
|
82
|
+
const statedCaller = types_1.CALLER_STAMPED_CAPABILITIES.includes(deps.capName)
|
|
83
|
+
? extractStatedCallerAddonId(params)
|
|
84
|
+
: undefined;
|
|
48
85
|
const route = resolver.resolveCapRoute(deps.capName, {
|
|
49
86
|
nodeId: targetNodeId ?? deps.nodeId,
|
|
50
87
|
...(deviceId !== undefined ? { deviceId } : {}),
|
|
51
88
|
});
|
|
52
|
-
return resolver.dispatch(route, method, params, deps.addonId);
|
|
89
|
+
return resolver.dispatch(route, method, params, deps.addonId, statedCaller);
|
|
53
90
|
}
|
|
54
91
|
// ── Pre-init window (resolver not yet constructed): legacy Moleculer call.
|
|
55
92
|
return deps.legacyBrokerCall(method, params, targetNodeId ?? deps.nodeId);
|
|
@@ -20,10 +20,6 @@ class MoleculerService {
|
|
|
20
20
|
capabilityService;
|
|
21
21
|
streamProbe;
|
|
22
22
|
broker;
|
|
23
|
-
/** Narrow-typed view of `this.broker` — see `BrokerLike` doc above. */
|
|
24
|
-
get brokerSafe() {
|
|
25
|
-
return this.broker;
|
|
26
|
-
}
|
|
27
23
|
logger;
|
|
28
24
|
/**
|
|
29
25
|
* D802, D857 — who may talk to the hub over the mesh: the broker middleware
|
|
@@ -172,7 +168,7 @@ class MoleculerService {
|
|
|
172
168
|
/** This hub's Moleculer node id (e.g. `hub`). Hub-local forked children
|
|
173
169
|
* register under `${nodeId}/${runnerId}`. */
|
|
174
170
|
get nodeId() {
|
|
175
|
-
return this.
|
|
171
|
+
return this.broker.nodeID;
|
|
176
172
|
}
|
|
177
173
|
constructor(eventBus, config, logging, capabilityService, streamProbe) {
|
|
178
174
|
this.eventBus = eventBus;
|
|
@@ -208,7 +204,7 @@ class MoleculerService {
|
|
|
208
204
|
this.broker = broker;
|
|
209
205
|
this.readinessRegistry = new types_1.ReadinessRegistry({
|
|
210
206
|
eventBus: this.eventBus,
|
|
211
|
-
sourceNodeId: this.
|
|
207
|
+
sourceNodeId: this.broker.nodeID,
|
|
212
208
|
logger: this.logging.createLogger('readiness'),
|
|
213
209
|
});
|
|
214
210
|
}
|
|
@@ -251,19 +247,20 @@ class MoleculerService {
|
|
|
251
247
|
});
|
|
252
248
|
},
|
|
253
249
|
onSetLogLevel: (level) => {
|
|
254
|
-
const factory = this.
|
|
255
|
-
const
|
|
250
|
+
const factory = this.broker.logger;
|
|
251
|
+
const moleculerLogFactory = factory;
|
|
252
|
+
const appenders = moleculerLogFactory.appenders;
|
|
256
253
|
if (appenders) {
|
|
257
254
|
for (const appender of appenders) {
|
|
258
255
|
appender.opts.level = level;
|
|
259
256
|
}
|
|
260
257
|
}
|
|
261
|
-
const cache =
|
|
258
|
+
const cache = moleculerLogFactory.cache;
|
|
262
259
|
if (cache) {
|
|
263
260
|
cache.clear();
|
|
264
261
|
}
|
|
265
262
|
logger.info('Moleculer log level changed', { meta: { level } });
|
|
266
|
-
this.
|
|
263
|
+
this.broker.call('$process.setLogLevel', { level }).catch(() => { });
|
|
267
264
|
},
|
|
268
265
|
// D3: registration-handshake path. Nodes send $hub.registerNode with
|
|
269
266
|
// their complete capability manifest; the hub applies it immediately.
|
|
@@ -277,7 +274,7 @@ class MoleculerService {
|
|
|
277
274
|
meshIdentity: this.meshGuard.gate,
|
|
278
275
|
};
|
|
279
276
|
const hubService = (0, system_1.createHubService)(hubDeps);
|
|
280
|
-
this.
|
|
277
|
+
this.broker.createService(hubService);
|
|
281
278
|
// Forked addon-runner children must root their storage (addons-data,
|
|
282
279
|
// the sqlite settings DB) under the SAME data path as the hub's main
|
|
283
280
|
// process. The configured key is `server.dataPath` (populated from the
|
|
@@ -293,7 +290,7 @@ class MoleculerService {
|
|
|
293
290
|
// broker-only — no parentUdsPath is propagated.
|
|
294
291
|
let parentUdsPath;
|
|
295
292
|
try {
|
|
296
|
-
const nodeId = this.
|
|
293
|
+
const nodeId = this.broker.nodeID;
|
|
297
294
|
// F0 (slice-5 outbound): when a forked child issues `ctx.api.<cap>` for a
|
|
298
295
|
// cap NO local sibling owns, route it from the PARENT and return the
|
|
299
296
|
// result over UDS — resolver-first, broker-fallback. Closes over `this`
|
|
@@ -419,7 +416,7 @@ class MoleculerService {
|
|
|
419
416
|
// `broker.call('$hub-cap-fwd.forward', …)` discovers it here. Without it a
|
|
420
417
|
// remote agent's raw `${cap}.*` discovery 30s-deadlines (hub-local addon
|
|
421
418
|
// runners expose no Moleculer service). See hub-cap-forward.ts.
|
|
422
|
-
this.
|
|
419
|
+
this.broker.createService((0, system_1.createHubCapForwardService)(onUnownedCall, {
|
|
423
420
|
// The agent stamps WHO is calling (its child's runner id) into the
|
|
424
421
|
// envelope (D787). Believe it only when that runner is on the agent
|
|
425
422
|
// the request came from — a node's registered manifest lists the
|
|
@@ -516,7 +513,7 @@ class MoleculerService {
|
|
|
516
513
|
// Phase F removes the child broker; `onRegisterNode`'s diff logic ensures
|
|
517
514
|
// double-apply is idempotent (same nodeId + same caps → no-op on the second call).
|
|
518
515
|
registry.onChildRegistered((child) => {
|
|
519
|
-
const hubNodeId = this.
|
|
516
|
+
const hubNodeId = this.broker.nodeID;
|
|
520
517
|
const childNodeId = `${hubNodeId}/${child.childId}`;
|
|
521
518
|
// The catalogs first, and unconditionally: the manifest gate below
|
|
522
519
|
// judges CAPS, and a frame whose caps are unchanged may still be the
|
|
@@ -568,7 +565,7 @@ class MoleculerService {
|
|
|
568
565
|
// E1: cleanup on child disconnect — same effect as `$node.disconnected`
|
|
569
566
|
// for hub-local children. The Moleculer path stays for AGENT nodes.
|
|
570
567
|
registry.onChildGone((childId, incarnation) => {
|
|
571
|
-
const hubNodeId = this.
|
|
568
|
+
const hubNodeId = this.broker.nodeID;
|
|
572
569
|
const childNodeId = `${hubNodeId}/${childId}`;
|
|
573
570
|
// The successor's first register must be a FIRST register: leaving the
|
|
574
571
|
// record behind would let a same-incarnation reconnect (mixed-version
|
|
@@ -604,15 +601,14 @@ class MoleculerService {
|
|
|
604
601
|
meta: { err: err instanceof Error ? err.message : String(err) },
|
|
605
602
|
});
|
|
606
603
|
}
|
|
607
|
-
const processService = (0, system_1.createProcessService)(this.
|
|
608
|
-
this.
|
|
604
|
+
const processService = (0, system_1.createProcessService)(this.broker.nodeID, dataDir, undefined, undefined, parentUdsPath);
|
|
605
|
+
this.broker.createService(processService);
|
|
609
606
|
// $addonHost — REMOVED (Sprint 6). Three-level settings are now
|
|
610
607
|
// served by the `addon-settings` singleton capability. Per-addon
|
|
611
608
|
// Moleculer services expose `settings.*` actions for remote agents.
|
|
612
609
|
// D3: mirror $node.disconnected onto the registry path so nodes that
|
|
613
610
|
// sent a $hub.registerNode manifest get cleaned up on disconnect.
|
|
614
|
-
|
|
615
|
-
bridgeBus.localBus.on('$node.disconnected', ({ node }) => {
|
|
611
|
+
this.broker.localBus.on('$node.disconnected', ({ node }) => {
|
|
616
612
|
// No owner token: this listener cannot observe WHICH incarnation died.
|
|
617
613
|
// For a hub-local child the UDS `onChildGone` is the authority (it holds
|
|
618
614
|
// the incarnation) and this call is refused if the id has been reclaimed;
|
|
@@ -630,16 +626,16 @@ class MoleculerService {
|
|
|
630
626
|
// this registry stuck on the synthesized `down` forever (2026-07-17:
|
|
631
627
|
// pipeline-executor@little-unraid, 3.5h dispatch outage). The
|
|
632
628
|
// periodic sweep below repairs deltas lost while CONNECTED.
|
|
633
|
-
|
|
629
|
+
this.broker.localBus.on('$node.connected', ({ node }) => {
|
|
634
630
|
const nodeId = node.id;
|
|
635
|
-
if (nodeId === this.
|
|
631
|
+
if (nodeId === this.broker.nodeID || nodeId.includes('/'))
|
|
636
632
|
return;
|
|
637
633
|
void this.pullAgentReadinessSnapshot(nodeId);
|
|
638
634
|
// The hub may have dropped this agent's registration on a disconnect
|
|
639
635
|
// only the hub saw (gossip, a failed write): ask it for its full set
|
|
640
636
|
// (`agent-registration-request.ts`, 2026-10-14).
|
|
641
637
|
void (0, agent_registration_request_js_1.requestAgentRegistration)({
|
|
642
|
-
call: (action, params, opts) => this.
|
|
638
|
+
call: (action, params, opts) => this.broker.call(action, params, opts),
|
|
643
639
|
isRegistered: (id) => this.isNodeRegistered(id),
|
|
644
640
|
warn: (message, id, meta) => this.logger.warn(message, { tags: { nodeId: id }, meta }),
|
|
645
641
|
info: (message, id, meta) => this.logger.info(message, { tags: { nodeId: id }, meta }),
|
|
@@ -647,7 +643,7 @@ class MoleculerService {
|
|
|
647
643
|
});
|
|
648
644
|
this.agentReadinessPullTimer = setInterval(() => {
|
|
649
645
|
for (const nodeId of this.nodeRegistry.listNodeIds()) {
|
|
650
|
-
if (nodeId === this.
|
|
646
|
+
if (nodeId === this.broker.nodeID || nodeId.includes('/'))
|
|
651
647
|
continue;
|
|
652
648
|
void this.pullAgentReadinessSnapshot(nodeId);
|
|
653
649
|
}
|
|
@@ -665,14 +661,14 @@ class MoleculerService {
|
|
|
665
661
|
// BEFORE broker.start() so workers / agents see it in their initial
|
|
666
662
|
// INFO packet — post-start `createService` calls propagate via
|
|
667
663
|
// heartbeat (several seconds) and would force workers to poll.
|
|
668
|
-
this.
|
|
664
|
+
this.broker.createService((0, system_1.createReadinessServiceForRegistry)(this.readinessRegistry));
|
|
669
665
|
// Register the hub-authoritative `$stream-probe` service —
|
|
670
666
|
// workers route RTSP probe + field-probe through this action when
|
|
671
667
|
// the tRPC WSS link isn't available (default path for forked
|
|
672
668
|
// workers). Keeps ffprobe + HTTP-reachability as a single
|
|
673
669
|
// hub-side implementation; see `createStreamProbeBrokerService`
|
|
674
670
|
// for the shape.
|
|
675
|
-
this.
|
|
671
|
+
this.broker.createService((0, system_1.createStreamProbeBrokerService)({
|
|
676
672
|
probe: (url, options) => this.streamProbe.probe(url, options),
|
|
677
673
|
probeField: (key, value) => this.streamProbe.probeField(key, value),
|
|
678
674
|
}));
|
|
@@ -693,7 +689,7 @@ class MoleculerService {
|
|
|
693
689
|
peerLeaf: (nodeId) => (0, system_1.getBrokerTlsTransporter)(this.broker)?.peerLeaf(nodeId),
|
|
694
690
|
dropPeer: (nodeId) => (0, system_1.getBrokerTlsTransporter)(this.broker)?.dropPeer(nodeId) ?? 0,
|
|
695
691
|
});
|
|
696
|
-
await this.
|
|
692
|
+
await this.broker.start();
|
|
697
693
|
logger.info('Moleculer broker started (TCP transport)');
|
|
698
694
|
// Construct the CapRouteResolver now that both the broker and
|
|
699
695
|
// localChildRegistry are ready. The resolver reads live registry state
|
|
@@ -702,8 +698,8 @@ class MoleculerService {
|
|
|
702
698
|
// correctly. The localChildRegistry reference is captured and may be
|
|
703
699
|
// null if UDS failed to start.
|
|
704
700
|
this.resolver = new system_1.CapRouteResolver({
|
|
705
|
-
hubNodeId: this.
|
|
706
|
-
broker: this.
|
|
701
|
+
hubNodeId: this.broker.nodeID,
|
|
702
|
+
broker: this.broker,
|
|
707
703
|
hubLocalRegistry: this.localChildRegistry,
|
|
708
704
|
nodeAuthority: (0, cap_route_authority_js_1.createNodeCapAuthority)(this.nodeRegistry, {
|
|
709
705
|
resolveSingleton: (capName, nodeId) => this.capabilityService.getRegistry()?.resolveSingletonAddonIdForNode(capName, nodeId) ??
|
|
@@ -736,7 +732,7 @@ class MoleculerService {
|
|
|
736
732
|
// is wired after attachBroker so the parentBus is backed by the real
|
|
737
733
|
// shared broker bus and broker.broadcast is live.
|
|
738
734
|
if (this.localChildRegistry !== null) {
|
|
739
|
-
const hubNodeId = this.
|
|
735
|
+
const hubNodeId = this.broker.nodeID;
|
|
740
736
|
this.udsEventBridgeDispose = (0, system_1.createUdsEventBridge)({
|
|
741
737
|
registry: this.localChildRegistry,
|
|
742
738
|
parentBus: this.eventBus,
|
|
@@ -763,7 +759,7 @@ class MoleculerService {
|
|
|
763
759
|
* the updated service list to the network.
|
|
764
760
|
*/
|
|
765
761
|
registerLogReceiver() {
|
|
766
|
-
this.
|
|
762
|
+
this.broker.createService({
|
|
767
763
|
name: 'log-receiver',
|
|
768
764
|
actions: {
|
|
769
765
|
ingest: {
|
|
@@ -795,7 +791,7 @@ class MoleculerService {
|
|
|
795
791
|
* `registerLogReceiver` relies on the same post-init registration.
|
|
796
792
|
*/
|
|
797
793
|
registerCoreCapService(service) {
|
|
798
|
-
this.
|
|
794
|
+
this.broker.createService(service);
|
|
799
795
|
}
|
|
800
796
|
/**
|
|
801
797
|
* The hub's claim authority (D663, S3): `CapabilityRegistry.resolveClaimedStatus`,
|
|
@@ -867,7 +863,7 @@ class MoleculerService {
|
|
|
867
863
|
// Pre-init fallback (resolver not yet constructed — before onModuleInit).
|
|
868
864
|
// This path is only reachable in tests that drive onRegisterNode without
|
|
869
865
|
// calling onModuleInit first.
|
|
870
|
-
if (nodeId === 'hub' || nodeId === this.
|
|
866
|
+
if (nodeId === 'hub' || nodeId === this.broker.nodeID) {
|
|
871
867
|
const registry = this.capabilityService.getRegistry();
|
|
872
868
|
const provider = registry?.getSingleton(capabilityName) ?? null;
|
|
873
869
|
if (provider !== null) {
|
|
@@ -946,7 +942,7 @@ class MoleculerService {
|
|
|
946
942
|
*/
|
|
947
943
|
applyNodeManifest(params, previousManifest, owner, previousOwner) {
|
|
948
944
|
const { nodeId, addons } = params;
|
|
949
|
-
const hubNodeId = this.
|
|
945
|
+
const hubNodeId = this.broker.nodeID;
|
|
950
946
|
const isLocalChild = nodeId.startsWith(hubNodeId + '/');
|
|
951
947
|
const isHubInProcess = nodeId === hubNodeId;
|
|
952
948
|
// Hub in-process addons register themselves during initialize() — skip.
|
|
@@ -1048,7 +1044,7 @@ class MoleculerService {
|
|
|
1048
1044
|
udsChildId,
|
|
1049
1045
|
getLocalChildRegistry: () => this.localChildRegistry,
|
|
1050
1046
|
getResolver: () => this.resolver,
|
|
1051
|
-
legacyBrokerCall: (method, methodParams, targetNode) => (0, system_1.callWithServiceDiscovery)(this.
|
|
1047
|
+
legacyBrokerCall: (method, methodParams, targetNode) => (0, system_1.callWithServiceDiscovery)(this.broker, addonId, (0, system_1.capActionName)(addonId, capName, method, false), (0, system_1.serializeTypedArrays)(methodParams), { nodeID: targetNode, timeout: 60_000 }),
|
|
1052
1048
|
onUdsRoute: (cap) => {
|
|
1053
1049
|
if (!this.udsRoutedCaps.has(cap)) {
|
|
1054
1050
|
this.udsRoutedCaps.add(cap);
|
|
@@ -1121,7 +1117,7 @@ class MoleculerService {
|
|
|
1121
1117
|
const manifest = this.nodeRegistry.getNodeManifest(nodeId);
|
|
1122
1118
|
if (!manifest)
|
|
1123
1119
|
return; // node never sent a handshake — nothing to do
|
|
1124
|
-
const hubNodeId = this.
|
|
1120
|
+
const hubNodeId = this.broker.nodeID;
|
|
1125
1121
|
const isLocalChild = nodeId.startsWith(hubNodeId + '/');
|
|
1126
1122
|
const disconnectGen = `disconnect-${nodeId}-${(0, node_crypto_1.randomUUID)()}`;
|
|
1127
1123
|
const agentNodeId = nodeId.includes('/') ? nodeId.split('/')[0] : nodeId;
|
|
@@ -1194,7 +1190,7 @@ class MoleculerService {
|
|
|
1194
1190
|
isReachableViaLegacy(nodeId, capabilityName) {
|
|
1195
1191
|
if (this.findCallFn(nodeId, capabilityName) !== undefined)
|
|
1196
1192
|
return true;
|
|
1197
|
-
return this.localChildRegistry !== null && nodeId.startsWith(`${this.
|
|
1193
|
+
return this.localChildRegistry !== null && nodeId.startsWith(`${this.broker.nodeID}/`);
|
|
1198
1194
|
}
|
|
1199
1195
|
/**
|
|
1200
1196
|
* Build a proxy object that forwards every method call on a capability
|
|
@@ -1329,7 +1325,7 @@ class MoleculerService {
|
|
|
1329
1325
|
* to the Moleculer `$node-mgmt.setLogLevel` action.
|
|
1330
1326
|
*/
|
|
1331
1327
|
setChildLogLevelByNodeId(nodeId, level) {
|
|
1332
|
-
const hubNodeId = this.
|
|
1328
|
+
const hubNodeId = this.broker.nodeID;
|
|
1333
1329
|
if (!nodeId.startsWith(`${hubNodeId}/`))
|
|
1334
1330
|
return false;
|
|
1335
1331
|
const childId = nodeId.slice(hubNodeId.length + 1);
|
|
@@ -1348,7 +1344,7 @@ class MoleculerService {
|
|
|
1348
1344
|
*/
|
|
1349
1345
|
async pullAgentReadinessSnapshot(nodeId) {
|
|
1350
1346
|
try {
|
|
1351
|
-
const snapshot = await this.
|
|
1347
|
+
const snapshot = await this.broker.call(`${system_1.AGENT_READINESS_SERVICE_NAME}.getSnapshot`, {}, { nodeID: nodeId });
|
|
1352
1348
|
if (!Array.isArray(snapshot) || snapshot.length === 0)
|
|
1353
1349
|
return;
|
|
1354
1350
|
const records = (0, agent_readiness_pull_js_1.filterAgentAuthoritativeRecords)(snapshot, nodeId);
|
|
@@ -1375,7 +1371,7 @@ class MoleculerService {
|
|
|
1375
1371
|
}
|
|
1376
1372
|
this.udsEventBridgeDispose?.();
|
|
1377
1373
|
this.udsEventBridgeDispose = null;
|
|
1378
|
-
await this.
|
|
1374
|
+
await this.broker.stop();
|
|
1379
1375
|
await this.localChildRegistry?.close();
|
|
1380
1376
|
}
|
|
1381
1377
|
}
|
package/dist/main.js
CHANGED
|
@@ -599,6 +599,7 @@ async function bootstrap() {
|
|
|
599
599
|
// settings backend (lazy getter: the backend lands after the sqlite-storage
|
|
600
600
|
// builtin registers; the service resolves it per call).
|
|
601
601
|
const shareTokenService = new share_token_service_1.ShareTokenService(() => addonRegistry.getSettingsBackend(), loggingService.createLogger('share-tokens'));
|
|
602
|
+
addonRegistry.setShareTokenService(shareTokenService);
|
|
602
603
|
// One-time native-app login handoff codes (in-memory, 60s TTL, single-use) —
|
|
603
604
|
// minted by `auth.createHandoffCode`, redeemed by the viewer app via the
|
|
604
605
|
// public `auth.redeemHandoffCode`.
|
package/dist/manual-boot.js
CHANGED
|
@@ -166,8 +166,10 @@ async function bootManual(opts) {
|
|
|
166
166
|
// A1: durable, routing-blind offline-node history — persisted through the
|
|
167
167
|
// hub-resident `settings-store` cap (resolved lazily; null until it boots).
|
|
168
168
|
// Wired post-construction so the service's 3-arg constructor stays intact.
|
|
169
|
-
|
|
170
|
-
null, loggingService.createLogger('cluster-node-history'))
|
|
169
|
+
const clusterNodeHistoryStore = new cluster_node_history_store_1.ClusterNodeHistoryStore(() => capabilityService.getRegistry()?.getSingleton('settings-store') ??
|
|
170
|
+
null, loggingService.createLogger('cluster-node-history'));
|
|
171
|
+
agentRegistryService.setClusterNodeHistoryStore(clusterNodeHistoryStore);
|
|
172
|
+
addonRegistryService.setClusterNodeHistoryStore(clusterNodeHistoryStore);
|
|
171
173
|
// ---- Layer 4: need AddonRegistry ---------------------------------------
|
|
172
174
|
// AddonWidgetsService — needs AddonRegistryService for the bundled-addon
|
|
173
175
|
// dist sub-folder lookup (see service docstring).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.595",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"files": [
|
|
@@ -38,19 +38,19 @@
|
|
|
38
38
|
]
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
42
|
-
"@camstack/addon-agent-ui": "1.2.
|
|
43
|
-
"@camstack/addon-auth": "1.2.
|
|
41
|
+
"@camstack/addon-admin-ui": "1.2.436",
|
|
42
|
+
"@camstack/addon-agent-ui": "1.2.243",
|
|
43
|
+
"@camstack/addon-auth": "1.2.149",
|
|
44
44
|
"@camstack/addon-decoder-nodeav": "1.2.123",
|
|
45
|
-
"@camstack/addon-notifiers": "1.2.
|
|
46
|
-
"@camstack/addon-pipeline": "1.2.
|
|
47
|
-
"@camstack/addon-pipeline-orchestrator": "1.2.
|
|
48
|
-
"@camstack/addon-post-analysis": "1.2.
|
|
49
|
-
"@camstack/develop": "0.1.
|
|
50
|
-
"@camstack/sdk": "1.2.
|
|
51
|
-
"@camstack/system": "1.2.
|
|
52
|
-
"@camstack/types": "1.2.
|
|
53
|
-
"@camstack/ui-library": "1.2.
|
|
45
|
+
"@camstack/addon-notifiers": "1.2.601",
|
|
46
|
+
"@camstack/addon-pipeline": "1.2.601",
|
|
47
|
+
"@camstack/addon-pipeline-orchestrator": "1.2.601",
|
|
48
|
+
"@camstack/addon-post-analysis": "1.2.601",
|
|
49
|
+
"@camstack/develop": "0.1.12",
|
|
50
|
+
"@camstack/sdk": "1.2.133",
|
|
51
|
+
"@camstack/system": "1.2.445",
|
|
52
|
+
"@camstack/types": "1.2.381",
|
|
53
|
+
"@camstack/ui-library": "1.2.299",
|
|
54
54
|
"@fastify/compress": "^9.0.0",
|
|
55
55
|
"@fastify/cookie": "^11.0.2",
|
|
56
56
|
"@fastify/cors": "^11.2.0",
|