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.
- package/README.md +9 -0
- package/cjs/PlaneTrackerAPI.d.ts +42 -9
- package/cjs/PlaneTrackerAPI.js +2 -1
- package/cjs/VapixAPI.js +1 -1
- package/cjs/browser.index.d.ts +2 -0
- package/cjs/browser.index.js +18 -0
- 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/browser.index.js +2 -0
- 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/examples/README.md +43 -0
- package/examples/camstreamerlib.js +13201 -0
- package/examples/camswitcher-events.html +137 -0
- package/examples/planetracker-events.html +186 -0
- package/examples/utils/example.css +102 -0
- package/package.json +110 -103
- package/types/PlaneTrackerAPI.d.ts +42 -9
- package/types/browser.index.d.ts +2 -0
- 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
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>
|
package/cjs/PlaneTrackerAPI.d.ts
CHANGED
|
@@ -45,8 +45,23 @@ export declare class PlaneTrackerAPI<Client extends IClient<TResponse, any>> ext
|
|
|
45
45
|
focusCalibrationPoints: string;
|
|
46
46
|
};
|
|
47
47
|
cameraConfig: {
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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" | "
|
|
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" | "
|
|
221
|
+
icon: "unknown" | "small" | "drone" | "large" | "heavy" | "helicopter" | "operator" | "vehicle";
|
|
189
222
|
}[];
|
|
190
223
|
}>>>;
|
|
191
224
|
fetchFlightInfo(targetId: string, options?: THttpRequestOptions): Promise<{
|
package/cjs/PlaneTrackerAPI.js
CHANGED
|
@@ -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
|
|
96
|
+
catch {
|
|
97
97
|
await this._postUrlEncoded('/axis-cgi/opticssetup.cgi', {
|
|
98
98
|
autofocus: 'perform',
|
|
99
99
|
source: '1',
|
|
@@ -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
|
@@ -32,12 +32,15 @@ class HttpRequestSender {
|
|
|
32
32
|
options.headers ??= {};
|
|
33
33
|
options.headers['Authorization'] = authorization;
|
|
34
34
|
}
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
+
});
|