diff --git a/signalk-client/apps-api.ts b/signalk-client/apps-api.ts index f099f9e..6470c49 100644 --- a/signalk-client/apps-api.ts +++ b/signalk-client/apps-api.ts @@ -1,11 +1,11 @@ /** Signal K server Apps API */ export class SignalKApps { /** apps API endpoint. */ - public endpoint = ""; + public endpoint = ''; /** Return List of installed apps. */ async list() { - const ep = this.endpoint.indexOf("webapps") === -1 + const ep = this.endpoint.indexOf('webapps') === -1 ? `${this.endpoint}list` : this.endpoint; const response = await fetch(ep); diff --git a/signalk-client/http-api.ts b/signalk-client/http-api.ts index edc3a2a..ab23a51 100644 --- a/signalk-client/http-api.ts +++ b/signalk-client/http-api.ts @@ -1,18 +1,18 @@ -import { Alarm, AlarmType, Path } from "./utils.ts"; -import { SKServer } from "./signalk-client.ts"; -import { debug } from "./mod.ts"; +import { Alarm, AlarmType, Path } from './utils.ts'; +import { SKServer } from './signalk-client.ts'; +import { debug } from './mod.ts'; /** Signal K HTTP API operations */ export class SignalKHttp { /** Authentication token * @private */ - private _token = ""; + private _token = ''; /** Signal K server information */ - public server: SKServer = { version: "", id: "" }; + public server: SKServer = { version: '', id: '' }; /** Connection endpoint */ - public endpoint = ""; + public endpoint = ''; /** API version to use */ public version = 1; @@ -62,14 +62,14 @@ export class SignalKHttp { } let ep: string; let path: string; - if (typeof p1 === "number") { - ep = this.endpoint.replace(new RegExp("/v[0-9]+/"), `/v${p1}/`); + if (typeof p1 === 'number') { + ep = this.endpoint.replace(new RegExp('/v[0-9]+/'), `/v${p1}/`); path = Path.dotToSlash(p2); } else { ep = this.endpoint; path = Path.dotToSlash(p1); } - if (path[0] === "/") { + if (path[0] === '/') { path = path.slice(1); } const url = `${ep}${path}`; @@ -114,8 +114,8 @@ export class SignalKHttp { let path: string; let value; let msg; - if (typeof p1 === "number") { - ep = this.endpoint.replace(new RegExp("/v[0-9]+/"), `/v${p1}/`); + if (typeof p1 === 'number') { + ep = this.endpoint.replace(new RegExp('/v[0-9]+/'), `/v${p1}/`); path = Path.dotToSlash(p2); value = p3; msg = this.parseApiPut(p1, value); @@ -125,19 +125,19 @@ export class SignalKHttp { value = p2; msg = this.parseApiPut(this.version, value); } - if (path[0] === "/") { + if (path[0] === '/') { path = path.slice(1); } const url = `${ep}${path}`; debug(`put ${url}`); - const headers = new Headers({ "Content-Type": "application/json" }); + const headers = new Headers({ 'Content-Type': 'application/json' }); if (this._token) { - headers.append("Authorization", `JWT ${this._token}`); + headers.append('Authorization', `JWT ${this._token}`); } const options: RequestInit = { - method: "PUT", + method: 'PUT', headers: headers, body: JSON.stringify(msg), }; @@ -176,32 +176,32 @@ export class SignalKHttp { let context: string; let value; let msg; - if (typeof p1 === "number") { - ep = this.endpoint.replace(new RegExp("/v[0-9]+/"), `/v${p1}/`); - context = p2 ? `${Path.contextToPath(p2)}/` : ""; + if (typeof p1 === 'number') { + ep = this.endpoint.replace(new RegExp('/v[0-9]+/'), `/v${p1}/`); + context = p2 ? `${Path.contextToPath(p2)}/` : ''; path = Path.dotToSlash(p3); value = p4; msg = this.parseApiPut(p1, value); } else { ep = this.endpoint; - context = p1 ? `${Path.contextToPath(p1)}/` : ""; + context = p1 ? `${Path.contextToPath(p1)}/` : ''; path = Path.dotToSlash(p2); value = p3; msg = this.parseApiPut(this.version, value); } - if (path[0] === "/") { + if (path[0] === '/') { path = path.slice(1); } const url = ep + context + path; debug(`put ${url}`); - const headers = new Headers({ "Content-Type": "application/json" }); + const headers = new Headers({ 'Content-Type': 'application/json' }); if (this._token) { - headers.append("Authorization", `JWT ${this._token}`); + headers.append('Authorization', `JWT ${this._token}`); } const options: RequestInit = { - method: "PUT", + method: 'PUT', headers: headers, body: JSON.stringify(msg), }; @@ -226,8 +226,8 @@ export class SignalKHttp { let ep: string; let path: string; let value; - if (typeof p1 === "number") { - ep = this.endpoint.replace(new RegExp("/v[0-9]+/"), `/v${p1}/`); + if (typeof p1 === 'number') { + ep = this.endpoint.replace(new RegExp('/v[0-9]+/'), `/v${p1}/`); path = Path.dotToSlash(p2); value = p3; } else { @@ -235,18 +235,18 @@ export class SignalKHttp { path = Path.dotToSlash(p1); value = p2; } - if (path[0] === "/") { + if (path[0] === '/') { path = path.slice(1); } const url = `${ep}${path}`; debug(`post ${url}`); - const headers = new Headers({ "Content-Type": "application/json" }); + const headers = new Headers({ 'Content-Type': 'application/json' }); if (this._token) { - headers.append("Authorization", `JWT ${this._token}`); + headers.append('Authorization', `JWT ${this._token}`); } const options: RequestInit = { - method: "POST", + method: 'POST', headers: headers, body: JSON.stringify(value), }; @@ -268,25 +268,25 @@ export class SignalKHttp { } let ep: string; let path: string; - if (typeof p1 === "number") { - ep = this.endpoint.replace(new RegExp("/v[0-9]+/"), `/v${p1}/`); + if (typeof p1 === 'number') { + ep = this.endpoint.replace(new RegExp('/v[0-9]+/'), `/v${p1}/`); path = Path.dotToSlash(p2); } else { ep = this.endpoint; path = Path.dotToSlash(p1); } - if (path[0] === "/") { + if (path[0] === '/') { path = path.slice(1); } const url = `${ep}${path}`; debug(`get ${url}`); - const headers = new Headers({ "Content-Type": "application/json" }); + const headers = new Headers({ 'Content-Type': 'application/json' }); if (this._token) { - headers.append("Authorization", `JWT ${this._token}`); + headers.append('Authorization', `JWT ${this._token}`); } const options: RequestInit = { - method: "DELETE", + method: 'DELETE', headers: headers, }; return await fetch(url, options); @@ -304,10 +304,10 @@ export class SignalKHttp { */ raiseAlarm(context: string, name: string, alarm: Alarm): Promise; raiseAlarm(context: string, type: AlarmType, alarm: Alarm): Promise; - raiseAlarm(context = "*", alarmId: string | AlarmType, alarm: Alarm) { + raiseAlarm(context = '*', alarmId: string | AlarmType, alarm: Alarm) { let path: string; - if (typeof alarmId === "string") { - path = alarmId.indexOf("notifications.") === -1 + if (typeof alarmId === 'string') { + path = alarmId.indexOf('notifications.') === -1 ? `notifications.${alarmId}` : alarmId; } else { @@ -320,8 +320,8 @@ export class SignalKHttp { * @param context Signal K context * @param name Alarm name. */ - clearAlarm(context = "*", name: string): Promise { - const path = name.indexOf("notifications.") === -1 + clearAlarm(context = '*', name: string): Promise { + const path = name.indexOf('notifications.') === -1 ? `notifications.${name}` : name; return this.putWithContext(context, path, null); diff --git a/signalk-client/mod.ts b/signalk-client/mod.ts index 606eead..1bd76be 100644 --- a/signalk-client/mod.ts +++ b/signalk-client/mod.ts @@ -2,11 +2,11 @@ * Signal K Client *****************************/ -export * from "./signalk-client.ts"; -export * from "./utils.ts"; -export * from "./stream-api.ts"; -export * from "./http-api.ts"; -export * from "./apps-api.ts"; +export * from './signalk-client.ts'; +export * from './utils.ts'; +export * from './stream-api.ts'; +export * from './http-api.ts'; +export * from './apps-api.ts'; let _isDev = false; @@ -18,6 +18,6 @@ export function setDev(val?: boolean) { /** Print debug messages to console (if development mode) */ export function debug(...args: any[]) { if (_isDev) { - console.log(`%cSignalKClient: `, "color: yellow", ...args); + console.log(`%cSignalKClient: `, 'color: yellow', ...args); } } diff --git a/signalk-client/signalk-client.ts b/signalk-client/signalk-client.ts index 8e03400..54547e4 100644 --- a/signalk-client/signalk-client.ts +++ b/signalk-client/signalk-client.ts @@ -1,8 +1,8 @@ -import { SignalKHttp } from "./http-api.ts"; -import { SignalKStream } from "./stream-api.ts"; -import { SignalKApps } from "./apps-api.ts"; -import { Message, Path } from "./utils.ts"; -import { debug } from "./mod.ts"; +import { SignalKHttp } from './http-api.ts'; +import { SignalKStream } from './stream-api.ts'; +import { SignalKApps } from './apps-api.ts'; +import { Message, Path } from './utils.ts'; +import { debug } from './mod.ts'; interface Server_Info { endpoints: { [key: string]: EndPoint }; @@ -12,8 +12,8 @@ interface Server_Info { interface EndPoint { version: string; - "signalk-http": string; - "signalk-ws": string; + 'signalk-http': string; + 'signalk-ws': string; } /** Signal K Server information */ @@ -34,15 +34,15 @@ interface PlaybackOptions { } interface JSON_Patch { - op: "add" | "replace" | "remove" | "copy" | "move" | "test"; + op: 'add' | 'replace' | 'remove' | 'copy' | 'move' | 'test'; path: string; value: unknown; } /** Application data contexts */ export enum APPDATA_CONTEXT { - USER = "user", - GLOBAL = "global", + USER = 'user', + GLOBAL = 'global', } /** Signal K Client class */ @@ -50,7 +50,7 @@ export class SignalKClient { /** Signal K server hostname / ip address. * @private */ - private hostname = "localhost"; + private hostname = 'localhost'; /** Port to connect on. * @private */ @@ -58,17 +58,17 @@ export class SignalKClient { /** Protocol to use http / https. * @private */ - private protocol = ""; + private protocol = ''; /** Signal K API version to use * @private */ - private _version = "v1"; + private _version = 'v1'; /** Authentication token value * @private */ - private _token = ""; // token for when security is enabled on the server + private _token = ''; // token for when security is enabled on the server /** endpoints to fallback to if hello response is not received. * @private @@ -76,20 +76,20 @@ export class SignalKClient { private fallbackEndpoints: HelloResponse = { endpoints: { v1: { - version: "1.0.0", - "signalk-http": "", - "signalk-ws": "", + version: '1.0.0', + 'signalk-http': '', + 'signalk-ws': '', }, }, - server: { id: "fallback", version: "1.43.0" }, + server: { id: 'fallback', version: '1.43.0' }, }; /** Server information block */ public server: Server_Info = { endpoints: {}, info: { - version: "", - id: "", + version: '', + id: '', }, apiVersions: [], }; @@ -111,7 +111,7 @@ export class SignalKClient { * @param val Major version number of API. e.g. 1 */ set version(val: number) { - const v: string = "v" + val; + const v: string = 'v' + val; if (this.server.apiVersions.length === 0) { this._version = v; debug(`Signal K api version set to: ${v}`); @@ -175,19 +175,19 @@ export class SignalKClient { ) { this.hostname = hostname ? hostname : this.hostname; if (useSSL) { - this.protocol = "https"; + this.protocol = 'https'; this.port = port || 443; } else { - this.protocol = "http"; + this.protocol = 'http'; this.port = port || 80; } const httpUrl = `${this.protocol}://${this.hostname}:${this.port}`; - const wsUrl = `${useSSL ? "wss" : "ws"}://${this.hostname}:${this.port}`; + const wsUrl = `${useSSL ? 'wss' : 'ws'}://${this.hostname}:${this.port}`; this.fallbackEndpoints.endpoints.v1[ - "signalk-http" + 'signalk-http' ] = `${httpUrl}/signalk/v1/api/`; this.fallbackEndpoints.endpoints.v1[ - "signalk-ws" + 'signalk-ws' ] = `${wsUrl}/signalk/v1/stream`; } @@ -204,7 +204,7 @@ export class SignalKClient { useSSL = false, ): Promise<{ [key: string]: unknown }> { this.init(hostname, port, useSSL); - return this.get("/signalk"); + return this.get('/signalk'); } /** connect to server (endpoint discovery) and DO NOT open Stream @@ -217,7 +217,7 @@ export class SignalKClient { port = 3000, useSSL = false, ): Promise { - debug("Contacting Signal K server........."); + debug('Contacting Signal K server.........'); try { const response: unknown = await this.hello(hostname, port, useSSL); // ** discover endpoints ** @@ -261,7 +261,7 @@ export class SignalKClient { hostname: string = this.hostname, port = 3000, useSSL = false, - subscribe = "", + subscribe = '', ): Promise { return new Promise((resolve, reject) => { this.connect(hostname, port, useSSL) @@ -297,7 +297,7 @@ export class SignalKClient { this.connect(hostname, port, useSSL) .then(() => { // ** connect to playback api at preferred version else fall back to default version - this.openPlayback("", options, this._token); + this.openPlayback('', options, this._token); resolve(true); }) .catch((e) => { @@ -316,12 +316,12 @@ export class SignalKClient { subscribe?: string, token?: string, ): boolean { - debug("openStream........."); + debug('openStream.........'); if (!url) { // connect to stream api at discovered endpoint url = this.resolveStreamEndpoint(); if (!url) { - throw new Error("Server has no advertised Stream endpoints!"); + throw new Error('Server has no advertised Stream endpoints!'); } } this.stream.open(url, subscribe, token); @@ -338,24 +338,24 @@ export class SignalKClient { options?: PlaybackOptions, token?: string, ): boolean { - debug("openPlayback........."); + debug('openPlayback.........'); if (!url) { // connect to stream api at discovered endpoint url = this.resolveStreamEndpoint(); if (!url) { - throw new Error("Server has no advertised Stream endpoints!"); + throw new Error('Server has no advertised Stream endpoints!'); } - url = url.replace("stream", "playback"); + url = url.replace('stream', 'playback'); } - let pb = ""; - let subscribe: string | undefined = ""; - if (options && typeof options === "object") { + let pb = ''; + let subscribe: string | undefined = ''; + if (options && typeof options === 'object') { pb += options.startTime - ? "?startTime=" + - options.startTime.slice(0, options.startTime.indexOf(".")) + - "Z" - : ""; - pb += options.playbackRate ? `&playbackRate=${options.playbackRate}` : ""; + ? '?startTime=' + + options.startTime.slice(0, options.startTime.indexOf('.')) + + 'Z' + : ''; + pb += options.playbackRate ? `&playbackRate=${options.playbackRate}` : ''; subscribe = options.subscribe ? options.subscribe : undefined; } this.stream.open(url + pb, subscribe, token); @@ -369,12 +369,12 @@ export class SignalKClient { if (this.proxied) { this.server.endpoints = this.fallbackEndpoints.endpoints; } else { - this.server.endpoints = response && response["endpoints"] - ? response["endpoints"] + this.server.endpoints = response && response['endpoints'] + ? response['endpoints'] : this.fallbackEndpoints.endpoints; } - this.server.info = response && response["server"] - ? response["server"] + this.server.info = response && response['server'] + ? response['server'] : this.fallbackEndpoints.server; this.server.apiVersions = this.server.endpoints ? Object.keys(this.server.endpoints) @@ -386,42 +386,42 @@ export class SignalKClient { /** Return signalk apps API url */ private resolveAppsEndpoint(): string { - return this.resolveHttpEndpoint().replace("api", "apps"); + return this.resolveHttpEndpoint().replace('api', 'apps'); } /** Return preferred WS stream url */ private resolveStreamEndpoint(): string { if ( this.server.endpoints[this._version] && - this.server.endpoints[this._version]["signalk-ws"] + this.server.endpoints[this._version]['signalk-ws'] ) { debug(`Connecting endpoint version: ${this._version}`); - return `${this.server.endpoints[this._version]["signalk-ws"]}`; + return `${this.server.endpoints[this._version]['signalk-ws']}`; } else if ( - this.server.endpoints["v1"] && - this.server.endpoints["v1"]["signalk-ws"] + this.server.endpoints['v1'] && + this.server.endpoints['v1']['signalk-ws'] ) { debug(`Connection falling back to: v1`); - return `${this.server.endpoints["v1"]["signalk-ws"]}`; + return `${this.server.endpoints['v1']['signalk-ws']}`; } else { - return ""; + return ''; } } /** Return signalk-http endpoint url */ private resolveHttpEndpoint(): string { - let url = ""; + let url = ''; if (this.server.endpoints[this._version]) { // ** connection made // ** connect to http endpoint at prescribed version else fall back to default version - if (this.server.endpoints[this._version]["signalk-http"]) { - url = `${this.server.endpoints[this._version]["signalk-http"]}`; + if (this.server.endpoints[this._version]['signalk-http']) { + url = `${this.server.endpoints[this._version]['signalk-http']}`; } else { - url = `${this.server.endpoints["v1"]["signalk-http"]}`; + url = `${this.server.endpoints['v1']['signalk-http']}`; } } else { const msg = - "No current connection http endpoint service! Use connect() to establish a connection."; + 'No current connection http endpoint service! Use connect() to establish a connection.'; debug(msg); } return url; @@ -431,8 +431,8 @@ export class SignalKClient { private disconnectedFromServer() { this.server.endpoints = {}; this.server.info = { - version: "", - id: "", + version: '', + id: '', }; this.server.apiVersions = []; } @@ -441,7 +441,7 @@ export class SignalKClient { * @param path Signal K path. */ async get(path: string): Promise<{ [key: string]: unknown }> { - if (path && path.length > 0 && path[0] === "/") { + if (path && path.length > 0 && path[0] === '/') { path = path.slice(1); } const url = `${this.protocol}://${this.hostname}:${this.port}/${ @@ -468,13 +468,13 @@ export class SignalKClient { }`; debug(`put ${url}`); - const headers = new Headers({ "Content-Type": "application/json" }); + const headers = new Headers({ 'Content-Type': 'application/json' }); if (this._token) { - headers.append("Authorization", `JWT ${this._token}`); + headers.append('Authorization', `JWT ${this._token}`); } const options: RequestInit = { - method: "PUT", + method: 'PUT', headers: headers, body: JSON.stringify(value), }; @@ -486,7 +486,7 @@ export class SignalKClient { * @param value Value to apply. */ async post(path: string, value: unknown): Promise { - if (path && path.length > 0 && path[0] === "/") { + if (path && path.length > 0 && path[0] === '/') { path = path.slice(1); } const url = `${this.protocol}://${this.hostname}:${this.port}/${ @@ -494,13 +494,13 @@ export class SignalKClient { }`; debug(`post ${url}`); - const headers = new Headers({ "Content-Type": "application/json" }); + const headers = new Headers({ 'Content-Type': 'application/json' }); if (this._token) { - headers.append("Authorization", `JWT ${this._token}`); + headers.append('Authorization', `JWT ${this._token}`); } const options: RequestInit = { - method: "POST", + method: 'POST', headers: headers, body: JSON.stringify(value), }; @@ -515,17 +515,17 @@ export class SignalKClient { username: string, password: string, ): Promise<{ ok: boolean; status: number; token: string }> { - const headers = new Headers({ "Content-Type": "application/json" }); + const headers = new Headers({ 'Content-Type': 'application/json' }); const url = `${this.protocol}://${this.hostname}:${this.port}/signalk/${this._version}/auth/login`; debug(`post ${url}`); if (this._token) { - headers.append("Authorization", `JWT ${this._token}`); + headers.append('Authorization', `JWT ${this._token}`); } const options = { - method: "POST", + method: 'POST', headers: headers, body: JSON.stringify({ username: username, password: password }), }; @@ -538,14 +538,14 @@ export class SignalKClient { const result = { ok: response.ok, status: response.status, - token: "", + token: '', }; if (response.ok) { - const cookies = response.headers.get("set-cookie")?.split(";"); + const cookies = response.headers.get('set-cookie')?.split(';'); cookies?.forEach((c) => { - if (c.indexOf("JAUTHENTICATION") !== -1) { - this.authToken = c.split("=")[1]; - debug("authToken: ", this._token); + if (c.indexOf('JAUTHENTICATION') !== -1) { + this.authToken = c.split('=')[1]; + debug('authToken: ', this._token); result.token = this._token; } }); @@ -559,13 +559,13 @@ export class SignalKClient { `${this.protocol}://${this.hostname}:${this.port}/signalk/${this._version}/auth/validate`; debug(`post ${url}`); - const headers = new Headers({ "Content-Type": "application/json" }); + const headers = new Headers({ 'Content-Type': 'application/json' }); if (this._token) { - headers.append("Authorization", `JWT ${this._token}`); + headers.append('Authorization', `JWT ${this._token}`); } const options = { - method: "POST", + method: 'POST', headers: headers, body: null, }; @@ -575,14 +575,14 @@ export class SignalKClient { const result = { ok: response.ok, status: response.status, - token: "", + token: '', }; if (response.ok) { - const cookies = response.headers.get("set-cookie")?.split(";"); + const cookies = response.headers.get('set-cookie')?.split(';'); cookies?.forEach((c) => { - if (c.indexOf("JAUTHENTICATION") !== -1) { - this.authToken = c.split("=")[1]; - debug("authToken: ", this._token); + if (c.indexOf('JAUTHENTICATION') !== -1) { + this.authToken = c.split('=')[1]; + debug('authToken: ', this._token); result.token = this._token; } }); @@ -596,13 +596,13 @@ export class SignalKClient { `${this.protocol}://${this.hostname}:${this.port}/signalk/${this._version}/auth/logout`; debug(`put ${url}`); - const headers = new Headers({ "Content-Type": "application/json" }); + const headers = new Headers({ 'Content-Type': 'application/json' }); if (this._token) { - headers.append("Authorization", `JWT ${this._token}`); + headers.append('Authorization', `JWT ${this._token}`); } const options = { - method: "PUT", + method: 'PUT', headers: headers, body: null, }; @@ -620,15 +620,15 @@ export class SignalKClient { */ async isLoggedIn(): Promise { const response = await this.getLoginStatus(); - return response.status === "loggedIn" ? true : false; + return response.status === 'loggedIn' ? true : false; } /** Fetch Signal K login status from server */ getLoginStatus(): Promise<{ [key: string]: unknown }> { let url = `/skServer/loginStatus`; - if (this.server && this.server.info.id === "signalk-server-node") { - const ver = this.server.info["version"].split("."); - url = ver[0] === "1" && parseInt(ver[1]) < 36 //use legacy link for older versions + if (this.server && this.server.info.id === 'signalk-server-node') { + const ver = this.server.info['version'].split('.'); + url = ver[0] === '1' && parseInt(ver[1]) < 36 //use legacy link for older versions ? `/loginstatus` : url; } @@ -641,14 +641,14 @@ export class SignalKClient { */ snapshot(context: string, time: string): Promise<{ [key: string]: unknown }> { if (!time) { - throw new Error("Error: No time value supplied!"); + throw new Error('Error: No time value supplied!'); } - time = time.slice(0, time.indexOf(".")) + "Z"; + time = time.slice(0, time.indexOf('.')) + 'Z'; let url = this.resolveHttpEndpoint(); if (!url) { - throw new Error("Error: Unable to resolve URL!"); + throw new Error('Error: Unable to resolve URL!'); } - url = `${url.replace("api", "snapshot")}${ + url = `${url.replace('api', 'snapshot')}${ Path.contextToPath( context, ) @@ -671,7 +671,7 @@ export class SignalKClient { id?: string, ): Promise<{ [key: string]: unknown }> { if (!name) { - throw new Error("Error: Name not supplied!"); + throw new Error('Error: Name not supplied!'); } if (!id && !this.clientId) { @@ -696,7 +696,7 @@ export class SignalKClient { */ async checkAccessRequest(href: string): Promise<{ [key: string]: unknown }> { if (!href) { - throw new Error("Error: href not supplied!"); + throw new Error('Error: href not supplied!'); } return await this.get(href); } @@ -710,12 +710,12 @@ export class SignalKClient { /** Target application id. * @private */ - private _appId = ""; + private _appId = ''; /** Target application version. * @private */ - private _appVersion = ""; + private _appVersion = ''; /** Return endpoint for target app. */ private resolveAppDataEndpoint( @@ -723,9 +723,9 @@ export class SignalKClient { appId: string, ): string { if (!context || !appId) { - return ""; + return ''; } - const url = this.resolveHttpEndpoint().replace("api", "applicationData"); + const url = this.resolveHttpEndpoint().replace('api', 'applicationData'); return `${url}${context}/${appId}/`; } @@ -762,12 +762,12 @@ export class SignalKClient { * @param version Version of data */ appDataKeys( - path = "", + path = '', context: APPDATA_CONTEXT = APPDATA_CONTEXT.USER, appId: string = this._appId, version: string = this._appVersion, ): Promise<{ [key: string]: unknown }> { - path = path.length != 0 && path[0] === "/" ? path.slice(1) : path; + path = path.length != 0 && path[0] === '/' ? path.slice(1) : path; let url = this.resolveAppDataEndpoint(context, appId); url += `${version}/${path}?keys=true`; return this.get(url); @@ -780,12 +780,12 @@ export class SignalKClient { * @param version Version of data */ appDataGet( - path = "", + path = '', context: APPDATA_CONTEXT = APPDATA_CONTEXT.USER, appId: string = this._appId, version: string = this._appVersion, ): Promise<{ [key: string]: unknown }> { - path = path.length != 0 && path[0] === "/" ? path.slice(1) : path; + path = path.length != 0 && path[0] === '/' ? path.slice(1) : path; let url = this.resolveAppDataEndpoint(context, appId); url += `${version}/${path}`; return this.get(url); @@ -805,14 +805,14 @@ export class SignalKClient { version: string = this._appVersion, ): Promise { if (!path) { - throw new Error("Error: Invalid path!"); + throw new Error('Error: Invalid path!'); } - if (path[0] === "/") { + if (path[0] === '/') { path = path.slice(1); } const ep = this.resolveAppDataEndpoint(context, appId); if (!ep) { - throw new Error("Error: Invalid path!"); + throw new Error('Error: Invalid path!'); } return this.post( `${ep}${version}/${Path.dotToSlash(path)}`, @@ -834,7 +834,7 @@ export class SignalKClient { ): Promise { const ep = this.resolveAppDataEndpoint(context, appId); if (!ep || !version) { - throw new Error("Error: Invalid path or version!"); + throw new Error('Error: Invalid path or version!'); } return this.post(`${ep}${version}`, value); } diff --git a/signalk-client/stream-api.ts b/signalk-client/stream-api.ts index 49d61e6..8faef9f 100644 --- a/signalk-client/stream-api.ts +++ b/signalk-client/stream-api.ts @@ -1,6 +1,6 @@ -import { EventEmitter } from "https://deno.land/x/eventemitter@1.2.1/mod.ts"; -import { Alarm, AlarmType, Message } from "./utils.ts"; -import { debug } from "./mod.ts"; +import { EventEmitter } from 'https://deno.land/x/eventemitter@1.2.1/mod.ts'; +import { Alarm, AlarmType, Message } from './utils.ts'; +import { debug } from './mod.ts'; /** Signal K Stream operations */ export class SignalKStream { @@ -10,7 +10,7 @@ export class SignalKStream { /** id of vessel to filter delta messages * @private */ - private _filter = ""; + private _filter = ''; /** websocket connection timeout * @private @@ -20,7 +20,7 @@ export class SignalKStream { /** Authentication token * @private */ - private _token = ""; + private _token = ''; /** Playback mode flag * @private */ @@ -34,20 +34,20 @@ export class SignalKStream { /** Client Id for approved device * @private */ - private _clientId: any = ""; + private _clientId: any = ''; // **************** ATTRIBUTES *************************** /** API version to use */ public version = 1; /** Connection endpoint */ - public endpoint = ""; + public endpoint = ''; /** self identifier value */ - public selfId = ""; + public selfId = ''; - /** Set source label for use in messages + /** Set source label for use in messages * @param val label value - */ + */ set source(val: string) { if (!this._source) { this._source = {}; @@ -55,9 +55,9 @@ export class SignalKStream { this._source['label'] = val; } - /** Set auth token value + /** Set auth token value * @param val token value - */ + */ set authToken(val: string) { this._token = val; } @@ -93,8 +93,8 @@ export class SignalKStream { /** Apply a filter for stream messages. Set filter = null to remove message filtering */ set filter(id: string) { - if (id && id.indexOf("self") != -1) { - this._filter = this.selfId ? this.selfId : ""; + if (id && id.indexOf('self') != -1) { + this._filter = this.selfId ? this.selfId : ''; } else { this._filter = id; } @@ -109,10 +109,10 @@ export class SignalKStream { /** Stream events */ public events: EventEmitter<{ - "connect"(ev: Event): any; - "close"(ev: Event): any; - "error"(ev: Event): any; - "message"(ev: MessageEvent): any; + 'connect'(ev: Event): any; + 'close'(ev: Event): any; + 'error'(ev: Event): any; + 'message'(ev: MessageEvent): any; }>; constructor() { @@ -137,12 +137,12 @@ export class SignalKStream { if (!url) { return; } - let q = url.indexOf("?") === -1 ? "?" : "&"; + let q = url.indexOf('?') === -1 ? '?' : '&'; if (subscribe) { url += `${q}subscribe=${subscribe}`; } if (this._token || token) { - url += `${subscribe ? "&" : "?"}token=${this._token || token}`; + url += `${subscribe ? '&' : '?'}token=${this._token || token}`; } this.close(); @@ -160,13 +160,13 @@ export class SignalKStream { }, this._wsTimeout); this.ws.onopen = (e: Event) => { - this.events.emit("connect", e); + this.events.emit('connect', e); }; this.ws.onclose = (e: Event) => { - this.events.emit("close", e); + this.events.emit('close', e); }; this.ws.onerror = (e: Event) => { - this.events.emit("error", e); + this.events.emit('error', e); }; this.ws.onmessage = (e: MessageEvent) => { this.parseOnMessage(e); @@ -178,7 +178,7 @@ export class SignalKStream { */ private parseOnMessage(e: MessageEvent) { let data: any; - if (typeof e.data === "string") { + if (typeof e.data === 'string') { try { data = JSON.parse(e.data); } catch { @@ -187,21 +187,21 @@ export class SignalKStream { } if (this.isHello(data)) { this.selfId = data.self; - this._playbackMode = typeof data.startTime != "undefined" ? true : false; - this.events.emit("message", data); + this._playbackMode = typeof data.startTime != 'undefined' ? true : false; + this.events.emit('message', data); } else if (this.isResponse(data)) { - if (typeof data.login !== "undefined") { - if (typeof data.login.token !== "undefined") { + if (typeof data.login !== 'undefined') { + if (typeof data.login.token !== 'undefined') { this._token = data.login.token; } } - this.events.emit("message", data); + this.events.emit('message', data); } else if (this._filter && this.isDelta(data)) { if (data.context === this._filter) { - this.events.emit("message", data); + this.events.emit('message', data); } } else { - this.events.emit("message", data); + this.events.emit('message', data); } } @@ -210,24 +210,24 @@ export class SignalKStream { * @returns String containing requestId */ sendRequest(value: any): string { - if (typeof value !== "object") { - return ""; + if (typeof value !== 'object') { + return ''; } const msg: any = Message.request(); - debug("requestId: ", msg.requestId); - debug("_token: ", this._token); - if (typeof value.login === "undefined" && this._token) { - msg["token"] = this._token; + debug('requestId: ', msg.requestId); + debug('_token: ', this._token); + if (typeof value.login === 'undefined' && this._token) { + msg['token'] = this._token; } - debug("_clientId: ", this._clientId); + debug('_clientId: ', this._clientId); if (this._clientId) { - msg["clientId"] = this._clientId; + msg['clientId'] = this._clientId; } const keys = Object.keys(value); keys.forEach((k) => { msg[k] = value[k]; }); - debug("msg: ", msg); + debug('msg: ', msg); this.send(msg); return msg.requestId; } @@ -240,7 +240,7 @@ export class SignalKStream { */ put(context: string, path: string, value: any): string { const msg = { - context: context === "self" ? "vessels.self" : context, + context: context === 'self' ? 'vessels.self' : context, put: { path: path, value: value }, }; return this.sendRequest(msg); @@ -263,7 +263,7 @@ export class SignalKStream { */ send(data: any) { if (this.ws) { - if (typeof data === "object") { + if (typeof data === 'object') { data = JSON.stringify(data); } debug(`sending -> `, data); @@ -283,25 +283,25 @@ export class SignalKStream { sendUpdate(context: string, path: Array): void; sendUpdate(context: string, path: string, value: any): void; sendUpdate( - context = "self", + context = 'self', path: string | Array, value?: any, ) { const val: any = Message.updates(); if (this._token) { - val["token"] = this._token; + val['token'] = this._token; } - debug("_clientId: ", this._clientId); + debug('_clientId: ', this._clientId); if (this._clientId) { - val["clientId"] = this._clientId; + val['clientId'] = this._clientId; } - val.context = context === "self" ? "vessels.self" : context; + val.context = context === 'self' ? 'vessels.self' : context; let uValues = []; - if (typeof path === "string") { + if (typeof path === 'string') { uValues.push({ path: path, value: value }); } - if (typeof path === "object" && Array.isArray(path)) { + if (typeof path === 'object' && Array.isArray(path)) { uValues = path; } const u: any = { @@ -309,7 +309,7 @@ export class SignalKStream { values: uValues, }; if (this._source) { - u["source"] = this._source; + u['source'] = this._source; } val.updates.push(u); this.send(val); @@ -327,45 +327,45 @@ export class SignalKStream { subscribe(context: string, path: Array): void; subscribe(context: string, path: string, options?: any): void; subscribe( - context = "*", - path: string | Array = "*", + context = '*', + path: string | Array = '*', options?: any, ) { const val: any = Message.subscribe(); if (this._token) { - val["token"] = this._token; + val['token'] = this._token; } - val.context = context === "self" ? "vessels.self" : context; + val.context = context === 'self' ? 'vessels.self' : context; if (this._token) { - val["token"] = this._token; + val['token'] = this._token; } - if (typeof path === "object" && Array.isArray(path)) { + if (typeof path === 'object' && Array.isArray(path)) { val.subscribe = path; } - if (typeof path === "string") { + if (typeof path === 'string') { const sValue: any = {}; - sValue["path"] = path; - if (options && typeof options === "object") { - if (options["period"]) { - sValue["period"] = options["period"]; + sValue['path'] = path; + if (options && typeof options === 'object') { + if (options['period']) { + sValue['period'] = options['period']; } - if (options["minPeriod"]) { - sValue["minPeriod"] = options["period"]; + if (options['minPeriod']) { + sValue['minPeriod'] = options['period']; } if ( - options["format"] && - (options["format"] === "delta" || options["format"] === "full") + options['format'] && + (options['format'] === 'delta' || options['format'] === 'full') ) { - sValue["format"] = options["format"]; + sValue['format'] = options['format']; } if ( - options["policy"] && - (options["policy"] === "instant" || - options["policy"] === "ideal" || - options["policy"] === "fixed") + options['policy'] && + (options['policy'] === 'instant' || + options['policy'] === 'ideal' || + options['policy'] === 'fixed') ) { - sValue["policy"] = options["policy"]; + sValue['policy'] = options['policy']; } } val.subscribe.push(sValue); @@ -377,26 +377,55 @@ export class SignalKStream { * @param context Signal K context * @param path Signal K path value */ - unsubscribe(context = "*", path = "*") { + unsubscribe(context = '*', path = '*') { const val: any = Message.unsubscribe(); if (this._token) { - val["token"] = this._token; + val['token'] = this._token; } - val.context = context === "self" ? "vessels.self" : context; + val.context = context === 'self' ? 'vessels.self' : context; if (this._token) { - val["token"] = this._token; + val['token'] = this._token; } - if (typeof path === "object" && Array.isArray(path)) { + if (typeof path === 'object' && Array.isArray(path)) { val.unsubscribe = path; } - if (typeof path === "string") { + if (typeof path === 'string') { val.unsubscribe.push({ path: path }); } this.send(val); } - + /** Access request + * @param name Name of sensor / process requesting access + * @param id Client id + * @returns requestId + */ + accessRequest( + name: string, + id?: string, + ): string { + if (!name) { + throw new Error('Error: Name not supplied!'); + } + + if (!id && !this.clientId) { + this.clientId = crypto.randomUUID(); + } else if (!this.clientId) { + this.clientId = id; + } + + const req = { + requestId: crypto.randomUUID(), + accessRequest: { + clientId: this.clientId, + description: name, + permissions: 'admin', + }, + }; + this.send(req); + return req.requestId; + } /** Raise alarm * @param context Signal K context @@ -410,10 +439,10 @@ export class SignalKStream { */ raiseAlarm(context: string, name: string, alarm: Alarm): void; raiseAlarm(context: string, type: AlarmType, alarm: Alarm): void; - raiseAlarm(context = "*", alarmId: string | AlarmType, alarm: Alarm) { + raiseAlarm(context = '*', alarmId: string | AlarmType, alarm: Alarm) { let path: string; - if (typeof alarmId === "string") { - path = alarmId.indexOf("notifications.") === -1 + if (typeof alarmId === 'string') { + path = alarmId.indexOf('notifications.') === -1 ? `notifications.${alarmId}` : alarmId; } else { @@ -426,8 +455,8 @@ export class SignalKStream { * @param context Signal K context * @param name alarm name */ - clearAlarm(context = "*", name: string) { - const path = name.indexOf("notifications.") === -1 + clearAlarm(context = '*', name: string) { + const path = name.indexOf('notifications.') === -1 ? `notifications.${name}` : name; this.put(context, path, null); @@ -445,20 +474,20 @@ export class SignalKStream { * @param msg Received stream message */ isDelta(msg: any): boolean { - return typeof msg.context != "undefined"; + return typeof msg.context != 'undefined'; } /** Tests if message is a Hello message * @param msg Received stream message */ isHello(msg: any): boolean { - return typeof msg.version != "undefined" && typeof msg.self != "undefined"; + return typeof msg.version != 'undefined' && typeof msg.self != 'undefined'; } /** Tests if message is a request Response message * @param msg Received stream message */ isResponse(msg: any): boolean { - return typeof msg.requestId != "undefined"; + return typeof msg.requestId != 'undefined'; } } diff --git a/signalk-client/utils.ts b/signalk-client/utils.ts index c23389c..3f933d9 100644 --- a/signalk-client/utils.ts +++ b/signalk-client/utils.ts @@ -2,8 +2,8 @@ export interface Subscription { path: string; period?: number; - format?: "delta" | "full"; - policy?: "instant" | "ideal" | "fixed"; + format?: 'delta' | 'full'; + policy?: 'instant' | 'ideal' | 'fixed'; minPeriod?: number; } @@ -14,11 +14,11 @@ export class Path { * @returns Path in slash notation e.g. navigation/position */ static dotToSlash(path: string): string { - const p = path.split("?"); - if (p[0].indexOf(".") != -1) { - p[0] = p[0].split(".").join("/"); + const p = path.split('?'); + if (p[0].indexOf('.') != -1) { + p[0] = p[0].split('.').join('/'); } - return p.join("?"); + return p.join('?'); } /** parse context to valid Signal K path @@ -26,8 +26,8 @@ export class Path { * @returns context in slash notation */ static contextToPath(context: string): string { - const res = context === "self" ? "vessels.self" : context; - return res.split(".").join("/"); + const res = context === 'self' ? 'vessels.self' : context; + return res.split('.').join('/'); } } @@ -93,7 +93,7 @@ export class Alarm { /** Alarm Message text * @private */ - private _message = ""; + private _message = ''; /** * @param message Alarm message text @@ -107,8 +107,8 @@ export class Alarm { visual?: boolean, sound?: boolean, ) { - this._message = typeof message !== "undefined" ? message : ""; - this._state = typeof state !== "undefined" ? state : AlarmState.alarm; + this._message = typeof message !== 'undefined' ? message : ''; + this._state = typeof state !== 'undefined' ? state : AlarmState.alarm; if (visual) { this._method.push(AlarmMethod.visual); } @@ -129,29 +129,29 @@ export class Alarm { /** AlarmState values */ export enum AlarmState { - normal = "normal", - alert = "alert", - warn = "warn", - alarm = "alarm", - emergency = "emergency", + normal = 'normal', + alert = 'alert', + warn = 'warn', + alarm = 'alarm', + emergency = 'emergency', } /** AlarmMethod values */ export enum AlarmMethod { - visual = "visual", - sound = "sound", + visual = 'visual', + sound = 'sound', } /** AlarmType values */ export enum AlarmType { - mob = "notifications.mob", - fire = "notifications.fire", - sinking = "notifications.sinking", - flooding = "notifications.flooding", - collision = "notifications.collision", - grounding = "notifications.grounding", - listing = "notifications.listing", - adrift = "notifications.adrift", - piracy = "notifications.piracy", - abandon = "notifications.abandon", + mob = 'notifications.mob', + fire = 'notifications.fire', + sinking = 'notifications.sinking', + flooding = 'notifications.flooding', + collision = 'notifications.collision', + grounding = 'notifications.grounding', + listing = 'notifications.listing', + adrift = 'notifications.adrift', + piracy = 'notifications.piracy', + abandon = 'notifications.abandon', }