@tak-ps/node-tak 8.3.0 → 8.4.1

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.
Files changed (63) hide show
  1. package/.github/workflows/doc.yml +6 -6
  2. package/.github/workflows/release.yml +3 -3
  3. package/.github/workflows/test.yml +14 -9
  4. package/CHANGELOG.md +8 -0
  5. package/dist/index.js +3 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/api/contacts.js +23 -0
  8. package/dist/lib/api/contacts.js.map +1 -0
  9. package/dist/lib/api/credentials.js +62 -0
  10. package/dist/lib/api/credentials.js.map +1 -0
  11. package/dist/lib/api/export.js +36 -0
  12. package/dist/lib/api/export.js.map +1 -0
  13. package/dist/lib/api/files.js +112 -0
  14. package/dist/lib/api/files.js.map +1 -0
  15. package/dist/lib/api/groups.js +47 -0
  16. package/dist/lib/api/groups.js.map +1 -0
  17. package/dist/lib/api/mission-layer.js +245 -0
  18. package/dist/lib/api/mission-layer.js.map +1 -0
  19. package/dist/lib/api/mission-log.js +108 -0
  20. package/dist/lib/api/mission-log.js.map +1 -0
  21. package/dist/lib/api/mission.js +583 -0
  22. package/dist/lib/api/mission.js.map +1 -0
  23. package/dist/lib/api/oauth.js +54 -0
  24. package/dist/lib/api/oauth.js.map +1 -0
  25. package/dist/lib/api/package.js +42 -0
  26. package/dist/lib/api/package.js.map +1 -0
  27. package/dist/lib/api/query.js +60 -0
  28. package/dist/lib/api/query.js.map +1 -0
  29. package/dist/lib/api/subscriptions.js +73 -0
  30. package/dist/lib/api/subscriptions.js.map +1 -0
  31. package/dist/lib/api/types.js +42 -0
  32. package/dist/lib/api/types.js.map +1 -0
  33. package/dist/lib/api/video.js +123 -0
  34. package/dist/lib/api/video.js.map +1 -0
  35. package/dist/lib/api.js +123 -0
  36. package/dist/lib/api.js.map +1 -0
  37. package/dist/lib/auth.js +92 -0
  38. package/dist/lib/auth.js.map +1 -0
  39. package/dist/lib/fetch.js +26 -0
  40. package/dist/lib/fetch.js.map +1 -0
  41. package/dist/lib/stream.js +9 -0
  42. package/dist/lib/stream.js.map +1 -0
  43. package/dist/tsconfig.tsbuildinfo +1 -1
  44. package/index.ts +7 -1
  45. package/lib/api/contacts.ts +27 -0
  46. package/lib/api/credentials.ts +74 -0
  47. package/lib/api/export.ts +44 -0
  48. package/lib/api/files.ts +151 -0
  49. package/lib/api/groups.ts +63 -0
  50. package/lib/api/mission-layer.ts +312 -0
  51. package/lib/api/mission-log.ts +140 -0
  52. package/lib/api/mission.ts +741 -0
  53. package/lib/api/oauth.ts +68 -0
  54. package/lib/api/package.ts +56 -0
  55. package/lib/api/query.ts +79 -0
  56. package/lib/api/subscriptions.ts +84 -0
  57. package/lib/api/types.ts +43 -0
  58. package/lib/api/video.ts +155 -0
  59. package/lib/api.ts +136 -0
  60. package/lib/auth.ts +117 -0
  61. package/lib/fetch.ts +38 -0
  62. package/lib/stream.ts +10 -0
  63. package/package.json +17 -4
