@tak-ps/node-tak 0.2.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/.eslintrc.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
3
+ "parser": "@typescript-eslint/parser",
4
+ "plugins": ["@typescript-eslint"],
5
+ "root": true,
6
+ "rules": {
7
+ "@typescript-eslint/ban-ts-comment": 1,
8
+ "@typescript-eslint/no-explicit-any": 1
9
+ }
10
+ }
@@ -0,0 +1,46 @@
1
+ name: Documentation
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ workflow_dispatch:
7
+
8
+ # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
9
+ permissions:
10
+ contents: read
11
+ pages: write
12
+ id-token: write
13
+
14
+ # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
15
+ # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
16
+ concurrency:
17
+ group: "pages"
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ deploy:
22
+ environment:
23
+ name: github-pages
24
+ url: ${{ steps.deployment.outputs.page_url }}
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - name: Checkout
28
+ uses: actions/checkout@v3
29
+ - name: Setup Pages
30
+ uses: actions/configure-pages@v3
31
+ - uses: actions/setup-node@v3
32
+ with:
33
+ node-version: 18
34
+ registry-url: https://registry.npmjs.org/
35
+ - name: npm install
36
+ run: npm install
37
+ - name: npm run doc
38
+ run: npm run doc
39
+ - name: Upload artifact
40
+ uses: actions/upload-pages-artifact@v1
41
+ with:
42
+ path: './docs/'
43
+ - name: Deploy to GitHub Pages
44
+ id: deployment
45
+ uses: actions/deploy-pages@v2
46
+
@@ -0,0 +1,32 @@
1
+ name: NPM Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - '*'
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v3
13
+
14
+ - name: Get tag
15
+ id: tag
16
+ uses: dawidd6/action-get-tag@v1
17
+
18
+ - uses: actions/setup-node@v3
19
+ with:
20
+ node-version: 18
21
+ registry-url: https://registry.npmjs.org/
22
+
23
+ - name: npm install
24
+ run: npm install
25
+
26
+ - name: npm run build
27
+ run: npm run build
28
+
29
+ - name: npm publish
30
+ run: npm publish
31
+ env:
32
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,35 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ types:
9
+ - opened
10
+ - synchronize
11
+ - reopened
12
+ - ready_for_review
13
+
14
+ jobs:
15
+ test:
16
+ runs-on: ubuntu-latest
17
+ if: github.event.pull_request.draft == false
18
+ steps:
19
+ - uses: actions/checkout@v3
20
+ with:
21
+ ref: ${{ github.event.pull_request.head.sha }}
22
+
23
+ - uses: actions/setup-node@v3
24
+ with:
25
+ node-version: 18
26
+ registry-url: https://registry.npmjs.org/
27
+
28
+ - name: Install
29
+ run: npm install
30
+
31
+ - name: Lint
32
+ run: npm run lint
33
+
34
+ - name: Test
35
+ run: npm test
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
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
+ ### v0.2.0
14
+
15
+ - :bug: Fix Lint Errors
16
+
17
+ ### v0.1.0
18
+
19
+ - :pencil2: Add a CHANGELOG, Initial Commit
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 DFPC - Center of Excellence
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ <h1 align=center>Node-TAK</h1>
2
+
3
+ <p align=center>Javascript TAK Server Library</p>
4
+
5
+ Lightweight JavaScript library for managing TAK TLS Connectoions for streaming CoT data
6
+
7
+ ## Installation
8
+
9
+ ### NPM
10
+
11
+ To install `node-tak` with npm run
12
+
13
+ ```bash
14
+ npm install @tak-ps/node-tak
15
+ ```
16
+
17
+ ## Usage Examples
18
+
19
+ ### Basic Usage
20
+
21
+ ```
22
+ import TAK from '@tak-ps/node-tak';
23
+
24
+ ```
package/dist/index.js ADDED
@@ -0,0 +1,173 @@
1
+ import EventEmitter from 'node:events';
2
+ import tls from 'node:tls';
3
+ import CoT from '@tak-ps/node-cot';
4
+ export default class TAK extends EventEmitter {
5
+ id;
6
+ type;
7
+ url;
8
+ auth;
9
+ open;
10
+ destroyed;
11
+ queue;
12
+ writing;
13
+ client;
14
+ version;
15
+ constructor(id, type, url, auth) {
16
+ super();
17
+ this.id = id;
18
+ this.type = type;
19
+ this.url = url;
20
+ this.auth = auth;
21
+ this.writing = false;
22
+ this.open = false;
23
+ this.destroyed = false;
24
+ this.queue = [];
25
+ }
26
+ static async connect(id, url, auth) {
27
+ const tak = new TAK(id, 'ssl', url, auth);
28
+ if (url.protocol === 'ssl:') {
29
+ if (!tak.auth.cert)
30
+ throw new Error('auth.cert required');
31
+ if (!tak.auth.key)
32
+ throw new Error('auth.key required');
33
+ return await tak.connect_ssl();
34
+ }
35
+ else {
36
+ throw new Error('Unknown TAK Server Protocol');
37
+ }
38
+ }
39
+ connect_ssl() {
40
+ return new Promise((resolve) => {
41
+ this.client = tls.connect({
42
+ host: this.url.hostname,
43
+ port: parseInt(this.url.port),
44
+ rejectUnauthorized: false,
45
+ cert: this.auth.cert,
46
+ key: this.auth.key
47
+ });
48
+ this.client.setNoDelay();
49
+ this.client.on('connect', () => {
50
+ console.error(`ok - ${this.id} @ connect:${this.client ? this.client.authorized : 'NO CLIENT'} - ${this.client ? this.client.authorizationError : 'NO CLIENT'}`);
51
+ });
52
+ this.client.on('secureConnect', () => {
53
+ console.error(`ok - ${this.id} @ secure:${this.client ? this.client.authorized : 'NO CLIENT'} - ${this.client ? this.client.authorizationError : 'NO CLIENT'}`);
54
+ });
55
+ let buff = '';
56
+ this.client.on('data', (data) => {
57
+ // Eventually Parse ProtoBuf
58
+ buff = buff + data.toString();
59
+ let result = TAK.findCoT(buff);
60
+ while (result && result.event) {
61
+ const cot = new CoT(result.event);
62
+ try {
63
+ // @ts-ignore: Remove once we have JSONSchema => TS Defs for CoT Messages
64
+ if (cot.raw.event._attributes.type === 't-x-c-t-r') {
65
+ this.open = true;
66
+ this.emit('ping');
67
+ // @ts-ignore: Remove once we have JSONSchema => TS Defs for CoT Messages
68
+ }
69
+ else if (cot.raw.event._attributes.type === 't-x-takp-v') {
70
+ // @ts-ignore: Remove once we have JSONSchema => TS Defs for CoT Messages
71
+ this.version = cot.raw.event.detail.TakControl.TakServerVersionInfo._attributes.serverVersion;
72
+ }
73
+ else {
74
+ this.emit('cot', cot);
75
+ }
76
+ }
77
+ catch (e) {
78
+ console.error('Error parsing', e, data.toString());
79
+ }
80
+ buff = result.remainder;
81
+ result = TAK.findCoT(buff);
82
+ }
83
+ }).on('timeout', () => {
84
+ if (!this.destroyed)
85
+ this.emit('timeout');
86
+ }).on('error', (err) => {
87
+ this.emit('error', err);
88
+ }).on('end', () => {
89
+ this.open = false;
90
+ if (!this.destroyed)
91
+ this.emit('end');
92
+ });
93
+ setInterval(() => {
94
+ this.ping();
95
+ }, 5000);
96
+ return resolve(this);
97
+ });
98
+ }
99
+ async reconnect() {
100
+ return await this.connect_ssl();
101
+ }
102
+ destroy() {
103
+ this.destroyed = true;
104
+ if (this.client) {
105
+ this.client.destroy();
106
+ }
107
+ }
108
+ async ping() {
109
+ this.write([CoT.ping()]);
110
+ }
111
+ writer(body) {
112
+ return new Promise((resolve, reject) => {
113
+ if (!this.client)
114
+ return reject(new Error('A Connection Client must first be created before it can be written'));
115
+ const res = this.client.write(body + '\n', () => {
116
+ return resolve(res);
117
+ });
118
+ });
119
+ }
120
+ async process() {
121
+ this.writing = true;
122
+ while (this.queue.length) {
123
+ const body = this.queue.shift();
124
+ if (!body)
125
+ continue;
126
+ await this.writer(body);
127
+ }
128
+ await this.writer('');
129
+ if (this.queue.length) {
130
+ process.nextTick(() => {
131
+ this.process();
132
+ });
133
+ }
134
+ else {
135
+ this.writing = false;
136
+ }
137
+ }
138
+ /**
139
+ * Write a CoT to the TAK Connection
140
+ *
141
+ * @param {CoT} cot CoT Object
142
+ */
143
+ write(cots) {
144
+ for (const cot of cots) {
145
+ this.queue.push(cot.to_xml());
146
+ }
147
+ if (this.queue.length && !this.writing)
148
+ this.process();
149
+ }
150
+ write_xml(body) {
151
+ this.queue.push(body);
152
+ if (this.queue.length && !this.writing)
153
+ this.process();
154
+ }
155
+ // https://github.com/vidterra/multitak/blob/main/app/lib/helper.js#L4
156
+ static findCoT(str) {
157
+ /* eslint-disable no-control-regex */
158
+ str = str.replace(/[\u0000-\u001F\u007F-\u009F]/g, "");
159
+ let match = str.match(/(<event.*?<\/event>)(.*)/); // find first CoT
160
+ if (!match) {
161
+ match = str.match(/(<event[^>]*\/>)(.*)/); // find first CoT
162
+ if (!match)
163
+ return null;
164
+ }
165
+ return {
166
+ event: match[1],
167
+ remainder: match[2],
168
+ discard: match[0]
169
+ };
170
+ }
171
+ }
172
+ export { CoT };
173
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
package/index.ts ADDED
@@ -0,0 +1,214 @@
1
+ import EventEmitter from 'node:events';
2
+ import tls from 'node:tls';
3
+ import CoT from '@tak-ps/node-cot';
4
+ import type { TLSSocket } from 'node:tls'
5
+
6
+ export interface TAKAuth {
7
+ cert: string;
8
+ key: string;
9
+ }
10
+
11
+ export interface PartialCoT {
12
+ event: string;
13
+ remainder: string;
14
+ discard: string;
15
+ }
16
+
17
+ export default class TAK extends EventEmitter {
18
+ id: number;
19
+ type: string;
20
+ url: URL;
21
+ auth: TAKAuth;
22
+ open: boolean;
23
+ destroyed: boolean;
24
+ queue: string[];
25
+ writing: boolean;
26
+
27
+ client?: TLSSocket;
28
+ version?: string;
29
+
30
+ constructor(
31
+ id: number,
32
+ type: string,
33
+ url: URL,
34
+ auth: TAKAuth
35
+ ) {
36
+ super();
37
+
38
+ this.id = id;
39
+
40
+ this.type = type;
41
+ this.url = url;
42
+ this.auth = auth;
43
+
44
+ this.writing = false;
45
+
46
+ this.open = false;
47
+ this.destroyed = false;
48
+
49
+ this.queue = [];
50
+ }
51
+
52
+ static async connect(id: number, url: URL, auth: TAKAuth) {
53
+ const tak = new TAK(id, 'ssl', url, auth);
54
+
55
+ if (url.protocol === 'ssl:') {
56
+ if (!tak.auth.cert) throw new Error('auth.cert required');
57
+ if (!tak.auth.key) throw new Error('auth.key required');
58
+ return await tak.connect_ssl();
59
+ } else {
60
+ throw new Error('Unknown TAK Server Protocol');
61
+ }
62
+ }
63
+
64
+ connect_ssl(): Promise<TAK> {
65
+ return new Promise((resolve) => {
66
+ this.client = tls.connect({
67
+ host: this.url.hostname,
68
+ port: parseInt(this.url.port),
69
+ rejectUnauthorized: false,
70
+ cert: this.auth.cert,
71
+ key: this.auth.key
72
+ });
73
+
74
+ this.client.setNoDelay();
75
+
76
+ this.client.on('connect', () => {
77
+ console.error(`ok - ${this.id} @ connect:${this.client ? this.client.authorized : 'NO CLIENT'} - ${this.client ? this.client.authorizationError : 'NO CLIENT'}`);
78
+ });
79
+
80
+ this.client.on('secureConnect', () => {
81
+ console.error(`ok - ${this.id} @ secure:${this.client ? this.client.authorized : 'NO CLIENT'} - ${this.client ? this.client.authorizationError : 'NO CLIENT'}`);
82
+ });
83
+
84
+ let buff = '';
85
+ this.client.on('data', (data: Buffer) => {
86
+ // Eventually Parse ProtoBuf
87
+ buff = buff + data.toString();
88
+
89
+ let result = TAK.findCoT(buff);
90
+ while (result && result.event) {
91
+ const cot = new CoT(result.event);
92
+
93
+ try {
94
+ // @ts-ignore: Remove once we have JSONSchema => TS Defs for CoT Messages
95
+ if (cot.raw.event._attributes.type === 't-x-c-t-r') {
96
+ this.open = true;
97
+ this.emit('ping');
98
+ // @ts-ignore: Remove once we have JSONSchema => TS Defs for CoT Messages
99
+ } else if (cot.raw.event._attributes.type === 't-x-takp-v') {
100
+ // @ts-ignore: Remove once we have JSONSchema => TS Defs for CoT Messages
101
+ this.version = cot.raw.event.detail.TakControl.TakServerVersionInfo._attributes.serverVersion;
102
+ } else {
103
+ this.emit('cot', cot);
104
+ }
105
+ } catch (e) {
106
+ console.error('Error parsing', e, data.toString());
107
+ }
108
+
109
+ buff = result.remainder;
110
+
111
+ result = TAK.findCoT(buff);
112
+ }
113
+ }).on('timeout', () => {
114
+ if (!this.destroyed) this.emit('timeout');
115
+ }).on('error', (err: Error) => {
116
+ this.emit('error', err);
117
+ }).on('end', () => {
118
+ this.open = false;
119
+ if (!this.destroyed) this.emit('end');
120
+ });
121
+
122
+ setInterval(() => {
123
+ this.ping();
124
+ }, 5000);
125
+
126
+ return resolve(this);
127
+ });
128
+ }
129
+
130
+ async reconnect() {
131
+ return await this.connect_ssl();
132
+ }
133
+
134
+ destroy() {
135
+ this.destroyed = true;
136
+ if (this.client) {
137
+ this.client.destroy();
138
+ }
139
+ }
140
+
141
+ async ping() {
142
+ this.write([CoT.ping()]);
143
+ }
144
+
145
+ writer(body: string): Promise<boolean> {
146
+ return new Promise((resolve, reject) => {
147
+ if (!this.client) return reject(new Error('A Connection Client must first be created before it can be written'));
148
+
149
+ const res: boolean = this.client.write(body + '\n', () => {
150
+ return resolve(res)
151
+ });
152
+ });
153
+ }
154
+
155
+ async process() {
156
+ this.writing = true;
157
+ while (this.queue.length) {
158
+ const body = this.queue.shift()
159
+ if (!body) continue;
160
+ await this.writer(body);
161
+ }
162
+
163
+ await this.writer('');
164
+
165
+ if (this.queue.length) {
166
+ process.nextTick(() => {
167
+ this.process();
168
+ });
169
+ } else {
170
+ this.writing = false;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Write a CoT to the TAK Connection
176
+ *
177
+ * @param {CoT} cot CoT Object
178
+ */
179
+ write(cots: CoT[]) {
180
+ for (const cot of cots) {
181
+ this.queue.push(cot.to_xml());
182
+ }
183
+
184
+ if (this.queue.length && !this.writing) this.process();
185
+ }
186
+
187
+ write_xml(body: string) {
188
+ this.queue.push(body);
189
+
190
+ if (this.queue.length && !this.writing) this.process();
191
+ }
192
+
193
+ // https://github.com/vidterra/multitak/blob/main/app/lib/helper.js#L4
194
+ static findCoT(str: string): null | PartialCoT {
195
+ /* eslint-disable no-control-regex */
196
+ str = str.replace(/[\u0000-\u001F\u007F-\u009F]/g, "");
197
+
198
+ let match = str.match(/(<event.*?<\/event>)(.*)/); // find first CoT
199
+ if (!match) {
200
+ match = str.match(/(<event[^>]*\/>)(.*)/); // find first CoT
201
+ if (!match) return null;
202
+ }
203
+
204
+ return {
205
+ event: match[1],
206
+ remainder: match[2],
207
+ discard: match[0]
208
+ };
209
+ }
210
+ }
211
+
212
+ export {
213
+ CoT
214
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@tak-ps/node-tak",
3
+ "type": "module",
4
+ "version": "0.2.0",
5
+ "description": "Lightweight JavaScript library for communicating with TAK Server",
6
+ "author": "Nick Ingalls <nick@ingalls.ca>",
7
+ "main": "dist/index.js",
8
+ "types": "index.ts",
9
+ "scripts": {
10
+ "test": "ts-node-test test/",
11
+ "lint": "eslint *.ts test/*.ts",
12
+ "doc": "typedoc index.ts",
13
+ "build": "tsc --build",
14
+ "pretest": "npm run lint"
15
+ },
16
+ "dependencies": {
17
+ "@tak-ps/node-cot": "^4.1.2",
18
+ "ajv": "^8.12.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^20.0.0",
22
+ "@types/tape": "^5.6.0",
23
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
24
+ "@typescript-eslint/parser": "^6.0.0",
25
+ "eslint": "^8.35.0",
26
+ "eslint-plugin-node": "^11.1.0",
27
+ "tape": "^5.6.1",
28
+ "ts-node": "^10.9.1",
29
+ "ts-node-test": "^0.4.0",
30
+ "typedoc": "^0.25.0",
31
+ "typescript": "^5.0.0"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/dfpc-coe/node-tak.git"
36
+ },
37
+ "keywords": [
38
+ "tak",
39
+ "atak",
40
+ "wintak",
41
+ "cot",
42
+ "cusor",
43
+ "target",
44
+ "tactical"
45
+ ],
46
+ "license": "MIT",
47
+ "bugs": {
48
+ "url": "https://github.com/dfpc-coe/node-tak/issues"
49
+ },
50
+ "homepage": "https://github.com/dfpc-coe/node-tak#readme"
51
+ }
@@ -0,0 +1,9 @@
1
+ import TAK, { CoT } from '../index.js';
2
+ import test from 'tape';
3
+
4
+ test('Ensure Export', (t) => {
5
+ t.ok(TAK);
6
+ t.ok(CoT);
7
+ t.end();
8
+ });
9
+
package/tsconfig.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "ts-node": {
3
+ "esm": true
4
+ },
5
+ "compilerOptions": {
6
+ "strict": true,
7
+ "module": "es2022",
8
+ "esModuleInterop": true,
9
+ "target": "es2022",
10
+ "lib": ["es2022", "dom"],
11
+ "noImplicitAny": true,
12
+ "moduleResolution": "node",
13
+ "resolveJsonModule": true,
14
+ "sourceMap": true,
15
+ "outDir": "dist",
16
+ "baseUrl": ".",
17
+ "paths": {
18
+ "*": [
19
+ "node_modules/*"
20
+ ]
21
+ }
22
+ },
23
+ "include": [
24
+ "index.ts",
25
+ "test/**/*",
26
+ "lib/**/*",
27
+ "lib/schema.json"
28
+ ]
29
+ }