diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0c04304..befdf12 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,9 @@ jobs: - name: Install run: npm install + - name: Build + run: npm run build + - name: Lint run: npm run lint diff --git a/CHANGELOG.md b/CHANGELOG.md index ffd9276..55265eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,34 @@ ## Version History +### v5.0.5 + +- :bug: Object Defs + +### v5.0.4 + +- :bug: Optional `_flow-tags_._attributes` + +### v5.0.3 + +- :bug: `remarks._text` is optional + +### v5.0.2 + +- :bug: `remarks._attributes` is optional + +### v5.0.1 + +- :bug: Add `package.json` in `dist/` + +### v5.0.0 + +- :tada: `schema.json` is now automatically generated from Type Defs +- :tada: Add Chat interface for easily creating Direct Messages +- :rocket: Add Flow Tag when parsed by NodeCoT +- :arrow_up: Update Typescript +- :white_check_mark: Add Chat & Flow tests + ### v4.6.0 - :rocket: Decode `__group` to `.properties.group` diff --git a/index.ts b/index.ts index dd04159..65d3440 100644 --- a/index.ts +++ b/index.ts @@ -1,3 +1,4 @@ import CoT from './lib/cot.js'; +export * from './lib/chat.js' export default CoT; diff --git a/lib/chat.ts b/lib/chat.ts new file mode 100644 index 0000000..02be17b --- /dev/null +++ b/lib/chat.ts @@ -0,0 +1,73 @@ +import Util from './util.js' +import CoT from './cot.js'; +import JSONCoT from './types.js'; +import { randomUUID } from 'node:crypto'; + +export type DirectChatMember = { + uid: string; + callsign: string; +} + +export type DirectChatInput = { + to: DirectChatMember; + from: DirectChatMember; + + message: string; + + parent?: string; + chatroom?: string; + groupOwner?: boolean; + messageId?: string; + id?: string; +} + +export class DirectChat extends CoT { + constructor(chat: DirectChatInput) { + const cot: JSONCoT = { + event: { + _attributes: Util.cot_event_attr('b-t-f', 'h-g-i-g-o'), + point: Util.cot_point(), + detail: { + __chat: { + _attributes: { + parent: chat.parent || 'RootContactGroup', + groupOwner: chat.groupOwner ? 'true' : 'false', + messageId: chat.messageId || randomUUID(), + chatroom: chat.chatroom || chat.to.callsign, + id: chat.to.uid, + senderCallsign: chat.from.callsign + }, + chatgrp: { + _attributes: { + uid0: chat.from.uid, + uid1: chat.to.uid, + id: chat.to.uid + } + } + }, + } + } + } + + cot.event._attributes.uid = `GeoChat.${chat.from.uid}.${chat.to.uid}.${randomUUID()}`; + + cot.event.detail.link = { + _attributes: { + uid: chat.from.uid, + type: 'a-f-G', + relation: 'p-p' + } + } + + cot.event.detail.remarks = { + _attributes: { + source: chat.from.uid, + to: chat.to.uid, + time: cot.event._attributes.time + }, + _text: chat.message + } + + super(cot) + } +} diff --git a/lib/cot.ts b/lib/cot.ts index d95bb19..e1741bd 100644 --- a/lib/cot.ts +++ b/lib/cot.ts @@ -4,74 +4,18 @@ import { AllGeoJSON } from "@turf/helpers"; import Util from './util.js'; import Color from './color.js'; import PointOnFeature from '@turf/point-on-feature'; +import JSONCoT from './types.js' import AJV from 'ajv'; import fs from 'fs'; const schema = JSON.parse(String(fs.readFileSync(new URL('./schema.json', import.meta.url)))); -const ajv = (new AJV({ allErrors: true })) - .compile(schema); - -export interface Attributes { - version: string, - uid: string; - type: string; - how: string; - [k: string]: string; -} - -export interface GenericAttributes { - _attributes: { - [k: string]: string; - } -} - -export interface Track { - _attributes: TrackAttributes; -} - -export interface TrackAttributes { - speed: string, - course: string, - [k: string]: string -} - -export interface Detail { - contact?: GenericAttributes, - tog?: GenericAttributes, - strokeColor?: GenericAttributes, - strokeWeight?: GenericAttributes, - strokeStyle?: GenericAttributes, - labels_on?: GenericAttributes, - fillColor?: GenericAttributes, - link?: GenericAttributes[], - usericon?: GenericAttributes, - track?: Track, - TakControl?: { - TakServerVersionInfo?: GenericAttributes - }, - [k: string]: unknown -} +const pkg = JSON.parse(String(fs.readFileSync(new URL('../package.json', import.meta.url)))); -export interface Point { - _attributes: { - lat: string | number; - lon: string | number; - hae: string | number; - ce: string | number; - le: string | number; - [k: string]: string | number - } -} - -export interface JSONCoT { - event: { - _attributes: Attributes, - detail: Detail, - point: Point, - [k: string]: unknown - }, - [k: string]: unknown -} +const ajv = (new AJV({ + allErrors: true, + allowUnionTypes: true +})) + .compile(schema); /** * Convert to and from an XML CoT message @@ -98,6 +42,9 @@ export default class CoT { ajv(this.raw); if (ajv.errors) throw new Error(`${ajv.errors[0].message} (${ajv.errors[0].instancePath})`); + + if (!this.raw.event.detail['_flow-tags_']) this.raw.event.detail['_flow-tags_'] = {}; + this.raw.event.detail['_flow-tags_'][`NodeCoT-${pkg.version}`] = new Date().toISOString() } /** @@ -377,6 +324,15 @@ export default class CoT { }); } + /** + * Determines if the CoT message represents a Chat Message + * + * @return {boolean} + */ + is_chat(): boolean { + return !!(this.raw.event.detail && this.raw.event.detail.__chat); + } + /** * Determines if the CoT message represents a Friendly Element * diff --git a/lib/schema.json b/lib/schema.json index 9d1c78b..6919190 100644 --- a/lib/schema.json +++ b/lib/schema.json @@ -1,261 +1,508 @@ { - "type": "object", - "required": [ - "event" - ], - "properties": { - "event": { - "type": "object", - "required": [ - "_attributes", - "point" - ], + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": {}, + "definitions": { + "Chat": { "properties": { "_attributes": { - "type": "object", - "required": [ - "version", - "uid", - "type", - "time", - "start", - "stale" - ], + "additionalProperties": { + "type": "string" + }, "properties": { - "version": { + "chatroom": { "type": "string" }, - "uid": { + "groupOwner": { "type": "string" }, - "type": { + "id": { "type": "string" }, - "time": { + "messageId": { "type": "string" }, - "start": { + "parent": { "type": "string" }, - "stale": { + "senderCallsign": { "type": "string" } - } + }, + "required": [ + "chatroom", + "groupOwner", + "id", + "messageId", + "parent", + "senderCallsign" + ], + "type": "object" }, - "detail": { - "type": "object", + "chatgrp": { + "$ref": "#/definitions/GenericAttributes" + } + }, + "required": [ + "_attributes", + "chatgrp" + ], + "type": "object" + }, + "Contact": { + "properties": { + "_attributes": { + "additionalProperties": { + "type": "string" + }, "properties": { - "_flow-tags_": { - "type": "object" + "callsign": { + "type": "string" }, - "__group": { - "type": "object", - "properties": { - "_attributes": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "role": { - "type": "string" - } - } - } - } + "endpoint": { + "type": "string" }, - "takv": { - "type": "object", - "properties": { - "_attributes": { - "type": "object", - "properties": { - "device": { - "type": "string" - }, - "platform": { - "type": "string" - }, - "os": { - "type": "string" - }, - "version": { - "type": "string" - } - } - } - } + "phone": { + "type": "string" + } + }, + "required": [ + "callsign" + ], + "type": "object" + } + }, + "required": [ + "_attributes" + ], + "type": "object" + }, + "Detail": { + "additionalProperties": {}, + "properties": { + "TakControl": { + "properties": { + "TakServerVersionInfo": { + "$ref": "#/definitions/GenericAttributes" + } + }, + "type": "object" + }, + "__chat": { + "$ref": "#/definitions/Chat" + }, + "__group": { + "$ref": "#/definitions/Group" + }, + "_flow-tags_": { + "$ref": "#/definitions/FlowTags" + }, + "contact": { + "$ref": "#/definitions/Contact" + }, + "fillColor": { + "$ref": "#/definitions/GenericAttributes" + }, + "labels_on": { + "$ref": "#/definitions/GenericAttributes" + }, + "link": { + "anyOf": [ + { + "$ref": "#/definitions/GenericAttributes" }, - "status": { - "type": "object", - "properties": { - "_attributes": { - "type": "object", - "properties": { - "battery": { - "type": "string" - } - } - } - } + { + "items": { + "$ref": "#/definitions/GenericAttributes" + }, + "type": "array" + } + ] + }, + "precisionlocation": { + "$ref": "#/definitions/PrecisionLocation" + }, + "remarks": { + "$ref": "#/definitions/Remarks" + }, + "status": { + "$ref": "#/definitions/Status" + }, + "strokeColor": { + "$ref": "#/definitions/GenericAttributes" + }, + "strokeStyle": { + "$ref": "#/definitions/GenericAttributes" + }, + "strokeWeight": { + "$ref": "#/definitions/GenericAttributes" + }, + "takv": { + "$ref": "#/definitions/TakVersion" + }, + "tog": { + "$ref": "#/definitions/GenericAttributes" + }, + "track": { + "$ref": "#/definitions/Track" + }, + "uid": { + "$ref": "#/definitions/Uid" + }, + "usericon": { + "$ref": "#/definitions/UserIcon" + } + }, + "type": "object" + }, + "EventAttributes": { + "additionalProperties": { + "type": "string" + }, + "properties": { + "how": { + "type": "string" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "how", + "type", + "uid", + "version" + ], + "type": "object" + }, + "FlowTags": { + "additionalProperties": { + "anyOf": [ + { + "additionalProperties": true, + "properties": {}, + "type": "object" + }, + { + "type": "string" + } + ] + }, + "properties": { + "_attributes": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "GenericAttributes": { + "properties": { + "_attributes": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "required": [ + "_attributes" + ], + "type": "object" + }, + "Group": { + "properties": { + "_attributes": { + "additionalProperties": { + "type": "string" + }, + "properties": { + "name": { + "type": "string" + }, + "role": { + "type": "string" + } + }, + "required": [ + "name", + "role" + ], + "type": "object" + } + }, + "required": [ + "_attributes" + ], + "type": "object" + }, + "Point": { + "properties": { + "_attributes": { + "additionalProperties": { + "type": [ + "string", + "number" + ] + }, + "properties": { + "ce": { + "type": [ + "string", + "number" + ] + }, + "hae": { + "type": [ + "string", + "number" + ] + }, + "lat": { + "type": [ + "string", + "number" + ] + }, + "le": { + "type": [ + "string", + "number" + ] + }, + "lon": { + "type": [ + "string", + "number" + ] + } + }, + "required": [ + "ce", + "hae", + "lat", + "le", + "lon" + ], + "type": "object" + } + }, + "required": [ + "_attributes" + ], + "type": "object" + }, + "PrecisionLocation": { + "properties": { + "_attributes": { + "additionalProperties": { + "type": "string" + }, + "properties": { + "altsrc": { + "type": "string" }, - "uid": { - "type": "object", - "properties": { - "_attributes": { - "type": "object", - "properties": { - "Droid": { - "type": "string" - } - } - } - } + "geopointsrc": { + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "_attributes" + ], + "type": "object" + }, + "Remarks": { + "properties": { + "_attributes": { + "additionalProperties": { + "type": "string" + }, + "properties": { + "source": { + "type": "string" }, - "contact": { - "type": "object", - "properties": { - "_attributes": { - "type": "object", - "properties": { - "phone": { - "type": "string" - }, - "callsign": { - "type": "string" - }, - "endpoint": { - "type": "string" - } - } - } - } + "time": { + "type": "string" }, - "precisionlocation": { - "type": "object", - "properties": { - "_attributes": { - "type": "object", - "properties": { - "geopointsrc": { - "type": "string" - }, - "altsrc": { - "type": "string" - } - } - } - } + "to": { + "type": "string" + } + }, + "type": "object" + }, + "_text": { + "type": "string" + } + }, + "type": "object" + }, + "Status": { + "properties": { + "_attributes": { + "additionalProperties": { + "type": "string" + }, + "properties": { + "battery": { + "type": "string" + } + }, + "required": [ + "battery" + ], + "type": "object" + } + }, + "required": [ + "_attributes" + ], + "type": "object" + }, + "TakVersion": { + "properties": { + "_attributes": { + "additionalProperties": { + "type": "string" + }, + "properties": { + "device": { + "type": "string" }, - "remarks": { - "type": "object", - "properties": { - "_attributes": { - "type": "object" - }, - "_text": { - "type": "string" - } - } + "os": { + "type": "string" }, - "track": { - "type": "object", - "required": [ - "_attributes" - ], - "properties": { - "_attributes": { - "type": "object", - "required": [ - "course", - "speed" - ], - "properties": { - "course": { - "type": "string", - "description": "Speed measured in degrees from north" - }, - "speed": { - "type": "string", - "description": "Speed measured in meters/second" - }, - "slope": { - "type": "string", - "description": "Vertical component of motion vector. Measured in degrees. Negative indicates downward motion." - }, - "eCourse": { - "type": "string", - "description": "1-sigma error on a Gaussian distribution associated with the course attribute" - }, - "eSpeed": { - "type": "string", - "description": "1-sigma error on a Gaussian distribution associated with the speed attribute" - }, - "eSlope": { - "type": "string", - "description": "1-sigma error on a Gaussian distribution associated with the slope attribute" - } - } - } - } + "platform": { + "type": "string" }, - "usericon": { - "type": "object", - "required": [ - "_attributes" - ], - "properties": { - "_attributes": { - "type": "object", - "required": [ - "iconsetpath" - ], - "properties": { - "iconsetpath": { - "type": "string" - } - } - } - } + "version": { + "type": "string" } - } + }, + "type": "object" + } + }, + "required": [ + "_attributes" + ], + "type": "object" + }, + "Track": { + "properties": { + "_attributes": { + "$ref": "#/definitions/TrackAttributes" + } + }, + "required": [ + "_attributes" + ], + "type": "object" + }, + "TrackAttributes": { + "additionalProperties": { + "type": "string" + }, + "properties": { + "course": { + "type": "string" }, - "point": { - "type": "object", + "eCourse": { + "type": "string" + }, + "eSlope": { + "type": "string" + }, + "eSpeed": { + "type": "string" + }, + "slope": { + "type": "string" + }, + "speed": { + "type": "string" + } + }, + "type": "object" + }, + "Uid": { + "properties": { + "_attributes": { + "additionalProperties": { + "type": "string" + }, + "properties": { + "Droid": { + "type": "string" + } + }, "required": [ - "_attributes" + "Droid" ], + "type": "object" + } + }, + "required": [ + "_attributes" + ], + "type": "object" + }, + "UserIcon": { + "properties": { + "_attributes": { + "additionalProperties": { + "type": "string" + }, "properties": { - "_attributes": { - "type": "object", - "required": [ - "lat", - "lon", - "hae", - "ce", - "le" - ], - "properties": { - "lat": { - "type": "string" - }, - "lon": { - "type": "string" - }, - "hae": { - "type": "string" - }, - "ce": { - "type": "string" - }, - "le": { - "type": "string" - } - } + "iconsetpath": { + "type": "string" } - } + }, + "required": [ + "iconsetpath" + ], + "type": "object" + } + }, + "required": [ + "_attributes" + ], + "type": "object" + } + }, + "properties": { + "event": { + "additionalProperties": {}, + "properties": { + "_attributes": { + "$ref": "#/definitions/EventAttributes" + }, + "detail": { + "$ref": "#/definitions/Detail" + }, + "point": { + "$ref": "#/definitions/Point" } - } + }, + "required": [ + "_attributes", + "detail", + "point" + ], + "type": "object" } - } + }, + "required": [ + "event" + ], + "type": "object" } + diff --git a/lib/types.ts b/lib/types.ts new file mode 100644 index 0000000..284e238 --- /dev/null +++ b/lib/types.ts @@ -0,0 +1,160 @@ +export interface EventAttributes { + version: string, + uid: string; + type: string; + how: string; + [k: string]: string; +} + +export interface GenericAttributes { + _attributes: { + [k: string]: string; + } +} + +export interface Track { + _attributes: TrackAttributes; +} + +export interface Chat { + _attributes: { + parent: string; + groupOwner: string; + messageId: string; + chatroom: string; + id: string; + senderCallsign: string; + [k: string]: string; + } + + chatgrp: GenericAttributes +} + +export interface TrackAttributes { + speed?: string, + course?: string, + slope?: string; + eCourse?: string; + eSpeed?: string; + eSlope?: string + [k: string]: string | undefined +} + +export interface TakVersion { + _attributes: { + device?: string; + platform?: string; + os?: string; + version?: string; + [k: string]: string | undefined + } +} + +export interface FlowTags { + _attributes?: { + [k: string]: string + } + [k: string]: string | undefined | object; +} + +export interface Group { + _attributes: { + name: string; + role: string; + [k: string]: string; + } +} + +export interface Status { + _attributes: { + battery: string; + [k: string]: string; + } +} + +export interface Uid { + _attributes: { + Droid: string; + [k: string]: string; + } +} + +export interface Contact { + _attributes: { + phone?: string; + callsign: string; + endpoint?: string; + [k: string]: string | undefined; + } +} + +export interface Remarks { + _attributes?: { + source?: string; + to?: string; + time?: string; + [k: string]: string | undefined; + } + _text?: string; +} + +export interface PrecisionLocation { + _attributes: { + geopointsrc?: string; + altsrc?: string; + [k: string]: string | undefined; + } +} + +export interface UserIcon { + _attributes: { + iconsetpath: string; + [k: string]: string; + } +} + +export interface Detail { + contact?: Contact; + tog?: GenericAttributes; + '__group'?: Group; + '__chat'?: Chat; + '_flow-tags_'?: FlowTags; + uid?: Uid; + status?: Status; + remarks?: Remarks; + precisionlocation?: PrecisionLocation; + strokeColor?: GenericAttributes; + strokeWeight?: GenericAttributes; + strokeStyle?: GenericAttributes; + labels_on?: GenericAttributes; + fillColor?: GenericAttributes; + link?: GenericAttributes | GenericAttributes[]; + usericon?: UserIcon; + track?: Track; + takv?: TakVersion; + TakControl?: { + TakServerVersionInfo?: GenericAttributes + }; + [k: string]: unknown; +} + +export interface Point { + _attributes: { + lat: string | number; + lon: string | number; + hae: string | number; + ce: string | number; + le: string | number; + [k: string]: string | number + } +} + +export default interface JSONCoT { + event: { + _attributes: EventAttributes, + detail: Detail, + point: Point, + [k: string]: unknown + }, + [k: string]: unknown +} diff --git a/lib/util.ts b/lib/util.ts index 34a8864..bc130ed 100644 --- a/lib/util.ts +++ b/lib/util.ts @@ -1,10 +1,10 @@ import { randomUUID } from 'crypto' import type { - Attributes, + EventAttributes, TrackAttributes, Detail, Point -} from './cot.js'; +} from './types.js'; /** * Helper functions for generating CoT data @@ -26,7 +26,7 @@ export default class Util { time?: Date | string | null, start?: Date | string | null, stale?: Date | string | number | null - ): Attributes { + ): EventAttributes { if (!type) throw new Error('type param required'); if (!how) throw new Error('how param required'); diff --git a/package-lock.json b/package-lock.json index ad7ccff..ffa0ca9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@tak-ps/node-cot", - "version": "4.5.1", + "version": "5.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tak-ps/node-cot", - "version": "4.5.1", + "version": "5.0.4", "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", @@ -29,7 +29,8 @@ "ts-node": "^10.9.1", "ts-node-test": "^0.4.0", "typedoc": "^0.25.0", - "typescript": "^5.0.0" + "typescript": "^5.0.0", + "typescript-json-schema": "^0.62.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -154,13 +155,13 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -203,9 +204,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "node_modules/@jridgewell/resolve-uri": { @@ -533,9 +534,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.10.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz", - "integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==", + "version": "20.11.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.5.tgz", + "integrity": "sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==", "dependencies": { "undici-types": "~5.26.4" } @@ -566,16 +567,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz", - "integrity": "sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.1.tgz", + "integrity": "sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.17.0", - "@typescript-eslint/type-utils": "6.17.0", - "@typescript-eslint/utils": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/type-utils": "6.19.1", + "@typescript-eslint/utils": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -601,15 +602,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.17.0.tgz", - "integrity": "sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.1.tgz", + "integrity": "sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.17.0", - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/typescript-estree": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", "debug": "^4.3.4" }, "engines": { @@ -629,13 +630,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz", - "integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.1.tgz", + "integrity": "sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0" + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -646,13 +647,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.17.0.tgz", - "integrity": "sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.1.tgz", + "integrity": "sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.17.0", - "@typescript-eslint/utils": "6.17.0", + "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/utils": "6.19.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -673,9 +674,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz", - "integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.1.tgz", + "integrity": "sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -686,13 +687,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz", - "integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.1.tgz", + "integrity": "sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -714,17 +715,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz", - "integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.1.tgz", + "integrity": "sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.17.0", - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/typescript-estree": "6.17.0", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/typescript-estree": "6.19.1", "semver": "^7.5.4" }, "engines": { @@ -739,12 +740,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz", - "integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.1.tgz", + "integrity": "sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/types": "6.19.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -783,9 +784,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", - "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, "engines": { "node": ">=0.4.0" @@ -1983,13 +1984,16 @@ } }, "node_modules/has-dynamic-import": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-dynamic-import/-/has-dynamic-import-2.0.1.tgz", - "integrity": "sha512-X3fbtsZmwb6W7fJGR9o7x65fZoodygCrZ3TVycvghP62yYQfS0t4RS0Qcz+j5tQYUKeSWS09tHkWW6WhFV3XhQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-dynamic-import/-/has-dynamic-import-2.1.0.tgz", + "integrity": "sha512-su0anMkNEnJKZ/rB99jn3y6lV/J8Ro96hBJ28YAeVzj5rWxH+YL/AdCyiYYA1HDLV9YhmvqpWSJJj2KLo1MX6g==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2475,9 +2479,9 @@ "dev": true }, "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, "node_modules/keyv": { @@ -2762,6 +2766,12 @@ "node": ">=6" } }, + "node_modules/path-equal": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/path-equal/-/path-equal-1.2.5.tgz", + "integrity": "sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g==", + "dev": true + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2826,9 +2836,9 @@ } }, "node_modules/protobufjs": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", - "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz", + "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==", "hasInstallScript": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", @@ -2997,13 +3007,13 @@ } }, "node_modules/safe-array-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", - "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", + "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -3015,19 +3025,31 @@ } }, "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz", + "integrity": "sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/sax": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", @@ -3049,15 +3071,16 @@ } }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", + "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", "dev": true, "dependencies": { "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.2", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -3261,16 +3284,16 @@ } }, "node_modules/tape": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/tape/-/tape-5.7.2.tgz", - "integrity": "sha512-cvSyprYahyOYXbtBwV/B7nrx7kINeZ3VZ9fKoSywoPwZN3oQ1WVLvt+Vl0XCz/gi37CDrY3dlW790nzviIzoPw==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/tape/-/tape-5.7.3.tgz", + "integrity": "sha512-un2/TkloCBIxbrLac88Z9WElQ85WLE4t+jjfgrmxrlInWGYQf9r9Wbycc/nNP3zap6GBb7qln7h85QoNBNZdHg==", "dev": true, "dependencies": { "@ljharb/resumer": "^0.0.1", "@ljharb/through": "^2.3.11", "array.prototype.every": "^1.1.5", "call-bind": "^1.0.5", - "deep-equal": "^2.2.2", + "deep-equal": "^2.2.3", "defined": "^1.0.1", "dotignore": "^0.1.2", "for-each": "^0.3.3", @@ -3281,11 +3304,11 @@ "inherits": "^2.0.4", "is-regex": "^1.1.4", "minimist": "^1.2.8", - "mock-property": "^1.0.2", + "mock-property": "^1.0.3", "object-inspect": "^1.13.1", "object-is": "^1.1.5", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", + "object.assign": "^4.1.5", "resolve": "^2.0.0-next.5", "string.prototype.trim": "^1.2.8" }, @@ -3538,9 +3561,9 @@ } }, "node_modules/typedoc": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.6.tgz", - "integrity": "sha512-1rdionQMpOkpA58qfym1J+YD+ukyA1IEIa4VZahQI2ZORez7dhOvEyUotQL/8rSoMBopdzOS+vAIsORpQO4cTA==", + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.7.tgz", + "integrity": "sha512-m6A6JjQRg39p2ZVRIN3NKXgrN8vzlHhOS+r9ymUYtcUP/TIQPvWSq7YgE5ZjASfv5Vd5BW5xrir6Gm2XNNcOow==", "dev": true, "dependencies": { "lunr": "^2.3.9", @@ -3571,6 +3594,44 @@ "node": ">=14.17" } }, + "node_modules/typescript-json-schema": { + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.62.0.tgz", + "integrity": "sha512-qRO6pCgyjKJ230QYdOxDRpdQrBeeino4v5p2rYmSD72Jf4rD3O+cJcROv46sQukm46CLWoeusqvBgKpynEv25g==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/node": "^16.9.2", + "glob": "^7.1.7", + "path-equal": "^1.2.5", + "safe-stable-stringify": "^2.2.0", + "ts-node": "^10.9.1", + "typescript": "~5.1.0", + "yargs": "^17.1.1" + }, + "bin": { + "typescript-json-schema": "bin/typescript-json-schema" + } + }, + "node_modules/typescript-json-schema/node_modules/@types/node": { + "version": "16.18.74", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.74.tgz", + "integrity": "sha512-eEn8RkzZFcT0gb8qyi0CcfSOQnLE+NbGLIIaxGGmjn/N35v/C3M8ohxcpSlNlCv+H8vPpMGmrGDdCkzr8xu2tQ==", + "dev": true + }, + "node_modules/typescript-json-schema/node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/package.json b/package.json index 6f1c73c..1969811 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@tak-ps/node-cot", "type": "module", - "version": "4.6.0", + "version": "5.0.5", "description": "Lightweight JavaScript library for parsing and manipulating TAK messages", "author": "Nick Ingalls ", "main": "dist/index.js", @@ -10,7 +10,7 @@ "test": "ts-node-test test/", "lint": "eslint *.ts lib/*.ts", "doc": "typedoc index.ts", - "build": "tsc --build", + "build": "typescript-json-schema --required --out ./lib/schema.json ./lib/types.ts JSONCoT && tsc --build && cp package.json dist/", "pretest": "npm run lint" }, "dependencies": { @@ -34,7 +34,8 @@ "ts-node": "^10.9.1", "ts-node-test": "^0.4.0", "typedoc": "^0.25.0", - "typescript": "^5.0.0" + "typescript": "^5.0.0", + "typescript-json-schema": "^0.62.0" }, "repository": { "type": "git", diff --git a/test/chat.test.ts b/test/chat.test.ts new file mode 100644 index 0000000..345a6f2 --- /dev/null +++ b/test/chat.test.ts @@ -0,0 +1,94 @@ +import test from 'tape'; +import { DirectChat } from '../index.js'; + +test('DirectChat - Basic', (t) => { + const cot = new DirectChat({ + to: { + uid: '123456', + callsign: 'Alpha Operator' + }, + from: { + uid: '654321', + callsign: 'Bravo Operator' + }, + message: 'Direct Message Test' + }); + + t.equals(cot.is_chat(), true); + + t.ok(cot.raw.event._attributes.uid); + cot.raw.event._attributes.uid = '123'; + + t.ok(cot.raw.event.detail['_flow-tags_']); + delete cot.raw.event.detail['_flow-tags_']; + + for (const i of ['time', 'start', 'stale']) { + t.equals(typeof cot.raw.event._attributes[i], 'string'); + delete cot.raw.event._attributes[i]; + } + + if (!cot.raw.event.detail.__chat) { + t.fail() + } else { + t.equals(typeof cot.raw.event.detail.__chat._attributes.messageId, 'string'); + cot.raw.event.detail.__chat._attributes.messageId = '123'; + } + + if (!cot.raw.event.detail.remarks || !cot.raw.event.detail.remarks._attributes) { + t.fail() + } else { + t.equals(typeof cot.raw.event.detail.remarks._attributes.time, 'string'); + cot.raw.event.detail.remarks._attributes.time = '123'; + } + +console.error(JSON.stringify(cot.raw)); + t.deepEquals(cot.raw, { + event: { + _attributes: { + uid: '123', + version: '2.0', + type: 'b-t-f', + how: 'h-g-i-g-o', + }, + point: { + _attributes: { lat: '0.000000', lon: '0.000000', hae: '0.0', ce: '9999999.0', le: '9999999.0' } + }, + detail: { + __chat: { + _attributes: { + parent: 'RootContactGroup', + groupOwner: 'false', + messageId: '123', + chatroom: 'Alpha Operator', + id: '123456', + senderCallsign: 'Bravo Operator' + }, + chatgrp: { + _attributes: { + uid0: "654321", + uid1:"123456", + id:"123456" + } + } + }, + link: { + _attributes: { + uid: '654321', + type: 'a-f-G', + relation: 'p-p' + } + }, + remarks: { + _attributes: { + source: '654321', + to: '123456', + time: '123' + }, + _text: 'Direct Message Test' + } + } + } + }); + + t.end(); +}); diff --git a/test/cot-itak.test.ts b/test/cot-itak.test.ts index 3058969..8d79c02 100644 --- a/test/cot-itak.test.ts +++ b/test/cot-itak.test.ts @@ -4,7 +4,12 @@ import CoT from '../index.js'; test('Decode iTAK COT message', (t) => { const packet = '<__group name="Yellow" role="Team Member" />'; - t.deepEquals((new CoT(packet)).raw, { + const cot = new CoT(packet); + + t.ok(cot.raw.event.detail['_flow-tags_']); + delete cot.raw.event.detail['_flow-tags_']; + + t.deepEquals(cot.raw, { 'event': { '_attributes': { 'version': '2.0', @@ -42,7 +47,7 @@ test('Decode iTAK COT message', (t) => { } }); - t.deepEquals((new CoT(packet)).to_geojson(), { + t.deepEquals(cot.to_geojson(), { id: 'C94B9215-9BD4-4DBE-BDE1-83625F09153F', type: 'Feature', properties: { diff --git a/test/cot.test.ts b/test/cot.test.ts deleted file mode 100644 index d167a3c..0000000 --- a/test/cot.test.ts +++ /dev/null @@ -1,246 +0,0 @@ -import test from 'tape'; -import CoT from '../index.js'; - -test('Decode COT message', (t) => { - const packet = '<__group role="Team Member" name="Cyan"/>'; - - t.deepEquals((new CoT(packet)).raw, { - 'event': { - '_attributes': { - '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': { - '_attributes': { - 'lat': '1.234567', - 'lon': '-3.141592', - 'hae': '-25.7', - 'ce': '9.9', - 'le': '9999999.0' - } - }, - 'detail': { - 'takv': { - '_attributes': { - 'os': '29', - 'version': '4.0.0.0 (deadbeef).1234567890-CIV', - 'device': 'Some Android Device', - 'platform': 'ATAK-CIV' - } - }, - 'contact': { - '_attributes': { - 'xmppUsername': 'xmpp@host.com', - 'endpoint': '*:-1:stcp', - 'callsign': 'JENNY' - } - }, - 'uid': { '_attributes': { 'Droid': 'JENNY' } }, - 'precisionlocation': { '_attributes': { 'altsrc': 'GPS', 'geopointsrc': 'GPS' } }, - '__group': { '_attributes': { 'role': 'Team Member', 'name': 'Cyan' } }, - 'status': { '_attributes': { 'battery': '78' } }, - 'track': { '_attributes': { 'course': '80.24833892285461', 'speed': '0.0' } } - } - } - }); - - t.end(); -}); - -test('Decode COT message', (t) => { - const packet = '<__group role="Team Member" name="Cyan"/>'; - - t.deepEquals((new CoT(packet)).raw, { - 'event': { - '_attributes': { - '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': { - '_attributes': { - 'lat': '0.000000', - 'lon': '0.000000', - 'hae': '0.0', - 'ce': '9999999.0', - 'le': '9999999.0' - } - }, - 'detail': { - 'takv': { - '_attributes': { - 'os': 'Android', - 'version': '10', - 'device': 'Some Device', - 'platform': 'python unittest' - } - }, - 'status': { - '_attributes': { - 'battery': '83' - } - }, - 'uid': { - '_attributes': { - 'Droid': 'JENNY' - } - }, - 'contact': { - '_attributes': { - 'callsign': 'JENNY', - 'endpoint': '*:-1:stcp', - 'phone': '800-867-5309' - } - }, - '__group': { - '_attributes': { - 'role': 'Team Member', - 'name': 'Cyan' - } - }, - 'track': { - '_attributes': { - 'course': '90.1', - 'speed': '10.3' - } - } - } - } - }); - - t.end(); -}); - -test('Encode COT message', (t) => { - const packet = { - 'event': { - '_attributes': { - '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': { - '_attributes': { - 'lat': '1.234567', - 'lon': '-3.141592', - 'hae': '-25.7', - 'ce': '9.9', - 'le': '9999999' - } - }, - 'detail': { - 'takv': { - '_attributes': { - 'os': '29', - 'version': '4.0.0.0 (deadbeef).1234567890-CIV', - 'device': 'Some Android Device', - 'platform': 'ATAK-CIV' - } - }, - 'contact': { - '_attributes': { - 'xmppUsername': 'xmpp@host.com', - 'endpoint': '*:-1:stcp', - 'callsign': 'JENNY' - } - }, - 'uid': { '_attributes': { 'Droid': 'JENNY' } }, - 'precisionlocation': { '_attributes': { 'altsrc': 'GPS', 'geopointsrc': 'GPS' } }, - '__group': { '_attributes': { 'role': 'Team Member', 'name': 'Cyan' } }, - 'status': { '_attributes': { 'battery': '78' } }, - 'track': { '_attributes': { 'course': '80.24833892285461', 'speed': '0.0' } } - } - } - }; - - t.deepEquals( - (new CoT(packet)).to_xml(), - '<__group role="Team Member" name="Cyan"/>' - ); - - t.end(); -}); - -test('Parse GeoChat message', (t) => { - const geochat = '\n' + - ' \n' + - ' \n' + - ' <__chat parent="RootContactGroup" groupOwner="false" chatroom="JOKER MAN" id="ANDROID-cafebabe" senderCallsign="JENNY">\n' + - ' \n' + - ' \n' + - ' \n' + - ' test\n' + - ' <__serverdestination destinations="123.45.67.89:4242:tcp:ANDROID-deadbeef"/>\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ''; - - t.deepEquals((new CoT(geochat)).raw, { - 'event': { - '_attributes': { - 'version': '2.0', - 'uid': 'GeoChat.ANDROID-deadbeef.JOKER MAN.563040b9-2ac9-4af3-9e01-4cb2b05d98ea', - 'type': 'b-t-f', - 'how': 'h-g-i-g-o', - 'time': '2021-02-23T22:28:22.191Z', - 'start': '2021-02-23T22:28:22.191Z', - 'stale': '2021-02-24T22:28:22.191Z' - }, - 'point': { - '_attributes': { - 'lat': '1.234567', - 'lon': '-3.141592', - 'hae': '-25.8', - 'ce': '9.9', - 'le': '9999999.0' - } - }, - 'detail': { - '__chat': { - '_attributes': { - 'parent': 'RootContactGroup', - 'groupOwner': 'false', - 'chatroom': 'JOKER MAN', - 'id': 'ANDROID-cafebabe', - 'senderCallsign': 'JENNY' - }, - 'chatgrp': { - '_attributes': { - 'uid0': 'ANDROID-deadbeef', - 'uid1': 'ANDROID-cafebabe', - 'id': 'ANDROID-cafebabe' - } - } - }, - 'link': { '_attributes': { 'uid': 'ANDROID-deadbeef', 'type': 'a-f-G-U-C', 'relation': 'p-p' } }, - 'remarks': { - '_attributes': { - 'source': 'BAO.F.ATAK.ANDROID-deadbeef', - 'to': 'ANDROID-cafebabe', - 'time': '2021-02-23T22:28:22.191Z' - }, - '_text': 'test' - }, - '__serverdestination': { '_attributes': { 'destinations': '123.45.67.89:4242:tcp:ANDROID-deadbeef' } }, - 'marti': { 'dest': { '_attributes': { 'callsign': 'JOKER MAN' } } } - } - } - }); - - t.end(); -}); diff --git a/test/flow-tags.test.ts b/test/flow-tags.test.ts new file mode 100644 index 0000000..68a1a37 --- /dev/null +++ b/test/flow-tags.test.ts @@ -0,0 +1,21 @@ +import test from 'tape'; +import { DirectChat } from '../index.js'; +import fs from 'node:fs'; + +test('FlowTags - Basic', (t) => { + const pkg = JSON.parse(String(fs.readFileSync(new URL('../package.json', import.meta.url)))); + + const cot = new DirectChat({ + to: { uid: '123456', callsign: 'Alpha Operator' }, + from: { uid: '654321', callsign: 'Bravo Operator' }, + message: 'Direct Message Test' + }); + + if (!cot.raw.event.detail['_flow-tags_']) { + t.fail(); + } else { + t.equals(typeof cot.raw.event.detail['_flow-tags_'][`NodeCoT-${pkg.version}`], 'string'); + } + + t.end(); +}); diff --git a/test/from_geojson.test.ts b/test/from_geojson.test.ts index c21ae08..4d11670 100644 --- a/test/from_geojson.test.ts +++ b/test/from_geojson.test.ts @@ -23,6 +23,9 @@ test('CoT.from_geojson - Point', (t) => { _attributes: { lat: '2.2', lon: '1.1', hae: '0.0', ce: '9999999.0', le: '9999999.0' } }); + t.ok(geo.raw.event.detail['_flow-tags_']); + delete geo.raw.event.detail['_flow-tags_']; + t.deepEquals(geo.raw.event.detail, { contact: { _attributes: { callsign: 'UNKNOWN' } }, remarks: { _attributes: {}, _text: '' } @@ -59,6 +62,9 @@ test('CoT.from_geojson - Polygon', (t) => { _attributes: { lat: '39.065', lon: '-108.54599999999999', hae: '0.0', ce: '9999999.0', le: '9999999.0' } }); + t.ok(geo.raw.event.detail['_flow-tags_']); + delete geo.raw.event.detail['_flow-tags_']; + t.deepEquals(geo.raw.event.detail, { contact: { _attributes: { callsign: 'UNKNOWN' } }, link: [ @@ -108,6 +114,9 @@ test('CoT.from_geojson - LineString', (t) => { _attributes: { lat: '39.098', lon: '-108.505', hae: '0.0', ce: '9999999.0', le: '9999999.0' } }); + t.ok(geo.raw.event.detail['_flow-tags_']); + delete geo.raw.event.detail['_flow-tags_']; + t.deepEquals(geo.raw.event.detail, { contact: { _attributes: { callsign: 'UNKNOWN' } }, link: [ @@ -197,6 +206,9 @@ test('CoT.from_geojson - Icon', (t) => { } }); + t.ok(geo.raw.event.detail['_flow-tags_']); + delete geo.raw.event.detail['_flow-tags_']; + t.deepEquals(geo.raw.event.detail, { contact: { _attributes: { callsign: 'UNKNOWN' } }, usericon: { _attributes: { iconsetpath: '66f14976-4b62-4023-8edb-d8d2ebeaa336/Public Safety Air/EMS_ROTOR.png' } }, remarks: { _attributes: {}, _text: '' }