camstreamerlib 4.0.34 → 4.0.35-bc56d1e

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.
@@ -1,12 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.REMOTE_ID_CATEGORY_IDS = exports.ADSB_CATEGORY_IDS = exports.domainListSchema = exports.zonesSchema = exports.zonePerimeterSchema = exports.domainIdSchema = exports.mapInfoSchema = exports.mapTypeSchema = exports.friendlyListSchema = exports.priorityListSchema = exports.blackListSchema = exports.whiteListSchema = exports.priorityListEntrySchema = exports.listEntrySchema = exports.listEntryIdTypeSchema = exports.listEntryDomainSchema = exports.flightInfoSchema = exports.trackingModeSchema = exports.getIcaoSchema = exports.serverSettingsSchema = exports.cameraSettingsSchema = exports.widgetSchema = exports.milestoneCameraListSchema = exports.connectionSchema = exports.wsAliasResponseSchema = void 0;
3
+ exports.REMOTE_ID_CATEGORY_IDS = exports.ADSB_CATEGORY_IDS = exports.domainListSchema = exports.zonesSchema = exports.zonePerimeterSchema = exports.domainIdSchema = exports.mapInfoSchema = exports.mapTypeSchema = exports.friendlyListSchema = exports.priorityListSchema = exports.blackListSchema = exports.whiteListSchema = exports.priorityListEntrySchema = exports.listEntrySchema = exports.listEntryIdTypeSchema = exports.listEntryDomainSchema = exports.flightInfoSchema = exports.trackingModeSchema = exports.getIcaoSchema = exports.serverSettingsSchema = exports.cameraSettingsSchema = exports.widgetSchema = exports.milestoneCameraListSchema = exports.connectionSchema = void 0;
4
4
  const zod_1 = require("zod");
