@camstack/server 1.1.71 → 1.1.73

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.
@@ -1136,17 +1136,6 @@ function buildAddonsProvider(ar, ps, ls, moleculer, configService, ctx) {
1136
1136
  (0, collection_preference_js_1.persistCollectionDisabled)(configService, input.capName, updated?.disabledProviders ?? []);
1137
1137
  return { success: true };
1138
1138
  },
1139
- updateFrameworkPackage: async (input) => ps.updateFrameworkPackage({
1140
- packageName: input.packageName,
1141
- ...(input.version !== undefined ? { version: input.version } : {}),
1142
- ...(ctx.user?.username !== undefined
1143
- ? { requestedBy: ctx.user.username }
1144
- : ctx.user?.id !== undefined
1145
- ? { requestedBy: ctx.user.id }
1146
- : {}),
1147
- ...(input.deferRestart !== undefined ? { deferRestart: input.deferRestart } : {}),
1148
- runner: lifecycleRunner,
1149
- }),
1150
1139
  getVersions: async (input) => ps.getPackageVersions(input.name),
1151
1140
  restartAddon: async (input) => ar.restartAddon(input.addonId),
1152
1141
  retryLoad: async (input) => {
@@ -75,8 +75,6 @@ function createLifecycleJobRunner(deps) {
75
75
  applyAddonUpdate: deps.applyAddonUpdate,
76
76
  emit: deps.emit,
77
77
  now: nowFn,
78
- stageFramework: deps.stageFramework,
79
- requestFrameworkSwap: deps.requestFrameworkSwap,
80
78
  });
