@tx5dr/contracts 1.2.0

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 (180) hide show
  1. package/CLAUDE.md +41 -0
  2. package/dist/index.d.ts +55 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +86 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/schema/__tests__/plugin-runtime-log.schema.test.d.ts +2 -0
  7. package/dist/schema/__tests__/plugin-runtime-log.schema.test.d.ts.map +1 -0
  8. package/dist/schema/__tests__/plugin-runtime-log.schema.test.js +78 -0
  9. package/dist/schema/__tests__/plugin-runtime-log.schema.test.js.map +1 -0
  10. package/dist/schema/ability.d.ts +85 -0
  11. package/dist/schema/ability.d.ts.map +1 -0
  12. package/dist/schema/ability.js +119 -0
  13. package/dist/schema/ability.js.map +1 -0
  14. package/dist/schema/api-response.schema.d.ts +1017 -0
  15. package/dist/schema/api-response.schema.d.ts.map +1 -0
  16. package/dist/schema/api-response.schema.js +137 -0
  17. package/dist/schema/api-response.schema.js.map +1 -0
  18. package/dist/schema/audio-sidecar.schema.d.ts +106 -0
  19. package/dist/schema/audio-sidecar.schema.d.ts.map +1 -0
  20. package/dist/schema/audio-sidecar.schema.js +47 -0
  21. package/dist/schema/audio-sidecar.schema.js.map +1 -0
  22. package/dist/schema/audio.schema.d.ts +227 -0
  23. package/dist/schema/audio.schema.d.ts.map +1 -0
  24. package/dist/schema/audio.schema.js +56 -0
  25. package/dist/schema/audio.schema.js.map +1 -0
  26. package/dist/schema/auth.schema.d.ts +738 -0
  27. package/dist/schema/auth.schema.d.ts.map +1 -0
  28. package/dist/schema/auth.schema.js +161 -0
  29. package/dist/schema/auth.schema.js.map +1 -0
  30. package/dist/schema/cycle.schema.d.ts +33 -0
  31. package/dist/schema/cycle.schema.d.ts.map +1 -0
  32. package/dist/schema/cycle.schema.js +23 -0
  33. package/dist/schema/cycle.schema.js.map +1 -0
  34. package/dist/schema/desktop-https.schema.d.ts +146 -0
  35. package/dist/schema/desktop-https.schema.d.ts.map +1 -0
  36. package/dist/schema/desktop-https.schema.js +31 -0
  37. package/dist/schema/desktop-https.schema.js.map +1 -0
  38. package/dist/schema/ft8.schema.d.ts +1107 -0
  39. package/dist/schema/ft8.schema.d.ts.map +1 -0
  40. package/dist/schema/ft8.schema.js +268 -0
  41. package/dist/schema/ft8.schema.js.map +1 -0
  42. package/dist/schema/hello.schema.d.ts +10 -0
  43. package/dist/schema/hello.schema.d.ts.map +1 -0
  44. package/dist/schema/hello.schema.js +5 -0
  45. package/dist/schema/hello.schema.js.map +1 -0
  46. package/dist/schema/logbook.schema.d.ts +1472 -0
  47. package/dist/schema/logbook.schema.d.ts.map +1 -0
  48. package/dist/schema/logbook.schema.js +256 -0
  49. package/dist/schema/logbook.schema.js.map +1 -0
  50. package/dist/schema/mode.schema.d.ts +130 -0
  51. package/dist/schema/mode.schema.d.ts.map +1 -0
  52. package/dist/schema/mode.schema.js +141 -0
  53. package/dist/schema/mode.schema.js.map +1 -0
  54. package/dist/schema/openwebrx.schema.d.ts +352 -0
  55. package/dist/schema/openwebrx.schema.d.ts.map +1 -0
  56. package/dist/schema/openwebrx.schema.js +139 -0
  57. package/dist/schema/openwebrx.schema.js.map +1 -0
  58. package/dist/schema/operator.schema.d.ts +883 -0
  59. package/dist/schema/operator.schema.d.ts.map +1 -0
  60. package/dist/schema/operator.schema.js +97 -0
  61. package/dist/schema/operator.schema.js.map +1 -0
  62. package/dist/schema/plugin.schema.d.ts +1579 -0
  63. package/dist/schema/plugin.schema.d.ts.map +1 -0
  64. package/dist/schema/plugin.schema.js +336 -0
  65. package/dist/schema/plugin.schema.js.map +1 -0
  66. package/dist/schema/process-monitor.schema.d.ts +282 -0
  67. package/dist/schema/process-monitor.schema.d.ts.map +1 -0
  68. package/dist/schema/process-monitor.schema.js +31 -0
  69. package/dist/schema/process-monitor.schema.js.map +1 -0
  70. package/dist/schema/pskreporter.schema.d.ts +185 -0
  71. package/dist/schema/pskreporter.schema.d.ts.map +1 -0
  72. package/dist/schema/pskreporter.schema.js +80 -0
  73. package/dist/schema/pskreporter.schema.js.map +1 -0
  74. package/dist/schema/qso.schema.d.ts +419 -0
  75. package/dist/schema/qso.schema.d.ts.map +1 -0
  76. package/dist/schema/qso.schema.js +137 -0
  77. package/dist/schema/qso.schema.js.map +1 -0
  78. package/dist/schema/radio-capability.schema.d.ts +625 -0
  79. package/dist/schema/radio-capability.schema.d.ts.map +1 -0
  80. package/dist/schema/radio-capability.schema.js +191 -0
  81. package/dist/schema/radio-capability.schema.js.map +1 -0
  82. package/dist/schema/radio-power-support.d.ts +45 -0
  83. package/dist/schema/radio-power-support.d.ts.map +1 -0
  84. package/dist/schema/radio-power-support.js +64 -0
  85. package/dist/schema/radio-power-support.js.map +1 -0
  86. package/dist/schema/radio-power.schema.d.ts +319 -0
  87. package/dist/schema/radio-power.schema.d.ts.map +1 -0
  88. package/dist/schema/radio-power.schema.js +87 -0
  89. package/dist/schema/radio-power.schema.js.map +1 -0
  90. package/dist/schema/radio-profile.schema.d.ts +2694 -0
  91. package/dist/schema/radio-profile.schema.d.ts.map +1 -0
  92. package/dist/schema/radio-profile.schema.js +71 -0
  93. package/dist/schema/radio-profile.schema.js.map +1 -0
  94. package/dist/schema/radio.schema.d.ts +1438 -0
  95. package/dist/schema/radio.schema.d.ts.map +1 -0
  96. package/dist/schema/radio.schema.js +314 -0
  97. package/dist/schema/radio.schema.js.map +1 -0
  98. package/dist/schema/realtime.schema.d.ts +1299 -0
  99. package/dist/schema/realtime.schema.d.ts.map +1 -0
  100. package/dist/schema/realtime.schema.js +184 -0
  101. package/dist/schema/realtime.schema.js.map +1 -0
  102. package/dist/schema/rigctld.schema.d.ts +151 -0
  103. package/dist/schema/rigctld.schema.d.ts.map +1 -0
  104. package/dist/schema/rigctld.schema.js +42 -0
  105. package/dist/schema/rigctld.schema.js.map +1 -0
  106. package/dist/schema/server-message-keys.d.ts +21 -0
  107. package/dist/schema/server-message-keys.d.ts.map +1 -0
  108. package/dist/schema/server-message-keys.js +22 -0
  109. package/dist/schema/server-message-keys.js.map +1 -0
  110. package/dist/schema/slot-info.schema.d.ts +696 -0
  111. package/dist/schema/slot-info.schema.d.ts.map +1 -0
  112. package/dist/schema/slot-info.schema.js +144 -0
  113. package/dist/schema/slot-info.schema.js.map +1 -0
  114. package/dist/schema/spectrum.schema.d.ts +716 -0
  115. package/dist/schema/spectrum.schema.d.ts.map +1 -0
  116. package/dist/schema/spectrum.schema.js +128 -0
  117. package/dist/schema/spectrum.schema.js.map +1 -0
  118. package/dist/schema/station-info.schema.d.ts +202 -0
  119. package/dist/schema/station-info.schema.d.ts.map +1 -0
  120. package/dist/schema/station-info.schema.js +32 -0
  121. package/dist/schema/station-info.schema.js.map +1 -0
  122. package/dist/schema/system.schema.d.ts +113 -0
  123. package/dist/schema/system.schema.d.ts.map +1 -0
  124. package/dist/schema/system.schema.js +95 -0
  125. package/dist/schema/system.schema.js.map +1 -0
  126. package/dist/schema/transmission.schema.d.ts +413 -0
  127. package/dist/schema/transmission.schema.d.ts.map +1 -0
  128. package/dist/schema/transmission.schema.js +78 -0
  129. package/dist/schema/transmission.schema.js.map +1 -0
  130. package/dist/schema/voice.schema.d.ts +38 -0
  131. package/dist/schema/voice.schema.d.ts.map +1 -0
  132. package/dist/schema/voice.schema.js +24 -0
  133. package/dist/schema/voice.schema.js.map +1 -0
  134. package/dist/schema/websocket.schema.d.ts +16916 -0
  135. package/dist/schema/websocket.schema.d.ts.map +1 -0
  136. package/dist/schema/websocket.schema.js +1050 -0
  137. package/dist/schema/websocket.schema.js.map +1 -0
  138. package/dist/utils/grid.d.ts +13 -0
  139. package/dist/utils/grid.d.ts.map +1 -0
  140. package/dist/utils/grid.js +71 -0
  141. package/dist/utils/grid.js.map +1 -0
  142. package/eslint.config.js +15 -0
  143. package/package.json +28 -0
  144. package/src/index.ts +152 -0
  145. package/src/schema/__tests__/plugin-runtime-log.schema.test.ts +90 -0
  146. package/src/schema/ability.ts +205 -0
  147. package/src/schema/api-response.schema.ts +187 -0
  148. package/src/schema/audio-sidecar.schema.ts +53 -0
  149. package/src/schema/audio.schema.ts +73 -0
  150. package/src/schema/auth.schema.ts +224 -0
  151. package/src/schema/cycle.schema.ts +25 -0
  152. package/src/schema/desktop-https.schema.ts +43 -0
  153. package/src/schema/ft8.schema.ts +362 -0
  154. package/src/schema/hello.schema.ts +7 -0
  155. package/src/schema/logbook.schema.ts +310 -0
  156. package/src/schema/mode.schema.ts +158 -0
  157. package/src/schema/openwebrx.schema.ts +160 -0
  158. package/src/schema/operator.schema.ts +112 -0
  159. package/src/schema/plugin.schema.ts +503 -0
  160. package/src/schema/process-monitor.schema.ts +41 -0
  161. package/src/schema/pskreporter.schema.ts +102 -0
  162. package/src/schema/qso.schema.ts +173 -0
  163. package/src/schema/radio-capability.schema.ts +244 -0
  164. package/src/schema/radio-power-support.ts +107 -0
  165. package/src/schema/radio-power.schema.ts +101 -0
  166. package/src/schema/radio-profile.schema.ts +91 -0
  167. package/src/schema/radio.schema.ts +385 -0
  168. package/src/schema/realtime.schema.ts +262 -0
  169. package/src/schema/rigctld.schema.ts +51 -0
  170. package/src/schema/server-message-keys.ts +26 -0
  171. package/src/schema/slot-info.schema.ts +162 -0
  172. package/src/schema/spectrum.schema.ts +166 -0
  173. package/src/schema/station-info.schema.ts +40 -0
  174. package/src/schema/system.schema.ts +121 -0
  175. package/src/schema/transmission.schema.ts +105 -0
  176. package/src/schema/voice.schema.ts +28 -0
  177. package/src/schema/websocket.schema.ts +1480 -0
  178. package/src/utils/grid.ts +94 -0
  179. package/test/mode-schema.test.ts +48 -0
  180. package/tsconfig.json +9 -0
