Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Porting remaining APIs to tRPC #82

Merged
merged 3 commits into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions PLAN.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
- Milestones
- v2.1
- Switch the majority of the IPC using tRPC.
- Updater will require websocket for subscriptions.
- System will require moving the open and save file support to DOM APIs.
- Drivers will require an overhaul to no longer need handles.
- (#86) Add a means to select a not-in-use port for the tRPC channel.
- (#85) Attempt to secure `serialport` interactione.
- (#78) Switch the majority of the IPC using tRPC.
- (#87) Attempt to secure local RPC channel.
- More drivers.
- (#88) Add means to mark and label experimental drivers.
- (#83) Shinybow
- (#84) TESmart
- v2.2
- Move more modules to core.
- tRPC over Electron IPC.
- Wrap some Electron APIs as services for easier mocking without electron itself.
- Rearrangeable dashboard icons.
- More drivers.
- Monoprice Blackbird
- v3.0
- Remote UI support
- Need settings toggle to control it's activation.
- Need security or authentication method, preferrably just a PIN code.
- Need a means to identify it's URL via the local UI.
- May need a way to disable the power-off button in the remote UI.
- Drivers
- Shinybow
- Monoprice Blackbird
- J-Tech Digital
- ASHATA
- TESmart
- No Hassle AV
- J-Tech Digital -- Need to find actual command list.
- ASHATA -- Now unable to find.
- TESmart -- Brand of Tesla Elec (like TelsaSmart)
- No Hassle AV -- Need to contact.
18 changes: 6 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,19 @@
"@mdi/js": "^7.4.47",
"@mdi/svg": "^7.4.47",
"@sindresorhus/is": "^7.0.1",
"@sixxgate/lint": "^3.2.1",
"@sixxgate/lint": "^3.3.0",
"@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2",
"@tsconfig/node20": "^20.1.4",
"@tsconfig/strictest": "^2.0.5",
"@types/duplexify": "^3.6.4",
"@types/eslint": "^8.56.12",
"@types/ini": "^4.1.1",
"@types/leveldown": "^4.0.6",
"@types/levelup": "^5.1.5",
"@types/node": "^20.17.5",
"@types/pouchdb-core": "^7.0.15",
"@types/pouchdb-find": "^7.3.3",
"@types/setimmediate": "^1.0.4",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.5.13",
"@typescript-eslint/eslint-plugin": "^8.12.2",
"@typescript-eslint/parser": "^8.12.2",
"@vitejs/plugin-vue": "^5.1.4",
Expand All @@ -95,8 +93,7 @@
"@zip.js/zip.js": "^2.7.53",
"assert": "^2.1.0",
"auto-bind": "^5.0.1",
"buffer": "^6.0.3",
"duplexify": "^4.1.3",
"bufferutil": "^4.0.8",
"electron": "^31.7.3",
"electron-builder": "^24.13.3",
"electron-unhandled": "^5.0.0",
Expand All @@ -111,7 +108,6 @@
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^7.1.0",
"eslint-plugin-vue": "^9.30.0",
"events": "^3.3.0",
"execa": "^9.5.1",
"husky": "^9.1.6",
"ini": "^5.0.0",
Expand All @@ -126,16 +122,13 @@
"pouchdb-find": "^9.0.0",
"prettier": "^3.3.3",
"radash": "^12.1.0",
"sass": "^1.80.5",
"setimmediate": "^1.0.5",
"stream-browserify": "^3.0.0",
"sass": "^1.80.6",
"superjson": "^2.2.1",
"tslib": "^2.8.1",
"type-fest": "^4.26.1",
"typescript": "^5.6.3",
"typescript-eslint-parser-for-extra-files": "^0.7.0",
"util": "^0.12.5",
"uuid": "^11.0.2",
"utf-8-validate": "^6.0.5",
"vite": "^5.4.10",
"vite-plugin-vue-devtools": "^7.6.2",
"vite-plugin-vuetify": "^2.0.4",
Expand All @@ -147,6 +140,7 @@
"vue-router": "^4.4.5",
"vue-tsc": "^2.1.10",
"vuetify": "^3.7.3",
"ws": "^8.18.0",
"xdg-basedir": "^5.1.0",
"zod": "^3.23.8"
},
Expand Down
39 changes: 34 additions & 5 deletions src/core/basics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,40 @@ export function toArray<T>(value: T): T extends unknown[] ? T : T[] {
}

/**
* Wait a specified amount of time.
* @param timeout - The amount of time to wait in milliseconds.
* Creates a new promise with externally accessible fulfillment operations.
*
* This is a polyfill for
* [Promise.withResolver](https://developer.mozilla.org/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers).
*
* @returns An object with a Promise and its fulfillment operations.
*/
export async function waitTill(timeout: number) {
await new Promise<void>((resolve) => {
setTimeout(resolve, timeout)
export function withResolvers<T>() {
let resolve: (value: T | PromiseLike<T>) => void = () => undefined
let reject: (reason?: unknown) => void = () => undefined

const promise = new Promise<T>((_resolve, _reject) => {
resolve = _resolve
reject = _reject
})

return { resolve, reject, promise }
}

/**
* Run an operation asynchronously as a microtask.
* @param op - The operation to run as a micro-task.
* @returns The result of the operation.
*/
export async function asMicrotask<Result>(op: () => MaybePromise<Result>) {
return await new Promise<Result>((resolve, reject) => {
queueMicrotask(() => {
try {
// eslint-disable-next-line @typescript-eslint/use-unknown-in-catch-callback-variable -- Proxied
Promise.resolve(op()).then(resolve).catch(reject)
} catch (cause) {
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- Proxied
reject(cause)
}
})
})
}
5 changes: 0 additions & 5 deletions src/core/struct.ts

This file was deleted.

25 changes: 25 additions & 0 deletions src/core/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const protocol = ['http:', 'ws:'] as const
const support = protocol.join(',')
export type Protocol = (typeof protocol)[number]

function isSupportedProtocol(value: unknown): value is Protocol {
return protocol.includes(value as never)
}

export type ServerSettings = [host: string, port: number, protocol: Protocol]

export function getServerUrl(url: URL, defaultPort: number) {
if (!isSupportedProtocol(url.protocol)) throw new TypeError(`${url.protocol} is not supported; only ${support}`)
if (url.pathname.length > 1) throw new TypeError('Server must be at the root')
if (url.search.length > 0) throw new TypeError('Query parameters mean nothing')
if (url.hash.length > 0) throw new TypeError('Query parameters mean nothing')
if (url.username.length > 0) throw new TypeError('Username currently unsupported')
if (url.password.length > 0) throw new TypeError('Password currently unsupported')
if (url.hostname.length === 0) return ['127.0.0.1', defaultPort, url.protocol] satisfies ServerSettings
if (url.port.length === 0) return [url.hostname, defaultPort, url.protocol] satisfies ServerSettings

const port = Number(url.port)
if (Number.isNaN(port)) throw new TypeError(`${url.port} is not a valid port`)

return [url.hostname, port, url.protocol] satisfies ServerSettings
}
1 change: 0 additions & 1 deletion src/main/dao/storage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { memo } from 'radash'
import { useLevelDb } from '../services/level'

export type UserStore = ReturnType<typeof useUserStore>
const useUserStore = memo(function useUserStore() {
const { levelup } = useLevelDb()

Expand Down
22 changes: 11 additions & 11 deletions src/main/drivers/extron/sis.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Logger from 'electron-log'
import { defineDriver, kDeviceCanDecoupleAudioOutput, kDeviceSupportsMultipleOutputs } from '../../services/driver'
import { defineDriver, kDeviceCanDecoupleAudioOutput, kDeviceSupportsMultipleOutputs } from '../../services/drivers'
import { createCommandStream } from '../../services/stream'

const extronSisDriver = defineDriver({
enable: true,
enabled: true,
guid: '4C8F2838-C91D-431E-84DD-3666D14A6E2C',
localized: {
en: {
Expand All @@ -13,8 +13,8 @@ const extronSisDriver = defineDriver({
}
},
capabilities: kDeviceSupportsMultipleOutputs | kDeviceCanDecoupleAudioOutput,
setup: async function setup(uri) {
async function sendCommand(command: string) {
setup: () => {
async function sendCommand(uri: string, command: string) {
const connection = await createCommandStream(uri, {
baudRate: 9600,
dataBits: 8,
Expand All @@ -36,11 +36,11 @@ const extronSisDriver = defineDriver({
await connection.close()
}

async function activate(inputChannel: number, videoOutputChannel: number, audioOutputChannel: number) {
Logger.log(`extronSisDriver.activate(${inputChannel}, ${videoOutputChannel}, ${audioOutputChannel})`)
const videoCommand = `${inputChannel}*${videoOutputChannel}%`
const audioCommand = `${inputChannel}*${audioOutputChannel}$`
await sendCommand(`${videoCommand}\r\n${audioCommand}\r\n`)
async function activate(uri: string, input: number, videoOutput: number, audioOutput: number) {
Logger.log(`extronSisDriver.activate(${input}, ${videoOutput}, ${audioOutput})`)
const videoCommand = `${input}*${videoOutput}%`
const audioCommand = `${input}*${audioOutput}$`
await sendCommand(uri, `${videoCommand}\r\n${audioCommand}\r\n`)
}

async function powerOn() {
Expand All @@ -55,11 +55,11 @@ const extronSisDriver = defineDriver({
await Promise.resolve()
}

return await Promise.resolve({
return {
activate,
powerOn,
powerOff
})
}
}
})

Expand Down
37 changes: 22 additions & 15 deletions src/main/drivers/sony/rs485.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import Logger from 'electron-log'
import { defineDriver, kDeviceHasNoExtraCapabilities } from '../../services/driver'
import { createAddress, createCommand, kAddressAll, kPowerOff, kPowerOn, kSetChannel } from '../../services/sonyRs485'
import { defineDriver, kDeviceHasNoExtraCapabilities } from '../../services/drivers'
import { createCommandStream } from '../../services/stream'
import type { Command, CommandArg } from '../../services/sonyRs485'
import {
createAddress,
createCommand,
kAddressAll,
kPowerOff,
kPowerOn,
kSetChannel
} from '../../services/support/sonyRs485'
import type { Command, CommandArg } from '../../services/support/sonyRs485'

const sonyRs485Driver = defineDriver({
enable: true,
enabled: true,
guid: '8626D6D3-C211-4D21-B5CC-F5E3B50D9FF0',
localized: {
en: {
Expand All @@ -15,8 +22,8 @@ const sonyRs485Driver = defineDriver({
}
},
capabilities: kDeviceHasNoExtraCapabilities,
setup: async function setup(uri) {
async function sendCommand(command: Command, arg0?: CommandArg, arg1?: CommandArg) {
setup: () => {
async function sendCommand(uri: string, command: Command, arg0?: CommandArg, arg1?: CommandArg) {
const source = createAddress(kAddressAll, 0)
const destination = createAddress(kAddressAll, 0)
const packet = createCommand(destination, source, command, arg0, arg1)
Expand All @@ -42,26 +49,26 @@ const sonyRs485Driver = defineDriver({
await connection.close()
}

async function activate(inputChannel: number) {
Logger.log(`sonyRs485Driver.activate(${inputChannel})`)
await sendCommand(kSetChannel, 1, inputChannel)
async function activate(uri: string, input: number) {
Logger.log(`sonyRs485Driver.activate(${input})`)
await sendCommand(uri, kSetChannel, 1, input)
}

async function powerOn() {
async function powerOn(uri: string) {
Logger.log('sonyRs485Driver.powerOn')
await sendCommand(kPowerOn)
await sendCommand(uri, kPowerOn)
}

async function powerOff() {
async function powerOff(uri: string) {
Logger.log('sonyRs485Driver.powerOff')
await sendCommand(kPowerOff)
await sendCommand(uri, kPowerOff)
}

return await Promise.resolve({
return {
activate,
powerOn,
powerOff
})
}
}
})

Expand Down
18 changes: 9 additions & 9 deletions src/main/drivers/tesla-smart/kvm.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Logger from 'electron-log'
import { defineDriver, kDeviceHasNoExtraCapabilities } from '../../services/driver'
import { defineDriver, kDeviceHasNoExtraCapabilities } from '../../services/drivers'
import { createCommandStream } from '../../services/stream'

const teslaSmartKvmDriver = defineDriver({
enable: true,
enabled: true,
guid: '91D5BC95-A8E2-4F58-BCAC-A77BA1054D61',
localized: {
en: {
Expand All @@ -13,8 +13,8 @@ const teslaSmartKvmDriver = defineDriver({
}
},
capabilities: kDeviceHasNoExtraCapabilities,
setup: async function setup(uri) {
async function sendCommand(command: Buffer) {
setup: () => {
async function sendCommand(uri: string, command: Buffer) {
const connection = await createCommandStream(uri, {
baudRate: 9600,
dataBits: 8,
Expand All @@ -36,9 +36,9 @@ const teslaSmartKvmDriver = defineDriver({
await connection.close()
}

async function activate(inputChannel: number) {
Logger.log(`teslaSmartKvmDriver.activate(${inputChannel})`)
await sendCommand(Buffer.of(0xaa, 0xbb, 0x03, 0x01, inputChannel, 0xee))
async function activate(uri: string, input: number) {
Logger.log(`teslaSmartKvmDriver.activate(${input})`)
await sendCommand(uri, Buffer.of(0xaa, 0xbb, 0x03, 0x01, input, 0xee))
}

async function powerOn() {
Expand All @@ -53,11 +53,11 @@ const teslaSmartKvmDriver = defineDriver({
await Promise.resolve()
}

return await Promise.resolve({
return {
activate,
powerOn,
powerOff
})
}
}
})

Expand Down
Loading