import { SKVersion, AutopilotProviderRegistry, WithFeatures, PropertyValuesEmitter, ResourceProviderRegistry, WeatherProviderRegistry, BLEProviderRegistry, Delta, MetaValue, WithResourcesApi, WithNotificationsApi } from '.'; import { RadarProviderRegistry, WithRadarApi } from './radarapi'; import { CourseApi } from './course'; import { HistoryProviderRegistry, WithHistoryApi } from './history'; import { TrackProviderRegistry, WithTrackApi } from './tracks'; import { StreamBundle } from './streambundle'; import { SubscriptionManager } from './subscriptionmanager'; import type { WebSocket } from 'ws'; import type { IncomingMessage } from 'http'; import type { Duplex } from 'stream'; import type { EnhancedDisplayUnits } from './typebox/protocol-schemas'; export type { EnhancedDisplayUnits } from './typebox/protocol-schemas'; /** * A WebSocket endpoint under the plugin's route, returned by * {@link ServerAPI.registerWebSocket}. A subset of * [ws](https://github.com/websockets/ws)'s `WebSocketServer`. * * @category Server API */ export interface PluginWebSocketServer { /** * Emitted with each WebSocket connection after the server has completed * the handshake. Not emitted when an `upgrade` listener is attached. */ on(event: 'connection', listener: (ws: WebSocket, request: IncomingMessage) => void): this; /** * Escape hatch for plugins that need the raw HTTP `upgrade` — typically * to hand it to a reverse proxy. When at least one `upgrade` listener is * attached the plugin handles the upgrade entirely itself: the server * does not complete the handshake and `connection` is never emitted. * The arguments match Node.js's `http.Server` `upgrade` event. */ on(event: 'upgrade', listener: (request: IncomingMessage, socket: Duplex, head: Buffer) => void): this; on(event: 'error', listener: (error: Error) => void): this; /** Connections completed by the server (empty when using `upgrade`). */ readonly clients: ReadonlySet; /** * Stops accepting new connections and removes the endpoint. Called * automatically when the plugin stops, which also terminates any * connected clients. */ close(cb?: (err?: Error) => void): void; } /** * SignalK server provides an interface to allow {@link Plugin | Plugins } to: * * - Discover Features. * - Access / update the full data model * - send / receive deltas (updates) * - Interact with APIs * - Expose HTTP endpoints * * These functions are available via the app object passed to the plugin when it is invoked. * * > [!WARNING] * > Typing is incomplete. If you find a missing or inaccurate type, please [report it](https://github.com/SignalK/signalk-server/issues/1917). * @category Server API */ export interface ServerAPI extends PropertyValuesEmitter, ResourceProviderRegistry, WithResourcesApi, AutopilotProviderRegistry, WeatherProviderRegistry, RadarProviderRegistry, WithRadarApi, BLEProviderRegistry, WithHistoryApi, HistoryProviderRegistry, TrackProviderRegistry, WithTrackApi, WithFeatures, CourseApi, WithNotificationsApi, SelfIdentity { /** * Returns the entry for the provided path starting from `vessels.self` in the full data model. * * @remarks * Returns `unknown` because the Signal K data model is dynamic and path-dependent. * Callers should validate the returned type at runtime. * * A value can be `null`: reported as such by its source, or set by the server * when the path timed out under stale data detection. Treat `null` as a * missing value, the same as `undefined`. * * @example * ```ts * let uuid = app.getSelfPath('uuid'); * // Note: This is synonymous with app.getPath('vessels.self.uuid') * * app.debug(uuid); * // urn:mrn:signalk:uuid:a9d2c3b1-611b-4b00-8628-0b89d014ed60 * ``` * * @category Data Model */ getSelfPath(path: string): unknown; /** * Returns the entry for the provided path starting from the `root` of the full data model. * * @remarks * Returns `unknown` because the Signal K data model is dynamic and path-dependent. * Callers should validate the returned type at runtime. * * @example * ```javascript * let baseStations = app.getPath('shore.basestations'); * * // baseStations: * { * 'urn:mrn:imo:mmsi:2766140': { * url: 'basestations', * navigation: { position: {latitude: 45.2, longitude: 76.4} }, * mmsi: '2766140' * }, * 'urn:mrn:imo:mmsi:2766160': { * url: 'basestations', * navigation: { position: {latitude: 46.9, longitude: 72.22} }, * mmsi: '2766160' * } * } * ``` * * @category Data Model */ getPath(path: string): unknown; /** * @category Data Model */ getMetadata(path: string): Metadata | undefined; /** * Suggest default metadata for a path. The server persists this metadata * only for fields not already set by the user in baseDeltas.json. * * Use this to provide sensible defaults (e.g., displayUnits) for paths * your plugin publishes, without overwriting user choices. * * Idempotent: on subsequent server starts, fields already persisted * from a previous call will not be overwritten. * * @example * ```javascript * await app.setDefaultMetadata('electrical.batteries.house.energy', { * displayUnits: { category: 'energy', targetUnit: 'Wh' } * }) * ``` * * @param path - Signal K path (e.g., 'electrical.batteries.house.energy') * @param value - Metadata fields to suggest * @returns true if any new fields were applied, false if all fields already existed * @category Data Model */ setDefaultMetadata(path: string, value: MetaValue): Promise; /** * Resolve the display units for a path: the target unit, conversion * formulas, symbol and display format that clients receive in the path's * `meta.displayUnits`. * * Without a username, resolves under the active preset the admin chose, * so output the plugin produces for everyone (an alert message, a * notification) reads the same for every reader. With a username, * resolves as REST and WebSocket metadata do for that user: under the * user's own preset, and with the user's primary category for an SI unit * that several categories share. A plugin answering a request on its own * routes can pass `req.skPrincipal?.identifier`. * * The preset is read on each call, so a preset change applies to the * next call. * * @example * ```javascript * const displayUnits = app.getDisplayUnits('navigation.speedOverGround') * // { category: 'speed', targetUnit: 'kn', formula: 'value * 1.94384', * // inverseFormula: 'value * 0.514444', symbol: 'kn', displayFormat: '0.0' } * ``` * * @param path - Signal K path relative to `vessels.self` (e.g., 'navigation.speedOverGround') * @param username - Resolve under this user's unit preferences instead of the admin's * @returns The resolved display units, or undefined when the path has no * unit category, the category has no target unit, or the conversion is unknown * @category Data Model */ getDisplayUnits(path: string, username?: string): EnhancedDisplayUnits | undefined; /** * Convert a value from the path's SI unit to its display unit, as resolved * by {@link ServerAPI.getDisplayUnits}. * * Convert only a value of the path's quantity itself. A difference, such * as a 5 K temperature rise, must stay in SI: the conversion formulas can * include an offset (Kelvin to Celsius subtracts 273.15). * * Only formulas built from the operators `+ - * / ^`, the functions * `exp`, `log` and `log10`, numbers, parentheses and the value are * evaluated. The time category's formatted * targets, such as `HH:MM:SS`, are client-side formatters, so they return * undefined. * * @example * ```javascript * const converted = app.convertToDisplayUnits( * 'environment.outside.temperature', * 288.15 * ) * // { value: 15, symbol: '°C', displayFormat: '0.0' } * ``` * * @param path - Signal K path relative to `vessels.self` (e.g., 'environment.outside.temperature') * @param value - The value in the path's SI unit * @param username - Convert under this user's unit preferences instead of the admin's * @returns The converted value with its unit symbol, or undefined when the * value is not a finite number, the path has no display units, or the * conversion formula uses more than the allowed operations, fails, or * yields a number that is not finite * @category Data Model */ convertToDisplayUnits(path: string, value: number, username?: string): DisplayUnitsValue | undefined; /** * Call the PUT handler for a path on the vessel's own context. * @param aPath - The path for the PUT request * @param value - The value to send in the PUT request * @param updateCb - Callback invoked when the update completes * @category Data Model */ putSelfPath(aPath: string, value: unknown, updateCb: () => void): Promise; /** * Call the PUT handler for a path with a specified context. * @param aPath - The path for the PUT request * @param value - The value to send in the PUT request * @param updateCb - Callback invoked when the update completes, with error if failed * @param source - The source identifier for this update * @category Data Model */ putPath(aPath: string, value: number | string | object | boolean, updateCb: (err?: Error) => void, source: string): Promise; /** * @category Data Model */ streambundle: StreamBundle; /** * @see [Processing data from the server](../../../docs/develop/plugins/deltas.md#subscribing-to-deltas) * @category Data Model */ subscriptionmanager: SubscriptionManager; /** * Query the status of an async request by its ID. * @param requestId - The request ID to query * @returns Promise resolving to the request status/result */ queryRequest(requestId: string): Promise; /** * @category Status and Debugging */ error(msg: string): void; /** * Log debug messages. * * This function exposes the `debug` method from the [debug module](https://www.npmjs.com/package/debug). * The npm module name is used as the debug name. * * `app.debug()` can take any type and will serialize it before outputting. * * Use `app.debug.enabled` to guard expensive log arguments so they are not * computed when the debug name is disabled: * * ```ts * if (app.debug.enabled) app.debug('state: ' + JSON.stringify(big)) * ``` * * > [!note] * > Do not use `debug` from the debug module directly! Using `app.debug()` provided by the server ensures that the plugin taps into the server's debug logging system, including the helper switches in Admin UI's Server Log page. * * @category Status and Debugging */ debug: { (msg: any, ...args: any[]): void; enabled: boolean; }; /** * Register a function to intercept all delta messages _before_ they are processed by the server. * * The callback function should call `next(delta)` with either: * - A modified delta (if it wants to alter the incoming delta) * - With the original delta to process it normally. * * > [!important] * > Not calling `next(delta)` will cause the incoming delta to be dropped and will only show in delta statistics. * * Other, non-delta messages produced by provider pipe elements are emitted normally. * * @example * ```javascript * app.registerDeltaInputHandler((delta, next) => { * delta.updates.forEach(update => { * update.values.forEach(pathValue => { * if(pathValue.startsWith("foo")) { * pathValue.path = "bar" * } * }) * }) * next(delta) * }); * ``` * * @category Data Model */ registerDeltaInputHandler(handler: DeltaInputHandler): void; /** * Set the current status of the plugin that is displayed in the plugin configuration UI and the Dashboard. * * The `msg` parameter should be a short text message describing the current status of the plugin. * * @example * ```javascript * app.setPluginStatus('Initializing'); * // Do something * app.setPluginStatus('Done initializing'); * ``` * * _Note: Replaces deprecated `setProviderStatus()`_ * * @category Status and Debugging */ setPluginStatus(msg: string): void; /** * Set the current error status of the plugin that is displayed in the plugin configuration UI and the Dashboard. * * The `msg` parameter should be a short text message describing the current status of the plugin. * * @example * ```javascript * app.setPluginError('Error connecting to database'); * ``` * * _Note: Replaces deprecated `setProviderError()`_ * * @category Status and Debugging */ setPluginError(msg: string): void; /** * Emit a delta message. * * _Note: These deltas are handled by the server in the same way as any other incoming deltas._ * * @example * ```javascript * app.handleMessage('my-signalk-plugin', { * updates: [ * { * values: [ * { * path: 'navigation.courseOverGroundTrue', * value: 1.0476934 * } * ] * } * ] * }); * ``` * * Plugins emitting deltas that use Signal K v2 paths (like the [Course API](http://localhost:3000/admin/openapi/?urls.primaryName=course) paths) should call `handleMessage` with the optional `skVersion` parameter set to `v2`. This prevents v2 API data getting mixed in v1 paths' data in full data model & the v1 http API. * * Omitting the `skVersion` parameter will cause the delta to be sent as `v1`. * * @param skVersion Optional parameter to specify the Signal K version of the delta. * @category Data Model */ handleMessage(id: string, msg: Partial, skVersion?: SKVersion): void; /** * Save changes to the plugin's configuration options. * * @example * ```javascript * let options = { * myConfigValue = 'Something the plugin calculated' * }; * * app.savePluginOptions(options, () => {app.debug('Plugin options saved')}); * ``` * * @category Configuration */ savePluginOptions(configuration: object, cb: (err: NodeJS.ErrnoException | null) => void): void; /** * Read the stored plugin configuration options. * * @example * ```javascript * let options = app.readPluginOptions(); * ``` * * @category Configuration */ readPluginOptions(): object; /** * Returns the full path of the directory where the plugin can persist its internal data, e.g. data files, etc. * Callable in plugin's `start()` function and later, not in plugin constructor function. * * @example * ```javascript * let myDataFile = require('path').join( app.getDataDirPath(), 'somedatafile.ext') * ``` * @category Configuration */ getDataDirPath(): string; /** * Register a handler to action [`PUT`](http://signalk.org/specification/1.3.0/doc/put.html) requests for a specific path. * * The action handler can handle the request synchronously or asynchronously. * * The `callback` parameter should be a function which accepts the following arguments: * - `context` * - `path` * - `value` * - `callback` * * For synchronous actions, the handler must return a value describing the response of the request: * * ```javascript * { * state: 'COMPLETED', * statusCode: 200 * } * ``` * * or * * ```javascript * { * state:'COMPLETED', * statusCode: 400, * message:'Some Error Message' * } * ``` * * The `statusCode` value can be any valid HTTP response code. * * For asynchronous actions, that may take considerable time to complete and the requester should not be kept waiting for the result, the handler must return: * * ```javascript * { state: 'PENDING' } * ``` * * When the action has completed the handler should call the `callback` function with the result: * * ```javascript * callback({ state: 'COMPLETED', statusCode: 200 }) * ``` * or * * ```javascript * callback({ * state:'COMPLETED', * statusCode: 400, * message:'Some Error Message' * }) * ``` * * _Example: Synchronous response:_ * ```javascript * function myActionHandler(context, path, value, callback) { * if(doSomething(context, path, value)){ * return { state: 'COMPLETED', statusCode: 200 }; * } else { * return { state: 'COMPLETED', statusCode: 400 }; * } * } * * plugin.start = (options) => { * app.registerPutHandler('vessels.self', 'some.path', myActionHandler, 'somesource.1'); * } * ``` * * _Example: Asynchronous response:_ * ```javascript * function myActionHandler(context, path, value, callback) { * * doSomethingAsync(context, path, value, (result) =>{ * if(result) { * callback({ state: 'COMPLETED', result: 200 }) * } else { * callback({ state: 'COMPLETED', result: 400 }) * } * }); * * return { state: 'PENDING' }; * } * * plugin.start = (options) => { * app.registerPutHandler('vessels.self', 'some.path', myActionHandler); * } * ``` */ registerPutHandler(context: string, path: string, callback: ActionHandler, source?: string): void; registerActionHandler(context: string, path: string, callback: ActionHandler, source?: string): void; registerHistoryProvider(provider: { hasAnydata: (options: object, cb: (hasResults: boolean) => void) => void; getHistory: (date: Date, path: string, cb: (deltas: object[]) => void) => void; streamHistory: ( /** Primus spark connection - typed as unknown due to dynamic Primus types */ spark: unknown, options: object, onDelta: (delta: object) => void) => void; }): void; /** * Registers a WebSocket endpoint at `path` under the plugin's route, i.e. * at `/plugins/`. Express routers never see HTTP `upgrade` * requests — those are emitted on the underlying HTTP server — so * WebSocket endpoints are registered with this method instead of * {@link Plugin.registerWithRouter}. * * The path is matched exactly (query string excluded). Call in * `plugin.start()`; the endpoint is removed automatically when the plugin * stops. * * By default the server completes the WebSocket handshake and emits * `connection` with a ready-to-use socket: * * ```typescript * plugin.start = () => { * const wss = app.registerWebSocket('/stream') * wss.on('connection', (ws, request) => { * ws.on('message', (data) => ws.send(`got ${data}`)) * }) * } * ``` * * A plugin that needs the raw upgrade — e.g. to forward it to another * server — attaches an `upgrade` listener and handles everything itself: * * ```typescript * const wss = app.registerWebSocket('/ws') * wss.on('upgrade', (request, socket, head) => * proxy.upgrade(request, socket, head) * ) * ``` * * Authentication and authorization are the plugin's responsibility. * * @category Server API */ registerWebSocket(path: string): PluginWebSocketServer; /** * Returns Ports object which contains information about the serial ports available on the machine. * * @category Serial Ports */ getSerialPorts(): Promise; /** * Report to the server that the plugin has sent data to other hosts, which will update the output message rate and * icon in the Dashboard. * * _Note: This function is for use when the plugin is sending data to hosts other than the Signal K server (e.g. * network packets, http requests or messages sent to a broker)._ * * _**This function should NOT be used for deltas that the plugin sends with `handleMessage()`!**_ * * @example * ```javascript * app.reportOutputMessages(54); * ``` * * @param count - number of handled messages between the last * call and this one. If omitted the call will count as one output * message. * * @category Status and Debugging */ reportOutputMessages(count?: number): void; } /** * @deprecated Use {@link ServerAPI} instead. * @category Server API */ export type PluginServerApp = ServerAPI; /** @category Server API */ export type DeltaInputHandler = (delta: Delta, next: (delta: Delta) => void) => void; /** @category Server API */ export interface Ports { byId: string[]; byPath: string[]; byOpenPlotter: string[]; /** Raw serial port info from serialport library - structure varies by platform */ serialports: unknown; } /** @category Server API */ export interface SelfIdentity { selfType: string; selfId: string; selfContext: string; } /** @category Server API */ export interface Metadata { units?: string; description?: string; } /** * A value converted to its path's display unit by * {@link ServerAPI.convertToDisplayUnits}. * * @category Server API */ export interface DisplayUnitsValue { /** The converted value */ value: number; /** Unit symbol for display (e.g. "kn", "°C") */ symbol: string; /** Display format string from the preset or the path (e.g. "0.0") */ displayFormat?: string; } /** * Handler function for PUT/action requests. * @param context - The context (e.g., 'vessels.self') * @param path - The path specified in the PUT request * @param value - The value being set (type depends on the path) * @param callback - Callback for async completion * @category Server API */ export type ActionHandler = (context: string, path: string, value: unknown, callback: (result: ActionResult) => void) => ActionResult; /** @category Server API */ export interface ActionResult { state: 'COMPLETED' | 'PENDING' | 'FAILED'; statusCode?: number; message?: string; timestamp?: string; } //# sourceMappingURL=serverapi.d.ts.map