UNPKG

signalk-slack-notify

Version:

Send notifications from Signal K to Slack using Webhook API

189 lines (188 loc) 7.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createPlugin = createPlugin; const slack_notify_1 = __importDefault(require("slack-notify")); const types_1 = require("./types"); const helpers_1 = require("./helpers"); const PLUGIN_ID = 'signalk-slack-notify'; const PLUGIN_NAME = 'Signal K notifications to Slack'; const PLUGIN_DESCRIPTION = 'Send notifications from Signal K to Slack using Webhook API'; const pluginSchema = { type: 'object', required: ['webhook'], properties: { webhook: { type: 'string', title: 'Slack Webhook URL' }, slackTitle: { type: 'string', title: 'Slack message title' }, slackChannel: { type: 'string', title: 'Slack channel', default: types_1.DEFAULT_SLACK_CHANNEL }, alertLevels: { type: 'array', title: 'Alert levels to send', description: 'Select which alert levels should trigger Slack notifications (leave empty to send all)', uniqueItems: true, items: { type: 'string', enum: types_1.ALERT_LEVELS }, default: types_1.DEFAULT_ALERT_LEVELS } } }; const pluginUiSchema = { alertLevels: { 'ui:widget': 'checkboxes' } }; const defaultDependencies = { createSlackNotifier: (webhookUrl) => (0, slack_notify_1.default)(webhookUrl), schedule: (callback, delayMs) => setTimeout(callback, delayMs), cancelSchedule: (handle) => clearTimeout(handle), emitAsync: (callback) => setImmediate(callback) }; function safeGetSelfPath(app, path) { try { return app.getSelfPath(path); } catch (error) { app.debug(`Unable to read Signal K path "${path}": ${(0, helpers_1.formatError)(error)}`); return undefined; } } function isSelectedLevel(selectedLevels, notificationState) { return selectedLevels.some((level) => level === notificationState); } function resolveReportedLevels(selectedLevels) { return selectedLevels.length > 0 ? selectedLevels : types_1.ALERT_LEVELS; } function formatAlertLevels(levels) { return levels.length > 0 ? levels.join(', ') : 'none'; } function normalizeOptions(options) { return { ...options, slackChannel: options.slackChannel ?? types_1.DEFAULT_SLACK_CHANNEL, slackTitle: options.slackTitle ?? types_1.DEFAULT_SLACK_TITLE }; } function fallbackMeasurement(rawValue, rawUnits) { return { valueText: (0, helpers_1.stringifyValue)(rawValue), unitLabel: typeof rawUnits === 'string' ? rawUnits : '' }; } function createPlugin(app, dependencies = defaultDependencies) { let unsubscribes = []; const pendingTimers = new Set(); let isStopped = true; const setStatus = app.setPluginStatus ?? app.setProviderStatus; const lastKnownStates = new Map(); function clearPendingTimers() { for (const timer of pendingTimers) { dependencies.cancelSchedule(timer); } pendingTimers.clear(); } function resetRuntime() { isStopped = true; clearPendingTimers(); for (const unsubscribe of unsubscribes) { unsubscribe(); } unsubscribes = []; lastKnownStates.clear(); } async function sendNotification(slack, options, notification) { if (isStopped) { return; } const slackData = safeGetSelfPath(app, notification.dataPath); const rawValue = slackData?.value; const rawUnits = slackData?.meta?.units; let measurement; try { measurement = (0, helpers_1.formatMeasurement)(rawValue, rawUnits); } catch (error) { app.debug(`Unable to format value for "${notification.dataPath}": ${(0, helpers_1.formatError)(error)}`); measurement = fallbackMeasurement(rawValue, rawUnits); } const slackMessage = (0, helpers_1.buildSlackMessage)(options, types_1.DEFAULT_SLACK_TITLE, notification, measurement); try { await slack.send(slackMessage); app.debug(`Reported to Slack for "${notification.notificationPath}": ${JSON.stringify(slackMessage)}`); dependencies.emitAsync(() => { app.emit('connectionwrite', { providerId: PLUGIN_ID }); }); } catch (error) { const errorMessage = `Failed to send Slack notification: ${(0, helpers_1.formatError)(error)}`; app.error(errorMessage); setStatus?.(errorMessage); } } function stop() { resetRuntime(); setStatus?.('Stopped'); } function start(options) { resetRuntime(); isStopped = false; const normalizedOptions = normalizeOptions(options); const slack = dependencies.createSlackNotifier(normalizedOptions.webhook); const selectedLevels = normalizedOptions.alertLevels ?? []; const reportedLevels = resolveReportedLevels(selectedLevels); const skippedLevels = types_1.ALERT_LEVELS.filter((level) => !reportedLevels.includes(level)); app.debug(`${PLUGIN_NAME} started`); app.debug(`Alert level filter. Reported to Slack: ${formatAlertLevels(reportedLevels)}. Skipped: ${formatAlertLevels(skippedLevels)}.`); setStatus?.('Running'); app.subscriptionmanager.subscribe(types_1.NOTIFICATION_SUBSCRIPTION, unsubscribes, (subscriptionError) => { const errorMessage = `Subscription error: ${(0, helpers_1.formatError)(subscriptionError)}`; app.error(errorMessage); setStatus?.(errorMessage); }, (delta) => { const notifications = (0, helpers_1.extractNotifications)(delta); if (notifications.length === 0) { app.debug('No valid notifications found in delta payload.'); return; } for (const notification of notifications) { if (selectedLevels.length > 0 && !isSelectedLevel(selectedLevels, notification.state)) { continue; } const previousState = lastKnownStates.get(notification.notificationPath); if (previousState === notification.state) { app.debug(`Skipping duplicate state "${notification.state}" for "${notification.notificationPath}"`); continue; } lastKnownStates.set(notification.notificationPath, notification.state); const timer = dependencies.schedule(() => { pendingTimers.delete(timer); void sendNotification(slack, normalizedOptions, notification); }, types_1.SEND_DELAY_MS); pendingTimers.add(timer); } }); } return { id: PLUGIN_ID, name: PLUGIN_NAME, description: PLUGIN_DESCRIPTION, start, stop, schema: pluginSchema, uiSchema: pluginUiSchema }; }