@tak-ps/node-tak 8.2.1 → 8.4.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.
Files changed (63) hide show
  1. package/.github/workflows/doc.yml +6 -6
  2. package/.github/workflows/release.yml +3 -3
  3. package/.github/workflows/test.yml +14 -9
  4. package/CHANGELOG.md +9 -0
  5. package/dist/index.js +5 -5
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/api/contacts.js +23 -0
  8. package/dist/lib/api/contacts.js.map +1 -0
  9. package/dist/lib/api/credentials.js +62 -0
  10. package/dist/lib/api/credentials.js.map +1 -0
  11. package/dist/lib/api/export.js +36 -0
  12. package/dist/lib/api/export.js.map +1 -0
  13. package/dist/lib/api/files.js +112 -0
  14. package/dist/lib/api/files.js.map +1 -0
  15. package/dist/lib/api/groups.js +47 -0
  16. package/dist/lib/api/groups.js.map +1 -0
  17. package/dist/lib/api/mission-layer.js +245 -0
  18. package/dist/lib/api/mission-layer.js.map +1 -0
  19. package/dist/lib/api/mission-log.js +108 -0
  20. package/dist/lib/api/mission-log.js.map +1 -0
  21. package/dist/lib/api/mission.js +583 -0
  22. package/dist/lib/api/mission.js.map +1 -0
  23. package/dist/lib/api/oauth.js +54 -0
  24. package/dist/lib/api/oauth.js.map +1 -0
  25. package/dist/lib/api/package.js +42 -0
  26. package/dist/lib/api/package.js.map +1 -0
  27. package/dist/lib/api/query.js +60 -0
  28. package/dist/lib/api/query.js.map +1 -0
  29. package/dist/lib/api/subscriptions.js +73 -0
  30. package/dist/lib/api/subscriptions.js.map +1 -0
  31. package/dist/lib/api/types.js +42 -0
  32. package/dist/lib/api/types.js.map +1 -0
  33. package/dist/lib/api/video.js +123 -0
  34. package/dist/lib/api/video.js.map +1 -0
  35. package/dist/lib/api.js +123 -0
  36. package/dist/lib/api.js.map +1 -0
  37. package/dist/lib/auth.js +92 -0
  38. package/dist/lib/auth.js.map +1 -0
  39. package/dist/lib/fetch.js +26 -0
  40. package/dist/lib/fetch.js.map +1 -0
  41. package/dist/lib/stream.js +9 -0
  42. package/dist/lib/stream.js.map +1 -0
  43. package/dist/tsconfig.tsbuildinfo +1 -1
  44. package/index.ts +8 -3
  45. package/lib/api/contacts.ts +27 -0
  46. package/lib/api/credentials.ts +74 -0
  47. package/lib/api/export.ts +44 -0
  48. package/lib/api/files.ts +151 -0
  49. package/lib/api/groups.ts +63 -0
  50. package/lib/api/mission-layer.ts +312 -0
  51. package/lib/api/mission-log.ts +140 -0
  52. package/lib/api/mission.ts +741 -0
  53. package/lib/api/oauth.ts +68 -0
  54. package/lib/api/package.ts +56 -0
  55. package/lib/api/query.ts +79 -0
  56. package/lib/api/subscriptions.ts +84 -0
  57. package/lib/api/types.ts +43 -0
  58. package/lib/api/video.ts +155 -0
  59. package/lib/api.ts +136 -0
  60. package/lib/auth.ts +117 -0
  61. package/lib/fetch.ts +38 -0
  62. package/lib/stream.ts +10 -0
  63. package/package.json +17 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.js","sourceRoot":"","sources":["../../lib/stream.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,aAAa,CAAC,MAAc;IACtD,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,MAAM,IAAI,GAAG,KAAK,EAAU,CAAC;QAC7B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE,CAAC,MAAM,CAAC,6BAA6B,GAAG,EAAE,CAAC,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -1 +1 @@
1
- {"root":["../index.ts","../test/default.test.ts","../test/findCoT.test.ts"],"version":"5.8.2"}
1
+ {"root":["../index.ts","../test/default.test.ts","../test/findCoT.test.ts","../lib/api.ts","../lib/auth.ts","../lib/fetch.ts","../lib/stream.ts","../lib/api/contacts.ts","../lib/api/credentials.ts","../lib/api/export.ts","../lib/api/files.ts","../lib/api/groups.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/query.ts","../lib/api/subscriptions.ts","../lib/api/types.ts","../lib/api/video.ts"],"version":"5.8.3"}
package/index.ts CHANGED
@@ -3,6 +3,9 @@ import tls from 'node:tls';
3
3
  import CoT from '@tak-ps/node-cot';