81
79
  /**
82
80
  * Set of jobIds currently executing — used to guard cancellation.
@@ -48,13 +48,25 @@ function requireDeviceScoped(registry, capName) {
48
48
  message: `${String(capName)}.${prop}: input must carry numeric "deviceId"`,
49
49
  });
50
50
  }
51
- const native = registry.getNativeProvider(capName, deviceId);
52
- if (!native) {
53
- // No native provider. For `getStatus`, the device may still have a
54
- // SYNTHESIZED status from device-links (battery/consumables wired
55
- // from sibling sensors). Consult the resolver with a null base
56
- // (the synthesize path) before failing it returns null when the
57
- // device has no links for this cap, preserving today's error.
51
+ // Fast path (unchanged): a per-device native provider registered for
52
+ // the exact (cap, deviceId) pair.
53
+ let provider = registry.getNativeProvider(capName, deviceId);
54
+ // D13 fallback: a device cap may be served by a WRAPPER / system
55
+ // provider bound to this device (e.g. the `virtual-doorbell` builtin
56
+ // registers a SINGLETON wrapperno native). Consult the unified
57
+ // per-device resolver `getProviderForDevice` (native OR device-bound
58
+ // system) BEFORE the synthesize fallback + the final throw. This
59
+ // never runs when a native was found, keeping the fast path intact.
60
+ if (!provider) {
61
+ provider = registry.getProviderForDevice(capName, deviceId);
62
+ }
63
+ if (!provider) {
64
+ // No native and no device-bound provider. For `getStatus`, the
65
+ // device may still have a SYNTHESIZED status from device-links
66
+ // (battery/consumables wired from sibling sensors). Consult the
67
+ // resolver with a null base (the synthesize path) before failing —
68
+ // it returns null when the device has no links for this cap,
69
+ // preserving today's error.
58
70
  if (prop === 'getStatus') {
59
71
  const deviceManager = registry.getSingleton('device-manager');
60
72
  const synthesized = await deviceManager?.resolveLinkedStatus?.({
@@ -70,14 +82,14 @@ function requireDeviceScoped(registry, capName) {
70
82
  message: `Capability "${String(capName)}" not registered for device ${deviceId}`,
71
83
  });
72
84
  }
73
- const fn = native[prop];
85
+ const fn = provider[prop];
74
86
  if (typeof fn !== 'function') {
75
87
  throw new server_1.TRPCError({
76
88
  code: 'NOT_IMPLEMENTED',
77
89
  message: `Capability "${String(capName)}" provider for device ${deviceId} does not implement "${prop}"`,
78
90
  });
79
91
  }
80
- const result = await fn.call(native, input);
92
+ const result = await fn.call(provider, input);
81
93
  // Device-property-wiring overlay (read-time): only `getStatus`, and only
82
94
  // when the device has links for this cap (resolveLinkedStatus returns
83
95
  // null otherwise → base result untouched). One in-process singleton hop.
@@ -605,14 +605,6 @@ function createCapRouter_addons(getProvider, _createRemoteProxy) {
605
605
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
606
606
  return p.setCapabilityProviderEnabled(input);
607
607
  }),
608
- updateFrameworkPackage: trpc_middleware_js_1.adminProcedure
609
- .input(types_10.addonsCapability.methods.updateFrameworkPackage.input.loose())
610
- .output(types_10.addonsCapability.methods.updateFrameworkPackage.output)
611
- .mutation(async ({ input, ctx }) => {
612
- const p = requireCapProvider('addons', () => getProvider(ctx));
613
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
614
- return p.updateFrameworkPackage(input);
615
- }),
616
608
  getVersions: trpc_middleware_js_1.protectedProcedure
617
609
  .input(types_10.addonsCapability.methods.getVersions.input.loose())
618
610
  .output(types_10.addonsCapability.methods.getVersions.output)
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.enrichInputWithUserAgent = enrichInputWithUserAgent;
4
4
  exports.enrichInputWithRelayClass = enrichInputWithRelayClass;
5
+ exports.enrichHintsWithClientClass = enrichHintsWithClientClass;
5
6
  exports.wrapWebrtcSessionProviderWithRelay = wrapWebrtcSessionProviderWithRelay;
6
7
  exports.buildAppRouter = buildAppRouter;
7
8
  const addon_settings_router_js_1 = require("../core/addon-settings.router.js");
@@ -87,6 +88,52 @@ function enrichInputWithRelayClass(input, clientClass) {
87
88
  const relayOnly = FORCE_RELAY_REMOTE ? (0, client_ip_js_1.deriveRelayOnly)(clientClass) : false;
88
89
  return { ...input, relayOnly };
89
90
  }
91
+ /**
92
+ * Initial adaptive rung for a genuinely-REMOTE viewer.
93
+ *
94
+ * A `remote` (public / CGNAT-4G / internet) client cannot reliably receive the
95
+ * high tier's large keyframe over a lossy relay path — LAN can, which is why
96
+ * adaptive works on LAN but a remote adaptive viewer sees a permanent black
97
+ * frame (ICE/DTLS up, no decodable frame). So a remote adaptive session STARTS
98
+ * on the smallest tier; the broker's `AdaptiveController` upgrades from there
99
+ * once the link proves healthy. `lan` and `vpn` (Tailscale, direct overlay)
100
+ * keep the full-quality default and are never biased.
101
+ */
102
+ const REMOTE_INITIAL_ADAPTIVE_TIER = 'low';
103
+ /**
104
+ * Bias the INITIAL adaptive-tier selection for a genuinely-remote viewer by
105
+ * injecting a `prefersTier` hint the broker's `selectBestBroker` already
106
+ * honours. The hub is the only layer that knows the client's network class
107
+ * (the forked broker cannot see the HTTP request), so — exactly like
108
+ * `relayOnly` and the User-Agent — the class is read here and threaded down
109
+ * through the EXISTING `hints` cap field (no cap/schema change: `prefersTier`
110
+ * is already part of `webrtcClientHintsSchema`).
111
+ *
112
+ * Applied ONLY when:
113
+ * - the client class is `remote` (lan/vpn keep the full-quality default), and
114
+ * - the target is adaptive (`handleOffer` defaults an absent target to
115
+ * adaptive; a pinned profile/cam-stream is the operator's explicit choice
116
+ * and passes through untouched), and
117
+ * - the caller did not already set `prefersTier` (no client does today, but
118
+ * keep the trusted-injection rule symmetric with relayOnly/userAgent).
119
+ *
120
+ * Immutable — builds a NEW input (and a new `hints`), never mutates the
121
+ * caller's. On a downgrade re-offer the broker's controller-chosen tier intent
122
+ * overrides this hint, so a healthy remote session can still climb above `low`.
123
+ */
124
+ function enrichHintsWithClientClass(input, clientClass) {
125
+ if (clientClass !== 'remote')
126
+ return input;
127
+ const isAdaptive = input.target === undefined || input.target.kind === 'adaptive';
128
+ if (!isAdaptive)
129
+ return input;
130
+ if (input.hints?.prefersTier !== undefined)
131
+ return input;
132
+ return {
133
+ ...input,
134
+ hints: { ...input.hints, prefersTier: REMOTE_INITIAL_ADAPTIVE_TIER },
135
+ };
136
+ }
90
137
  /**
91
138
  * Per-request wrapper around the resolved `webrtc-session` broker singleton.
92
139
  *
@@ -125,8 +172,8 @@ function wrapWebrtcSessionProviderWithRelay(provider, ctx) {
125
172
  const clientClass = (0, client_ip_js_1.classifyClientRequest)(ctx.req);
126
173
  return {
127
174
  ...provider,
128
- createSession: (input) => provider.createSession(enrichInputWithRelayClass(enrichInputWithUserAgent(input, userAgent), clientClass)),
129
- handleOffer: (input) => provider.handleOffer(enrichInputWithRelayClass(enrichInputWithUserAgent(input, userAgent), clientClass)),
175
+ createSession: (input) => provider.createSession(enrichHintsWithClientClass(enrichInputWithRelayClass(enrichInputWithUserAgent(input, userAgent), clientClass), clientClass)),
176
+ handleOffer: (input) => provider.handleOffer(enrichHintsWithClientClass(enrichInputWithRelayClass(enrichInputWithUserAgent(input, userAgent), clientClass), clientClass)),
130
177
  };
131
178
  }
132
179
  /**
@@ -4,7 +4,6 @@ exports.PostBootService = void 0;
4
4
  const node_crypto_1 = require("node:crypto");
5
5
  const system_1 = require("@camstack/system");
6
6
  const types_1 = require("@camstack/types");
7
- const resume_framework_swap_js_1 = require("./resume-framework-swap.js");
8
7
  const reconcile_lifecycle_jobs_js_1 = require("./reconcile-lifecycle-jobs.js");
9
8
  class PostBootService {
10
9
  eventBus;
@@ -57,24 +56,12 @@ class PostBootService {
57
56
  // restart, …). `readPendingRestart` clears the marker atomically so
58
57
  // we never re-fire on a crash-loop boot.
59
58
  this.emitRestartCompletedIfPending(dataPath);
60
- // If a framework swap was applied on the previous boot and left a
61
- // `.framework-swap-confirm.json` marker, mark the journal job done and
62
- // delete the marker + backups now that the hub is healthy. This also
63
- // disarms the crash-loop rollback (the NEXT boot won't roll back a
64
- // healthy update).
65
- await this.resumeFrameworkSwapIfPending(dataPath);
66
- // Then re-drive every other non-terminal lifecycle job (addon task phases)
67
- // from its on-disk checkpoint. Order matters: framework resume FIRST (marks
68
- // the framework task `applied`→`done`), THEN addon reconcile (which leaves
69
- // framework tasks untouched).
59
+ // Re-drive every non-terminal lifecycle job (addon task phases) from its
60
+ // on-disk checkpoint. Framework tasks are no longer applied by the engine
61
+ // (single-copy collapse framework updates ship via applyServerUpdate);
62
+ // any stray framework task is skipped during reconcile.
70
63
  await this.reconcileLifecycleJobsIfAny();
71
64
  }
72
- async resumeFrameworkSwapIfPending(dataDir) {
73
- const result = await (0, resume_framework_swap_js_1.resumeFrameworkSwapJob)(dataDir);
74
- if (result.resumed) {
75
- this.logger.info('Framework update completed', { meta: { jobId: result.jobId } });
76
- }
77
- }
78
65
  async reconcileLifecycleJobsIfAny() {
79
66
  const { resumed, failed } = await (0, reconcile_lifecycle_jobs_js_1.reconcileLifecycleJobsAtBoot)();
80
67
  if (resumed + failed > 0) {
@@ -8,12 +8,13 @@
8
8
  * resumes from its checkpoint (reusing a still-valid `stagedPath` or
9
9
  * re-fetching, then applying).
10
10
  *
11
- * Best-effort, mirrors `resume-framework-swap.ts`: this NEVER throws — any error
12
- * (runner not initialized, journal corruption, …) is swallowed and reported as
13
- * `{ resumed: 0, failed: 0 }` so the post-boot path can never crash the hub.
11
+ * Best-effort: this NEVER throws — any error (runner not initialized, journal
12
+ * corruption, …) is swallowed and reported as `{ resumed: 0, failed: 0 }` so the
13
+ * post-boot path can never crash the hub.
14
14
  *
15
- * Ordering note: the framework `applied`→`done` resume (`resumeFrameworkSwapJob`)
16
- * MUST run BEFORE this reconcile deliberately leaves framework tasks untouched.
15
+ * Framework tasks are no longer applied by the engine (single-copy collapse —
16
+ * framework updates ship via `applyServerUpdate`); any stray non-terminal
17
+ * framework task is marked `skipped` during reconcile so the job can finalize.
17
18
  */
18
19
  Object.defineProperty(exports, "__esModule", { value: true });
19
20
  exports.reconcileLifecycleJobsAtBoot = reconcileLifecycleJobsAtBoot;