camstreamerlib 4.0.33 → 4.0.34-be26059
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.
- package/cjs/PlaneTrackerAPI.d.ts +42 -9
- package/cjs/PlaneTrackerAPI.js +2 -1
- package/cjs/VapixAPI.js +1 -1
- package/cjs/helpers.js +1 -1
- package/cjs/node/HttpRequestSender.js +8 -5
- package/cjs/node/HttpRequestSender.test.d.ts +1 -0
- package/cjs/node/HttpRequestSender.test.js +161 -0
- package/cjs/types/PlaneTrackerAPI.d.ts +205 -26
- package/cjs/types/PlaneTrackerAPI.js +59 -15
- package/cjs/types/PlaneTrackerAPI.test.d.ts +1 -0
- package/cjs/types/PlaneTrackerAPI.test.js +90 -0
- package/cjs/types/ws/PlaneTrackerEvents.d.ts +47 -0
- package/cjs/types/ws/PlaneTrackerEvents.js +15 -1
- package/cjs/ws/PlaneTrackerEvents.d.ts +1 -1
- package/cjs/ws/PlaneTrackerEvents.js +2 -2
- package/cjs/ws/PlaneTrackerEvents.test.d.ts +1 -0
- package/cjs/ws/PlaneTrackerEvents.test.js +27 -0
- package/esm/PlaneTrackerAPI.js +2 -1
- package/esm/VapixAPI.js +1 -1
- package/esm/helpers.js +1 -1
- package/esm/node/HttpRequestSender.js +9 -6
- package/esm/node/HttpRequestSender.test.js +136 -0
- package/esm/types/PlaneTrackerAPI.js +59 -15
- package/esm/types/PlaneTrackerAPI.test.js +88 -0
- package/esm/types/ws/PlaneTrackerEvents.js +14 -0
- package/esm/ws/PlaneTrackerEvents.js +3 -3
- package/esm/ws/PlaneTrackerEvents.test.js +25 -0
- package/package.json +110 -106
- package/types/PlaneTrackerAPI.d.ts +42 -9
- package/types/node/HttpRequestSender.test.d.ts +1 -0
- package/types/types/PlaneTrackerAPI.d.ts +205 -26
- package/types/types/PlaneTrackerAPI.test.d.ts +1 -0
- package/types/types/ws/PlaneTrackerEvents.d.ts +47 -0
- package/types/ws/PlaneTrackerEvents.d.ts +1 -1
- package/types/ws/PlaneTrackerEvents.test.d.ts +1 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
import { FormData } from 'undici';
|
|
3
|
+
import { describe, test, expect, beforeEach, afterEach } from '@jest/globals';
|
|
4
|
+
import { HttpRequestSender } from './HttpRequestSender';
|
|
5
|
+
const DIGEST_CHALLENGE = 'Digest realm="test", qop="auth", nonce="abc123", opaque="xyz"';
|
|
6
|
+
const okHandler = (_req, res) => {
|
|
7
|
+
res.end('ok');
|
|
8
|
+
};
|
|
9
|
+
const digestHandler = (req, res) => {
|
|
10
|
+
if (req.headers.authorization?.startsWith('Digest ') !== true) {
|
|
11
|
+
res.writeHead(401, { 'WWW-Authenticate': DIGEST_CHALLENGE });
|
|
12
|
+
res.end();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
res.end('ok');
|
|
16
|
+
};
|
|
17
|
+
describe('HttpRequestSender', () => {
|
|
18
|
+
let server;
|
|
19
|
+
let received;
|
|
20
|
+
let handler;
|
|
21
|
+
const options = (overrides = {}) => ({
|
|
22
|
+
protocol: 'http:',
|
|
23
|
+
host: '127.0.0.1',
|
|
24
|
+
port: server.address().port,
|
|
25
|
+
path: '/test.cgi',
|
|
26
|
+
user: 'root',
|
|
27
|
+
pass: 'pass',
|
|
28
|
+
...overrides,
|
|
29
|
+
});
|
|
30
|
+
beforeEach(async () => {
|
|
31
|
+
received = [];
|
|
32
|
+
handler = okHandler;
|
|
33
|
+
server = http.createServer((req, res) => {
|
|
34
|
+
let body = '';
|
|
35
|
+
req.on('data', (chunk) => {
|
|
36
|
+
body += chunk.toString();
|
|
37
|
+
});
|
|
38
|
+
req.on('end', () => {
|
|
39
|
+
received.push({
|
|
40
|
+
method: req.method,
|
|
41
|
+
url: req.url,
|
|
42
|
+
authorization: req.headers.authorization,
|
|
43
|
+
contentType: req.headers['content-type'],
|
|
44
|
+
body,
|
|
45
|
+
});
|
|
46
|
+
handler(req, res);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
await new Promise((resolve) => {
|
|
50
|
+
server.listen(0, '127.0.0.1', resolve);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
afterEach(async () => {
|
|
54
|
+
server.closeAllConnections();
|
|
55
|
+
await new Promise((resolve) => {
|
|
56
|
+
server.close(() => resolve());
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
test('sends basic auth when the server accepts it', async () => {
|
|
60
|
+
const res = await new HttpRequestSender().sendRequest(options());
|
|
61
|
+
expect(res.status).toBe(200);
|
|
62
|
+
expect(await res.text()).toBe('ok');
|
|
63
|
+
expect(received).toHaveLength(1);
|
|
64
|
+
expect(received[0]?.authorization).toBe(`Basic ${Buffer.from('root:pass').toString('base64')}`);
|
|
65
|
+
});
|
|
66
|
+
test('sends no authorization without credentials', async () => {
|
|
67
|
+
await new HttpRequestSender().sendRequest(options({ user: undefined, pass: undefined }));
|
|
68
|
+
expect(received[0]?.authorization).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
test('retries with digest auth on a digest 401 and reuses it for later requests', async () => {
|
|
71
|
+
handler = digestHandler;
|
|
72
|
+
const sender = new HttpRequestSender();
|
|
73
|
+
const first = await sender.sendRequest(options());
|
|
74
|
+
const second = await sender.sendRequest(options());
|
|
75
|
+
expect(first.status).toBe(200);
|
|
76
|
+
expect(second.status).toBe(200);
|
|
77
|
+
expect(received.map((r) => r.authorization?.split(' ')[0])).toEqual(['Basic', 'Digest', 'Digest']);
|
|
78
|
+
});
|
|
79
|
+
test('keeps digest auth after a non-401 error response', async () => {
|
|
80
|
+
const sender = new HttpRequestSender();
|
|
81
|
+
handler = digestHandler;
|
|
82
|
+
await sender.sendRequest(options());
|
|
83
|
+
handler = (_req, res) => {
|
|
84
|
+
res.writeHead(500);
|
|
85
|
+
res.end();
|
|
86
|
+
};
|
|
87
|
+
await sender.sendRequest(options());
|
|
88
|
+
handler = digestHandler;
|
|
89
|
+
const res = await sender.sendRequest(options());
|
|
90
|
+
expect(res.status).toBe(200);
|
|
91
|
+
expect(received.map((r) => r.authorization?.split(' ')[0])).toEqual(['Basic', 'Digest', 'Digest', 'Digest']);
|
|
92
|
+
});
|
|
93
|
+
test('retries a digest 401 only once', async () => {
|
|
94
|
+
handler = (_req, res) => {
|
|
95
|
+
res.writeHead(401, { 'WWW-Authenticate': DIGEST_CHALLENGE });
|
|
96
|
+
res.end();
|
|
97
|
+
};
|
|
98
|
+
const res = await new HttpRequestSender().sendRequest(options());
|
|
99
|
+
expect(res.status).toBe(401);
|
|
100
|
+
expect(received).toHaveLength(2);
|
|
101
|
+
});
|
|
102
|
+
test('rejects when the response does not arrive within the timeout', async () => {
|
|
103
|
+
handler = () => undefined;
|
|
104
|
+
await expect(new HttpRequestSender().sendRequest(options({ timeout: 200 }))).rejects.toThrow();
|
|
105
|
+
});
|
|
106
|
+
test('sends a url-encoded POST body', async () => {
|
|
107
|
+
const res = await new HttpRequestSender().sendRequest(options({ method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }), 'action=settext&text=hello');
|
|
108
|
+
expect(res.ok).toBe(true);
|
|
109
|
+
expect(received[0]).toMatchObject({
|
|
110
|
+
method: 'POST',
|
|
111
|
+
contentType: 'application/x-www-form-urlencoded',
|
|
112
|
+
body: 'action=settext&text=hello',
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
test('sends a JSON POST body', async () => {
|
|
116
|
+
const payload = JSON.stringify({ text: 'hello', index: 1 });
|
|
117
|
+
await new HttpRequestSender().sendRequest(options({ method: 'POST', headers: { 'Content-Type': 'application/json' } }), payload);
|
|
118
|
+
expect(received[0]).toMatchObject({ method: 'POST', contentType: 'application/json', body: payload });
|
|
119
|
+
});
|
|
120
|
+
test('sends a Buffer POST body', async () => {
|
|
121
|
+
await new HttpRequestSender().sendRequest(options({ method: 'POST' }), Buffer.from('binary-data'));
|
|
122
|
+
expect(received[0]?.body).toBe('binary-data');
|
|
123
|
+
});
|
|
124
|
+
test('sends a FormData POST body as multipart', async () => {
|
|
125
|
+
const formData = new FormData();
|
|
126
|
+
formData.append('field', 'value');
|
|
127
|
+
await new HttpRequestSender().sendRequest(options({ method: 'POST' }), formData);
|
|
128
|
+
expect(received[0]?.contentType).toMatch(/^multipart\/form-data; boundary=/);
|
|
129
|
+
expect(received[0]?.body).toContain('value');
|
|
130
|
+
});
|
|
131
|
+
test('resends the POST body on the digest retry', async () => {
|
|
132
|
+
handler = digestHandler;
|
|
133
|
+
await new HttpRequestSender().sendRequest(options({ method: 'POST' }), 'payload');
|
|
134
|
+
expect(received.map((r) => r.body)).toEqual(['payload', 'payload']);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -37,13 +37,59 @@ const labelOptionsSchema = z.union([
|
|
|
37
37
|
z.literal('flight_number'),
|
|
38
38
|
z.literal('icao'),
|
|
39
39
|
]);
|
|
40
|
-
const
|
|
40
|
+
const aircraftIdentificationLabelSchema = z.object({
|
|
41
41
|
firstRow: labelOptionsSchema,
|
|
42
42
|
secondRow: labelOptionsSchema,
|
|
43
43
|
thirdRow: labelOptionsSchema,
|
|
44
44
|
fourthRow: labelOptionsSchema,
|
|
45
45
|
opacity: z.number().positive(),
|
|
46
46
|
});
|
|
47
|
+
const droneLabelOptionsSchema = z.union([z.literal('blank'), z.literal('remote_id')]);
|
|
48
|
+
const droneIdentificationLabelSchema = z.object({
|
|
49
|
+
firstRow: droneLabelOptionsSchema,
|
|
50
|
+
opacity: z.number().positive(),
|
|
51
|
+
});
|
|
52
|
+
const DEFAULT_DRONE_IDENTIFICATION_LABEL = {
|
|
53
|
+
firstRow: 'remote_id',
|
|
54
|
+
opacity: 30,
|
|
55
|
+
};
|
|
56
|
+
const identificationLabelSchema = z.union([
|
|
57
|
+
z.object({
|
|
58
|
+
aircraft: aircraftIdentificationLabelSchema,
|
|
59
|
+
drone: droneIdentificationLabelSchema.default(DEFAULT_DRONE_IDENTIFICATION_LABEL),
|
|
60
|
+
}),
|
|
61
|
+
aircraftIdentificationLabelSchema.transform((aircraft) => ({
|
|
62
|
+
aircraft,
|
|
63
|
+
drone: { ...DEFAULT_DRONE_IDENTIFICATION_LABEL },
|
|
64
|
+
})),
|
|
65
|
+
]);
|
|
66
|
+
const captureConfigSchema = z.object({
|
|
67
|
+
defaultCaptureSizeMeters: z.number().positive(),
|
|
68
|
+
captureSizeExtensionMeters: z.number().positive(),
|
|
69
|
+
});
|
|
70
|
+
const DEFAULT_CAPTURE_CONFIG = {
|
|
71
|
+
defaultCaptureSizeMeters: 120,
|
|
72
|
+
captureSizeExtensionMeters: 80,
|
|
73
|
+
};
|
|
74
|
+
const cameraConfigSchema = z.union([
|
|
75
|
+
z.object({
|
|
76
|
+
aircraft: captureConfigSchema,
|
|
77
|
+
drone: captureConfigSchema.default(() => ({ ...DEFAULT_CAPTURE_CONFIG })),
|
|
78
|
+
}),
|
|
79
|
+
z
|
|
80
|
+
.object({
|
|
81
|
+
defaultCaptureSizeMeters: z.number().positive().default(DEFAULT_CAPTURE_CONFIG.defaultCaptureSizeMeters),
|
|
82
|
+
captureSizeExtensionMeters: z
|
|
83
|
+
.number()
|
|
84
|
+
.positive()
|
|
85
|
+
.default(DEFAULT_CAPTURE_CONFIG.captureSizeExtensionMeters),
|
|
86
|
+
})
|
|
87
|
+
.strict()
|
|
88
|
+
.transform((captureConfig) => ({
|
|
89
|
+
aircraft: captureConfig,
|
|
90
|
+
drone: { ...captureConfig },
|
|
91
|
+
})),
|
|
92
|
+
]);
|
|
47
93
|
export const cameraSettingsSchema = z.object({
|
|
48
94
|
units: z.union([z.literal('metric'), z.literal('imperial')]).default('imperial'),
|
|
49
95
|
adsbSource: z
|
|
@@ -81,15 +127,10 @@ export const cameraSettingsSchema = z.object({
|
|
|
81
127
|
nightSkyCalibrationFocusValue: 9000,
|
|
82
128
|
focusCalibrationPoints: '',
|
|
83
129
|
}),
|
|
84
|
-
cameraConfig:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
})
|
|
89
|
-
.default({
|
|
90
|
-
defaultCaptureSizeMeters: 120,
|
|
91
|
-
captureSizeExtensionMeters: 80,
|
|
92
|
-
}),
|
|
130
|
+
cameraConfig: cameraConfigSchema.default(() => ({
|
|
131
|
+
aircraft: { ...DEFAULT_CAPTURE_CONFIG },
|
|
132
|
+
drone: { ...DEFAULT_CAPTURE_CONFIG },
|
|
133
|
+
})),
|
|
93
134
|
stream: z
|
|
94
135
|
.object({
|
|
95
136
|
width: z.number().positive(),
|
|
@@ -200,11 +241,14 @@ export const cameraSettingsSchema = z.object({
|
|
|
200
241
|
port: 80,
|
|
201
242
|
}),
|
|
202
243
|
identificationLabel: identificationLabelSchema.default({
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
244
|
+
aircraft: {
|
|
245
|
+
firstRow: 'icao',
|
|
246
|
+
secondRow: 'call_sign',
|
|
247
|
+
thirdRow: 'blank',
|
|
248
|
+
fourthRow: 'blank',
|
|
249
|
+
opacity: 30,
|
|
250
|
+
},
|
|
251
|
+
drone: DEFAULT_DRONE_IDENTIFICATION_LABEL,
|
|
208
252
|
}),
|
|
209
253
|
acs: connectionSchema
|
|
210
254
|
.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
|
+
});
|
|
@@ -3,6 +3,7 @@ import { blackListSchema, friendlyListSchema, priorityListSchema, trackingModeSc
|
|
|
3
3
|
const wsApiFlightDataSchema = z.object({
|
|
4
4
|
targetId: z.string(),
|
|
5
5
|
icao: z.string(),
|
|
6
|
+
callsign: z.string().optional(),
|
|
6
7
|
domain: domainIdSchema,
|
|
7
8
|
categoryId: z.string(),
|
|
8
9
|
groupId: z.string().optional(),
|
|
@@ -44,6 +45,18 @@ const apiUserSchema = userSchema.extend({
|
|
|
44
45
|
ip: z.string(),
|
|
45
46
|
userPriority: z.number(),
|
|
46
47
|
});
|
|
48
|
+
export const USER_PRIORITY_MIN = 1;
|
|
49
|
+
export const USER_PRIORITY_MAX = 255;
|
|
50
|
+
const USER_PRIORITY_ERROR = `userPriority must be an integer between ${USER_PRIORITY_MIN} and ${USER_PRIORITY_MAX}, where ${USER_PRIORITY_MIN} is the highest priority`;
|
|
51
|
+
export const apiUserInputSchema = apiUserSchema.omit({ ip: true }).extend({
|
|
52
|
+
userId: z.string().nonempty(),
|
|
53
|
+
userName: z.string().nonempty(),
|
|
54
|
+
userPriority: z
|
|
55
|
+
.number()
|
|
56
|
+
.int(USER_PRIORITY_ERROR)
|
|
57
|
+
.min(USER_PRIORITY_MIN, USER_PRIORITY_ERROR)
|
|
58
|
+
.max(USER_PRIORITY_MAX, USER_PRIORITY_ERROR),
|
|
59
|
+
});
|
|
47
60
|
export var EUserActions;
|
|
48
61
|
(function (EUserActions) {
|
|
49
62
|
EUserActions["TRACK_ICAO"] = "trackIcao.cgi";
|
|
@@ -67,6 +80,7 @@ const eventsDataSchema = z.union([
|
|
|
67
80
|
type: z.literal('TRACKING_START'),
|
|
68
81
|
icao: z.string(),
|
|
69
82
|
targetId: z.string(),
|
|
83
|
+
callsign: z.string().optional(),
|
|
70
84
|
domain: domainIdSchema,
|
|
71
85
|
categoryId: z.string(),
|
|
72
86
|
}),
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { WsEvents } from '../internal/WsEvents';
|
|
2
|
-
import { ptrEventsSchema } from '../types/ws/PlaneTrackerEvents';
|
|
2
|
+
import { ptrEventsSchema, apiUserInputSchema, } from '../types/ws/PlaneTrackerEvents';
|
|
3
3
|
export class PlaneTrackerEvents extends WsEvents {
|
|
4
4
|
_apiUser;
|
|
5
|
-
constructor(ws,
|
|
5
|
+
constructor(ws, apiUser) {
|
|
6
6
|
super((data) => ptrEventsSchema.parse(data), ws);
|
|
7
|
-
this._apiUser =
|
|
7
|
+
this._apiUser = apiUserInputSchema.parse(apiUser);
|
|
8
8
|
this.ws.onOpen = this.sendInitMsg;
|
|
9
9
|
}
|
|
10
10
|
sendInitMsg = () => {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { PlaneTrackerEvents } from './PlaneTrackerEvents';
|
|
2
|
+
import { USER_PRIORITY_MAX, USER_PRIORITY_MIN } from '../types/ws/PlaneTrackerEvents';
|
|
3
|
+
const createWsClient = () => ({
|
|
4
|
+
send: jest.fn(),
|
|
5
|
+
onOpen: undefined,
|
|
6
|
+
onMessage: undefined,
|
|
7
|
+
});
|
|
8
|
+
const VALID_USER = { userId: 'asd', userName: 'Asd', userPriority: 1 };
|
|
9
|
+
describe('PlaneTrackerEvents', () => {
|
|
10
|
+
it.each([0, -1, USER_PRIORITY_MAX + 1, 1.5])('rejects userPriority %p', (userPriority) => {
|
|
11
|
+
expect(() => new PlaneTrackerEvents(createWsClient(), { ...VALID_USER, userPriority })).toThrow(/userPriority must be an integer/);
|
|
12
|
+
});
|
|
13
|
+
it.each([USER_PRIORITY_MIN, USER_PRIORITY_MAX])('accepts userPriority %p', (userPriority) => {
|
|
14
|
+
expect(() => new PlaneTrackerEvents(createWsClient(), { ...VALID_USER, userPriority })).not.toThrow();
|
|
15
|
+
});
|
|
16
|
+
it('rejects an empty userId', () => {
|
|
17
|
+
expect(() => new PlaneTrackerEvents(createWsClient(), { ...VALID_USER, userId: '' })).toThrow();
|
|
18
|
+
});
|
|
19
|
+
it('sends USER_INFO once the socket opens', () => {
|
|
20
|
+
const ws = createWsClient();
|
|
21
|
+
new PlaneTrackerEvents(ws, VALID_USER);
|
|
22
|
+
ws.onOpen();
|
|
23
|
+
expect(ws.send).toHaveBeenCalledWith(JSON.stringify({ type: 'USER_INFO', ...VALID_USER }));
|
|
24
|
+
});
|
|
25
|
+
});
|
package/package.json
CHANGED
|
@@ -1,113 +1,117 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
"name": "camstreamerlib",
|
|
3
|
+
"version": "4.0.34-be26059",
|
|
4
|
+
"description": "Helper library for CamStreamer ACAP applications.",
|
|
5
|
+
"prettier": "@camstreamer/prettier-config",
|
|
6
|
+
"engine": {
|
|
7
|
+
"node": ">=18.0.0"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"adm-zip": "^0.6.0",
|
|
11
|
+
"eventemitter2": "^5.0.1",
|
|
12
|
+
"fast-xml-parser": "^5.3.6",
|
|
13
|
+
"type-fest": "^4.41.0",
|
|
14
|
+
"undici": "^6.28.1",
|
|
15
|
+
"ws": "^8.18.0",
|
|
16
|
+
"zod": "^3.25.76"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@camstreamer/eslint-config": "^1.0.2",
|
|
20
|
+
"@camstreamer/prettier-config": "^2.0.4",
|
|
21
|
+
"@types/adm-zip": "^0.5.5",
|
|
22
|
+
"@types/jest": "^30.0.0",
|
|
23
|
+
"@types/node": "^18.19.127",
|
|
24
|
+
"@types/ws": "^8.5.10",
|
|
25
|
+
"@typescript-eslint/eslint-plugin": "^8.71.1",
|
|
26
|
+
"@typescript-eslint/parser": "^8.71.1",
|
|
27
|
+
"esbuild": "^0.28.2",
|
|
28
|
+
"eslint": "^8.51.0",
|
|
29
|
+
"eslint-plugin-deprecation": "^2.0.0",
|
|
30
|
+
"eslint-plugin-unused-imports": "^4.4.1",
|
|
31
|
+
"jest": "^30.5.2",
|
|
32
|
+
"npm-run-all": "^4.1.5",
|
|
33
|
+
"prettier": "^2.7.1",
|
|
34
|
+
"ts-jest": "^29.4.14",
|
|
35
|
+
"ts-node": "^10.7.0",
|
|
36
|
+
"typescript": "5.3.3"
|
|
37
|
+
},
|
|
38
|
+
"overrides": {
|
|
39
|
+
"js-yaml": "^4.3.2",
|
|
40
|
+
"browserslist": "^4.28.7",
|
|
41
|
+
"eslint-plugin-deprecation": {
|
|
42
|
+
"@typescript-eslint/utils": "^8.71.1"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"clean": "rimraf dist/*",
|
|
47
|
+
"build": "npm-run-all clean build:cjs build:esm copyPackage",
|
|
48
|
+
"build:cjs": "tsc --project tsconfig.cjs.json",
|
|
49
|
+
"build:esm": "tsc --project tsconfig.esm.json",
|
|
50
|
+
"build:browser": "esbuild src/browser.index.ts --bundle --format=esm --platform=browser --target=es2022 --outfile=examples/camstreamerlib.js",
|
|
51
|
+
"copyPackage": "cp -f LICENSE dist/ && cp -f README.md dist/ && cp -f package.json dist/ && cp -r -f examples dist/",
|
|
52
|
+
"publishPackage": "./publish.sh",
|
|
53
|
+
"lint": "eslint \"src/**/*.ts\"",
|
|
54
|
+
"lint:fix": "eslint \"src/**/*.ts\" --fix",
|
|
55
|
+
"pretty": "prettier --write \"./{{src,doc}/**/*.{ts,tsx,md},README.md}\"",
|
|
56
|
+
"pretty:check": "prettier --check \"./{{src,doc}/**/*.{ts,tsx,md},README.md}\"",
|
|
57
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
58
|
+
"audit": "npm audit --audit-level=high"
|
|
59
|
+
},
|
|
60
|
+
"files": [
|
|
61
|
+
"/**/*.js",
|
|
62
|
+
"/**/*.ts",
|
|
63
|
+
"/examples/**/*"
|
|
64
|
+
],
|
|
65
|
+
"main": "./cjs/index.js",
|
|
66
|
+
"module": "./esm/index.js",
|
|
67
|
+
"types": "types/index.d.ts",
|
|
68
|
+
"exports": {
|
|
69
|
+
".": {
|
|
70
|
+
"import": "./esm/index.js",
|
|
71
|
+
"require": "./cjs/index.js",
|
|
72
|
+
"types": "./types/index.d.ts"
|
|
8
73
|
},
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"type-fest": "^4.41.0",
|
|
14
|
-
"undici": "^6.28.0",
|
|
15
|
-
"ws": "^8.18.0",
|
|
16
|
-
"zod": "^3.25.76"
|
|
74
|
+
"./web": {
|
|
75
|
+
"import": "./esm/web/index.js",
|
|
76
|
+
"require": "./cjs/web/index.js",
|
|
77
|
+
"types": "./types/web/index.d.ts"
|
|
17
78
|
},
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"@types/jest": "^28.0.0",
|
|
23
|
-
"@types/node": "^18.19.127",
|
|
24
|
-
"@types/ws": "^8.5.10",
|
|
25
|
-
"@typescript-eslint/eslint-plugin": "^6.8.0",
|
|
26
|
-
"@typescript-eslint/parser": "^6.8.0",
|
|
27
|
-
"esbuild": "^0.28.2",
|
|
28
|
-
"eslint": "^8.51.0",
|
|
29
|
-
"eslint-plugin-deprecation": "^2.0.0",
|
|
30
|
-
"eslint-plugin-unused-imports": "^3.0.0",
|
|
31
|
-
"jest": "^28.1.3",
|
|
32
|
-
"npm-run-all": "^4.1.5",
|
|
33
|
-
"prettier": "^2.7.1",
|
|
34
|
-
"ts-jest": "^28.0.0",
|
|
35
|
-
"ts-node": "^10.7.0",
|
|
36
|
-
"typescript": "5.3.3"
|
|
79
|
+
"./node": {
|
|
80
|
+
"import": "./esm/node/index.js",
|
|
81
|
+
"require": "./cjs/node/index.js",
|
|
82
|
+
"types": "./types/node/index.d.ts"
|
|
37
83
|
},
|
|
38
|
-
"
|
|
39
|
-
|
|
84
|
+
"./cjs/index": {
|
|
85
|
+
"import": "./esm/index.js",
|
|
86
|
+
"require": "./cjs/index.js",
|
|
87
|
+
"types": "./types/index.d.ts"
|
|
40
88
|
},
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"build:esm": "tsc --project tsconfig.esm.json",
|
|
46
|
-
"build:browser": "esbuild src/browser.index.ts --bundle --format=esm --platform=browser --target=es2022 --outfile=examples/camstreamerlib.js",
|
|
47
|
-
"copyPackage": "cp -f LICENSE dist/ && cp -f README.md dist/ && cp -f package.json dist/ && cp -r -f examples dist/",
|
|
48
|
-
"publishPackage": "./publish.sh",
|
|
49
|
-
"lint": "eslint \"src/**/*.ts\"",
|
|
50
|
-
"lint:fix": "eslint \"src/**/*.ts\" --fix",
|
|
51
|
-
"pretty": "prettier --write \"./{{src,doc}/**/*.{ts,tsx,md},README.md}\"",
|
|
52
|
-
"pretty:check": "prettier --check \"./{{src,doc}/**/*.{ts,tsx,md},README.md}\"",
|
|
53
|
-
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
54
|
-
"audit": "npm audit --audit-level=high"
|
|
89
|
+
"./cjs/node": {
|
|
90
|
+
"import": "./esm/node/index.js",
|
|
91
|
+
"require": "./cjs/node/index.js",
|
|
92
|
+
"types": "./types/node/index.js"
|
|
55
93
|
},
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
},
|
|
80
|
-
"./cjs/index": {
|
|
81
|
-
"import": "./esm/index.js",
|
|
82
|
-
"require": "./cjs/index.js",
|
|
83
|
-
"types": "./types/index.d.ts"
|
|
84
|
-
},
|
|
85
|
-
"./cjs/node": {
|
|
86
|
-
"import": "./esm/node/index.js",
|
|
87
|
-
"require": "./cjs/node/index.js",
|
|
88
|
-
"types": "./types/node/index.js"
|
|
89
|
-
},
|
|
90
|
-
"./cjs/node/index": {
|
|
91
|
-
"import": "./esm/node/index.js",
|
|
92
|
-
"require": "./cjs/node/index.js",
|
|
93
|
-
"types": "./types/node/index.js"
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
"repository": {
|
|
97
|
-
"type": "git",
|
|
98
|
-
"url": "git+https://github.com/CamStreamer/CamStreamerLib.git"
|
|
99
|
-
},
|
|
100
|
-
"keywords": [
|
|
101
|
-
"CamStreamer",
|
|
102
|
-
"CamOverlay",
|
|
103
|
-
"CamScripter",
|
|
104
|
-
"Camera",
|
|
105
|
-
"Axis"
|
|
106
|
-
],
|
|
107
|
-
"author": "CamStreamer s.r.o",
|
|
108
|
-
"license": "ISC",
|
|
109
|
-
"bugs": {
|
|
110
|
-
"url": "https://github.com/CamStreamer/CamStreamerLib/issues"
|
|
111
|
-
},
|
|
112
|
-
"homepage": "https://github.com/CamStreamer/CamStreamerLib#readme"
|
|
94
|
+
"./cjs/node/index": {
|
|
95
|
+
"import": "./esm/node/index.js",
|
|
96
|
+
"require": "./cjs/node/index.js",
|
|
97
|
+
"types": "./types/node/index.js"
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"repository": {
|
|
101
|
+
"type": "git",
|
|
102
|
+
"url": "git+https://github.com/CamStreamer/CamStreamerLib.git"
|
|
103
|
+
},
|
|
104
|
+
"keywords": [
|
|
105
|
+
"CamStreamer",
|
|
106
|
+
"CamOverlay",
|
|
107
|
+
"CamScripter",
|
|
108
|
+
"Camera",
|
|
109
|
+
"Axis"
|
|
110
|
+
],
|
|
111
|
+
"author": "CamStreamer s.r.o",
|
|
112
|
+
"license": "ISC",
|
|
113
|
+
"bugs": {
|
|
114
|
+
"url": "https://github.com/CamStreamer/CamStreamerLib/issues"
|
|
115
|
+
},
|
|
116
|
+
"homepage": "https://github.com/CamStreamer/CamStreamerLib#readme"
|
|
113
117
|
}
|