From 41a409afdd89b99f24e9dd62e384f6c37022d21a Mon Sep 17 00:00:00 2001 From: ingalls Date: Wed, 10 Apr 2024 07:09:32 -0600 Subject: [PATCH 1/5] Migrate Schema to Typebox to avoid codegen --- lib/chat.ts | 3 +- lib/cot.ts | 22 +- lib/schema.json | 545 ---------------------------------------------- lib/types.ts | 329 +++++++++++++--------------- lib/util.ts | 11 +- package-lock.json | 10 +- package.json | 6 +- 7 files changed, 185 insertions(+), 741 deletions(-) delete mode 100644 lib/schema.json diff --git a/lib/chat.ts b/lib/chat.ts index 20718fd..5d6bd10 100644 --- a/lib/chat.ts +++ b/lib/chat.ts @@ -1,6 +1,7 @@ import Util from './util.js' import CoT from './cot.js'; import JSONCoT from './types.js'; +import { Static } from '@sinclair/typebox'; import { randomUUID } from 'node:crypto'; export type DirectChatMember = { @@ -23,7 +24,7 @@ export type DirectChatInput = { export class DirectChat extends CoT { constructor(chat: DirectChatInput) { - const cot: JSONCoT = { + const cot: Static = { event: { _attributes: Util.cot_event_attr('b-t-f', 'h-g-i-g-o'), point: Util.cot_point(), diff --git a/lib/cot.ts b/lib/cot.ts index 486bf56..d75d4fe 100644 --- a/lib/cot.ts +++ b/lib/cot.ts @@ -1,21 +1,21 @@ import xmljs from 'xml-js'; +import { Static } from '@sinclair/typebox'; import { Feature } from 'geojson'; 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 JSONCoT, { MartiDest } 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 pkg = JSON.parse(String(fs.readFileSync(new URL('../package.json', import.meta.url)))); const ajv = (new AJV({ allErrors: true, allowUnionTypes: true })) - .compile(schema); + .compile(JSONCoT); /** * Convert to and from an XML CoT message @@ -26,19 +26,19 @@ const ajv = (new AJV({ * @prop raw Raw XML-JS representation of CoT */ export default class CoT { - raw: JSONCoT; + raw: Static; - constructor(cot: Buffer | JSONCoT | string) { + constructor(cot: Buffer | Static | string) { if (typeof cot === 'string' || cot instanceof Buffer) { if (cot instanceof Buffer) cot = String(cot); const raw = xmljs.xml2js(cot, { compact: true }); - this.raw = raw as JSONCoT; + this.raw = raw as Static; } else { this.raw = cot; } - if (!this.raw.event._attributes.uid) this.raw.event._attributes.uuid = Util.cot_uuid(); + if (!this.raw.event._attributes.uid) this.raw.event._attributes.uid = Util.cot_uuid(); ajv(this.raw); if (ajv.errors) throw new Error(`${ajv.errors[0].message} (${ajv.errors[0].instancePath})`); @@ -62,7 +62,7 @@ export default class CoT { if (feature.type !== 'Feature') throw new Error('Must be GeoJSON Feature'); if (!feature.properties) throw new Error('Feature must have properties'); - const cot: JSONCoT = { + const cot: Static = { event: { _attributes: Util.cot_event_attr( feature.properties.type || 'a-f-G', @@ -92,7 +92,7 @@ export default class CoT { const dest = !Array.isArray(feature.properties.dest) ? [ feature.properties.dest ] : feature.properties.dest; cot.event.detail.marti = { - dest: dest.map((dest: object) => { + dest: dest.map((dest: Static) => { return { _attributes: { ...dest } }; }) } @@ -208,7 +208,7 @@ export default class CoT { * Return a GeoJSON Feature from an XML CoT message */ to_geojson(): Feature { - const raw: JSONCoT = JSON.parse(JSON.stringify(this.raw)); + const raw: Static = JSON.parse(JSON.stringify(this.raw)); if (!raw.event.detail) raw.event.detail = {}; if (!raw.event.detail.contact) raw.event.detail.contact = { _attributes: { callsign: 'UNKNOWN' } }; if (!raw.event.detail.contact._attributes) raw.event.detail.contact._attributes = { callsign: 'UNKNOWN' }; @@ -262,7 +262,7 @@ export default class CoT { if (raw.event.detail.marti && raw.event.detail.marti.dest) { if (!Array.isArray(raw.event.detail.marti.dest)) raw.event.detail.marti.dest = [raw.event.detail.marti.dest]; - const dest = raw.event.detail.marti.dest.map((d) => { + const dest = raw.event.detail.marti.dest.map((d: Static) => { return { ...d._attributes }; }); diff --git a/lib/schema.json b/lib/schema.json deleted file mode 100644 index ce4ec68..0000000 --- a/lib/schema.json +++ /dev/null @@ -1,545 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": {}, - "definitions": { - "Chat": { - "properties": { - "_attributes": { - "additionalProperties": { - "type": "string" - }, - "properties": { - "chatroom": { - "type": "string" - }, - "groupOwner": { - "type": "string" - }, - "id": { - "type": "string" - }, - "messageId": { - "type": "string" - }, - "parent": { - "type": "string" - }, - "senderCallsign": { - "type": "string" - } - }, - "required": [ - "chatroom", - "id", - "senderCallsign" - ], - "type": "object" - }, - "chatgrp": { - "$ref": "#/definitions/GenericAttributes" - } - }, - "required": [ - "_attributes", - "chatgrp" - ], - "type": "object" - }, - "Contact": { - "properties": { - "_attributes": { - "additionalProperties": { - "type": "string" - }, - "properties": { - "callsign": { - "type": "string" - }, - "endpoint": { - "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" - }, - "archived": { - "$ref": "#/definitions/GenericAttributes" - }, - "contact": { - "$ref": "#/definitions/Contact" - }, - "fillColor": { - "$ref": "#/definitions/GenericAttributes" - }, - "labels_on": { - "$ref": "#/definitions/GenericAttributes" - }, - "link": { - "anyOf": [ - { - "$ref": "#/definitions/GenericAttributes" - }, - { - "items": { - "$ref": "#/definitions/GenericAttributes" - }, - "type": "array" - } - ] - }, - "marti": { - "$ref": "#/definitions/Marti" - }, - "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" - } - }, - "type": "object" - }, - "Marti": { - "properties": { - "_attributes": { - "additionalProperties": { - "type": "string" - }, - "type": "object" - }, - "dest": { - "anyOf": [ - { - "$ref": "#/definitions/MartiDest" - }, - { - "items": { - "$ref": "#/definitions/MartiDest" - }, - "type": "array" - } - ] - } - }, - "type": "object" - }, - "MartiDest": { - "properties": { - "_attributes": { - "properties": { - "callsign": { - "type": "string" - }, - "mission": { - "type": "string" - }, - "uid": { - "type": "string" - } - }, - "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" - }, - "geopointsrc": { - "type": "string" - } - }, - "type": "object" - } - }, - "required": [ - "_attributes" - ], - "type": "object" - }, - "Remarks": { - "properties": { - "_attributes": { - "additionalProperties": { - "type": "string" - }, - "properties": { - "source": { - "type": "string" - }, - "time": { - "type": "string" - }, - "to": { - "type": "string" - } - }, - "type": "object" - }, - "_text": { - "type": "string" - } - }, - "type": "object" - }, - "Status": { - "properties": { - "_attributes": { - "additionalProperties": { - "type": "string" - }, - "type": "object" - } - }, - "required": [ - "_attributes" - ], - "type": "object" - }, - "TakVersion": { - "properties": { - "_attributes": { - "additionalProperties": { - "type": "string" - }, - "properties": { - "device": { - "type": "string" - }, - "os": { - "type": "string" - }, - "platform": { - "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" - }, - "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": [ - "Droid" - ], - "type": "object" - } - }, - "required": [ - "_attributes" - ], - "type": "object" - }, - "UserIcon": { - "properties": { - "_attributes": { - "additionalProperties": { - "type": "string" - }, - "properties": { - "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", - "point" - ], - "type": "object" - } - }, - "required": [ - "event" - ], - "type": "object" -} - diff --git a/lib/types.ts b/lib/types.ts index 33a282c..51e0bb7 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,177 +1,158 @@ -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 | undefined; - } - +import { Type } from '@sinclair/typebox'; + +export const EventAttributes = Type.Object({ + version: Type.String(), + uid: Type.String(), + type: Type.String(), + how: Type.String() +}); + +export const GenericAttributes = Type.Object({ + _attributes: Type.Optional(Type.Object({})) +}) + +export const TrackAttributes = Type.Object({ + speed: Type.Optional(Type.String()), + course: Type.Optional(Type.String()), + slope: Type.Optional(Type.String()), + eCourse: Type.Optional(Type.String()), + eSpeed: Type.Optional(Type.String()), + eSlope: Type.Optional(Type.String()) +}); + + +export const Track = Type.Object({ + _attributes: TrackAttributes +}) + +export const Chat = Type.Object({ + _attributes: Type.Object({ + parent: Type.Optional(Type.String()), + groupOwner: Type.Optional(Type.String()), + messageId: Type.Optional(Type.String()), + chatroom: Type.String(), + id: Type.String(), + senderCallsign: Type.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: { - [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 Marti { - _attributes?: { - [k: string]: string | undefined; - } - - dest?: MartiDest | MartiDest[] -} - -export interface MartiDest { - _attributes: { - uid?: string; - mission?: string; - callsign?: string; - } -} - -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; - archived?: GenericAttributes; - strokeWeight?: GenericAttributes; - strokeStyle?: GenericAttributes; - labels_on?: GenericAttributes; - fillColor?: GenericAttributes; - link?: GenericAttributes | GenericAttributes[]; - usericon?: UserIcon; - track?: Track; - takv?: TakVersion; - marti?: Marti; - 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: { +}) + +export const TakVersion = Type.Object({ + _attributes: Type.Object({ + device: Type.Optional(Type.String()), + platform: Type.Optional(Type.String()), + os: Type.Optional(Type.String()), + version: Type.Optional(Type.String()) + }) +}) + +export const FlowTags = Type.Object({ + _attributes: Type.Optional(Type.Object({})) +}) + +export const Group = Type.Object({ + _attributes: Type.Optional(Type.Object({ + name: Type.String(), + role: Type.String() + })) +}) + +export const Status = Type.Object({ + _attributes: Type.Object({ + battery: Type.Optional(Type.String()), + readiness: Type.Optional(Type.String()) + }) +}) + +export const Uid = Type.Object({ + _attributes: Type.Object({ + Droid: Type.String() + }) +}) + +export const Contact = Type.Object({ + _attributes: Type.Object({ + phone: Type.Optional(Type.String()), + callsign: Type.String(), + endpoint: Type.Optional(Type.String()) + }) +}) + +export const MartiDest = Type.Object({ + _attributes: Type.Object({ + uid: Type.Optional(Type.String()), + mission: Type.Optional(Type.String()), + callsign: Type.Optional(Type.String()) + }) +}) + +export const Marti = Type.Object({ + _attributes: Type.Optional(Type.Object({})), + dest: Type.Optional(Type.Union([MartiDest, Type.Array(MartiDest)])) +}); + +export const Remarks = Type.Object({ + _attributes: Type.Optional(Type.Object({ + source: Type.Optional(Type.String()), + to: Type.Optional(Type.String()), + time: Type.Optional(Type.String()) + })), + _text: Type.Optional(Type.String()) +}) + +export const PrecisionLocation = Type.Object({ + _attributes: Type.Object({ + geopointsrc: Type.Optional(Type.String()), + altsrc: Type.Optional(Type.String()) + }) +}) + +export const UserIcon = Type.Object({ + _attributes: Type.Object({ + iconsetpath: Type.String() + }) +}) + +export const Detail = Type.Object({ + contact: Type.Optional(Contact), + tog: Type.Optional(GenericAttributes), + '__group': Type.Optional(Group), + '__chat': Type.Optional(Chat), + '_flow-tags_': Type.Optional(FlowTags), + uid: Type.Optional(Uid), + status: Type.Optional(Status), + remarks: Type.Optional(Remarks), + precisionlocation: Type.Optional(PrecisionLocation), + strokeColor: Type.Optional(GenericAttributes), + archived: Type.Optional(GenericAttributes), + strokeWeight: Type.Optional(GenericAttributes), + strokeStyle: Type.Optional(GenericAttributes), + labels_on: Type.Optional(GenericAttributes), + fillColor: Type.Optional(GenericAttributes), + link: Type.Optional(Type.Union([GenericAttributes, Type.Array(GenericAttributes)])), + usericon: Type.Optional(UserIcon), + track: Type.Optional(Track), + takv: Type.Optional(TakVersion), + marti: Type.Optional(Marti), + TakControl: Type.Optional(Type.Object({ + TakServerVersionInfo: Type.Optional(GenericAttributes) + })) +}) + +export const Point = Type.Object({ + _attributes: Type.Object({ + lat: Type.Union([Type.String(), Type.Number()]), + lon: Type.Union([Type.String(), Type.Number()]), + hae: Type.Union([Type.String(), Type.Number()]), + ce: Type.Union([Type.String(), Type.Number()]), + le: Type.Union([Type.String(), Type.Number()]), + }) +}) + +export default Type.Object({ + event: Type.Object({ _attributes: EventAttributes, - detail?: Detail, + detail: Type.Optional(Detail), point: Point, - [k: string]: unknown - }, - [k: string]: unknown -} + }), +}) diff --git a/lib/util.ts b/lib/util.ts index bc130ed..4cec0b3 100644 --- a/lib/util.ts +++ b/lib/util.ts @@ -1,5 +1,6 @@ import { randomUUID } from 'crypto' -import type { +import { Static } from '@sinclair/typebox'; +import { EventAttributes, TrackAttributes, Detail, @@ -26,7 +27,7 @@ export default class Util { time?: Date | string | null, start?: Date | string | null, stale?: Date | string | number | null - ): EventAttributes { + ): Static { if (!type) throw new Error('type param required'); if (!how) throw new Error('how param required'); @@ -44,7 +45,7 @@ export default class Util { * * @param [callsign=UNKNOWN] Display Callsign */ - static cot_event_detail(callsign = 'UNKNOWN'): Detail { + static cot_event_detail(callsign = 'UNKNOWN'): Static { return { contact: { _attributes: { callsign } @@ -58,7 +59,7 @@ export default class Util { * @param [course] Speed in degrees from north * @param [speed=0] Speed in m/s */ - static cot_track_attr(course: number, speed = 0): TrackAttributes { + static cot_track_attr(course: number, speed = 0): Static { return { course: String(course || 0), speed: String(speed || 0) @@ -82,7 +83,7 @@ export default class Util { /** * Generate Null Island CoT point object */ - static cot_point(): Point { + static cot_point(): Static { return { '_attributes': { 'lat': '0.000000', diff --git a/package-lock.json b/package-lock.json index 6f99f02..c6173ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "@tak-ps/node-cot", - "version": "5.3.1", + "version": "5.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tak-ps/node-cot", - "version": "5.3.1", + "version": "5.4.0", "license": "MIT", "dependencies": { + "@sinclair/typebox": "^0.32.20", "@turf/helpers": "^6.5.0", "@turf/point-on-feature": "^6.5.0", "@types/color": "^3.0.3", @@ -348,6 +349,11 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@sinclair/typebox": { + "version": "0.32.20", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.32.20.tgz", + "integrity": "sha512-ziK497ILSIYMxD/thl496idIb03IZPlha04itLQu1xAFQbumWZ+Dj4PMMCkDRpAYhvVSdmRlTjGu2B2MA5RplQ==" + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", diff --git a/package.json b/package.json index bd2d0dd..eb814f7 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,11 @@ "test": "ts-node-test test/", "lint": "eslint *.ts lib/*.ts", "doc": "typedoc index.ts", - "build": "typescript-json-schema --required --out ./lib/schema.json ./lib/types.ts JSONCoT && tsc --build && cp package.json dist/", + "build": "tsc --build && cp package.json dist/", "pretest": "npm run lint" }, "dependencies": { + "@sinclair/typebox": "^0.32.20", "@turf/helpers": "^6.5.0", "@turf/point-on-feature": "^6.5.0", "@types/color": "^3.0.3", @@ -34,8 +35,7 @@ "ts-node": "^10.9.1", "ts-node-test": "^0.4.0", "typedoc": "^0.25.0", - "typescript": "^5.0.0", - "typescript-json-schema": "^0.62.0" + "typescript": "^5.0.0" }, "repository": { "type": "git", From b8ac11d3285ae50426ffad8487533962591c62d4 Mon Sep 17 00:00:00 2001 From: ingalls Date: Wed, 10 Apr 2024 08:05:13 -0600 Subject: [PATCH 2/5] Passing TS Build --- lib/cot.ts | 9 +++++++-- lib/types.ts | 36 ++++++++++++++++++++++++++++-------- test/chat.test.ts | 7 +++---- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/lib/cot.ts b/lib/cot.ts index d75d4fe..7f00c93 100644 --- a/lib/cot.ts +++ b/lib/cot.ts @@ -47,7 +47,7 @@ export default class CoT { 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() - if (this.raw.event.detail.archived && Object.keys(this.raw.event.detail.archived).length === 0) this.raw.event.archived = { _attributes: {} }; + if (this.raw.event.detail.archived && Object.keys(this.raw.event.detail.archived).length === 0) this.raw.event.detail.archived = { _attributes: {} }; } @@ -92,7 +92,11 @@ export default class CoT { const dest = !Array.isArray(feature.properties.dest) ? [ feature.properties.dest ] : feature.properties.dest; cot.event.detail.marti = { - dest: dest.map((dest: Static) => { + dest: dest.map((dest: { + uid?: string; + mission?: string; + callsign?: string; + }) => { return { _attributes: { ...dest } }; }) } @@ -306,6 +310,7 @@ export default class CoT { const coordinates = []; for (const l of raw.event.detail.link) { + if (!l._attributes.point) continue; coordinates.push(l._attributes.point.split(',').map((p: string) => { return Number(p.trim()) }).splice(0, 2).reverse()); } diff --git a/lib/types.ts b/lib/types.ts index 51e0bb7..1044459 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -4,11 +4,33 @@ export const EventAttributes = Type.Object({ version: Type.String(), uid: Type.String(), type: Type.String(), - how: Type.String() + how: Type.String(), + + time: Type.String(), + stale: Type.String(), + start: Type.String(), }); +export const TogAttributes = Type.Object({ + _attributes: Type.Optional(Type.Object({ + enabled: Type.Optional(Type.String()) + })) +}) + +export const LinkAttributes = Type.Object({ + _attributes: Type.Object({ + point: Type.Optional(Type.String()), + + uid: Type.Optional(Type.String()), + type: Type.Optional(Type.String()), + relation: Type.Optional(Type.String()), + }) +}) + export const GenericAttributes = Type.Object({ - _attributes: Type.Optional(Type.Object({})) + _attributes: Type.Optional(Type.Object({ + value: Type.Optional(Type.String()) + })) }) export const TrackAttributes = Type.Object({ @@ -34,7 +56,7 @@ export const Chat = Type.Object({ id: Type.String(), senderCallsign: Type.String() }), - chatgrp: GenericAttributes + chatgrp: Type.Any() }) export const TakVersion = Type.Object({ @@ -46,9 +68,7 @@ export const TakVersion = Type.Object({ }) }) -export const FlowTags = Type.Object({ - _attributes: Type.Optional(Type.Object({})) -}) +export const FlowTags = Type.Any(); export const Group = Type.Object({ _attributes: Type.Optional(Type.Object({ @@ -115,7 +135,7 @@ export const UserIcon = Type.Object({ export const Detail = Type.Object({ contact: Type.Optional(Contact), - tog: Type.Optional(GenericAttributes), + tog: Type.Optional(TogAttributes), '__group': Type.Optional(Group), '__chat': Type.Optional(Chat), '_flow-tags_': Type.Optional(FlowTags), @@ -129,7 +149,7 @@ export const Detail = Type.Object({ strokeStyle: Type.Optional(GenericAttributes), labels_on: Type.Optional(GenericAttributes), fillColor: Type.Optional(GenericAttributes), - link: Type.Optional(Type.Union([GenericAttributes, Type.Array(GenericAttributes)])), + link: Type.Optional(Type.Union([LinkAttributes, Type.Array(LinkAttributes)])), usericon: Type.Optional(UserIcon), track: Type.Optional(Track), takv: Type.Optional(TakVersion), diff --git a/test/chat.test.ts b/test/chat.test.ts index 8adfabd..93b36f9 100644 --- a/test/chat.test.ts +++ b/test/chat.test.ts @@ -25,10 +25,9 @@ test('DirectChat - Basic', (t) => { 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]; - } + t.equals(typeof cot.raw.event._attributes.time, 'string'); + t.equals(typeof cot.raw.event._attributes.start, 'string'); + t.equals(typeof cot.raw.event._attributes.stale, 'string'); if (!cot.raw.event.detail.__chat) { t.fail('No Detail Section') From cdda01b234bffc5d5f25da4497692592d6e062b5 Mon Sep 17 00:00:00 2001 From: ingalls Date: Wed, 10 Apr 2024 08:07:31 -0600 Subject: [PATCH 3/5] Get Tests Passing --- test/chat.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/chat.test.ts b/test/chat.test.ts index 93b36f9..f180c78 100644 --- a/test/chat.test.ts +++ b/test/chat.test.ts @@ -26,8 +26,11 @@ test('DirectChat - Basic', (t) => { delete cot.raw.event.detail['_flow-tags_']; t.equals(typeof cot.raw.event._attributes.time, 'string'); + cot.raw.event._attributes.time = '2024-04-01' t.equals(typeof cot.raw.event._attributes.start, 'string'); + cot.raw.event._attributes.start = '2024-04-01' t.equals(typeof cot.raw.event._attributes.stale, 'string'); + cot.raw.event._attributes.stale = '2024-04-01' if (!cot.raw.event.detail.__chat) { t.fail('No Detail Section') @@ -50,6 +53,10 @@ test('DirectChat - Basic', (t) => { version: '2.0', type: 'b-t-f', how: 'h-g-i-g-o', + + time: '2024-04-01', + stale: '2024-04-01', + start: '2024-04-01' }, point: { _attributes: { lat: '0.000000', lon: '0.000000', hae: '0.0', ce: '9999999.0', le: '9999999.0' } From cc2958e7442b95ffbd51352aa3cdde2aba94abf3 Mon Sep 17 00:00:00 2001 From: ingalls Date: Wed, 10 Apr 2024 08:11:00 -0600 Subject: [PATCH 4/5] Update README --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6839c91..83dd15e 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,12 @@

Javascript Cursor-On-Target Library

-Lightweight JavaScript library for parsing and manipulating TAK messages, primarily Cursor-on-Target (COT) +Lightweight JavaScript library for parsing and manipulating TAK related messages, primarily [Cursor-on-Target (COT)](https://git.tak.gov/standards/takcot) ## About -tak.js converts between TAK message protocols and a Javascript object/JSON format. This makes it easy to read and write TAK messages in a Node.js application. +`node-tak` converts between TAK message protocols and a Javascript object/JSON format. +It also can bidirectionally convert CoT messages into a GeoJSON format ## Installation From 840e56517c1bb69155baa280e6991671c5e9d6f4 Mon Sep 17 00:00:00 2001 From: ingalls Date: Wed, 10 Apr 2024 08:16:22 -0600 Subject: [PATCH 5/5] Update README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 83dd15e..023e524 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,10 @@ understanding of the spec primarily developed through reverse engineering TAK cl | `version` | CoT Version, currently `2.0` | `version="2.0"` | | `uid` | Globally unique name for this information on this event | `uid="any-unique-string-123"` | | `type` | Hierarchically organized hint about event type | `type="a-f-G-E"' | -| `time` | The time at which the event was generated | `time="2023-07-18T15:25:09.00Z"` | | `how` | Gives a hint about how the coordinates were generated | `how=""` -| `start` | | | -| `stale` | | | +| `time` | The time at which the event was generated | `time="2023-07-18T15:25:09.00Z"` | +| `start` | The time at which the event starts or is relevant | `start="2023-07-18T15:25:09.00Z"` | +| `stale` | The time at which the event ends or is not relevant | `stale="2023-07-18T15:25:09.00Z"` | ## CoT GeoJSON Spec