camstreamerlib 4.0.32 → 4.0.33-10a29ef

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 (45) hide show
  1. package/README.md +9 -0
  2. package/cjs/PlaneTrackerAPI.d.ts +42 -9
  3. package/cjs/PlaneTrackerAPI.js +2 -1
  4. package/cjs/VapixAPI.js +1 -1
  5. package/cjs/browser.index.d.ts +2 -0
  6. package/cjs/browser.index.js +18 -0
  7. package/cjs/helpers.js +1 -1
  8. package/cjs/node/HttpRequestSender.js +8 -5
  9. package/cjs/node/HttpRequestSender.test.d.ts +1 -0
  10. package/cjs/node/HttpRequestSender.test.js +161 -0
  11. package/cjs/types/PlaneTrackerAPI.d.ts +205 -26
  12. package/cjs/types/PlaneTrackerAPI.js +59 -15
  13. package/cjs/types/PlaneTrackerAPI.test.d.ts +1 -0
  14. package/cjs/types/PlaneTrackerAPI.test.js +90 -0
  15. package/cjs/types/ws/PlaneTrackerEvents.d.ts +47 -0
  16. package/cjs/types/ws/PlaneTrackerEvents.js +15 -1
  17. package/cjs/ws/PlaneTrackerEvents.d.ts +1 -1
  18. package/cjs/ws/PlaneTrackerEvents.js +2 -2
  19. package/cjs/ws/PlaneTrackerEvents.test.d.ts +1 -0
  20. package/cjs/ws/PlaneTrackerEvents.test.js +27 -0
  21. package/esm/PlaneTrackerAPI.js +2 -1
  22. package/esm/VapixAPI.js +1 -1
  23. package/esm/browser.index.js +2 -0
  24. package/esm/helpers.js +1 -1
  25. package/esm/node/HttpRequestSender.js +9 -6
  26. package/esm/node/HttpRequestSender.test.js +136 -0
  27. package/esm/types/PlaneTrackerAPI.js +59 -15
  28. package/esm/types/PlaneTrackerAPI.test.js +88 -0
  29. package/esm/types/ws/PlaneTrackerEvents.js +14 -0
  30. package/esm/ws/PlaneTrackerEvents.js +3 -3
  31. package/esm/ws/PlaneTrackerEvents.test.js +25 -0
  32. package/examples/README.md +43 -0
  33. package/examples/camstreamerlib.js +13201 -0
  34. package/examples/camswitcher-events.html +137 -0
  35. package/examples/planetracker-events.html +186 -0
  36. package/examples/utils/example.css +102 -0
  37. package/package.json +110 -103
  38. package/types/PlaneTrackerAPI.d.ts +42 -9
  39. package/types/browser.index.d.ts +2 -0
  40. package/types/node/HttpRequestSender.test.d.ts +1 -0
  41. package/types/types/PlaneTrackerAPI.d.ts +205 -26
  42. package/types/types/PlaneTrackerAPI.test.d.ts +1 -0
  43. package/types/types/ws/PlaneTrackerEvents.d.ts +47 -0
  44. package/types/ws/PlaneTrackerEvents.d.ts +1 -1
  45. package/types/ws/PlaneTrackerEvents.test.d.ts +1 -0
package/README.md CHANGED
@@ -39,6 +39,15 @@ npm install camstreamerlib
39
39
 
40
40
  </br>
41
41
 
