@camstack/server 1.2.580 → 1.2.582
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/api/core/auth-device-cert.procedures.js +3 -5
- package/dist/api/core/auth-reauth.procedures.js +4 -3
- package/dist/api/core/auth-sessions.procedures.js +2 -1
- package/dist/api/core/auth.router.js +23 -30
- package/dist/api/core/live-events.router.js +5 -4
- package/dist/api/trpc/cap-route-error-formatter.js +24 -0
- package/dist/api/trpc/generated-cap-mounts.js +17 -1
- package/dist/api/trpc/generated-cap-routers.js +1689 -1640
- package/dist/api/trpc/localized-schema-paths.js +26 -0
- package/dist/api/trpc/trpc.context.js +3 -0
- package/dist/api/trpc/trpc.middleware.js +21 -3
- package/dist/api/trpc/user-error.js +17 -0
- package/dist/auth/app-confirmations.js +2 -1
- package/dist/auth/device-enroll-proofs.js +2 -1
- package/dist/auth/sso-finish-deps.js +4 -3
- package/dist/core/addon/addon-package.service.js +36 -35
- package/dist/core/addon/addon-registry.service.js +20 -0
- package/dist/core/auth/handoff-code.service.js +2 -1
- package/dist/core/auth/share-token.service.js +7 -7
- package/dist/i18n/request-lang.js +27 -0
- package/package.json +12 -12
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LOCALIZED_SCHEMA_PATHS = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The tRPC methods whose answer carries a settings schema (`ConfigUISchema`
|
|
6
|
+
* or `ConfigUISchemaWithValues`) - the ONLY places the hub swaps a `labelKey`
|
|
7
|
+
* for the catalog text (D863). Walking every answer would charge every call
|
|
8
|
+
* for a feature one in a hundred uses; an explicit list keeps the cost where
|
|
9
|
+
* the schemas are.
|
|
10
|
+
*
|
|
11
|
+
* A new cap method that returns a schema is added HERE (and nowhere else): a
|
|
12
|
+
* schema served from a path not listed simply stays English.
|
|
13
|
+
*/
|
|
14
|
+
exports.LOCALIZED_SCHEMA_PATHS = new Set([
|
|
15
|
+
'addonSettings.getGlobalSettings',
|
|
16
|
+
'addonSettings.getIntegrationSettings',
|
|
17
|
+
'addonSettings.getDeviceSettings',
|
|
18
|
+
'deviceManager.getDeviceSettingsAggregate',
|
|
19
|
+
'deviceManager.getDeviceLiveInfoAggregate',
|
|
20
|
+
'deviceManager.getSettingsSchema',
|
|
21
|
+
'deviceManager.getConfigSchema',
|
|
22
|
+
'deviceManager.getCreationSchema',
|
|
23
|
+
'deviceManager.providerDiscoveryParamsSchema',
|
|
24
|
+
'storage.listProviders',
|
|
25
|
+
'notificationOutput.listTargetKinds',
|
|
26
|
+
]);
|
|
@@ -7,6 +7,7 @@ const system_1 = require("@camstack/system");
|
|
|
7
7
|
const types_1 = require("@camstack/types");
|
|
8
8
|
const auth_network_policy_js_1 = require("../../auth/auth-network-policy.js");
|
|
9
9
|
const share_token_service_js_1 = require("../../core/auth/share-token.service.js");
|
|
10
|
+
const request_lang_js_1 = require("../../i18n/request-lang.js");
|
|
10
11
|
const trpc_error_principal_js_1 = require("./trpc-error-principal.js");
|
|
11
12
|
/**
|
|
12
13
|
* Live-scope refresh memo (see the JWT branch of `resolveUser`).
|
|
@@ -346,6 +347,7 @@ async function createTrpcContext(req, authService, addonRegistry, shareTokens =
|
|
|
346
347
|
return {
|
|
347
348
|
user: await resolveUser(token, authService, addonRegistry, shareTokens, logger, requestFacts(req)),
|
|
348
349
|
req,
|
|
350
|
+
lang: (0, request_lang_js_1.resolveRequestLang)((0, request_lang_js_1.requestLangInputFrom)(req.headers, readQuery(req))),
|
|
349
351
|
deviceScopeLookup: makeDeviceScopeLookup(addonRegistry),
|
|
350
352
|
deviceFleet: () => addonRegistry.getPersistedDeviceList(),
|
|
351
353
|
};
|
|
@@ -364,6 +366,7 @@ async function createWsTrpcContext(opts, authService, addonRegistry, shareTokens
|
|
|
364
366
|
return {
|
|
365
367
|
user,
|
|
366
368
|
req: opts.req,
|
|
369
|
+
lang: (0, request_lang_js_1.resolveRequestLang)((0, request_lang_js_1.requestLangInputFrom)(opts.req.headers, readQuery(opts.req), opts.info.connectionParams?.['lang'])),
|
|
367
370
|
...(user === null && token !== null && token !== '' ? { credentialRejected: true } : {}),
|
|
368
371
|
...(user !== null && sessionId !== undefined
|
|
369
372
|
? {
|
|
@@ -7,13 +7,16 @@ exports.adminProcedure = exports.protectedProcedure = exports.createCallerFactor
|
|
|
7
7
|
exports.iterableSubscription = iterableSubscription;
|
|
8
8
|
exports.iterableInterval = iterableInterval;
|
|
9
9
|
const system_1 = require("@camstack/system");
|
|
10
|
+
const types_1 = require("@camstack/types");
|
|
10
11
|
const server_1 = require("@trpc/server");
|
|
11
12
|
const superjson_1 = __importDefault(require("superjson"));
|
|
12
13
|
const cap_route_error_formatter_js_1 = require("./cap-route-error-formatter.js");
|
|
13
14
|
const device_config_secret_redaction_js_1 = require("./device-config-secret-redaction.js");
|
|
14
15
|
const principal_visibility_js_1 = require("./principal-visibility.js");
|
|
16
|
+
const localized_schema_paths_js_1 = require("./localized-schema-paths.js");
|
|
15
17
|
const scope_access_js_1 = require("./scope-access.js");
|
|
16
18
|
const share_view_access_js_1 = require("./share-view-access.js");
|
|
19
|
+
const user_error_js_1 = require("./user-error.js");
|
|
17
20
|
const t = server_1.initTRPC.context().create({
|
|
18
21
|
transformer: superjson_1.default,
|
|
19
22
|
errorFormatter: cap_route_error_formatter_js_1.formatTrpcError,
|
|
@@ -63,6 +66,21 @@ async function* iterableInterval(intervalMs, getValue) {
|
|
|
63
66
|
// cleanup handled by generator return
|
|
64
67
|
}
|
|
65
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Outermost middleware (D863): on the methods that serialise a settings schema
|
|
71
|
+
* for the UI, substitute `labelKey` / `descriptionKey` / `titleKey` with the
|
|
72
|
+
* catalog text in the request language. English (and an unknown key) leave the
|
|
73
|
+
* English text the field carries untouched.
|
|
74
|
+
*/
|
|
75
|
+
const localizeSchemaOutput = t.middleware(async ({ ctx, path, next }) => {
|
|
76
|
+
const out = await next();
|
|
77
|
+
const lang = ctx.lang ?? types_1.DEFAULT_CATALOG_LANG;
|
|
78
|
+
if (!out.ok || lang === types_1.DEFAULT_CATALOG_LANG || !localized_schema_paths_js_1.LOCALIZED_SCHEMA_PATHS.has(path))
|
|
79
|
+
return out;
|
|
80
|
+
return { ...out, data: (0, types_1.localizeConfigTree)(out.data, (0, system_1.getCatalogRegistry)().lookupFor(lang)) };
|
|
81
|
+
});
|
|
82
|
+
/** Every authenticated procedure starts from here, so the localizing middleware is the outermost. */
|
|
83
|
+
const localized = t.procedure.use(localizeSchemaOutput);
|
|
66
84
|
exports.trpcRouter = t.router;
|
|
67
85
|
exports.publicProcedure = t.procedure;
|
|
68
86
|
/**
|
|
@@ -92,7 +110,7 @@ exports.createCallerFactory = t.createCallerFactory;
|
|
|
92
110
|
* Single source of authority for caps: `isAdmin`. The legacy role enum
|
|
93
111
|
* collapsed onto this boolean in v2.
|
|
94
112
|
*/
|
|
95
|
-
exports.protectedProcedure =
|
|
113
|
+
exports.protectedProcedure = localized.use(async ({ ctx, next, path, getRawInput }) => {
|
|
96
114
|
if (!ctx.user) {
|
|
97
115
|
throw new server_1.TRPCError({ code: 'UNAUTHORIZED' });
|
|
98
116
|
}
|
|
@@ -100,7 +118,7 @@ exports.protectedProcedure = t.procedure.use(async ({ ctx, next, path, getRawInp
|
|
|
100
118
|
// re-checked on every call, so a logout or revocation ends a socket that is
|
|
101
119
|
// already open — not only the next one.
|
|
102
120
|
if (ctx.sessionLive !== undefined && !ctx.sessionLive()) {
|
|
103
|
-
throw
|
|
121
|
+
throw (0, user_error_js_1.trpcUserError)('UNAUTHORIZED', 'auth.session-ended', undefined, 'Session ended — sign in again');
|
|
104
122
|
}
|
|
105
123
|
// Share-view principals (`csv_*` tokens) NEVER reach the admin bypass,
|
|
106
124
|
// the hand-written-route pass-through, or the scope matcher below —
|
|
@@ -201,7 +219,7 @@ exports.protectedProcedure = t.procedure.use(async ({ ctx, next, path, getRawInp
|
|
|
201
219
|
*/
|
|
202
220
|
exports.adminProcedure = exports.protectedProcedure.use(({ ctx, next }) => {
|
|
203
221
|
if (!ctx.user.isAdmin) {
|
|
204
|
-
throw
|
|
222
|
+
throw (0, user_error_js_1.trpcUserError)('FORBIDDEN', 'auth.admin-required', undefined, 'Admin required');
|
|
205
223
|
}
|
|
206
224
|
return next({ ctx });
|
|
207
225
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.trpcUserError = trpcUserError;
|
|
4
|
+
const server_1 = require("@trpc/server");
|
|
5
|
+
const types_1 = require("@camstack/types");
|
|
6
|
+
/**
|
|
7
|
+
* A `TRPCError` that carries a stable i18n code (D863). `message` stays the
|
|
8
|
+
* English text; the error formatter swaps it for the `server` catalog text in
|
|
9
|
+
* the request language and keeps `{ code, params }` in `data.i18n`.
|
|
10
|
+
*/
|
|
11
|
+
function trpcUserError(trpcCode, i18nCode, params, englishFallback) {
|
|
12
|
+
return new server_1.TRPCError({
|
|
13
|
+
code: trpcCode,
|
|
14
|
+
message: englishFallback,
|
|
15
|
+
cause: (0, types_1.userError)(i18nCode, params, englishFallback),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.AppConfirmations = exports.APP_CONFIRM_COOKIE = exports.APP_CONFIRM_TTL_MS = void 0;
|
|
37
|
+
const types_1 = require("@camstack/types");
|
|
37
38
|
/**
|
|
38
39
|
* Pending "Sign in the CamStack app as <user>?" confirmations (D858).
|
|
39
40
|
*
|
|
@@ -73,7 +74,7 @@ class AppConfirmations {
|
|
|
73
74
|
create(confirmation) {
|
|
74
75
|
this.prune();
|
|
75
76
|
if (this.pending.size >= MAX_PENDING)
|
|
76
|
-
throw
|
|
77
|
+
throw (0, types_1.userError)('server.app-confirmations-busy', undefined, 'Too many pending app confirmations');
|
|
77
78
|
const token = crypto.randomBytes(32).toString('base64url');
|
|
78
79
|
const cookie = crypto.randomBytes(32).toString('base64url');
|
|
79
80
|
this.pending.set(sha256(token), {
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.DeviceEnrollProofs = exports.DEVICE_ENROLL_PROOF_TTL_MS = void 0;
|
|
37
|
+
const types_1 = require("@camstack/types");
|
|
37
38
|
/**
|
|
38
39
|
* Step-up proofs for `auth.enrollSelf` (D855).
|
|
39
40
|
*
|
|
@@ -74,7 +75,7 @@ class DeviceEnrollProofs {
|
|
|
74
75
|
mint(userId) {
|
|
75
76
|
this.prune();
|
|
76
77
|
if (this.pending.size >= MAX_PENDING)
|
|
77
|
-
throw
|
|
78
|
+
throw (0, types_1.userError)('server.step-up-busy', undefined, 'Too many pending step-up proofs');
|
|
78
79
|
const proof = `cdp_${crypto.randomBytes(32).toString('base64url')}`;
|
|
79
80
|
const expiresAt = this.now() + exports.DEVICE_ENROLL_PROOF_TTL_MS;
|
|
80
81
|
this.pending.set(hash(proof), { userId, expiresAt });
|
|
@@ -4,13 +4,14 @@ exports.sessionUserIdFromCookie = sessionUserIdFromCookie;
|
|
|
4
4
|
exports.readCookie = readCookie;
|
|
5
5
|
exports.ssoBrowserBinding = ssoBrowserBinding;
|
|
6
6
|
exports.createSsoFinishDeps = createSsoFinishDeps;
|
|
7
|
+
const types_1 = require("@camstack/types");
|
|
7
8
|
/**
|
|
8
9
|
* The live wiring of `finishSsoLogin`'s deps (D777, D782) — kept out of
|
|
9
10
|
* `main.ts` so the route there stays a three-line mapping.
|
|
10
11
|
*/
|
|
11
12
|
const node_crypto_1 = require("node:crypto");
|
|
12
13
|
const system_1 = require("@camstack/system");
|
|
13
|
-
const
|
|
14
|
+
const types_2 = require("@camstack/types");
|
|
14
15
|
const auth_provider_gate_js_1 = require("./auth-provider-gate.js");
|
|
15
16
|
const passkey_provider_js_1 = require("./passkey-provider.js");
|
|
16
17
|
const second_factors_js_1 = require("./second-factors.js");
|
|
@@ -54,7 +55,7 @@ function readCookie(header, name) {
|
|
|
54
55
|
}
|
|
55
56
|
/** SHA-256 (hex) of the request's SSO state cookie, or `null` without one. */
|
|
56
57
|
function ssoBrowserBinding(cookieHeader) {
|
|
57
|
-
const value = readCookie(cookieHeader,
|
|
58
|
+
const value = readCookie(cookieHeader, types_2.SSO_STATE_COOKIE);
|
|
58
59
|
return value === null ? null : (0, node_crypto_1.createHash)('sha256').update(value).digest('hex');
|
|
59
60
|
}
|
|
60
61
|
function createSsoFinishDeps(wiring, cookieHeader) {
|
|
@@ -62,7 +63,7 @@ function createSsoFinishDeps(wiring, cookieHeader) {
|
|
|
62
63
|
const userManagement = () => {
|
|
63
64
|
const um = registry.getSingleton('user-management');
|
|
64
65
|
if (!um)
|
|
65
|
-
throw
|
|
66
|
+
throw (0, types_1.userError)('server.login-unavailable', undefined, 'Login unavailable — `user-management` capability not registered');
|
|
66
67
|
return um;
|
|
67
68
|
};
|
|
68
69
|
return {
|
|
@@ -37,6 +37,7 @@ exports.AddonPackageService = exports.FRAMEWORK_PACKAGES = exports.AUTO_UPDATE_E
|
|
|
37
37
|
exports.isVersionNewer = isVersionNewer;
|
|
38
38
|
exports.isFrameworkPackage = isFrameworkPackage;
|
|
39
39
|
exports.extractTgzStripped = extractTgzStripped;
|
|
40
|
+
const types_1 = require("@camstack/types");
|
|
40
41
|
const node_child_process_1 = require("node:child_process");
|
|
41
42
|
const node_crypto_1 = require("node:crypto");
|
|
42
43
|
const fs = __importStar(require("node:fs"));
|
|
@@ -44,7 +45,7 @@ const os = __importStar(require("node:os"));
|
|
|
44
45
|
const path = __importStar(require("node:path"));
|
|
45
46
|
const node_util_1 = require("node:util");
|
|
46
47
|
const system_1 = require("@camstack/system");
|
|
47
|
-
const
|
|
48
|
+
const types_2 = require("@camstack/types");
|
|
48
49
|
const update_availability_emitter_js_1 = require("../update-availability-emitter.js");
|
|
49
50
|
const update_check_scheduler_js_1 = require("../updates/update-check-scheduler.js");
|
|
50
51
|
const restart_marker_js_1 = require("../updates/restart-marker.js");
|
|
@@ -257,7 +258,7 @@ class AddonPackageService {
|
|
|
257
258
|
(0, system_1.ensureDir)(addonsDir);
|
|
258
259
|
}
|
|
259
260
|
catch (error) {
|
|
260
|
-
const msg = (0,
|
|
261
|
+
const msg = (0, types_2.errMsg)(error);
|
|
261
262
|
this.logger.warn('Installer init failed', { meta: { error: msg } });
|
|
262
263
|
}
|
|
263
264
|
// Load auto-update config from disk
|
|
@@ -301,7 +302,7 @@ class AddonPackageService {
|
|
|
301
302
|
}
|
|
302
303
|
catch (err) {
|
|
303
304
|
this.logger.debug('Non-fatal: failed to write .install-source marker', {
|
|
304
|
-
meta: { error: (0,
|
|
305
|
+
meta: { error: (0, types_2.errMsg)(err) },
|
|
305
306
|
});
|
|
306
307
|
}
|
|
307
308
|
this.installer.manifest.upsert(result.name, {
|
|
@@ -326,7 +327,7 @@ class AddonPackageService {
|
|
|
326
327
|
async rollbackPackage(name) {
|
|
327
328
|
this.requireInstaller();
|
|
328
329
|
if (!this.isAllowedPackage(name)) {
|
|
329
|
-
throw
|
|
330
|
+
throw (0, types_1.userError)('server.package-not-allowed', { name: String(name) }, `Package "${name}" is not an allowed @camstack/* package`);
|
|
330
331
|
}
|
|
331
332
|
if (exports.AUTO_UPDATE_EXCLUDED_PACKAGES.has(name)) {
|
|
332
333
|
throw new Error(`"${name}" is a framework package — roll it back via serverManagement, not the addon-package path`);
|
|
@@ -357,7 +358,7 @@ class AddonPackageService {
|
|
|
357
358
|
}
|
|
358
359
|
catch (reloadError) {
|
|
359
360
|
this.logger.warn('Restart failed after rollback', {
|
|
360
|
-
meta: { name, error: (0,
|
|
361
|
+
meta: { name, error: (0, types_2.errMsg)(reloadError) },
|
|
361
362
|
});
|
|
362
363
|
}
|
|
363
364
|
// Clear update cache so the UI reflects the new state.
|
|
@@ -368,7 +369,7 @@ class AddonPackageService {
|
|
|
368
369
|
async uninstall(name) {
|
|
369
370
|
this.requireInstaller();
|
|
370
371
|
if (this.isProtected(name)) {
|
|
371
|
-
throw
|
|
372
|
+
throw (0, types_1.userError)('server.package-protected', { name: String(name) }, `Cannot uninstall protected package "${name}"`);
|
|
372
373
|
}
|
|
373
374
|
await this.installer.uninstall(name);
|
|
374
375
|
this.logger.info('Uninstalled', { meta: { name } });
|
|
@@ -395,7 +396,7 @@ class AddonPackageService {
|
|
|
395
396
|
});
|
|
396
397
|
}
|
|
397
398
|
catch (err) {
|
|
398
|
-
const msg = (0,
|
|
399
|
+
const msg = (0, types_2.errMsg)(err);
|
|
399
400
|
this.logger.error('npm install failed', { meta: { packageName, error: msg } });
|
|
400
401
|
this.toastService.broadcast({
|
|
401
402
|
title: 'Install Failed',
|
|
@@ -408,7 +409,7 @@ class AddonPackageService {
|
|
|
408
409
|
await this.reloadPackages();
|
|
409
410
|
}
|
|
410
411
|
catch (err) {
|
|
411
|
-
const msg = (0,
|
|
412
|
+
const msg = (0, types_2.errMsg)(err);
|
|
412
413
|
this.logger.error('reloadPackages failed', { meta: { error: msg } });
|
|
413
414
|
}
|
|
414
415
|
let loaded = [];
|
|
@@ -419,7 +420,7 @@ class AddonPackageService {
|
|
|
419
420
|
failed = result.failed;
|
|
420
421
|
}
|
|
421
422
|
catch (err) {
|
|
422
|
-
const msg = (0,
|
|
423
|
+
const msg = (0, types_2.errMsg)(err);
|
|
423
424
|
this.logger.error('loadNewAddons failed', { meta: { error: msg } });
|
|
424
425
|
failed.push(packageName);
|
|
425
426
|
}
|
|
@@ -446,7 +447,7 @@ class AddonPackageService {
|
|
|
446
447
|
});
|
|
447
448
|
}
|
|
448
449
|
catch (err) {
|
|
449
|
-
const msg = (0,
|
|
450
|
+
const msg = (0, types_2.errMsg)(err);
|
|
450
451
|
this.logger.error('Workspace install failed', { meta: { error: msg } });
|
|
451
452
|
this.toastService.broadcast({ title: 'Install Failed', message: msg, severity: 'warning' });
|
|
452
453
|
throw new Error(`Workspace install failed: ${msg}`, { cause: err });
|
|
@@ -456,7 +457,7 @@ class AddonPackageService {
|
|
|
456
457
|
}
|
|
457
458
|
catch (err) {
|
|
458
459
|
this.logger.warn('Non-fatal: failed to reload packages after workspace install', {
|
|
459
|
-
meta: { error: (0,
|
|
460
|
+
meta: { error: (0, types_2.errMsg)(err) },
|
|
460
461
|
});
|
|
461
462
|
}
|
|
462
463
|
let loaded = [];
|
|
@@ -467,7 +468,7 @@ class AddonPackageService {
|
|
|
467
468
|
failed = result.failed;
|
|
468
469
|
}
|
|
469
470
|
catch (err) {
|
|
470
|
-
this.logger.warn('Failed to load new addons after install', { meta: { error: (0,
|
|
471
|
+
this.logger.warn('Failed to load new addons after install', { meta: { error: (0, types_2.errMsg)(err) } });
|
|
471
472
|
failed.push(packageName);
|
|
472
473
|
}
|
|
473
474
|
this.toastService.broadcast({
|
|
@@ -487,7 +488,7 @@ class AddonPackageService {
|
|
|
487
488
|
*/
|
|
488
489
|
async uninstallAndReload(packageName) {
|
|
489
490
|
if (this.isProtected(packageName)) {
|
|
490
|
-
throw
|
|
491
|
+
throw (0, types_1.userError)('server.package-required', { packageName: String(packageName) }, `Package ${packageName} is required and cannot be uninstalled`);
|
|
491
492
|
}
|
|
492
493
|
try {
|
|
493
494
|
this.addonRegistry.emitUninstallEvent(packageName);
|
|
@@ -496,19 +497,19 @@ class AddonPackageService {
|
|
|
496
497
|
this.logger.info('Uninstall complete', { meta: { packageName } });
|
|
497
498
|
}
|
|
498
499
|
catch (err) {
|
|
499
|
-
const msg = (0,
|
|
500
|
+
const msg = (0, types_2.errMsg)(err);
|
|
500
501
|
this.logger.error('Uninstall failed', { meta: { packageName, error: msg } });
|
|
501
502
|
this.toastService.broadcast({ title: 'Uninstall Failed', message: msg, severity: 'warning' });
|
|
502
503
|
throw new Error(`Uninstall failed: ${msg}`, { cause: err });
|
|
503
504
|
}
|
|
504
505
|
await this.reloadPackages().catch((err) => {
|
|
505
506
|
this.logger.warn('Non-fatal: failed to reload packages after uninstall', {
|
|
506
|
-
meta: { error: (0,
|
|
507
|
+
meta: { error: (0, types_2.errMsg)(err) },
|
|
507
508
|
});
|
|
508
509
|
});
|
|
509
510
|
await this.addonRegistry.loadNewAddons().catch((err) => {
|
|
510
511
|
this.logger.warn('Non-fatal: failed to load new addons after uninstall', {
|
|
511
|
-
meta: { error: (0,
|
|
512
|
+
meta: { error: (0, types_2.errMsg)(err) },
|
|
512
513
|
});
|
|
513
514
|
});
|
|
514
515
|
this.toastService.broadcast({
|
|
@@ -558,7 +559,7 @@ class AddonPackageService {
|
|
|
558
559
|
}
|
|
559
560
|
}
|
|
560
561
|
catch (err) {
|
|
561
|
-
this.logger.warn('Failed to read workspace directory', { meta: { error: (0,
|
|
562
|
+
this.logger.warn('Failed to read workspace directory', { meta: { error: (0, types_2.errMsg)(err) } });
|
|
562
563
|
}
|
|
563
564
|
return results;
|
|
564
565
|
}
|
|
@@ -945,7 +946,7 @@ class AddonPackageService {
|
|
|
945
946
|
return result;
|
|
946
947
|
}
|
|
947
948
|
catch (error) {
|
|
948
|
-
const msg = (0,
|
|
949
|
+
const msg = (0, types_2.errMsg)(error);
|
|
949
950
|
this.logger.warn('Failed to fetch versions', { meta: { name, error: msg } });
|
|
950
951
|
return cached?.versions ?? [];
|
|
951
952
|
}
|
|
@@ -984,7 +985,7 @@ class AddonPackageService {
|
|
|
984
985
|
}
|
|
985
986
|
catch (err) {
|
|
986
987
|
this.logger.warn('Post-update confirmation failed — backup retained for rollback', {
|
|
987
|
-
meta: { name, backupDir, error: (0,
|
|
988
|
+
meta: { name, backupDir, error: (0, types_2.errMsg)(err) },
|
|
988
989
|
});
|
|
989
990
|
}
|
|
990
991
|
}
|
|
@@ -1079,7 +1080,7 @@ class AddonPackageService {
|
|
|
1079
1080
|
return { success: true, version: updatedVersion, requiresRestart: true };
|
|
1080
1081
|
}
|
|
1081
1082
|
catch (error) {
|
|
1082
|
-
const msg = (0,
|
|
1083
|
+
const msg = (0, types_2.errMsg)(error);
|
|
1083
1084
|
this.logger.error('Failed to update package', { meta: { name, error: msg } });
|
|
1084
1085
|
return { success: false, version: '', requiresRestart: false, error: msg };
|
|
1085
1086
|
}
|
|
@@ -1137,7 +1138,7 @@ class AddonPackageService {
|
|
|
1137
1138
|
return { success: true, version: updatedVersion, requiresRestart: false };
|
|
1138
1139
|
}
|
|
1139
1140
|
catch (error) {
|
|
1140
|
-
const msg = (0,
|
|
1141
|
+
const msg = (0, types_2.errMsg)(error);
|
|
1141
1142
|
this.logger.error('Failed to apply staged addon update', { meta: { name, error: msg } });
|
|
1142
1143
|
return { success: false, version: '', requiresRestart: false, error: msg };
|
|
1143
1144
|
}
|
|
@@ -1221,14 +1222,14 @@ class AddonPackageService {
|
|
|
1221
1222
|
catch (err) {
|
|
1222
1223
|
// Marker write failure shouldn't block the restart — log + continue.
|
|
1223
1224
|
this.logger.warn('Failed to write restart marker; restart will proceed without completion toast', {
|
|
1224
|
-
meta: { error: (0,
|
|
1225
|
+
meta: { error: (0, types_2.errMsg)(err) },
|
|
1225
1226
|
});
|
|
1226
1227
|
}
|
|
1227
1228
|
this.eventBusService.emit({
|
|
1228
1229
|
id: (0, node_crypto_1.randomUUID)(),
|
|
1229
1230
|
timestamp: new Date(),
|
|
1230
1231
|
source: { type: 'core', id: 'addon-package-service' },
|
|
1231
|
-
category:
|
|
1232
|
+
category: types_2.EventCategory.SystemRestarting,
|
|
1232
1233
|
// The node, as `system.restart-completed` names it after the boot
|
|
1233
1234
|
// (`post-boot.service.ts`): the announcement and the landing of one
|
|
1234
1235
|
// restart must name the same node, or a stale `node-updating` could not
|
|
@@ -1284,7 +1285,7 @@ class AddonPackageService {
|
|
|
1284
1285
|
catch (err) {
|
|
1285
1286
|
lookupFailed = true;
|
|
1286
1287
|
this.logger.debug('listFrameworkPackages: npm view failed', {
|
|
1287
|
-
meta: { packageName, error: (0,
|
|
1288
|
+
meta: { packageName, error: (0, types_2.errMsg)(err) },
|
|
1288
1289
|
});
|
|
1289
1290
|
}
|
|
1290
1291
|
// STRICTLY newer, never merely different. The hub runs
|
|
@@ -1363,7 +1364,7 @@ class AddonPackageService {
|
|
|
1363
1364
|
this.logger.info('Reloaded', { meta: { count: this.loader.listAddons().length } });
|
|
1364
1365
|
}
|
|
1365
1366
|
catch (error) {
|
|
1366
|
-
const msg = (0,
|
|
1367
|
+
const msg = (0, types_2.errMsg)(error);
|
|
1367
1368
|
this.logger.warn('reloadPackages failed', { meta: { error: msg } });
|
|
1368
1369
|
}
|
|
1369
1370
|
}
|
|
@@ -1451,7 +1452,7 @@ class AddonPackageService {
|
|
|
1451
1452
|
});
|
|
1452
1453
|
this.autoUpdateTimer = setInterval(() => {
|
|
1453
1454
|
this.runAutoUpdate().catch((err) => {
|
|
1454
|
-
this.logger.error('Auto-update check failed', { meta: { error: (0,
|
|
1455
|
+
this.logger.error('Auto-update check failed', { meta: { error: (0, types_2.errMsg)(err) } });
|
|
1455
1456
|
});
|
|
1456
1457
|
}, intervalMs);
|
|
1457
1458
|
}
|
|
@@ -1529,7 +1530,7 @@ class AddonPackageService {
|
|
|
1529
1530
|
}
|
|
1530
1531
|
catch (err) {
|
|
1531
1532
|
this.logger.warn('Auto-update check failed', {
|
|
1532
|
-
meta: { name: pkg.name, error: (0,
|
|
1533
|
+
meta: { name: pkg.name, error: (0, types_2.errMsg)(err) },
|
|
1533
1534
|
});
|
|
1534
1535
|
}
|
|
1535
1536
|
}
|
|
@@ -1554,7 +1555,7 @@ class AddonPackageService {
|
|
|
1554
1555
|
});
|
|
1555
1556
|
}
|
|
1556
1557
|
catch (err) {
|
|
1557
|
-
this.logger.error('Auto-update job failed to start', { meta: { error: (0,
|
|
1558
|
+
this.logger.error('Auto-update job failed to start', { meta: { error: (0, types_2.errMsg)(err) } });
|
|
1558
1559
|
}
|
|
1559
1560
|
}
|
|
1560
1561
|
// =========================================================================
|
|
@@ -1590,7 +1591,7 @@ class AddonPackageService {
|
|
|
1590
1591
|
}
|
|
1591
1592
|
catch (err) {
|
|
1592
1593
|
this.logger.debug('Corrupt auto-update config, falling back to defaults', {
|
|
1593
|
-
meta: { error: (0,
|
|
1594
|
+
meta: { error: (0, types_2.errMsg)(err) },
|
|
1594
1595
|
});
|
|
1595
1596
|
}
|
|
1596
1597
|
return {
|
|
@@ -1613,7 +1614,7 @@ class AddonPackageService {
|
|
|
1613
1614
|
fs.writeFileSync(configPath, JSON.stringify(this.autoUpdateConfig, null, 2));
|
|
1614
1615
|
}
|
|
1615
1616
|
catch (err) {
|
|
1616
|
-
this.logger.warn('Failed to save auto-update config', { meta: { error: (0,
|
|
1617
|
+
this.logger.warn('Failed to save auto-update config', { meta: { error: (0, types_2.errMsg)(err) } });
|
|
1617
1618
|
}
|
|
1618
1619
|
}
|
|
1619
1620
|
/** Resolve the top-level data directory */
|
|
@@ -1708,7 +1709,7 @@ class AddonPackageService {
|
|
|
1708
1709
|
// The package could not be evaluated at all (unreadable manifest, …).
|
|
1709
1710
|
// Counted as a failure for the same reason a registry miss is.
|
|
1710
1711
|
failures += 1;
|
|
1711
|
-
const msg = (0,
|
|
1712
|
+
const msg = (0, types_2.errMsg)(error);
|
|
1712
1713
|
this.logger.warn('Failed to check updates for addon', {
|
|
1713
1714
|
meta: { pkgJsonPath, error: msg },
|
|
1714
1715
|
});
|
|
@@ -1761,7 +1762,7 @@ class AddonPackageService {
|
|
|
1761
1762
|
return { ok: true, latestVersion: version || null };
|
|
1762
1763
|
}
|
|
1763
1764
|
catch (error) {
|
|
1764
|
-
return { ok: false, error: (0,
|
|
1765
|
+
return { ok: false, error: (0, types_2.errMsg)(error) };
|
|
1765
1766
|
}
|
|
1766
1767
|
}
|
|
1767
1768
|
/**
|
|
@@ -1834,7 +1835,7 @@ class AddonPackageService {
|
|
|
1834
1835
|
return results;
|
|
1835
1836
|
}
|
|
1836
1837
|
catch (err) {
|
|
1837
|
-
this.logger.warn('npm search failed', { meta: { error: (0,
|
|
1838
|
+
this.logger.warn('npm search failed', { meta: { error: (0, types_2.errMsg)(err) } });
|
|
1838
1839
|
// Stale cache is better than nothing on transient network failure
|
|
1839
1840
|
return this.searchCache?.results ?? [];
|
|
1840
1841
|
}
|
|
@@ -1870,7 +1871,7 @@ class AddonPackageService {
|
|
|
1870
1871
|
}
|
|
1871
1872
|
catch (err) {
|
|
1872
1873
|
this.logger.debug('Installed-version lookup failed', {
|
|
1873
|
-
meta: { packageName, error: (0,
|
|
1874
|
+
meta: { packageName, error: (0, types_2.errMsg)(err) },
|
|
1874
1875
|
});
|
|
1875
1876
|
}
|
|
1876
1877
|
this.logger.debug('No readable installed copy — previous version is unknown', {
|
|
@@ -1953,7 +1954,7 @@ class AddonPackageService {
|
|
|
1953
1954
|
timestamp: Date.now(),
|
|
1954
1955
|
})
|
|
1955
1956
|
.catch((err) => {
|
|
1956
|
-
const msg = (0,
|
|
1957
|
+
const msg = (0, types_2.errMsg)(err);
|
|
1957
1958
|
this.logger.debug('Update notification failed', { meta: { error: msg } });
|
|
1958
1959
|
});
|
|
1959
1960
|
this.toastService.broadcast({
|
|
@@ -645,6 +645,7 @@ class AddonRegistryService {
|
|
|
645
645
|
: path.join(addonsDir, registered.packageName),
|
|
646
646
|
declaration: registered.declaration,
|
|
647
647
|
});
|
|
648
|
+
this.loadAddonCatalogs(registered.packageName, this.addonEntries.get(registered.declaration.id)?.addonDir);
|
|
648
649
|
}
|
|
649
650
|
catch (err) {
|
|
650
651
|
const msg = (0, types_1.errMsg)(err);
|
|
@@ -1362,6 +1363,20 @@ class AddonRegistryService {
|
|
|
1362
1363
|
// during their own initialize(). The backend no longer loops addons
|
|
1363
1364
|
// looking for `getModelRequirements` / `configure` — those hooks were
|
|
1364
1365
|
// removed from the ICamstackAddon contract.
|
|
1366
|
+
/**
|
|
1367
|
+
* Loads a package's optional `i18n/<lang>.json` catalogs (D863). The
|
|
1368
|
+
* namespace is the package's short name (`@camstack/addon-provider-reolink`
|
|
1369
|
+
* -> `provider-reolink`): one package may host several addons, and the
|
|
1370
|
+
* catalog lives with the package. A broken file is logged and ignored.
|
|
1371
|
+
*/
|
|
1372
|
+
loadAddonCatalogs(packageName, addonDir) {
|
|
1373
|
+
if (addonDir === undefined)
|
|
1374
|
+
return;
|
|
1375
|
+
const ns = (0, system_1.catalogNamespaceOf)(packageName);
|
|
1376
|
+
const catalogs = (0, system_1.getCatalogRegistry)((message) => this.logger.warn(message, { meta: { ns } }));
|
|
1377
|
+
catalogs.clear(ns);
|
|
1378
|
+
catalogs.loadAddonDir(ns, addonDir);
|
|
1379
|
+
}
|
|
1365
1380
|
registerAddon(addon, source = 'installed', packageName, packageVersion) {
|
|
1366
1381
|
const manifest = addon.manifest;
|
|
1367
1382
|
if (!manifest) {
|
|
@@ -2323,6 +2338,7 @@ class AddonRegistryService {
|
|
|
2323
2338
|
addonDir: path.join(addonsDir, registered.packageName),
|
|
2324
2339
|
declaration: registered.declaration,
|
|
2325
2340
|
});
|
|
2341
|
+
this.loadAddonCatalogs(registered.packageName, path.join(addonsDir, registered.packageName));
|
|
2326
2342
|
// Initialize the new addon
|
|
2327
2343
|
await this.initializeAddon(registered.declaration.id);
|
|
2328
2344
|
this.wireCapabilities(registered.declaration.id);
|
|
@@ -3059,6 +3075,10 @@ class AddonRegistryService {
|
|
|
3059
3075
|
return provider().delete(stated);
|
|
3060
3076
|
},
|
|
3061
3077
|
getState: () => provider().getState({}),
|
|
3078
|
+
deleteDeviceScope: (input) => {
|
|
3079
|
+
const stated = { deviceId: input.deviceId, callerAddonId };
|
|
3080
|
+
return provider().deleteDeviceScope(stated);
|
|
3081
|
+
},
|
|
3062
3082
|
};
|
|
3063
3083
|
}
|
|
3064
3084
|
/**
|
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.HandoffCodeService = exports.HANDOFF_CODE_MAX_PENDING = exports.HANDOFF_CODE_TTL_MS = exports.HANDOFF_CODE_PREFIX = void 0;
|
|
37
37
|
exports.pkceChallenge = pkceChallenge;
|
|
38
|
+
const types_1 = require("@camstack/types");
|
|
38
39
|
/**
|
|
39
40
|
* HandoffCodeService — one-time login handoff codes for the native-app
|
|
40
41
|
* auth handoff (viewer "Sign in with CamStack").
|
|
@@ -87,7 +88,7 @@ class HandoffCodeService {
|
|
|
87
88
|
create(userId, options = {}) {
|
|
88
89
|
this.prune();
|
|
89
90
|
if (this.pending.size >= exports.HANDOFF_CODE_MAX_PENDING) {
|
|
90
|
-
throw
|
|
91
|
+
throw (0, types_1.userError)('server.handoff-busy', undefined, 'Too many pending handoff codes — try again shortly');
|
|
91
92
|
}
|
|
92
93
|
const code = `${exports.HANDOFF_CODE_PREFIX}${crypto.randomBytes(32).toString('hex')}`;
|
|
93
94
|
const expiresAt = this.now() + this.ttlMs;
|
|
@@ -58,9 +58,9 @@ exports.ShareTokenService = exports.ShareTokenRecordSchema = exports.ShareTokenS
|
|
|
58
58
|
* live in, reached directly instead of over the settings-store cap since
|
|
59
59
|
* this service is hub-resident). Collection: `share_view_tokens`.
|
|
60
60
|
*/
|
|
61
|
-
const types_1 = require("@camstack/types");
|
|
62
61
|
const crypto = __importStar(require("node:crypto"));
|
|
63
62
|
const zod_1 = require("zod");
|
|
63
|
+
const types_1 = require("@camstack/types");
|
|
64
64
|
/**
|
|
65
65
|
* @durable class=config owner=core-auth
|
|
66
66
|
* write="a user mints a share link; and a best-effort lastUsedAt touch on every successful validation"
|
|
@@ -281,21 +281,21 @@ class ShareTokenService {
|
|
|
281
281
|
});
|
|
282
282
|
const first = results[0];
|
|
283
283
|
if (!first) {
|
|
284
|
-
throw
|
|
284
|
+
throw (0, types_1.userError)('auth.share-token-not-found', { id: input.id }, `Share token not found: ${input.id}`);
|
|
285
285
|
}
|
|
286
286
|
const record = parseShareToken(first.data);
|
|
287
287
|
if (!record) {
|
|
288
|
-
throw
|
|
288
|
+
throw (0, types_1.userError)('auth.share-token-not-found', { id: input.id }, `Share token not found: ${input.id}`);
|
|
289
289
|
}
|
|
290
290
|
if (!input.callerIsAdmin && record.userId !== input.callerUserId) {
|
|
291
|
-
throw
|
|
291
|
+
throw (0, types_1.userError)('auth.share-owner-only-extend', undefined, 'Only the token owner or an admin can extend a share token');
|
|
292
292
|
}
|
|
293
293
|
if (record.expiresAt === null) {
|
|
294
|
-
throw
|
|
294
|
+
throw (0, types_1.userError)('auth.share-never-expiring-extend', undefined, 'A never-expiring share token cannot be extended');
|
|
295
295
|
}
|
|
296
296
|
const now = Date.now();
|
|
297
297
|
if (now > record.expiresAt) {
|
|
298
|
-
throw
|
|
298
|
+
throw (0, types_1.userError)('server.share-token-expired', { id: String(input.id) }, `Share token expired: ${input.id}`);
|
|
299
299
|
}
|
|
300
300
|
const updated = {
|
|
301
301
|
...record,
|
|
@@ -335,7 +335,7 @@ class ShareTokenService {
|
|
|
335
335
|
return true;
|
|
336
336
|
}
|
|
337
337
|
if (!input.callerIsAdmin && record.userId !== input.callerUserId) {
|
|
338
|
-
throw
|
|
338
|
+
throw (0, types_1.userError)('auth.share-owner-only-revoke', undefined, 'Only the token owner or an admin can revoke a share token');
|
|
339
339
|
}
|
|
340
340
|
await (await this.store()).delete({ collection: SHARE_TOKENS_COLLECTION, key: input.id });
|
|
341
341
|
this.logger?.info('Share token revoked', {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveRequestLang = resolveRequestLang;
|
|
4
|
+
exports.requestLangInputFrom = requestLangInputFrom;
|
|
5
|
+
const types_1 = require("@camstack/types");
|
|
6
|
+
const app_page_strings_js_1 = require("../auth/app-page-strings.js");
|
|
7
|
+
function parseLang(value) {
|
|
8
|
+
const parsed = types_1.CatalogLangSchema.safeParse(typeof value === 'string' ? value.toLowerCase() : value);
|
|
9
|
+
return parsed.success ? parsed.data : undefined;
|
|
10
|
+
}
|
|
11
|
+
function resolveRequestLang(input) {
|
|
12
|
+
return (parseLang(input.profileLanguage) ??
|
|
13
|
+
parseLang(input.explicitLang) ??
|
|
14
|
+
(0, app_page_strings_js_1.langFromAcceptLanguage)(input.acceptLanguage) ??
|
|
15
|
+
types_1.DEFAULT_CATALOG_LANG);
|
|
16
|
+
}
|
|
17
|
+
function firstHeader(value) {
|
|
18
|
+
return Array.isArray(value) ? value[0] : value;
|
|
19
|
+
}
|
|
20
|
+
/** Reads the request-borne inputs; `query` is the parsed query object when there is one. */
|
|
21
|
+
function requestLangInputFrom(headers, query, connectionLang) {
|
|
22
|
+
const acceptLanguage = headers['accept-language'];
|
|
23
|
+
return {
|
|
24
|
+
explicitLang: firstHeader(headers['x-camstack-lang']) ?? query?.['lang'] ?? connectionLang,
|
|
25
|
+
...(acceptLanguage !== undefined ? { acceptLanguage } : {}),
|
|
26
|
+
};
|
|
27
|
+
}
|