@tak-ps/node-cot 1.0.2 → 2.1.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 ADDED
@@ -0,0 +1,23 @@
1
+ # CHANGELOG
2
+
3
+ ## Emoji Cheatsheet
4
+ - :pencil2: doc updates
5
+ - :bug: when fixing a bug
6
+ - :rocket: when making general improvements
7
+ - :white_check_mark: when adding tests
8
+ - :arrow_up: when upgrading dependencies
9
+ - :tada: when adding new features
10
+
11
+ ## Version History
12
+
13
+ ### v2.1.0
14
+
15
+ - :rocket: Allow setting `type` & `how` from GeoJSON
16
+
17
+ ### v2.0.0
18
+
19
+ - :rocket: Class based XML CoT approach
20
+ - :tada: Add GeoJSON to/from parsing (experimental)
21
+ - :white_check_mark: Update tests to use `tape`
22
+ - :pencil2: Update docs to Class approach
23
+ - :pencil2: Add a CHANGELOG
package/README.md CHANGED
@@ -23,8 +23,15 @@ npm install @tak-ps/node-cot
23
23
  ### Basic Usage
24
24
 
25
25
  ```
26
- import { cot } from '@tak-ps/node-cot';
26
+ import { XML } from '@tak-ps/node-cot';
27
+
27
28
  const message = '<event version="2.0" uid="ANDROID-deadbeef" type="a-f-G-U-C" how="m-g" time="2021-02-27T20:32:24.771Z" start="2021-02-27T20:32:24.771Z" stale="2021-02-27T20:38:39.771Z"><point lat="1.234567" lon="-3.141592" hae="-25.7" ce="9.9" le="9999999.0"/><detail><takv os="29" version="4.0.0.0 (deadbeef).1234567890-CIV" device="Some Android Device" platform="ATAK-CIV"/><contact xmppUsername="xmpp@host.com" endpoint="*:-1:stcp" callsign="JENNY"/><uid Droid="JENNY"/><precisionlocation altsrc="GPS" geopointsrc="GPS"/><__group role="Team Member" name="Cyan"/><status battery="78"/><track course="80.24833892285461" speed="0.0"/></detail></event>'
28
- const json = cot.xml2js(message)
29
- console.log(json)
29
+
30
+ const cot = new XML(message);
31
+
32
+ // Export Formats
33
+ cot.to_geojson(); // Output GeoJSON Representation
34
+ cot.to_xml(); // Output String XML Representation
35
+
36
+ cot.raw; // JSON XML Representation
30
37
  ```
package/index.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import XML from './src/xml.js';
2
- import Proto from './src/proto.js';
3
2
 