@@ -0,0 +1,68 @@
1
+ import Err from '@openaddresses/batch-error';
2
+ import TAKAPI from '../api.js';
3
+ import { Type, Static } from '@sinclair/typebox';
4
+
5
+ export const LoginInput = Type.Object({
6
+ username: Type.String(),
7
+ password: Type.String()
8
+ })
9
+
10
+ export const TokenContents = Type.Object({
11
+ sub: Type.String(),
12
+ aud: Type.String(),
13
+ nbf: Type.Number(),
14
+ exp: Type.Number(),
15
+ iat: Type.Number()
16
+ })
17
+
18
+ export default class {
19
+ api: TAKAPI;
20
+
21
+ constructor(api: TAKAPI) {
22
+ this.api = api;
23
+ }
24
+
25
+ parse(jwt: string): Static<typeof TokenContents>{
26
+ const split = Buffer.from(jwt, 'base64').toString().split('}').map((ext) => { return ext + '}'});
27
+ if (split.length < 2) throw new Err(500, null, 'Unexpected TAK JWT Format');
28
+ const contents: { sub: string; aud: string; nbf: number; exp: number; iat: number; } = JSON.parse(split[1]);
29
+
30
+ return contents;
31
+ }
32
+
33
+ async login(query: Static<typeof LoginInput>): Promise<{
34
+ token: string;
35
+ contents: Static<typeof TokenContents>
36
+ }> {
37
+ const url = new URL(`/oauth/token`, this.api.url);
38
+
39
+ url.searchParams.append('grant_type', 'password');
40
+ url.searchParams.append('username', query.username);
41
+ url.searchParams.append('password', query.password);
42
+
43
+ const authres = await this.api.fetch(url, {
44
+ method: 'POST'
45
+ }, true);
46
+
47
+ const text = await authres.text();
48
+
49
+ if (authres.status === 401) {
50
+ throw new Err(400, new Error(text), 'TAK Server reports incorrect Username or Password');
51
+ } else if (!authres.ok) {
52
+ throw new Err(400, new Error(`Status: ${authres.status}: ${text}`), 'Non-200 Response from Auth Server - Token');
53
+ }
54
+
55
+ const body: any = JSON.parse(text);
56
+
57
+ if (body.error === 'invalid_grant' && body.error_description.startsWith('Bad credentials')) {
58
+ throw new Err(400, null, 'Invalid Username or Password');
59
+ } else if (body.error || !body.access_token) {
60
+ throw new Err(500, new Error(body.error_description), 'Unknown Login Error');
61
+ }
62
+
63
+ return {
64
+ token: body.access_token,
65
+ contents: this.parse(body.access_token)
66
+ };
67
+ }
68
+ }
@@ -0,0 +1,56 @@
1
+ import TAKAPI from '../api.js';
2
+ import { Type, Static } from '@sinclair/typebox';
3
+
4
+ export const Package = Type.Object({
5
+ EXPIRATION: Type.String(),
6
+ UID: Type.String(),
7
+ SubmissionDateTime: Type.String(),
8
+ Keywords: Type.Array(Type.String()),
9
+ MIMEType: Type.String(),
10
+ Size: Type.String(),
11
+ SubmissionUser: Type.String(),
12
+ PrimaryKey: Type.String(),
13
+ Hash: Type.String(),
14
+ CreatorUid: Type.Optional(Type.Union([Type.Null(), Type.String()])),
15
+ Name: Type.String(),
16
+ Tool: Type.String()
17
+ });
18
+
19
+ export const ListInput = Type.Object({
20
+ tool: Type.Optional(Type.String()),
21
+ uid: Type.Optional(Type.String())
22
+ });
23
+
24
+ /**
25
+ * @class
26
+ */
27
+ export default class {
28
+ api: TAKAPI;
29
+
30
+ constructor(api: TAKAPI) {
31
+ this.api = api;
32
+ }
33
+
34
+ async list(query: Static<typeof ListInput>): Promise<{
35
+ resultCount: number;
36
+ results: Array<Static<typeof Package>>
37
+ }> {
38
+ const url = new URL(`/Marti/sync/search`, this.api.url);
39
+
40
+ let q: keyof Static<typeof ListInput>;
41
+ for (q in query) {
42
+ if (query[q] !== undefined) {
43
+ url.searchParams.append(q, String(query[q]));
44
+ }
45
+ }
46
+
47
+ const res = await this.api.fetch(url, {
48
+ method: 'GET'
49
+ });
50
+
51
+ return JSON.parse(res) as {
52
+ resultCount: number;
53
+ results: Array<Static<typeof Package>>
54
+ };
55
+ }
56
+ }
@@ -0,0 +1,79 @@
1
+ import TAKAPI from '../api.js';
2
+ import Err from '@openaddresses/batch-error';
3
+ import xmljs from 'xml-js';
4
+ import { Type, Static } from '@sinclair/typebox';
5
+ import CoT from '@tak-ps/node-cot';
6
+ import type { Feature } from '@tak-ps/node-cot';
7
+
8
+ export const HistoryOptions = Type.Object({
9
+ start: Type.Optional(Type.String()),
10
+ end: Type.Optional(Type.String()),
11
+ secago: Type.Optional(Type.String()),
12
+ })
13
+
14
+ export default class COTQuery {
15
+ api: TAKAPI;
16
+
17
+ constructor(api: TAKAPI) {
18
+ this.api = api;
19
+ }
20
+
21
+ async singleFeat(uid: string): Promise<Static<typeof Feature.Feature>> {
22
+ const cotstr = await this.single(uid);
23
+ return new CoT(cotstr).to_geojson();
24
+ }
25
+
26
+ async single(uid: string): Promise<string> {
27
+ const url = new URL(`/Marti/api/cot/xml/${encodeURIComponent(uid)}`, this.api.url);
28
+
29
+ const res = await this.api.fetch(url, {
30
+ method: 'GET'
31
+ }, true);
32
+
33
+ const body = await res.text();
34
+
35
+ if (body.trim().length === 0) {
36
+ throw new Err(404, null, 'CoT by that UID Not Found');
37
+ }
38
+
39
+ return body;
40
+ }
41
+
42
+ async historyFeats(uid: string, opts?: Static<typeof HistoryOptions>): Promise<Array<Static<typeof Feature.Feature>>> {
43
+ const feats: Static<typeof Feature.Feature>[] = [];
44
+
45
+ const res: any = xmljs.xml2js(await this.history(uid, opts), { compact: true });
46
+
47
+ if (!Object.keys(res).length || !Object.keys(res.events).length) return feats;
48
+ if (!res.events.event || (Array.isArray(res.events.event) && !res.events.event.length)) return feats;
49
+
50
+ for (const event of Array.isArray(res.events.event) ? res.events.event : [res.events.event] ) {
51
+ feats.push((new CoT({ event })).to_geojson());
52
+ }
53
+
54
+ return feats;
55
+ }
56
+
57
+ async history(uid: string, opts?: Static<typeof HistoryOptions>): Promise<string> {
58
+ const url = new URL(`/Marti/api/cot/xml/${encodeURIComponent(uid)}/all`, this.api.url);
59
+
60
+ if (opts) {
61
+ let q: keyof Static<typeof HistoryOptions>;
62
+ for (q in opts) {
63
+ if (opts[q] !== undefined) {
64
+ url.searchParams.append(q, String(opts[q]));
65
+ }
66
+ }
67
+ }
68
+
69
+ const res = await this.api.fetch(url, {
70
+ method: 'GET'
71
+ }, true);
72
+
73
+ const body = await res.text();
74
+
75
+ console.error(body);
76
+
77
+ return body;
78
+ }
79
+ }
@@ -0,0 +1,84 @@
1
+ import TAKAPI from '../api.js';
2
+ import { Type, Static } from '@sinclair/typebox';
3
+ import { Group } from './groups.js';
4
+ import { TAKList } from './types.js';
5
+
6
+ export const Subscription = Type.Object({
7
+ dn: Type.Union([Type.String(), Type.Null()]),
8
+ callsign: Type.String(),
9
+ clientUid: Type.String(),
10
+ lastReportMilliseconds: Type.Integer(),
11
+ takClient: Type.String(),
12
+ takVersion: Type.String(),
13
+ username: Type.String(),
14
+ groups: Type.Array(Group),
15
+ role: Type.String(),
16
+ team: Type.String(),
17
+ ipAddress: Type.String(),
18
+ port: Type.String(),
19
+ pendingWrites: Type.Integer(),
20
+ numProcessed: Type.Integer(),
21
+ protocol: Type.String(),
22
+ xpath: Type.Union([Type.String(), Type.Null()]),
23
+ subscriptionUid: Type.String(),
24
+ appFramerate: Type.String(),
25
+ battery: Type.String(),
26
+ batteryStatus: Type.String(),
27
+ batteryTemp: Type.String(),
28
+ deviceDataRx: Type.String(),
29
+ deviceDataTx: Type.String(),
30
+ heapCurrentSize: Type.String(),
31
+ heapFreeSize: Type.String(),
32
+ heapMaxSize: Type.String(),
33
+ deviceIPAddress: Type.String(),
34
+ storageAvailable: Type.String(),
35
+ storageTotal: Type.String(),
36
+ incognito: Type.Boolean(),
37
+ handlerType: Type.String(),
38
+ lastReportDiffMilliseconds: Type.Integer()
39
+ });
40
+
41
+ export const ListSubscriptionInput = Type.Object({
42
+ sortBy: Type.String({
43
+ default: 'CALLSIGN',
44
+ enum: ['CALLSIGN', 'UID']
45
+ }),
46
+ direction: Type.String({
47
+ default: 'ASCENDING',
48
+ enum: ['ASCENDING', 'DESCENDING']
49
+ }),
50
+ page: Type.Integer({
51
+ default: -1
52
+ }),
53
+ limit: Type.Integer({
54
+ default: -1
55
+ })
56
+ })
57
+
58
+ export const TAKList_Subscription = TAKList(Subscription);
59
+
60
+
61
+ export default class {
62
+ api: TAKAPI;
63
+
64
+ constructor(api: TAKAPI) {
65
+ this.api = api;
66
+ }
67
+
68
+ async list(
69
+ query: Static<typeof ListSubscriptionInput>
70
+ ): Promise<Static<typeof TAKList_Subscription>> {
71
+ const url = new URL(`/Marti/api/subscriptions/all`, this.api.url);
72
+
73
+ let q: keyof Static<typeof ListSubscriptionInput>;
74
+ for (q in query) {
75
+ if (query[q] !== undefined) {
76
+ url.searchParams.append(q, String(query[q]));
77
+ }
78
+ }
79
+
80
+ return await this.api.fetch(url, {
81
+ method: 'GET'
82
+ });
83
+ }
84
+ }
@@ -0,0 +1,43 @@
1
+ import { TSchema, Type } from '@sinclair/typebox';
2
+
3
+ export const TAKItem = <T extends TSchema>(T: T) => {
4
+ return Type.Object({
5
+ version: Type.String(),
6
+ type: Type.String(),
7
+ data: T,
8
+ messages: Type.Optional(Type.Array(Type.String())),
9
+ nodeId: Type.Optional(Type.String())
10
+ })
11
+ };
12
+
13
+ export const TAKList = <T extends TSchema>(T: T) => {
14
+ return TAKItem(Type.Array(T));
15
+ }
16
+
17
+ export enum TAKGroup {
18
+ WHITE = "White",
19
+ YELLOW = "Yellow",
20
+ ORANGE = "Orange",
21
+ MAGENTA = "Magenta",
22
+ RED = "Red",
23
+ MAROON = "Maroon",
24
+ PURPLE = "Purple",
25
+ DARK_BLUE = "Dark Blue",
26
+ BLUE = "Blue",
27
+ CYAN = "Cyan",
28
+ TEAL = "Teal",
29
+ GREEN = "Green",
30
+ DARK_GREEN = "Dark Green",
31
+ BROWN = "Brown"
32
+ }
33
+
34
+ export enum TAKRole {
35
+ TEAM_MEMBER = "Team Member",
36
+ TEAM_LEAD = "Team Lead",
37
+ HQ = "HQ",
38
+ SNIPER = "Sniper",
39
+ MEDIC = "Medic",
40
+ FORWARD_OBSERVER = "Forward Observer",
41
+ RTO = "RTO",
42
+ K9 = "K9"
43
+ }
@@ -0,0 +1,155 @@
1
+ import Err from '@openaddresses/batch-error';
2
+ import { Static, Type } from '@sinclair/typebox';
3
+ import { randomUUID } from 'node:crypto';
4
+ import TAKAPI from '../api.js';
5
+
6
+ export const FeedInput = Type.Object({
7
+ uuid: Type.Optional(Type.String()),
8
+ active: Type.Boolean({ default: true }),
9
+ alias: Type.String(),
10
+ url: Type.String(),
11
+ });
12
+
13
+ export const VideoConnectionInput = Type.Object({
14
+ uuid: Type.Optional(Type.String()),
15
+ active: Type.Boolean({
16
+ default: true
17
+ }),
18
+ alias: Type.String(),
19
+ feeds: Type.Array(FeedInput)
20
+ });
21
+
22
+ export const Feed = Type.Object({
23
+ uuid: Type.String(),
24
+ active: Type.Boolean(),
25
+ alias: Type.String(),
26
+ url: Type.String(),
27
+
28
+ order: Type.Union([Type.Integer(), Type.Null()]),
29
+ macAddress: Type.String(),
30
+ roverPort: Type.String(),
31
+ ignoreEmbeddedKLV: Type.String(),
32
+ source: Type.Union([Type.String(), Type.Null()]),
33
+ networkTimeout: Type.String(),
34
+ bufferTime: Type.String(),
35
+ rtspReliable: Type.String(),
36
+ thumbnail: Type.Union([Type.String(), Type.Null()]),
37
+ classification: Type.Union([Type.String(), Type.Null()]),
38
+ latitude: Type.Union([Type.String(), Type.Null()]),
39
+ longitude: Type.Union([Type.String(), Type.Null()]),
40
+ fov: Type.Union([Type.String(), Type.Null()]),
41
+ heading: Type.Union([Type.String(), Type.Null()]),
42
+ range: Type.Union([Type.String(), Type.Null()]),
43
+ width: Type.Union([Type.Integer(), Type.Null()]),
44
+ height: Type.Union([Type.Integer(), Type.Null()]),
45
+ bitrate: Type.Union([Type.Integer(), Type.Null()]),
46
+ });
47
+
48
+ export const VideoConnection = Type.Object({
49
+ uuid: Type.String(),
50
+ active: Type.Boolean(),
51
+ alias: Type.String(),
52
+ thumbnail: Type.Union([Type.String(), Type.Null()]),
53
+ classification: Type.Union([Type.String(), Type.Null()]),
54
+ feeds: Type.Array(Feed)
55
+ })
56
+
57
+ export const VideoConnectionList = Type.Object({
58
+ videoConnections: Type.Array(VideoConnection)
59
+ });
60
+
61
+ export const VideoConnectionListInput = Type.Object({
62
+ protocol: Type.Optional(Type.String())
63
+ })
64
+
65
+ export default class {
66
+ api: TAKAPI;
67
+
68
+ constructor(api: TAKAPI) {
69
+ this.api = api;
70
+ }
71
+
72
+ async list(
73
+ query: Static<typeof VideoConnectionListInput> = {}
74
+ ): Promise<Static<typeof VideoConnectionList>> {
75
+ const url = new URL(`/Marti/api/video`, this.api.url);
76
+
77
+ let q: keyof Static<typeof VideoConnectionListInput>;
78
+ for (q in query) {
79
+ if (query[q] !== undefined) {
80
+ url.searchParams.append(q, String(query[q]));
81
+ }
82
+ }
83
+
84
+ return await this.api.fetch(url, {
85
+ method: 'GET'
86
+ });
87
+ }
88
+
89
+ async update(
90
+ connection: Static<typeof VideoConnectionInput>
91
+ ): Promise<Static<typeof VideoConnection>> {
92
+ const url = new URL(`/Marti/api/video/${connection.uuid}`, this.api.url);
93
+
94
+ if (!connection.uuid) throw new Err(400, null, 'UUID must be set when updating Video');
95
+
96
+ await this.api.fetch(url, {
97
+ method: 'PUT',
98
+ body: {
99
+ ...connection,
100
+ feeds: connection.feeds.map((feed) => {
101
+ if (!feed.uuid) feed.uuid = randomUUID();
102
+ return feed;
103
+ })
104
+ }
105
+ });
106
+
107
+ return await this.get(connection.uuid);
108
+ }
109
+
110
+ async create(
111
+ connection: Static<typeof VideoConnectionInput>
112
+ ): Promise<Static<typeof VideoConnection>> {
113
+ const url = new URL(`/Marti/api/video`, this.api.url);
114
+
115
+ const uuid = connection.uuid || randomUUID();
116
+
117
+ await this.api.fetch(url, {
118
+ method: 'POST',
119
+ body: {
120
+ videoConnections: [{
121
+ uuid,
122
+ ...connection,
123
+ feeds: connection.feeds.map((feed) => {
124
+ return {
125
+ uuid: randomUUID(),
126
+ ...feed,
127
+ }
128
+ })
129
+ }]
130
+ }
131
+ });
132
+
133
+ return await this.get(uuid);
134
+ }
135
+
136
+ async get(
137
+ uid: string
138
+ ): Promise<Static<typeof VideoConnection>> {
139
+ const url = new URL(`/Marti/api/video/${encodeURIComponent(uid)}`, this.api.url);
140
+
141
+ return await this.api.fetch(url, {
142
+ method: 'GET'
143
+ });
144
+ }
145
+
146
+ async delete(
147
+ uid: string
148
+ ): Promise<void> {
149
+ const url = new URL(`/Marti/api/video/${encodeURIComponent(uid)}`, this.api.url);
150
+
151
+ await this.api.fetch(url, {
152
+ method: 'DELETE'
153
+ });
154
+ }
155
+ }
package/lib/api.ts ADDED
@@ -0,0 +1,136 @@
1
+ import FormData from 'form-data';
2
+ import OAuth from './api/oauth.js';
3
+ import Package from './api/package.js';
4
+ import Query from './api/query.js';
5
+ import Mission from './api/mission.js';
6
+ import MissionLog from './api/mission-log.js';
7
+ import MissionLayer from './api/mission-layer.js';
8
+ import Credentials from './api/credentials.js';
9
+ import Contacts from './api/contacts.js';
10
+ import Files from './api/files.js';
11
+ import Group from './api/groups.js';
12
+ import Subscription from './api/subscriptions.js';
13
+ import Video from './api/video.js';
14
+ import Export from './api/export.js';
15
+ import Err from '@openaddresses/batch-error';
16
+ import * as auth from './auth.js';
17
+
18
+ /**
19
+ * Handle TAK HTTP API Operations
20
+ * @class
21
+ */
22
+ export default class TAKAPI {
23
+ auth: auth.APIAuth;
24
+ url: URL;
25
+ Package: Package;
26
+ OAuth: OAuth;
27
+ Mission: Mission;
28
+ MissionLog: MissionLog;
29
+ MissionLayer: MissionLayer;
30
+ Credentials: Credentials;
31
+ Contacts: Contacts;
32
+ Subscription: Subscription;
33
+ Group: Group;
34
+ Video: Video;
35
+ Export: Export;
36
+ Query: Query;
37
+ Files: Files;
38
+
39
+ constructor(url: URL, auth: auth.APIAuth) {
40
+ this.url = url;
41
+ this.auth = auth;
42
+
43
+ this.Query = new Query(this);
44
+ this.Package = new Package(this);
45
+ this.OAuth = new OAuth(this);
46
+ this.Export = new Export(this);
47
+ this.Mission = new Mission(this);
48
+ this.MissionLog = new MissionLog(this);
49
+ this.MissionLayer = new MissionLayer(this);
50
+ this.Credentials = new Credentials(this);
51
+ this.Contacts = new Contacts(this);
52
+ this.Subscription = new Subscription(this);
53
+ this.Group = new Group(this);
54
+ this.Video = new Video(this);
55
+ this.Files = new Files(this);
56
+ }
57
+
58
+ static async init(url: URL, auth: auth.APIAuth): Promise<TAKAPI> {
59
+ const api = new TAKAPI(url, auth);
60
+
61
+ await api.auth.init(api);
62
+
63
+ return api;
64
+ }
65
+
66
+ stdurl(url: string | URL) {
67
+ try {
68
+ url = new URL(url);
69
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
70
+ } catch (err) {
71
+ url = new URL(url, this.url);
72
+ }
73
+
74
+ return url;
75
+ }
76
+
77
+ /**
78
+ * Standardize interactions with the backend API
79
+ *
80
+ * @param {URL|String} url - Full URL or API fragment to request
81
+ * @param {Object} [opts={}] - Options
82
+ */
83
+ async fetch(url: URL, opts: any = {}, raw=false) {
84
+ url = this.stdurl(url);
85
+
86
+ try {
87
+ if (!opts.headers) opts.headers = {};
88
+
89
+ if (
90
+ (isPlainObject(opts.body) || Array.isArray(opts.body))
91
+ && (
92
+ !opts.headers['Content-Type']
93
+ || opts.headers['Content-Type'].startsWith('application/json')
94
+ )
95
+ ) {
96
+ opts.body = JSON.stringify(opts.body);
97
+ opts.headers['Content-Type'] = 'application/json';
98
+ } else if (opts.body instanceof FormData) {
99
+ opts.headers = opts.body.getHeaders();
100
+ } else if (opts.body instanceof URLSearchParams) {
101
+ opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'
102
+ opts.body = String(opts.body);
103
+ }
104
+
105
+ const res = await this.auth.fetch(this, url, opts)
106
+
107
+ if (raw) return res;
108
+
109
+ let bdy: any = {};
110
+
111
+ if ((res.status < 200 || res.status >= 400)) {
112
+ try {
113
+ bdy = await res.text();
114
+ } catch (err) {
115
+ console.error(err);
116
+ bdy = null;
117
+ }
118
+
119
+ throw new Err(res.status, null, bdy || `Status Code: ${res.status}`);
120
+ }
121
+
122
+ if (res.headers.get('content-type') === 'application/json') {
123
+ return await res.json();
124
+ } else {
125
+ return await res.text();
126
+ }
127
+ } catch (err) {
128
+ if (err instanceof Error && err.name === 'PublicError') throw err;
129
+ throw new Err(400, null, err instanceof Error ? err.message : String(err));
130
+ }
131
+ }
132
+ }
133
+
134
+ function isPlainObject(value: object) {
135
+ return value?.constructor === Object;
136
+ }