@camstack/server 0.2.2 → 1.0.1

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.
Files changed (234) hide show
  1. package/{src/agent-status-page.ts → dist/agent-status-page.js} +30 -45
  2. package/dist/api/addon-upload.js +441 -0
  3. package/dist/api/addons-custom.router.js +91 -0
  4. package/dist/api/auth-whoami.js +55 -0
  5. package/dist/api/bridge-addons.router.js +109 -0
  6. package/dist/api/capabilities.router.js +229 -0
  7. package/dist/api/core/addon-settings.router.js +117 -0
  8. package/dist/api/core/agents.router.js +73 -0
  9. package/dist/api/core/auth.router.js +286 -0
  10. package/dist/api/core/bulk-update-coordinator.js +229 -0
  11. package/dist/api/core/cap-providers.js +1124 -0
  12. package/dist/api/core/capabilities.router.js +138 -0
  13. package/dist/api/core/collection-preference.js +17 -0
  14. package/dist/api/core/event-bus-proxy.router.js +45 -0
  15. package/dist/api/core/hwaccel.router.js +91 -0
  16. package/dist/api/core/live-events.router.js +61 -0
  17. package/dist/api/core/logs.router.js +172 -0
  18. package/dist/api/core/notifications.router.js +67 -0
  19. package/dist/api/core/repl.router.js +35 -0
  20. package/dist/api/core/settings-backend.router.js +121 -0
  21. package/dist/api/core/stream-probe.router.js +58 -0
  22. package/dist/api/core/system-events.router.js +100 -0
  23. package/dist/api/health/health.routes.js +68 -0
  24. package/{src/api/oauth2/consent-page.ts → dist/api/oauth2/consent-page.js} +11 -20
  25. package/dist/api/oauth2/oauth2-routes.js +219 -0
  26. package/dist/api/trpc/cap-mount-helpers.js +194 -0
  27. package/dist/api/trpc/cap-route-error-formatter.js +133 -0
  28. package/dist/api/trpc/client-ip.js +147 -0
  29. package/dist/api/trpc/core-cap-bridge.js +115 -0
  30. package/dist/api/trpc/generated-cap-mounts.js +388 -0
  31. package/dist/api/trpc/generated-cap-routers.js +7635 -0
  32. package/dist/api/trpc/scope-access.js +93 -0
  33. package/dist/api/trpc/trpc.context.js +184 -0
  34. package/dist/api/trpc/trpc.middleware.js +139 -0
  35. package/dist/api/trpc/trpc.router.js +188 -0
  36. package/dist/auth/session-cookie.js +47 -0
  37. package/dist/boot/boot-config.js +241 -0
  38. package/dist/boot/integration-id-backfill.js +76 -0
  39. package/dist/boot/post-boot.service.js +85 -0
  40. package/dist/core/addon/addon-call-gateway.js +99 -0
  41. package/dist/core/addon/addon-package.service.js +1560 -0
  42. package/dist/core/addon/addon-registry.service.js +2739 -0
  43. package/{src/core/addon/addon-row-manifest.ts → dist/core/addon/addon-row-manifest.js} +5 -5
  44. package/dist/core/addon/addon-search.service.js +62 -0
  45. package/dist/core/addon/addon-settings-provider.js +102 -0
  46. package/dist/core/addon/addon.tokens.js +5 -0
  47. package/dist/core/addon-bridge/addon-bridge.service.js +145 -0
  48. package/dist/core/addon-pages/addon-pages.service.js +107 -0
  49. package/dist/core/addon-widgets/addon-widgets.service.js +120 -0
  50. package/dist/core/agent/agent-registry.service.js +477 -0
  51. package/dist/core/auth/auth.service.js +10 -0
  52. package/dist/core/capability/capability.service.js +58 -0
  53. package/dist/core/config/config.schema.js +7 -0
  54. package/dist/core/config/config.service.js +10 -0
  55. package/dist/core/events/event-bus.service.js +83 -0
  56. package/dist/core/feature/feature.service.js +10 -0
  57. package/dist/core/lifecycle/lifecycle-state-machine.js +6 -0
  58. package/dist/core/logging/log-ring-buffer.js +6 -0
  59. package/dist/core/logging/logging.service.js +130 -0
  60. package/dist/core/logging/scoped-logger.js +6 -0
  61. package/dist/core/moleculer/cap-call-fn.js +50 -0
  62. package/dist/core/moleculer/cap-route-authority.js +122 -0
  63. package/dist/core/moleculer/moleculer.service.js +898 -0
  64. package/dist/core/network/network-quality.service.js +7 -0
  65. package/dist/core/notification/notification-wrapper.service.js +33 -0
  66. package/dist/core/notification/toast-wrapper.service.js +25 -0
  67. package/dist/core/provider/provider.tokens.js +4 -0
  68. package/dist/core/repl/repl-engine.service.js +140 -0
  69. package/dist/core/storage/fs-storage-backend.js +6 -0
  70. package/dist/core/storage/storage-location-manager.js +6 -0
  71. package/dist/core/storage/storage.service.js +7 -0
  72. package/dist/core/streaming/stream-probe.service.js +209 -0
  73. package/dist/core/topology/topology-emitter.service.js +106 -0
  74. package/dist/launcher.js +325 -0
  75. package/dist/main.js +1098 -0
  76. package/dist/manual-boot.js +227 -0
  77. package/package.json +5 -1
  78. package/src/__tests__/addon-install-e2e.test.ts +0 -74
  79. package/src/__tests__/addon-pages-e2e.test.ts +0 -200
  80. package/src/__tests__/addon-route-session.test.ts +0 -17
  81. package/src/__tests__/addon-settings-router.spec.ts +0 -67
  82. package/src/__tests__/addon-upload.spec.ts +0 -475
  83. package/src/__tests__/agent-registry.spec.ts +0 -179
  84. package/src/__tests__/agent-status-page.spec.ts +0 -82
  85. package/src/__tests__/auth-session-cookie.test.ts +0 -48
  86. package/src/__tests__/bulk-update-coordinator.spec.ts +0 -303
  87. package/src/__tests__/cap-ownership-authority.spec.ts +0 -431
  88. package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +0 -206
  89. package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +0 -37
  90. package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +0 -110
  91. package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +0 -292
  92. package/src/__tests__/cap-providers-bulk-update.spec.ts +0 -408
  93. package/src/__tests__/cap-route-adapter.spec.ts +0 -302
  94. package/src/__tests__/cap-routers/_meta.spec.ts +0 -199
  95. package/src/__tests__/cap-routers/addon-settings.router.spec.ts +0 -115
  96. package/src/__tests__/cap-routers/broker-routing.router.spec.ts +0 -177
  97. package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +0 -125
  98. package/src/__tests__/cap-routers/capabilities-node.spec.ts +0 -68
  99. package/src/__tests__/cap-routers/device-link-overlay.spec.ts +0 -137
  100. package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +0 -194
  101. package/src/__tests__/cap-routers/harness.ts +0 -163
  102. package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +0 -133
  103. package/src/__tests__/cap-routers/null-provider-guard.spec.ts +0 -64
  104. package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +0 -159
  105. package/src/__tests__/cap-routers/settings-store.router.spec.ts +0 -291
  106. package/src/__tests__/capability-e2e.test.ts +0 -384
  107. package/src/__tests__/cli-e2e.test.ts +0 -150
  108. package/src/__tests__/core-cap-bridge.spec.ts +0 -91
  109. package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +0 -40
  110. package/src/__tests__/device-settings-contribution-dispatch.spec.ts +0 -280
  111. package/src/__tests__/embedded-deps-e2e.test.ts +0 -125
  112. package/src/__tests__/event-bus-proxy-router.spec.ts +0 -75
  113. package/src/__tests__/fixtures/mock-analysis-addon-a.ts +0 -37
  114. package/src/__tests__/fixtures/mock-analysis-addon-b.ts +0 -37
  115. package/src/__tests__/fixtures/mock-log-addon.ts +0 -37
  116. package/src/__tests__/fixtures/mock-storage-addon.ts +0 -40
  117. package/src/__tests__/framework-allowlist.spec.ts +0 -96
  118. package/src/__tests__/framework-installer-defer-restart.spec.ts +0 -165
  119. package/src/__tests__/https-e2e.test.ts +0 -124
  120. package/src/__tests__/lifecycle-e2e.test.ts +0 -189
  121. package/src/__tests__/live-events-subscription.spec.ts +0 -149
  122. package/src/__tests__/moleculer/uds-readiness.spec.ts +0 -150
  123. package/src/__tests__/moleculer/uds-topology.spec.ts +0 -418
  124. package/src/__tests__/moleculer/uds-unowned-call.spec.ts +0 -383
  125. package/src/__tests__/moleculer-register-node-idempotency.spec.ts +0 -273
  126. package/src/__tests__/native-cap-route.spec.ts +0 -427
  127. package/src/__tests__/oauth2-account-linking.spec.ts +0 -867
  128. package/src/__tests__/post-boot-restart.spec.ts +0 -161
  129. package/src/__tests__/singleton-contention.test.ts +0 -499
  130. package/src/__tests__/streaming-diagnostic.test.ts +0 -615
  131. package/src/__tests__/streaming-scale.test.ts +0 -314
  132. package/src/__tests__/uds-addon-call-wiring.spec.ts +0 -242
  133. package/src/__tests__/uds-log-ingest.spec.ts +0 -183
  134. package/src/api/__tests__/addons-custom.spec.ts +0 -148
  135. package/src/api/__tests__/capabilities.router.test.ts +0 -56
  136. package/src/api/addon-upload.ts +0 -529
  137. package/src/api/addons-custom.router.ts +0 -101
  138. package/src/api/auth-whoami.ts +0 -101
  139. package/src/api/bridge-addons.router.ts +0 -122
  140. package/src/api/capabilities.router.ts +0 -265
  141. package/src/api/core/__tests__/auth-router-totp.spec.ts +0 -297
  142. package/src/api/core/__tests__/integration-markers.spec.ts +0 -10
  143. package/src/api/core/addon-settings.router.ts +0 -127
  144. package/src/api/core/agents.router.ts +0 -86
  145. package/src/api/core/auth.router.ts +0 -322
  146. package/src/api/core/bulk-update-coordinator.ts +0 -305
  147. package/src/api/core/cap-providers.ts +0 -1339
  148. package/src/api/core/capabilities.router.ts +0 -149
  149. package/src/api/core/collection-preference.ts +0 -40
  150. package/src/api/core/event-bus-proxy.router.ts +0 -45
  151. package/src/api/core/hwaccel.router.ts +0 -108
  152. package/src/api/core/live-events.router.ts +0 -67
  153. package/src/api/core/logs.router.ts +0 -195
  154. package/src/api/core/notifications.router.ts +0 -66
  155. package/src/api/core/repl.router.ts +0 -39
  156. package/src/api/core/settings-backend.router.ts +0 -140
  157. package/src/api/core/stream-probe.router.ts +0 -57
  158. package/src/api/core/system-events.router.ts +0 -125
  159. package/src/api/health/health.routes.ts +0 -117
  160. package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +0 -62
  161. package/src/api/oauth2/oauth2-routes.ts +0 -281
  162. package/src/api/trpc/__tests__/client-ip.spec.ts +0 -146
  163. package/src/api/trpc/__tests__/scope-access-device.spec.ts +0 -268
  164. package/src/api/trpc/__tests__/scope-access.spec.ts +0 -102
  165. package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +0 -136
  166. package/src/api/trpc/cap-mount-helpers.ts +0 -245
  167. package/src/api/trpc/cap-route-error-formatter.ts +0 -171
  168. package/src/api/trpc/client-ip.ts +0 -147
  169. package/src/api/trpc/core-cap-bridge.ts +0 -154
  170. package/src/api/trpc/generated-cap-mounts.ts +0 -1240
  171. package/src/api/trpc/generated-cap-routers.ts +0 -11523
  172. package/src/api/trpc/scope-access.ts +0 -110
  173. package/src/api/trpc/trpc.context.ts +0 -258
  174. package/src/api/trpc/trpc.middleware.ts +0 -146
  175. package/src/api/trpc/trpc.router.ts +0 -389
  176. package/src/auth/session-cookie.ts +0 -54
  177. package/src/boot/__tests__/integration-id-backfill.spec.ts +0 -131
  178. package/src/boot/boot-config.ts +0 -259
  179. package/src/boot/integration-id-backfill.ts +0 -109
  180. package/src/boot/post-boot.service.ts +0 -105
  181. package/src/core/addon/__tests__/addon-registry-capability.test.ts +0 -62
  182. package/src/core/addon/__tests__/addon-row-manifest.spec.ts +0 -62
  183. package/src/core/addon/addon-call-gateway.ts +0 -171
  184. package/src/core/addon/addon-package.service.ts +0 -1787
  185. package/src/core/addon/addon-registry.service.ts +0 -3130
  186. package/src/core/addon/addon-search.service.ts +0 -91
  187. package/src/core/addon/addon-settings-provider.ts +0 -220
  188. package/src/core/addon/addon.tokens.ts +0 -2
  189. package/src/core/addon-bridge/addon-bridge.service.ts +0 -130
  190. package/src/core/addon-pages/addon-pages.service.spec.ts +0 -117
  191. package/src/core/addon-pages/addon-pages.service.ts +0 -82
  192. package/src/core/addon-widgets/addon-widgets.service.ts +0 -95
  193. package/src/core/agent/agent-registry.service.ts +0 -529
  194. package/src/core/auth/auth.service.spec.ts +0 -86
  195. package/src/core/auth/auth.service.ts +0 -8
  196. package/src/core/capability/capability.service.ts +0 -66
  197. package/src/core/config/config.schema.ts +0 -3
  198. package/src/core/config/config.service.spec.ts +0 -175
  199. package/src/core/config/config.service.ts +0 -7
  200. package/src/core/events/event-bus.service.spec.ts +0 -235
  201. package/src/core/events/event-bus.service.ts +0 -89
  202. package/src/core/feature/feature.service.spec.ts +0 -99
  203. package/src/core/feature/feature.service.ts +0 -8
  204. package/src/core/lifecycle/lifecycle-state-machine.spec.ts +0 -166
  205. package/src/core/lifecycle/lifecycle-state-machine.ts +0 -3
  206. package/src/core/logging/log-ring-buffer.ts +0 -3
  207. package/src/core/logging/logging.service.spec.ts +0 -287
  208. package/src/core/logging/logging.service.ts +0 -143
  209. package/src/core/logging/scoped-logger.ts +0 -3
  210. package/src/core/moleculer/cap-call-fn.spec.ts +0 -173
  211. package/src/core/moleculer/cap-call-fn.ts +0 -107
  212. package/src/core/moleculer/cap-route-authority.ts +0 -194
  213. package/src/core/moleculer/moleculer.service.ts +0 -1072
  214. package/src/core/network/network-quality.service.spec.ts +0 -53
  215. package/src/core/network/network-quality.service.ts +0 -5
  216. package/src/core/notification/notification-wrapper.service.ts +0 -34
  217. package/src/core/notification/toast-wrapper.service.ts +0 -27
  218. package/src/core/provider/provider.tokens.ts +0 -1
  219. package/src/core/repl/repl-engine.service.spec.ts +0 -444
  220. package/src/core/repl/repl-engine.service.ts +0 -155
  221. package/src/core/storage/fs-storage-backend.spec.ts +0 -70
  222. package/src/core/storage/fs-storage-backend.ts +0 -3
  223. package/src/core/storage/storage-location-manager.spec.ts +0 -130
  224. package/src/core/storage/storage-location-manager.ts +0 -3
  225. package/src/core/storage/storage.service.spec.ts +0 -73
  226. package/src/core/storage/storage.service.ts +0 -3
  227. package/src/core/streaming/stream-probe.service.ts +0 -221
  228. package/src/core/topology/topology-emitter.service.ts +0 -105
  229. package/src/launcher.ts +0 -314
  230. package/src/main.ts +0 -1245
  231. package/src/manual-boot.ts +0 -301
  232. package/tsconfig.build.json +0 -8
  233. package/tsconfig.json +0 -33
  234. package/vitest.config.ts +0 -26
