-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added websocket support for tRPC and ported updated system over
- Loading branch information
Showing
16 changed files
with
255 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { observable } from '@trpc/server/observable' | ||
import { memo } from 'radash' | ||
import { procedure, router } from '../services/trpc' | ||
import useUpdater from '../services/updater' | ||
import type { AppUpdaterEventMap } from '../services/updater' | ||
|
||
const useUpdaterRouter = memo(function useUpdaterRouter() { | ||
const updater = useUpdater() | ||
|
||
function defineEvent<Name extends keyof AppUpdaterEventMap>(name: Name) { | ||
type Args = AppUpdaterEventMap[Name] | ||
// TODO: tRPC 11, use "for await...of on" with AbortSignal | ||
return () => | ||
observable<Args>((emit) => { | ||
const proxy = (...args: Args) => { | ||
emit.next(args) | ||
} | ||
|
||
updater.on(name, proxy as never) | ||
|
||
return () => { | ||
updater.off(name, proxy as never) | ||
} | ||
}) | ||
} | ||
|
||
return router({ | ||
onChecking: procedure.subscription(defineEvent('checking')), | ||
onAvailable: procedure.subscription(defineEvent('available')), | ||
onProgress: procedure.subscription(defineEvent('progress')), | ||
onDownloaded: procedure.subscription(defineEvent('downloaded')), | ||
onCancelled: procedure.subscription(defineEvent('cancelled')), | ||
checkForUpdates: procedure.query(async () => await updater.checkForUpdates()), | ||
downloadUpdate: procedure.query(async () => { | ||
await updater.downloadUpdate() | ||
}), | ||
cancelUpdate: procedure.query(async () => { | ||
await updater.cancelUpdate() | ||
}), | ||
installUpdate: procedure.query(async () => { | ||
await updater.installUpdate() | ||
}) | ||
}) | ||
}) | ||
|
||
export default useUpdaterRouter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,42 +1,61 @@ | ||
import { createHTTPServer } from '@trpc/server/adapters/standalone' | ||
import { applyWSSHandler } from '@trpc/server/adapters/ws' | ||
import Logger from 'electron-log' | ||
import { WebSocketServer } from 'ws' | ||
import useAppConfig from './info/config' | ||
import { useAppRouter } from './routes/router' | ||
import { getServerUrl } from '@/url' | ||
|
||
function getServerUrl(url: URL): [host: string, port: number] { | ||
Logger.log(url.pathname) | ||
if (url.protocol !== 'http:') throw new TypeError('Only HTTP is supported') | ||
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', 7180] | ||
if (url.port.length === 0) return [url.hostname, 7180] | ||
|
||
const port = Number(url.port) | ||
if (Number.isNaN(port)) throw new TypeError(`${url.port} is not a valid port`) | ||
|
||
return [url.hostname, port] | ||
} | ||
function startWebSocketServer(url: URL, host: string, port: number) { | ||
// TODO: Authentication via the IPC, later we'll implement a proper authentication model. | ||
|
||
export default function useApiServer() { | ||
const config = useAppConfig() | ||
const url = new URL(config.rpcUrl) | ||
const [host, port] = getServerUrl(url) | ||
process.env['WS_NO_UTF_8_VALIDATE'] = '1' | ||
|
||
const wss = new WebSocketServer({ host, port }) | ||
|
||
wss.on('listening', () => { | ||
Logger.info(`RPC server at ${url}`) | ||
}) | ||
|
||
const handler = applyWSSHandler({ wss, router: useAppRouter() }) | ||
|
||
process.on('exit', () => { | ||
handler.broadcastReconnectNotification() | ||
wss.close() | ||
}) | ||
|
||
process.on('SIGTERM', () => { | ||
handler.broadcastReconnectNotification() | ||
wss.close() | ||
}) | ||
} | ||
|
||
function startHttpServer(url: URL, host: string, port: number) { | ||
// TODO: Authentication via the IPC, later we'll implement a proper authentication model. | ||
|
||
const httpServer = createHTTPServer({ | ||
const server = createHTTPServer({ | ||
router: useAppRouter() | ||
}) | ||
|
||
httpServer.listen(port, host) | ||
httpServer.server.on('listening', () => { | ||
server.server.on('listening', () => { | ||
Logger.info(`RPC server at ${url}`) | ||
}) | ||
|
||
server.listen(port, host) | ||
process.on('exit', () => { | ||
httpServer.server.close() | ||
server.server.close() | ||
}) | ||
|
||
process.on('SIGTERM', () => { | ||
server.server.close() | ||
}) | ||
} | ||
|
||
export default function useApiServer() { | ||
const config = useAppConfig() | ||
const url = new URL(config.rpcUrl) | ||
const [host, port, protocol] = getServerUrl(url, 7180) | ||
|
||
if (protocol === 'http:') startHttpServer(url, host, port) | ||
if (protocol === 'ws:') startWebSocketServer(url, host, port) | ||
} |
Oops, something went wrong.