@nextera.one/axis-server-sdk 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +131 -2
- package/dist/index.d.ts +131 -2
- package/dist/index.js +549 -113
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +537 -111
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -18,6 +18,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
18
18
|
if (kind && result) __defProp(target, key, result);
|
|
19
19
|
return result;
|
|
20
20
|
};
|
|
21
|
+
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
21
22
|
|
|
22
23
|
// src/decorators/handler.decorator.ts
|
|
23
24
|
import { Injectable, SetMetadata } from "@nestjs/common";
|
|
@@ -56,6 +57,24 @@ function Intent(action, options) {
|
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
// src/decorators/intent-body.decorator.ts
|
|
61
|
+
import "reflect-metadata";
|
|
62
|
+
var INTENT_BODY_KEY = "axis:intent:body";
|
|
63
|
+
function IntentBody(decoder) {
|
|
64
|
+
return (target, propertyKey) => {
|
|
65
|
+
Reflect.defineMetadata(INTENT_BODY_KEY, decoder, target, propertyKey);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/decorators/intent-sensors.decorator.ts
|
|
70
|
+
import "reflect-metadata";
|
|
71
|
+
var INTENT_SENSORS_KEY = "axis:intent:sensors";
|
|
72
|
+
function IntentSensors(sensors) {
|
|
73
|
+
return (target, propertyKey) => {
|
|
74
|
+
Reflect.defineMetadata(INTENT_SENSORS_KEY, sensors, target, propertyKey);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
59
78
|
// src/decorators/tlv-field.decorator.ts
|
|
60
79
|
import "reflect-metadata";
|
|
61
80
|
var TLV_FIELDS_KEY = "axis:tlv:fields";
|
|
@@ -269,11 +288,146 @@ __decorateClass([
|
|
|
269
288
|
], AxisResponseDto.prototype, "updated_by", 2);
|
|
270
289
|
|
|
271
290
|
// src/engine/intent.router.ts
|
|
272
|
-
import { Injectable as Injectable2 } from "@nestjs/common";
|
|
291
|
+
import { Injectable as Injectable2, Logger, Optional } from "@nestjs/common";
|
|
292
|
+
|
|
293
|
+
// src/sensor/axis-sensor.ts
|
|
294
|
+
var Decision = /* @__PURE__ */ ((Decision2) => {
|
|
295
|
+
Decision2["ALLOW"] = "ALLOW";
|
|
296
|
+
Decision2["DENY"] = "DENY";
|
|
297
|
+
Decision2["THROTTLE"] = "THROTTLE";
|
|
298
|
+
Decision2["FLAG"] = "FLAG";
|
|
299
|
+
return Decision2;
|
|
300
|
+
})(Decision || {});
|
|
301
|
+
function normalizeSensorDecision(sensorDecision) {
|
|
302
|
+
if ("action" in sensorDecision) {
|
|
303
|
+
switch (sensorDecision.action) {
|
|
304
|
+
case "ALLOW":
|
|
305
|
+
return {
|
|
306
|
+
allow: true,
|
|
307
|
+
riskScore: 0,
|
|
308
|
+
reasons: [],
|
|
309
|
+
meta: sensorDecision.meta
|
|
310
|
+
};
|
|
311
|
+
case "DENY":
|
|
312
|
+
return {
|
|
313
|
+
allow: false,
|
|
314
|
+
riskScore: 100,
|
|
315
|
+
reasons: [sensorDecision.code, sensorDecision.reason].filter(
|
|
316
|
+
Boolean
|
|
317
|
+
),
|
|
318
|
+
meta: sensorDecision.meta,
|
|
319
|
+
retryAfterMs: sensorDecision.retryAfterMs
|
|
320
|
+
};
|
|
321
|
+
case "THROTTLE":
|
|
322
|
+
return {
|
|
323
|
+
allow: false,
|
|
324
|
+
riskScore: 50,
|
|
325
|
+
reasons: ["RATE_LIMIT"],
|
|
326
|
+
retryAfterMs: sensorDecision.retryAfterMs,
|
|
327
|
+
meta: sensorDecision.meta
|
|
328
|
+
};
|
|
329
|
+
case "FLAG":
|
|
330
|
+
return {
|
|
331
|
+
allow: true,
|
|
332
|
+
riskScore: sensorDecision.scoreDelta,
|
|
333
|
+
reasons: sensorDecision.reasons,
|
|
334
|
+
meta: sensorDecision.meta
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
allow: sensorDecision.allow,
|
|
340
|
+
riskScore: sensorDecision.riskScore,
|
|
341
|
+
reasons: sensorDecision.reasons,
|
|
342
|
+
tags: sensorDecision.tags,
|
|
343
|
+
meta: sensorDecision.meta,
|
|
344
|
+
tighten: sensorDecision.tighten,
|
|
345
|
+
retryAfterMs: sensorDecision.retryAfterMs
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
var SensorDecisions = {
|
|
349
|
+
allow(meta, tags) {
|
|
350
|
+
return {
|
|
351
|
+
decision: "ALLOW" /* ALLOW */,
|
|
352
|
+
allow: true,
|
|
353
|
+
riskScore: 0,
|
|
354
|
+
reasons: [],
|
|
355
|
+
tags,
|
|
356
|
+
meta
|
|
357
|
+
};
|
|
358
|
+
},
|
|
359
|
+
deny(code, reason, meta) {
|
|
360
|
+
return {
|
|
361
|
+
decision: "DENY" /* DENY */,
|
|
362
|
+
allow: false,
|
|
363
|
+
riskScore: 100,
|
|
364
|
+
code,
|
|
365
|
+
reasons: [code, reason].filter(Boolean),
|
|
366
|
+
meta
|
|
367
|
+
};
|
|
368
|
+
},
|
|
369
|
+
throttle(retryAfterMs, meta) {
|
|
370
|
+
return {
|
|
371
|
+
decision: "THROTTLE" /* THROTTLE */,
|
|
372
|
+
allow: false,
|
|
373
|
+
riskScore: 50,
|
|
374
|
+
retryAfterMs,
|
|
375
|
+
code: "RATE_LIMIT",
|
|
376
|
+
reasons: ["RATE_LIMIT"],
|
|
377
|
+
meta
|
|
378
|
+
};
|
|
379
|
+
},
|
|
380
|
+
flag(scoreDelta, reasons, meta) {
|
|
381
|
+
return {
|
|
382
|
+
decision: "FLAG" /* FLAG */,
|
|
383
|
+
allow: true,
|
|
384
|
+
riskScore: scoreDelta,
|
|
385
|
+
scoreDelta,
|
|
386
|
+
reasons,
|
|
387
|
+
meta
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// src/engine/intent.router.ts
|
|
273
393
|
var IntentRouter = class {
|
|
274
|
-
constructor() {
|
|
394
|
+
constructor(moduleRef) {
|
|
395
|
+
this.moduleRef = moduleRef;
|
|
396
|
+
this.logger = new Logger(IntentRouter.name);
|
|
275
397
|
/** Internal registry of dynamic intent handlers */
|
|
276
398
|
this.handlers = /* @__PURE__ */ new Map();
|
|
399
|
+
/** Per-intent sensor classes (resolved at call time) */
|
|
400
|
+
this.intentSensors = /* @__PURE__ */ new Map();
|
|
401
|
+
/** Per-intent body decoders */
|
|
402
|
+
this.intentDecoders = /* @__PURE__ */ new Map();
|
|
403
|
+
/** Per-intent TLV schemas */
|
|
404
|
+
this.intentSchemas = /* @__PURE__ */ new Map();
|
|
405
|
+
/** Per-intent custom validators */
|
|
406
|
+
this.intentValidators = /* @__PURE__ */ new Map();
|
|
407
|
+
/** Per-intent operation kind */
|
|
408
|
+
this.intentKinds = /* @__PURE__ */ new Map();
|
|
409
|
+
}
|
|
410
|
+
getSchema(intent) {
|
|
411
|
+
return this.intentSchemas.get(intent);
|
|
412
|
+
}
|
|
413
|
+
getValidators(intent) {
|
|
414
|
+
return this.intentValidators.get(intent);
|
|
415
|
+
}
|
|
416
|
+
has(intent) {
|
|
417
|
+
return this.handlers.has(intent) || IntentRouter.BUILTIN_INTENTS.has(intent);
|
|
418
|
+
}
|
|
419
|
+
getRegisteredIntents() {
|
|
420
|
+
return [...IntentRouter.BUILTIN_INTENTS, ...this.handlers.keys()];
|
|
421
|
+
}
|
|
422
|
+
getIntentEntry(intent) {
|
|
423
|
+
if (!this.has(intent)) return null;
|
|
424
|
+
return {
|
|
425
|
+
schema: this.intentSchemas.get(intent),
|
|
426
|
+
validators: this.intentValidators.get(intent),
|
|
427
|
+
hasSensors: this.intentSensors.has(intent),
|
|
428
|
+
builtin: IntentRouter.BUILTIN_INTENTS.has(intent),
|
|
429
|
+
kind: this.intentKinds.get(intent)
|
|
430
|
+
};
|
|
277
431
|
}
|
|
278
432
|
/**
|
|
279
433
|
* Registers a handler for a specific intent.
|
|
@@ -309,6 +463,16 @@ var IntentRouter = class {
|
|
|
309
463
|
} else {
|
|
310
464
|
this.register(intentName, fn);
|
|
311
465
|
}
|
|
466
|
+
this.registerIntentMeta(intentName, Object.getPrototypeOf(instance), String(route.methodName));
|
|
467
|
+
}
|
|
468
|
+
const proto = Object.getPrototypeOf(instance);
|
|
469
|
+
for (const key of Object.getOwnPropertyNames(proto)) {
|
|
470
|
+
const meta = Reflect.getMetadata(INTENT_METADATA_KEY, proto, key);
|
|
471
|
+
if (!meta?.intent) continue;
|
|
472
|
+
if (!this.handlers.has(meta.intent)) {
|
|
473
|
+
this.register(meta.intent, instance[key].bind(instance));
|
|
474
|
+
}
|
|
475
|
+
this.registerIntentMeta(meta.intent, proto, key);
|
|
312
476
|
}
|
|
313
477
|
}
|
|
314
478
|
/**
|
|
@@ -332,6 +496,7 @@ var IntentRouter = class {
|
|
|
332
496
|
intent = new TextDecoder().decode(intentBytes);
|
|
333
497
|
let effect;
|
|
334
498
|
if (intent === "system.ping" || intent === "public.ping") {
|
|
499
|
+
this.logger.debug("PING received");
|
|
335
500
|
effect = {
|
|
336
501
|
ok: true,
|
|
337
502
|
effect: "pong",
|
|
@@ -372,6 +537,7 @@ var IntentRouter = class {
|
|
|
372
537
|
if (!innerIntent) {
|
|
373
538
|
throw new Error("INTENT.EXEC missing inner intent");
|
|
374
539
|
}
|
|
540
|
+
this.logger.debug(`EXEC: routing to inner intent '${innerIntent}'`);
|
|
375
541
|
const innerFrame = {
|
|
376
542
|
...frame,
|
|
377
543
|
headers: new Map(frame.headers),
|
|
@@ -387,8 +553,23 @@ var IntentRouter = class {
|
|
|
387
553
|
if (!handler) {
|
|
388
554
|
throw new Error(`Intent not found: ${intent}`);
|
|
389
555
|
}
|
|
556
|
+
const sensorClasses = this.intentSensors.get(intent);
|
|
557
|
+
if (sensorClasses && sensorClasses.length > 0) {
|
|
558
|
+
await this.runIntentSensors(sensorClasses, intent, frame);
|
|
559
|
+
}
|
|
560
|
+
const decoder = this.intentDecoders.get(intent);
|
|
561
|
+
let decodedBody = frame.body;
|
|
562
|
+
if (decoder) {
|
|
563
|
+
try {
|
|
564
|
+
decodedBody = decoder(Buffer.from(frame.body));
|
|
565
|
+
} catch (decodeErr) {
|
|
566
|
+
throw new Error(
|
|
567
|
+
`IntentBody decode failed for ${intent}: ${decodeErr.message}`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
390
571
|
if (typeof handler === "function") {
|
|
391
|
-
const resultBody = await handler(frame.body, frame.headers);
|
|
572
|
+
const resultBody = decoder ? await handler(decodedBody, frame.headers) : await handler(frame.body, frame.headers);
|
|
392
573
|
effect = {
|
|
393
574
|
ok: true,
|
|
394
575
|
effect: "complete",
|
|
@@ -398,10 +579,7 @@ var IntentRouter = class {
|
|
|
398
579
|
if (typeof handler.handle === "function") {
|
|
399
580
|
effect = await handler.handle(frame);
|
|
400
581
|
} else if (typeof handler.execute === "function") {
|
|
401
|
-
const bodyRes = await handler.execute(
|
|
402
|
-
frame.body,
|
|
403
|
-
frame.headers
|
|
404
|
-
);
|
|
582
|
+
const bodyRes = decoder ? await handler.execute(decodedBody, frame.headers) : await handler.execute(frame.body, frame.headers);
|
|
405
583
|
effect = {
|
|
406
584
|
ok: true,
|
|
407
585
|
effect: "complete",
|
|
@@ -414,20 +592,130 @@ var IntentRouter = class {
|
|
|
414
592
|
}
|
|
415
593
|
}
|
|
416
594
|
}
|
|
417
|
-
this.
|
|
595
|
+
this.logIntent(intent, start, true);
|
|
418
596
|
return effect;
|
|
419
597
|
} catch (e) {
|
|
420
|
-
|
|
598
|
+
this.logIntent(intent, start, false, e.message);
|
|
421
599
|
throw e;
|
|
422
600
|
}
|
|
423
601
|
}
|
|
424
|
-
|
|
602
|
+
logIntent(intent, start, ok, error) {
|
|
425
603
|
const diff = process.hrtime(start);
|
|
426
|
-
|
|
604
|
+
const ms = (diff[0] * 1e3 + diff[1] / 1e6).toFixed(2);
|
|
605
|
+
if (ok) {
|
|
606
|
+
this.logger.debug(`${intent} completed in ${ms}ms`);
|
|
607
|
+
} else {
|
|
608
|
+
this.logger.warn(`${intent} failed in ${ms}ms - ${error}`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
registerIntentMeta(intent, proto, methodName) {
|
|
612
|
+
const decoder = Reflect.getMetadata(INTENT_BODY_KEY, proto, methodName);
|
|
613
|
+
if (decoder) {
|
|
614
|
+
this.intentDecoders.set(intent, decoder);
|
|
615
|
+
}
|
|
616
|
+
const sensors = Reflect.getMetadata(INTENT_SENSORS_KEY, proto, methodName);
|
|
617
|
+
if (sensors && Array.isArray(sensors) && sensors.length > 0) {
|
|
618
|
+
this.intentSensors.set(intent, sensors);
|
|
619
|
+
}
|
|
620
|
+
const meta = Reflect.getMetadata(INTENT_METADATA_KEY, proto, methodName);
|
|
621
|
+
if (meta) {
|
|
622
|
+
this.storeSchema(meta);
|
|
623
|
+
if (meta.kind) {
|
|
624
|
+
this.intentKinds.set(intent, meta.kind);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
async runIntentSensors(sensorClasses, intent, frame) {
|
|
629
|
+
if (!this.moduleRef) return;
|
|
630
|
+
for (const SensorClass of sensorClasses) {
|
|
631
|
+
let sensor;
|
|
632
|
+
try {
|
|
633
|
+
sensor = this.moduleRef.get(SensorClass, { strict: false });
|
|
634
|
+
} catch {
|
|
635
|
+
this.logger.warn(
|
|
636
|
+
`@IntentSensors: could not resolve ${SensorClass.name} for ${intent}`
|
|
637
|
+
);
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
const sensorInput = {
|
|
641
|
+
rawBytes: frame.body,
|
|
642
|
+
intent,
|
|
643
|
+
body: frame.body,
|
|
644
|
+
headerTLVs: frame.headers,
|
|
645
|
+
metadata: { phase: "intent", intent }
|
|
646
|
+
};
|
|
647
|
+
if (sensor.supports && !sensor.supports(sensorInput)) continue;
|
|
648
|
+
const decision = normalizeSensorDecision(await sensor.run(sensorInput));
|
|
649
|
+
if (!decision.allow) {
|
|
650
|
+
const reason = decision.reasons[0] || `${sensor.name}:DENIED`;
|
|
651
|
+
this.logger.warn(
|
|
652
|
+
`Intent sensor ${sensor.name} denied ${intent}: ${reason}`
|
|
653
|
+
);
|
|
654
|
+
throw new Error(`SENSOR_DENY:${reason}`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
storeSchema(meta) {
|
|
659
|
+
if (meta.dto) {
|
|
660
|
+
if (meta.tlv && meta.tlv.length > 0) {
|
|
661
|
+
this.logger.warn(
|
|
662
|
+
`${meta.intent}: both 'dto' and 'tlv' specified - using dto, ignoring tlv`
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
const extracted = extractDtoSchema(meta.dto);
|
|
666
|
+
const schema2 = {
|
|
667
|
+
intent: meta.intent,
|
|
668
|
+
version: 1,
|
|
669
|
+
bodyProfile: meta.bodyProfile || "TLV_MAP",
|
|
670
|
+
fields: extracted.fields.map((f) => ({
|
|
671
|
+
name: f.name,
|
|
672
|
+
tlv: f.tag,
|
|
673
|
+
kind: f.kind,
|
|
674
|
+
required: f.required,
|
|
675
|
+
maxLen: f.maxLen,
|
|
676
|
+
max: f.max,
|
|
677
|
+
scope: f.scope
|
|
678
|
+
}))
|
|
679
|
+
};
|
|
680
|
+
this.intentSchemas.set(meta.intent, schema2);
|
|
681
|
+
if (extracted.validators.size > 0) {
|
|
682
|
+
this.intentValidators.set(meta.intent, extracted.validators);
|
|
683
|
+
}
|
|
684
|
+
if (!this.intentDecoders.has(meta.intent)) {
|
|
685
|
+
this.intentDecoders.set(meta.intent, buildDtoDecoder(meta.dto));
|
|
686
|
+
}
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
if (!meta.tlv || meta.tlv.length === 0) return;
|
|
690
|
+
const schema = {
|
|
691
|
+
intent: meta.intent,
|
|
692
|
+
version: 1,
|
|
693
|
+
bodyProfile: meta.bodyProfile || "TLV_MAP",
|
|
694
|
+
fields: meta.tlv.map((f) => ({
|
|
695
|
+
name: f.name,
|
|
696
|
+
tlv: f.tag,
|
|
697
|
+
kind: f.kind,
|
|
698
|
+
required: f.required,
|
|
699
|
+
maxLen: f.maxLen,
|
|
700
|
+
max: f.max,
|
|
701
|
+
scope: f.scope
|
|
702
|
+
}))
|
|
703
|
+
};
|
|
704
|
+
this.intentSchemas.set(meta.intent, schema);
|
|
427
705
|
}
|
|
428
706
|
};
|
|
707
|
+
/** Intents handled inline in route() — not in `handlers` map */
|
|
708
|
+
IntentRouter.BUILTIN_INTENTS = /* @__PURE__ */ new Set([
|
|
709
|
+
"system.ping",
|
|
710
|
+
"public.ping",
|
|
711
|
+
"system.time",
|
|
712
|
+
"system.echo",
|
|
713
|
+
"INTENT.EXEC",
|
|
714
|
+
"axis.intent.exec"
|
|
715
|
+
]);
|
|
429
716
|
IntentRouter = __decorateClass([
|
|
430
|
-
Injectable2()
|
|
717
|
+
Injectable2(),
|
|
718
|
+
__decorateParam(0, Optional())
|
|
431
719
|
], IntentRouter);
|
|
432
720
|
|
|
433
721
|
// src/core/constants.ts
|
|
@@ -1866,105 +2154,6 @@ function buildPacket(hdr, body, sig, flags = 0) {
|
|
|
1866
2154
|
};
|
|
1867
2155
|
}
|
|
1868
2156
|
|
|
1869
|
-
// src/sensor/axis-sensor.ts
|
|
1870
|
-
var Decision = /* @__PURE__ */ ((Decision2) => {
|
|
1871
|
-
Decision2["ALLOW"] = "ALLOW";
|
|
1872
|
-
Decision2["DENY"] = "DENY";
|
|
1873
|
-
Decision2["THROTTLE"] = "THROTTLE";
|
|
1874
|
-
Decision2["FLAG"] = "FLAG";
|
|
1875
|
-
return Decision2;
|
|
1876
|
-
})(Decision || {});
|
|
1877
|
-
function normalizeSensorDecision(sensorDecision) {
|
|
1878
|
-
if ("action" in sensorDecision) {
|
|
1879
|
-
switch (sensorDecision.action) {
|
|
1880
|
-
case "ALLOW":
|
|
1881
|
-
return {
|
|
1882
|
-
allow: true,
|
|
1883
|
-
riskScore: 0,
|
|
1884
|
-
reasons: [],
|
|
1885
|
-
meta: sensorDecision.meta
|
|
1886
|
-
};
|
|
1887
|
-
case "DENY":
|
|
1888
|
-
return {
|
|
1889
|
-
allow: false,
|
|
1890
|
-
riskScore: 100,
|
|
1891
|
-
reasons: [sensorDecision.code, sensorDecision.reason].filter(
|
|
1892
|
-
Boolean
|
|
1893
|
-
),
|
|
1894
|
-
meta: sensorDecision.meta,
|
|
1895
|
-
retryAfterMs: sensorDecision.retryAfterMs
|
|
1896
|
-
};
|
|
1897
|
-
case "THROTTLE":
|
|
1898
|
-
return {
|
|
1899
|
-
allow: false,
|
|
1900
|
-
riskScore: 50,
|
|
1901
|
-
reasons: ["RATE_LIMIT"],
|
|
1902
|
-
retryAfterMs: sensorDecision.retryAfterMs,
|
|
1903
|
-
meta: sensorDecision.meta
|
|
1904
|
-
};
|
|
1905
|
-
case "FLAG":
|
|
1906
|
-
return {
|
|
1907
|
-
allow: true,
|
|
1908
|
-
riskScore: sensorDecision.scoreDelta,
|
|
1909
|
-
reasons: sensorDecision.reasons,
|
|
1910
|
-
meta: sensorDecision.meta
|
|
1911
|
-
};
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
return {
|
|
1915
|
-
allow: sensorDecision.allow,
|
|
1916
|
-
riskScore: sensorDecision.riskScore,
|
|
1917
|
-
reasons: sensorDecision.reasons,
|
|
1918
|
-
tags: sensorDecision.tags,
|
|
1919
|
-
meta: sensorDecision.meta,
|
|
1920
|
-
tighten: sensorDecision.tighten,
|
|
1921
|
-
retryAfterMs: sensorDecision.retryAfterMs
|
|
1922
|
-
};
|
|
1923
|
-
}
|
|
1924
|
-
var SensorDecisions = {
|
|
1925
|
-
allow(meta, tags) {
|
|
1926
|
-
return {
|
|
1927
|
-
decision: "ALLOW" /* ALLOW */,
|
|
1928
|
-
allow: true,
|
|
1929
|
-
riskScore: 0,
|
|
1930
|
-
reasons: [],
|
|
1931
|
-
tags,
|
|
1932
|
-
meta
|
|
1933
|
-
};
|
|
1934
|
-
},
|
|
1935
|
-
deny(code, reason, meta) {
|
|
1936
|
-
return {
|
|
1937
|
-
decision: "DENY" /* DENY */,
|
|
1938
|
-
allow: false,
|
|
1939
|
-
riskScore: 100,
|
|
1940
|
-
code,
|
|
1941
|
-
reasons: [code, reason].filter(Boolean),
|
|
1942
|
-
meta
|
|
1943
|
-
};
|
|
1944
|
-
},
|
|
1945
|
-
throttle(retryAfterMs, meta) {
|
|
1946
|
-
return {
|
|
1947
|
-
decision: "THROTTLE" /* THROTTLE */,
|
|
1948
|
-
allow: false,
|
|
1949
|
-
riskScore: 50,
|
|
1950
|
-
retryAfterMs,
|
|
1951
|
-
code: "RATE_LIMIT",
|
|
1952
|
-
reasons: ["RATE_LIMIT"],
|
|
1953
|
-
meta
|
|
1954
|
-
};
|
|
1955
|
-
},
|
|
1956
|
-
flag(scoreDelta, reasons, meta) {
|
|
1957
|
-
return {
|
|
1958
|
-
decision: "FLAG" /* FLAG */,
|
|
1959
|
-
allow: true,
|
|
1960
|
-
riskScore: scoreDelta,
|
|
1961
|
-
scoreDelta,
|
|
1962
|
-
reasons,
|
|
1963
|
-
meta
|
|
1964
|
-
};
|
|
1965
|
-
}
|
|
1966
|
-
};
|
|
1967
|
-
|
|
1968
2157
|
// src/security/scopes.ts
|
|
1969
2158
|
function hasScope(scopes, required) {
|
|
1970
2159
|
if (!Array.isArray(scopes) || scopes.length === 0) {
|
|
@@ -2262,13 +2451,245 @@ function isTimestampValid(ts, skewSeconds = 120) {
|
|
|
2262
2451
|
const diff = Math.abs(now - ts);
|
|
2263
2452
|
return diff <= skewSeconds;
|
|
2264
2453
|
}
|
|
2454
|
+
|
|
2455
|
+
// src/upload/axis-files.handlers.ts
|
|
2456
|
+
import { Inject, Injectable as Injectable3, Logger as Logger2, Optional as Optional2 } from "@nestjs/common";
|
|
2457
|
+
import * as crypto2 from "crypto";
|
|
2458
|
+
|
|
2459
|
+
// src/upload/upload.tokens.ts
|
|
2460
|
+
var AXIS_UPLOAD_SESSION_STORE = "AXIS_UPLOAD_SESSION_STORE";
|
|
2461
|
+
var AXIS_UPLOAD_FILE_STORE = "AXIS_UPLOAD_FILE_STORE";
|
|
2462
|
+
var AXIS_UPLOAD_RECEIPT_SIGNER = "AXIS_UPLOAD_RECEIPT_SIGNER";
|
|
2463
|
+
|
|
2464
|
+
// src/upload/axis-files.handlers.ts
|
|
2465
|
+
var AxisFilesDownloadHandler = class {
|
|
2466
|
+
constructor(sessions, files) {
|
|
2467
|
+
this.sessions = sessions;
|
|
2468
|
+
this.files = files;
|
|
2469
|
+
this.logger = new Logger2(AxisFilesDownloadHandler.name);
|
|
2470
|
+
this.name = "axis.files.download";
|
|
2471
|
+
this.open = true;
|
|
2472
|
+
this.description = "File download handler";
|
|
2473
|
+
}
|
|
2474
|
+
async execute(body, headers) {
|
|
2475
|
+
const h = headers;
|
|
2476
|
+
if (!h) throw new Error("MISSING_HEADERS");
|
|
2477
|
+
const uploadIdBytes = h.get(20);
|
|
2478
|
+
if (!uploadIdBytes) throw new Error("MISSING_UPLOAD_ID");
|
|
2479
|
+
const uploadId = new TextDecoder().decode(uploadIdBytes);
|
|
2480
|
+
let rangeStart = 0;
|
|
2481
|
+
let rangeLen = -1;
|
|
2482
|
+
const startBytes = h.get(21);
|
|
2483
|
+
if (startBytes) {
|
|
2484
|
+
const { value } = decodeVarint(startBytes);
|
|
2485
|
+
rangeStart = value;
|
|
2486
|
+
}
|
|
2487
|
+
const lenBytes = h.get(22);
|
|
2488
|
+
if (lenBytes) {
|
|
2489
|
+
const { value } = decodeVarint(lenBytes);
|
|
2490
|
+
rangeLen = value;
|
|
2491
|
+
}
|
|
2492
|
+
const session = await this.sessions.findByFileId(uploadId);
|
|
2493
|
+
if (!session) {
|
|
2494
|
+
throw new Error(`SESSION_NOT_FOUND: ${uploadId}`);
|
|
2495
|
+
}
|
|
2496
|
+
if (session.status !== "COMPLETE") {
|
|
2497
|
+
throw new Error(`FILE_NOT_READY: Status is ${session.status}`);
|
|
2498
|
+
}
|
|
2499
|
+
const stat = await this.files.statFinal(
|
|
2500
|
+
uploadId,
|
|
2501
|
+
session.filename
|
|
2502
|
+
);
|
|
2503
|
+
const fileSize = stat.size;
|
|
2504
|
+
if (rangeStart < 0) rangeStart = 0;
|
|
2505
|
+
if (rangeStart >= fileSize) throw new Error("RANGE_OUT_OF_BOUNDS");
|
|
2506
|
+
let end = fileSize;
|
|
2507
|
+
if (rangeLen >= 0) {
|
|
2508
|
+
end = Math.min(rangeStart + rangeLen, fileSize);
|
|
2509
|
+
}
|
|
2510
|
+
const actualLen = end - rangeStart;
|
|
2511
|
+
const buffer = await this.files.readFinalRange(
|
|
2512
|
+
uploadId,
|
|
2513
|
+
session.filename,
|
|
2514
|
+
rangeStart,
|
|
2515
|
+
actualLen
|
|
2516
|
+
);
|
|
2517
|
+
const responseHeaders = /* @__PURE__ */ new Map();
|
|
2518
|
+
responseHeaders.set(30, encodeVarint(fileSize));
|
|
2519
|
+
responseHeaders.set(31, encodeVarint(rangeStart));
|
|
2520
|
+
responseHeaders.set(32, encodeVarint(actualLen));
|
|
2521
|
+
return {
|
|
2522
|
+
ok: true,
|
|
2523
|
+
effect: "FILE_PART",
|
|
2524
|
+
body: buffer,
|
|
2525
|
+
headers: responseHeaders
|
|
2526
|
+
};
|
|
2527
|
+
}
|
|
2528
|
+
};
|
|
2529
|
+
__decorateClass([
|
|
2530
|
+
Intent("file.download", { absolute: true, kind: "read" })
|
|
2531
|
+
], AxisFilesDownloadHandler.prototype, "execute", 1);
|
|
2532
|
+
AxisFilesDownloadHandler = __decorateClass([
|
|
2533
|
+
Handler("axis.files.download"),
|
|
2534
|
+
Injectable3(),
|
|
2535
|
+
__decorateParam(0, Inject(AXIS_UPLOAD_SESSION_STORE)),
|
|
2536
|
+
__decorateParam(1, Inject(AXIS_UPLOAD_FILE_STORE))
|
|
2537
|
+
], AxisFilesDownloadHandler);
|
|
2538
|
+
var AxisFilesFinalizeHandler = class {
|
|
2539
|
+
constructor(sessions, files, keyring) {
|
|
2540
|
+
this.sessions = sessions;
|
|
2541
|
+
this.files = files;
|
|
2542
|
+
this.keyring = keyring;
|
|
2543
|
+
this.logger = new Logger2(AxisFilesFinalizeHandler.name);
|
|
2544
|
+
this.name = "axis.files.finalize";
|
|
2545
|
+
this.open = false;
|
|
2546
|
+
this.description = "File upload finalization handler";
|
|
2547
|
+
}
|
|
2548
|
+
async execute(body, headers) {
|
|
2549
|
+
const bodyStr = new TextDecoder().decode(body);
|
|
2550
|
+
const req = JSON.parse(bodyStr);
|
|
2551
|
+
const { fileId, expectedHash } = req;
|
|
2552
|
+
if (!fileId) throw new Error("MISSING_FILE_ID");
|
|
2553
|
+
const session = await this.sessions.findByFileId(fileId);
|
|
2554
|
+
if (!session) throw new Error("SESSION_NOT_FOUND");
|
|
2555
|
+
if (!await this.files.hasTemp(fileId)) {
|
|
2556
|
+
throw new Error("CHUNKS_NOT_FOUND");
|
|
2557
|
+
}
|
|
2558
|
+
const hash = crypto2.createHash("sha256");
|
|
2559
|
+
const rs = this.files.createTempReadStream(fileId);
|
|
2560
|
+
for await (const chunk of rs) {
|
|
2561
|
+
hash.update(chunk);
|
|
2562
|
+
}
|
|
2563
|
+
const finalHash = hash.digest("hex");
|
|
2564
|
+
if (expectedHash && finalHash !== expectedHash) {
|
|
2565
|
+
throw new Error("HASH_MISMATCH");
|
|
2566
|
+
}
|
|
2567
|
+
const finalPath = await this.files.moveTempToFinal(
|
|
2568
|
+
fileId,
|
|
2569
|
+
session.filename
|
|
2570
|
+
);
|
|
2571
|
+
await this.sessions.updateStatus(fileId, "COMPLETE", null);
|
|
2572
|
+
if (!this.keyring) {
|
|
2573
|
+
this.logger.warn("Receipt signer not configured; returning unsigned receipt");
|
|
2574
|
+
return {
|
|
2575
|
+
ok: true,
|
|
2576
|
+
effect: "FILE_FINALIZED",
|
|
2577
|
+
body: new TextEncoder().encode(
|
|
2578
|
+
JSON.stringify({
|
|
2579
|
+
uploadId: fileId,
|
|
2580
|
+
sha256_final: finalHash,
|
|
2581
|
+
totalSize: session.totalSize,
|
|
2582
|
+
tsMs: Date.now(),
|
|
2583
|
+
path: finalPath
|
|
2584
|
+
})
|
|
2585
|
+
)
|
|
2586
|
+
};
|
|
2587
|
+
}
|
|
2588
|
+
const receiptData = {
|
|
2589
|
+
uploadId: fileId,
|
|
2590
|
+
sha256_final: finalHash,
|
|
2591
|
+
totalSize: session.totalSize,
|
|
2592
|
+
tsMs: Date.now()
|
|
2593
|
+
};
|
|
2594
|
+
const receiptJson = JSON.stringify(receiptData);
|
|
2595
|
+
const receiptBody = new TextEncoder().encode(receiptJson);
|
|
2596
|
+
const SIG_PRESENT = 1;
|
|
2597
|
+
const responseFrame = {
|
|
2598
|
+
flags: SIG_PRESENT,
|
|
2599
|
+
headers: /* @__PURE__ */ new Map(),
|
|
2600
|
+
body: receiptBody,
|
|
2601
|
+
sig: new Uint8Array(0)
|
|
2602
|
+
};
|
|
2603
|
+
const signTarget = getSignTarget(responseFrame);
|
|
2604
|
+
const { sig, kid } = this.keyring.signActive(signTarget);
|
|
2605
|
+
responseFrame.sig = sig;
|
|
2606
|
+
return {
|
|
2607
|
+
ok: true,
|
|
2608
|
+
effect: "FILE_FINALIZED",
|
|
2609
|
+
data: encodeFrame(responseFrame),
|
|
2610
|
+
headers: /* @__PURE__ */ new Map([[1, new TextEncoder().encode(kid)]])
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
};
|
|
2614
|
+
__decorateClass([
|
|
2615
|
+
Intent("file.finalize", { absolute: true, kind: "action" })
|
|
2616
|
+
], AxisFilesFinalizeHandler.prototype, "execute", 1);
|
|
2617
|
+
AxisFilesFinalizeHandler = __decorateClass([
|
|
2618
|
+
Handler("axis.files.finalize"),
|
|
2619
|
+
Injectable3(),
|
|
2620
|
+
__decorateParam(0, Inject(AXIS_UPLOAD_SESSION_STORE)),
|
|
2621
|
+
__decorateParam(1, Inject(AXIS_UPLOAD_FILE_STORE)),
|
|
2622
|
+
__decorateParam(2, Optional2()),
|
|
2623
|
+
__decorateParam(2, Inject(AXIS_UPLOAD_RECEIPT_SIGNER))
|
|
2624
|
+
], AxisFilesFinalizeHandler);
|
|
2625
|
+
|
|
2626
|
+
// src/upload/disk-upload-file.store.ts
|
|
2627
|
+
import * as fs from "fs";
|
|
2628
|
+
import * as path from "path";
|
|
2629
|
+
var DiskUploadFileStore = class {
|
|
2630
|
+
constructor(options) {
|
|
2631
|
+
this.uploadDir = options.uploadDir;
|
|
2632
|
+
this.chunkDir = options.chunkDir;
|
|
2633
|
+
}
|
|
2634
|
+
getFinalPath(fileId, filename) {
|
|
2635
|
+
const safeFilename = filename ? path.basename(filename) : fileId;
|
|
2636
|
+
return path.join(this.uploadDir, safeFilename);
|
|
2637
|
+
}
|
|
2638
|
+
getTempPath(fileId) {
|
|
2639
|
+
const safeId = path.basename(fileId);
|
|
2640
|
+
return path.join(this.chunkDir, safeId);
|
|
2641
|
+
}
|
|
2642
|
+
async statFinal(fileId, filename) {
|
|
2643
|
+
const finalPath = this.getFinalPath(fileId, filename);
|
|
2644
|
+
if (!fs.existsSync(finalPath)) {
|
|
2645
|
+
throw new Error("FILE_MISSING_ON_DISK");
|
|
2646
|
+
}
|
|
2647
|
+
const stat = fs.statSync(finalPath);
|
|
2648
|
+
return { path: finalPath, size: stat.size };
|
|
2649
|
+
}
|
|
2650
|
+
async readFinalRange(fileId, filename, start, length) {
|
|
2651
|
+
const finalPath = this.getFinalPath(fileId, filename);
|
|
2652
|
+
const buffer = Buffer.alloc(length);
|
|
2653
|
+
const fd = fs.openSync(finalPath, "r");
|
|
2654
|
+
try {
|
|
2655
|
+
fs.readSync(fd, buffer, 0, length, start);
|
|
2656
|
+
} finally {
|
|
2657
|
+
fs.closeSync(fd);
|
|
2658
|
+
}
|
|
2659
|
+
return buffer;
|
|
2660
|
+
}
|
|
2661
|
+
async hasTemp(fileId) {
|
|
2662
|
+
const tempPath = this.getTempPath(fileId);
|
|
2663
|
+
return fs.existsSync(tempPath);
|
|
2664
|
+
}
|
|
2665
|
+
async moveTempToFinal(fileId, filename) {
|
|
2666
|
+
const tempPath = this.getTempPath(fileId);
|
|
2667
|
+
const finalPath = this.getFinalPath(fileId, filename);
|
|
2668
|
+
try {
|
|
2669
|
+
await fs.promises.rename(tempPath, finalPath);
|
|
2670
|
+
} catch {
|
|
2671
|
+
await fs.promises.copyFile(tempPath, finalPath);
|
|
2672
|
+
await fs.promises.unlink(tempPath);
|
|
2673
|
+
}
|
|
2674
|
+
return finalPath;
|
|
2675
|
+
}
|
|
2676
|
+
createTempReadStream(fileId) {
|
|
2677
|
+
const tempPath = this.getTempPath(fileId);
|
|
2678
|
+
return fs.createReadStream(tempPath);
|
|
2679
|
+
}
|
|
2680
|
+
};
|
|
2265
2681
|
export {
|
|
2266
2682
|
ATS1_HDR,
|
|
2267
2683
|
ATS1_SCHEMA,
|
|
2268
2684
|
AXIS_MAGIC,
|
|
2269
2685
|
AXIS_OPCODES,
|
|
2686
|
+
AXIS_UPLOAD_FILE_STORE,
|
|
2687
|
+
AXIS_UPLOAD_RECEIPT_SIGNER,
|
|
2688
|
+
AXIS_UPLOAD_SESSION_STORE,
|
|
2270
2689
|
AXIS_VERSION,
|
|
2271
2690
|
ats1_exports as Ats1Codec,
|
|
2691
|
+
AxisFilesDownloadHandler,
|
|
2692
|
+
AxisFilesFinalizeHandler,
|
|
2272
2693
|
AxisFrameZ,
|
|
2273
2694
|
AxisIdDto,
|
|
2274
2695
|
T as AxisPacketTags,
|
|
@@ -2281,6 +2702,7 @@ export {
|
|
|
2281
2702
|
DEFAULT_CONTRACTS,
|
|
2282
2703
|
DEFAULT_TIMEOUT,
|
|
2283
2704
|
Decision,
|
|
2705
|
+
DiskUploadFileStore,
|
|
2284
2706
|
ERR_BAD_SIGNATURE,
|
|
2285
2707
|
ERR_CONTRACT_VIOLATION,
|
|
2286
2708
|
ERR_INVALID_PACKET,
|
|
@@ -2292,14 +2714,18 @@ export {
|
|
|
2292
2714
|
FLAG_HAS_WITNESS,
|
|
2293
2715
|
HANDLER_METADATA_KEY,
|
|
2294
2716
|
Handler,
|
|
2717
|
+
INTENT_BODY_KEY,
|
|
2295
2718
|
INTENT_METADATA_KEY,
|
|
2296
2719
|
INTENT_REQUIREMENTS,
|
|
2297
2720
|
INTENT_ROUTES_KEY,
|
|
2298
2721
|
INTENT_SENSITIVITY_MAP,
|
|
2722
|
+
INTENT_SENSORS_KEY,
|
|
2299
2723
|
INTENT_TIMEOUTS,
|
|
2300
2724
|
Intent,
|
|
2725
|
+
IntentBody,
|
|
2301
2726
|
IntentRouter,
|
|
2302
2727
|
IntentSensitivity,
|
|
2728
|
+
IntentSensors,
|
|
2303
2729
|
MAX_BODY_LEN,
|
|
2304
2730
|
MAX_FRAME_LEN,
|
|
2305
2731
|
MAX_HDR_LEN,
|