Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ec6e912
feat: allow enabling/disabling device sources and known devices
heavyrubberslave Jul 5, 2026
2586e6f
Some improvements, still wip
heavyrubberslave Jul 5, 2026
6b3088e
Centralize device enable/disable handling in DeviceManager
heavyrubberslave Jul 18, 2026
a0489e6
Clear pending retry on device disappearance, align virtual provider
heavyrubberslave Jul 18, 2026
f18c599
Generalize device provider detection flow into a base class
heavyrubberslave Jul 18, 2026
68da237
Introduce AnyDevice to erase setAttribute bivariance at device bounda…
heavyrubberslave Jul 18, 2026
cd3e0db
Type AnyDevice.setAttribute with AttributeValue instead of unknown
heavyrubberslave Jul 18, 2026
d25ea81
Restore device-family type safety for BLE/serial providers
heavyrubberslave Jul 18, 2026
bd140d6
Tighten test attribute-value typings from unknown to AttributeValue
heavyrubberslave Jul 18, 2026
c267f46
Address code review findings on device lifecycle and settings
heavyrubberslave Jul 18, 2026
0f9d1d3
Merge DetectedDeviceProvider into DeviceProvider, route virtual throu…
heavyrubberslave Jul 18, 2026
00088ea
Drive buttplug.io scanning off 'scanningfinished' instead of fixed ti…
heavyrubberslave Jul 18, 2026
bb40c9d
Revert "Drive buttplug.io scanning off 'scanningfinished' instead of …
heavyrubberslave Jul 18, 2026
a03632a
Name buttplug.io scan timing constants and cover the scan duty-cycle
heavyrubberslave Jul 18, 2026
9fea5d7
Drop the auto-scan duty-cycle test and its injectable timers
heavyrubberslave Jul 18, 2026
150ed88
Remove unnecessary comments, rename some things for clarity
heavyrubberslave Jul 18, 2026
4de46df
Rename DeviceInfo to DeviceDetectionInfo, disambiguate detection vs c…
heavyrubberslave Jul 19, 2026
833c607
Removed some more unnecessay comments
heavyrubberslave Jul 19, 2026
faf22f7
Reference-count BLE/serial observers; buttplugIo devices close themse…
heavyrubberslave Jul 19, 2026
dd3a17b
Revert renaming
heavyrubberslave Jul 19, 2026
d472a40
Mock the usb module in SerialPortObserver tests instead of double-cal…
heavyrubberslave Jul 19, 2026
e661512
Stop recomputing buttplugIo device id in the factory, matching other …
heavyrubberslave Jul 19, 2026
338aca2
Don't abort virtual device reconciliation when one device fails to close
heavyrubberslave Jul 19, 2026
3e16303
Rename buttplugIo naming leftovers for clarity
heavyrubberslave Jul 19, 2026
7ea3b0d
Don't leave a provider permanently zombied when device.close() fails …
heavyrubberslave Jul 19, 2026
440a058
Replace DeviceProviderManager's hand-rolled operation queue with Sequ…
heavyrubberslave Jul 19, 2026
249e4a0
Extract shared reference-counting logic from BleObserver/SerialPortOb…
heavyrubberslave Jul 19, 2026
d68a8bd
Hydrate schema defaults and validate via AJV in PlainToClassSerialize…
heavyrubberslave Jul 19, 2026
448318b
Drop redundant void prefix on a promise that already has a .catch()
heavyrubberslave Jul 19, 2026
1a9005c
Remove some more comments
heavyrubberslave Jul 19, 2026
e2bfc6b
Fix remaining CodeRabbit findings, parallelize provider start/stop, u…
heavyrubberslave Jul 20, 2026
1a6466c
Fix settings-change races, provider restart, and DI/scope cleanup
heavyrubberslave Jul 22, 2026
72736a5
fix: guard provider shutdown/stop errors so graceful shutdown completes
heavyrubberslave Jul 22, 2026
3ebf4a3
fix: close race between automation log file creation and read
heavyrubberslave Jul 23, 2026
4dffc96
fix: open automation log before isolate creation to avoid leak on wri…
heavyrubberslave Jul 23, 2026
11e4e1c
Split scriptRuntime, wip
heavyrubberslave Jul 24, 2026
24d868b
Restructure scriptRuntime
heavyrubberslave Jul 24, 2026
c857bb0
Give automation scripts their own AutomationScript logger, drop conso…
heavyrubberslave Jul 24, 2026
61071f9
Remove unnecessary comment
heavyrubberslave Jul 25, 2026
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
44 changes: 26 additions & 18 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { ClientToServerEvents, ServerToClientEvents, WebsocketServer } from './s
import { SerializedDevice } from './device/serializedTypes.js';
import { SerializedSettings } from './settings/serializedTypes.js';
import AutomationServiceProvider from './serviceProvider/automationServiceProvider.js';
import Device from './device/device.js';
import { AnyDevice } from './device/device.js';
import WebSocketEvent from './device/webSocketEvent.js';
import AutomationEventType from './automation/automationEventType.js';
import LoggerServiceProvider from './serviceProvider/loggerServiceProvider.js';
Expand Down Expand Up @@ -104,22 +104,22 @@ const configureWebsocket = (io: WebsocketServer, container: Container<ServiceMap
socket.on(WebSocketEvent.deviceUpdateReceived, (data) => deviceUpdateHandler.handle(data));
});

