@camstack/server 1.2.581 → 1.2.583

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.
@@ -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 = t.procedure.use(async ({ ctx, next, path, getRawInput }) => {
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 new server_1.TRPCError({ code: 'UNAUTHORIZED', message: 'Session ended — sign in again' });
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 new server_1.TRPCError({ code: 'FORBIDDEN', message: 'Admin required' });
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 new Error('Too many pending app confirmations');
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 new Error('Too many pending step-up proofs');
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 types_1 = require("@camstack/types");
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, types_1.SSO_STATE_COOKIE);
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 new Error('Login unavailable — `user-management` capability not registered');
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 types_1 = require("@camstack/types");
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, types_1.errMsg)(error);
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, types_1.errMsg)(err) },
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 new Error(`Package "${name}" is not an allowed @camstack/* package`);
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, types_1.errMsg)(reloadError) },
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 new Error(`Cannot uninstall protected package "${name}"`);
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, types_1.errMsg)(err);
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, types_1.errMsg)(err);
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, types_1.errMsg)(err);
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, types_1.errMsg)(err);
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, types_1.errMsg)(err) },
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, types_1.errMsg)(err) } });
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 new Error(`Package ${packageName} is required and cannot be uninstalled`);
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, types_1.errMsg)(err);
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, types_1.errMsg)(err) },
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, types_1.errMsg)(err) },
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, types_1.errMsg)(err) } });
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, types_1.errMsg)(error);
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, types_1.errMsg)(err) },
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, types_1.errMsg)(error);
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, types_1.errMsg)(error);
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, types_1.errMsg)(err) },
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: types_1.EventCategory.SystemRestarting,
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, types_1.errMsg)(err) },
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, types_1.errMsg)(error);
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, types_1.errMsg)(err) } });
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, types_1.errMsg)(err) },
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, types_1.errMsg)(err) } });
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, types_1.errMsg)(err) },
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, types_1.errMsg)(err) } });
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, types_1.errMsg)(error);
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, types_1.errMsg)(error) };
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, types_1.errMsg)(err) } });
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, types_1.errMsg)(err) },
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, types_1.errMsg)(err);
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 new Error('Too many pending handoff codes — try again shortly');
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 new Error(`Share token not found: ${input.id}`);
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 new Error(`Share token not found: ${input.id}`);
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 new Error('Only the token owner or an admin can extend a share token');
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 new Error('A never-expiring share token cannot be extended');
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 new Error(`Share token expired: ${input.id}`);
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 new Error('Only the token owner or an admin can revoke a share token');
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
+ }