@@ -1,389 +0,0 @@
1
- import { trpcRouter } from './trpc.middleware'
2
- // All capability tRPC routers are auto-mounted via `mountAllCaps()` below.
3
- // Only the OVERRIDES (service-backed providers, custom collection
4
- // dispatch logic, hub-only remote-proxy caps) and the CORE (non-cap)
5
- // routers are wired manually in this file. Tracking comment for the
6
- // historical migration index (every `create<X>Router` that used to live
7
- // here has been replaced by its codegen'd cap-router counterpart):
8
- // auth → auth (kept manual for AuthService DI), users/system →
9
- // `userMgmt`/`system` caps, settings → `system-config` builtin,
10
- // devices → `deviceManager` cap, integrations → `integrations` cap,
11
- // streaming → `streamingManagement` cap, events → `eventQuery` cap,
12
- // logs → kept manual, live → kept manual, processes → `processMgmt`
13
- // cap, agents → `nodes` cap, sessions → `session` cap, trackMedia /
14
- // trackTrail → caps, network →
15
- // `networkQuality` cap, addons → `addons` cap, bridgePipeline removed
16
- // (legacy), detection → `detectionConfig` cap, capabilities → kept
17
- // manual, update → addons cap, addonPages → cap, notification →
18
- // `notifications` (kept manual for NotificationService DI),
19
- // scopedToken → `userManagement`, toast → `toast` cap, inference →
20
- // `pipelineExecutor` cap, pipeline → `pipelineConfig` cap,
21
- // systemEvents → kept manual.
22
- import type { CapabilityRegistry } from '@camstack/kernel'
23
- import type { InferProvider, BrokerConsumerAttribution } from '@camstack/types'
24
- import {
25
- pipelineExecutorCapability,
26
- pipelineRunnerCapability,
27
- pipelineOrchestratorCapability,
28
- audioAnalyzerCapability,
29
- audioCodecCapability,
30
- platformProbeCapability,
31
- decoderCapability,
32
- localNetworkCapability,
33
- webrtcSessionCapability,
34
- } from '@camstack/types'
35
- import {
36
- // The auto-mount covers ~75 caps. The handful re-imported below back
37
- // the manual overrides that need service-backed providers or custom
38
- // collection dispatch logic; the auto-mount entry is replaced by the
39
- // override line via spread precedence.
40
- createCapRouter_pipelineExecutor,
41
- createCapRouter_pipelineRunner,
42
- createCapRouter_pipelineOrchestrator,
43
- createCapRouter_audioAnalyzer,
44
- createCapRouter_audioCodec,
45
- createCapRouter_decoder,
46
- createCapRouter_platformProbe,
47
- createCapRouter_localNetwork,
48
- createCapRouter_snapshotProvider,
49
- createCapRouter_networkQuality,
50
- createCapRouter_system,
51
- createCapRouter_toast,
52
- createCapRouter_integrations,
53
- createCapRouter_nodes,
54
- createCapRouter_addons,
55
- createCapRouter_webrtcSession,
56
- } from './generated-cap-routers'
57
- import { mountAllCaps } from './generated-cap-mounts.js'
58
- import {
59
- buildSystemProvider,
60
- buildNetworkQualityProvider,
61
- buildToastProvider,
62
- buildNodesProvider,
63
- buildIntegrationsProvider,
64
- buildAddonsProvider,
65
- } from '../core/cap-providers.js'
66
- import { createAuthRouter } from '../core/auth.router.js'
67
- import { createAddonSettingsRouter } from '../core/addon-settings.router.js'
68
- import { createSettingsBackendRouter } from '../core/settings-backend.router.js'
69
- import { createEventBusProxyRouter } from '../core/event-bus-proxy.router.js'
70
- import { createReplRouter } from '../core/repl.router.js'
71
- import { createNotificationsRouter } from '../core/notifications.router.js'
72
- import { createLogsRouter } from '../core/logs.router.js'
73
- import { createSystemEventsRouter } from '../core/system-events.router.js'
74
- import { createLiveEventsRouter } from '../core/live-events.router.js'
75
- import { createCapabilitiesRouter } from '../core/capabilities.router.js'
76
- import { createStreamProbeRouter } from '../core/stream-probe.router.js'
77
- import { createHwAccelRouter } from '../core/hwaccel.router.js'
78
- import { requireSingleton, firstSupported, anySupports } from './cap-mount-helpers.js'
79
- import { extractUserAgent } from './client-ip.js'
80
- import type { TrpcContext } from './trpc.context.js'
81
- import type { AuthService } from '../../core/auth/auth.service'
82
- import type { ConfigService } from '../../core/config/config.service'
83
- import type { FeatureService } from '../../core/feature/feature.service'
84
- import type { LoggingService } from '../../core/logging/logging.service'
85
- import type { EventBusService } from '../../core/events/event-bus.service'
86
- import type { AgentRegistryService } from '../../core/agent/agent-registry.service'
87
- import type { MoleculerService } from '../../core/moleculer/moleculer.service'
88
- import type { AddonRegistryService } from '../../core/addon/addon-registry.service'
89
- import type { AddonPackageService } from '../../core/addon/addon-package.service'
90
- import type { ReplEngineService } from '../../core/repl/repl-engine.service'
91
- import type { NetworkQualityService } from '../../core/network/network-quality.service'
92
- import type { AddonBridgeService } from '../../core/addon-bridge/addon-bridge.service'
93
- import type { NotificationService, ToastService } from '@camstack/core'
94
- import type { StreamProbeService } from '../../core/streaming/stream-probe.service'
95
-
96
- export interface RouterServices {
97
- authService: AuthService
98
- configService: ConfigService
99
- featureService: FeatureService
100
- loggingService: LoggingService
101
- eventBus: EventBusService
102
- agentRegistry: AgentRegistryService
103
- moleculer: MoleculerService
104
- addonRegistry: AddonRegistryService
105
- replEngine: ReplEngineService
106
- networkQualityService: NetworkQualityService
107
- addonBridge: AddonBridgeService
108
- addonPackageService: AddonPackageService
109
- notificationService: NotificationService | null
110
- toastService: ToastService | null
111
- capabilityRegistry: CapabilityRegistry | null
112
- streamProbe: StreamProbeService | null
113
- }
114
-
115
- type WebrtcSessionProvider = InferProvider<typeof webrtcSessionCapability>
116
- type CreateSessionInput = Parameters<WebrtcSessionProvider['createSession']>[0]
117
- type HandleOfferInput = Parameters<WebrtcSessionProvider['handleOffer']>[0]
118
-
119
- /**
120
- * Merge the server-read User-Agent into a signaling call's
121
- * `consumerAttribution`, building a NEW input object (immutable — never
122
- * mutates the caller's input). When `userAgent` is null (mesh-originated
123
- * call, or a client that omits the header) the input passes through
124
- * unchanged. Any client-supplied `userAgent` is OVERWRITTEN — the hub
125
- * trusts only the request context, never the client.
126
- */
127
- export function enrichInputWithUserAgent<
128
- TInput extends { consumerAttribution?: BrokerConsumerAttribution },
129
- >(input: TInput, userAgent: string | null): TInput {
130
- if (userAgent === null) return input
131
- const base: BrokerConsumerAttribution = input.consumerAttribution ?? { kind: 'webrtc-browser' }
132
- return {
133
- ...input,
134
- consumerAttribution: { ...base, userAgent },
135
- }
136
- }
137
-
138
- /**
139
- * Relay-only forcing for remote viewers is DISABLED (2026-05-26).
140
- *
141
- * It was meant to give CGNAT/4G viewers a clean relay↔relay path, but werift's
142
- * TURN media-forward is unreliable between two real TURN servers (relay↔relay
143
- * connects yet media never arrives → connected-but-black), and forcing relay
144
- * ALSO kills the direct LAN/Tailscale host pair — which carries full native
145
- * quality with no relay. We now offer ALL candidates (host incl. the hub's
146
- * advertised Tailscale address, srflx, relay) and let ICE nominate the best
147
- * reachable pair: direct when possible, relay only as a fallback. The
148
- * `relayOnly` cap field + broker support remain for when relay media-forward
149
- * is fixed.
150
- *
151
- * The wrapper additionally enriches the `createSession` / `handleOffer`
152
- * subscriber attribution with the originating client's User-Agent, read
153
- * from the tRPC request context (browser sessions). All OTHER methods
154
- * delegate straight through — auth, the remote-proxy factory and every
155
- * signaling behaviour are untouched.
156
- */
157
- export function wrapWebrtcSessionProviderWithRelay(
158
- provider: WebrtcSessionProvider,
159
- ctx: TrpcContext,
160
- ): WebrtcSessionProvider {
161
- const userAgent = extractUserAgent(ctx.req)
162
- return {
163
- ...provider,
164
- createSession: (input: CreateSessionInput) =>
165
- provider.createSession(enrichInputWithUserAgent(input, userAgent)),
166
- handleOffer: (input: HandleOfferInput) =>
167
- provider.handleOffer(enrichInputWithUserAgent(input, userAgent)),
168
- }
169
- }
170
-
171
- /**
172
- * Build the AppRouter. Mounts every codegen'd cap router via the auto-
173
- * mount entrypoint and overrides the handful that need service-backed
174
- * providers or custom collection dispatch. Non-cap (core) routers ride
175
- * alongside in the same root object.
176
- *
177
- * Override-by-spread: spread `mountAllCaps(services)` first, then the
178
- * overrides AFTER — the later property wins. The drift guard in
179
- * `scripts/codegen.ts` ensures every codegen'd `createCapRouter_X` is
180
- * present in the auto-mount inventory (or explicitly in the legacy
181
- * skip-list), so the override list below NEVER needs to add a new entry
182
- * just to mount a new cap — only to swap in a custom provider.
183
- */
184
- function buildCapabilityRouters(services: RouterServices) {
185
- return {
186
- // ── Auto-mount: every codegen'd cap router with a canonical
187
- // provider shape. Everything below this line OVERRIDES the
188
- // auto-mount entry for caps with service-backed providers,
189
- // custom collection routing, or a hub-only `null` remote proxy.
190
- ...mountAllCaps(services),
191
-
192
- // ── Non-cap (core) routers — hand-written, single-impl ──────────
193
- notifications: createNotificationsRouter(services.notificationService),
194
- // Raw DB proxy for forked workers to read/write addon store.
195
- // Workers use ctx.api.addonSettingsRaw.getGlobal.query({...}).
196
- // NOT the three-level settings gateway — that's the codegen'd
197
- // `addonSettings` cap router (mounted via auto-mount above).
198
- addonSettingsRaw: createAddonSettingsRouter(services.configService),
199
- settingsBackend: createSettingsBackendRouter(() => services.addonRegistry.getSettingsBackend()),
200
- eventBusProxy: createEventBusProxyRouter(services.eventBus),
201
- repl: createReplRouter(services.replEngine),
202
- systemEvents: createSystemEventsRouter(services.eventBus),
203
- capabilities: createCapabilitiesRouter(services.capabilityRegistry, services.configService),
204
- logs: createLogsRouter(services.loggingService),
205
- live: createLiveEventsRouter(services.eventBus, services.addonRegistry),
206
- // stream-probe — fixed core API (ffprobe wrapper), not a cap.
207
- streamProbe: createStreamProbeRouter(services.streamProbe),
208
- // hwaccel — fixed core API, wraps the per-node `$hwaccel` Moleculer
209
- // service. UI pipeline / NodeDetail pages query per-node to show
210
- // which hardware backend each agent will use.
211
- hwaccel: createHwAccelRouter(services.moleculer?.broker ?? null),
212
- auth: createAuthRouter(services.authService, services.capabilityRegistry),
213
-
214
- // ── Cap overrides: service-backed providers ─────────────────────
215
- // These caps don't have an addon registering a provider in the
216
- // CapabilityRegistry — the provider is built on-demand from
217
- // backend services. `mountAllCaps` would return `null` for them
218
- // (registry lookup miss), so we re-mount with `buildXProvider`.
219
- networkQuality: createCapRouter_networkQuality((_ctx) =>
220
- buildNetworkQualityProvider(services.networkQualityService),
221
- ),
222
- system: createCapRouter_system((_ctx) =>
223
- buildSystemProvider(services.featureService, services.capabilityRegistry),
224
- ),
225
- toast: createCapRouter_toast((ctx) => buildToastProvider(services.toastService, ctx)),
226
- integrations: createCapRouter_integrations((_ctx) =>
227
- buildIntegrationsProvider(
228
- services.addonRegistry,
229
- services.eventBus,
230
- services.loggingService,
231
- services.capabilityRegistry,
232
- ),
233
- ),
234
- nodes: createCapRouter_nodes((_ctx) =>
235
- buildNodesProvider(services.agentRegistry, services.moleculer, services.addonRegistry),
236
- ),
237
- addons: createCapRouter_addons((ctx) =>
238
- buildAddonsProvider(
239
- services.addonRegistry,
240
- services.addonPackageService,
241
- services.loggingService,
242
- services.moleculer,
243
- services.configService,
244
- ctx,
245
- services.eventBus,
246
- ),
247
- ),
248
-
249
- // ── Cap overrides: cross-node remote-proxy cast ─────────────────
250
- // These caps' providers have manual interface types that pre-date
251
- // `InferProvider<typeof xCap>` — structurally identical, nominally
252
- // distinct. Casting at the override site is cheaper than reworking
253
- // the provider declarations. Auto-mount can't infer the cast.
254
- pipelineExecutor: createCapRouter_pipelineExecutor(
255
- (_ctx) =>
256
- requireSingleton(services.capabilityRegistry, 'pipeline-executor') as InferProvider<
257
- typeof pipelineExecutorCapability
258
- > | null,
259
- (capName, nodeId) =>
260
- services.moleculer.createCapabilityProxy(capName, nodeId) as InferProvider<
261
- typeof pipelineExecutorCapability
262
- > | null,
263
- ),
264
- pipelineRunner: createCapRouter_pipelineRunner(
265
- (_ctx) =>
266
- requireSingleton(services.capabilityRegistry, 'pipeline-runner') as InferProvider<
267
- typeof pipelineRunnerCapability
268
- > | null,
269
- (capName, nodeId) =>
270
- services.moleculer.createCapabilityProxy(capName, nodeId) as InferProvider<
271
- typeof pipelineRunnerCapability
272
- > | null,
273
- ),
274
- pipelineOrchestrator: createCapRouter_pipelineOrchestrator(
275
- (_ctx) =>
276
- requireSingleton(services.capabilityRegistry, 'pipeline-orchestrator') as InferProvider<
277
- typeof pipelineOrchestratorCapability
278
- > | null,
279
- (capName, nodeId) =>
280
- services.moleculer.createCapabilityProxy(capName, nodeId) as InferProvider<
281
- typeof pipelineOrchestratorCapability
282
- > | null,
283
- ),
284
- audioAnalyzer: createCapRouter_audioAnalyzer(
285
- (_ctx) => requireSingleton(services.capabilityRegistry, 'audio-analyzer'),
286
- (capName, nodeId) =>
287
- services.moleculer.createCapabilityProxy(capName, nodeId) as InferProvider<
288
- typeof audioAnalyzerCapability
289
- > | null,
290
- ),
291
- audioCodec: createCapRouter_audioCodec(
292
- (_ctx) =>
293
- requireSingleton(services.capabilityRegistry, 'audio-codec') as InferProvider<
294
- typeof audioCodecCapability
295
- > | null,
296
- (capName, nodeId) =>
297
- services.moleculer.createCapabilityProxy(capName, nodeId) as InferProvider<
298
- typeof audioCodecCapability
299
- > | null,
300
- ),
301
- decoder: createCapRouter_decoder(
302
- (_ctx) =>
303
- requireSingleton(services.capabilityRegistry, 'decoder') as InferProvider<
304
- typeof decoderCapability
305
- > | null,
306
- (capName, nodeId) =>
307
- services.moleculer.createCapabilityProxy(capName, nodeId) as InferProvider<
308
- typeof decoderCapability
309
- > | null,
310
- ),
311
- platformProbe: createCapRouter_platformProbe(
312
- (_ctx) => requireSingleton(services.capabilityRegistry, 'platform-probe'),
313
- (capName, nodeId) =>
314
- services.moleculer.createCapabilityProxy(capName, nodeId) as InferProvider<
315
- typeof platformProbeCapability
316
- > | null,
317
- ),
318
-
319
- // ── Cap overrides: hub-only, no remote fallback ─────────────────
320
- // The cap is intentionally single-node; agents are not directly
321
- // addressable. Auto-mount would still set up a proxy factory; we
322
- // explicitly return `null` to short-circuit any cross-node attempt.
323
- localNetwork: createCapRouter_localNetwork(
324
- (_ctx) => requireSingleton(services.capabilityRegistry, 'local-network'),
325
- (_capName, _nodeId) => null as InferProvider<typeof localNetworkCapability> | null,
326
- ),
327
-
328
- // ── Cap overrides: collection dispatch (contribution / probe) ──
329
- // `turn-provider.getTurnServers` is now handled generically by the
330
- // auto-mount: it's an array-output method on a `collection` cap, so
331
- // `mountAllCaps` fans it across every enabled provider via
332
- // `concatCollection` when no `addonId` is supplied. No hand-written
333
- // override needed.
334
- //
335
- // `snapshot-provider.supportsDevice` is an OR across providers;
336
- // `getSnapshot` picks the first one that claims the device. The
337
- // generic first-provider resolver from the auto-mount can't model
338
- // this — we hand-write the probe + fan-out logic.
339
- snapshotProvider: createCapRouter_snapshotProvider((_ctx) => {
340
- const reg = services.capabilityRegistry
341
- if (!reg) return null
342
- type SnapshotProviderInferred =
343
- import('@camstack/types').CapabilityProviderMap['snapshot-provider']
344
- const providers = reg.getCollection<SnapshotProviderInferred>('snapshot-provider')
345
- if (!providers || providers.length === 0) return null
346
- const supportsDevice = anySupports(providers, 'supportsDevice')
347
- const getSnapshot = firstSupported(providers, 'supportsDevice', 'getSnapshot')
348
- return { supportsDevice, getSnapshot }
349
- }),
350
-
351
- // ── Cap override: server-detected remote → relay-only ────────────
352
- // The broker (a forked addon) can't see the HTTP request, so it
353
- // can't tell a LAN viewer from a remote one. We override only the
354
- // `getProvider` accessor to return a per-request provider whose
355
- // `createSession` carries a server-computed `relayOnly` flag derived
356
- // from the client IP in `ctx.req`. Remote (CGNAT/4G) viewers force
357
- // TURN-relay-only ICE; LAN viewers keep the direct host/srflx path.
358
- // All other methods delegate straight through, and the cross-node
359
- // remote-proxy routing is preserved (forked/agent-hosted brokers).
360
- webrtcSession: createCapRouter_webrtcSession(
361
- (ctx) => {
362
- const provider =
363
- services.capabilityRegistry?.getSingleton<WebrtcSessionProvider>('webrtc-session') ?? null
364
- return provider ? wrapWebrtcSessionProviderWithRelay(provider, ctx) : null
365
- },
366
- (capName, nodeId) =>
367
- services.moleculer.createCapabilityProxy(capName, nodeId) as WebrtcSessionProvider | null,
368
- ),
369
-
370
- // NOT MOUNTED — legacy provider shapes (positional args / sync
371
- // returns) that don't match the codegen routers' {input}-object +
372
- // Promise<T> contract. Tracked by `LEGACY_SHAPE_SKIP` in
373
- // `generated-cap-mounts.ts` until the provider refactor (task #195):
374
- // - addon-routes (IAddonRouteProvider: getRoutes sync)
375
- // - auth-provider (IAuthProvider: positional credentials)
376
- // - log-destination (ILogDestination: positional + extra lifecycle)
377
- // - restreamer (IRestreamer: registerDevice positional)
378
- // - streaming-engine (IStreamingEngine: registerStream positional)
379
- // - webrtc (IWebRtcProvider: missing hasAdaptiveBitrate)
380
- }
381
- }
382
-
383
- export function buildAppRouter(services: RouterServices) {
384
- return trpcRouter({
385
- ...buildCapabilityRouters(services),
386
- })
387
- }
388
-
389
- export type AppRouter = ReturnType<typeof buildAppRouter>
@@ -1,54 +0,0 @@
1
- /** Browser session cookie carrying the hub JWT. Set by POST /api/auth/session
2
- * after a tRPC login; read by the addon-route catch-all for `authenticated`
3
- * routes hit by a plain browser navigation. */
4
- export const SESSION_COOKIE = 'camstack_session'
5
-
6
- interface CookieSpec {
7
- readonly name: string
8
- readonly value: string
9
- readonly options: {
10
- httpOnly: boolean
11
- sameSite: 'lax'
12
- secure: boolean
13
- path: string
14
- maxAge: number
15
- }
16
- }
17
-
18
- export function buildSessionCookie(token: string, ttlSec: number): CookieSpec {
19
- return {
20
- name: SESSION_COOKIE,
21
- value: token,
22
- options: { httpOnly: true, sameSite: 'lax', secure: true, path: '/', maxAge: ttlSec },
23
- }
24
- }
25
-
26
- export function clearSessionCookie(): CookieSpec {
27
- return {
28
- name: SESSION_COOKIE,
29
- value: '',
30
- options: { httpOnly: true, sameSite: 'lax', secure: true, path: '/', maxAge: 0 },
31
- }
32
- }
33
-
34
- /** A browser navigation we can bounce to the login page: a top-level GET
35
- * that wants HTML. Anything else (API call, POST, non-HTML) keeps the
36
- * 401 behavior so programmatic clients get a clean error. */
37
- export function shouldRedirectToLogin(method: string, accept: string | undefined): boolean {
38
- return method === 'GET' && typeof accept === 'string' && accept.includes('text/html')
39
- }
40
-
41
- /** Build the `/login?next=…` URL for an unauthenticated browser request. */
42
- export function loginRedirectUrl(originalUrl: string): string {
43
- return `/login?next=${encodeURIComponent(originalUrl)}`
44
- }
45
-
46
- /** Allowed redirect target for `GET /api/embed-auth`: a same-origin RELATIVE
47
- * path to a stream-broker embed page. Defeats open-redirects — the endpoint
48
- * sets the session cookie from a Bearer token, so the `next` must be safe to
49
- * bounce to. Rejects absolute/protocol-relative URLs, backslashes, and `..`. */
50
- export function isEmbedRedirectTarget(next: string): boolean {
51
- if (!next.startsWith('/addon/stream-broker/embed/')) return false
52
- if (next.includes('\\') || next.includes('://') || next.includes('..')) return false
53
- return true
54
- }
@@ -1,131 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import {
3
- planIntegrationIdBackfill,
4
- planDeleteTimeStamps,
5
- runIntegrationIdBackfill,
6
- } from '../integration-id-backfill'
7
-
8
- describe('planDeleteTimeStamps', () => {
9
- it("claims an untagged top-level device of the deleted integration's single-integration addon", () => {
10
- const stamps = planDeleteTimeStamps(
11
- 'int_rtsp',
12
- [{ id: 'int_rtsp', addonId: 'provider-rtsp' }],
13
- [
14
- { id: 4, addonId: 'provider-rtsp', parentDeviceId: null },
15
- { id: 6, addonId: 'provider-rtsp', parentDeviceId: null },
16
- ],
17
- )
18
- expect(stamps).toEqual([
19
- { deviceId: 4, integrationId: 'int_rtsp' },
20
- { deviceId: 6, integrationId: 'int_rtsp' },
21
- ])
22
- })
23
-
24
- it('returns no stamps for a multi-integration addon (ambiguous — never auto-claim)', () => {
25
- const stamps = planDeleteTimeStamps(
26
- 'int_a',
27
- [
28
- { id: 'int_a', addonId: 'provider-ha' },
29
- { id: 'int_b', addonId: 'provider-ha' },
30
- ],
31
- [{ id: 11, addonId: 'provider-ha', parentDeviceId: null }],
32
- )
33
- expect(stamps).toEqual([])
34
- })
35
-
36
- it('only returns stamps for the integration being deleted, not siblings of other addons', () => {
37
- const stamps = planDeleteTimeStamps(
38
- 'int_rtsp',
39
- [
40
- { id: 'int_rtsp', addonId: 'provider-rtsp' },
41
- { id: 'int_onvif', addonId: 'provider-onvif' },
42
- ],
43
- [
44
- { id: 4, addonId: 'provider-rtsp', parentDeviceId: null },
45
- { id: 5, addonId: 'provider-onvif', parentDeviceId: null },
46
- ],
47
- )
48
- expect(stamps).toEqual([{ deviceId: 4, integrationId: 'int_rtsp' }])
49
- })
50
-
51
- it('skips devices already tagged with the deleted integration', () => {
52
- const stamps = planDeleteTimeStamps(
53
- 'int_rtsp',
54
- [{ id: 'int_rtsp', addonId: 'provider-rtsp' }],
55
- [{ id: 4, addonId: 'provider-rtsp', parentDeviceId: null, integrationId: 'int_rtsp' }],
56
- )
57
- expect(stamps).toEqual([])
58
- })
59
- })
60
-
61
- describe('planIntegrationIdBackfill', () => {
62
- it('stamps a top-level untagged device whose addon has exactly one integration', () => {
63
- const stamps = planIntegrationIdBackfill(
64
- [{ id: 'int_1', addonId: 'provider-rtsp' }],
65
- [{ id: 10, addonId: 'provider-rtsp', parentDeviceId: null }],
66
- )
67
- expect(stamps).toEqual([{ deviceId: 10, integrationId: 'int_1' }])
68
- })
69
-
70
- it('skips devices whose addon hosts multiple integrations (ambiguous)', () => {
71
- const stamps = planIntegrationIdBackfill(
72
- [
73
- { id: 'int_a', addonId: 'provider-ha' },
74
- { id: 'int_b', addonId: 'provider-ha' },
75
- ],
76
- [{ id: 11, addonId: 'provider-ha', parentDeviceId: null }],
77
- )
78
- expect(stamps).toEqual([])
79
- })
80
-
81
- it('skips already-tagged devices and child devices', () => {
82
- const stamps = planIntegrationIdBackfill(
83
- [{ id: 'int_1', addonId: 'provider-rtsp' }],
84
- [
85
- { id: 12, addonId: 'provider-rtsp', parentDeviceId: null, integrationId: 'int_1' },
86
- { id: 13, addonId: 'provider-rtsp', parentDeviceId: 12 },
87
- ],
88
- )
89
- expect(stamps).toEqual([])
90
- })
91
-
92
- it('skips devices whose addon has no integration', () => {
93
- const stamps = planIntegrationIdBackfill(
94
- [{ id: 'int_1', addonId: 'provider-rtsp' }],
95
- [{ id: 14, addonId: 'provider-onvif', parentDeviceId: null }],
96
- )
97
- expect(stamps).toEqual([])
98
- })
99
- })
100
-
101
- describe('runIntegrationIdBackfill', () => {
102
- it('applies stamps and reports the count, skipping failures', async () => {
103
- const stamped: Array<{ deviceId: number; integrationId: string }> = []
104
- const result = await runIntegrationIdBackfill({
105
- listIntegrations: async () => [{ id: 'int_1', addonId: 'provider-rtsp' }],
106
- listDevices: async () => [
107
- { id: 10, addonId: 'provider-rtsp', parentDeviceId: null },
108
- { id: 11, addonId: 'provider-rtsp', parentDeviceId: null },
109
- ],
110
- setIntegrationId: async (deviceId, integrationId) => {
111
- if (deviceId === 11) throw new Error('boom')
112
- stamped.push({ deviceId, integrationId })
113
- },
114
- logger: { info: () => {}, warn: () => {} },
115
- })
116
- expect(result).toEqual({ stamped: 1 })
117
- expect(stamped).toEqual([{ deviceId: 10, integrationId: 'int_1' }])
118
- })
119
-
120
- it('does nothing when there is nothing to stamp', async () => {
121
- const result = await runIntegrationIdBackfill({
122
- listIntegrations: async () => [],
123
- listDevices: async () => [],
124
- setIntegrationId: async () => {
125
- throw new Error('should not be called')
126
- },
127
- logger: { info: () => {}, warn: () => {} },
128
- })
129
- expect(result).toEqual({ stamped: 0 })
130
- })
131
- })