@tak-ps/node-tak 12.23.1 → 12.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/api/credentials.d.ts +3 -1
- package/dist/lib/api/credentials.js +23 -10
- package/dist/lib/api/credentials.js.map +1 -1
- package/dist/lib/api/mission-layer.d.ts +2 -3
- package/dist/lib/api/mission-layer.js +5 -3
- package/dist/lib/api/mission-layer.js.map +1 -1
- package/dist/lib/api/mission.d.ts +436 -2
- package/dist/lib/api/mission.js +38 -11
- package/dist/lib/api/mission.js.map +1 -1
- package/dist/lib/api.js +5 -1
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/utils/html-error.d.ts +32 -0
- package/dist/lib/utils/html-error.js +172 -0
- package/dist/lib/utils/html-error.js.map +1 -0
- package/dist/test/html-error.test.d.ts +1 -0
- package/dist/test/html-error.test.js +115 -0
- package/dist/test/html-error.test.js.map +1 -0
- package/dist/test/mission-layer.test.d.ts +1 -0
- package/dist/test/mission-layer.test.js +105 -0
- package/dist/test/mission-layer.test.js.map +1 -0
- package/dist/test/mission.test.d.ts +1 -0
- package/dist/test/mission.test.js +137 -0
- package/dist/test/mission.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/index.ts +1 -0
- package/lib/api/credentials.ts +23 -9
- package/lib/api/mission-layer.ts +7 -6
- package/lib/api/mission.ts +40 -11
- package/lib/api.ts +5 -1
- package/lib/utils/html-error.ts +208 -0
- package/package.json +3 -3
- package/test/html-error.test.ts +139 -0
- package/test/mission-layer.test.ts +137 -0
- package/test/mission.test.ts +173 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { Readable } from 'node:stream';
|
|
4
|
+
import { Client } from 'undici';
|
|
5
|
+
import TAKAPI from '../lib/api.js';
|
|
6
|
+
import { APIAuthCertificate } from '../lib/auth.js';
|
|
7
|
+
function mockResponse(statusCode, contentType, body) {
|
|
8
|
+
return {
|
|
9
|
+
statusCode,
|
|
10
|
+
headers: { 'content-type': contentType },
|
|
11
|
+
body: Readable.from([Buffer.from(body)]),
|
|
12
|
+
trailers: {}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
test('MissionLayer.attachUids files UIDs under a layer via the contents paths body', async () => {
|
|
16
|
+
const originalRequest = Client.prototype.request;
|
|
17
|
+
let captured;
|
|
18
|
+
Client.prototype.request = async function (opts) {
|
|
19
|
+
captured = opts;
|
|
20
|
+
return mockResponse(200, 'text/plain', '');
|
|
21
|
+
};
|
|
22
|
+
try {
|
|
23
|
+
const api = new TAKAPI(new URL('https://tak.example.com'), new APIAuthCertificate('cert', 'key'));
|
|
24
|
+
await api.MissionLayer.attachUids('mission-1', 'layer-1', {
|
|
25
|
+
uids: ['uid-1', 'uid-2'],
|
|
26
|
+
creatorUid: 'alice'
|
|
27
|
+
});
|
|
28
|
+
assert.ok(captured);
|
|
29
|
+
assert.equal(captured.method, 'PUT');
|
|
30
|
+
assert.equal(captured.path, '/Marti/api/missions/mission-1/contents?creatorUid=alice');
|
|
31
|
+
assert.deepEqual(JSON.parse(String(captured.body)), {
|
|
32
|
+
paths: {
|
|
33
|
+
'layer-1': [{ uids: ['uid-1', 'uid-2'] }]
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
Client.prototype.request = originalRequest;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
test('MissionLayer.attachUids uses the guid contents endpoint for GUID missions', async () => {
|
|
42
|
+
const originalRequest = Client.prototype.request;
|
|
43
|
+
let captured;
|
|
44
|
+
Client.prototype.request = async function (opts) {
|
|
45
|
+
captured = opts;
|
|
46
|
+
return mockResponse(200, 'text/plain', '');
|
|
47
|
+
};
|
|
48
|
+
try {
|
|
49
|
+
const api = new TAKAPI(new URL('https://tak.example.com'), new APIAuthCertificate('cert', 'key'));
|
|
50
|
+
await api.MissionLayer.attachUids('11111111-2222-3333-4444-555555555555', 'layer-1', {
|
|
51
|
+
uids: ['uid-1'],
|
|
52
|
+
creatorUid: 'alice'
|
|
53
|
+
});
|
|
54
|
+
assert.ok(captured);
|
|
55
|
+
assert.equal(captured.method, 'PUT');
|
|
56
|
+
assert.equal(captured.path, '/Marti/api/missions/guid/11111111-2222-3333-4444-555555555555/contents?creatorUid=alice');
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
Client.prototype.request = originalRequest;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
test('MissionLayer.setParent re-parents layers via query params', async () => {
|
|
63
|
+
const originalRequest = Client.prototype.request;
|
|
64
|
+
let captured;
|
|
65
|
+
Client.prototype.request = async function (opts) {
|
|
66
|
+
captured = opts;
|
|
67
|
+
return mockResponse(200, 'text/plain', '');
|
|
68
|
+
};
|
|
69
|
+
try {
|
|
70
|
+
const api = new TAKAPI(new URL('https://tak.example.com'), new APIAuthCertificate('cert', 'key'));
|
|
71
|
+
await api.MissionLayer.setParent('mission-1', {
|
|
72
|
+
layerUids: ['uid-1', 'uid-2'],
|
|
73
|
+
parentUid: 'layer-2',
|
|
74
|
+
creatorUid: 'alice'
|
|
75
|
+
});
|
|
76
|
+
assert.ok(captured);
|
|
77
|
+
assert.equal(captured.method, 'PUT');
|
|
78
|
+
assert.equal(captured.path, '/Marti/api/missions/mission-1/layers/parent?layerUid=uid-1&layerUid=uid-2&parentUid=layer-2&creatorUid=alice');
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
Client.prototype.request = originalRequest;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
test('MissionLayer.setParent omits parentUid to move a layer to the mission root', async () => {
|
|
85
|
+
const originalRequest = Client.prototype.request;
|
|
86
|
+
let captured;
|
|
87
|
+
Client.prototype.request = async function (opts) {
|
|
88
|
+
captured = opts;
|
|
89
|
+
return mockResponse(200, 'text/plain', '');
|
|
90
|
+
};
|
|
91
|
+
try {
|
|
92
|
+
const api = new TAKAPI(new URL('https://tak.example.com'), new APIAuthCertificate('cert', 'key'));
|
|
93
|
+
await api.MissionLayer.setParent('11111111-2222-3333-4444-555555555555', {
|
|
94
|
+
layerUids: ['uid-1'],
|
|
95
|
+
creatorUid: 'alice'
|
|
96
|
+
});
|
|
97
|
+
assert.ok(captured);
|
|
98
|
+
assert.equal(captured.method, 'PUT');
|
|
99
|
+
assert.equal(captured.path, '/Marti/api/missions/guid/11111111-2222-3333-4444-555555555555/layers/parent?layerUid=uid-1&creatorUid=alice');
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
Client.prototype.request = originalRequest;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=mission-layer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mission-layer.test.js","sourceRoot":"","sources":["../../test/mission-layer.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { Readable } from 'node:stream';
|
|
4
|
+
import { Client } from 'undici';
|
|
5
|
+
import TAKAPI from '../lib/api.js';
|
|
6
|
+
import { APIAuthCertificate } from '../lib/auth.js';
|
|
7
|
+
import { MissionSubscriberRole } from '../lib/api/mission.js';
|
|
8
|
+
function mockResponse(statusCode, contentType, body) {
|
|
9
|
+
return {
|
|
10
|
+
statusCode,
|
|
11
|
+
headers: { 'content-type': contentType },
|
|
12
|
+
body: Readable.from([Buffer.from(body)]),
|
|
13
|
+
trailers: {}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
test('Mission.setRole sends role as a bare query param and returns void', async () => {
|
|
17
|
+
const originalRequest = Client.prototype.request;
|
|
18
|
+
let captured;
|
|
19
|
+
Client.prototype.request = async function (opts) {
|
|
20
|
+
captured = opts;
|
|
21
|
+
return mockResponse(200, 'text/plain', '');
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
const api = new TAKAPI(new URL('https://tak.example.com'), new APIAuthCertificate('cert', 'key'));
|
|
25
|
+
const res = await api.Mission.setRole('mission-1', {
|
|
26
|
+
clientUid: 'client-1',
|
|
27
|
+
username: 'alice',
|
|
28
|
+
role: MissionSubscriberRole.MISSION_OWNER
|
|
29
|
+
});
|
|
30
|
+
assert.equal(res, undefined);
|
|
31
|
+
assert.ok(captured);
|
|
32
|
+
assert.equal(captured.method, 'PUT');
|
|
33
|
+
assert.equal(captured.path, '/Marti/api/missions/mission-1/role?clientUid=client-1&username=alice&role=MISSION_OWNER');
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
Client.prototype.request = originalRequest;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
test('Mission.subscriptionRoles parses a list of subscribers with roles', async () => {
|
|
40
|
+
const originalRequest = Client.prototype.request;
|
|
41
|
+
Client.prototype.request = async function () {
|
|
42
|
+
return mockResponse(200, 'application/json', JSON.stringify({
|
|
43
|
+
data: [{
|
|
44
|
+
clientUid: 'client-1',
|
|
45
|
+
username: 'alice',
|
|
46
|
+
createTime: '2026-01-01T00:00:00.000Z',
|
|
47
|
+
role: {
|
|
48
|
+
permissions: ['MISSION_READ', 'MISSION_WRITE'],
|
|
49
|
+
type: 'MISSION_SUBSCRIBER'
|
|
50
|
+
}
|
|
51
|
+
}]
|
|
52
|
+
}));
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
const api = new TAKAPI(new URL('https://tak.example.com'), new APIAuthCertificate('cert', 'key'));
|
|
56
|
+
const res = await api.Mission.subscriptionRoles('mission-1');
|
|
57
|
+
assert.equal(res.data.length, 1);
|
|
58
|
+
assert.equal(res.data[0].username, 'alice');
|
|
59
|
+
assert.equal(res.data[0].role.type, 'MISSION_SUBSCRIBER');
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
Client.prototype.request = originalRequest;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
test('Mission.unsubscribe issues a DELETE with uid query param', async () => {
|
|
66
|
+
const originalRequest = Client.prototype.request;
|
|
67
|
+
let captured;
|
|
68
|
+
Client.prototype.request = async function (opts) {
|
|
69
|
+
captured = opts;
|
|
70
|
+
return mockResponse(200, 'text/plain', '');
|
|
71
|
+
};
|
|
72
|
+
try {
|
|
73
|
+
const api = new TAKAPI(new URL('https://tak.example.com'), new APIAuthCertificate('cert', 'key'));
|
|
74
|
+
await api.Mission.unsubscribe('mission-1', { uid: 'client-1' });
|
|
75
|
+
assert.ok(captured);
|
|
76
|
+
assert.equal(captured.method, 'DELETE');
|
|
77
|
+
assert.equal(captured.path, '/Marti/api/missions/mission-1/subscription?uid=client-1');
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
Client.prototype.request = originalRequest;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
test('Mission.latestFeats parses each CoT individually and isolates invalid CoTs', async () => {
|
|
84
|
+
const originalRequest = Client.prototype.request;
|
|
85
|
+
let captured;
|
|
86
|
+
Client.prototype.request = async function (opts) {
|
|
87
|
+
captured = opts;
|
|
88
|
+
return mockResponse(200, 'application/xml', `
|
|
89
|
+
<events>
|
|
90
|
+
<event version="2.0" uid="uid-valid" type="a-f-G" time="2026-08-11T00:00:00.000Z" start="2026-08-11T00:00:00.000Z" stale="2026-08-11T00:05:00.000Z" how="h-g-i-g-o">
|
|
91
|
+
<point lat="1.1" lon="2.2" hae="0.0" ce="9999999.0" le="9999999.0"/>
|
|
92
|
+
<detail>
|
|
93
|
+
<contact callsign="ALPHA"/>
|
|
94
|
+
</detail>
|
|
95
|
+
</event>
|
|
96
|
+
<event version="2.0" uid="uid-poisoned" type="a-f-G" time="2026-08-11T00:00:00.000Z" start="2026-08-11T00:00:00.000Z" stale="2026-08-11T00:05:00.000Z" how="h-g-i-g-o">
|
|
97
|
+
<detail>
|
|
98
|
+
<contact callsign="BRAVO"/>
|
|
99
|
+
</detail>
|
|
100
|
+
</event>
|
|
101
|
+
</events>
|
|
102
|
+
`);
|
|
103
|
+
};
|
|
104
|
+
try {
|
|
105
|
+
const api = new TAKAPI(new URL('https://tak.example.com'), new APIAuthCertificate('cert', 'key'));
|
|
106
|
+
const res = await api.Mission.latestFeats('mission-1');
|
|
107
|
+
assert.ok(captured);
|
|
108
|
+
assert.equal(captured.method, 'GET');
|
|
109
|
+
assert.equal(captured.path, '/Marti/api/missions/mission-1/cot');
|
|
110
|
+
assert.equal(res.features.length, 1);
|
|
111
|
+
assert.equal(res.features[0].id, 'uid-valid');
|
|
112
|
+
assert.equal(res.features[0].properties.callsign, 'ALPHA');
|
|
113
|
+
assert.equal(res.invalid.length, 1);
|
|
114
|
+
assert.ok(res.invalid[0].error.length > 0);
|
|
115
|
+
const event = res.invalid[0].feature;
|
|
116
|
+
assert.equal(event._attributes?.uid, 'uid-poisoned');
|
|
117
|
+
assert.equal(event.detail?.contact?._attributes?.callsign, 'BRAVO');
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
Client.prototype.request = originalRequest;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
test('Mission.latestFeats returns empty results for an empty document', async () => {
|
|
124
|
+
const originalRequest = Client.prototype.request;
|
|
125
|
+
Client.prototype.request = async function () {
|
|
126
|
+
return mockResponse(200, 'application/xml', '<events></events>');
|
|
127
|
+
};
|
|
128
|
+
try {
|
|
129
|
+
const api = new TAKAPI(new URL('https://tak.example.com'), new APIAuthCertificate('cert', 'key'));
|
|
130
|
+
const res = await api.Mission.latestFeats('mission-1');
|
|
131
|
+
assert.deepEqual(res, { features: [], invalid: [] });
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
Client.prototype.request = originalRequest;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
//# sourceMappingURL=mission.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mission.test.js","sourceRoot":"","sources":["../../test/mission.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../cli.ts","../index.ts","../test/default.test.ts","../test/findCoT.test.ts","../test/pipeline.test.ts","../test/queue.test.ts","../test/uploadPackage.test.ts","../lib/api.ts","../lib/auth.ts","../lib/commands.ts","../lib/fetch.ts","../lib/multipart.ts","../lib/stream.ts","../lib/api/certificate.ts","../lib/api/client.ts","../lib/api/contacts.ts","../lib/api/credentials.ts","../lib/api/export.ts","../lib/api/files.ts","../lib/api/groups.ts","../lib/api/iconsets.ts","../lib/api/injectors.ts","../lib/api/locate.ts","../lib/api/mission-invite.ts","../lib/api/mission-layer.ts","../lib/api/mission-log.ts","../lib/api/mission.ts","../lib/api/oauth.ts","../lib/api/package.ts","../lib/api/profile.ts","../lib/api/query.ts","../lib/api/repeater.ts","../lib/api/security.ts","../lib/api/subscriptions.ts","../lib/api/types.ts","../lib/api/video.ts","../lib/utils/queue.ts"],"version":"6.0.3"}
|
|
1
|
+
{"root":["../cli.ts","../index.ts","../test/default.test.ts","../test/findCoT.test.ts","../test/html-error.test.ts","../test/mission-layer.test.ts","../test/mission.test.ts","../test/pipeline.test.ts","../test/queue.test.ts","../test/uploadPackage.test.ts","../lib/api.ts","../lib/auth.ts","../lib/commands.ts","../lib/fetch.ts","../lib/multipart.ts","../lib/stream.ts","../lib/api/certificate.ts","../lib/api/client.ts","../lib/api/contacts.ts","../lib/api/credentials.ts","../lib/api/export.ts","../lib/api/files.ts","../lib/api/groups.ts","../lib/api/iconsets.ts","../lib/api/injectors.ts","../lib/api/locate.ts","../lib/api/mission-invite.ts","../lib/api/mission-layer.ts","../lib/api/mission-log.ts","../lib/api/mission.ts","../lib/api/oauth.ts","../lib/api/package.ts","../lib/api/profile.ts","../lib/api/query.ts","../lib/api/repeater.ts","../lib/api/security.ts","../lib/api/subscriptions.ts","../lib/api/types.ts","../lib/api/video.ts","../lib/utils/html-error.ts","../lib/utils/queue.ts"],"version":"6.0.3"}
|
package/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import TAKAPI from './lib/api.js';
|
|
|
9
9
|
import { TAKAuth } from './lib/auth.js';
|
|
10
10
|
import { Queue } from './lib/utils/queue.js';
|
|
11
11
|
export * from './lib/auth.js';
|
|
12
|
+
export * from './lib/utils/html-error.js';
|
|
12
13
|
|
|
13
14
|
/* eslint-disable no-control-regex */
|
|
14
15
|
export const REGEX_CONTROL = /[\u000B-\u001F\u007F-\u009F]/g;
|
package/lib/api/credentials.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { APIAuthPassword } from '../auth.js';
|
|
1
|
+
import { APIAuthPassword, APIAuthToken } from '../auth.js';
|
|
2
2
|
import { Static, Type } from '@sinclair/typebox';
|
|
3
3
|
import Commands, { CommandOutputFormat, type ParsedArgs } from '../commands.js';
|
|
4
4
|
import pem from 'pem';
|
|
@@ -35,8 +35,25 @@ export default class CredentialCommands extends Commands {
|
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
async generate(
|
|
39
|
-
|
|
38
|
+
async generate(opts: {
|
|
39
|
+
username?: string
|
|
40
|
+
} = {}): Promise<Static<typeof CertificateResponse>> {
|
|
41
|
+
let username: string;
|
|
42
|
+
const headers: Record<string, string> = {
|
|
43
|
+
Accept: 'application/json'
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (this.api.auth instanceof APIAuthPassword) {
|
|
47
|
+
username = opts.username || this.api.auth.username;
|
|
48
|
+
headers.Authorization = 'Basic ' + btoa(this.api.auth.username + ":" + this.api.auth.password);
|
|
49
|
+
} else if (this.api.auth instanceof APIAuthToken) {
|
|
50
|
+
// TAK Server derives the enrollment username from the token claims and requires
|
|
51
|
+
// the CSR CN to match it - the caller must supply that username explicitly
|
|
52
|
+
if (!opts.username) throw new Error('Token Auth requires a username for the Certificate CN');
|
|
53
|
+
username = opts.username;
|
|
54
|
+
} else {
|
|
55
|
+
throw new Error('Must use Password or Token Auth');
|
|
56
|
+
}
|
|
40
57
|
|
|
41
58
|
const config: any = xml2js(await this.config(), { compact: true });
|
|
42
59
|
|
|
@@ -58,20 +75,17 @@ export default class CredentialCommands extends Commands {
|
|
|
58
75
|
} = await createCSR({
|
|
59
76
|
organization,
|
|
60
77
|
organizationUnit,
|
|
61
|
-
commonName:
|
|
78
|
+
commonName: username
|
|
62
79
|
});
|
|
63
80
|
|
|
64
81
|
const url = new URL(`/Marti/api/tls/signClient/v2`, this.api.url);
|
|
65
|
-
url.searchParams.append('clientUid',
|
|
82
|
+
url.searchParams.append('clientUid', username + ' (ETL)');
|
|
66
83
|
url.searchParams.append('version', '3');
|
|
67
84
|
|
|
68
85
|
const res = await this.api.fetch(url, {
|
|
69
86
|
method: 'POST',
|
|
70
87
|
nocookies: true,
|
|
71
|
-
headers
|
|
72
|
-
Accept: 'application/json',
|
|
73
|
-
Authorization: 'Basic ' + btoa(this.api.auth.username + ":" + this.api.auth.password)
|
|
74
|
-
},
|
|
88
|
+
headers,
|
|
75
89
|
body: keys.csr
|
|
76
90
|
});
|
|
77
91
|
|
package/lib/api/mission-layer.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { TAKList, TAKItem } from './types.js';
|
|
2
2
|
import { Type, Static } from '@sinclair/typebox';
|
|
3
|
-
import type { MissionOptions } from './mission.js';
|
|
3
|
+
import type { MissionOptions, MissionLatestFeats } from './mission.js';
|
|
4
4
|
import { GUIDMatch } from './mission.js';
|
|
5
5
|
import Err from '@openaddresses/batch-error';
|
|
6
6
|
import Commands, { CommandOutputFormat } from '../commands.js';
|
|
7
|
-
import type { Feature } from '@tak-ps/node-cot';
|
|
8
7
|
|
|
9
8
|
export enum MissionLayerType {
|
|
10
9
|
GROUP = 'GROUP',
|
|
@@ -180,19 +179,21 @@ export default class MissionLayerCommands extends Commands {
|
|
|
180
179
|
name: string,
|
|
181
180
|
layerUid: string, // Layer UID
|
|
182
181
|
opts?: Static<typeof MissionOptions>
|
|
183
|
-
): Promise<Static<typeof
|
|
182
|
+
): Promise<Static<typeof MissionLatestFeats>> {
|
|
184
183
|
const layer = (await this.get(name, layerUid, opts)).data;
|
|
185
|
-
const
|
|
184
|
+
const res = await this.api.Mission.latestFeats(name, opts);
|
|
186
185
|
|
|
187
186
|
const layerUids = new Set((layer.uids || []).map((u) => {
|
|
188
187
|
return u.data
|
|
189
188
|
}));
|
|
190
189
|
|
|
191
|
-
const
|
|
190
|
+
const features = res.features.filter((f) => {
|
|
192
191
|
return layerUids.has(f.id)
|
|
193
192
|
});
|
|
194
193
|
|
|
195
|
-
|
|
194
|
+
// Invalid CoTs cannot be filtered by layer as their parse failed
|
|
195
|
+
// so all Mission-wide invalid CoTs are returned
|
|
196
|
+
return { features, invalid: res.invalid };
|
|
196
197
|
}
|
|
197
198
|
|
|
198
199
|
async get(
|
package/lib/api/mission.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import CoT, { CoTParser } from '@tak-ps/node-cot';
|
|
1
|
+
import { CoTParser } from '@tak-ps/node-cot';
|
|
3
2
|
import { Type, Static } from '@sinclair/typebox';
|
|
4
3
|
import Err from '@openaddresses/batch-error';
|
|
5
4
|
import { Readable } from 'node:stream'
|
|
6
5
|
import { TAKItem, TAKList } from './types.js';
|
|
7
6
|
import { MissionLog } from './mission-log.js';
|
|
8
|
-
import
|
|
7
|
+
import { Feature } from '@tak-ps/node-cot';
|
|
9
8
|
import Commands, { CommandOutputFormat, type ParsedArgs } from '../commands.js';
|
|
10
9
|
|
|
11
10
|
export enum MissionSubscriberRole {
|
|
@@ -108,6 +107,21 @@ export const MissionOptions = Type.Object({
|
|
|
108
107
|
token: Type.Optional(Type.String())
|
|
109
108
|
});
|
|
110
109
|
|
|
110
|
+
/**
|
|
111
|
+
* A Mission CoT that could not be parsed or failed validation,
|
|
112
|
+
* with the raw xml-js event included so the caller can extract
|
|
113
|
+
* whatever context (UID, Callsign, etc.) it needs
|
|
114
|
+
*/
|
|
115
|
+
export const MissionInvalidFeature = Type.Object({
|
|
116
|
+
error: Type.String(),
|
|
117
|
+
feature: Type.Unknown()
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
export const MissionLatestFeats = Type.Object({
|
|
121
|
+
features: Type.Array(Feature.Feature),
|
|
122
|
+
invalid: Type.Array(MissionInvalidFeature)
|
|
123
|
+
});
|
|
124
|
+
|
|
111
125
|
export const AttachContentsInput = Type.Object({
|
|
112
126
|
hashes: Type.Optional(Type.Array(Type.String())),
|
|
113
127
|
uids: Type.Optional(Type.Array(Type.String())),
|
|
@@ -324,23 +338,38 @@ export default class MissionCommands extends Commands {
|
|
|
324
338
|
|
|
325
339
|
/**
|
|
326
340
|
* Return all current features in the Data Sync as CoT GeoJSON Features
|
|
341
|
+
*
|
|
342
|
+
* Each CoT in the TAK Server response is parsed & validated individually -
|
|
343
|
+
* those that fail are returned in the `invalid` array so a single poisoned
|
|
344
|
+
* CoT doesn't prevent the valid features from being returned
|
|
327
345
|
*/
|
|
328
346
|
async latestFeats(
|
|
329
347
|
name: string,
|
|
330
348
|
opts?: Static<typeof MissionOptions>
|
|
331
|
-
): Promise<Static<typeof
|
|
332
|
-
const
|
|
349
|
+
): Promise<Static<typeof MissionLatestFeats>> {
|
|
350
|
+
const features: Static<typeof Feature.Feature>[] = [];
|
|
333
351
|
|
|
334
|
-
const res
|
|
352
|
+
const res = CoTParser.from_xml_document(await this.latestCots(name, opts));
|
|
335
353
|
|
|
336
|
-
|
|
337
|
-
|
|
354
|
+
const invalid: Static<typeof MissionInvalidFeature>[] = res.invalid.map((entry) => {
|
|
355
|
+
return {
|
|
356
|
+
error: entry.error,
|
|
357
|
+
feature: entry.event
|
|
358
|
+
};
|
|
359
|
+
});
|
|
338
360
|
|
|
339
|
-
for (const
|
|
340
|
-
|
|
361
|
+
for (const cot of res.cots) {
|
|
362
|
+
try {
|
|
363
|
+
features.push(await CoTParser.to_geojson(cot));
|
|
364
|
+
} catch (err) {
|
|
365
|
+
invalid.push({
|
|
366
|
+
error: err instanceof Error ? err.message : String(err),
|
|
367
|
+
feature: cot.raw.event
|
|
368
|
+
});
|
|
369
|
+
}
|
|
341
370
|
}
|
|
342
371
|
|
|
343
|
-
return
|
|
372
|
+
return { features, invalid };
|
|
344
373
|
}
|
|
345
374
|
|
|
346
375
|
/**
|
package/lib/api.ts
CHANGED
|
@@ -21,6 +21,7 @@ import Subscription from './api/subscriptions.js';
|
|
|
21
21
|
import Video from './api/video.js';
|
|
22
22
|
import Export from './api/export.js';
|
|
23
23
|
import Err from '@openaddresses/batch-error';
|
|
24
|
+
import { TAKServerError, isHTML } from './utils/html-error.js';
|
|
24
25
|
import * as auth from './auth.js';
|
|
25
26
|
|
|
26
27
|
type ErrorWithCause = Error & {
|
|
@@ -210,6 +211,9 @@ export default class TAKAPI {
|
|
|
210
211
|
bdy = null;
|
|
211
212
|
}
|
|
212
213
|
|
|
214
|
+
// TAK Server returns HTML error pages
|
|
215
|
+
if (isHTML(bdy)) throw new TAKServerError(res.status, bdy);
|
|
216
|
+
|
|
213
217
|
throw new Err(res.status, null, bdy || `Status Code: ${res.status}`);
|
|
214
218
|
}
|
|
215
219
|
|
|
@@ -219,7 +223,7 @@ export default class TAKAPI {
|
|
|
219
223
|
return await res.text();
|
|
220
224
|
}
|
|
221
225
|
} catch (err) {
|
|
222
|
-
if (err instanceof
|
|
226
|
+
if (err instanceof Err) throw err;
|
|
223
227
|
throw new Err(400, null, formatFetchError(err));
|
|
224
228
|
}
|
|
225
229
|
}
|