42
+ # Examples
43
+
44
+ | Example | Description | Open in browser |
45
+ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
46
+ | [CamSwitcherEvents](examples/camswitcher-events.html) ([source](https://github.com/CamStreamer/CamStreamerLib/blob/master/examples/camswitcher-events.html)) | Logs every CamSwitcher event. `DeviceConnectClient` (HTTP) backs [CamSwitcherAPI](doc/CamSwitcherAPI.md) to obtain the websocket authorization token, `DeviceConnectWsClient` (websocket) backs [CamSwitcherEvents](doc/ws/CamSwitcherEvents.md), which validates and dispatches the events. Connection state is taken from the `authorization` event. | [open](https://camstreamer.github.io/CamStreamerLib/examples/camswitcher-events.html) |
47
+ | [PlaneTrackerEvents](examples/planetracker-events.html) ([source](https://github.com/CamStreamer/CamStreamerLib/blob/master/examples/planetracker-events.html)) | Logs every PlaneTracker event. PlaneTracker needs no authorization token — [PlaneTrackerEvents](doc/ws/PlaneTrackerEvents.md) announces the connecting user (`userId` / `userName` / `userPriority`, editable in the form) as the init message instead, so only the websocket client is needed. The continuous `CAMERA_POSITION` and `FLIGHT_LIST` events only update the summary line unless the checkbox is ticked. | [open](https://camstreamer.github.io/CamStreamerLib/examples/planetracker-events.html) |
48
+
49
+ </br>
50
+
42
51
  # Breaking Changes
43
52
 
44
53
  <details open>
@@ -45,8 +45,23 @@ export declare class PlaneTrackerAPI<Client extends IClient<TResponse, any>> ext
45
45
  focusCalibrationPoints: string;
46
46
  };
47
47
  cameraConfig: {
48
- defaultCaptureSizeMeters: number;
49
- captureSizeExtensionMeters: number;
48
+ aircraft: {
49
+ defaultCaptureSizeMeters: number;
50
+ captureSizeExtensionMeters: number;
51
+ };
52
+ drone: {
53
+ defaultCaptureSizeMeters: number;
54
+ captureSizeExtensionMeters: number;
55
+ };
56
+ } | {
57
+ aircraft: {
58
+ defaultCaptureSizeMeters: number;
59
+ captureSizeExtensionMeters: number;
60
+ };
61
+ drone: {
62
+ defaultCaptureSizeMeters: number;
63
+ captureSizeExtensionMeters: number;
64
+ };
50
65
  };
51
66
  stream: {
52
67
  width: number;
@@ -99,11 +114,29 @@ export declare class PlaneTrackerAPI<Client extends IClient<TResponse, any>> ext
99
114
  priority: number;
100
115
  };
101
116
  identificationLabel: {
102
- firstRow: "blank" | "registration" | "call_sign" | "flight_number" | "icao";
103
- secondRow: "blank" | "registration" | "call_sign" | "flight_number" | "icao";
104
- thirdRow: "blank" | "registration" | "call_sign" | "flight_number" | "icao";
105
- fourthRow: "blank" | "registration" | "call_sign" | "flight_number" | "icao";
106
- opacity: number;
117
+ aircraft: {
118
+ firstRow: "blank" | "registration" | "call_sign" | "flight_number" | "icao";
119
+ secondRow: "blank" | "registration" | "call_sign" | "flight_number" | "icao";
120
+ thirdRow: "blank" | "registration" | "call_sign" | "flight_number" | "icao";
121
+ fourthRow: "blank" | "registration" | "call_sign" | "flight_number" | "icao";
122
+ opacity: number;
123
+ };
124
+ drone: {
125
+ firstRow: "blank" | "remote_id";
126
+ opacity: number;
127
+ };
128
+ } | {
129
+ aircraft: {
130
+ firstRow: "blank" | "registration" | "call_sign" | "flight_number" | "icao";
131
+ secondRow: "blank" | "registration" | "call_sign" | "flight_number" | "icao";
132
+ thirdRow: "blank" | "registration" | "call_sign" | "flight_number" | "icao";
133
+ fourthRow: "blank" | "registration" | "call_sign" | "flight_number" | "icao";
134
+ opacity: number;
135
+ };
136
+ drone: {
137
+ firstRow: "blank" | "remote_id";
138
+ opacity: number;
139
+ };
107
140
  };
108
141
  acs: {
109
142
  ip: string;
@@ -181,11 +214,11 @@ export declare class PlaneTrackerAPI<Client extends IClient<TResponse, any>> ext
181
214
  importAppSettings(dataType: TImportDataType, formData: Parameters<Client['post']>[0]['data'], options?: THttpRequestOptions): Promise<void>;
182
215
  getDomainList(options?: THttpRequestOptions): Promise<Partial<Record<"adsb" | "remoteId", {
183
216
  uiName: string;
184
- icon: "unknown" | "small" | "large" | "heavy" | "helicopter" | "drone" | "operator" | "vehicle";
217
+ icon: "unknown" | "small" | "drone" | "large" | "heavy" | "helicopter" | "operator" | "vehicle";
185
218
  categoryList: {
186
219
  categoryId: string;
187
220
  uiName: string;
188
- icon: "unknown" | "small" | "large" | "heavy" | "helicopter" | "drone" | "operator" | "vehicle";
221
+ icon: "unknown" | "small" | "drone" | "large" | "heavy" | "helicopter" | "operator" | "vehicle";
189
222
  }[];
190
223
  }>>>;
191
224
  fetchFlightInfo(targetId: string, options?: THttpRequestOptions): Promise<{
@@ -5,12 +5,13 @@ const zod_1 = require("zod");
5
5
  const PlaneTrackerAPI_1 = require("./types/PlaneTrackerAPI");
6
6
  const errors_1 = require("./errors/errors");
7
7
  const BasicAPI_1 = require("./internal/BasicAPI");
8
+ const PlaneTrackerEvents_1 = require("./types/ws/PlaneTrackerEvents");
8
9
  const BASE_PATH = '/local/planetracker';
9
10
  class PlaneTrackerAPI extends BasicAPI_1.BasicAPI {
10
11
  apiUser;
11
12
  constructor(client, apiUser) {
12
13
  super(client);
13
- this.apiUser = apiUser;
14
+ this.apiUser = PlaneTrackerEvents_1.apiUserInputSchema.parse(apiUser);
14
15
  }
15
16
  static getProxyPath = () => `${BASE_PATH}/proxy.cgi`;
16
17
  static getWsEventsPath = () => `${BASE_PATH}/package/ws`;
package/cjs/VapixAPI.js CHANGED
@@ -93,7 +93,7 @@ class VapixAPI extends BasicAPI_1.BasicAPI {
93
93
  };
94
94
  await this._postJsonEncoded('/axis-cgi/opticscontrol.cgi', data, undefined, options);
95
95
  }
96
- catch (err) {
96
+ catch {
97
97
  await this._postUrlEncoded('/axis-cgi/opticssetup.cgi', {
98
98
  autofocus: 'perform',
99
99
  source: '1',
@@ -0,0 +1,2 @@
1
+ export * from './index';
2
+ export * from './web/index';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./index"), exports);
18
+ __exportStar(require("./web/index"), exports);
package/cjs/helpers.js CHANGED
@@ -13,7 +13,7 @@ const jsonParseCameraParam = (param, paramName) => {
13
13
  try {
14
14
  return JSON.parse(decodeURIComponent(param.replaceAll('\\', '')));
15
15
  }
16
- catch (e) {
16
+ catch {
17
17
  throw new errors_1.JsonParseError(paramName, param);
18
18
  }
19
19
  }
@@ -32,12 +32,15 @@ class HttpRequestSender {
32
32
  options.headers ??= {};
33
33
  options.headers['Authorization'] = authorization;
34
34
  }
35
- const req = new undici_1.Request(url, { body: postData, method: options.method, headers: options.headers });
36
- const res = await (0, undici_1.fetch)(req, { signal: AbortSignal.timeout(options.timeout), dispatcher: this.agent });
37
- if (!res.ok) {
38
- this.invalidateAuthorization();
39
- }
35
+ const res = await (0, undici_1.fetch)(url, {
36
+ method: options.method,
37
+ headers: options.headers,
38
+ body: postData,
39
+ signal: AbortSignal.timeout(options.timeout),
40
+ dispatcher: this.agent,
41
+ });
40
42
  if (res.status === 401) {
43
+ this.invalidateAuthorization();
41
44
  const authenticateHeader = res.headers.get('www-authenticate');
42
45
  if (authenticateHeader !== null &&
43
46
  authenticateHeader.indexOf('Digest') !== -1 &&
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ const http = __importStar(require("http"));
27
+ const undici_1 = require("undici");
28
+ const globals_1 = require("@jest/globals");
29
+ const HttpRequestSender_1 = require("./HttpRequestSender");
30
+ const DIGEST_CHALLENGE = 'Digest realm="test", qop="auth", nonce="abc123", opaque="xyz"';
31
+ const okHandler = (_req, res) => {
32
+ res.end('ok');
33
+ };
34
+ const digestHandler = (req, res) => {
35
+ if (req.headers.authorization?.startsWith('Digest ') !== true) {
36
+ res.writeHead(401, { 'WWW-Authenticate': DIGEST_CHALLENGE });
37
+ res.end();
38
+ return;
39
+ }
40
+ res.end('ok');
41
+ };
42
+ (0, globals_1.describe)('HttpRequestSender', () => {
43
+ let server;
44
+ let received;
45
+ let handler;
46
+ const options = (overrides = {}) => ({
47
+ protocol: 'http:',
48
+ host: '127.0.0.1',
49
+ port: server.address().port,
50
+ path: '/test.cgi',
51
+ user: 'root',
52
+ pass: 'pass',
53
+ ...overrides,
54
+ });
55
+ (0, globals_1.beforeEach)(async () => {
56
+ received = [];
57
+ handler = okHandler;
58
+ server = http.createServer((req, res) => {
59
+ let body = '';
60
+ req.on('data', (chunk) => {
61
+ body += chunk.toString();
62
+ });
63
+ req.on('end', () => {
64
+ received.push({
65
+ method: req.method,
66
+ url: req.url,
67
+ authorization: req.headers.authorization,
68
+ contentType: req.headers['content-type'],
69
+ body,
70
+ });
71
+ handler(req, res);
72
+ });
73
+ });
74
+ await new Promise((resolve) => {
75
+ server.listen(0, '127.0.0.1', resolve);
76
+ });
77
+ });
78
+ (0, globals_1.afterEach)(async () => {
79
+ server.closeAllConnections();
80
+ await new Promise((resolve) => {
81
+ server.close(() => resolve());
82
+ });
83
+ });
84
+ (0, globals_1.test)('sends basic auth when the server accepts it', async () => {
85
+ const res = await new HttpRequestSender_1.HttpRequestSender().sendRequest(options());
86
+ (0, globals_1.expect)(res.status).toBe(200);
87
+ (0, globals_1.expect)(await res.text()).toBe('ok');
88
+ (0, globals_1.expect)(received).toHaveLength(1);
89
+ (0, globals_1.expect)(received[0]?.authorization).toBe(`Basic ${Buffer.from('root:pass').toString('base64')}`);
90
+ });
91
+ (0, globals_1.test)('sends no authorization without credentials', async () => {
92
+ await new HttpRequestSender_1.HttpRequestSender().sendRequest(options({ user: undefined, pass: undefined }));
93
+ (0, globals_1.expect)(received[0]?.authorization).toBeUndefined();
94
+ });
95
+ (0, globals_1.test)('retries with digest auth on a digest 401 and reuses it for later requests', async () => {
96
+ handler = digestHandler;
97
+ const sender = new HttpRequestSender_1.HttpRequestSender();
98
+ const first = await sender.sendRequest(options());
99
+ const second = await sender.sendRequest(options());
100
+ (0, globals_1.expect)(first.status).toBe(200);
101
+ (0, globals_1.expect)(second.status).toBe(200);
102
+ (0, globals_1.expect)(received.map((r) => r.authorization?.split(' ')[0])).toEqual(['Basic', 'Digest', 'Digest']);
103
+ });
104
+ (0, globals_1.test)('keeps digest auth after a non-401 error response', async () => {
105
+ const sender = new HttpRequestSender_1.HttpRequestSender();
106
+ handler = digestHandler;
107
+ await sender.sendRequest(options());
108
+ handler = (_req, res) => {
109
+ res.writeHead(500);
110
+ res.end();
111
+ };
112
+ await sender.sendRequest(options());
113
+ handler = digestHandler;
114
+ const res = await sender.sendRequest(options());
115
+ (0, globals_1.expect)(res.status).toBe(200);
116
+ (0, globals_1.expect)(received.map((r) => r.authorization?.split(' ')[0])).toEqual(['Basic', 'Digest', 'Digest', 'Digest']);
117
+ });
118
+ (0, globals_1.test)('retries a digest 401 only once', async () => {
119
+ handler = (_req, res) => {
120
+ res.writeHead(401, { 'WWW-Authenticate': DIGEST_CHALLENGE });
121
+ res.end();
122
+ };
123
+ const res = await new HttpRequestSender_1.HttpRequestSender().sendRequest(options());
124
+ (0, globals_1.expect)(res.status).toBe(401);
125
+ (0, globals_1.expect)(received).toHaveLength(2);
126
+ });
127
+ (0, globals_1.test)('rejects when the response does not arrive within the timeout', async () => {
128
+ handler = () => undefined;
129
+ await (0, globals_1.expect)(new HttpRequestSender_1.HttpRequestSender().sendRequest(options({ timeout: 200 }))).rejects.toThrow();
130
+ });
131
+ (0, globals_1.test)('sends a url-encoded POST body', async () => {
132
+ const res = await new HttpRequestSender_1.HttpRequestSender().sendRequest(options({ method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }), 'action=settext&text=hello');
133
+ (0, globals_1.expect)(res.ok).toBe(true);
134
+ (0, globals_1.expect)(received[0]).toMatchObject({
135
+ method: 'POST',
136
+ contentType: 'application/x-www-form-urlencoded',
137
+ body: 'action=settext&text=hello',
138
+ });
139
+ });
140
+ (0, globals_1.test)('sends a JSON POST body', async () => {
141
+ const payload = JSON.stringify({ text: 'hello', index: 1 });
142
+ await new HttpRequestSender_1.HttpRequestSender().sendRequest(options({ method: 'POST', headers: { 'Content-Type': 'application/json' } }), payload);
143
+ (0, globals_1.expect)(received[0]).toMatchObject({ method: 'POST', contentType: 'application/json', body: payload });
144
+ });
145
+ (0, globals_1.test)('sends a Buffer POST body', async () => {
146
+ await new HttpRequestSender_1.HttpRequestSender().sendRequest(options({ method: 'POST' }), Buffer.from('binary-data'));
147
+ (0, globals_1.expect)(received[0]?.body).toBe('binary-data');
148
+ });
149
+ (0, globals_1.test)('sends a FormData POST body as multipart', async () => {
150
+ const formData = new undici_1.FormData();
151
+ formData.append('field', 'value');
152
+ await new HttpRequestSender_1.HttpRequestSender().sendRequest(options({ method: 'POST' }), formData);
153
+ (0, globals_1.expect)(received[0]?.contentType).toMatch(/^multipart\/form-data; boundary=/);
154
+ (0, globals_1.expect)(received[0]?.body).toContain('value');
155
+ });
156
+ (0, globals_1.test)('resends the POST body on the digest retry', async () => {
157
+ handler = digestHandler;
158
+ await new HttpRequestSender_1.HttpRequestSender().sendRequest(options({ method: 'POST' }), 'payload');
159
+ (0, globals_1.expect)(received.map((r) => r.body)).toEqual(['payload', 'payload']);
160
+ });
161
+ });