diff --git a/README.md b/README.md index 06dc2a9..4740238 100644 --- a/README.md +++ b/README.md @@ -74,9 +74,10 @@ OBS will hide the background | **OBS-CWD** | | String | Location from where to launch OBS | | **OBS-Profile** | | String | Sets OBS Profile | | **OBS-Scene** | | String | Sets OBS Scene -| **OBS-Minimize** | | Switch | Sets OBS minimize on start to true -| **Cert** | | String | File path to SSL Certificate or directory with both SSL |ertificate and Key -| **Key** | | String | File path to SSL Key +| **OBS-Minimize** | obs-min | Switch | Sets OBS minimize on start to true +| **ssl** | | String | File path to SSL directory with both SSL certificate and Key +| **ssl-cert** | | String | File path to SSL Certificate +| **ssl-key** | | String | File path to SSL Key | **UDP** | u | Switch | Starts UDP socket on port 8889 | **UDP** | u | Number | Starts UDP socket on assigned port | **UDP-Interface** | | String | Sets which Interface will be used by UDP. (Can be Network Adapter Name or an IP address used by a Network Adapter) diff --git a/server/src/broadcast.ts b/server/src/broadcast.ts index 85cd0b3..33f4fdb 100644 --- a/server/src/broadcast.ts +++ b/server/src/broadcast.ts @@ -14,7 +14,6 @@ let send: null | ((msg: Buffer, port: number, dest?: string) => void) = null if (udp != null) { const { port, broadcast, user = [], interface: if_addr = getInterfaceAddress() } = udp - const int = networkInterfaces() console.log(`Starting UDP Socket on port ${port} of ${if_addr}`) if (broadcast) console.log('Socket broadcasting to ' + broadcast) const soc = createSocket('udp4') @@ -71,7 +70,7 @@ if (udp != null) { } if (tcp != null) { - const { port = 8891, interface: if_addr = getInterfaceAddress() } = tcp + const { port, interface: if_addr = getInterfaceAddress() } = tcp console.log(`Starting TCP Socket on port ${port} of ${if_addr}`) let clients: { client: Socket, users?: string[] }[] = [] let timer: NodeJS.Timeout | null = null diff --git a/server/src/index.ts b/server/src/index.ts index 9e46ef3..34e2fe8 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -2,12 +2,28 @@ import parameters from './preLoad' import createServer from './webServer' import { Socket } from 'socket.io' import { openOBS, open, getRandomStr64, isStringArray } from './tools' -import { IUser, IKeyVal, IResponseFn, IResponseInit, ISetting, ISettings } from './types' +import { IUser, IKeyVal, IResponseFn, IResponseInit, ISetting, ISettings, GithubReleaseResponse, VersionCheckStatus, GithubAsset } from './types' import { Settings } from './settings' import './broadcast' +import { get } from 'https' +import { platform } from 'os' +import { writeFile } from 'fs/promises' +import { readFileSync } from 'fs' const { oneID, obs, open: openLoc } = parameters +console.log('parameters', parameters) + +let lastVersion = 'v0.0.0' + +try { + const pkgFile = readFileSync('./package.json', 'utf8') + const pkg = JSON.parse(pkgFile) as { version: string, name: string } + lastVersion = 'v' + pkg.version +} catch { } + +console.log('Version:', lastVersion) + const users: { [id: string]: IUser } = {} const sockets: IKeyVal = {} @@ -16,7 +32,7 @@ const io = createServer(address => { if (openLoc != null) { const openVal = openLoc - open(address, openVal === '' ? undefined : { app: { name: openVal } }).catch(() => console.log('Can not Open')) + open(address, typeof openVal !== 'string' ? undefined : { app: { name: openVal } }).catch(() => console.log('Can not Open')) } if (obs != null) { openOBS(obs).catch(err => console.error(err, 'Unable to launch OBS\nPlease pass in the path to the OBS executable or install OBS\nhttps://obsproject.com/download\n')) @@ -66,7 +82,7 @@ io.on('connection', socket => { logUserCount() lastTimestamp = Date.now() - fn({ ok: true, id: userID, settings: user.data, serverTime: Date.now(), idLock: oneID != null }) + fn({ ok: true, id: userID, settings: user.data, serverTime: Date.now(), idLock: oneID != null, version: lastVersion }) } catch (err: any) { console.log({ init: { err } }) if (typeof err == 'string') { @@ -85,7 +101,7 @@ io.on('connection', socket => { lastTimestamp = Date.now() let { keysNotSet, keysSet } = user.set(settings) fn({ ok: true, keysNotSet }) - if(keysNotSet.length > 0) console.warn({ keysNotSet }) + if (keysNotSet.length > 0) console.warn({ keysNotSet }) if (Object.keys(keysSet).length > 0) { sockets[user.id].forEach(soc => { if (socket !== soc) { @@ -108,8 +124,103 @@ io.on('connection', socket => { fn({ ok: false, err: 'Invalid Keys' }) } }) + .on('checkVersion', (_msg: any, fn: IResponseFn) => { + if (!isCheckingVersion) checkVersion() + switch (versionStatus) { + case VersionStatus.checking: + fn({ ok: true, version: lastVersion, status: 'Checking' }) + break + case VersionStatus.downloading: + fn({ ok: true, version: lastVersion, status: 'Downloading' }) + break + case VersionStatus.err: + fn({ ok: false, err: 'Version Check Error' }) + break + case VersionStatus.newest: + fn({ ok: true, version: lastVersion, status: 'Newest' }) + break + case VersionStatus.noPlatform: + fn({ ok: false, err: 'Unknown Platform' }) + break + case VersionStatus.noRelease: + fn({ ok: true, version: lastVersion, status: 'Release Not Found' }) + break + } + }) }) +let lastCheck = 0 +let isCheckingVersion = false +let versionStatus = VersionStatus.newest + +const enum VersionStatus { + checking, + downloading, + newest, + noPlatform, + noRelease, + err +} + +async function checkVersion() { + const timestamp = Date.now() + if (timestamp - lastCheck < 1000) { + return + } + isCheckingVersion = true + lastCheck = Date.now() + try { + versionStatus = VersionStatus.checking + const data = await getP('https://api.github.com/repos/rakusan2/Toastmasters-Timer-Overlay/releases?per_page=4') + if (typeof data === 'string') { + isCheckingVersion = false + console.warn('Unable to parse GitHub Data') + isCheckingVersion = false + versionStatus = VersionStatus.err + return + } + const obj = data.find(a => !a.prerelease && !a.draft) + if (obj == null) { + console.warn('Unable to find release version') + isCheckingVersion = false + versionStatus = VersionStatus.noRelease + return + } + if (obj.tag_name === lastVersion) { + isCheckingVersion = false + versionStatus = VersionStatus.newest + return + } + + let assetID = 0 + let asset: GithubAsset | undefined + const os = platform() + let platformStr: string = '' + if (os === 'win32') platformStr = 'timer-overlay-win.exe' + else if (os === 'linux') platformStr = 'timer-overlay-linux' + else if (os === 'darwin') platformStr = 'timer-overlay-macos' + + asset = obj.assets.find(a => a.name === platformStr) + + if (asset == null) { + isCheckingVersion = false + versionStatus = VersionStatus.noPlatform + return + } + versionStatus = VersionStatus.downloading + const assetBin = await getP('https://api.github.com/repos/rakusan2/Toastmasters-Timer-Overlay/releases/assets/' + assetID, true) + await writeFile(platformStr, assetBin) + + } catch (err) { + console.warn(err) + versionStatus = VersionStatus.err + isCheckingVersion = false + return + } + isCheckingVersion = false + versionStatus = VersionStatus.newest +} + function initUser(id?: string | null) { if (typeof oneID === 'string') { id = oneID @@ -133,3 +244,35 @@ function getID() { users[id] = { settings: {}, lastMessageAt: Date.now() } return id } + +function getP(uri: string, bin: true): Promise +function getP(uri: string, bin?: false): Promise +function getP(uri: string, bin = false) { + return new Promise((res, rej) => { + const req = get(uri, socket => { + const result: Buffer[] = [] + let len = 0 + socket.on('data', (data: Buffer) => { + result.push(data) + len += data.length + }).on('close', () => { + const status = socket.statusCode ?? 0 + const binary = Buffer.concat(result, len) + const str = binary.toString('utf8') + if (status >= 200 || status < 300) { + if (bin) { + res(binary) + return + } + try { + const parsed = JSON.parse(str) + res(parsed) + } catch { + res(str) + } + } else rej(str) + }) + }) + req.on('error', rej) + }) +} diff --git a/server/src/types.d.ts b/server/src/types.d.ts index 1734e5d..9efb706 100644 --- a/server/src/types.d.ts +++ b/server/src/types.d.ts @@ -16,6 +16,7 @@ export interface IResponseInit extends ISettings { id: string idLock: boolean serverTime: number + version: string } export interface ISettings { settings: { @@ -54,4 +55,85 @@ export interface IParamOptions { keyword?: string alias?: string | string[] callback: IFun +} + +export interface VersionCheckStatus { + status: string + version: string +} + +interface GithubReleaseResponse { + url: string; + assets_url: string; + upload_url: string; + html_url: string; + id: number; + author: GithubAuthor; + node_id: string; + tag_name: string; + target_commitish: string; + name: string; + draft: boolean; + prerelease: boolean; + created_at: Date; + published_at: Date; + assets: GithubAsset[]; + tarball_url: string; + zipball_url: string; + body: string; +} +interface GithubAsset { + url: string; + id: number; + node_id: string; + name: string; + label?: any; + uploader: GithubUploader; + content_type: string; + state: string; + size: number; + download_count: number; + created_at: Date; + updated_at: Date; + browser_download_url: string; +} +interface GithubUploader { + login: string; + id: number; + node_id: string; + avatar_url: string; + gravatar_id: string; + url: string; + html_url: string; + followers_url: string; + following_url: string; + gists_url: string; + starred_url: string; + subscriptions_url: string; + organizations_url: string; + repos_url: string; + events_url: string; + received_events_url: string; + type: string; + site_admin: boolean; +} +interface GithubAuthor { + login: string; + id: number; + node_id: string; + avatar_url: string; + gravatar_id: string; + url: string; + html_url: string; + followers_url: string; + following_url: string; + gists_url: string; + starred_url: string; + subscriptions_url: string; + organizations_url: string; + repos_url: string; + events_url: string; + received_events_url: string; + type: string; + site_admin: boolean; } \ No newline at end of file diff --git a/server/src/webServer.ts b/server/src/webServer.ts index ef065fe..94f8a12 100644 --- a/server/src/webServer.ts +++ b/server/src/webServer.ts @@ -5,7 +5,7 @@ import { IServeHandler } from './serverTypes' const { cache, port, sslConfig } = parameters -const dir = __dirname.split(/\\|\//).slice(0,-2) +const dir = __dirname.split(/\\|\//).slice(0, -2) const webDir = dir.join('/') + "/web" console.log('Web: ' + webDir) @@ -19,7 +19,7 @@ const handlerConfig: IServeHandler = { ] } -if (cache) { +if (cache != null) { handlerConfig.headers = [ { source: '*', @@ -49,7 +49,7 @@ export default function startServer(afterStart?: (address: string) => any) { } server.listen(port, () => { - const address = `http://localhost:${port}` + const address = `${sslConfig != null ? 'https' : 'http'}://localhost:${port}` console.log(`listening at ${address} with cache set to ${cache ?? 'DISABLED'}`) if (afterStart != null) afterStart(address) diff --git a/web/index.html b/web/index.html index 06b5315..8e9458e 100644 --- a/web/index.html +++ b/web/index.html @@ -2,6 +2,7 @@ + Timer Overlay @@ -17,6 +18,10 @@
+
+ + +
@@ -33,16 +38,36 @@
- +