@camstack/server 1.2.72 → 1.2.74
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-service.js +7 -0
- package/dist/agent/infra-boot-guard.js +55 -0
- package/dist/agent/main.js +25 -3
- package/dist/api/sse-no-compression.js +12 -0
- package/dist/api/trpc/generated-cap-mounts.js +2 -1
- package/dist/api/trpc/generated-cap-routers.js +941 -737
- package/dist/core/auth/share-token.service.js +32 -9
- package/dist/main.js +18 -1
- package/package.json +8 -8
|
@@ -98,15 +98,38 @@ function parseShareToken(data) {
|
|
|
98
98
|
class ShareTokenService {
|
|
99
99
|
getStore;
|
|
100
100
|
logger;
|
|
101
|
+
/**
|
|
102
|
+
* Declaration is one-time per process; reset only if the backend itself
|
|
103
|
+
* is swapped (it never is at runtime — the getter is lazy solely because
|
|
104
|
+
* the sqlite builtin registers after this service is constructed).
|
|
105
|
+
*/
|
|
106
|
+
collectionDeclared = false;
|
|
101
107
|
constructor(getStore, logger = null) {
|
|
102
108
|
this.getStore = getStore;
|
|
103
109
|
this.logger = logger;
|
|
104
110
|
}
|
|
105
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Resolve the backend AND ensure `share_view_tokens` is declared before
|
|
113
|
+
* the first operation. The structured settings backend fail-closes on
|
|
114
|
+
* undeclared collections; this KV shape (`id` PK + `data` TEXT) is
|
|
115
|
+
* byte-identical to what the legacy on-demand path created, so existing
|
|
116
|
+
* rows keep working with no migration.
|
|
117
|
+
*/
|
|
118
|
+
async store() {
|
|
106
119
|
const store = this.getStore();
|
|
107
120
|
if (!store) {
|
|
108
121
|
throw new Error('Share tokens unavailable — settings backend not ready');
|
|
109
122
|
}
|
|
123
|
+
if (!this.collectionDeclared) {
|
|
124
|
+
await store.declareCollection({
|
|
125
|
+
collection: SHARE_TOKENS_COLLECTION,
|
|
126
|
+
columns: [
|
|
127
|
+
{ name: 'id', type: 'TEXT', primaryKey: true, notNull: true },
|
|
128
|
+
{ name: 'data', type: 'TEXT', notNull: true },
|
|
129
|
+
],
|
|
130
|
+
});
|
|
131
|
+
this.collectionDeclared = true;
|
|
132
|
+
}
|
|
110
133
|
return store;
|
|
111
134
|
}
|
|
112
135
|
/**
|
|
@@ -137,7 +160,7 @@ class ShareTokenService {
|
|
|
137
160
|
createdAt: now,
|
|
138
161
|
expiresAt: ttlSec === 'never' ? null : now + ttlSec * 1000,
|
|
139
162
|
};
|
|
140
|
-
await this.store().insert({
|
|
163
|
+
await (await this.store()).insert({
|
|
141
164
|
collection: SHARE_TOKENS_COLLECTION,
|
|
142
165
|
record: { id: record.id, data: { ...record } },
|
|
143
166
|
});
|
|
@@ -161,7 +184,7 @@ class ShareTokenService {
|
|
|
161
184
|
if (!rawToken.startsWith(exports.SHARE_TOKEN_PREFIX))
|
|
162
185
|
return null;
|
|
163
186
|
const tokenHash = crypto.createHash('sha256').update(rawToken).digest('hex');
|
|
164
|
-
const results = await this.store().query({
|
|
187
|
+
const results = await (await this.store()).query({
|
|
165
188
|
collection: SHARE_TOKENS_COLLECTION,
|
|
166
189
|
filter: { where: { tokenHash } },
|
|
167
190
|
});
|
|
@@ -186,7 +209,7 @@ class ShareTokenService {
|
|
|
186
209
|
* permission mismatch so the router surfaces a FORBIDDEN.
|
|
187
210
|
*/
|
|
188
211
|
async revoke(input) {
|
|
189
|
-
const results = await this.store().query({
|
|
212
|
+
const results = await (await this.store()).query({
|
|
190
213
|
collection: SHARE_TOKENS_COLLECTION,
|
|
191
214
|
filter: { where: { id: input.id } },
|
|
192
215
|
});
|
|
@@ -196,13 +219,13 @@ class ShareTokenService {
|
|
|
196
219
|
const record = parseShareToken(first.data);
|
|
197
220
|
if (!record) {
|
|
198
221
|
// Corrupt record — delete it regardless (it can never validate).
|
|
199
|
-
await this.store().delete({ collection: SHARE_TOKENS_COLLECTION, key: input.id });
|
|
222
|
+
await (await this.store()).delete({ collection: SHARE_TOKENS_COLLECTION, key: input.id });
|
|
200
223
|
return true;
|
|
201
224
|
}
|
|
202
225
|
if (!input.callerIsAdmin && record.userId !== input.callerUserId) {
|
|
203
226
|
throw new Error('Only the token owner or an admin can revoke a share token');
|
|
204
227
|
}
|
|
205
|
-
await this.store().delete({ collection: SHARE_TOKENS_COLLECTION, key: input.id });
|
|
228
|
+
await (await this.store()).delete({ collection: SHARE_TOKENS_COLLECTION, key: input.id });
|
|
206
229
|
this.logger?.info('Share token revoked', {
|
|
207
230
|
meta: { id: record.id, byUserId: input.callerUserId },
|
|
208
231
|
});
|
|
@@ -210,7 +233,7 @@ class ShareTokenService {
|
|
|
210
233
|
}
|
|
211
234
|
/** Every share token minted by `userId`, expired ones included. */
|
|
212
235
|
async listForUser(userId) {
|
|
213
|
-
const results = await this.store().query({
|
|
236
|
+
const results = await (await this.store()).query({
|
|
214
237
|
collection: SHARE_TOKENS_COLLECTION,
|
|
215
238
|
filter: { where: { userId } },
|
|
216
239
|
});
|
|
@@ -218,14 +241,14 @@ class ShareTokenService {
|
|
|
218
241
|
}
|
|
219
242
|
/** All share tokens (admin listing). */
|
|
220
243
|
async listAll() {
|
|
221
|
-
const results = await this.store().query({
|
|
244
|
+
const results = await (await this.store()).query({
|
|
222
245
|
collection: SHARE_TOKENS_COLLECTION,
|
|
223
246
|
filter: {},
|
|
224
247
|
});
|
|
225
248
|
return results.map((r) => parseShareToken(r.data)).filter((r) => r !== null);
|
|
226
249
|
}
|
|
227
250
|
async touchLastUsed(record) {
|
|
228
|
-
await this.store().update({
|
|
251
|
+
await (await this.store()).update({
|
|
229
252
|
collection: SHARE_TOKENS_COLLECTION,
|
|
230
253
|
id: record.id,
|
|
231
254
|
data: { ...record, lastUsedAt: Date.now() },
|
package/dist/main.js
CHANGED
|
@@ -42,6 +42,7 @@ const ws_1 = require("@trpc/server/adapters/ws");
|
|
|
42
42
|
const static_1 = __importDefault(require("@fastify/static"));
|
|
43
43
|
const compress_1 = __importDefault(require("@fastify/compress"));
|
|
44
44
|
const cookie_1 = __importDefault(require("@fastify/cookie"));
|
|
45
|
+
const sse_no_compression_js_1 = require("./api/sse-no-compression.js");
|
|
45
46
|
const ws_2 = require("ws");
|
|
46
47
|
const fs = __importStar(require("node:fs"));
|
|
47
48
|
const path = __importStar(require("node:path"));
|
|
@@ -223,7 +224,16 @@ async function bootstrap() {
|
|
|
223
224
|
// replaces — so the telemetry reaches a running hub the same day it is
|
|
224
225
|
// written. See packages/system/src/kernel/heap-watch.ts for why it exists
|
|
225
226
|
// (four silent OOMs in ~15h, one nine seconds after a viewer connected).
|
|
226
|
-
|
|
227
|
+
//
|
|
228
|
+
// The reclaimer is what turns the heartbeat from a report into a fix. hub-main's
|
|
229
|
+
// RSS is a HIGH-WATER MARK — measured over 1019 samples and seven boots, it never
|
|
230
|
+
// once fell below the running maximum of heapTotal+external — because V8 keeps the
|
|
231
|
+
// 256KB pages it committed at a sawtooth peak, and an ordinary major GC does not
|
|
232
|
+
// return them. `createV8Reclaimer()` returns undefined if V8 declines, and the
|
|
233
|
+
// heartbeat then behaves exactly as it did before.
|
|
234
|
+
const heapReclaimer = (0, system_2.createV8Reclaimer)();
|
|
235
|
+
const heapReclaim = heapReclaimer === undefined ? undefined : { reclaim: heapReclaimer };
|
|
236
|
+
(0, system_2.startHeapWatch)('hub-main', undefined, undefined, heapReclaim);
|
|
227
237
|
// Clean up orphaned processes from previous crashes before starting
|
|
228
238
|
cleanupOrphanProcesses();
|
|
229
239
|
// SPA fallback — set later when admin UI is resolved, used by addon route catch-all
|
|
@@ -252,6 +262,13 @@ async function bootstrap() {
|
|
|
252
262
|
// Registered before @fastify/static so the compress plugin wraps the
|
|
253
263
|
// static send path — hashed admin-ui chunks go from ~2MB to ~600KB on
|
|
254
264
|
// the wire. threshold:1024 skips compression for tiny payloads.
|
|
265
|
+
//
|
|
266
|
+
// SSE opt-out FIRST: without it a gzipped low-rate event stream starves
|
|
267
|
+
// silently in the compressor buffer — see `api/sse-no-compression.ts` for
|
|
268
|
+
// the mechanism and the live measurement.
|
|
269
|
+
fastify.addHook('onRequest', async (request) => {
|
|
270
|
+
(0, sse_no_compression_js_1.markSseRequestNoCompression)(request);
|
|
271
|
+
});
|
|
255
272
|
await fastify.register(compress_1.default, { global: true, threshold: 1024 });
|
|
256
273
|
// Data-plane POST bodies: the addon reverse-proxy (`proxyToUpstream`) pipes
|
|
257
274
|
// `request.raw` upstream, but Fastify's default application/json parser would
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.74",
|
|
4
4
|
"private": false,
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -33,19 +33,19 @@
|
|
|
33
33
|
]
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
36
|
+
"@camstack/addon-admin-ui": "1.2.35",
|
|
37
37
|
"@camstack/addon-agent-ui": "1.2.10",
|
|
38
38
|
"@camstack/addon-auth": "1.2.11",
|
|
39
39
|
"@camstack/addon-decoder-nodeav": "1.2.9",
|
|
40
40
|
"@camstack/addon-notifiers": "1.2.13",
|
|
41
|
-
"@camstack/addon-pipeline": "1.2.
|
|
42
|
-
"@camstack/addon-pipeline-orchestrator": "1.2.
|
|
43
|
-
"@camstack/addon-post-analysis": "1.2.
|
|
41
|
+
"@camstack/addon-pipeline": "1.2.46",
|
|
42
|
+
"@camstack/addon-pipeline-orchestrator": "1.2.29",
|
|
43
|
+
"@camstack/addon-post-analysis": "1.2.51",
|
|
44
44
|
"@camstack/sdk": "1.2.10",
|
|
45
45
|
"@camstack/shm-ring": "1.1.9",
|
|
46
|
-
"@camstack/system": "1.2.
|
|
47
|
-
"@camstack/types": "1.2.
|
|
48
|
-
"@camstack/ui-library": "1.2.
|
|
46
|
+
"@camstack/system": "1.2.61",
|
|
47
|
+
"@camstack/types": "1.2.45",
|
|
48
|
+
"@camstack/ui-library": "1.2.33",
|
|
49
49
|
"@fastify/compress": "^9.0.0",
|
|
50
50
|
"@fastify/cookie": "^11.0.2",
|
|
51
51
|
"@fastify/cors": "^11.2.0",
|