@tak-ps/node-tak 8.3.0 → 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 +4 -0
  5. package/dist/index.js +3 -1
  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 +6 -1
  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,741 @@
1
+ import xmljs from 'xml-js';
2
+ import TAKAPI from '../api.js';
3
+ import CoT from '@tak-ps/node-cot';
4
+ import { Type, Static } from '@sinclair/typebox';
5
+ import Err from '@openaddresses/batch-error';
6
+ import { Readable } from 'node:stream'
7
+ import { TAKItem, TAKList } from './types.js';
8
+ import { MissionLog } from './mission-log.js';
9
+ import type { Feature } from '@tak-ps/node-cot';
10
+
11
+ export enum MissionSubscriberRole {
12
+ MISSION_OWNER = 'MISSION_OWNER',
13
+ MISSION_SUBSCRIBER = 'MISSION_SUBSCRIBER',
14
+ MISSION_READONLY_SUBSCRIBER = 'MISSION_READONLY_SUBSCRIBER'
15
+ }
16
+
17
+ export const MissionContent = Type.Object({
18
+ keywords: Type.Array(Type.String()),
19
+ mimeType: Type.String(),
20
+ name: Type.String(),
21
+ hash: Type.String(),
22
+ submissionTime: Type.String(),
23
+ submitter: Type.String(),
24
+ uid: Type.String(),
25
+ creatorUid: Type.String(),
26
+ size: Type.Integer(),
27
+ expiration: Type.Integer()
28
+ });
29
+
30
+ export const Mission = Type.Object({
31
+ name: Type.String(),
32
+ description: Type.String(),
33
+ chatRoom: Type.String(),
34
+ baseLayer: Type.Optional(Type.String()),
35
+ bbox: Type.Optional(Type.String()),
36
+ path: Type.Optional(Type.String()),
37
+ classification: Type.Optional(Type.String()),
38
+ tool: Type.String(),
39
+ keywords: Type.Array(Type.Unknown()),
40
+ creatorUid: Type.String(),
41
+ createTime: Type.String(),
42
+ externalData: Type.Array(Type.Unknown()),
43
+ feeds: Type.Array(Type.Unknown()),
44
+ mapLayers: Type.Array(Type.Unknown()),
45
+ ownerRole: Type.Optional(Type.Object({
46
+ permissions: Type.Array(Type.String()),
47
+ type: Type.Enum(MissionSubscriberRole)
48
+ })),
49
+ inviteOnly: Type.Boolean(),
50
+ expiration: Type.Number(),
51
+ guid: Type.String(),
52
+ uids: Type.Array(Type.Unknown()),
53
+ logs: Type.Optional(Type.Array(MissionLog)), // Only present if ?logs=true
54
+ contents: Type.Array(Type.Object({
55
+ timestamp: Type.String(),
56
+ creatorUid: Type.String(),
57
+ data: MissionContent
58
+ })),
59
+ passwordProtected: Type.Boolean(),
60
+ token: Type.Optional(Type.String()), // Only present when mission created
61
+ groups: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())])), // Only present on Mission.get()
62
+ missionChanges: Type.Optional(Type.Array(Type.Unknown())) // Only present on Mission.get()
63
+ });
64
+
65
+ export const MissionChange = Type.Object({
66
+ isFederatedChange: Type.Boolean(),
67
+ type: Type.String(),
68
+ missionName: Type.String(),
69
+ timestamp: Type.String(),
70
+ serverTime: Type.String(),
71
+ creatorUid: Type.String(),
72
+ contentUid: Type.Optional(Type.String()),
73
+ details: Type.Optional(Type.Object({
74
+ type: Type.String(),
75
+ callsign: Type.String(),
76
+ color: Type.Optional(Type.String()),
77
+ location: Type.Object({
78
+ lat: Type.Number(),
79
+ lon: Type.Number()
80
+ })
81
+ })),
82
+ contentResource: Type.Optional(MissionContent)
83
+ });
84
+
85
+ export const MissionRole = Type.Object({
86
+ permissions: Type.Array(Type.String()),
87
+ hibernateLazyInitializer: Type.Optional(Type.Any()),
88
+ type: Type.Enum(MissionSubscriberRole)
89
+ })
90
+
91
+ export const MissionSubscriber = Type.Object({
92
+ token: Type.Optional(Type.String()),
93
+ clientUid: Type.String(),
94
+ username: Type.String(),
95
+ createTime: Type.String(),
96
+ role: MissionRole
97
+ })
98
+
99
+ export const MissionOptions = Type.Object({
100
+ token: Type.Optional(Type.String())
101
+ });
102
+
103
+ export const AttachContentsInput = Type.Object({
104
+ hashes: Type.Optional(Type.Array(Type.String())),
105
+ uids: Type.Optional(Type.Array(Type.String())),
106
+ });
107
+
108
+ export const DetachContentsInput = Type.Object({
109
+ hash: Type.Optional(Type.String()),
110
+ uid: Type.Optional(Type.String())
111
+ });
112
+
113
+ export const MissionChangesInput = Type.Object({
114
+ secago: Type.Optional(Type.Integer()),
115
+ start: Type.Optional(Type.String()),
116
+ end: Type.Optional(Type.String()),
117
+ squashed: Type.Optional(Type.Boolean())
118
+ })
119
+
120
+ export const SubscribedInput = Type.Object({
121
+ uid: Type.String(),
122
+ })
123
+
124
+ export const UnsubscribeInput = Type.Object({
125
+ uid: Type.String(),
126
+ disconnectOnly: Type.Optional(Type.Boolean())
127
+ })
128
+
129
+ export const SubscriptionInput = Type.Object({
130
+ uid: Type.String(),
131
+ });
132
+
133
+ export const SubscribeInput = Type.Object({
134
+ uid: Type.String(),
135
+ password: Type.Optional(Type.String()),
136
+ secago: Type.Optional(Type.Integer()),
137
+ start: Type.Optional(Type.String()),
138
+ end: Type.Optional(Type.String())
139
+ })
140
+
141
+ export const MissionDeleteInput = Type.Object({
142
+ creatorUid: Type.Optional(Type.String()),
143
+ deepDelete: Type.Optional(Type.Boolean())
144
+ })
145
+
146
+ export const GetInput = Type.Object({
147
+ password: Type.Optional(Type.String()),
148
+ changes: Type.Optional(Type.Boolean()),
149
+ logs: Type.Optional(Type.Boolean()),
150
+ secago: Type.Optional(Type.Integer()),
151
+ start: Type.Optional(Type.String()),
152
+ end: Type.Optional(Type.String())
153
+ });
154
+
155
+ export const SetRoleInput = Type.Object({
156
+ clientUid: Type.String(),
157
+ username: Type.String(),
158
+ role: MissionRole
159
+ });
160
+
161
+ export const MissionListInput = Type.Object({
162
+ passwordProtected: Type.Optional(Type.Boolean()),
163
+ defaultRole: Type.Optional(Type.Boolean()),
164
+ tool: Type.Optional(Type.String())
165
+ });
166
+
167
+ export const MissionCreateInput = Type.Object({
168
+ group: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()])),
169
+ creatorUid: Type.String(),
170
+ description: Type.Optional(Type.String({ default: '' })),
171
+ chatRoom: Type.Optional(Type.String()),
172
+ baseLayer: Type.Optional(Type.String()),
173
+ bbox: Type.Optional(Type.String()),
174
+ boundingPolygon: Type.Optional(Type.Array(Type.String())),
175
+ path: Type.Optional(Type.String()),
176
+ classification: Type.Optional(Type.String()),
177
+ tool: Type.Optional(Type.String({ default: 'public' })),
178
+ password: Type.Optional(Type.String()),
179
+ defaultRole: Type.Optional(Type.String()),
180
+ expiration: Type.Optional(Type.Integer()),
181
+ inviteOnly: Type.Optional(Type.Boolean({ default: false })),
182
+ allowDupe: Type.Optional(Type.Boolean({ default: false })),
183
+ });
184
+
185
+ export const GUIDMatch = new RegExp(/^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$/);
186
+
187
+ export const TAKList_Mission = TAKList(Mission);
188
+ export const TAKList_MissionChange = TAKList(MissionChange);
189
+ export const TAKList_MissionSubscriber = TAKList(MissionSubscriber);
190
+ export const TAKItem_MissionSubscriber = TAKItem(MissionSubscriber);
191
+
192
+ /**
193
+ * @class
194
+ */
195
+ export default class {
196
+ api: TAKAPI;
197
+
198
+ constructor(api: TAKAPI) {
199
+ this.api = api;
200
+ }
201
+
202
+ #isGUID(id: string): boolean {
203
+ return GUIDMatch.test(id)
204
+ }
205
+
206
+ #encodeName(name: string): string {
207
+ return encodeURIComponent(name.trim())
208
+ }
209
+
210
+ #headers(opts?: Static<typeof MissionOptions>): object {
211
+ if (opts && opts.token) {
212
+ return {
213
+ MissionAuthorization: `Bearer ${opts.token}`
214
+ }
215
+ } else {
216
+ return {};
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Return Zip archive of Mission Sync
222
+ *
223
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/getMissionArchive_1 TAK Server Docs}.
224
+ */
225
+ async getArchive(
226
+ name: string,
227
+ opts?: Static<typeof MissionOptions>
228
+ ): Promise<Readable> {
229
+ const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}/archive`, this.api.url);
230
+
231
+ const res = await this.api.fetch(url, {
232
+ method: 'GET',
233
+ headers: this.#headers(opts),
234
+ }, true);
235
+
236
+ return res.body;
237
+ }
238
+
239
+ /**
240
+ * Return Mission Sync changes in a given time range
241
+ *
242
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/getMissionChanges TAK Server Docs}.
243
+ */
244
+ async changes(
245
+ name: string,
246
+ query: Static<typeof MissionChangesInput>,
247
+ opts?: Static<typeof MissionOptions>
248
+ ): Promise<Static<typeof TAKList_MissionChange>> {
249
+ if (this.#isGUID(name)) name = (await this.getGuid(name, {})).name;
250
+
251
+ const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}/changes`, this.api.url);
252
+
253
+ let q: keyof Static<typeof MissionChangesInput>;
254
+ for (q in query) {
255
+ if (query[q] !== undefined) {
256
+ url.searchParams.append(q, String(query[q]));
257
+ }
258
+ }
259
+
260
+ const changes = await this.api.fetch(url, {
261
+ method: 'GET',
262
+ headers: this.#headers(opts),
263
+ });
264
+
265
+ return changes;
266
+ }
267
+
268
+ /**
269
+ * Return all current features in the Data Sync as CoT GeoJSON Features
270
+ */
271
+ async latestFeats(
272
+ name: string,
273
+ opts?: Static<typeof MissionOptions>
274
+ ): Promise<Static<typeof Feature.Feature>[]> {
275
+ const feats: Static<typeof Feature.Feature>[] = [];
276
+
277
+ const res: any = xmljs.xml2js(await this.latestCots(name, opts), { compact: true });
278
+
279
+ if (!Object.keys(res.events).length) return feats;
280
+ if (!res.events.event || (Array.isArray(res.events.event) && !res.events.event.length)) return feats;
281
+
282
+ for (const event of Array.isArray(res.events.event) ? res.events.event : [res.events.event] ) {
283
+ feats.push((new CoT({ event })).to_geojson());
284
+ }
285
+
286
+ return feats;
287
+ }
288
+
289
+ /**
290
+ * Return all current features in the Data Sync as CoT GeoJSON Features
291
+ *
292
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/getLatestMissionCotEvents TAK Server Docs}.
293
+ */
294
+ async latestCots(
295
+ name: string,
296
+ opts?: Static<typeof MissionOptions>
297
+ ): Promise<string> {
298
+ const url = this.#isGUID(name)
299
+ ? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}/cot`, this.api.url)
300
+ : new URL(`/Marti/api/missions/${this.#encodeName(name)}/cot`, this.api.url);
301
+
302
+ return await this.api.fetch(url, {
303
+ method: 'GET',
304
+ headers: this.#headers(opts)
305
+ });
306
+ }
307
+
308
+ /**
309
+ * Return users associated with this mission
310
+ *
311
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/getMissionContacts TAK Server Docs}.
312
+ */
313
+ async contacts(
314
+ name: string,
315
+ opts?: Static<typeof MissionOptions>
316
+ ) {
317
+ const url = this.#isGUID(name)
318
+ ? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}/contacts`, this.api.url)
319
+ : new URL(`/Marti/api/missions/${this.#encodeName(name)}/contacts`, this.api.url);
320
+
321
+ return await this.api.fetch(url, {
322
+ method: 'GET',
323
+ headers: this.#headers(opts)
324
+ });
325
+ }
326
+
327
+ /**
328
+ * Remove a file from the mission
329
+ *
330
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/removeMissionContent TAK Server Docs}.
331
+ */
332
+ async detachContents(
333
+ name: string,
334
+ body: Static<typeof DetachContentsInput>,
335
+ opts?: Static<typeof MissionOptions>
336
+ ) {
337
+ const url = this.#isGUID(name)
338
+ ? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}/contents`, this.api.url)
339
+ : new URL(`/Marti/api/missions/${this.#encodeName(name)}/contents`, this.api.url);
340
+
341
+ if (body.hash) url.searchParams.append('hash', body.hash);
342
+ if (body.uid) url.searchParams.append('uid', body.uid);
343
+
344
+ return await this.api.fetch(url, {
345
+ method: 'DELETE',
346
+ headers: this.#headers(opts),
347
+ });
348
+ }
349
+
350
+ /**
351
+ * Attach a file resource by hash from the TAK Server file manager
352
+ *
353
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/addMissionContent TAK Server Docs}.
354
+ */
355
+ async attachContents(
356
+ name: string,
357
+ body: Static<typeof AttachContentsInput>,
358
+ opts?: Static<typeof MissionOptions>
359
+ ) {
360
+ const url = this.#isGUID(name)
361
+ ? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}/contents`, this.api.url)
362
+ : new URL(`/Marti/api/missions/${this.#encodeName(name)}/contents`, this.api.url);
363
+
364
+ return await this.api.fetch(url, {
365
+ method: 'PUT',
366
+ headers: this.#headers(opts),
367
+ body
368
+ });
369
+ }
370
+
371
+ /**
372
+ * Upload a Mission Package
373
+ *
374
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/addMissionPackage TAK Server Docs}.
375
+ */
376
+ async upload(
377
+ name: string,
378
+ creatorUid: string,
379
+ body: Readable,
380
+ opts?: Static<typeof MissionOptions>
381
+ ) {
382
+ if (this.#isGUID(name)) name = (await this.getGuid(name, {})).name;
383
+
384
+ const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}/contents/missionpackage`, this.api.url);
385
+ url.searchParams.append('creatorUid', creatorUid);
386
+
387
+ return await this.api.fetch(url, {
388
+ method: 'PUT',
389
+ headers: this.#headers(opts),
390
+ body
391
+ });
392
+ }
393
+
394
+ /**
395
+ * Return UIDs associated with any subscribed users
396
+ *
397
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/getMissionSubscriptions TAK Server Docs}.
398
+ */
399
+ async subscriptions(
400
+ name: string,
401
+ opts?: Static<typeof MissionOptions>
402
+ ): Promise<Static<typeof TAKItem_MissionSubscriber>> {
403
+ const url = this.#isGUID(name)
404
+ ? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}/subscriptions`, this.api.url)
405
+ : new URL(`/Marti/api/missions/${this.#encodeName(name)}/subscriptions`, this.api.url);
406
+
407
+ return await this.api.fetch(url, {
408
+ method: 'GET',
409
+ headers: this.#headers(opts),
410
+ });
411
+ }
412
+
413
+ /**
414
+ * Return permissions associated with any subscribed users
415
+ *
416
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/getMissionSubscriptionRoles TAK Server Docs}.
417
+ */
418
+ async subscriptionRoles(
419
+ name: string,
420
+ opts?: Static<typeof MissionOptions>
421
+ ): Promise<Static<typeof TAKList_MissionSubscriber>> {
422
+ const url = this.#isGUID(name)
423
+ ? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}/subscriptions/roles`, this.api.url)
424
+ : new URL(`/Marti/api/missions/${this.#encodeName(name)}/subscriptions/roles`, this.api.url);
425
+
426
+ return await this.api.fetch(url, {
427
+ method: 'GET',
428
+ headers: this.#headers(opts),
429
+ });
430
+ }
431
+
432
+ /**
433
+ * Return Role associated with a given mission if subscribed
434
+ *
435
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/setMissionRole TAK Server Docs}.
436
+ */
437
+ async setRole(
438
+ name: string,
439
+ query: Static<typeof SetRoleInput>,
440
+ opts?: Static<typeof MissionOptions>
441
+ ): Promise<Static<typeof MissionRole>> {
442
+ const url = this.#isGUID(name)
443
+ ? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}/role`, this.api.url)
444
+ : new URL(`/Marti/api/missions/${this.#encodeName(name)}/role`, this.api.url);
445
+
446
+ let q: keyof Static<typeof SetRoleInput>;
447
+ for (q in query) {
448
+ if (query[q] !== undefined) {
449
+ url.searchParams.append(q, String(query[q]));
450
+ }
451
+ }
452
+
453
+ const res = await this.api.fetch(url, {
454
+ method: 'PUT',
455
+ headers: this.#headers(opts),
456
+ });
457
+
458
+ return res.data;
459
+ }
460
+
461
+ /**
462
+ * Return Role associated with a given mission if subscribed
463
+ *
464
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/getMissionRoleFromToken TAK Server Docs}.
465
+ */
466
+ async role(
467
+ name: string,
468
+ opts?: Static<typeof MissionOptions>
469
+ ): Promise<Static<typeof MissionRole>> {
470
+ const url = this.#isGUID(name)
471
+ ? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}/role`, this.api.url)
472
+ : new URL(`/Marti/api/missions/${this.#encodeName(name)}/role`, this.api.url);
473
+
474
+ const res = await this.api.fetch(url, {
475
+ method: 'GET',
476
+ headers: this.#headers(opts),
477
+ });
478
+
479
+ return res.data;
480
+ }
481
+
482
+ /**
483
+ * Return subscription associated with a given mission if subscribed
484
+ *
485
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/getSubscriptionForUser TAK Server Docs}.
486
+ */
487
+ async subscription(
488
+ name: string,
489
+ query: Static<typeof SubscriptionInput>,
490
+ opts?: Static<typeof MissionOptions>
491
+ ): Promise<Static<typeof MissionSubscriber>> {
492
+ const url = this.#isGUID(name)
493
+ ? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}/subscription`, this.api.url)
494
+ : new URL(`/Marti/api/missions/${this.#encodeName(name)}/subscription`, this.api.url);
495
+
496
+ let q: keyof Static<typeof SubscriptionInput>;
497
+ for (q in query) {
498
+ if (query[q] !== undefined) {
499
+ url.searchParams.append(q, String(query[q]));
500
+ }
501
+ }
502
+
503
+ const res = await this.api.fetch(url, {
504
+ method: 'GET',
505
+ headers: this.#headers(opts),
506
+ });
507
+
508
+ return res.data;
509
+ }
510
+
511
+ /**
512
+ * Subscribe to a mission
513
+ *
514
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/createMissionSubscription TAK Server Docs}.
515
+ */
516
+ async subscribe(
517
+ name: string,
518
+ query: Static<typeof SubscribeInput>,
519
+ opts?: Static<typeof MissionOptions>
520
+ ): Promise<Static<typeof TAKItem_MissionSubscriber>> {
521
+ const url = this.#isGUID(name)
522
+ ? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}/subscription`, this.api.url)
523
+ : new URL(`/Marti/api/missions/${this.#encodeName(name)}/subscription`, this.api.url);
524
+
525
+ let q: keyof Static<typeof SubscribeInput>;
526
+ for (q in query) {
527
+ if (query[q] !== undefined) {
528
+ url.searchParams.append(q, String(query[q]));
529
+ }
530
+ }
531
+
532
+ return await this.api.fetch(url, {
533
+ method: 'PUT',
534
+ headers: this.#headers(opts),
535
+ });
536
+ }
537
+
538
+ /**
539
+ * Unsubscribe from a mission
540
+ *
541
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/deleteMissionSubscription TAK Server Docs}.
542
+ */
543
+ async unsubscribe(
544
+ name: string,
545
+ query: Static<typeof UnsubscribeInput>,
546
+ opts?: Static<typeof MissionOptions>
547
+ ) {
548
+ const url = this.#isGUID(name)
549
+ ? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}/subscription`, this.api.url)
550
+ : new URL(`/Marti/api/missions/${this.#encodeName(name)}/subscription`, this.api.url);
551
+
552
+ let q: keyof Static<typeof UnsubscribeInput>;
553
+ for (q in query) {
554
+ if (query[q] !== undefined) {
555
+ url.searchParams.append(q, String(query[q]));
556
+ }
557
+ }
558
+
559
+ return await this.api.fetch(url, {
560
+ method: 'DELETE',
561
+ headers: this.#headers(opts),
562
+ });
563
+ }
564
+
565
+ /**
566
+ * List missions in currently active channels
567
+ *
568
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/getAllMissions_1 TAK Server Docs}.
569
+ */
570
+ async list(query: Static<typeof MissionListInput>): Promise<Static<typeof TAKList_Mission>> {
571
+ const url = new URL('/Marti/api/missions', this.api.url);
572
+
573
+ let q: keyof Static<typeof MissionListInput>;
574
+ for (q in query) {
575
+ if (query[q] !== undefined) {
576
+ url.searchParams.append(q, String(query[q]));
577
+ }
578
+ }
579
+
580
+ return await this.api.fetch(url, {
581
+ method: 'GET'
582
+ });
583
+ }
584
+
585
+ /**
586
+ * Get mission by its GUID
587
+ *
588
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/getMissionByGuid TAK Server Docs}.
589
+ */
590
+ async getGuid(
591
+ guid: string,
592
+ query: Static<typeof GetInput>,
593
+ opts?: Static<typeof MissionOptions>
594
+ ): Promise<Static<typeof Mission>> {
595
+ const url = new URL(`/Marti/api/missions/guid/${encodeURIComponent(guid)}`, this.api.url);
596
+
597
+ let q: keyof Static<typeof GetInput>;
598
+ for (q in query) {
599
+ if (query[q] !== undefined) {
600
+ url.searchParams.append(q, String(query[q]));
601
+ }
602
+ }
603
+
604
+ const missions: Static<typeof TAKList_Mission> = await this.api.fetch(url, {
605
+ method: 'GET',
606
+ headers: this.#headers(opts),
607
+ });
608
+
609
+ if (!missions.data.length) throw new Err(404, null, `No Mission for GUID: ${guid}`);
610
+ return missions.data[0];
611
+ }
612
+
613
+ /**
614
+ * Check if you have access to a given mission
615
+ */
616
+ async access(
617
+ name: string,
618
+ opts?: Static<typeof MissionOptions>
619
+ ): Promise<boolean> {
620
+ try {
621
+ const url = this.#isGUID(name)
622
+ ? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}`, this.api.url)
623
+ : new URL(`/Marti/api/missions/${this.#encodeName(name)}`, this.api.url);
624
+
625
+ const missions: Static<typeof TAKList_Mission> = await this.api.fetch(url, {
626
+ method: 'GET',
627
+ headers: this.#headers(opts),
628
+ });
629
+
630
+ if (!missions.data.length) return false;
631
+ return true;
632
+ } catch (err) {
633
+ console.error(err);
634
+ return false;
635
+ }
636
+ }
637
+
638
+ /**
639
+ * Get mission by its Name
640
+ *
641
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/getMission TAK Server Docs}.
642
+ */
643
+ async get(
644
+ name: string,
645
+ query: Static<typeof GetInput>,
646
+ opts?: Static<typeof MissionOptions>
647
+ ): Promise<Static<typeof Mission>> {
648
+ const url = this.#isGUID(name)
649
+ ? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}`, this.api.url)
650
+ : new URL(`/Marti/api/missions/${this.#encodeName(name)}`, this.api.url);
651
+
652
+ let q: keyof Static<typeof GetInput>;
653
+ for (q in query) {
654
+ if (query[q] !== undefined) {
655
+ url.searchParams.append(q, String(query[q]));
656
+ }
657
+ }
658
+
659
+ const missions: Static<typeof TAKList_Mission> = await this.api.fetch(url, {
660
+ method: 'GET',
661
+ headers: this.#headers(opts),
662
+ });
663
+
664
+ if (!missions.data.length) throw new Err(404, null, `No Mission for Name: ${name}`);
665
+
666
+ return missions.data[0];
667
+ }
668
+
669
+ /**
670
+ * Create a new mission
671
+ *
672
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/createMission TAK Server Docs}.
673
+ */
674
+ async create(
675
+ name: string,
676
+ query: Static<typeof MissionCreateInput>
677
+ ): Promise<Static<typeof Mission>> {
678
+ const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}`, this.api.url);
679
+
680
+ if (query.group && Array.isArray(query.group)) query.group = query.group.join(',');
681
+
682
+ let q: keyof Static<typeof MissionCreateInput>;
683
+ for (q in query) {
684
+ if (query[q] !== undefined) {
685
+ url.searchParams.append(q, String(query[q]));
686
+ }
687
+ }
688
+
689
+ const missions = await this.api.fetch(url, {
690
+ method: 'POST'
691
+ });
692
+
693
+ if (!missions.data.length) throw new Error('Create Mission didn\'t return a mission or an error');
694
+ const mission = missions.data[0];
695
+
696
+ return mission;
697
+ }
698
+
699
+ /**
700
+ * Delete a mission
701
+ *
702
+ * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/deleteMission TAK Server Docs}.
703
+ */
704
+ async delete(
705
+ name: string,
706
+ query: Static<typeof MissionDeleteInput>,
707
+ opts?: Static<typeof MissionOptions>
708
+ ) {
709
+ if (this.#isGUID(name)) {
710
+ const url = new URL('/Marti/api/missions', this.api.url);
711
+
712
+ url.searchParams.append('guid', name);
713
+
714
+ let q: keyof Static<typeof MissionDeleteInput>;
715
+ for (q in query) {
716
+ if (query[q] !== undefined) {
717
+ url.searchParams.append(q, String(query[q]));
718
+ }
719
+ }
720
+
721
+ return await this.api.fetch(url, {
722
+ method: 'DELETE',
723
+ headers: this.#headers(opts),
724
+ });
725
+ } else {
726
+ const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}`, this.api.url);
727
+
728
+ let q: keyof Static<typeof MissionDeleteInput>;
729
+ for (q in query) {
730
+ if (query[q] !== undefined) {
731
+ url.searchParams.append(q, String(query[q]));
732
+ }
733
+ }
734
+
735
+ return await this.api.fetch(url, {
736
+ method: 'DELETE',
737
+ headers: this.#headers(opts),
738
+ });
739
+ }
740
+ }
741
+ }