deviceManager.on(DeviceManagerEvent.deviceConnected, (device: Device) => {
deviceManager.on(DeviceManagerEvent.deviceConnected, (device: AnyDevice) => {
io.emit(WebSocketEvent.deviceConnected, serializer.transform<SerializedDevice>(device, deviceDiscriminator));
void scriptRuntime.runForEvent({ type: DeviceManagerEvent.deviceConnected, device, args: [] });
});

deviceManager.on(DeviceManagerEvent.deviceDisconnected, (device: Device) => {
deviceManager.on(DeviceManagerEvent.deviceDisconnected, (device: AnyDevice) => {
io.emit(WebSocketEvent.deviceDisconnected, serializer.transform<SerializedDevice>(device, deviceDiscriminator));
void scriptRuntime.runForEvent({ type: DeviceManagerEvent.deviceDisconnected, device, args: [] });
});

deviceManager.on(DeviceManagerEvent.deviceRefreshed, (device: Device) => {
deviceManager.on(DeviceManagerEvent.deviceRefreshed, (device: AnyDevice) => {
io.emit(WebSocketEvent.deviceRefreshed, serializer.transform<SerializedDevice>(device, deviceDiscriminator));
void scriptRuntime.runForEvent({ type: DeviceManagerEvent.deviceRefreshed, device, args: [] });
});

deviceManager.on(DeviceManagerEvent.deviceNotification, (device: Device, notification) => {
deviceManager.on(DeviceManagerEvent.deviceNotification, (device: AnyDevice, notification) => {
io.emit(WebSocketEvent.deviceNotification, serializer.transform<SerializedDevice>(device, deviceDiscriminator), notification);
void scriptRuntime.runForEvent({ type: DeviceManagerEvent.deviceNotification, device, args: [notification] });
});
Expand All @@ -132,21 +132,25 @@ const configureWebsocket = (io: WebsocketServer, container: Container<ServiceMap
scriptRuntime.on(AutomationEventType.consoleLog, (data: string) => io.emit(AutomationEventType.consoleLog, data));
};

const loadDeviceProviders = (container: Container<ServiceMap>): void => {
const serialPortObserver = container.get('device.observer.serial');
const bleObserver = container.get('device.observer.ble');
const startDeviceProviders = (container: Container<ServiceMap>): void => {
const logger = container.get('logger.default');
const settingsManager = container.get('settings.manager');
const settings = container.get('settings');
const deviceProviderManager = container.get('device.provider.loader');

deviceProviderManager.loadFromSettings(settings);
const deviceProviderManager = container.get('device.provider.manager');
const deviceManager = container.get('device.manager');

deviceProviderManager
.startProviders()
.loadFromSettings(settings)
.catch(e => logError(logger, `Loading device providers failed`, e));

serialPortObserver.start().catch(e => logError(logger, `Initializing serial port observer failed`, e));
bleObserver.init().catch(e => logError(logger, `Initializing BLE observer failed`, e));
settingsManager.on(SettingsEventType.changed, (changedSettings: Settings) => {
// Reload device sources first so re-enabled devices are only re-announced once their provider runs again
deviceProviderManager
.loadFromSettings(changedSettings)
.catch(e => logError(logger, 'Failed to reload device sources after settings change', e))
.then(() => deviceManager.onSettingsChanged())
.catch(e => logError(logger, 'Failed to apply device enabled/disabled changes', e));
});
};

const buildCorsOptions = (allowedOrigins: string[]): CorsOptions => ({
Expand Down Expand Up @@ -205,7 +209,7 @@ export const createApp = (container: Container<ServiceMap>, options: AppOptions)

configureRoutes(app, container);
configureWebsocket(websocketServer, container);
loadDeviceProviders(container);
startDeviceProviders(container);

let serveResult: ServeResult | undefined;
let canBeShutDown = false;
Expand Down Expand Up @@ -259,9 +263,13 @@ export const createApp = (container: Container<ServiceMap>, options: AppOptions)
logger.info('Shutting down...');

await container.get('automation.scriptRuntime').stop();
await container.get('device.observer.serial').stop();
await container.get('device.observer.ble').stop();
await container.get('device.provider.loader').stopProviders();

try {
await container.get('device.provider.manager').stopProviders();
} catch (e: unknown) {
logError(logger, 'Failed to stop device providers during shutdown', e);
}

container.get('health.metricsCollector').stop();
Comment thread
heavyrubberslave marked this conversation as resolved.

await websocketServer.close();
Expand Down
Loading
Loading