@tak-ps/node-tak
Version:
Lightweight JavaScript library for communicating with TAK Server
259 lines (227 loc) • 7.31 kB
text/typescript
import OAuth from './api/oauth.js';
import Package from './api/package.js';
import Certificate from './api/certificate.js';
import Query from './api/query.js';
import Locate from './api/locate.js';
import Mission, { MissionSubscriberRole } from './api/mission.js';
import MissionInvite, { MissionInviteType } from './api/mission-invite.js';
import MissionLog from './api/mission-log.js';
import MissionLayer, { MissionLayerType } from './api/mission-layer.js';
import Credentials from './api/credentials.js';
import Security from './api/security.js';
import Contacts from './api/contacts.js';
import Client from './api/client.js';
import Profile from './api/profile.js';
import Files from './api/files.js';
import Iconsets from './api/iconsets.js';
import Injectors from './api/injectors.js';
import Repeater from './api/repeater.js';
import Group from './api/groups.js';
import Subscription from './api/subscriptions.js';
import Video from './api/video.js';
import Export from './api/export.js';
import Err from '@openaddresses/batch-error';
import * as auth from './auth.js';
type ErrorWithCause = Error & {
code?: unknown;
cause?: unknown;
};
function formatFetchError(err: unknown): string {
if (!(err instanceof Error)) return String(err);
const root = err as ErrorWithCause;
const messages = new Set<string>();
const push = (value: unknown) => {
if (!value) return;
const message = String(value).trim();
if (!message || message === 'fetch failed') return;
messages.add(message);
};
push(root.code);
push(root.message);
let cause: unknown = root.cause;
while (cause) {
if (cause instanceof Error) {
const nested = cause as ErrorWithCause;
push(nested.code);
push(nested.message);
cause = nested.cause;
} else {
push(cause);
break;
}
}
if (messages.size) return Array.from(messages).join(': ');
return root.message;
}
export const CommandList: Record<string, keyof TAKAPI> = {
package: 'Package',
security: 'Security',
profile: 'Profile',
oauth: 'OAuth',
locate: 'Locate',
mission: 'Mission',
'mission-invite': 'MissionInvite',
'mission-log': 'MissionLog',
'mission-layer': 'MissionLayer',
credential: 'Credentials',
certificate: 'Certificate',
iconsets: 'Iconsets',
contact: 'Contacts',
client: 'Client',
subscription: 'Subscription',
injector: 'Injectors',
repeater: 'Repeater',
group: 'Group',
video: 'Video',
export: 'Export',
query: 'Query',
file: 'Files'
}
/**
* Handle TAK HTTP API Operations
* @class
*/
export default class TAKAPI {
auth: auth.APIAuth;
url: URL;
Package: Package;
OAuth: OAuth;
Locate: Locate;
Security: Security;
Iconsets: Iconsets;
Mission: Mission;
MissionLog: MissionLog;
MissionInvite: MissionInvite;
MissionLayer: MissionLayer;
Credentials: Credentials;
Client: Client;
Contacts: Contacts;
Certificate: Certificate;
Subscription: Subscription;
Profile: Profile;
Injectors: Injectors;
Repeater: Repeater;
Group: Group;
Video: Video;
Export: Export;
Query: Query;
Files: Files;
constructor(url: URL, auth: auth.APIAuth) {
this.url = url;
this.auth = auth;
this.Query = new Query(this);
this.Security = new Security(this);
this.Locate = new Locate(this);
this.Package = new Package(this);
this.Profile = new Profile(this);
this.Certificate = new Certificate(this);
this.OAuth = new OAuth(this);
this.Export = new Export(this);
this.Iconsets = new Iconsets(this);
this.Mission = new Mission(this);
this.MissionLog = new MissionLog(this);
this.MissionInvite = new MissionInvite(this);
this.MissionLayer = new MissionLayer(this);
this.Client = new Client(this);
this.Credentials = new Credentials(this);
this.Contacts = new Contacts(this);
this.Subscription = new Subscription(this);
this.Group = new Group(this);
this.Video = new Video(this);
this.Injectors = new Injectors(this);
this.Repeater = new Repeater(this);
this.Files = new Files(this);
}
static async init(url: URL, auth: auth.APIAuth): Promise<TAKAPI> {
const api = new TAKAPI(url, auth);
await api.auth.init(api);
return api;
}
stdurl(url: string | URL) {
try {
url = new URL(url);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (err) {
url = new URL(url, this.url);
}
return url;
}
/**
* Standardize interactions with the backend API
*
* @param {URL|String} url - Full URL or API fragment to request
* @param {Object} [opts={}] - Options
*/
async fetch(url: URL, opts: any = {}, raw=false) {
url = this.stdurl(url);
try {
if (!opts.headers) opts.headers = {};
if (
(isPlainObject(opts.body) || Array.isArray(opts.body))
&& (
!opts.headers['Content-Type']
|| opts.headers['Content-Type'].startsWith('application/json')
)
) {
opts.body = JSON.stringify(opts.body);
opts.headers['Content-Type'] = 'application/json';
} else if (typeof FormData !== 'undefined' && opts.body instanceof FormData) {
// Let fetch/undici set multipart headers and boundary automatically.
delete opts.headers['Content-Type'];
} else if (opts.body instanceof URLSearchParams) {
opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'
opts.body = String(opts.body);
}
const res = await this.auth.fetch(this, url, opts)
if (raw) return res;
let bdy: any = {};
if ((res.status < 200 || res.status >= 400)) {
try {
bdy = await res.text();
} catch (err) {
console.error(err);
bdy = null;
}
throw new Err(res.status, null, bdy || `Status Code: ${res.status}`);
}
if (res.headers.get('content-type') === 'application/json') {
return await res.json();
} else {
return await res.text();
}
} catch (err) {
if (err instanceof Error && err.name === 'PublicError') throw err;
throw new Err(400, null, formatFetchError(err));
}
}
}
function isPlainObject(value: object) {
return value?.constructor === Object;
}
export {
OAuth,
Package,
Certificate,
Query,
Locate,
Mission,
MissionInvite,
MissionLog,
MissionLayer,
Client,
Credentials,
Security,
Contacts,
Profile,
Files,
Iconsets,
Injectors,
Repeater,
Group,
Subscription,
Video,
Export,
MissionSubscriberRole,
MissionLayerType,
MissionInviteType
};