5
- exports.wsAliasResponseSchema = zod_1.z.object({
6
- alias: zod_1.z.string(),
7
- ws: zod_1.z.string(),
8
- ws_initial_message: zod_1.z.string(),
9
- });
10
5
  exports.connectionSchema = zod_1.z.object({
11
6
  protocol: zod_1.z.union([zod_1.z.literal('http'), zod_1.z.literal('https'), zod_1.z.literal('https_insecure')]),
12
7
  ip: zod_1.z.union([zod_1.z.string().ip(), zod_1.z.literal('')]),
@@ -40,13 +35,59 @@ const labelOptionsSchema = zod_1.z.union([
40
35
  zod_1.z.literal('flight_number'),
41
36
  zod_1.z.literal('icao'),
42
37
  ]);
43
- const identificationLabelSchema = zod_1.z.object({
38
+ const aircraftIdentificationLabelSchema = zod_1.z.object({
44
39
  firstRow: labelOptionsSchema,
45
40
  secondRow: labelOptionsSchema,
46
41
  thirdRow: labelOptionsSchema,
47
42
  fourthRow: labelOptionsSchema,
48
- opacity: zod_1.z.number().positive(),
43
+ opacity: zod_1.z.number().nonnegative(),
44
+ });
45
+ const droneLabelOptionsSchema = zod_1.z.union([zod_1.z.literal('blank'), zod_1.z.literal('remote_id')]);
46
+ const droneIdentificationLabelSchema = zod_1.z.object({
47
+ firstRow: droneLabelOptionsSchema,
48
+ opacity: zod_1.z.number().nonnegative(),
49
+ });
50
+ const DEFAULT_DRONE_IDENTIFICATION_LABEL = {
51
+ firstRow: 'remote_id',
52
+ opacity: 30,
53
+ };
54
+ const identificationLabelSchema = zod_1.z.union([
55
+ zod_1.z.object({
56
+ aircraft: aircraftIdentificationLabelSchema,
57
+ drone: droneIdentificationLabelSchema.default(DEFAULT_DRONE_IDENTIFICATION_LABEL),
58
+ }),
59
+ aircraftIdentificationLabelSchema.transform((aircraft) => ({
60
+ aircraft,
61
+ drone: { ...DEFAULT_DRONE_IDENTIFICATION_LABEL },
62
+ })),
63
+ ]);
64
+ const captureConfigSchema = zod_1.z.object({
65
+ defaultCaptureSizeMeters: zod_1.z.number().positive(),
66
+ captureSizeExtensionMeters: zod_1.z.number().positive(),
49
67
  });
68
+ const DEFAULT_CAPTURE_CONFIG = {
69
+ defaultCaptureSizeMeters: 120,
70
+ captureSizeExtensionMeters: 80,
71
+ };
72
+ const cameraConfigSchema = zod_1.z.union([
73
+ zod_1.z.object({
74
+ aircraft: captureConfigSchema,
75
+ drone: captureConfigSchema.default(() => ({ ...DEFAULT_CAPTURE_CONFIG })),
76
+ }),
77
+ zod_1.z
78
+ .object({
79
+ defaultCaptureSizeMeters: zod_1.z.number().positive().default(DEFAULT_CAPTURE_CONFIG.defaultCaptureSizeMeters),
80
+ captureSizeExtensionMeters: zod_1.z
81
+ .number()
82
+ .positive()
83
+ .default(DEFAULT_CAPTURE_CONFIG.captureSizeExtensionMeters),
84
+ })
85
+ .strict()
86
+ .transform((captureConfig) => ({
87
+ aircraft: captureConfig,
88
+ drone: { ...captureConfig },
89
+ })),
90
+ ]);
50
91
  exports.cameraSettingsSchema = zod_1.z.object({
51
92
  units: zod_1.z.union([zod_1.z.literal('metric'), zod_1.z.literal('imperial')]).default('imperial'),
52
93
  adsbSource: zod_1.z
@@ -84,15 +125,10 @@ exports.cameraSettingsSchema = zod_1.z.object({
84
125
  nightSkyCalibrationFocusValue: 9000,
85
126
  focusCalibrationPoints: '',
86
127
  }),
87
- cameraConfig: zod_1.z
88
- .object({
89
- defaultCaptureSizeMeters: zod_1.z.number().positive().default(120),
90
- captureSizeExtensionMeters: zod_1.z.number().positive().default(80),
91
- })
92
- .default({
93
- defaultCaptureSizeMeters: 120,
94
- captureSizeExtensionMeters: 80,
95
- }),
128
+ cameraConfig: cameraConfigSchema.default(() => ({
129
+ aircraft: { ...DEFAULT_CAPTURE_CONFIG },
130
+ drone: { ...DEFAULT_CAPTURE_CONFIG },
131
+ })),
96
132
  stream: zod_1.z
97
133
  .object({
98
134
  width: zod_1.z.number().positive(),
@@ -203,11 +239,14 @@ exports.cameraSettingsSchema = zod_1.z.object({
203
239
  port: 80,
204
240
  }),
205
241
  identificationLabel: identificationLabelSchema.default({
206
- firstRow: 'registration',
207
- secondRow: 'blank',
208
- thirdRow: 'blank',
209
- fourthRow: 'blank',
210
- opacity: 30,
242
+ aircraft: {
243
+ firstRow: 'icao',
244
+ secondRow: 'call_sign',
245
+ thirdRow: 'blank',
246
+ fourthRow: 'blank',
247
+ opacity: 30,
248
+ },
249
+ drone: DEFAULT_DRONE_IDENTIFICATION_LABEL,
211
250
  }),
212
251
  acs: exports.connectionSchema
213
252
  .extend({
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const PlaneTrackerAPI_1 = require("./PlaneTrackerAPI");
4
+ const globals_1 = require("@jest/globals");
5
+ const AIRCRAFT_LABEL = {
6
+ firstRow: 'icao',
7
+ secondRow: 'call_sign',
8
+ thirdRow: 'blank',
9
+ fourthRow: 'registration',
10
+ opacity: 50,
11
+ };
12
+ const DEFAULT_DRONE_LABEL = { firstRow: 'remote_id', opacity: 30 };
13
+ (0, globals_1.describe)('cameraSettingsSchema identificationLabel', () => {
14
+ (0, globals_1.test)('defaults both aircraft and drone labels', () => {
15
+ (0, globals_1.expect)(PlaneTrackerAPI_1.cameraSettingsSchema.parse({}).identificationLabel).toEqual({
16
+ aircraft: {
17
+ firstRow: 'icao',
18
+ secondRow: 'call_sign',
19
+ thirdRow: 'blank',
20
+ fourthRow: 'blank',
21
+ opacity: 30,
22
+ },
23
+ drone: DEFAULT_DRONE_LABEL,
24
+ });
25
+ });
26
+ (0, globals_1.test)('parses the aircraft and drone label', () => {
27
+ const identificationLabel = { aircraft: AIRCRAFT_LABEL, drone: { firstRow: 'blank', opacity: 70 } };
28
+ (0, globals_1.expect)(PlaneTrackerAPI_1.cameraSettingsSchema.parse({ identificationLabel }).identificationLabel).toEqual(identificationLabel);
29
+ });
30
+ (0, globals_1.test)('defaults a missing drone label', () => {
31
+ (0, globals_1.expect)(PlaneTrackerAPI_1.cameraSettingsSchema.parse({ identificationLabel: { aircraft: AIRCRAFT_LABEL } }).identificationLabel).toEqual({ aircraft: AIRCRAFT_LABEL, drone: DEFAULT_DRONE_LABEL });
32
+ });
33
+ (0, globals_1.test)('converts the legacy flat label to the aircraft label', () => {
34
+ (0, globals_1.expect)(PlaneTrackerAPI_1.cameraSettingsSchema.parse({ identificationLabel: AIRCRAFT_LABEL }).identificationLabel).toEqual({
35
+ aircraft: AIRCRAFT_LABEL,
36
+ drone: DEFAULT_DRONE_LABEL,
37
+ });
38
+ });
39
+ (0, globals_1.test)('returns an independent drone label for each legacy parse', () => {
40
+ const first = PlaneTrackerAPI_1.cameraSettingsSchema.parse({ identificationLabel: AIRCRAFT_LABEL });
41
+ first.identificationLabel.drone.opacity = 90;
42
+ const second = PlaneTrackerAPI_1.cameraSettingsSchema.parse({ identificationLabel: AIRCRAFT_LABEL });
43
+ (0, globals_1.expect)(second.identificationLabel.drone).toEqual(DEFAULT_DRONE_LABEL);
44
+ });
45
+ (0, globals_1.test)('rejects an unknown drone label row', () => {
46
+ const identificationLabel = { aircraft: AIRCRAFT_LABEL, drone: { firstRow: 'icao', opacity: 30 } };
47
+ (0, globals_1.expect)(PlaneTrackerAPI_1.cameraSettingsSchema.safeParse({ identificationLabel }).success).toBe(false);
48
+ });
49
+ });
50
+ const CAPTURE_CONFIG = { defaultCaptureSizeMeters: 50, captureSizeExtensionMeters: 20 };
51
+ const DEFAULT_CAPTURE_CONFIG = { defaultCaptureSizeMeters: 120, captureSizeExtensionMeters: 80 };
52
+ (0, globals_1.describe)('cameraSettingsSchema cameraConfig', () => {
53
+ (0, globals_1.test)('defaults both aircraft and drone capture config', () => {
54
+ (0, globals_1.expect)(PlaneTrackerAPI_1.cameraSettingsSchema.parse({}).cameraConfig).toEqual({
55
+ aircraft: DEFAULT_CAPTURE_CONFIG,
56
+ drone: DEFAULT_CAPTURE_CONFIG,
57
+ });
58
+ });
59
+ (0, globals_1.test)('parses independent aircraft and drone capture config', () => {
60
+ const cameraConfig = {
61
+ aircraft: CAPTURE_CONFIG,
62
+ drone: { defaultCaptureSizeMeters: 5, captureSizeExtensionMeters: 3 },
63
+ };
64
+ (0, globals_1.expect)(PlaneTrackerAPI_1.cameraSettingsSchema.parse({ cameraConfig }).cameraConfig).toEqual(cameraConfig);
65
+ });
66
+ (0, globals_1.test)('defaults a missing drone capture config', () => {
67
+ (0, globals_1.expect)(PlaneTrackerAPI_1.cameraSettingsSchema.parse({ cameraConfig: { aircraft: CAPTURE_CONFIG } }).cameraConfig).toEqual({
68
+ aircraft: CAPTURE_CONFIG,
69
+ drone: DEFAULT_CAPTURE_CONFIG,
70
+ });
71
+ });
72
+ (0, globals_1.test)('copies the legacy flat capture config to both aircraft and drone', () => {
73
+ (0, globals_1.expect)(PlaneTrackerAPI_1.cameraSettingsSchema.parse({ cameraConfig: CAPTURE_CONFIG }).cameraConfig).toEqual({
74
+ aircraft: CAPTURE_CONFIG,
75
+ drone: CAPTURE_CONFIG,
76
+ });
77
+ });
78
+ (0, globals_1.test)('returns independent aircraft and drone objects', () => {
79
+ const legacy = PlaneTrackerAPI_1.cameraSettingsSchema.parse({ cameraConfig: CAPTURE_CONFIG }).cameraConfig;
80
+ legacy.drone.defaultCaptureSizeMeters = 1;
81
+ (0, globals_1.expect)(legacy.aircraft).toEqual(CAPTURE_CONFIG);
82
+ const first = PlaneTrackerAPI_1.cameraSettingsSchema.parse({}).cameraConfig;
83
+ first.drone.defaultCaptureSizeMeters = 1;
84
+ (0, globals_1.expect)(PlaneTrackerAPI_1.cameraSettingsSchema.parse({}).cameraConfig.drone).toEqual(DEFAULT_CAPTURE_CONFIG);
85
+ });
86
+ (0, globals_1.test)('rejects a non-positive drone capture size', () => {
87
+ const cameraConfig = { aircraft: CAPTURE_CONFIG, drone: { ...CAPTURE_CONFIG, defaultCaptureSizeMeters: 0 } };
88
+ (0, globals_1.expect)(PlaneTrackerAPI_1.cameraSettingsSchema.safeParse({ cameraConfig }).success).toBe(false);
89
+ });
90
+ });
@@ -26,6 +26,19 @@ export declare const keyboardShortcutSchema: z.ZodNullable<z.ZodString>;
26
26
  export declare const keyboardShortcutsSchema: z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodString>>;
27
27
  export type TKeyboardShortcut = z.infer<typeof keyboardShortcutSchema>;
28
28
  export type TKeyboardShortcuts = z.infer<typeof keyboardShortcutsSchema>;
29
+ export declare const wsAliasResponseSchema: z.ZodObject<{
30
+ alias: z.ZodString;
31
+ ws: z.ZodString;
32
+ ws_initial_message: z.ZodString;
33
+ }, "strip", z.ZodTypeAny, {
34
+ alias: string;
35
+ ws: string;
36
+ ws_initial_message: string;
37
+ }, {
38
+ alias: string;
39
+ ws: string;
40
+ ws_initial_message: string;
41
+ }>;
29
42
  export type TProxyTarget = {
30
43
  ip: string;
31
44
  mdnsName: string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.fileSchema = exports.bitrateVapixParamsSchema = exports.bitrateModeSchema = exports.keyboardShortcutsSchema = exports.keyboardShortcutSchema = exports.networkCameraListSchema = exports.storageTypeSchema = exports.sdCardStorageTypeSchema = exports.flashStorageTypeSchema = exports.h264ProfileSchema = exports.audioChannelCountSchema = exports.audioChannelSchema = exports.booleanSchema = void 0;
3
+ exports.fileSchema = exports.bitrateVapixParamsSchema = exports.bitrateModeSchema = exports.wsAliasResponseSchema = exports.keyboardShortcutsSchema = exports.keyboardShortcutSchema = exports.networkCameraListSchema = exports.storageTypeSchema = exports.sdCardStorageTypeSchema = exports.flashStorageTypeSchema = exports.h264ProfileSchema = exports.audioChannelCountSchema = exports.audioChannelSchema = exports.booleanSchema = void 0;
4
4
  const zod_1 = require("zod");
5
5
  exports.booleanSchema = zod_1.z.union([zod_1.z.literal(0), zod_1.z.literal(1)]);
6
6
  exports.audioChannelSchema = zod_1.z.union([zod_1.z.literal('mono'), zod_1.z.literal('stereo')]);
@@ -15,6 +15,11 @@ exports.networkCameraListSchema = zod_1.z.array(zod_1.z.object({
15
15
  }));
16
16
  exports.keyboardShortcutSchema = zod_1.z.string().nullable();
17
17
  exports.keyboardShortcutsSchema = zod_1.z.record(exports.keyboardShortcutSchema);
18
+ exports.wsAliasResponseSchema = zod_1.z.object({
19
+ alias: zod_1.z.string(),
20
+ ws: zod_1.z.string(),
21
+ ws_initial_message: zod_1.z.string(),
22
+ });
18
23
  exports.bitrateModeSchema = zod_1.z.union([zod_1.z.literal('VBR'), zod_1.z.literal('MBR'), zod_1.z.literal('ABR')]);
19
24
  exports.bitrateVapixParamsSchema = zod_1.z.object({
20
25
  bitrateMode: exports.bitrateModeSchema,
@@ -1,5 +1,5 @@
1
1
  import { ErrorWithResponse, ServiceNotFoundError, StorageDataFetchError } from './errors/errors';
2
- import { networkCameraListSchema } from './types/common';
2
+ import { networkCameraListSchema, wsAliasResponseSchema } from './types/common';
3
3
  import { z } from 'zod';
4
4
  import { ImageType, serviceListSchema, servicesSchema, wsResponseSchema, getStorageDataListSchema, getFileListSchema, } from './types/CamOverlayAPI';
5
5
  import { BasicAPI } from './internal/BasicAPI';
@@ -26,6 +26,22 @@ export class CamOverlayAPI extends BasicAPI {
26
26
  async getMjpegStreamImage(mjpegUrl, options) {
27
27
  return await this._getBlob(`${BASE_PATH}/fetch_mjpeg_image.cgi`, { mjpeg_url: decodeURIComponent(mjpegUrl) }, options);
28
28
  }
29
+ async isViewerPassValid(pass, options) {
30
+ const res = await this._getJson(`${BASE_PATH}/check_pass.cgi`, { pass }, options);
31
+ if (res.status !== 200) {
32
+ throw new Error(res.message);
33
+ }
34
+ return res.data === '1';
35
+ }
36
+ async getLiveViewAlias(rtspUrl, options) {
37
+ const agent = this.getClient(options?.proxyParams);
38
+ const res = await agent.get({
39
+ path: `${BASE_PATH}/getLiveViewAlias.cgi`,
40
+ parameters: { rtsp_url: rtspUrl },
41
+ timeout: options?.timeout,
42
+ });
43
+ return wsAliasResponseSchema.parse(await res.json());
44
+ }
29
45
  async listFiles(fileType, options) {
30
46
  const res = await this._getJson(`${BASE_PATH}/upload_${fileType}.cgi`, { action: 'list' }, options);
31
47
  return getFileListSchema(fileType).parse(res.list);
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
- import { blackListSchema, cameraSettingsSchema, domainListSchema, flightInfoSchema, getIcaoSchema, mapInfoSchema, milestoneCameraListSchema, priorityListSchema, serverSettingsSchema, trackingModeSchema, whiteListSchema, wsAliasResponseSchema, zonesSchema, } from './types/PlaneTrackerAPI';
2
+ import { blackListSchema, cameraSettingsSchema, domainListSchema, flightInfoSchema, getIcaoSchema, mapInfoSchema, milestoneCameraListSchema, priorityListSchema, serverSettingsSchema, trackingModeSchema, whiteListSchema, zonesSchema, } from './types/PlaneTrackerAPI';
3
3
  import { CannotSetCoordsInAutoModeError, ImportSettingsError, InvalidAltitudeError, InvalidLatLngError, ResetCalibrationError, ServerError, BadRequestError, } from './errors/errors';
4
+ import { wsAliasResponseSchema } from './types/common';
4
5
  import { BasicAPI } from './internal/BasicAPI';
5
6
  import { apiUserInputSchema } from './types/ws/PlaneTrackerEvents';
6
7
  const BASE_PATH = '/local/planetracker';
package/esm/VapixAPI.js CHANGED
@@ -90,7 +90,9 @@ export class VapixAPI extends BasicAPI {
90
90
  };
91
91
  await this._postJsonEncoded('/axis-cgi/opticscontrol.cgi', data, undefined, options);
92
92
  }
93
- catch {
93
+ catch (error) {
94
+ console.warn('Failed to perform autofocus: ', error instanceof Error ? error.message : String(error));
95
+ console.warn('Falling back to deprecated optics setup API');
94
96
  await this._postUrlEncoded('/axis-cgi/opticssetup.cgi', {
95
97
  autofocus: 'perform',
96
98
  source: '1',
@@ -141,7 +143,7 @@ export class VapixAPI extends BasicAPI {
141
143
  return data.data.activeTimeZone;
142
144
  }
143
145
  catch (error) {
144
- console.warn('Failed to fetch time zone data from time API v2:', error instanceof Error ? error.message : JSON.stringify(error));
146
+ console.warn('Failed to fetch time zone data from time API v2:', error instanceof Error ? error.message : String(error));
145
147
  console.warn('Falling back to deprecated time API v1');
146
148
  }
147
149
  try {
@@ -20,7 +20,7 @@ export class ServiceNotFoundError extends Error {
20
20
  }
21
21
  export class ParsingBlobError extends Error {
22
22
  constructor(err) {
23
- super('Error parsing response as Blob: ' + err);
23
+ super('Error parsing response as Blob: ' + String(err));
24
24
  this.name = 'ParsingBlobError';
25
25
  }
26
26
  }
@@ -84,7 +84,7 @@ export class NoDeviceInfoError extends Error {
84
84
  }
85
85
  export class FetchDeviceInfoError extends Error {
86
86
  constructor(err) {
87
- super('Error fetching remote camera data: ' + err);
87
+ super('Error fetching remote camera data: ' + String(err));
88
88
  this.name = 'NoDeviceInfoFromCameraError';
89
89
  }
90
90
  }
@@ -108,7 +108,7 @@ export class PtzNotSupportedError extends Error {
108
108
  }
109
109
  export class StorageDataFetchError extends Error {
110
110
  constructor(err) {
111
- super('Error fetching storage data: ' + err);
111
+ super('Error fetching storage data: ' + String(err));
112
112
  this.name = 'StorageDataFetchError';
113
113
  }
114
114
  }
@@ -132,7 +132,7 @@ export class TimezoneNotSetupError extends Error {
132
132
  }
133
133
  export class TimezoneFetchError extends Error {
134
134
  constructor(err) {
135
- super('Error fetching time zone information: ' + err);
135
+ super('Error fetching time zone information: ' + String(err));
136
136
  this.name = 'TimezoneFetchError';
137
137
  }
138
138
  }
@@ -1,9 +1,4 @@
1
1
  import { z } from 'zod';
2
- export const wsAliasResponseSchema = z.object({
3
- alias: z.string(),
4
- ws: z.string(),
5
- ws_initial_message: z.string(),
6
- });
7
2
  export const connectionSchema = z.object({
8
3
  protocol: z.union([z.literal('http'), z.literal('https'), z.literal('https_insecure')]),
9
4
  ip: z.union([z.string().ip(), z.literal('')]),
@@ -37,13 +32,59 @@ const labelOptionsSchema = z.union([
37
32
  z.literal('flight_number'),
38
33
  z.literal('icao'),
39
34
  ]);
40
- const identificationLabelSchema = z.object({
35
+ const aircraftIdentificationLabelSchema = z.object({
41
36
  firstRow: labelOptionsSchema,
42
37
  secondRow: labelOptionsSchema,
43
38
  thirdRow: labelOptionsSchema,
44
39
  fourthRow: labelOptionsSchema,
45
- opacity: z.number().positive(),
40
+ opacity: z.number().nonnegative(),
41
+ });
42
+ const droneLabelOptionsSchema = z.union([z.literal('blank'), z.literal('remote_id')]);
43
+ const droneIdentificationLabelSchema = z.object({
44
+ firstRow: droneLabelOptionsSchema,
45
+ opacity: z.number().nonnegative(),
46
+ });
47
+ const DEFAULT_DRONE_IDENTIFICATION_LABEL = {
48
+ firstRow: 'remote_id',
49
+ opacity: 30,
50
+ };
51
+ const identificationLabelSchema = z.union([
52
+ z.object({
53
+ aircraft: aircraftIdentificationLabelSchema,
54
+ drone: droneIdentificationLabelSchema.default(DEFAULT_DRONE_IDENTIFICATION_LABEL),
55
+ }),
56
+ aircraftIdentificationLabelSchema.transform((aircraft) => ({
57
+ aircraft,
58
+ drone: { ...DEFAULT_DRONE_IDENTIFICATION_LABEL },
59
+ })),
60
+ ]);
61
+ const captureConfigSchema = z.object({
62
+ defaultCaptureSizeMeters: z.number().positive(),
63
+ captureSizeExtensionMeters: z.number().positive(),
46
64
  });
65
+ const DEFAULT_CAPTURE_CONFIG = {
66
+ defaultCaptureSizeMeters: 120,
67
+ captureSizeExtensionMeters: 80,
68
+ };
69
+ const cameraConfigSchema = z.union([
70
+ z.object({
71
+ aircraft: captureConfigSchema,
72
+ drone: captureConfigSchema.default(() => ({ ...DEFAULT_CAPTURE_CONFIG })),
73
+ }),
74
+ z
75
+ .object({
76
+ defaultCaptureSizeMeters: z.number().positive().default(DEFAULT_CAPTURE_CONFIG.defaultCaptureSizeMeters),
77
+ captureSizeExtensionMeters: z
78
+ .number()
79
+ .positive()
80
+ .default(DEFAULT_CAPTURE_CONFIG.captureSizeExtensionMeters),
81
+ })
82
+ .strict()
83
+ .transform((captureConfig) => ({
84
+ aircraft: captureConfig,
85
+ drone: { ...captureConfig },
86
+ })),
87
+ ]);
47
88
  export const cameraSettingsSchema = z.object({
48
89
  units: z.union([z.literal('metric'), z.literal('imperial')]).default('imperial'),
49
90
  adsbSource: z
@@ -81,15 +122,10 @@ export const cameraSettingsSchema = z.object({
81
122
  nightSkyCalibrationFocusValue: 9000,
82
123
  focusCalibrationPoints: '',
83
124
  }),
84
- cameraConfig: z
85
- .object({
86
- defaultCaptureSizeMeters: z.number().positive().default(120),
87
- captureSizeExtensionMeters: z.number().positive().default(80),
88
- })
89
- .default({
90
- defaultCaptureSizeMeters: 120,
91
- captureSizeExtensionMeters: 80,
92
- }),
125
+ cameraConfig: cameraConfigSchema.default(() => ({
126
+ aircraft: { ...DEFAULT_CAPTURE_CONFIG },
127
+ drone: { ...DEFAULT_CAPTURE_CONFIG },
128
+ })),
93
129
  stream: z
94
130
  .object({
95
131
  width: z.number().positive(),
@@ -200,11 +236,14 @@ export const cameraSettingsSchema = z.object({
200
236
  port: 80,
201
237
  }),
202
238
  identificationLabel: identificationLabelSchema.default({
203
- firstRow: 'registration',
204
- secondRow: 'blank',
205
- thirdRow: 'blank',
206
- fourthRow: 'blank',
207
- opacity: 30,
239
+ aircraft: {
240
+ firstRow: 'icao',
241
+ secondRow: 'call_sign',
242
+ thirdRow: 'blank',
243
+ fourthRow: 'blank',
244
+ opacity: 30,
245
+ },
246
+ drone: DEFAULT_DRONE_IDENTIFICATION_LABEL,
208
247
  }),
209
248
  acs: connectionSchema
210
249
  .extend({
@@ -0,0 +1,88 @@
1
+ import { cameraSettingsSchema } from './PlaneTrackerAPI';
2
+ import { describe, test, expect } from '@jest/globals';
3
+ const AIRCRAFT_LABEL = {
4
+ firstRow: 'icao',
5
+ secondRow: 'call_sign',
6
+ thirdRow: 'blank',
7
+ fourthRow: 'registration',
8
+ opacity: 50,
9
+ };
10
+ const DEFAULT_DRONE_LABEL = { firstRow: 'remote_id', opacity: 30 };
11
+ describe('cameraSettingsSchema identificationLabel', () => {
12
+ test('defaults both aircraft and drone labels', () => {
13
+ expect(cameraSettingsSchema.parse({}).identificationLabel).toEqual({
14
+ aircraft: {
15
+ firstRow: 'icao',
16
+ secondRow: 'call_sign',
17
+ thirdRow: 'blank',
18
+ fourthRow: 'blank',
19
+ opacity: 30,
20
+ },
21
+ drone: DEFAULT_DRONE_LABEL,
22
+ });
23
+ });
24
+ test('parses the aircraft and drone label', () => {
25
+ const identificationLabel = { aircraft: AIRCRAFT_LABEL, drone: { firstRow: 'blank', opacity: 70 } };
26
+ expect(cameraSettingsSchema.parse({ identificationLabel }).identificationLabel).toEqual(identificationLabel);
27
+ });
28
+ test('defaults a missing drone label', () => {
29
+ expect(cameraSettingsSchema.parse({ identificationLabel: { aircraft: AIRCRAFT_LABEL } }).identificationLabel).toEqual({ aircraft: AIRCRAFT_LABEL, drone: DEFAULT_DRONE_LABEL });
30
+ });
31
+ test('converts the legacy flat label to the aircraft label', () => {
32
+ expect(cameraSettingsSchema.parse({ identificationLabel: AIRCRAFT_LABEL }).identificationLabel).toEqual({
33
+ aircraft: AIRCRAFT_LABEL,
34
+ drone: DEFAULT_DRONE_LABEL,
35
+ });
36
+ });
37
+ test('returns an independent drone label for each legacy parse', () => {
38
+ const first = cameraSettingsSchema.parse({ identificationLabel: AIRCRAFT_LABEL });
39
+ first.identificationLabel.drone.opacity = 90;
40
+ const second = cameraSettingsSchema.parse({ identificationLabel: AIRCRAFT_LABEL });
41
+ expect(second.identificationLabel.drone).toEqual(DEFAULT_DRONE_LABEL);
42
+ });
43
+ test('rejects an unknown drone label row', () => {
44
+ const identificationLabel = { aircraft: AIRCRAFT_LABEL, drone: { firstRow: 'icao', opacity: 30 } };
45
+ expect(cameraSettingsSchema.safeParse({ identificationLabel }).success).toBe(false);
46
+ });
47
+ });
48
+ const CAPTURE_CONFIG = { defaultCaptureSizeMeters: 50, captureSizeExtensionMeters: 20 };
49
+ const DEFAULT_CAPTURE_CONFIG = { defaultCaptureSizeMeters: 120, captureSizeExtensionMeters: 80 };
50
+ describe('cameraSettingsSchema cameraConfig', () => {
51
+ test('defaults both aircraft and drone capture config', () => {
52
+ expect(cameraSettingsSchema.parse({}).cameraConfig).toEqual({
53
+ aircraft: DEFAULT_CAPTURE_CONFIG,
54
+ drone: DEFAULT_CAPTURE_CONFIG,
55
+ });
56
+ });
57
+ test('parses independent aircraft and drone capture config', () => {
58
+ const cameraConfig = {
59
+ aircraft: CAPTURE_CONFIG,
60
+ drone: { defaultCaptureSizeMeters: 5, captureSizeExtensionMeters: 3 },
61
+ };
62
+ expect(cameraSettingsSchema.parse({ cameraConfig }).cameraConfig).toEqual(cameraConfig);
63
+ });
64
+ test('defaults a missing drone capture config', () => {
65
+ expect(cameraSettingsSchema.parse({ cameraConfig: { aircraft: CAPTURE_CONFIG } }).cameraConfig).toEqual({
66
+ aircraft: CAPTURE_CONFIG,
67
+ drone: DEFAULT_CAPTURE_CONFIG,
68
+ });
69
+ });
70
+ test('copies the legacy flat capture config to both aircraft and drone', () => {
71
+ expect(cameraSettingsSchema.parse({ cameraConfig: CAPTURE_CONFIG }).cameraConfig).toEqual({
72
+ aircraft: CAPTURE_CONFIG,
73
+ drone: CAPTURE_CONFIG,
74
+ });
75
+ });
76
+ test('returns independent aircraft and drone objects', () => {
77
+ const legacy = cameraSettingsSchema.parse({ cameraConfig: CAPTURE_CONFIG }).cameraConfig;
78
+ legacy.drone.defaultCaptureSizeMeters = 1;
79
+ expect(legacy.aircraft).toEqual(CAPTURE_CONFIG);
80
+ const first = cameraSettingsSchema.parse({}).cameraConfig;
81
+ first.drone.defaultCaptureSizeMeters = 1;
82
+ expect(cameraSettingsSchema.parse({}).cameraConfig.drone).toEqual(DEFAULT_CAPTURE_CONFIG);
83
+ });
84
+ test('rejects a non-positive drone capture size', () => {
85
+ const cameraConfig = { aircraft: CAPTURE_CONFIG, drone: { ...CAPTURE_CONFIG, defaultCaptureSizeMeters: 0 } };
86
+ expect(cameraSettingsSchema.safeParse({ cameraConfig }).success).toBe(false);
87
+ });
88
+ });
@@ -12,6 +12,11 @@ export const networkCameraListSchema = z.array(z.object({
12
12
  }));
13
13
  export const keyboardShortcutSchema = z.string().nullable();
14
14
  export const keyboardShortcutsSchema = z.record(keyboardShortcutSchema);
15
+ export const wsAliasResponseSchema = z.object({
16
+ alias: z.string(),
17
+ ws: z.string(),
18
+ ws_initial_message: z.string(),
19
+ });
15
20
  export const bitrateModeSchema = z.union([z.literal('VBR'), z.literal('MBR'), z.literal('ABR')]);
16
21
  export const bitrateVapixParamsSchema = z.object({
17
22
  bitrateMode: bitrateModeSchema,