This commit is contained in:
jeanotx32
2026-08-11 00:26:56 +02:00
commit 6f11b72cbb
43 changed files with 7175 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
{
"name": "@stream-control/shared",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": ["dist"],
"scripts": {
"build": "tsc -b",
"watch": "tsc -b --watch"
},
"devDependencies": {
"typescript": "^5.7.3"
}
}

View File

@@ -0,0 +1,257 @@
/**
* Protocole partagé entre le serveur de contrôle, les agents et le dashboard.
*
* Transport : WebSocket, messages JSON, un champ `type` discriminant.
* - agent <-> serveur : /ws/agent (l'agent initie la connexion sortante)
* - browser <- serveur : /ws/dashboard (flux de statut temps réel)
*/
export const PROTOCOL_VERSION = 1;
export type Platform = 'windows' | 'linux' | 'darwin' | 'unknown';
/** Paramètres de connexion à obs-websocket (plugin intégré à OBS >= 28). */
export interface ObsSettings {
host: string;
port: number;
/** Mot de passe obs-websocket ; chaîne vide si l'authentification est désactivée. */
password: string;
}
export const DEFAULT_OBS_SETTINGS: ObsSettings = {
host: '127.0.0.1',
port: 4455,
password: '',
};
// ---------------------------------------------------------------------------
// Actions pilotables sur un agent
// ---------------------------------------------------------------------------
export const AGENT_ACTIONS = [
'obs.connect',
'obs.disconnect',
'obs.refresh',
'record.start',
'record.stop',
'record.pause',
'record.resume',
'record.split',
'stream.start',
'stream.stop',
'scene.set',
'profile.set',
'collection.set',
'recordDirectory.set',
'agent.ping',
] as const;
export type AgentAction = (typeof AGENT_ACTIONS)[number];
export function isAgentAction(value: unknown): value is AgentAction {
return typeof value === 'string' && (AGENT_ACTIONS as readonly string[]).includes(value);
}
/** Paramètres attendus par action (les autres actions n'en prennent aucun). */
export interface AgentActionParams {
'scene.set': { scene: string };
'profile.set': { profile: string };
'collection.set': { collection: string };
'recordDirectory.set': { directory: string };
}
// ---------------------------------------------------------------------------
// Statut remonté par un agent
// ---------------------------------------------------------------------------
export interface AgentStatus {
/** L'agent a-t-il une session obs-websocket établie ? */
obsConnected: boolean;
obsVersion?: string;
obsError?: string;
recording: boolean;
recordPaused: boolean;
/** Durée d'enregistrement au format HH:MM:SS.mmm renvoyé par OBS. */
recordTimecode?: string;
recordBytes?: number;
/** Chemin du dernier fichier écrit (renseigné à l'arrêt de l'enregistrement). */
lastRecordingPath?: string;
recordDirectory?: string;
streaming: boolean;
streamTimecode?: string;
currentScene?: string;
scenes: string[];
currentProfile?: string;
profiles: string[];
currentCollection?: string;
collections: string[];
/** Statistiques OBS. */
cpuUsage?: number;
fps?: number;
droppedFrames?: number;
renderSkippedFrames?: number;
/** Statistiques machine (collectées par l'agent, pas par OBS). */
systemCpu?: number;
systemMemoryUsed?: number;
systemMemoryTotal?: number;
diskFreeBytes?: number;
diskTotalBytes?: number;
updatedAt: number;
}
export function emptyStatus(): AgentStatus {
return {
obsConnected: false,
recording: false,
recordPaused: false,
streaming: false,
scenes: [],
profiles: [],
collections: [],
updatedAt: 0,
};
}
// ---------------------------------------------------------------------------
// Messages agent -> serveur
// ---------------------------------------------------------------------------
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
export interface HelloMessage {
type: 'hello';
protocol: number;
/** Absent lors du tout premier enrôlement : le serveur en attribue un. */
agentId?: string;
name: string;
hostname: string;
platform: Platform;
agentVersion: string;
}
export interface StatusMessage {
type: 'status';
status: AgentStatus;
}
export interface ResultMessage {
type: 'result';
requestId: string;
ok: boolean;
data?: unknown;
error?: string;
}
export interface LogMessage {
type: 'log';
level: LogLevel;
message: string;
ts: number;
}
export interface PongMessage {
type: 'pong';
ts: number;
}
export type AgentToServer =
| HelloMessage
| StatusMessage
| ResultMessage
| LogMessage
| PongMessage;
// ---------------------------------------------------------------------------
// Messages serveur -> agent
// ---------------------------------------------------------------------------
export interface WelcomeMessage {
type: 'welcome';
agentId: string;
/** Fourni uniquement lors de l'enrôlement : l'agent doit le persister. */
token?: string;
obs: ObsSettings;
/** Fréquence de remontée de statut demandée. */
statusIntervalMs: number;
/** Si vrai, l'agent tente de se connecter à OBS dès le démarrage. */
autoConnectObs: boolean;
}
export interface CommandMessage {
type: 'command';
requestId: string;
action: AgentAction;
params?: Record<string, unknown>;
}
export interface ConfigMessage {
type: 'config';
obs: ObsSettings;
autoConnectObs: boolean;
}
export interface PingMessage {
type: 'ping';
ts: number;
}
export type ServerToAgent = WelcomeMessage | CommandMessage | ConfigMessage | PingMessage;
// ---------------------------------------------------------------------------
// Vue agrégée exposée au dashboard
// ---------------------------------------------------------------------------
export interface AgentView {
id: string;
name: string;
hostname: string | null;
platform: Platform;
agentVersion: string | null;
online: boolean;
lastSeenAt: number | null;
createdAt: number;
obs: ObsSettings;
autoConnectObs: boolean;
notes: string | null;
status: AgentStatus;
}
export interface LogEntry {
id: number;
agentId: string | null;
agentName: string | null;
level: LogLevel;
message: string;
ts: number;
}
export type ServerToDashboard =
| { type: 'snapshot'; agents: AgentView[]; logs: LogEntry[] }
| { type: 'agent'; agent: AgentView }
| { type: 'agent.removed'; agentId: string }
| { type: 'log'; entry: LogEntry };
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
export function detectPlatform(raw: string): Platform {
if (raw === 'win32') return 'windows';
if (raw === 'linux') return 'linux';
if (raw === 'darwin') return 'darwin';
return 'unknown';
}
export function safeJsonParse<T>(raw: string): T | null {
try {
return JSON.parse(raw) as T;
} catch {
return null;
}
}

View File

@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src/**/*.ts"]
}