@camstack/server 1.0.0 → 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,39 +0,0 @@
1
- /**
2
- * REPL router — fixed core API (not a capability).
3
- *
4
- * The REPL engine is a single NestJS-backed service in the backend
5
- * (`ReplEngineService` extends `ReplEngine` from `@camstack/core`).
6
- * Only super_admin can execute code.
7
- */
8
- import { z } from 'zod'
9
- import type { ReplEngineService } from '../../core/repl/repl-engine.service.js'
10
- import { trpcRouter, adminProcedure } from '../trpc/trpc.middleware.js'
11
-
12
- const ReplScopeSchema = z.discriminatedUnion('type', [
13
- z.object({ type: z.literal('system') }),
14
- z.object({ type: z.literal('device'), deviceId: z.number() }),
15
- z.object({ type: z.literal('provider'), providerId: z.string() }),
16
- z.object({ type: z.literal('addon'), addonId: z.string() }),
17
- ])
18
-
19
- const ReplResultSchema = z.object({
20
- output: z.string(),
21
- error: z.string().optional(),
22
- duration: z.number().optional(),
23
- })
24
-
25
- export function createReplRouter(repl: ReplEngineService) {
26
- return trpcRouter({
27
- execute: adminProcedure
28
- .input(z.object({ code: z.string().max(10000), scope: ReplScopeSchema }))
29
- .output(ReplResultSchema)
30
- .mutation(({ input }) => repl.execute(input.code, { scope: input.scope, variables: {} })),
31
-
32
- completions: adminProcedure
33
- .input(z.object({ partial: z.string(), scope: ReplScopeSchema }))
34
- .output(z.array(z.string()).readonly())
35
- .query(({ input }) =>
36
- repl.getCompletions(input.partial, { scope: input.scope, variables: {} }),
37
- ),
38
- })
39
- }
@@ -1,140 +0,0 @@
1
- /**
2
- * Settings backend router — tRPC proxy for ISettingsBackend operations.
3
- *
4
- * Exposes the core collection-based operations (get, set, query, insert,
5
- * update, delete, count, isEmpty) so forked worker addons can use
6
- * context.settingsBackend via tRPC instead of requiring in-process access
7
- * to the SQLite database.
8
- *
9
- * Introduced for Task 11 — TrpcSettingsBackend for forked workers.
10
- */
11
- import { z } from 'zod'
12
- import type { ISettingsBackend } from '@camstack/types'
13
- import { trpcRouter, protectedProcedure } from '../trpc/trpc.middleware.js'
14
-
15
- // ---------------------------------------------------------------------------
16
- // Zod schemas
17
- // ---------------------------------------------------------------------------
18
-
19
- const CollectionKeySchema = z.object({
20
- collection: z.string(),
21
- key: z.string(),
22
- })
23
-
24
- const SetValueSchema = z.object({
25
- collection: z.string(),
26
- key: z.string(),
27
- value: z.unknown(),
28
- })
29
-
30
- const QueryFilterSchema = z
31
- .object({
32
- where: z.record(z.string(), z.unknown()).optional(),
33
- whereIn: z.record(z.string(), z.array(z.unknown())).optional(),
34
- whereBetween: z.record(z.string(), z.tuple([z.unknown(), z.unknown()])).optional(),
35
- orderBy: z
36
- .object({
37
- field: z.string(),
38
- direction: z.enum(['asc', 'desc']),
39
- })
40
- .optional(),
41
- limit: z.number().optional(),
42
- offset: z.number().optional(),
43
- })
44
- .optional()
45
-
46
- const QueryInputSchema = z.object({
47
- collection: z.string(),
48
- filter: QueryFilterSchema,
49
- })
50
-
51
- const InsertInputSchema = z.object({
52
- collection: z.string(),
53
- record: z.object({
54
- id: z.string(),
55
- data: z.record(z.string(), z.unknown()),
56
- }),
57
- })
58
-
59
- const UpdateInputSchema = z.object({
60
- collection: z.string(),
61
- id: z.string(),
62
- data: z.record(z.string(), z.unknown()),
63
- })
64
-
65
- const CountInputSchema = z.object({
66
- collection: z.string(),
67
- filter: QueryFilterSchema,
68
- })
69
-
70
- const IsEmptyInputSchema = z.object({
71
- collection: z.string(),
72
- })
73
-
74
- // ---------------------------------------------------------------------------
75
- // Router factory
76
- // ---------------------------------------------------------------------------
77
-
78
- export function createSettingsBackendRouter(getBackend: () => ISettingsBackend | null) {
79
- const requireBackend = (): ISettingsBackend => {
80
- const backend = getBackend()
81
- if (!backend) {
82
- throw new Error(
83
- 'Settings backend not available — settings-store addon may not be initialized yet',
84
- )
85
- }
86
- return backend
87
- }
88
-
89
- return trpcRouter({
90
- get: protectedProcedure.input(CollectionKeySchema).query(async ({ input }) => {
91
- const result = await requireBackend().get(input)
92
- return { value: result }
93
- }),
94
-
95
- set: protectedProcedure.input(SetValueSchema).mutation(async ({ input }) => {
96
- await requireBackend().set({
97
- collection: input.collection,
98
- key: input.key,
99
- value: input.value,
100
- })
101
- return { success: true as const }
102
- }),
103
-
104
- query: protectedProcedure.input(QueryInputSchema).query(async ({ input }) => {
105
- const records = await requireBackend().query({
106
- collection: input.collection,
107
- filter: input.filter ?? undefined,
108
- })
109
- return { records: records.map((r) => ({ id: r.id, data: r.data })) }
110
- }),
111
-
112
- insert: protectedProcedure.input(InsertInputSchema).mutation(async ({ input }) => {
113
- await requireBackend().insert(input)
114
- return { success: true as const }
115
- }),
116
-
117
- update: protectedProcedure.input(UpdateInputSchema).mutation(async ({ input }) => {
118
- await requireBackend().update(input)
119
- return { success: true as const }
120
- }),
121
-
122
- delete: protectedProcedure.input(CollectionKeySchema).mutation(async ({ input }) => {
123
- await requireBackend().delete(input)
124
- return { success: true as const }
125
- }),
126
-
127
- count: protectedProcedure.input(CountInputSchema).query(async ({ input }) => {
128
- const result = await requireBackend().count({
129
- collection: input.collection,
130
- filter: input.filter ?? undefined,
131
- })
132
- return { count: result }
133
- }),
134
-
135
- isEmpty: protectedProcedure.input(IsEmptyInputSchema).query(async ({ input }) => {
136
- const result = await requireBackend().isEmpty(input)
137
- return { empty: result }
138
- }),
139
- })
140
- }
@@ -1,57 +0,0 @@
1
- /**
2
- * Stream probe router — fixed core API (not a capability).
3
- *
4
- * Thin wrapper over `StreamProbeService` (backend REAL_LOGIC: ffprobe +
5
- * HTTP probes + 1h cache). Not pluggable: there is exactly one stream
6
- * probe implementation shipping with the server.
7
- *
8
- * Previously these endpoints lived under `streaming.probeStream` — moved
9
- * here as part of eliminating the `streaming` aggregator capability.
10
- */
11
- import { z } from 'zod'
12
- import { classifyStream } from '@camstack/types'
13
- import type { StreamProbeService } from '../../core/streaming/stream-probe.service.js'
14
- import { trpcRouter, adminProcedure } from '../trpc/trpc.middleware.js'
15
-
16
- const ProbedStreamSchema = z.object({
17
- width: z.number().optional(),
18
- height: z.number().optional(),
19
- codec: z.string().optional(),
20
- fps: z.number().optional(),
21
- bitrateKbps: z.number().optional(),
22
- quality: z.enum(['high', 'mid', 'low']),
23
- })
24
-
25
- const FieldProbeResultSchema = z.object({
26
- status: z.enum(['ok', 'error']),
27
- labels: z.array(z.string()).optional(),
28
- error: z.string().optional(),
29
- })
30
-
31
- export function createStreamProbeRouter(sp: StreamProbeService | null) {
32
- return trpcRouter({
33
- probe: adminProcedure
34
- .input(z.object({ url: z.string(), force: z.boolean().optional() }))
35
- .output(ProbedStreamSchema)
36
- .mutation(async ({ input }) => {
37
- if (!sp) throw new Error('StreamProbeService not available')
38
- const metadata = await sp.probe(input.url, { force: input.force })
39
- return { ...metadata, quality: classifyStream(metadata) }
40
- }),
41
- /**
42
- * Generic field probe — decides stream vs HTTP reachability based on
43
- * the field key (keys starting with `stream` trigger ffprobe;
44
- * everything else falls back to an HTTP GET that aborts at headers).
45
- * Wraps `StreamProbeService.probeField` — used by device providers
46
- * that need the kernel's probe without re-implementing ffprobe /
47
- * HTTP fetch in every addon.
48
- */
49
- probeField: adminProcedure
50
- .input(z.object({ key: z.string(), value: z.unknown() }))
51
- .output(FieldProbeResultSchema)
52
- .mutation(async ({ input }) => {
53
- if (!sp) throw new Error('StreamProbeService not available')
54
- return sp.probeField(input.key, input.value)
55
- }),
56
- })
57
- }
@@ -1,125 +0,0 @@
1
- /**
2
- * System events router — fixed core API (not a capability).
3
- *
4
- * Exposes recent-events query and live subscribe over the `EventBusService`.
5
- * Supports hierarchical filtering via EventFilter: agentId → addonId → deviceId.
6
- * All filters are applied server-side by the SystemEventBus — the client
7
- * only receives matching events.
8
- */
9
- import { z } from 'zod'
10
- import type { SystemEvent } from '@camstack/types'
11
- import type { EventBusService } from '../../core/events/event-bus.service.js'
12
- import { trpcRouter, protectedProcedure, iterableSubscription } from '../trpc/trpc.middleware.js'
13
-
14
- type SerializedEvent = {
15
- id: string
16
- timestamp: string
17
- source: {
18
- type: string
19
- id: string | number
20
- nodeId?: string
21
- addonId?: string
22
- deviceId?: number
23
- }
24
- category: string
25
- data: unknown
26
- }
27
-
28
- function serialize(e: SystemEvent): SerializedEvent {
29
- return {
30
- id: e.id,
31
- timestamp: new Date(e.timestamp).toISOString(),
32
- source: {
33
- type: e.source.type,
34
- id: e.source.id,
35
- ...(e.source.nodeId ? { nodeId: e.source.nodeId } : {}),
36
- ...(e.source.addonId ? { addonId: e.source.addonId } : {}),
37
- ...(e.source.deviceId !== undefined ? { deviceId: e.source.deviceId } : {}),
38
- },
39
- category: e.category,
40
- data: e.data,
41
- }
42
- }
43
-
44
- /**
45
- * Source / agent / addon / device fields shared by query + subscribe.
46
- * Category handling is split (asymmetric): see the per-method schemas
47
- * below.
48
- */
49
- const ScopeFieldsSchema = z.object({
50
- /** Legacy source filter (exact type+id match). */
51
- source: z
52
- .object({
53
- type: z.string(),
54
- id: z.union([z.string(), z.number()]),
55
- })
56
- .optional(),
57
- /** Agent/node filter (prefix match: 'hub' matches 'hub/pipeline'). */
58
- agentId: z.string().optional(),
59
- /** Addon filter. Matches source.addonId or source.id when type='addon'. */
60
- addonId: z.string().optional(),
61
- /** Device filter. Matches source.deviceId or source.id when type='device'. */
62
- deviceId: z.number().optional(),
63
- })
64
-
65
- /**
66
- * `getRecent` accepts a category array so the UI can drive a
67
- * server-side whitelist when reading the historical buffer. Without
68
- * this, getRecent returns the last `limit` events of any category and
69
- * a noisy category (per-frame metrics) can displace relevant events
70
- * (provider.motion, detection.result) out of the window — which was
71
- * the symptom of "no events visible after page refresh".
72
- *
73
- * The underlying ring-buffer filter
74
- * (`addon-context-factory.ts:getRecent`) already iterates the
75
- * `category` field as `string | string[]`.
76
- */
77
- const GetRecentInputSchema = ScopeFieldsSchema.extend({
78
- category: z.union([z.string(), z.array(z.string())]).optional(),
79
- limit: z.number().int().min(1).max(500).optional(),
80
- })
81
-
82
- /**
83
- * `subscribe` keeps category as a single string. The bus's
84
- * `extractCategoryPattern` keys handlers by a single pattern; passing
85
- * an array would silently route only the first category. UIs that
86
- * need multi-category live filtering must subscribe by deviceId
87
- * (single-category-per-call or wildcard) and narrow client-side.
88
- */
89
- const SubscribeInputSchema = ScopeFieldsSchema.extend({
90
- category: z.string().optional(),
91
- })
92
-
93
- export function createSystemEventsRouter(eb: EventBusService) {
94
- return trpcRouter({
95
- getRecent: protectedProcedure.input(GetRecentInputSchema).query(({ input }) => {
96
- return eb
97
- .getRecent(
98
- {
99
- ...(input.source ? { source: input.source } : {}),
100
- ...(input.agentId ? { agentId: input.agentId } : {}),
101
- ...(input.addonId ? { addonId: input.addonId } : {}),
102
- ...(input.deviceId !== undefined ? { deviceId: input.deviceId } : {}),
103
- ...(input.category ? { category: input.category } : {}),
104
- },
105
- input.limit,
106
- )
107
- .map(serialize)
108
- }),
109
-
110
- subscribe: protectedProcedure.input(SubscribeInputSchema).subscription(({ input }) => {
111
- return iterableSubscription<unknown>((push) => {
112
- return eb.subscribe(
113
- {
114
- ...(input.source ? { source: input.source } : {}),
115
- ...(input.agentId ? { agentId: input.agentId } : {}),
116
- ...(input.addonId ? { addonId: input.addonId } : {}),
117
- ...(input.deviceId !== undefined ? { deviceId: input.deviceId } : {}),
118
- ...(input.category ? { category: input.category } : {}),
119
- },
120
- (event: SystemEvent) => push(serialize(event)),
121
- )
122
- })
123
- }),
124
- })
125
- }
@@ -1,117 +0,0 @@
1
- /**
2
- * Health endpoints — fast probe surface for monitoring (k8s, uptime,
3
- * external watchdogs).
4
- *
5
- * Routes:
6
- * - GET /health → hub self-health
7
- * - GET /health/agents → list of online agent node IDs
8
- * - GET /health/agents/:nodeId → forward to agent's `$agent.health`
9
- * - GET /health/cluster → hub + every online agent in one shot
10
- *
11
- * The same health shape is exposed by every agent at its own
12
- * `http://<agent>:4444/health`. Both surfaces are backed by the
13
- * `$agent.health` Moleculer action so monitors that talk to the hub
14
- * and monitors that talk directly to an agent see identical payloads.
15
- */
16
- import type { FastifyInstance, FastifyReply } from 'fastify'
17
- import type { AgentHealth, AgentHealthError, ClusterHealth, HubHealth } from '@camstack/types'
18
- import type { MoleculerService } from '../../core/moleculer/moleculer.service'
19
- import type { AgentRegistryService } from '../../core/agent/agent-registry.service'
20
-
21
- interface HealthRoutesDeps {
22
- readonly moleculer: MoleculerService
23
- readonly agentRegistry: AgentRegistryService
24
- readonly hubVersion: string
25
- }
26
-
27
- interface ProcessLike {
28
- uptime(): number
29
- pid: number
30
- cpuUsage(previous?: NodeJS.CpuUsage): NodeJS.CpuUsage
31
- memoryUsage(): NodeJS.MemoryUsage
32
- }
33
-
34
- const AGENT_HEALTH_TIMEOUT_MS = 3_000
35
-
36
- function nowIso(): string {
37
- return new Date().toISOString()
38
- }
39
-
40
- async function buildHubHealth(
41
- deps: HealthRoutesDeps,
42
- proc: ProcessLike = process,
43
- ): Promise<HubHealth> {
44
- const nodes = await deps.agentRegistry.listNodes()
45
- const remote = nodes.filter((n) => !n.isHub)
46
- const online = remote.filter((n) => n.isOnline !== false).length
47
- const total = remote.length
48
- const memUsage = proc.memoryUsage()
49
- const totalMem = memUsage.heapTotal + memUsage.external + memUsage.arrayBuffers
50
- const memoryPercent = totalMem > 0 ? Math.round((memUsage.heapUsed / totalMem) * 100) : 0
51
- return {
52
- ok: true,
53
- nodeId: 'hub',
54
- version: deps.hubVersion,
55
- uptimeSeconds: Math.round(proc.uptime()),
56
- pid: proc.pid,
57
- agents: { total, online, offline: total - online },
58
- cpuPercent: 0,
59
- memoryPercent,
60
- checkedAt: nowIso(),
61
- }
62
- }
63
-
64
- async function fetchAgentHealth(
65
- deps: HealthRoutesDeps,
66
- nodeId: string,
67
- ): Promise<AgentHealth | AgentHealthError> {
68
- try {
69
- const result = (await deps.moleculer.broker.call(
70
- '$agent.health',
71
- {},
72
- { nodeID: nodeId, timeout: AGENT_HEALTH_TIMEOUT_MS },
73
- )) as AgentHealth
74
- return result
75
- } catch (err) {
76
- return {
77
- ok: false,
78
- nodeId,
79
- error: err instanceof Error ? err.message : String(err),
80
- }
81
- }
82
- }
83
-
84
- export function registerHealthRoutes(fastify: FastifyInstance, deps: HealthRoutesDeps): void {
85
- fastify.get('/health', async (): Promise<HubHealth> => buildHubHealth(deps))
86
-
87
- fastify.get('/health/agents', async (): Promise<{ readonly agents: readonly string[] }> => {
88
- const nodes = await deps.agentRegistry.listNodes()
89
- return {
90
- agents: nodes.filter((n) => !n.isHub && n.isOnline !== false).map((n) => n.info.id),
91
- }
92
- })
93
-
94
- fastify.get<{ Params: { nodeId: string } }>(
95
- '/health/agents/:nodeId',
96
- async (req, reply: FastifyReply) => {
97
- const { nodeId } = req.params
98
- if (!nodeId) {
99
- return reply.status(400).send({ ok: false, error: 'nodeId required' })
100
- }
101
- const result = await fetchAgentHealth(deps, nodeId)
102
- if (!result.ok) {
103
- return reply.status(503).send(result)
104
- }
105
- return result
106
- },
107
- )
108
-
109
- fastify.get('/health/cluster', async (): Promise<ClusterHealth> => {
110
- const hub = await buildHubHealth(deps)
111
- const nodes = await deps.agentRegistry.listNodes()
112
- const remote = nodes.filter((n) => !n.isHub && n.isOnline !== false)
113
- const agents = await Promise.all(remote.map((n) => fetchAgentHealth(deps, n.info.id)))
114
- const ok = hub.ok && agents.every((a) => a.ok)
115
- return { ok, hub, agents, checkedAt: nowIso() }
116
- })
117
- }
@@ -1,62 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { validateAuthorizeQuery, isRedirectUriAllowed } from '../oauth2-routes.js'
3
-
4
- describe('validateAuthorizeQuery', () => {
5
- const known = new Set(['export-alexa'])
6
- it('accepts a well-formed query for a known integration', () => {
7
- const r = validateAuthorizeQuery(
8
- {
9
- response_type: 'code',
10
- integration: 'export-alexa',
11
- redirect_uri: 'https://cb/r',
12
- state: 's',
13
- },
14
- known,
15
- )
16
- expect(r.ok).toBe(true)
17
- })
18
- it('rejects an unknown integration', () => {
19
- const r = validateAuthorizeQuery(
20
- { response_type: 'code', integration: 'nope', redirect_uri: 'https://cb/r', state: 's' },
21
- known,
22
- )
23
- expect(r.ok).toBe(false)
24
- })
25
- it('rejects a missing state', () => {
26
- const r = validateAuthorizeQuery(
27
- { response_type: 'code', integration: 'export-alexa', redirect_uri: 'https://cb/r' },
28
- known,
29
- )
30
- expect(r.ok).toBe(false)
31
- })
32
- it('rejects a non-code response_type', () => {
33
- const r = validateAuthorizeQuery(
34
- {
35
- response_type: 'token',
36
- integration: 'export-alexa',
37
- redirect_uri: 'https://cb/r',
38
- state: 's',
39
- },
40
- known,
41
- )
42
- expect(r.ok).toBe(false)
43
- })
44
- })
45
-
46
- describe('isRedirectUriAllowed', () => {
47
- const prefixes = ['https://layla.amazon.com/', 'https://pitangui.amazon.com/']
48
-
49
- it('returns true when redirect_uri starts with an allowed prefix', () => {
50
- expect(isRedirectUriAllowed('https://layla.amazon.com/oauth/callback', prefixes)).toBe(true)
51
- expect(isRedirectUriAllowed('https://pitangui.amazon.com/oauth/callback', prefixes)).toBe(true)
52
- })
53
-
54
- it('returns false when redirect_uri does not match any allowed prefix', () => {
55
- expect(isRedirectUriAllowed('https://evil.example/grab', prefixes)).toBe(false)
56
- expect(isRedirectUriAllowed('https://layla.amazon.com.evil.example/', prefixes)).toBe(false)
57
- })
58
-
59
- it('returns false when allowedPrefixes is empty', () => {
60
- expect(isRedirectUriAllowed('https://layla.amazon.com/oauth/callback', [])).toBe(false)
61
- })
62
- })