@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
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireSingleton = requireSingleton;
4
+ exports.requireDeviceScoped = requireDeviceScoped;
5
+ exports.concatCollection = concatCollection;
6
+ exports.firstSupported = firstSupported;
7
+ exports.anySupports = anySupports;
8
+ /**
9
+ * Small helpers for wiring capability routers to the `CapabilityRegistry`
10
+ * in `trpc.router.ts`. These functions don't generate code — they just
11
+ * remove boilerplate from the mount lambdas we pass to `createCapRouter_X`.
12
+ *
13
+ * When to use what:
14
+ * - `requireSingleton(registry, name)` — singleton caps with no custom
15
+ * composition. Returns the active provider or null (the codegen'd
16
+ * router itself throws PRECONDITION_FAILED when null).
17
+ * - `concatCollection(providers, method)` — collection caps whose
18
+ * methods return arrays and where the desired behaviour is "union of
19
+ * every provider's contribution" (e.g. `turn-provider.getTurnServers`).
20
+ * - `firstSupported(providers, probe, action)` — collection caps where
21
+ * exactly one provider should handle each request, selected by a
22
+ * probe method (e.g. `snapshot-provider.supportsDevice`).
23
+ *
24
+ * Collections that route by an input key (e.g. `webrtc` picking the
25
+ * provider responsible for a given `streamId`) are NOT covered here —
26
+ * they have app-specific routing logic that belongs in the mount.
27
+ */
28
+ const server_1 = require("@trpc/server");
29
+ /**
30
+ * Fetch the currently active singleton provider for a capability.
31
+ * Returns null if no provider is registered; the downstream codegen'd
32
+ * router surfaces this as `PRECONDITION_FAILED` to the caller.
33
+ */
34
+ function requireSingleton(registry, capName) {
35
+ return registry?.getSingleton(capName) ?? null;
36
+ }
37
+ /**
38
+ * Build a per-device dispatcher that satisfies the singleton-provider
39
+ * shape but resolves the actual implementation lazily via
40
+ * `registry.getNativeProvider(capName, deviceId)` on every method call.
41
+ *
42
+ * Use for device-scoped caps that have NO system-level wrapper (PTZ,
43
+ * reboot, doorbell, brightness, motion-trigger, switch, …) — drivers
44
+ * register per-device native providers via
45
+ * `DeviceContext.registerNativeCap`, and this helper bridges the cap-
46
+ * router's "fetch a singleton then call methods on it" flow into a
47
+ * "resolve native by deviceId per call" flow.
48
+ *
49
+ * Method input MUST carry `deviceId: number`. Methods without that
50
+ * field (auto-injected `getStatus({deviceId})` for caps with a status
51
+ * block, every business method that follows the cap-definition
52
+ * convention) work transparently.
53
+ *
54
+ * Throws PRECONDITION_FAILED with a device-specific message when no
55
+ * native provider exists for the requested deviceId — much friendlier
56
+ * than the singleton fallthrough's "no provider" generic error.
57
+ */
58
+ function requireDeviceScoped(registry, capName) {
59
+ if (!registry)
60
+ return null;
61
+ // The Proxy is the singleton stand-in. Each property access returns
62
+ // a function that, on call, looks up the per-device native and
63
+ // forwards the call. No caching — the lookup is cheap (Map.get) and
64
+ // re-doing it per call lets devices come/go without stale refs.
65
+ const dispatcher = new Proxy({}, {
66
+ get(_target, prop) {
67
+ if (typeof prop !== 'string')
68
+ return undefined;
69
+ return async (input) => {
70
+ const deviceId = input?.deviceId;
71
+ if (typeof deviceId !== 'number') {
72
+ throw new server_1.TRPCError({
73
+ code: 'BAD_REQUEST',
74
+ message: `${String(capName)}.${prop}: input must carry numeric "deviceId"`,
75
+ });
76
+ }
77
+ const native = registry.getNativeProvider(capName, deviceId);
78
+ if (!native) {
79
+ throw new server_1.TRPCError({
80
+ code: 'PRECONDITION_FAILED',
81
+ message: `Capability "${String(capName)}" not registered for device ${deviceId}`,
82
+ });
83
+ }
84
+ const fn = native[prop];
85
+ if (typeof fn !== 'function') {
86
+ throw new server_1.TRPCError({
87
+ code: 'NOT_IMPLEMENTED',
88
+ message: `Capability "${String(capName)}" provider for device ${deviceId} does not implement "${prop}"`,
89
+ });
90
+ }
91
+ const result = await fn.call(native, input);
92
+ // Device-property-wiring overlay (read-time): only `getStatus`, and only
93
+ // when the device has links for this cap (resolveLinkedStatus returns
94
+ // null otherwise → base result untouched). One in-process singleton hop.
95
+ if (prop === 'getStatus') {
96
+ const deviceManager = registry.getSingleton('device-manager');
97
+ const overlaid = await deviceManager?.resolveLinkedStatus?.({
98
+ deviceId,
99
+ cap: String(capName),
100
+ baseStatus: result,
101
+ });
102
+ if (overlaid != null)
103
+ return overlaid;
104
+ }
105
+ return result;
106
+ };
107
+ },
108
+ });
109
+ return dispatcher;
110
+ }
111
+ /**
112
+ * Build a method that fan-outs a call to every provider in a collection
113
+ * and concatenates their array results. Useful for contribution-style
114
+ * caps where each provider adds to a shared pool.
115
+ */
116
+ function concatCollection(providers, method) {
117
+ const wrapper = async (...args) => {
118
+ const results = await Promise.all(providers.map(async (p) => {
119
+ const member = Reflect.get(p, method);
120
+ if (typeof member !== 'function')
121
+ return [];
122
+ // `Reflect.apply` returns `any`; funnel through unknown.
123
+ const out = await Reflect.apply(member, p, args);
124
+ if (!Array.isArray(out))
125
+ return [];
126
+ const arr = out;
127
+ return arr;
128
+ }));
129
+ return results.flat();
130
+ };
131
+ // Type-level bridge: the runtime wrapper signature (unknown → Promise<unknown[]>)
132
+ // matches the declared generic conditional return; TypeScript's
133
+ // conditional types can't be narrowed inside a function body, so this
134
+ // boundary assertion is required.
135
+ return wrapper;
136
+ }
137
+ /**
138
+ * Iterate a collection asking each provider "do you handle this?" via a
139
+ * probe method, then call an action on the first one that answers yes.
140
+ * Each provider is tried in registration order; errors are swallowed and
141
+ * the next provider is attempted. Returns null if no provider matches or
142
+ * if every matching provider's action throws/returns null.
143
+ */
144
+ function firstSupported(providers, probe, action) {
145
+ const wrapper = async (...args) => {
146
+ const [first] = args;
147
+ for (const p of providers) {
148
+ try {
149
+ const probeMember = Reflect.get(p, probe);
150
+ if (typeof probeMember !== 'function')
151
+ continue;
152
+ const supported = await Reflect.apply(probeMember, p, [first]);
153
+ if (supported !== true)
154
+ continue;
155
+ const actionMember = Reflect.get(p, action);
156
+ if (typeof actionMember !== 'function')
157
+ continue;
158
+ const result = await Reflect.apply(actionMember, p, args);
159
+ if (result !== null && result !== undefined)
160
+ return result;
161
+ }
162
+ catch {
163
+ // try next provider
164
+ }
165
+ }
166
+ return null;
167
+ };
168
+ // Type-level bridge — see concatCollection for the same pattern.
169
+ return wrapper;
170
+ }
171
+ /**
172
+ * Convenience for collection caps that want a "logical OR of probes"
173
+ * (e.g. `supportsDevice` across every snapshot-provider).
174
+ */
175
+ function anySupports(providers, probe) {
176
+ const wrapper = async (...args) => {
177
+ for (const p of providers) {
178
+ const member = Reflect.get(p, probe);
179
+ if (typeof member !== 'function')
180
+ continue;
181
+ try {
182
+ const result = await Reflect.apply(member, p, args);
183
+ if (result === true)
184
+ return true;
185
+ }
186
+ catch {
187
+ /* next */
188
+ }
189
+ }
190
+ return false;
191
+ };
192
+ // Type-level bridge — see concatCollection for the same pattern.
193
+ return wrapper;
194
+ }
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatTrpcError = formatTrpcError;
4
+ /**
5
+ * tRPC error formatter that serializes CapRouteError diagnostic fields
6
+ * across the server→client boundary.
7
+ *
8
+ * tRPC only carries `error.message` by default. This formatter augments
9
+ * the default shape's `data` block with typed CapRouteError fields so the
10
+ * admin-UI can read `capRouteReason` instead of substring-matching message text.
11
+ *
12
+ * Fields added (all optional — absent when the error is not a CapRouteError):
13
+ * - `capRouteReason` — 'no-provider' | 'node-offline' | 'cap-unknown' | 'transport-failed'
14
+ * - `capRouteRejected` — array of `{ kind: string; why: string }` route-rejection descriptors
15
+ * - `capRouteNodeId` — the target node id, when known
16
+ *
17
+ * The formatter is EXPORTED for unit testing (no side-effects, pure function).
18
+ */
19
+ const kernel_1 = require("@camstack/kernel");
20
+ // ---------------------------------------------------------------------------
21
+ // Type guards
22
+ // ---------------------------------------------------------------------------
23
+ /** Known CapRouteError reason values — used as a runtime safety rail. */
24
+ const KNOWN_REASONS = new Set([
25
+ 'no-provider',
26
+ 'node-offline',
27
+ 'cap-unknown',
28
+ 'transport-failed',
29
+ ]);
30
+ /** Narrows a plain string to the `CapRouteError['reason']` union. */
31
+ function isCapRouteReason(r) {
32
+ return KNOWN_REASONS.has(r);
33
+ }
34
+ /** Narrows an `unknown` value to `RejectedRoute` by checking structural shape. */
35
+ function isRejectedRoute(r) {
36
+ if (typeof r !== 'object' || r === null)
37
+ return false;
38
+ const kind = Reflect.get(r, 'kind');
39
+ const why = Reflect.get(r, 'why');
40
+ return typeof kind === 'string' && typeof why === 'string';
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // CapRouteError extraction helpers
44
+ // ---------------------------------------------------------------------------
45
+ /**
46
+ * Walks the `.cause` chain of an error to find a CapRouteError.
47
+ * Returns the first one found, or null.
48
+ *
49
+ * Detection is dual-mode:
50
+ * 1. `instanceof CapRouteError` — works when the same module is loaded.
51
+ * 2. Duck-type: `name === 'CapRouteError'` + `typeof reason === 'string'`
52
+ * — robust against module-boundary issues (self-contained addons).
53
+ */
54
+ function extractCapRouteError(err) {
55
+ let current = err;
56
+ for (let depth = 0; depth < 8; depth++) {
57
+ if (current === null || current === undefined)
58
+ return null;
59
+ if (current instanceof kernel_1.CapRouteError) {
60
+ return current;
61
+ }
62
+ // Duck-type fallback: err.name + err.reason field present
63
+ if (typeof current === 'object' && Reflect.get(current, 'name') === 'CapRouteError') {
64
+ const rawReason = Reflect.get(current, 'reason');
65
+ if (typeof rawReason === 'string') {
66
+ // Runtime safety: reject unrecognised reason strings so the formatter
67
+ // only promotes values it knows are valid CapRouteError reasons.
68
+ if (!isCapRouteReason(rawReason)) {
69
+ // Unrecognised reason — treat as a non-CapRouteError and keep walking
70
+ const cause = Reflect.get(current, 'cause');
71
+ if (cause === current)
72
+ return null;
73
+ current = cause;
74
+ continue;
75
+ }
76
+ const reason = rawReason;
77
+ const rawRejected = Reflect.get(current, 'rejected');
78
+ const rejected = Array.isArray(rawRejected)
79
+ ? rawRejected.filter(isRejectedRoute)
80
+ : [];
81
+ const nodeId = Reflect.get(current, 'nodeId');
82
+ const message = Reflect.get(current, 'message');
83
+ const rawCapName = Reflect.get(current, 'capName');
84
+ const capName = typeof rawCapName === 'string' ? rawCapName : '(unknown)';
85
+ // Build a minimal object with the same shape — enough for the formatter.
86
+ const synthetic = Object.assign(new kernel_1.CapRouteError(capName, undefined, {
87
+ reason,
88
+ rejected,
89
+ ...(typeof nodeId === 'string' ? { nodeId } : {}),
90
+ }), {
91
+ // Override message from the original if available
92
+ message: typeof message === 'string' ? message : '(duck-typed CapRouteError)',
93
+ });
94
+ return synthetic;
95
+ }
96
+ }
97
+ // Walk the cause chain
98
+ if (typeof current !== 'object')
99
+ return null;
100
+ const cause = Reflect.get(current, 'cause');
101
+ if (cause === current)
102
+ return null; // Guard against circular refs
103
+ current = cause;
104
+ }
105
+ return null;
106
+ }
107
+ /**
108
+ * Augments the default tRPC error shape with CapRouteError diagnostic fields
109
+ * when the thrown error (or any error in its `.cause` chain) is a CapRouteError.
110
+ * Returns the shape unchanged for all other errors.
111
+ */
112
+ function formatTrpcError(opts) {
113
+ const { error, shape } = opts;
114
+ // extractCapRouteError already walks the full .cause chain, so a single call
115
+ // starting from `error` covers both `error instanceof CapRouteError` and
116
+ // `error.cause` (and deeper nesting). No second call needed.
117
+ const capRouteError = extractCapRouteError(error);
118
+ if (capRouteError === null) {
119
+ return { ...shape, data: { ...shape.data } };
120
+ }
121
+ const extraData = {
122
+ capRouteReason: capRouteError.reason,
123
+ capRouteRejected: capRouteError.rejected,
124
+ ...(capRouteError.nodeId !== undefined ? { capRouteNodeId: capRouteError.nodeId } : {}),
125
+ };
126
+ return {
127
+ ...shape,
128
+ data: {
129
+ ...shape.data,
130
+ ...extraData,
131
+ },
132
+ };
133
+ }
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractClientIp = extractClientIp;
4
+ exports.extractUserAgent = extractUserAgent;
5
+ exports.isRemoteClientIp = isRemoteClientIp;
6
+ /**
7
+ * Best-effort extraction of the originating client IP from a tRPC
8
+ * request. Order of preference:
9
+ * 1. `X-Forwarded-For` first hop (when behind a reverse proxy — the
10
+ * hub is typically fronted by Caddy/nginx/Cloudflare for remote
11
+ * access, so the socket peer is the proxy, not the viewer).
12
+ * 2. Fastify's `req.ip` (already proxy-aware when `trustProxy` is on).
13
+ * 3. The raw socket remote address.
14
+ *
15
+ * Returns `null` when no address can be determined (e.g. mesh-originated
16
+ * calls that carry no HTTP request) — the caller treats `null` as "not
17
+ * remote" so the LAN/direct path stays the safe default.
18
+ */
19
+ function extractClientIp(req) {
20
+ if (!req)
21
+ return null;
22
+ // 1. X-Forwarded-For — comma-separated list, client is the FIRST entry.
23
+ const xff = req.headers['x-forwarded-for'];
24
+ const xffValue = Array.isArray(xff) ? xff[0] : xff;
25
+ if (typeof xffValue === 'string' && xffValue.length > 0) {
26
+ const first = xffValue.split(',')[0]?.trim();
27
+ if (first)
28
+ return normalizeIp(first);
29
+ }
30
+ // 2. Fastify's parsed `req.ip` (honours `trustProxy` config).
31
+ if ('ip' in req && typeof req.ip === 'string' && req.ip.length > 0) {
32
+ return normalizeIp(req.ip);
33
+ }
34
+ // 3. Raw socket peer (WS / IncomingMessage path).
35
+ const remote = req.socket?.remoteAddress;
36
+ if (typeof remote === 'string' && remote.length > 0) {
37
+ return normalizeIp(remote);
38
+ }
39
+ return null;
40
+ }
41
+ /**
42
+ * Best-effort read of the originating client's `User-Agent` header. Both
43
+ * request shapes in the union (`FastifyRequest` and the raw WS-transport
44
+ * `IncomingMessage`) expose `.headers['user-agent']`. Returns `null` when
45
+ * absent (mesh-originated calls carry no HTTP request, and some clients
46
+ * omit the header). Used by the `webrtc-session` mount to tag a browser
47
+ * subscriber's broker attribution with the viewer's UA — the SERVER reads
48
+ * it from the request context; any client-supplied value is ignored.
49
+ */
50
+ function extractUserAgent(req) {
51
+ if (!req)
52
+ return null;
53
+ const ua = req.headers['user-agent'];
54
+ const value = Array.isArray(ua) ? ua[0] : ua;
55
+ if (typeof value === 'string' && value.length > 0)
56
+ return value;
57
+ return null;
58
+ }
59
+ /**
60
+ * Strip an IPv4-mapped IPv6 prefix (`::ffff:192.168.1.5` → `192.168.1.5`)
61
+ * and any zone id (`fe80::1%en0` → `fe80::1`) so the range checks below
62
+ * see a clean address.
63
+ */
64
+ function normalizeIp(ip) {
65
+ let out = ip.trim();
66
+ if (out.startsWith('::ffff:'))
67
+ out = out.slice('::ffff:'.length);
68
+ const pct = out.indexOf('%');
69
+ if (pct !== -1)
70
+ out = out.slice(0, pct);
71
+ return out;
72
+ }
73
+ /**
74
+ * True when the address is NOT in a private / loopback / link-local /
75
+ * Tailscale range — i.e. an internet (remote) viewer. Covers IPv4
76
+ * (10/8, 172.16/12, 192.168/16, 127/8, 100.64/10 Tailscale CGNAT) and
77
+ * IPv6 (::1, fc00::/7 unique-local, fe80::/10 link-local — fd7a::/16
78
+ * Tailscale ULA is a subset of fc00::/7 and is therefore already
79
+ * covered).
80
+ *
81
+ * Tailscale clients (the 100.64.0.0/10 CGNAT overlay or the fd7a::/16
82
+ * ULA overlay) are deliberately classified LOCAL: over the Tailscale
83
+ * mesh BOTH peers sit on the 100.x / fd7a:: overlay and are mutually
84
+ * reachable, so the broker offers ALL candidates (host/srflx/relay)
85
+ * — including the hub's Tailscale host candidate — and a direct
86
+ * host↔host pair wins ICE with native (non-re-encoded) media. Forcing
87
+ * relay-only for Tailscale would push them onto the broken relay path.
88
+ *
89
+ * Unparseable or null addresses return `false` (treated as LAN — the
90
+ * safe default that preserves the existing direct path).
91
+ */
92
+ function isRemoteClientIp(ip) {
93
+ if (!ip)
94
+ return false;
95
+ if (ip.includes(':'))
96
+ return !isPrivateIpv6(ip);
97
+ if (isIpv4(ip))
98
+ return !isPrivateIpv4(ip);
99
+ // Not a recognisable IP literal — be conservative, treat as LAN.
100
+ return false;
101
+ }
102
+ function isIpv4(ip) {
103
+ const parts = ip.split('.');
104
+ if (parts.length !== 4)
105
+ return false;
106
+ return parts.every((p) => {
107
+ if (!/^\d{1,3}$/.test(p))
108
+ return false;
109
+ const n = Number.parseInt(p, 10);
110
+ return n >= 0 && n <= 255;
111
+ });
112
+ }
113
+ function isPrivateIpv4(ip) {
114
+ const parts = ip.split('.').map((p) => Number.parseInt(p, 10));
115
+ const [a, b] = parts;
116
+ if (a === undefined || b === undefined)
117
+ return false;
118
+ if (a === 10)
119
+ return true; // 10.0.0.0/8
120
+ if (a === 127)
121
+ return true; // 127.0.0.0/8 loopback
122
+ if (a === 172 && b >= 16 && b <= 31)
123
+ return true; // 172.16.0.0/12
124
+ if (a === 192 && b === 168)
125
+ return true; // 192.168.0.0/16
126
+ if (a === 169 && b === 254)
127
+ return true; // 169.254.0.0/16 link-local
128
+ // 100.64.0.0/10 — Tailscale CGNAT overlay (100.64.0.0 – 100.127.255.255).
129
+ // Both Tailscale peers are mutually reachable on this overlay, so treat
130
+ // it as local (direct host↔host pair, no relay).
131
+ if (a === 100 && b >= 64 && b <= 127)
132
+ return true;
133
+ return false;
134
+ }
135
+ function isPrivateIpv6(ip) {
136
+ const lower = ip.toLowerCase();
137
+ if (lower === '::1')
138
+ return true; // loopback
139
+ if (lower === '::')
140
+ return true; // unspecified
141
+ if (lower.startsWith('fe80'))
142
+ return true; // fe80::/10 link-local
143
+ // fc00::/7 unique-local — covers fc.. and fd..
144
+ if (lower.startsWith('fc') || lower.startsWith('fd'))
145
+ return true;
146
+ return false;
147
+ }
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildCoreCapService = buildCoreCapService;
4
+ const kernel_1 = require("@camstack/kernel");
5
+ const trpc_middleware_js_1 = require("./trpc.middleware.js");
6
+ const trpc_context_js_1 = require("./trpc.context.js");
7
+ /**
8
+ * appRouter namespaces that back the CORE API surface and must be
9
+ * reachable cross-process. This is the core-router list from
10
+ * `trpc.router.ts` (`buildCapabilityRouters`) minus the deliberate
11
+ * exclusions below.
12
+ *
13
+ * Excluded on purpose:
14
+ * - `auth` — issuing service / scoped tokens over the trusted mesh
15
+ * would let any forked addon mint admin credentials. Addons must
16
+ * not perform authentication operations; this stays hub-local.
17
+ * - `live`, `systemEvents` — listed in `NEVER_BRIDGED_CAPS`
18
+ * (`trpc-links.ts`). They are hub-only push streams, not
19
+ * request/reply, and the worker side fast-fails on them by design.
20
+ */
21
+ const CORE_NAMESPACES = new Set([
22
+ // Service-backed cap routers (no addon provider).
23
+ 'system',
24
+ 'toast',
25
+ 'integrations',
26
+ 'nodes',
27
+ 'addons',
28
+ // Hand-written single-impl core routers.
29
+ 'capabilities',
30
+ 'notifications',
31
+ 'logs',
32
+ 'hwaccel',
33
+ 'streamProbe',
34
+ 'settingsBackend',
35
+ 'eventBusProxy',
36
+ 'repl',
37
+ 'addonSettingsRaw',
38
+ ]);
39
+ /**
40
+ * camelCase → kebab-case. Mirrors `toKebab` in `trpc-links.ts` so the
41
+ * action name matches what `brokerTransportLink` derives from `op.path`.
42
+ */
43
+ function toKebab(name) {
44
+ return name.replace(/[A-Z]/g, (m, i) => (i > 0 ? '-' : '') + m.toLowerCase());
45
+ }
46
+ function isProcedureNode(value) {
47
+ // A built tRPC procedure is a callable object — `_def.procedures`
48
+ // stores the procedure function directly, not a wrapper object.
49
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function'))
50
+ return false;
51
+ const def = value._def;
52
+ return (def !== null && typeof def === 'object' && def.procedure === true);
53
+ }
54
+ /**
55
+ * Recursively flatten a tRPC router record to dotted procedure paths.
56
+ * Robust to both layouts tRPC v11 may use for `_def.procedures`: a
57
+ * nested record of sub-routers, or a flat record already keyed by
58
+ * dotted path (a flat key simply yields its key verbatim).
59
+ */
60
+ function collectProcedures(record, prefix, out) {
61
+ for (const [key, value] of Object.entries(record)) {
62
+ const path = prefix.length > 0 ? `${prefix}.${key}` : key;
63
+ if (isProcedureNode(value)) {
64
+ const type = value._def.type;
65
+ out.push({ path, type: typeof type === 'string' ? type : 'query' });
66
+ }
67
+ else if (value !== null && typeof value === 'object') {
68
+ collectProcedures(value, path, out);
69
+ }
70
+ }
71
+ }
72
+ /**
73
+ * Navigate the decorated caller record (a recursive proxy) to the
74
+ * procedure function at `path`. Every node on the proxy is callable,
75
+ * so navigation uses `Reflect.get` across both objects and functions.
76
+ */
77
+ function resolveInvoker(caller, path) {
78
+ let node = caller;
79
+ for (const segment of path.split('.')) {
80
+ if (node === null || (typeof node !== 'object' && typeof node !== 'function'))
81
+ return null;
82
+ // Documented boundary: `node` is an object or function past the
83
+ // guard above; `Reflect.get` is typed for `object` targets.
84
+ node = Reflect.get(node, segment);
85
+ }
86
+ return typeof node === 'function' ? node : null;
87
+ }
88
+ /**
89
+ * Build the `$core-caps` Moleculer service schema from the hub
90
+ * appRouter. Query + mutation procedures in {@link CORE_NAMESPACES} are
91
+ * exposed; subscriptions are skipped (the broker transport is
92
+ * request/reply only).
93
+ */
94
+ function buildCoreCapService(appRouter) {
95
+ const meshCaller = (0, trpc_middleware_js_1.createCallerFactory)(appRouter)((0, trpc_context_js_1.createMeshTrpcContext)());
96
+ const discovered = [];
97
+ collectProcedures(appRouter._def.procedures, '', discovered);
98
+ const actions = [];
99
+ for (const { path, type } of discovered) {
100
+ const dot = path.indexOf('.');
101
+ if (dot < 0)
102
+ continue;
103
+ const namespace = path.slice(0, dot);
104
+ if (!CORE_NAMESPACES.has(namespace))
105
+ continue;
106
+ if (type === 'subscription')
107
+ continue;
108
+ const invoke = resolveInvoker(meshCaller, path);
109
+ if (invoke === null)
110
+ continue;
111
+ const method = path.slice(dot + 1);
112
+ actions.push({ actionName: `${toKebab(namespace)}.${method}`, invoke });
113
+ }
114
+ return (0, kernel_1.createCoreCapService)({ actions });
115
+ }