@@ -0,0 +1,262 @@
1
+ import { z } from 'zod';
2
+
3
+ export const RealtimeScopeSchema = z.enum(['radio', 'openwebrx-preview']);
4
+ export type RealtimeScope = z.infer<typeof RealtimeScopeSchema>;
5
+
6
+ export const RealtimeParticipantKindSchema = z.enum(['listener', 'publisher', 'bridge']);
7
+ export type RealtimeParticipantKind = z.infer<typeof RealtimeParticipantKindSchema>;
8
+
9
+ export const RealtimeTransportKindSchema = z.enum(['livekit', 'ws-compat']);
10
+ export type RealtimeTransportKind = z.infer<typeof RealtimeTransportKindSchema>;
11
+
12
+ export const RealtimeSessionDirectionSchema = z.enum(['recv', 'send']);
13
+ export type RealtimeSessionDirection = z.infer<typeof RealtimeSessionDirectionSchema>;
14
+
15
+ export const RealtimeConnectivityErrorCodeSchema = z.enum([
16
+ 'TOKEN_REQUEST_FAILED',
17
+ 'SIGNALING_UNREACHABLE',
18
+ 'PUBLIC_URL_MISCONFIGURED',
19
+ 'ICE_CONNECTION_FAILED',
20
+ 'NO_AUDIO_TRACK',
21
+ 'AUDIO_PLAYBACK_BLOCKED',
22
+ 'SESSION_EXPIRED_OR_INVALID',
23
+ 'MEDIA_DEVICE_PERMISSION_DENIED',
24
+ 'UNKNOWN_REALTIME_ERROR',
25
+ ]);
26
+ export type RealtimeConnectivityErrorCode = z.infer<typeof RealtimeConnectivityErrorCodeSchema>;
27
+
28
+ export const RealtimeConnectivityHintsSchema = z.object({
29
+ signalingUrl: z.string(),
30
+ signalingPort: z.number().int().positive(),
31
+ rtcTcpPort: z.number().int().positive(),
32
+ udpPortRange: z.string(),
33
+ publicUrlOverrideActive: z.boolean(),
34
+ });
35
+ export type RealtimeConnectivityHints = z.infer<typeof RealtimeConnectivityHintsSchema>;
36
+
37
+ export const RealtimeConnectivityIssueSchema = z.object({
38
+ code: RealtimeConnectivityErrorCodeSchema,
39
+ scope: RealtimeScopeSchema,
40
+ stage: z.enum(['token', 'connect', 'publish', 'subscribe', 'runtime']),
41
+ userMessage: z.string(),
42
+ suggestions: z.array(z.string()),
43
+ technicalDetails: z.string().optional(),
44
+ context: z.record(z.string()).optional(),
45
+ });
46
+ export type RealtimeConnectivityIssue = z.infer<typeof RealtimeConnectivityIssueSchema>;
47
+
48
+ export const RealtimeTokenRequestSchema = z.object({
49
+ scope: RealtimeScopeSchema,
50
+ publish: z.boolean().optional(),
51
+ previewSessionId: z.string().optional(),
52
+ });
53
+
54
+ export type RealtimeTokenRequest = z.infer<typeof RealtimeTokenRequestSchema>;
55
+
56
+ export const RealtimeSessionRequestSchema = z.object({
57
+ scope: RealtimeScopeSchema,
58
+ direction: RealtimeSessionDirectionSchema,
59
+ previewSessionId: z.string().optional(),
60
+ transportOverride: RealtimeTransportKindSchema.optional(),
61
+ });
62
+
63
+ export type RealtimeSessionRequest = z.infer<typeof RealtimeSessionRequestSchema>;
64
+
65
+ export const RealtimeParticipantMetadataSchema = z.object({
66
+ role: z.string(),
67
+ tokenId: z.string().nullable().optional(),
68
+ operatorIds: z.array(z.string()).optional(),
69
+ clientKind: z.string(),
70
+ participantKind: RealtimeParticipantKindSchema,
71
+ scope: RealtimeScopeSchema,
72
+ previewSessionId: z.string().optional(),
73
+ });
74
+
75
+ export type RealtimeParticipantMetadata = z.infer<typeof RealtimeParticipantMetadataSchema>;
76
+
77
+ export const RealtimeTokenResponseSchema = z.object({
78
+ url: z.string(),
79
+ roomName: z.string(),
80
+ token: z.string(),
81
+ participantIdentity: z.string(),
82
+ participantName: z.string(),
83
+ participantMetadata: RealtimeParticipantMetadataSchema,
84
+ connectivityHints: RealtimeConnectivityHintsSchema,
85
+ });
86
+
87
+ export type RealtimeTokenResponse = z.infer<typeof RealtimeTokenResponseSchema>;
88
+
89
+ export const RealtimeTransportOfferSchema = z.object({
90
+ transport: RealtimeTransportKindSchema,
91
+ direction: RealtimeSessionDirectionSchema,
92
+ url: z.string(),
93
+ token: z.string(),
94
+ participantIdentity: z.string().nullable().optional(),
95
+ participantName: z.string().nullable().optional(),
96
+ roomName: z.string().nullable().optional(),
97
+ });
98
+
99
+ export type RealtimeTransportOffer = z.infer<typeof RealtimeTransportOfferSchema>;
100
+
101
+ export const RealtimeTransportPolicySchema = z.enum(['auto', 'force-compat']);
102
+ export type RealtimeTransportPolicy = z.infer<typeof RealtimeTransportPolicySchema>;
103
+
104
+ export const LiveKitNetworkModeSchema = z.enum(['lan', 'internet-auto', 'internet-manual']);
105
+ export type LiveKitNetworkMode = z.infer<typeof LiveKitNetworkModeSchema>;
106
+
107
+ export const RealtimeSessionResponseSchema = z.object({
108
+ scope: RealtimeScopeSchema,
109
+ direction: RealtimeSessionDirectionSchema,
110
+ preferredTransport: RealtimeTransportKindSchema,
111
+ effectiveTransportPolicy: RealtimeTransportPolicySchema,
112
+ selectionReason: z.enum([
113
+ 'client-override',
114
+ 'server-policy',
115
+ 'bridge-unhealthy',
116
+ 'livekit-disabled',
117
+ 'runtime-unavailable',
118
+ 'default-livekit',
119
+ ]),
120
+ forcedCompatibilityMode: z.boolean(),
121
+ offers: z.array(RealtimeTransportOfferSchema).min(1),
122
+ connectivityHints: RealtimeConnectivityHintsSchema,
123
+ });
124
+
125
+ export type RealtimeSessionResponse = z.infer<typeof RealtimeSessionResponseSchema>;
126
+
127
+ export const RealtimeSettingsSchema = z.object({
128
+ publicWsUrl: z.string().url().nullable().optional(),
129
+ transportPolicy: RealtimeTransportPolicySchema.optional(),
130
+ networkMode: LiveKitNetworkModeSchema.optional(),
131
+ nodeIp: z.string().nullable().optional(),
132
+ });
133
+
134
+ export type RealtimeSettings = z.infer<typeof RealtimeSettingsSchema>;
135
+
136
+ export const RealtimeCredentialSourceSchema = z.enum(['managed-file', 'environment-override', 'missing']);
137
+ export type RealtimeCredentialSource = z.infer<typeof RealtimeCredentialSourceSchema>;
138
+
139
+ export const RealtimeCredentialStatusSchema = z.object({
140
+ initialized: z.boolean(),
141
+ source: RealtimeCredentialSourceSchema,
142
+ filePath: z.string().nullable(),
143
+ apiKeyPreview: z.string().nullable(),
144
+ createdAt: z.string().nullable(),
145
+ rotatedAt: z.string().nullable(),
146
+ });
147
+ export type RealtimeCredentialStatus = z.infer<typeof RealtimeCredentialStatusSchema>;
148
+
149
+ export const RealtimeSettingsRuntimeSchema = z.object({
150
+ liveKitEnabled: z.boolean(),
151
+ connectivityHints: RealtimeConnectivityHintsSchema,
152
+ radioReceiveTransport: RealtimeTransportKindSchema,
153
+ radioBridgeHealthy: z.boolean(),
154
+ radioBridgeIssueCode: RealtimeConnectivityErrorCodeSchema.nullable(),
155
+ credentialStatus: RealtimeCredentialStatusSchema,
156
+ });
157
+
158
+ export type RealtimeSettingsRuntime = z.infer<typeof RealtimeSettingsRuntimeSchema>;
159
+
160
+ export const RealtimeSettingsResponseDataSchema = RealtimeSettingsSchema.extend({
161
+ runtime: RealtimeSettingsRuntimeSchema.optional(),
162
+ });
163
+
164
+ export type RealtimeSettingsResponseData = z.infer<typeof RealtimeSettingsResponseDataSchema>;
165
+
166
+ export const RealtimeSourceStatsSchema = z.object({
167
+ latencyMs: z.number(),
168
+ bufferFillPercent: z.number(),
169
+ isActive: z.boolean(),
170
+ audioLevel: z.number().optional(),
171
+ droppedSamples: z.number().optional(),
172
+ sampleRate: z.number(),
173
+ });
174
+
175
+ export type RealtimeSourceStats = z.infer<typeof RealtimeSourceStatsSchema>;
176
+
177
+ export const RealtimeStatsRequestSchema = z.object({
178
+ scope: RealtimeScopeSchema,
179
+ previewSessionId: z.string().optional(),
180
+ });
181
+
182
+ export type RealtimeStatsRequest = z.infer<typeof RealtimeStatsRequestSchema>;
183
+
184
+ export const RealtimeStatsResponseSchema = z.object({
185
+ scope: RealtimeScopeSchema,
186
+ previewSessionId: z.string().nullable().optional(),
187
+ source: RealtimeSourceStatsSchema.nullable(),
188
+ transport: RealtimeTransportKindSchema.nullable().optional(),
189
+ });
190
+
191
+ export type RealtimeStatsResponse = z.infer<typeof RealtimeStatsResponseSchema>;
192
+
193
+ export const RealtimeVoiceTxMetricWindowSchema = z.object({
194
+ current: z.number().nullable(),
195
+ rolling: z.number().nullable(),
196
+ peak: z.number().nullable(),
197
+ });
198
+
199
+ export type RealtimeVoiceTxMetricWindow = z.infer<typeof RealtimeVoiceTxMetricWindowSchema>;
200
+
201
+ export const RealtimeVoiceTxBottleneckStageSchema = z.enum([
202
+ 'client-capture',
203
+ 'transport',
204
+ 'server-ingress',
205
+ 'server-queue',
206
+ 'server-output',
207
+ ]);
208
+
209
+ export type RealtimeVoiceTxBottleneckStage = z.infer<typeof RealtimeVoiceTxBottleneckStageSchema>;
210
+
211
+ export const RealtimeVoiceTxSummarySchema = z.object({
212
+ active: z.boolean(),
213
+ transport: RealtimeTransportKindSchema.nullable(),
214
+ bottleneckStage: RealtimeVoiceTxBottleneckStageSchema.nullable(),
215
+ startedAt: z.number().nullable(),
216
+ updatedAt: z.number().nullable(),
217
+ clientId: z.string().nullable(),
218
+ label: z.string().nullable(),
219
+ });
220
+
221
+ export type RealtimeVoiceTxSummary = z.infer<typeof RealtimeVoiceTxSummarySchema>;
222
+
223
+ export const RealtimeVoiceTxTransportStatsSchema = z.object({
224
+ receivedFrames: z.number().int().nonnegative(),
225
+ sequenceGaps: z.number().int().nonnegative(),
226
+ lastSequence: z.number().int().nullable(),
227
+ clientToServerMs: RealtimeVoiceTxMetricWindowSchema,
228
+ });
229
+
230
+ export type RealtimeVoiceTxTransportStats = z.infer<typeof RealtimeVoiceTxTransportStatsSchema>;
231
+
232
+ export const RealtimeVoiceTxServerIngressStatsSchema = z.object({
233
+ frameIntervalMs: RealtimeVoiceTxMetricWindowSchema,
234
+ queueDepthFrames: z.number().int().nonnegative(),
235
+ queuedAudioMs: z.number().nonnegative(),
236
+ droppedFrames: z.number().int().nonnegative(),
237
+ });
238
+
239
+ export type RealtimeVoiceTxServerIngressStats = z.infer<typeof RealtimeVoiceTxServerIngressStatsSchema>;
240
+
241
+ export const RealtimeVoiceTxServerOutputStatsSchema = z.object({
242
+ resampleMs: RealtimeVoiceTxMetricWindowSchema,
243
+ queueWaitMs: RealtimeVoiceTxMetricWindowSchema,
244
+ writeMs: RealtimeVoiceTxMetricWindowSchema,
245
+ endToEndMs: RealtimeVoiceTxMetricWindowSchema,
246
+ outputBufferedMs: RealtimeVoiceTxMetricWindowSchema,
247
+ outputSampleRate: z.number().nullable(),
248
+ outputBufferSize: z.number().nullable(),
249
+ writeFailures: z.number().int().nonnegative(),
250
+ });
251
+
252
+ export type RealtimeVoiceTxServerOutputStats = z.infer<typeof RealtimeVoiceTxServerOutputStatsSchema>;
253
+
254
+ export const RealtimeVoiceTxStatsResponseSchema = z.object({
255
+ scope: RealtimeScopeSchema,
256
+ summary: RealtimeVoiceTxSummarySchema,
257
+ transport: RealtimeVoiceTxTransportStatsSchema,
258
+ serverIngress: RealtimeVoiceTxServerIngressStatsSchema,
259
+ serverOutput: RealtimeVoiceTxServerOutputStatsSchema,
260
+ });
261
+
262
+ export type RealtimeVoiceTxStatsResponse = z.infer<typeof RealtimeVoiceTxStatsResponseSchema>;
@@ -0,0 +1,51 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * rigctld-compatible TCP bridge configuration.
5
+ *
6
+ * Disabled by default — opening a TCP port that accepts CAT control without
7
+ * authentication is a sensitive default, so users must opt in.
8
+ */
9
+ export const RigctldBridgeConfigSchema = z.object({
10
+ enabled: z.boolean().default(false),
11
+ bindAddress: z.string().default('0.0.0.0'),
12
+ port: z.number().int().min(1).max(65535).default(4532),
13
+ /**
14
+ * When true (default), the bridge rejects every write command
15
+ * (`F`/`M`/`T`/`S`/`L`/`set_*`) with `RPRT -11` and only allows reads.
16
+ * Covers the safe "N1MM records QSOs while the operator tunes manually"
17
+ * workflow. Disable only when you need software like N1MM / WSJT-X to
18
+ * actively control the rig (set frequency, PTT, split).
19
+ */
20
+ readOnly: z.boolean().default(true),
21
+ });
22
+
23
+ export type RigctldBridgeConfig = z.infer<typeof RigctldBridgeConfigSchema>;
24
+
25
+ export const DEFAULT_RIGCTLD_BRIDGE_CONFIG: RigctldBridgeConfig = {
26
+ enabled: false,
27
+ bindAddress: '0.0.0.0',
28
+ port: 4532,
29
+ readOnly: true,
30
+ };
31
+
32
+ /** Per-connection client snapshot used by the status API and UI. */
33
+ export const RigctldClientSnapshotSchema = z.object({
34
+ id: z.number().int(),
35
+ peer: z.string(),
36
+ connectedAt: z.number().int(),
37
+ lastCommand: z.string().optional(),
38
+ lastCommandAt: z.number().int().optional(),
39
+ });
40
+
41
+ export type RigctldClientSnapshot = z.infer<typeof RigctldClientSnapshotSchema>;
42
+
43
+ export const RigctldStatusSchema = z.object({
44
+ config: RigctldBridgeConfigSchema,
45
+ running: z.boolean(),
46
+ address: z.object({ host: z.string(), port: z.number().int() }).optional(),
47
+ clients: z.array(RigctldClientSnapshotSchema),
48
+ error: z.string().optional(),
49
+ });
50
+
51
+ export type RigctldStatus = z.infer<typeof RigctldStatusSchema>;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 服务端推送的文本消息 key 枚举
3
+ * 前端根据 key 查找对应翻译,兜底显示 title/text 原始值
4
+ */
5
+ export enum ServerMessageKey {
6
+ /** 时序告警:操作员自动决策可能赶不上此发射时隙的编码 */
7
+ TIMING_ALERT = 'timingAlert',
8
+
9
+ /** 电台已连接 */
10
+ RADIO_CONNECTED = 'radioConnected',
11
+
12
+ /** QSO 已记录 */
13
+ QSO_LOGGED = 'qsoLogged',
14
+
15
+ /** QSO 已更新 */
16
+ QSO_UPDATED = 'qsoUpdated',
17
+
18
+ /** 实时语音连接异常 */
19
+ REALTIME_CONNECTIVITY_WARNING = 'realtimeConnectivityWarning',
20
+
21
+ /** 服务端实时语音桥接异常 */
22
+ REALTIME_BRIDGE_DOWN = 'realtimeBridgeDown',
23
+
24
+ /** 实时语音链路无音频数据 */
25
+ REALTIME_NO_AUDIO = 'realtimeNoAudio',
26
+ }
@@ -0,0 +1,162 @@
1
+ import { z } from 'zod';
2
+ import { DxccStatusSchema, SubdivisionConfidenceSchema } from './qso.schema.js';
3
+
4
+ /**
5
+ * 时隙周期(偶数奇数)
6
+ */
7
+
8
+ /**
9
+ * 时隙信息
10
+ */
11
+ export const SlotInfoSchema = z.object({
12
+ /** 时隙唯一标识符 */
13
+ id: z.string(),
14
+ /** 时隙开始时间戳(毫秒) */
15
+ startMs: z.number(),
16
+ /** 相位偏移(毫秒) */
17
+ phaseMs: z.number(),
18
+ /** 时钟漂移(毫秒) */
19
+ driftMs: z.number().default(0),
20
+ /** 时隙周期号 */
21
+ cycleNumber: z.number(),
22
+ /** 时隙UTC时间戳(秒) */
23
+ utcSeconds: z.number(),
24
+ /** 时隙类型(FT8/FT4) */
25
+ mode: z.string()
26
+ });
27
+
28
+ export type SlotInfo = z.infer<typeof SlotInfoSchema>;
29
+
30
+ /**
31
+ * 基于日志本的消息分析结果
32
+ */
33
+ export const LogbookAnalysisSchema = z.object({
34
+ /** 是否为新呼号(之前没有通联过) */
35
+ isNewCallsign: z.boolean().optional(),
36
+ /** 是否为新 DXCC 实体(之前没有通联过该实体) */
37
+ isNewDxccEntity: z.boolean().optional(),
38
+ /** 是否为该波段的新 DXCC 实体 */
39
+ isNewBandDxccEntity: z.boolean().optional(),
40
+ /** 是否为已确认 DXCC */
41
+ isConfirmedDxcc: z.boolean().optional(),
42
+ /** 是否为新网格(之前没有通联过此网格) */
43
+ isNewGrid: z.boolean().optional(),
44
+ /** 解析出的呼号(如果有) */
45
+ callsign: z.string().optional(),
46
+ /** 解析出的网格(如果有) */
47
+ grid: z.string().optional(),
48
+ /** 解析出的前缀(如果有) */
49
+ prefix: z.string().optional(),
50
+ /** 美国 subdivision code(州/属地),仅在可解析时提供 */
51
+ state: z.string().optional(),
52
+ /** subdivision 置信度 */
53
+ stateConfidence: SubdivisionConfidenceSchema.optional(),
54
+ /** DXCC 实体编号 */
55
+ dxccId: z.number().int().positive().optional(),
56
+ /** DXCC 实体名 */
57
+ dxccEntity: z.string().optional(),
58
+ /** 实体状态 */
59
+ dxccStatus: DxccStatusSchema.optional(),
60
+ });
61
+
62
+ export type LogbookAnalysis = z.infer<typeof LogbookAnalysisSchema>;
63
+
64
+ /**
65
+ * FT8 帧数据
66
+ */
67
+ export const FrameMessageSchema = z.object({
68
+ /** 信号强度 (dB) */
69
+ snr: z.number(),
70
+ /** 频率偏移 (Hz) */
71
+ freq: z.number(),
72
+ /** 时间偏移 (秒) */
73
+ dt: z.number(),
74
+ /** 解码消息 */
75
+ message: z.string(),
76
+ /** 置信度 0-1 */
77
+ confidence: z.number().min(0).max(1).default(1.0),
78
+ /** 基于日志本的分析结果(可选,仅在客户端定制化数据中提供) */
79
+ logbookAnalysis: LogbookAnalysisSchema.optional(),
80
+ /** 操作员ID(可选,仅 TX 帧使用,用于多操作员覆盖识别) */
81
+ operatorId: z.string().optional()
82
+ });
83
+
84
+ export type FrameMessage = z.infer<typeof FrameMessageSchema>;
85
+
86
+ /**
87
+ * 解码请求,用于SlotScheduler内部
88
+ */
89
+ export const DecodeRequestSchema = z.object({
90
+ /** 关联的时隙ID */
91
+ slotId: z.string(),
92
+ /** 子窗口索引 */
93
+ windowIdx: z.number().int().nonnegative(),
94
+ /** PCM 音频数据 */
95
+ pcm: z.instanceof(ArrayBuffer),
96
+ /** 采样率 */
97
+ sampleRate: z.number().positive().default(12000),
98
+ /** 请求时间戳 */
99
+ timestamp: z.number().default(() => Date.now()),
100
+ /** 窗口时间偏移(毫秒) */
101
+ windowOffsetMs: z.number().default(0)
102
+ });
103
+
104
+ export type DecodeRequest = z.infer<typeof DecodeRequestSchema>;
105
+
106
+ /**
107
+ * 解码结果,用于SlotScheduler内部
108
+ */
109
+ export const DecodeResultSchema = z.object({
110
+ /** 关联的时隙ID */
111
+ slotId: z.string(),
112
+ /** 子窗口索引 */
113
+ windowIdx: z.number().int().nonnegative(),
114
+ /** 解码出的帧数据 */
115
+ frames: z.array(FrameMessageSchema),
116
+ /** 结果时间戳 */
117
+ timestamp: z.number().default(() => Date.now()),
118
+ /** 处理耗时(毫秒) */
119
+ processingTimeMs: z.number().nonnegative().default(0),
120
+ /** 错误信息(如果有) */
121
+ error: z.string().optional(),
122
+ /** 窗口时间偏移(毫秒) */
123
+ windowOffsetMs: z.number().default(0)
124
+ });
125
+
126
+ export type DecodeResult = z.infer<typeof DecodeResultSchema>;
127
+
128
+ /**
129
+ * 时隙封装信息(去重和多次解码取优)
130
+ */
131
+ export const SlotPackSchema = z.object({
132
+ /** 时隙ID */
133
+ slotId: z.string(),
134
+ /** 时隙开始时间戳(毫秒) */
135
+ startMs: z.number(),
136
+ /** 时隙结束时间戳(毫秒) */
137
+ endMs: z.number(),
138
+ /** 去重后的最优解码结果 */
139
+ frames: z.array(FrameMessageSchema),
140
+ /** 解码统计信息 */
141
+ stats: z.object({
142
+ /** 总解码次数 */
143
+ totalDecodes: z.number().default(0),
144
+ /** 成功解码次数 */
145
+ successfulDecodes: z.number().default(0),
146
+ /** 去重前的总帧数 */
147
+ totalFramesBeforeDedup: z.number().default(0),
148
+ /** 去重后的帧数 */
149
+ totalFramesAfterDedup: z.number().default(0),
150
+ /** 最后更新时间戳 */
151
+ lastUpdated: z.number().default(() => Date.now())
152
+ }).default({}),
153
+ /** 解码历史(用于调试) */
154
+ decodeHistory: z.array(z.object({
155
+ windowIdx: z.number(),
156
+ timestamp: z.number(),
157
+ frameCount: z.number(),
158
+ processingTimeMs: z.number()
159
+ })).default([])
160
+ });
161
+
162
+ export type SlotPack = z.infer<typeof SlotPackSchema>;
@@ -0,0 +1,166 @@
1
+ import { z } from 'zod';
2
+
3
+ export const SpectrumKindSchema = z.enum(['audio', 'radio-sdr', 'openwebrx-sdr']);
4
+ export type SpectrumKind = z.infer<typeof SpectrumKindSchema>;
5
+
6
+ export const SpectrumDisplayModeSchema = z.enum(['center', 'fixed', 'scroll-center', 'scroll-fixed', 'unknown']);
7
+ export type SpectrumDisplayMode = z.infer<typeof SpectrumDisplayModeSchema>;
8
+
9
+ export const SpectrumSessionSourceModeSchema = z.enum([
10
+ 'baseband',
11
+ 'center',
12
+ 'fixed',
13
+ 'scroll-center',
14
+ 'scroll-fixed',
15
+ 'full',
16
+ 'detail',
17
+ 'unknown',
18
+ ]);
19
+ export type SpectrumSessionSourceMode = z.infer<typeof SpectrumSessionSourceModeSchema>;
20
+
21
+ export const SpectrumSessionFrequencyRangeModeSchema = z.enum([
22
+ 'baseband',
23
+ 'absolute-center',
24
+ 'absolute-fixed',
25
+ 'absolute-windowed',
26
+ ]);
27
+ export type SpectrumSessionFrequencyRangeMode = z.infer<typeof SpectrumSessionFrequencyRangeModeSchema>;
28
+
29
+ export const SpectrumFrequencyRangeSchema = z.object({
30
+ min: z.number(),
31
+ max: z.number(),
32
+ });
33
+
34
+ export const SpectrumBinaryFormatSchema = z.object({
35
+ type: z.literal('int16'),
36
+ length: z.number().int().positive(),
37
+ scale: z.number().optional(),
38
+ offset: z.number().optional(),
39
+ });
40
+
41
+ export const SpectrumBinaryDataSchema = z.object({
42
+ data: z.string(),
43
+ format: SpectrumBinaryFormatSchema,
44
+ });
45
+
46
+ export const SpectrumFrameMetaSchema = z.object({
47
+ sourceBinCount: z.number().int().positive(),
48
+ displayBinCount: z.number().int().positive(),
49
+ centerFrequency: z.number().optional(),
50
+ spanHz: z.number().optional(),
51
+ profileId: z.string().nullable().optional(),
52
+ radioModel: z.string().optional(),
53
+ });
54
+
55
+ export const SpectrumFrameSchema = z.object({
56
+ timestamp: z.number(),
57
+ kind: SpectrumKindSchema,
58
+ frequencyRange: SpectrumFrequencyRangeSchema,
59
+ binaryData: SpectrumBinaryDataSchema,
60
+ meta: SpectrumFrameMetaSchema,
61
+ });
62
+
63
+ export const SpectrumSourceAvailabilitySchema = z.object({
64
+ kind: SpectrumKindSchema,
65
+ supported: z.boolean(),
66
+ available: z.boolean(),
67
+ defaultSelected: z.boolean(),
68
+ reason: z.string().optional(),
69
+ sourceBinCount: z.number().int().positive().nullable().optional(),
70
+ displayBinCount: z.number().int().positive(),
71
+ supportsWaterfall: z.boolean(),
72
+ frequencyRangeMode: z.enum(['absolute', 'baseband']),
73
+ });
74
+
75
+ export const SpectrumCapabilitiesSchema = z.object({
76
+ profileId: z.string().nullable(),
77
+ defaultKind: SpectrumKindSchema,
78
+ sources: z.array(SpectrumSourceAvailabilitySchema),
79
+ });
80
+
81
+ export const SpectrumSessionControlIdSchema = z.enum([
82
+ 'zoom-step',
83
+ 'digital-window-toggle',
84
+ 'openwebrx-detail-toggle',
85
+ 'viewport-zoom',
86
+ ]);
87
+ export type SpectrumSessionControlId = z.infer<typeof SpectrumSessionControlIdSchema>;
88
+
89
+ export const SpectrumSessionControlActionSchema = z.enum(['in', 'out', 'toggle']);
90
+ export type SpectrumSessionControlAction = z.infer<typeof SpectrumSessionControlActionSchema>;
91
+
92
+ export const SpectrumSessionControlKindSchema = z.enum(['server', 'local']);
93
+ export type SpectrumSessionControlKind = z.infer<typeof SpectrumSessionControlKindSchema>;
94
+
95
+ export const SpectrumSessionControlSchema = z.object({
96
+ id: SpectrumSessionControlIdSchema,
97
+ action: SpectrumSessionControlActionSchema,
98
+ kind: SpectrumSessionControlKindSchema,
99
+ visible: z.boolean(),
100
+ enabled: z.boolean(),
101
+ active: z.boolean(),
102
+ pending: z.boolean(),
103
+ });
104
+ export type SpectrumSessionControl = z.infer<typeof SpectrumSessionControlSchema>;
105
+
106
+ export const SpectrumSessionVoiceStateSchema = z.object({
107
+ radioMode: z.string().nullable(),
108
+ bandwidthLabel: z.string().nullable(),
109
+ occupiedBandwidthHz: z.number().nullable(),
110
+ offsetModel: z.enum(['upper', 'lower', 'symmetric']).nullable(),
111
+ });
112
+ export type SpectrumSessionVoiceState = z.infer<typeof SpectrumSessionVoiceStateSchema>;
113
+
114
+ export const SpectrumSessionPresetMarkerSchema = z.object({
115
+ id: z.string(),
116
+ frequency: z.number(),
117
+ label: z.string(),
118
+ description: z.string().nullable(),
119
+ clickable: z.boolean(),
120
+ });
121
+ export type SpectrumSessionPresetMarker = z.infer<typeof SpectrumSessionPresetMarkerSchema>;
122
+
123
+ export const SpectrumSessionInteractionStateSchema = z.object({
124
+ showTxMarkers: z.boolean(),
125
+ showRxMarkers: z.boolean(),
126
+ canDragTx: z.boolean(),
127
+ canRightClickSetFrequency: z.boolean(),
128
+ canDoubleClickSetFrequency: z.boolean(),
129
+ canDragFrequency: z.boolean(),
130
+ frequencyGestureTarget: z.enum(['operator-tx', 'radio-frequency']).nullable(),
131
+ frequencyStepHz: z.number().int().positive().nullable(),
132
+ presetMarkers: z.array(SpectrumSessionPresetMarkerSchema),
133
+ canDragVoiceOverlay: z.boolean(),
134
+ showVoiceOverlay: z.boolean(),
135
+ canLocalViewportZoom: z.boolean(),
136
+ canLocalViewportPan: z.boolean(),
137
+ supportsManualRange: z.boolean(),
138
+ supportsAutoRange: z.boolean(),
139
+ defaultRangeMode: z.enum(['auto', 'manual']).nullable(),
140
+ });
141
+ export type SpectrumSessionInteractionState = z.infer<typeof SpectrumSessionInteractionStateSchema>;
142
+
143
+ export const SpectrumSessionStateSchema = z.object({
144
+ kind: SpectrumKindSchema.nullable(),
145
+ sourceMode: SpectrumSessionSourceModeSchema,
146
+ frequencyRangeMode: SpectrumSessionFrequencyRangeModeSchema,
147
+ displayRange: SpectrumFrequencyRangeSchema.nullable(),
148
+ centerFrequency: z.number().nullable(),
149
+ currentRadioFrequency: z.number().nullable(),
150
+ standardFrequencyHz: z.number().nullable(),
151
+ edgeLowHz: z.number().nullable(),
152
+ edgeHighHz: z.number().nullable(),
153
+ spanHz: z.number().nullable(),
154
+ voice: SpectrumSessionVoiceStateSchema,
155
+ interaction: SpectrumSessionInteractionStateSchema,
156
+ controls: z.array(SpectrumSessionControlSchema),
157
+ });
158
+ export type SpectrumSessionState = z.infer<typeof SpectrumSessionStateSchema>;
159
+
160
+ export type SpectrumFrequencyRange = z.infer<typeof SpectrumFrequencyRangeSchema>;
161
+ export type SpectrumBinaryFormat = z.infer<typeof SpectrumBinaryFormatSchema>;
162
+ export type SpectrumBinaryData = z.infer<typeof SpectrumBinaryDataSchema>;
163
+ export type SpectrumFrameMeta = z.infer<typeof SpectrumFrameMetaSchema>;
164
+ export type SpectrumFrame = z.infer<typeof SpectrumFrameSchema>;
165
+ export type SpectrumSourceAvailability = z.infer<typeof SpectrumSourceAvailabilitySchema>;
166
+ export type SpectrumCapabilities = z.infer<typeof SpectrumCapabilitiesSchema>;