4
3
  export {
5
- XML,
6
- Proto
4
+ XML
7
5
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tak-ps/node-cot",
3
3
  "type": "module",
4
- "version": "1.0.2",
4
+ "version": "2.1.0",
5
5
  "description": "Lightweight JavaScript library for parsing and manipulating TAK messages",
6
6
  "main": "index.js",
7
7
  "scripts": {
@@ -10,6 +10,7 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "protobufjs": "^7.1.2",
13
+ "uuid": "^9.0.0",
13
14
  "xml-js": "^1.6.11"
14
15
  },
15
16
  "devDependencies": {
package/src/util.js ADDED
@@ -0,0 +1,93 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+
3
+ /**
4
+ * Helper functions for generating CoT data
5
+ * @class
6
+ */
7
+ export default class Util {
8
+ /**
9
+ * Return an event._attributes object with as many defaults as possible
10
+ *
11
+ * @param {String} type CoT Type
12
+ * @param {String} how CoT how
13
+ *
14
+ * @returns {Object}
15
+ */
16
+ static cot_event_attr(type, how) {
17
+ if (!type) throw new Error('type param required');
18
+ if (!how) throw new Error('how param required');
19
+
20
+ return {
21
+ ...Util.cot_version(),
22
+ ...Util.cot_uuid(),
23
+ type,
24
+ how,
25
+ ...Util.cot_date()
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Generate a random UUID
31
+ *
32
+ * @returns {Object}
33
+ */
34
+ static cot_uuid() {
35
+ return {
36
+ uid: uuidv4()
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Return the current version number this library supports
42
+ *
43
+ * @returns {Object}
44
+ */
45
+ static cot_version() {
46
+ return {
47
+ version: '2.0'
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Generate Null Island CoT point object
53
+ *
54
+ * @returns {Object}
55
+ */
56
+ static cot_point() {
57
+ return {
58
+ '_attributes': {
59
+ 'lat': '0.000000',
60
+ 'lon': '0.000000',
61
+ 'hae': '0.0',
62
+ 'ce': '9999999.0',
63
+ 'le': '9999999.0'
64
+ }
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Generate CoT date objects
70
+ *
71
+ * cot_date() - Time: now, Start: now, Stale: now + 20s
72
+ *
73
+ * @param {Date|null} time Time of CoT Message - if omitted, current time is used
74
+ * @param {Date|null} start Start Time of CoT - if omitted, current time is used
75
+ * @param {Date|null|numeric} stale Expiration of CoT - if null now+20s is used. Alternative an integer representing the start + ms
76
+ *
77
+ * @returns {Object}
78
+ */
79
+ static cot_date(time, start, stale) {
80
+ const now = Date.now();
81
+
82
+ if (!time) time = new Date(now).toISOString();
83
+ if (!start) start = new Date(now).toISOString();
84
+
85
+ if (!stale) {
86
+ stale = new Date(now + 20 * 1000).toISOString();
87
+ } else if (!isNaN(parseInt(stale))) {
88
+ stale = new Date(now + 20 * 1000).toISOString();
89
+ }
90
+
91
+ return { time, start, stale };
92
+ }
93
+ }
package/src/xml.js CHANGED
@@ -1,73 +1,101 @@
1
1
  import xmljs from 'xml-js';
2
+ import Util from './util.js';
2
3
 
4
+ /**
5
+ * Convert to and from an XML CoT message
6
+ * @class
7
+ *
8
+ * @param {String|Object|Buffer} cot A string/buffer containing the XML representation or the xml-js object tree
9
+ *
10
+ * @prop {Object} raw Raw XML-JS representation of CoT
11
+ */
3
12
  export default class XMLCot {
4
- static js2xml(js) {
5
- if (typeof js === 'undefined' || !js) {
6
- throw new Error('Attempted to parse empty Object');
7
- }
8
-
9
- return xmljs.js2xml(js, { compact: true });
10
- }
13
+ constructor(cot) {
14
+ if (cot instanceof Buffer) String(cot);
11
15
 
12
- // accepts an Object decoded with xml2js.decodeType(type)
13
- static encodeType(type) {
14
- let result = type.atom;
15
- if (type.descriptor) {
16
- result += `-${type.descriptor}`;
16
+ if (typeof cot === 'string') {
17
+ this.raw = xmljs.xml2js(cot, { compact: true });
18
+ } else {
19
+ this.raw = cot;
17
20
  }
18
- if (type.domain) {
19
- result += `-${type.domain}`;
20
- }
21
- if (type.milstd.length > 0) {
22
- result += `-${type.milstd.join('-')}`;
21
+
22
+ // Attempt to cast all point to numerics
23
+ for (const key of Object.keys(this.raw.event.point)) {
24
+ if (!isNaN(parseFloat(this.raw.event.point[key]))) {
25
+ this.raw.event.point[key] = parseFloat(this.raw.event.point[key]);
26
+ }
23
27
  }
24
- return result;
25
28
  }
26
29
 
27
- static jsDate2cot(unix) {
28
- return new Date(unix).toISOString();
29
- }
30
+ /**
31
+ * Return an XMLCot Message
32
+ *
33
+ * @param {Object} feature GeoJSON Point Feature
34
+ *
35
+ * @return {XMLCot}
36
+ */
37
+ static from_geojson(feature) {
38
+ if (feature.type !== 'Feature') throw new Error('Must be GeoJSON Feature');
39
+ if (!feature.geometry || feature.geometry.type !== 'Point') throw new Error('Must be GeoJSON Point Feature');
40
+ if (!feature.properties) throw new Error('Feature must have properties');
30
41
 
31
- static xml2js(cot) {
32
- if (typeof cot === 'undefined' || cot === null) {
33
- throw new Error('Attempted to parse empty COT message');
34
- }
42
+ const cot = {
43
+ 'event': {
44
+ '_attributes': Util.cot_event_attr(feature.properties.type || 'a-f-G', feature.properties.how || 'm-g'),
45
+ 'point': Util.cot_point()
46
+ }
47
+ };
35
48
 
36
- if (typeof cot === 'object') { // accept a data buffer or string for conversion
37
- cot = cot.toString();
49
+ for (const key of ['time', 'start', 'stale', 'type', 'how']) {
50
+ if (feature.properties[key]) cot.event._attributes[key] = feature.properties[key];
38
51
  }
39
52
 
40
- return xmljs.xml2js(cot, { compact: true });
53
+ return new XMLCot(cot);
41
54
  }
42
55
 
43
- // accepts a string formatted like 'a-f-G-U-C-I'
44
- static decodeType(type) {
45
- const split = type.split('-');
46
- const atom = split[0];
47
- const descriptor = split[1] || null;
48
- const domain = split[2] || null;
49
- const milstd = split.slice(3);
50
- return {
51
- type,
52
- atom,
53
- descriptor,
54
- domain,
55
- milstd
56
+ /**
57
+ * Return a GeoJSON Feature from an XML CoT message
58
+ *
59
+ * @returns {Object}
60
+ */
61
+ to_geojson() {
62
+ const geojson = {
63
+ id: this.raw.event._attributes.uid,
64
+ type: 'Feature',
65
+ properties: {
66
+ time: this.raw.event._attributes.time,
67
+ start: this.raw.event._attributes.start,
68
+ stale: this.raw.event._attributes.stale
69
+ },
70
+ geometry: {
71
+ type: 'Point',
72
+ coordinates: [
73
+ this.raw.event.point._attributes.lon,
74
+ this.raw.event.point._attributes.lon
75
+ ]
76
+ }
56
77
  };
78
+
79
+ return geojson;
57
80
  }
58
81
 
59
- static cotDate2js(iso) {
60
- return Date.parse(iso);
82
+ to_xml() {
83
+ return xmljs.js2xml(this.raw, {
84
+ compact: true
85
+ });
61
86
  }
62
87
 
63
- // convert a decoded point from xml2js(cot) from String to Numbers
64
- static parsePoint(point) {
88
+ /**
89
+ * Return a CoT Message
90
+ *
91
+ * @returns {XMLCot}
92
+ */
93
+ static ping() {
65
94
  return {
66
- lat: parseFloat(point.lat),
67
- lon: parseFloat(point.lon),
68
- hae: parseFloat(point.hae),
69
- ce: parseFloat(point.ce),
70
- le: parseFloat(point.le)
95
+ event: {
96
+ _attributes: Util.cot_event_attr('t-x-c-t', 'h-g-i-g-o'),
97
+ point: Util.cot_point()
98
+ }
71
99
  };
72
100
  }
73
101
  }
package/test/cot.test.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import test from 'tape';
2
- import XML from '../src/xml.js';
2
+ import { XML } from '../index.js';
3
3
 
4
4
  test('Decode COT message', (t) => {
5
5
  const packet = '<event version="2.0" uid="ANDROID-deadbeef" type="a-f-G-U-C" how="m-g" time="2021-02-27T20:32:24.771Z" start="2021-02-27T20:32:24.771Z" stale="2021-02-27T20:38:39.771Z"><point lat="1.234567" lon="-3.141592" hae="-25.7" ce="9.9" le="9999999.0"/><detail><takv os="29" version="4.0.0.0 (deadbeef).1234567890-CIV" device="Some Android Device" platform="ATAK-CIV"/><contact xmppUsername="xmpp@host.com" endpoint="*:-1:stcp" callsign="JENNY"/><uid Droid="JENNY"/><precisionlocation altsrc="GPS" geopointsrc="GPS"/><__group role="Team Member" name="Cyan"/><status battery="78"/><track course="80.24833892285461" speed="0.0"/></detail></event>';
6
6
 
7
- t.deepEquals(XML.xml2js(packet), {
7
+ t.deepEquals((new XML(packet)).raw, {
8
8
  'event': {
9
9
  '_attributes': {
10
10
  'version': '2.0',
@@ -55,7 +55,7 @@ test('Decode COT message', (t) => {
55
55
  test('Decode COT message', (t) => {
56
56
  const packet = '<event version="2.0" uid="TEST-deadbeef" type="a" how="m-g" time="2021-03-12T15:49:07.138Z" start="2021-03-12T15:49:07.138Z" stale="2021-03-12T15:49:07.138Z"><point lat="0.000000" lon="0.000000" hae="0.0" ce="9999999.0" le="9999999.0"/><detail><takv os="Android" version="10" device="Some Device" platform="python unittest"/><status battery="83"/><uid Droid="JENNY"/><contact callsign="JENNY" endpoint="*:-1:stcp" phone="800-867-5309"/><__group role="Team Member" name="Cyan"/><track course="90.1" speed="10.3"/></detail></event>';
57
57
 
58
- t.deepEquals(XML.xml2js(packet), {
58
+ t.deepEquals((new XML(packet)).raw, {
59
59
  'event': {
60
60
  '_attributes': {
61
61
  'version': '2.0',
@@ -167,7 +167,7 @@ test('Encode COT message', (t) => {
167
167
  };
168
168
 
169
169
  t.deepEquals(
170
- XML.js2xml(packet),
170
+ (new XML(packet)).to_xml(),
171
171
  '<event version="2.0" uid="ANDROID-deadbeef" type="a-f-G-U-C" how="m-g" time="2021-02-27T20:32:24.771Z" start="2021-02-27T20:32:24.771Z" stale="2021-02-27T20:38:39.771Z"><point lat="1.234567" lon="-3.141592" hae="-25.7" ce="9.9" le="9999999.0"/><detail><takv os="29" version="4.0.0.0 (deadbeef).1234567890-CIV" device="Some Android Device" platform="ATAK-CIV"/><contact xmppUsername="xmpp@host.com" endpoint="*:-1:stcp" callsign="JENNY"/><uid Droid="JENNY"/><precisionlocation altsrc="GPS" geopointsrc="GPS"/><__group role="Team Member" name="Cyan"/><status battery="78"/><track course="80.24833892285461" speed="0.0"/></detail></event>'
172
172
  );
173
173
 
@@ -190,7 +190,7 @@ test('Parse GeoChat message', (t) => {
190
190
  ' </detail>\n' +
191
191
  '</event>';
192
192
 
193
- t.deepEquals(XML.xml2js(geochat), {
193
+ t.deepEquals((new XML(geochat)).raw, {
194
194
  'event': {
195
195
  '_attributes': {
196
196
  'version': '2.0',
@@ -1,14 +0,0 @@
1
- syntax = "proto3";
2
- option optimize_for = LITE_RUNTIME;
3
-
4
- package atakmap.commoncommo.protobuf.v1;
5
-
6
- // All items are required unless otherwise noted!
7
- // "required" means if they are missing on send, the conversion
8
- // to the message format will be rejected and fall back to opaque
9
- // XML representation
10
- message Contact {
11
- // Endpoint is optional; if missing/empty do not populate.
12
- string endpoint = 1; // endpoint=
13
- string callsign = 2; // callsign=
14
- }
@@ -1,45 +0,0 @@
1
-
2
- syntax = "proto3";
3
-
4
- option optimize_for = LITE_RUNTIME;
5
-
6
- package atakmap.commoncommo.protobuf.v1;
7
-
8
- import "detail.proto";
9
-
10
- // A note about timestamps:
11
- // Uses "timeMs" units, which is number of milliseconds since
12
- // 1970-01-01 00:00:00 UTC
13
- //
14
- // All items are required unless otherwise noted!
15
- // "required" means if they are missing in the XML during outbound
16
- // conversion to protobuf, the message will be
17
- // rejected
18
- message CotEvent {
19
- // <event>
20
-
21
- string type = 1; // <event type="x">
22
-
23
- string access = 2; // optional
24
- string qos = 3; // optional
25
- string opex = 4; // optional
26
-
27
- string uid = 5; // <event uid="x">
28
- uint64 sendTime = 6; // <event time="x"> converted to timeMs
29
- uint64 startTime = 7; // <event start="x"> converted to timeMs
30
- uint64 staleTime = 8; // <event stale="x"> converted to timeMs
31
- string how = 9; // <event how="x">
32
-
33
- // <point>
34
- double lat = 10; // <point lat="x">
35
- double lon = 11; // <point lon="x">
36
- double hae = 12; // <point hae="x"> use 999999 for unknown
37
- double ce = 13; // <point ce="x"> use 999999 for unknown
38
- double le = 14; // <point ce="x"> use 999999 for unknown
39
-
40
- // comprises children of <detail>
41
- // This is optional - if omitted, then the cot message
42
- // had no data under <detail>
43
- Detail detail = 15;
44
- }
45
-
@@ -1,78 +0,0 @@
1
- syntax = "proto3";
2
- option optimize_for = LITE_RUNTIME;
3
-
4
- package atakmap.commoncommo.protobuf.v1;
5
-
6
- import "contact.proto";
7
- import "group.proto";
8
- import "precisionlocation.proto";
9
- import "status.proto";
10
- import "takv.proto";
11
- import "track.proto";
12
-
13
- // CotEvent detail
14
- // The strong typed message fields are optional. If used, they *MUST* adhere
15
- // to the requirements of the message (see their proto file) and
16
- // their XML source element used to populate the message MUST
17
- // be omitted from the xmlDetail.
18
- // WHOLE ELEMENTS MUST BE CONVERTED TO MESSAGES. Do not try to
19
- // put part of the data from a given element into one of the messages
20
- // and put other parts of the data in an element of xmlDetail! This applies
21
- // especially if you add new things to the XML representation which do not
22
- // have a place in the equivalent protobuf message. Instead, omit the
23
- // message and put the entire element in xmlDetail!
24
- //
25
- // xmlDetail is optional. If omitted, all Detail data has been
26
- // converted to the strongly typed message fields.
27
- // If present, this contains any remaining detail data that has NOT been
28
- // included in one of the strongly typed message fields. To process the
29
- // xmlDetail, the following rules MUST be followed:
30
- // Senders of this message MUST:
31
- // 1. Remove child elements used to populate the other message
32
- // fields. If the same child element appears more times than an
33
- // associated message field(s) is intended to encompass, or if any
34
- // error occurs mapping to the message equivalent, do not remove
35
- // the element(s) in question and do not populate the message
36
- // equivalent.
37
- // 2. If no data under <detail> remains, STOP - do not populate
38
- // xmlDetail
39
- // 3. Serialize the remaining XML tree under <detail>....</detail>
40
- // as XML in UTF-8 encoding
41
- // 4. Remove the <detail> and </detail> element tags
42
- // 5. Remove the XML header
43
- // 6. Place the result in xmlDetail
44
- // Receivers of this message MUST do the equivalent of the following:
45
- // 1. If the field is not present (zero length), stop - do nothing
46
- // 2. Prepend <detail> and append </detail>
47
- // 3. Prepend an XML header for UTF-8 encoding, version 1.0
48
- // (<?xml version="1.0" encoding="UTF-8"?> or similar)
49
- // 4. Read the result, expecting a valid XML document with a document
50
- // root of <detail>
51
- // 5. Merge in XML equivalents of each of the strongly typed
52
- // messages present in this Detail message.
53
- // In the event that a sending application does not follow
54
- // sending rule #1 above properly and data for the same element
55
- // appears in xmlDetail, the data in xmlDetail should be left alone
56
- // and the data in the equivalent message should ignored.
57
-
58
- message Detail {
59
- string xmlDetail = 1;
60
-
61
- // <contact>
62
- Contact contact = 2;
63
-
64
- // <__group>
65
- Group group = 3;
66
-
67
- // <precisionlocation>
68
- PrecisionLocation precisionLocation = 4;
69
-
70
- // <status>
71
- Status status = 5;
72
-
73
- // <takv>
74
- Takv takv = 6;
75
-
76
- // <track>
77
- Track track = 7;
78
- }
@@ -1,13 +0,0 @@
1
- syntax = "proto3";
2
- option optimize_for = LITE_RUNTIME;
3
-
4
- package atakmap.commoncommo.protobuf.v1;
5
-
6
- // All items are required unless otherwise noted!
7
- // "required" means if they are missing on send, the conversion
8
- // to the message format will be rejected and fall back to opaque
9
- // XML representation
10
- message Group {
11
- string name = 1; // name=
12
- string role = 2; // role=
13
- }
@@ -1,13 +0,0 @@
1
- syntax = "proto3";
2
- option optimize_for = LITE_RUNTIME;
3
-
4
- package atakmap.commoncommo.protobuf.v1;
5
-
6
- // All items are required unless otherwise noted!
7
- // "required" means if they are missing on send, the conversion
8
- // to the message format will be rejected and fall back to opaque
9
- // XML representation
10
- message PrecisionLocation {
11
- string geopointsrc = 1; // geopointsrc=
12
- string altsrc = 2; // altsrc=
13
- }
@@ -1,12 +0,0 @@
1
- syntax = "proto3";
2
- option optimize_for = LITE_RUNTIME;
3
-
4
- package atakmap.commoncommo.protobuf.v1;
5
-
6
- // All items are required unless otherwise noted!
7
- // "required" means if they are missing on send, the conversion
8
- // to the message format will be rejected and fall back to opaque
9
- // XML representation
10
- message Status {
11
- uint32 battery = 1; // battery=
12
- }
@@ -1,24 +0,0 @@
1
- syntax = "proto3";
2
- option optimize_for = LITE_RUNTIME;
3
-
4
- package atakmap.commoncommo.protobuf.v1;
5
-
6
- // TAK Protocol control message
7
- // This specifies to a recipient what versions
8
- // of protocol elements this sender supports during
9
- // decoding.
10
- message TakControl {
11
- // Lowest TAK protocol version supported
12
- // If not filled in (reads as 0), version 1 is assumed
13
- uint32 minProtoVersion = 1;
14
-
15
- // Highest TAK protocol version supported
16
- // If not filled in (reads as 0), version 1 is assumed
17
- uint32 maxProtoVersion = 2;
18
-
19
- // UID of the sending contact. May be omitted if
20
- // this message is paired in a TakMessage with a CotEvent
21
- // and the CotEvent contains this information
22
- string contactUid = 3;
23
- }
24
-
@@ -1,18 +0,0 @@
1
- syntax = "proto3";
2
- option optimize_for = LITE_RUNTIME;
3
-
4
- import "cotevent.proto";
5
- import "takcontrol.proto";
6
-
7
- package atakmap.commoncommo.protobuf.v1;
8
-
9
- // Top level message sent for TAK Messaging Protocol Version 1.
10
- message TakMessage {
11
- // Optional - if omitted, continue using last reported control
12
- // information
13
- TakControl takControl = 1;
14
-
15
- // Optional - if omitted, no event data in this message
16
- CotEvent cotEvent = 2;
17
- }
18
-
package/assets/takv.proto DELETED
@@ -1,15 +0,0 @@
1
- syntax = "proto3";
2
- option optimize_for = LITE_RUNTIME;
3
-
4
- package atakmap.commoncommo.protobuf.v1;
5
-
6
- // All items are required unless otherwise noted!
7
- // "required" means if they are missing on send, the conversion
8
- // to the message format will be rejected and fall back to opaque
9
- // XML representation
10
- message Takv {
11
- string device = 1; // device=
12
- string platform = 2; // platform=
13
- string os = 3; // os=
14
- string version = 4; // version=
15
- }
@@ -1,13 +0,0 @@
1
- syntax = "proto3";
2
- option optimize_for = LITE_RUNTIME;
3
-
4
- package atakmap.commoncommo.protobuf.v1;
5
-
6
- // All items are required unless otherwise noted!
7
- // "required" means if they are missing on send, the conversion
8
- // to the message format will be rejected and fall back to opaque
9
- // XML representation
10
- message Track {
11
- double speed = 1; // speed=
12
- double course = 2; // course=
13
- }
package/src/proto.js DELETED
@@ -1,110 +0,0 @@
1
- import path from 'path';
2
- import protobuf from 'protobufjs';
3
- import xmljs from 'xml-js';
4
-
5
- //const root = protobuf.loadSync(new URL('../assets/takmessage.proto', import.meta.url));
6
- //const TakMessage = root.lookupType('atakmap.commoncommo.protobuf.v1.TakMessage');
7
-
8
- export default class Proto {
9
- static proto2js(message) {
10
- if (typeof message === 'undefined' || message === null) {
11
- throw new Error('Attempted to parse empty TAK proto message');
12
- }
13
-
14
- if (typeof message !== Buffer) {
15
- message = Buffer.from(message, 'hex');
16
- }
17
-
18
- const result = TakMessage.decode(message);
19
- return TakMessage.toObject(result, {
20
- longs: String
21
- }); // or decode
22
- }
23
-
24
- static protojs2cotjs(proto) {
25
- if (!proto.cotEvent) return null;
26
-
27
- const cot = {
28
- 'event': {
29
- '_attributes': {
30
- 'version': '2.0',
31
- 'uid': proto.cotEvent.uid,
32
- 'type': proto.cotEvent.type,
33
- 'time': new Date(parseInt(proto.cotEvent.sendTime)).toISOString(),
34
- 'start': new Date(parseInt(proto.cotEvent.startTime)).toISOString(),
35
- 'stale': new Date(parseInt(proto.cotEvent.staleTime)).toISOString(),
36
- 'how': proto.cotEvent.how
37
- },
38
- 'point': {
39
- '_attributes': {
40
- 'lat': proto.cotEvent.lat,
41
- 'lon': proto.cotEvent.lon,
42
- 'hae': proto.cotEvent.hae,
43
- 'ce': proto.cotEvent.ce,
44
- 'le': proto.cotEvent.le
45
- }
46
- }
47
- }
48
- };
49
- if (proto.cotEvent.detail) {
50
- cot.event.detail = {};
51
- if (proto.cotEvent.detail.takv) {
52
- cot.event.detail.takv = {
53
- '_attributes': {
54
- 'os': proto.cotEvent.detail.takv.os,
55
- 'version': proto.cotEvent.detail.takv.version,
56
- 'device': proto.cotEvent.detail.takv.device,
57
- 'platform': proto.cotEvent.detail.takv.platform
58
- }
59
- };
60
- }
61
- if (proto.cotEvent.detail.contact) {
62
- cot.event.detail.contact = {
63
- '_attributes': {
64
- 'endpoint': proto.cotEvent.detail.contact.endpoint,
65
- 'callsign': proto.cotEvent.detail.contact.callsign
66
- }
67
- };
68
- // todo add phone
69
- }
70
- if (proto.cotEvent.detail.xmlDetail) {
71
- const result = xmljs.xml2js(proto.cotEvent.detail.xmlDetail, { compact: true });
72
- cot.event.detail = {
73
- ...cot.event.detail,
74
- ...result
75
- };
76
- }
77
- if (proto.cotEvent.detail.group) {
78
- cot.event.detail.__group = {
79
- '_attributes': {
80
- 'role': proto.cotEvent.detail.group.role,
81
- 'name': proto.cotEvent.detail.group.name
82
- }
83
- };
84
- }
85
- if (proto.cotEvent.detail.status) {
86
- cot.event.detail.status = {
87
- '_attributes': {
88
- 'battery': proto.cotEvent.detail.status.battery
89
- }
90
- };
91
- }
92
- /*
93
- "precisionlocation": {
94
- "_attributes": {
95
- "altsrc": "GPS",
96
- "geopointsrc": "GPS"
97
- }
98
- },
99
-
100
- "track": {
101
- "_attributes": {
102
- "course": "228.71039582198793",
103
- "speed": "0.0"
104
- }
105
- }
106
- }*/
107
- }
108
- return cot;
109
- }
110
- }