From b0cc645c1cea1555fedcc01e89b45c72a699fd39 Mon Sep 17 00:00:00 2001 From: Alex Christoffer Rasmussen Date: Mon, 6 Jan 2025 20:21:25 +0100 Subject: [PATCH] Handle urls better (#96) * sanitizeHost by casting to url * make upgrade script * ensure ending slash * bump to v4.6.0 * unroll ternaries * makeURL function * use `makeURL` for all url needs * remove outdated help text --- companion/HELP.md | 9 ++++----- companion/manifest.json | 2 +- package.json | 2 +- src/config.ts | 16 +++------------ src/upgrades.ts | 28 +++++++++++++++++++++++++- src/utilities.ts | 31 ++++++++++++++++++++++++++--- src/v3/connection.ts | 44 +++++++++++++++++++++++------------------ 7 files changed, 89 insertions(+), 43 deletions(-) diff --git a/companion/HELP.md b/companion/HELP.md index a1bac31..3cbf841 100644 --- a/companion/HELP.md +++ b/companion/HELP.md @@ -1,14 +1,13 @@ -## Ontime Companion Module +# Ontime Companion Module This module gives control over Ontime leveraging its [WebSockets API](https://docs.getontime.no/api/protocols/websockets/) -### Requirements +## Requirements + - This module version requires Ontime v3 or above -### Configuration -- **Ontime server port** which is by default 4001. Keep in mind that this can be changed by the user +## Links -### Links You can download ontime from the website [www.getontime.no](https://www.getontime.no/) \ Read the docs at [http://docs.getontime.no](https://docs.getontime.no/) \ Follow Ontime's development on [GitHub](https://github.com/cpvalente/ontime) diff --git a/companion/manifest.json b/companion/manifest.json index aa02604..f7638d7 100644 --- a/companion/manifest.json +++ b/companion/manifest.json @@ -3,7 +3,7 @@ "name": "getontime-ontime", "shortname": "ontime", "description": "Companion module for ontime", - "version": "4.5.0", + "version": "4.6.0", "license": "MIT", "repository": "git+https://github.com/bitfocus/companion-module-getontime-ontime.git", "bugs": "https://github.com/bitfocus/companion-module-getontime-ontime/issues", diff --git a/package.json b/package.json index 42ce85c..b61a5a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "getontime-ontime", - "version": "4.5.0", + "version": "4.6.0", "main": "/dist/index.js", "license": "MIT", "prettier": "@companion-module/tools/.prettierrc.json", diff --git a/src/config.ts b/src/config.ts index dde7804..269f605 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,8 +1,8 @@ -import { SomeCompanionConfigField, Regex } from '@companion-module/base' +import { SomeCompanionConfigField } from '@companion-module/base' export interface OntimeConfig { host: string - port: string + port: string | null //TODO: remove ssl: boolean version: string //TODO: remove refetchEvents: boolean @@ -28,17 +28,7 @@ export function GetConfigFields(): SomeCompanionConfigField[] { default: '127.0.0.1', width: 6, required: true, - tooltip: 'Ontime server address. Valid are IP or URL', - }, - { - label: 'Ontime server port ', - id: 'port', - type: 'textinput', - default: '4001', - required: true, - width: 3, - regex: Regex.PORT, - tooltip: 'Ontime server port. Default is 4001', + tooltip: 'Ontime server address. eg. http://127.0.0.1:4001', }, { label: 'Use SSL', diff --git a/src/upgrades.ts b/src/upgrades.ts index 0e93265..6d094a1 100644 --- a/src/upgrades.ts +++ b/src/upgrades.ts @@ -291,4 +291,30 @@ function update4xx( return result } -export const UpgradeScripts: CompanionStaticUpgradeScript[] = [update2x4x0, update3x4x0, update4xx] +function update46x(_context: CompanionUpgradeContext, props: CompanionStaticUpgradeProps) { + const result: CompanionStaticUpgradeResult = { + updatedConfig: null, + updatedActions: new Array(), + updatedFeedbacks: new Array(), + } + + if (props.config === null) { + return result + } + + const { host, port } = props.config + if (port === null) { + return result + } + + const newAddress = `${host}:${port}` + result.updatedConfig = { ...props.config, host: newAddress, port: null } + return result +} + +export const UpgradeScripts: CompanionStaticUpgradeScript[] = [ + update2x4x0, + update3x4x0, + update4xx, + update46x, +] diff --git a/src/utilities.ts b/src/utilities.ts index df531fc..d48650e 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -18,9 +18,34 @@ const defaultTimerObject = { negative: '', } -export function sanitizeHost(host: string) { - const pattern = /^((http|https):\/\/)/ - return host.replace(pattern, '') +function ensureTrailingSlash(url: URL): URL { + if (!url.pathname.endsWith('/')) { + url.pathname += '/' + } + return url +} + +export function makeURL(host: string, path = '', ssl = false, ws = false) { + let url: URL | undefined + + if (URL.canParse(host)) { + url = new URL(host) + } else if (URL.canParse(`http://${host}`)) { + url = new URL(`http://${host}`) + } + + if (url === undefined) return + + url = ensureTrailingSlash(url) + url.pathname += path + + if (ssl) { + url.protocol = ws ? 'wss' : 'https' + } else { + url.protocol = ws ? 'ws' : 'http' + } + + return url } type SplitTime = typeof defaultTimerObject diff --git a/src/v3/connection.ts b/src/v3/connection.ts index d68921c..d3d93f3 100644 --- a/src/v3/connection.ts +++ b/src/v3/connection.ts @@ -1,7 +1,7 @@ import { InputValue, InstanceStatus } from '@companion-module/base' import { OnTimeInstance } from '..' import Websocket from 'ws' -import { findPreviousPlayableEvent, msToSplitTime, sanitizeHost, variablesFromCustomFields } from '../utilities' +import { findPreviousPlayableEvent, msToSplitTime, makeURL, variablesFromCustomFields } from '../utilities' import { feedbackId, variableId } from '../enums' import { CurrentBlockState, @@ -26,11 +26,10 @@ export function connect(self: OnTimeInstance, ontime: OntimeV3): void { reconnectInterval = self.config.reconnectInterval * 1000 shouldReconnect = self.config.reconnect - const host = sanitizeHost(self.config.host) - const port = self.config.port + const wsUrls = makeURL(self.config.host, 'ws', self.config.ssl, true) - if (!host || !port) { - self.updateStatus(InstanceStatus.BadConfig, `no host and/or port defined`) + if (!wsUrls) { + self.updateStatus(InstanceStatus.BadConfig, `host format error`) return } @@ -40,9 +39,9 @@ export function connect(self: OnTimeInstance, ontime: OntimeV3): void { ws.close() } - const prefix = self.config.ssl ? 'wss' : 'ws' + ws = new Websocket(wsUrls) - ws = new Websocket(`${prefix}://${host}:${port}/ws`) + self.log('info', `connection to server with: ${wsUrls}`) ws.onopen = () => { clearTimeout(reconnectionTimeout as NodeJS.Timeout) @@ -352,24 +351,25 @@ export function socketSendJson(type: string, payload?: InputValue | object): voi let rundownEtag: string = '' async function fetchAllEvents(self: OnTimeInstance, ontime: OntimeV3): Promise { - const prefix = self.config.ssl ? 'https' : 'http' - const host = sanitizeHost(self.config.host) - + const serverHttp = makeURL(self.config.host, 'data/rundown', self.config.ssl) + if (!serverHttp) { + return + } self.log('debug', 'fetching events from ontime') try { - const response = await fetch(`${prefix}://${host}:${self.config.port}/data/rundown`, { + const response = await fetch(serverHttp.href, { method: 'GET', headers: { 'if-none-match': rundownEtag, 'cache-control': '3600', pragma: '' }, }) + if (response.status === 304) { + self.log('debug', '304 -> nothing change in rundown') + return + } if (!response.ok) { ontime.events = [] self.log('error', `uable to fetch events: ${response.statusText}`) return } - if (response.status === 304) { - self.log('debug', '304 -> nothing change in rundown') - return - } rundownEtag = response.headers.get('Etag') ?? '' const data = (await response.json()) as OntimeBaseEvent[] ontime.events = data.filter((entry) => entry.type === SupportedEvent.Event) as OntimeEvent[] @@ -385,12 +385,13 @@ let customFieldsEtag: string = '' //TODO: this might need to be updated on an interval async function fetchCustomFields(self: OnTimeInstance, ontime: OntimeV3): Promise { - const prefix = self.config.ssl ? 'https' : 'http' - const host = sanitizeHost(self.config.host) - + const serverHttp = makeURL(self.config.host, 'data/custom-fields', self.config.ssl) + if (!serverHttp) { + return false + } self.log('debug', 'fetching custom-fields from ontime') try { - const response = await fetch(`${prefix}://${host}:${self.config.port}/data/custom-fields`, { + const response = await fetch(serverHttp, { method: 'GET', headers: { 'if-none-match': customFieldsEtag, 'cache-control': '3600', pragma: '' }, }) @@ -398,6 +399,11 @@ async function fetchCustomFields(self: OnTimeInstance, ontime: OntimeV3): Promis self.log('debug', '304 -> nothing change custom fields') return false } + if (!response.ok) { + ontime.events = [] + self.log('error', `uable to fetch events: ${response.statusText}`) + return false + } customFieldsEtag = response.headers.get('Etag') ?? '' const data = (await response.json()) as CustomFields ontime.customFields = data