@tak-ps/node-tak 12.9.0 → 12.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,10 @@
10
10
 
11
11
  ## Version History
12
12
 
13
+ ### v12.10.0 - 2025-04-14
14
+
15
+ - :rocket: handle multipart form data internally
16
+
13
17
  ### v12.9.0 - 2025-04-13
14
18
 
15
19
  - :arrow_up: Remove `tape` in favor of `node:test` and update tests accordingly
package/dist/lib/auth.js CHANGED
@@ -2,6 +2,7 @@ import fetch from './fetch.js';
2
2
  import { Type } from '@sinclair/typebox';
3
3
  import { Client } from 'undici';
4
4
  import stream2buffer from './stream.js';
5
+ import { encodeMultipartFormData } from './multipart.js';
5
6
  /**
6
7
  * Store the TAK Client Certificate for a connection
7
8
  */
@@ -76,6 +77,17 @@ export class APIAuthCertificate extends APIAuth {
76
77
  rejectUnauthorized: false,
77
78
  }
78
79
  });
80
+ if (typeof FormData !== 'undefined' && opts.body instanceof FormData) {
81
+ const multipart = await encodeMultipartFormData(opts.body);
82
+ opts = {
83
+ ...opts,
84
+ headers: {
85
+ ...(opts.headers || {}),
86
+ ...multipart.headers
87
+ },
88
+ body: multipart.body
89
+ };
90
+ }
79
91
  const res = await client.request({
80
92
  path: String(url).replace(api.url.origin, ''),
81
93
  ...opts
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../lib/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../lib/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { Readable } from 'node:stream';
2
+ export declare function encodeMultipartFormData(form: FormData): Promise<{
3
+ body: Readable;
4
+ headers: {
5
+ 'Content-Type': string;
6
+ 'Content-Length': string;
7
+ };
8
+ }>;
@@ -0,0 +1,61 @@
1
+ import crypto from 'node:crypto';
2
+ import { Readable } from 'node:stream';
3
+ function escapeQuoted(value) {
4
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
5
+ }
6
+ export async function encodeMultipartFormData(form) {
7
+ const boundary = `----node-tak-${crypto.randomUUID()}`;
8
+ const closing = Buffer.from(`--${boundary}--\r\n`);
9
+ const parts = Array.from(form.entries()).map(([name, value]) => {
10
+ if (typeof value === 'string') {
11
+ const header = Buffer.from(`--${boundary}\r\n`
12
+ + `Content-Disposition: form-data; name="${escapeQuoted(name)}"\r\n\r\n`);
13
+ const body = Buffer.from(value);
14
+ const trailer = Buffer.from('\r\n');
15
+ return {
16
+ header,
17
+ kind: 'buffer',
18
+ body,
19
+ trailer,
20
+ size: header.length + body.length + trailer.length,
21
+ };
22
+ }
23
+ const filename = 'name' in value && typeof value.name === 'string'
24
+ ? value.name
25
+ : 'blob';
26
+ const type = value.type || 'application/octet-stream';
27
+ const header = Buffer.from(`--${boundary}\r\n`
28
+ + `Content-Disposition: form-data; name="${escapeQuoted(name)}"; filename="${escapeQuoted(filename)}"\r\n`
29
+ + `Content-Type: ${type}\r\n\r\n`);
30
+ const trailer = Buffer.from('\r\n');
31
+ return {
32
+ header,
33
+ kind: 'file',
34
+ body: value,
35
+ trailer,
36
+ size: header.length + value.size + trailer.length,
37
+ };
38
+ });
39
+ const contentLength = parts.reduce((sum, part) => sum + part.size, 0) + closing.length;
40
+ const body = Readable.from((async function* () {
41
+ for (const part of parts) {
42
+ yield part.header;
43
+ if (part.kind === 'buffer') {
44
+ yield part.body;
45
+ }
46
+ else {
47
+ yield Buffer.from(await part.body.arrayBuffer());
48
+ }
49
+ yield part.trailer;
50
+ }
51
+ yield closing;
52
+ })());
53
+ return {
54
+ body,
55
+ headers: {
56
+ 'Content-Type': `multipart/form-data; boundary=${boundary}`,
57
+ 'Content-Length': String(contentLength),
58
+ }
59
+ };
60
+ }
61
+ //# sourceMappingURL=multipart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multipart.js","sourceRoot":"","sources":["../../lib/multipart.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { Readable } from 'node:stream';
4
+ import { Client } from 'undici';
5
+ import TAKAPI from '../lib/api.js';
6
+ import { APIAuthCertificate } from '../lib/auth.js';
7
+ import stream2buffer from '../lib/stream.js';
8
+ test('Files.uploadPackage serializes multipart for certificate auth', async () => {
9
+ const originalRequest = Client.prototype.request;
10
+ let captured;
11
+ Client.prototype.request = async function (opts) {
12
+ captured = opts;
13
+ return {
14
+ statusCode: 200,
15
+ headers: {
16
+ 'content-type': 'text/plain'
17
+ },
18
+ body: Readable.from([Buffer.from('ok')]),
19
+ trailers: {}
20
+ };
21
+ };
22
+ try {
23
+ const api = new TAKAPI(new URL('https://tak.example.com'), new APIAuthCertificate('cert', 'key'));
24
+ const res = await api.Files.uploadPackage({
25
+ name: 'mission.zip',
26
+ creatorUid: 'user-1',
27
+ hash: 'hash-123',
28
+ keywords: ['alpha'],
29
+ groups: ['Blue']
30
+ }, Buffer.from('zip-bytes'));
31
+ assert.equal(res, 'ok');
32
+ assert.ok(captured);
33
+ assert.equal(captured.path, '/Marti/sync/missionupload?filename=mission.zip&creatorUid=user-1&hash=hash-123&keyword=missionpackage&keyword=alpha&Groups=Blue');
34
+ const headers = captured.headers;
35
+ assert.equal(typeof headers['Content-Type'], 'string');
36
+ assert.equal(typeof headers['Content-Length'], 'string');
37
+ assert.match(headers['Content-Type'], /^multipart\/form-data; boundary=----node-tak-/);
38
+ assert.equal(Number(headers['Content-Length']) > 0, true);
39
+ assert.ok(captured.body instanceof Readable);
40
+ const body = await stream2buffer(captured.body);
41
+ const multipart = body.toString('utf8');
42
+ assert.match(multipart, /Content-Disposition: form-data; name="assetfile"; filename="mission.zip"/);
43
+ assert.match(multipart, /Content-Type: application\/zip/);
44
+ assert.match(multipart, /zip-bytes/);
45
+ assert.match(multipart, /--$/m);
46
+ }
47
+ finally {
48
+ Client.prototype.request = originalRequest;
49
+ }
50
+ });
51
+ //# sourceMappingURL=uploadPackage.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploadPackage.test.js","sourceRoot":"","sources":["../../test/uploadPackage.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,OAAO,MAAM,MAAM,eAAe,CAAC;AACnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,aAAa,MAAM,kBAAkB,CAAC;AAI7C,IAAI,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;IAC7E,MAAM,eAAe,GAAG,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC;IAEjD,IAAI,QAAiC,CAAC;IAEtC,MAAM,CAAC,SAAS,CAAC,OAAO,GAAG,KAAK,WAAU,IAAiB;QACvD,QAAQ,GAAG,IAAI,CAAC;QAEhB,OAAO;YACH,UAAU,EAAE,GAAG;YACf,OAAO,EAAE;gBACL,cAAc,EAAE,YAAY;aAC/B;YACD,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YACxC,QAAQ,EAAE,EAAE;SACuB,CAAC;IAC5C,CAAC,CAAC;IAEF,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,yBAAyB,CAAC,EAAE,IAAI,kBAAkB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;QAElG,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC;YACtC,IAAI,EAAE,aAAa;YACnB,UAAU,EAAE,QAAQ;YACpB,IAAI,EAAE,UAAU;YAChB,QAAQ,EAAE,CAAC,OAAO,CAAC;YACnB,MAAM,EAAE,CAAC,MAAM,CAAC;SACnB,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;QAE7B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACxB,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;QACpB,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,iIAAiI,CAAC,CAAC;QAC/J,MAAM,OAAO,GAAG,QAAQ,CAAC,OAA8B,CAAC;QACxD,MAAM,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,CAAC;QACvD,MAAM,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,gBAAgB,CAAC,EAAE,QAAQ,CAAC,CAAC;QACzD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAW,EAAE,+CAA+C,CAAC,CAAC;QACjG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;QAE1D,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,YAAY,QAAQ,CAAC,CAAC;QAC7C,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAChD,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAExC,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,0EAA0E,CAAC,CAAC;QACpG,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,gCAAgC,CAAC,CAAC;QAC1D,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;YAAS,CAAC;QACP,MAAM,CAAC,SAAS,CAAC,OAAO,GAAG,eAAe,CAAC;IAC/C,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"root":["../cli.ts","../index.ts","../test/default.test.ts","../test/findCoT.test.ts","../test/pipeline.test.ts","../test/queue.test.ts","../lib/api.ts","../lib/auth.ts","../lib/commands.ts","../lib/fetch.ts","../lib/stream.ts","../lib/api/certificate.ts","../lib/api/client.ts","../lib/api/contacts.ts","../lib/api/credentials.ts","../lib/api/export.ts","../lib/api/files.ts","../lib/api/groups.ts","../lib/api/iconsets.ts","../lib/api/injectors.ts","../lib/api/locate.ts","../lib/api/mission-invite.ts","../lib/api/mission-layer.ts","../lib/api/mission-log.ts","../lib/api/mission.ts","../lib/api/oauth.ts","../lib/api/package.ts","../lib/api/profile.ts","../lib/api/query.ts","../lib/api/repeater.ts","../lib/api/security.ts","../lib/api/subscriptions.ts","../lib/api/types.ts","../lib/api/video.ts","../lib/utils/queue.ts"],"version":"6.0.2"}
1
+ {"root":["../cli.ts","../index.ts","../test/default.test.ts","../test/findCoT.test.ts","../test/pipeline.test.ts","../test/queue.test.ts","../test/uploadPackage.test.ts","../lib/api.ts","../lib/auth.ts","../lib/commands.ts","../lib/fetch.ts","../lib/multipart.ts","../lib/stream.ts","../lib/api/certificate.ts","../lib/api/client.ts","../lib/api/contacts.ts","../lib/api/credentials.ts","../lib/api/export.ts","../lib/api/files.ts","../lib/api/groups.ts","../lib/api/iconsets.ts","../lib/api/injectors.ts","../lib/api/locate.ts","../lib/api/mission-invite.ts","../lib/api/mission-layer.ts","../lib/api/mission-log.ts","../lib/api/mission.ts","../lib/api/oauth.ts","../lib/api/package.ts","../lib/api/profile.ts","../lib/api/query.ts","../lib/api/repeater.ts","../lib/api/security.ts","../lib/api/subscriptions.ts","../lib/api/types.ts","../lib/api/video.ts","../lib/utils/queue.ts"],"version":"6.0.2"}
package/lib/auth.ts CHANGED
@@ -3,6 +3,7 @@ import { Type } from '@sinclair/typebox';
3
3
  import { Client } from 'undici';
4
4
  import TAKAPI from './api.js';
5
5
  import stream2buffer from './stream.js';
6
+ import { encodeMultipartFormData } from './multipart.js';
6
7
 
7
8
  /**
8
9
  * Store the TAK Client Certificate for a connection
@@ -97,6 +98,18 @@ export class APIAuthCertificate extends APIAuth {
97
98
  }
98
99
  });
99
100
 
101
+ if (typeof FormData !== 'undefined' && opts.body instanceof FormData) {
102
+ const multipart = await encodeMultipartFormData(opts.body);
103
+ opts = {
104
+ ...opts,
105
+ headers: {
106
+ ...(opts.headers || {}),
107
+ ...multipart.headers
108
+ },
109
+ body: multipart.body
110
+ };
111
+ }
112
+
100
113
  const res = await client.request({
101
114
  path: String(url).replace(api.url.origin, ''),
102
115
  ...opts
@@ -0,0 +1,99 @@
1
+ import crypto from 'node:crypto';
2
+ import { Readable } from 'node:stream';
3
+
4
+ type MultipartBufferPart = {
5
+ header: Buffer;
6
+ kind: 'buffer';
7
+ body: Buffer;
8
+ trailer: Buffer;
9
+ size: number;
10
+ };
11
+
12
+ type MultipartFilePart = {
13
+ header: Buffer;
14
+ kind: 'file';
15
+ body: File;
16
+ trailer: Buffer;
17
+ size: number;
18
+ };
19
+
20
+ type MultipartPart = MultipartBufferPart | MultipartFilePart;
21
+
22
+ function escapeQuoted(value: string): string {
23
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
24
+ }
25
+
26
+ export async function encodeMultipartFormData(form: FormData): Promise<{
27
+ body: Readable;
28
+ headers: {
29
+ 'Content-Type': string;
30
+ 'Content-Length': string;
31
+ };
32
+ }> {
33
+ const boundary = `----node-tak-${crypto.randomUUID()}`;
34
+ const closing = Buffer.from(`--${boundary}--\r\n`);
35
+
36
+ const parts: MultipartPart[] = Array.from(form.entries()).map(([name, value]) => {
37
+ if (typeof value === 'string') {
38
+ const header = Buffer.from(
39
+ `--${boundary}\r\n`
40
+ + `Content-Disposition: form-data; name="${escapeQuoted(name)}"\r\n\r\n`
41
+ );
42
+ const body = Buffer.from(value);
43
+ const trailer = Buffer.from('\r\n');
44
+
45
+ return {
46
+ header,
47
+ kind: 'buffer',
48
+ body,
49
+ trailer,
50
+ size: header.length + body.length + trailer.length,
51
+ };
52
+ }
53
+
54
+ const filename = 'name' in value && typeof value.name === 'string'
55
+ ? value.name
56
+ : 'blob';
57
+ const type = value.type || 'application/octet-stream';
58
+ const header = Buffer.from(
59
+ `--${boundary}\r\n`
60
+ + `Content-Disposition: form-data; name="${escapeQuoted(name)}"; filename="${escapeQuoted(filename)}"\r\n`
61
+ + `Content-Type: ${type}\r\n\r\n`
62
+ );
63
+ const trailer = Buffer.from('\r\n');
64
+
65
+ return {
66
+ header,
67
+ kind: 'file',
68
+ body: value,
69
+ trailer,
70
+ size: header.length + value.size + trailer.length,
71
+ };
72
+ });
73
+
74
+ const contentLength = parts.reduce((sum, part) => sum + part.size, 0) + closing.length;
75
+
76
+ const body = Readable.from((async function*() {
77
+ for (const part of parts) {
78
+ yield part.header;
79
+
80
+ if (part.kind === 'buffer') {
81
+ yield part.body;
82
+ } else {
83
+ yield Buffer.from(await part.body.arrayBuffer());
84
+ }
85
+
86
+ yield part.trailer;
87
+ }
88
+
89
+ yield closing;
90
+ })());
91
+
92
+ return {
93
+ body,
94
+ headers: {
95
+ 'Content-Type': `multipart/form-data; boundary=${boundary}`,
96
+ 'Content-Length': String(contentLength),
97
+ }
98
+ };
99
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tak-ps/node-tak",
3
3
  "type": "module",
4
- "version": "12.9.0",
4
+ "version": "12.10.0",
5
5
  "description": "Lightweight JavaScript library for communicating with TAK Server",
6
6
  "author": "Nick Ingalls <nick@ingalls.ca>",
7
7
  "main": "dist/index.js",
@@ -0,0 +1,62 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { Readable } from 'node:stream';
4
+ import { Client } from 'undici';
5
+ import type { IncomingHttpHeaders } from 'node:http';
6
+ import type { Dispatcher } from 'undici';
7
+ import TAKAPI from '../lib/api.js';
8
+ import { APIAuthCertificate } from '../lib/auth.js';
9
+ import stream2buffer from '../lib/stream.js';
10
+
11
+ type RequestArgs = Parameters<Client['request']>[0];
12
+
13
+ test('Files.uploadPackage serializes multipart for certificate auth', async () => {
14
+ const originalRequest = Client.prototype.request;
15
+
16
+ let captured: RequestArgs | undefined;
17
+
18
+ Client.prototype.request = async function(opts: RequestArgs): Promise<Dispatcher.ResponseData> {
19
+ captured = opts;
20
+
21
+ return {
22
+ statusCode: 200,
23
+ headers: {
24
+ 'content-type': 'text/plain'
25
+ },
26
+ body: Readable.from([Buffer.from('ok')]),
27
+ trailers: {}
28
+ } as unknown as Dispatcher.ResponseData;
29
+ };
30
+
31
+ try {
32
+ const api = new TAKAPI(new URL('https://tak.example.com'), new APIAuthCertificate('cert', 'key'));
33
+
34
+ const res = await api.Files.uploadPackage({
35
+ name: 'mission.zip',
36
+ creatorUid: 'user-1',
37
+ hash: 'hash-123',
38
+ keywords: ['alpha'],
39
+ groups: ['Blue']
40
+ }, Buffer.from('zip-bytes'));
41
+
42
+ assert.equal(res, 'ok');
43
+ assert.ok(captured);
44
+ assert.equal(captured.path, '/Marti/sync/missionupload?filename=mission.zip&creatorUid=user-1&hash=hash-123&keyword=missionpackage&keyword=alpha&Groups=Blue');
45
+ const headers = captured.headers as IncomingHttpHeaders;
46
+ assert.equal(typeof headers['Content-Type'], 'string');
47
+ assert.equal(typeof headers['Content-Length'], 'string');
48
+ assert.match(headers['Content-Type'] as string, /^multipart\/form-data; boundary=----node-tak-/);
49
+ assert.equal(Number(headers['Content-Length']) > 0, true);
50
+
51
+ assert.ok(captured.body instanceof Readable);
52
+ const body = await stream2buffer(captured.body);
53
+ const multipart = body.toString('utf8');
54
+
55
+ assert.match(multipart, /Content-Disposition: form-data; name="assetfile"; filename="mission.zip"/);
56
+ assert.match(multipart, /Content-Type: application\/zip/);
57
+ assert.match(multipart, /zip-bytes/);
58
+ assert.match(multipart, /--$/m);
59
+ } finally {
60
+ Client.prototype.request = originalRequest;
61
+ }
62
+ });