4
4
  import type { TLSSocket } from 'node:tls'
5
5
 
6
+ export * from './lib/api.js';
7
+ export * from './lib/auth.js';
8
+
6
9
  /* eslint-disable no-control-regex */
7
10
  export const REGEX_CONTROL = /[\u000B-\u001F\u007F-\u009F]/g;
8
11
 
@@ -127,12 +130,12 @@ export default class TAK extends EventEmitter {
127
130
  result = TAK.findCoT(buff);
128
131
  }
129
132
  }).on('timeout', () => {
130
- if (!this.destroyed) this.emit('timeout');
133
+ this.emit('timeout');
131
134
  }).on('error', (err: Error) => {
132
135
  this.emit('error', err);
133
136
  }).on('end', () => {
134
137
  this.open = false;
135
- if (!this.destroyed) this.emit('end');
138
+ this.emit('end');
136
139
  });
137
140
 
138
141
  this.pingInterval = setInterval(() => {
@@ -234,4 +237,6 @@ export default class TAK extends EventEmitter {
234
237
  }
235
238
  }
236
239
 
237
- export { CoT }
240
+ export {
241
+ CoT,
242
+ }
@@ -0,0 +1,27 @@
1
+ import TAKAPI from '../api.js';
2
+ import { Type, Static } from '@sinclair/typebox';
3
+
4
+ export const Contact = Type.Object({
5
+ filterGroups: Type.Any(), // I'm not familiar with this one
6
+ notes: Type.String(),
7
+ callsign: Type.String(),
8
+ team: Type.String(),
9
+ role: Type.String(),
10
+ takv: Type.String(),
11
+ uid: Type.String()
12
+ });
13
+
14
+ export default class {
15
+ api: TAKAPI;
16
+
17
+ constructor(api: TAKAPI) {
18
+ this.api = api;
19
+ }
20
+
21
+ async list(): Promise<Array<Static<typeof Contact>>> {
22
+ const url = new URL(`/Marti/api/contacts/all`, this.api.url);
23
+ return await this.api.fetch(url, {
24
+ method: 'GET'
25
+ });
26
+ }
27
+ }
@@ -0,0 +1,74 @@
1
+ import TAKAPI from '../api.js';
2
+ import { APIAuthPassword } from '../auth.js';
3
+ import { Static, Type } from '@sinclair/typebox';
4
+ import pem from 'pem';
5
+ import xml2js from 'xml2js';
6
+
7
+ export const CertificateResponse = Type.Object({
8
+ cert: Type.String(),
9
+ key: Type.String()
10
+ });
11
+
12
+ export default class {
13
+ api: TAKAPI;
14
+
15
+ constructor(api: TAKAPI) {
16
+ this.api = api;
17
+ }
18
+
19
+ async config() {
20
+ const url = new URL(`/Marti/api/tls/config`, this.api.url);
21
+ return await this.api.fetch(url, {
22
+ method: 'GET'
23
+ });
24
+ }
25
+
26
+ async generate(): Promise<Static<typeof CertificateResponse>> {
27
+ if (!(this.api.auth instanceof APIAuthPassword)) throw new Error('Must use Password Auth');
28
+
29
+ const config = await xml2js.parseStringPromise(await this.config());
30
+
31
+ let organization = null;
32
+ let organizationUnit = null;
33
+ for (const nameEntry of config['ns2:certificateConfig'].nameEntries) {
34
+ for (const ne of nameEntry.nameEntry) {
35
+ if (ne['$'].name === 'O') organization = ne['$'].value;
36
+ if (ne['$'].name === 'OU') organizationUnit = ne['$'].value;
37
+ }
38
+ }
39
+
40
+ const createCSR = pem.promisified.createCSR;
41
+
42
+ const keys: {
43
+ csr: string,
44
+ clientKey: string
45
+ } = await createCSR({
46
+ organization,
47
+ organizationUnit,
48
+ commonName: this.api.auth.username
49
+ });
50
+
51
+ const url = new URL(`/Marti/api/tls/signClient/v2`, this.api.url);
52
+ url.searchParams.append('clientUid', this.api.auth.username + ' (ETL)');
53
+ url.searchParams.append('version', '3');
54
+
55
+ const res = await this.api.fetch(url, {
56
+ method: 'POST',
57
+ nocookies: true,
58
+ headers: {
59
+ Accept: 'application/json',
60
+ Authorization: 'Basic ' + btoa(this.api.auth.username + ":" + this.api.auth.password)
61
+ },
62
+ body: keys.csr
63
+ });
64
+
65
+ let cert = '-----BEGIN CERTIFICATE-----\n' + res.signedCert;
66
+ if (!res.signedCert.endsWith('\n')) cert = cert + '\n';
67
+ cert = cert + '-----END CERTIFICATE-----';
68
+
69
+ return {
70
+ cert,
71
+ key: keys.clientKey
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,44 @@
1
+ import TAKAPI from '../api.js';
2
+ import { Type, Static } from '@sinclair/typebox';
3
+ import { Readable } from 'node:stream';
4
+
5
+ export const ExportInput = Type.Object({
6
+ startTime: Type.String(),
7
+ endTime: Type.String(),
8
+ groups: Type.Array(Type.String()),
9
+ format: Type.String({ enum: ['kmz', 'kml'] }),
10
+ interval: Type.Optional(Type.Number()),
11
+ multiTrackThreshold: Type.Optional(Type.String()),
12
+ extendedData: Type.Optional(Type.Boolean()),
13
+ optimizeExport: Type.Optional(Type.Boolean()),
14
+ });
15
+
16
+ /**
17
+ * @class
18
+ */
19
+ export default class {
20
+ api: TAKAPI;
21
+
22
+ constructor(api: TAKAPI) {
23
+ this.api = api;
24
+ }
25
+
26
+ async export(query: Static<typeof ExportInput>): Promise<Readable> {
27
+ const url = new URL(`/Marti/ExportMissionKML`, this.api.url);
28
+
29
+ const params = new URLSearchParams();
30
+ let q: keyof Static<typeof ExportInput>;
31
+ for (q in query) {
32
+ if (query[q] !== undefined ) {
33
+ params.append(q, String(query[q]));
34
+ }
35
+ }
36
+
37
+ const res = await this.api.fetch(url, {
38
+ method: 'POST',
39
+ body: params
40
+ }, true);
41
+
42
+ return res.body;
43
+ }
44
+ }
@@ -0,0 +1,151 @@
1
+ import TAKAPI from '../api.js';
2
+ import FormData from 'form-data';
3
+ import { Readable } from 'node:stream';
4
+ import mime from 'mime';
5
+ import { Type, Static } from '@sinclair/typebox';
6
+
7
+ export const Content = Type.Object({
8
+ UID: Type.String(),
9
+ SubmissionDateTime: Type.String(),
10
+ Keywords: Type.Array(Type.String()),
11
+ MIMEType: Type.String(),
12
+ SubmissionUser: Type.String(),
13
+ PrimaryKey: Type.String(),
14
+ Hash: Type.String(),
15
+ CreatorUid: Type.String(),
16
+ Name: Type.String()
17
+ });
18
+
19
+ export const Config = Type.Object({
20
+ uploadSizeLimit: Type.Integer()
21
+ })
22
+
23
+ export default class File {
24
+ api: TAKAPI;
25
+
26
+ constructor(api: TAKAPI) {
27
+ this.api = api;
28
+ }
29
+
30
+ // TODO Investigate this endpoint
31
+ list() {
32
+ new URL(`/Marti/api/sync/search`, this.api.url);
33
+ // param hash=<hash>
34
+ }
35
+
36
+ async meta(hash: string): Promise<string> {
37
+ const url = new URL(`/Marti/sync/${encodeURIComponent(hash)}/metadata`, this.api.url);
38
+
39
+ const res = await this.api.fetch(url, {
40
+ method: 'GET'
41
+ }, true);
42
+
43
+ const body = await res.text();
44
+
45
+ return body;
46
+ }
47
+
48
+ async download(hash: string): Promise<Readable> {
49
+ const url = new URL(`/Marti/sync/content`, this.api.url);
50
+ url.searchParams.append('hash', hash);
51
+
52
+ const res = await this.api.fetch(url, {
53
+ method: 'GET'
54
+ }, true);
55
+
56
+ return res.body;
57
+ }
58
+
59
+ async adminDelete(hash: string) {
60
+ const url = new URL(`/Marti/api/files/${hash}`, this.api.url);
61
+
62
+ return await this.api.fetch(url, {
63
+ method: 'DELETE',
64
+ });
65
+ }
66
+
67
+ async delete(hash: string) {
68
+ const url = new URL(`/Marti/sync/delete`, this.api.url);
69
+ url.searchParams.append('hash', hash)
70
+
71
+ return await this.api.fetch(url, {
72
+ method: 'DELETE',
73
+ });
74
+ }
75
+
76
+ // TODO Return a Content Object
77
+ async uploadPackage(opts: {
78
+ name: string;
79
+ creatorUid: string;
80
+ hash: string;
81
+ }, body: Readable | Buffer): Promise<string> {
82
+ const url = new URL(`/Marti/sync/missionupload`, this.api.url);
83
+ url.searchParams.append('filename', opts.name)
84
+ url.searchParams.append('creatorUid', opts.creatorUid)
85
+ url.searchParams.append('hash', opts.hash)
86
+
87
+ if (body instanceof Buffer) {
88
+ body = Readable.from(body as Buffer);
89
+ }
90
+
91
+ const form = new FormData()
92
+ form.append('assetfile', body as Readable);
93
+
94
+ const res = await this.api.fetch(url, {
95
+ method: 'POST',
96
+ body: form
97
+ }) as string;
98
+
99
+ return res;
100
+ }
101
+
102
+ async upload(opts: {
103
+ name: string;
104
+ contentLength: number;
105
+ contentType?: string;
106
+ keywords: string[];
107
+ creatorUid: string;
108
+ latitude?: string;
109
+ longitude?: string;
110
+ altitude?: string;
111
+ }, body: Readable | Buffer): Promise<Static<typeof Content>> {
112
+ const url = new URL(`/Marti/sync/upload`, this.api.url);
113
+ url.searchParams.append('name', opts.name)
114
+ url.searchParams.append('keywords', opts.keywords.join(','))
115
+ url.searchParams.append('creatorUid', opts.creatorUid)
116
+ if (opts.altitude) url.searchParams.append('altitude', opts.altitude);
117
+ if (opts.longitude) url.searchParams.append('longitude', opts.longitude);
118
+ if (opts.latitude) url.searchParams.append('latitude', opts.latitude);
119
+
120
+ if (body instanceof Buffer) {
121
+ body = Readable.from(body as Buffer);
122
+ }
123
+
124
+ const res = await this.api.fetch(url, {
125
+ method: 'POST',
126
+ headers: {
127
+ 'Content-Type': opts.contentType ? opts.contentType : mime.getType(opts.name),
128
+ 'Content-Length': opts.contentLength
129
+ },
130
+ body: body as Readable
131
+ });
132
+
133
+ return JSON.parse(res);
134
+ }
135
+
136
+ async count() {
137
+ const url = new URL('/Marti/api/files/metadata/count', this.api.url);
138
+
139
+ return await this.api.fetch(url, {
140
+ method: 'GET'
141
+ });
142
+ }
143
+
144
+ async config(): Promise<Static<typeof Config>> {
145
+ const url = new URL('/files/api/config', this.api.url);
146
+
147
+ return await this.api.fetch(url, {
148
+ method: 'GET'
149
+ });
150
+ }
151
+ }
@@ -0,0 +1,63 @@
1
+ import { Static, Type } from '@sinclair/typebox';
2
+ import TAKAPI from '../api.js';
3
+ import { TAKList } from './types.js';
4
+
5
+ export const Group = Type.Object({
6
+ name: Type.String(),
7
+ direction: Type.String(),
8
+ created: Type.String(),
9
+ type: Type.String(),
10
+ bitpos: Type.Number(),
11
+ active: Type.Boolean(),
12
+ description: Type.Optional(Type.String())
13
+ })
14
+
15
+ export const GroupListInput = Type.Object({
16
+ useCache: Type.Optional(Type.Boolean())
17
+ })
18
+
19
+ export const TAKList_Group = TAKList(Group);
20
+
21
+ export default class {
22
+ api: TAKAPI;
23
+
24
+ constructor(api: TAKAPI) {
25
+ this.api = api;
26
+ }
27
+
28
+ async list(
29
+ query: Static<typeof GroupListInput> = {}
30
+ ): Promise<Static<typeof TAKList_Group>> {
31
+ const url = new URL(`/Marti/api/groups/all`, this.api.url);
32
+
33
+ let q: keyof Static<typeof GroupListInput>;
34
+ for (q in query) {
35
+ if (query[q] !== undefined) {
36
+ url.searchParams.append(q, String(query[q]));
37
+ }
38
+ }
39
+
40
+ return await this.api.fetch(url, {
41
+ method: 'GET'
42
+ });
43
+ }
44
+
45
+ async update(
46
+ body: Static<typeof Group>[],
47
+ query: Static<typeof GroupListInput> = {}
48
+ ): Promise<void> {
49
+ const url = new URL(`/Marti/api/groups/active`, this.api.url);
50
+
51
+ let q: keyof Static<typeof GroupListInput>;
52
+ for (q in query) {
53
+ if (query[q] !== undefined) {
54
+ url.searchParams.append(q, String(query[q]));
55
+ }
56
+ }
57
+
58
+ await this.api.fetch(url, {
59
+ method: 'PUT',
60
+ body
61
+ });
62
+ }
63
+ }