Implement standalone control server

This commit is contained in:
Max Goodhart
2025-06-14 06:46:27 +00:00
parent ec6b7bd360
commit 9ded048667
29 changed files with 3414 additions and 415 deletions

View File

@@ -5,7 +5,8 @@
"type": "module",
"main": "./src/index.ts",
"dependencies": {
"color": "^5.0.0"
"color": "^5.0.0",
"jsondiffpatch": "^0.7.3"
},
"devDependencies": {
"typescript": "~5.6.2"

View File

@@ -1,6 +1,6 @@
import { Rectangle } from 'electron'
import type { Rectangle } from 'electron'
import { isEqual } from 'lodash-es'
import { ContentKind } from './types'
import type { ContentKind } from './types.ts'
export interface ViewPos extends Rectangle {
/**

View File

@@ -1,4 +1,5 @@
export * from './colors'
export * from './geometry'
export * from './roles'
export * from './types'
export * from './colors.ts'
export * from './geometry.ts'
export * from './roles.ts'
export * from './stateDiff.ts'
export * from './types.ts'

View File

@@ -1,6 +1,12 @@
export const validRoles = ['local', 'admin', 'operator', 'monitor'] as const
export const validRolesSet = new Set(validRoles)
const adminActions = ['dev-tools', 'browse', 'edit-tokens'] as const
const adminActions = [
'dev-tools',
'browse',
'create-invite',
'delete-token',
] as const
const operatorActions = [
'set-listening-view',

View File

@@ -0,0 +1,6 @@
import * as jsondiffpatch from 'jsondiffpatch'
export const stateDiff = jsondiffpatch.create({
objectHash: (obj: any, idx) => obj._id || `$$index:${idx}`,
omitRemovedValues: true,
})

View File

@@ -1,4 +1,5 @@
import { ViewContent, ViewPos } from './geometry'
import type { ViewContent, ViewPos } from './geometry.ts'
import type { StreamwallRole } from './roles.ts'
export interface StreamWindowConfig {
gridCount: number
@@ -75,13 +76,35 @@ export interface StreamDelayStatus {
state: string
}
export type AuthTokenKind = 'invite' | 'session' | 'streamwall'
export interface AuthTokenInfo {
tokenId: string
kind: AuthTokenKind
role: StreamwallRole
name: string
}
export interface StreamwallState {
identity: {
role: StreamwallRole
}
auth?: {
invites: AuthTokenInfo[]
sessions: AuthTokenInfo[]
}
config: StreamWindowConfig
streams: StreamList
customStreams: StreamList
views: ViewState[]
streamdelay: StreamDelayStatus | null
}
type MessageMeta = {
id: number
clientId: string
}
export type ControlCommand =
| { type: 'set-listening-view'; viewIdx: number | null }
| {
@@ -100,3 +123,12 @@ export type ControlCommand =
| { type: 'set-stream-running'; isStreamRunning: boolean }
| { type: 'create-invite'; role: string; name: string }
| { type: 'delete-token'; tokenId: string }
export type ControlUpdate = {
type: 'state'
state: StreamwallState
}
export type ControlCommandMessage = MessageMeta & ControlCommand
export type ControlUpdateMessage = MessageMeta & ControlUpdate

View File

@@ -1,31 +1,8 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"paths": {
"react": ["./node_modules/preact/compat/"],
"react-dom": ["./node_modules/preact/compat/"]
},
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"jsxImportSource": "preact",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"extends": [
"@tsconfig/recommended/tsconfig",
"@tsconfig/node22/tsconfig",
"@tsconfig/node-ts/tsconfig"
],
"include": ["src"]
}