@camstack/server 1.2.381 → 1.2.383
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.
|
@@ -218,6 +218,8 @@ function buildNetworkQualityProvider(nq) {
|
|
|
218
218
|
jitterMs: input.jitterMs,
|
|
219
219
|
estimatedBandwidthKbps: input.estimatedBandwidthKbps,
|
|
220
220
|
packetLossPercent: input.packetLossPercent,
|
|
221
|
+
...(input.downlinkMbps !== undefined ? { downlinkMbps: input.downlinkMbps } : {}),
|
|
222
|
+
...(input.downlinkAgeMs !== undefined ? { downlinkAgeMs: input.downlinkAgeMs } : {}),
|
|
221
223
|
});
|
|
222
224
|
},
|
|
223
225
|
};
|
|
@@ -1,12 +1,66 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ADAPTIVE_PROBE_BYTES = void 0;
|
|
3
|
+
exports.ADAPTIVE_PROBE_PER_IP_PER_MINUTE = exports.ADAPTIVE_PROBE_BYTES = void 0;
|
|
4
4
|
exports.registerAdaptiveProbeRoute = registerAdaptiveProbeRoute;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Burst blob for adaptive-tier downlink measurement (D635).
|
|
7
|
+
*
|
|
8
|
+
* Size: 1 MiB. The client times the body from its FIRST byte and decides
|
|
9
|
+
* against 2.5 Mbps (`mid`) and 8 Mbps (`high`). At 8 Mbps, 1 MiB is ~1.05 s of
|
|
10
|
+
* transfer, so a 16 ms WebView timer tick is ~1.5 % of the reading and a WAN
|
|
11
|
+
* slow start (~3 RTTs at 50 ms) is ~15 %; the previous 128 KiB was 131 ms at
|
|
12
|
+
* the same rate — a single timer tick was 12 % of it, and timed from the
|
|
13
|
+
* request on a busy hub it measured latency (1.2–1.46 Mbps on a LAN).
|
|
14
|
+
*
|
|
15
|
+
* Cost, including cellular: one probe per camera app-wide per 30 s cache
|
|
16
|
+
* window (the client single-flights it), cut by the client's 2.5 s deadline —
|
|
17
|
+
* at most ~0.8 MB at 2.5 Mbps, 1 MiB on anything faster. That is about three
|
|
18
|
+
* minutes of a 41 kbps sub stream per camera OPEN, not per minute of viewing;
|
|
19
|
+
* renegotiations reuse the cached value and never probe.
|
|
20
|
+
*
|
|
21
|
+
* Access (round 3, review I6): a 100-byte request that returns 1 MiB is an
|
|
22
|
+
* amplifier, and the hub is reachable through relays. The route therefore
|
|
23
|
+
* requires a SESSION — Bearer JWT or the session cookie, the same session-grade
|
|
24
|
+
* check the stream data plane applies — and is rate-limited per client IP.
|
|
25
|
+
* Everything a refusal returns is a few bytes.
|
|
26
|
+
*/
|
|
27
|
+
const node_crypto_1 = require("node:crypto");
|
|
28
|
+
const health_routes_js_1 = require("./health.routes.js");
|
|
29
|
+
const auth_rate_limit_js_1 = require("../../auth/auth-rate-limit.js");
|
|
30
|
+
exports.ADAPTIVE_PROBE_BYTES = 1024 * 1024;
|
|
31
|
+
/**
|
|
32
|
+
* Probes a client IP may draw per minute: 12. The client probes at most once
|
|
33
|
+
* per 30 s per app (single-flight + cache), so 12 leaves room for several
|
|
34
|
+
* apps/devices behind one NAT and caps an abuser at 12 MiB/min.
|
|
35
|
+
*/
|
|
36
|
+
exports.ADAPTIVE_PROBE_PER_IP_PER_MINUTE = 12;
|
|
37
|
+
/**
|
|
38
|
+
* Random, so it is INCOMPRESSIBLE. The previous body was one repeated byte and
|
|
39
|
+
* `@fastify/compress` (global in `main.ts`; its default types include
|
|
40
|
+
* `octet-stream`) gzipped it to a few hundred bytes — the client timed a round
|
|
41
|
+
* trip and called it bandwidth. `compress: false` below stops our own
|
|
42
|
+
* compressor; randomness also defeats one in a relay on the way.
|
|
43
|
+
*/
|
|
44
|
+
const BODY = (0, node_crypto_1.randomBytes)(exports.ADAPTIVE_PROBE_BYTES);
|
|
45
|
+
function registerAdaptiveProbeRoute(fastify, deps) {
|
|
46
|
+
fastify.get('/adaptive-probe', { compress: false }, async (req, reply) => {
|
|
9
47
|
reply.header('cache-control', 'no-store');
|
|
48
|
+
const authorized = (0, health_routes_js_1.isAuthorizedHealthRequest)({
|
|
49
|
+
...(typeof req.headers.authorization === 'string'
|
|
50
|
+
? { authorization: req.headers.authorization }
|
|
51
|
+
: {}),
|
|
52
|
+
...(typeof req.headers.cookie === 'string' ? { cookie: req.headers.cookie } : {}),
|
|
53
|
+
}, deps.verifyToken);
|
|
54
|
+
if (!authorized)
|
|
55
|
+
return reply.status(401).send({ ok: false, error: 'unauthorized' });
|
|
56
|
+
const key = (0, auth_rate_limit_js_1.clientKeyFromRequest)(req);
|
|
57
|
+
if (key !== null) {
|
|
58
|
+
const verdict = deps.limiter.check(`adaptive-probe:${key}`);
|
|
59
|
+
if (!verdict.allowed) {
|
|
60
|
+
reply.header('retry-after', String(verdict.retryAfterSeconds));
|
|
61
|
+
return reply.status(429).send({ ok: false, error: 'rate limited' });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
10
64
|
reply.header('content-type', 'application/octet-stream');
|
|
11
65
|
return reply.send(BODY);
|
|
12
66
|
});
|
|
@@ -3136,6 +3136,15 @@ function createCapRouter_deviceManager(getProvider, createRemoteProxy) {
|
|
|
3136
3136
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
3137
3137
|
return p.getAllBindings(methodInput);
|
|
3138
3138
|
}),
|
|
3139
|
+
listDeviceIdsByCaps: trpc_middleware_js_1.protectedProcedure
|
|
3140
|
+
.input(types_43.deviceManagerCapability.methods.listDeviceIdsByCaps.input.loose())
|
|
3141
|
+
.output(types_43.deviceManagerCapability.methods.listDeviceIdsByCaps.output)
|
|
3142
|
+
.query(async ({ input, ctx }) => {
|
|
3143
|
+
const { nodeId, ...methodInput } = input;
|
|
3144
|
+
const p = resolveProvider('device-manager', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
3145
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
3146
|
+
return p.listDeviceIdsByCaps(methodInput);
|
|
3147
|
+
}),
|
|
3139
3148
|
setWrapperActive: trpc_middleware_js_1.adminProcedure
|
|
3140
3149
|
.input(types_43.deviceManagerCapability.methods.setWrapperActive.input.loose())
|
|
3141
3150
|
.output(types_43.deviceManagerCapability.methods.setWrapperActive.output)
|
|
@@ -4564,6 +4573,33 @@ function createCapRouter_llmRuntime(getProvider, createRemoteProxy) {
|
|
|
4564
4573
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4565
4574
|
return p.getDiskUsage(methodInput);
|
|
4566
4575
|
}),
|
|
4576
|
+
cancel: trpc_middleware_js_1.adminProcedure
|
|
4577
|
+
.input(types_58.llmRuntimeCapability.methods.cancel.input.loose())
|
|
4578
|
+
.output(types_58.llmRuntimeCapability.methods.cancel.output)
|
|
4579
|
+
.mutation(async ({ input, ctx }) => {
|
|
4580
|
+
const { nodeId, ...methodInput } = input;
|
|
4581
|
+
const p = resolveProvider('llm-runtime', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
4582
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4583
|
+
return p.cancel(methodInput);
|
|
4584
|
+
}),
|
|
4585
|
+
appleAvailability: trpc_middleware_js_1.adminProcedure
|
|
4586
|
+
.input(types_58.llmRuntimeCapability.methods.appleAvailability.input.loose())
|
|
4587
|
+
.output(types_58.llmRuntimeCapability.methods.appleAvailability.output)
|
|
4588
|
+
.query(async ({ input, ctx }) => {
|
|
4589
|
+
const { nodeId, ...methodInput } = input;
|
|
4590
|
+
const p = resolveProvider('llm-runtime', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
4591
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4592
|
+
return p.appleAvailability(methodInput);
|
|
4593
|
+
}),
|
|
4594
|
+
completeApple: trpc_middleware_js_1.adminProcedure
|
|
4595
|
+
.input(types_58.llmRuntimeCapability.methods.completeApple.input.loose())
|
|
4596
|
+
.output(types_58.llmRuntimeCapability.methods.completeApple.output)
|
|
4597
|
+
.mutation(async ({ input, ctx }) => {
|
|
4598
|
+
const { nodeId, ...methodInput } = input;
|
|
4599
|
+
const p = resolveProvider('llm-runtime', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
4600
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4601
|
+
return p.completeApple(methodInput);
|
|
4602
|
+
}),
|
|
4567
4603
|
});
|
|
4568
4604
|
}
|
|
4569
4605
|
function createCapRouter_loadContribution(getProvider, createRemoteProxy) {
|
package/dist/main.js
CHANGED
|
@@ -60,6 +60,7 @@ const addon_upload_1 = require("./api/addon-upload");
|
|
|
60
60
|
const auth_whoami_1 = require("./api/auth-whoami");
|
|
61
61
|
const backup_download_js_1 = require("./api/backup-download.js");
|
|
62
62
|
const health_routes_1 = require("./api/health/health.routes");
|
|
63
|
+
const auth_rate_limit_1 = require("./auth/auth-rate-limit");
|
|
63
64
|
const adaptive_probe_routes_1 = require("./api/health/adaptive-probe.routes");
|
|
64
65
|
const model_distributor_js_1 = require("./api/model-distributor.js");
|
|
65
66
|
const oauth2_routes_js_1 = require("./api/oauth2/oauth2-routes.js");
|
|
@@ -583,7 +584,10 @@ async function bootstrap() {
|
|
|
583
584
|
verifyToken: (token) => app.get(auth_service_1.AuthService).verifyToken(token),
|
|
584
585
|
});
|
|
585
586
|
console.log(`[bootstrap] Health routes registered (hub v${hubVersion})`);
|
|
586
|
-
(0, adaptive_probe_routes_1.registerAdaptiveProbeRoute)(fastify
|
|
587
|
+
(0, adaptive_probe_routes_1.registerAdaptiveProbeRoute)(fastify, {
|
|
588
|
+
verifyToken: (token) => app.get(auth_service_1.AuthService).verifyToken(token),
|
|
589
|
+
limiter: (0, auth_rate_limit_1.createRateLimiter)({ windowMs: 60_000, max: adaptive_probe_routes_1.ADAPTIVE_PROBE_PER_IP_PER_MINUTE }),
|
|
590
|
+
});
|
|
587
591
|
}
|
|
588
592
|
catch (err) {
|
|
589
593
|
console.warn('[bootstrap] Failed to register health routes:', err);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.383",
|
|
4
4
|
"private": false,
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -35,18 +35,18 @@
|
|
|
35
35
|
]
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
39
|
-
"@camstack/addon-agent-ui": "1.2.
|
|
38
|
+
"@camstack/addon-admin-ui": "1.2.265",
|
|
39
|
+
"@camstack/addon-agent-ui": "1.2.145",
|
|
40
40
|
"@camstack/addon-auth": "1.2.123",
|
|
41
41
|
"@camstack/addon-decoder-nodeav": "1.2.115",
|
|
42
42
|
"@camstack/addon-notifiers": "1.2.120",
|
|
43
|
-
"@camstack/addon-pipeline": "1.2.
|
|
44
|
-
"@camstack/addon-pipeline-orchestrator": "1.2.
|
|
45
|
-
"@camstack/addon-post-analysis": "1.2.
|
|
43
|
+
"@camstack/addon-pipeline": "1.2.292",
|
|
44
|
+
"@camstack/addon-pipeline-orchestrator": "1.2.250",
|
|
45
|
+
"@camstack/addon-post-analysis": "1.2.280",
|
|
46
46
|
"@camstack/sdk": "1.2.118",
|
|
47
|
-
"@camstack/system": "1.2.
|
|
48
|
-
"@camstack/types": "1.2.
|
|
49
|
-
"@camstack/ui-library": "1.2.
|
|
47
|
+
"@camstack/system": "1.2.298",
|
|
48
|
+
"@camstack/types": "1.2.254",
|
|
49
|
+
"@camstack/ui-library": "1.2.203",
|
|
50
50
|
"@fastify/compress": "^9.0.0",
|
|
51
51
|
"@fastify/cookie": "^11.0.2",
|
|
52
52
|
"@fastify/cors": "^11.2.0",
|