InputKit is a dependency-free JavaScript client for the current Input WebSocket API. It runs on Node.js 22.4 and later and on any runtime with a standard global WebSocket. Input must be running, and each client must request authorization once before it can authenticate on later connections. It follows the protocol documented by the Input application.
import { InputClient } from '@worklouder/input-kit';
const input = new InputClient({
applicationName: 'My Integration',
// host: '127.0.0.1',
// port: 19147,
// requestTimeoutMs: 10_000,
// addLayerTimeoutMs: 120_000,
});applicationName is normalized, trimmed, and limited to 80 printable characters. It cannot contain Unicode control, format, line, or paragraph separators. host accepts only 127.0.0.1, ::1, or localhost. port defaults to 19147 and the path is /input. requestTimeoutMs defaults to 10 seconds for ordinary requests. addLayerTimeoutMs defaults to 120 seconds. Both options accept integer milliseconds from 1 through 2_147_483_647. requestAuthorization has a fixed 29-second response timeout. Input closes an unauthenticated connection 30 seconds after opening it, so request authorization promptly after connecting.
Opens the WebSocket connection. It does not request authorization or authenticate automatically.
connect() rejects with InputConnectionError when Input is not listening, when Input closes the connection before it opens, when the WebSocket handshake does not complete within 5 seconds, or when the runtime has no global WebSocket. Calling close() while connecting rejects the pending connect().
Closes the connection and rejects queued and in-flight requests with InputConnectionError. A disconnect also rejects outstanding requests. A new connection starts with an empty queue.
Reports whether the WebSocket is currently open. url: string exposes the resolved loopback URL.
Requests first-time approval using the requestAuthorization wire method. Input returns the application ID and token and authenticates the current connection.
interface AuthorizationCredentials {
applicationId: string;
token: string;
}Store these credentials in the calling application's secure storage. InputKit does not write credentials to disk.
Authenticates a previously authorized application. The saved permission must allow the connection origin used by the client. Node.js clients send no Origin header. Browsers always send the page's origin, so a browser application needs its own authorization rather than reusing credentials from a Node.js pairing.
All request methods require an open, authenticated connection, except requestAuthorization and authenticate.
InputKit sends requests in call order, with one request awaiting a response at a time and at least 400 ms between sends. This keeps traffic below Input's limit of 30 requests per rolling 10 seconds per connection. The response timeout begins when the frame is sent, so time spent in the queue is separate. InputKit measures the entire serialized request in UTF-8 and rejects any message larger than 512 KiB before sending it. InputKit never retries requests automatically.
Returns ready devices supported by the current Input build. Device IDs are runtime IDs and can change after reconnection.
interface DeviceSummary {
id: number;
deviceType: string;
}Returns the latest DeviceInfo snapshot available to Input.
interface DeviceInfo {
portPath: string;
devicePid: string;
serialNumber?: string;
connectionType: number;
deviceType: string;
layoutType: string;
isUsbConnection: boolean;
deviceStatus?: DeviceStatus;
}
interface DeviceStatus {
firmwareVersion?: string;
selectedProfileIndex?: number; // zero-based
selectedLayerIndex?: number; // one-based
batteryPercentage?: number; // 0–100
isCharging?: boolean;
}Selects the active profile and layer together using the setActiveProfileLayer wire method. deviceId must be a positive safe integer. profileIndex is zero-based and layerIndex is one-based. Watch deviceInfoChanged for updates to the reported selection.
interface SetActiveProfileLayerOptions {
deviceId: number;
profileIndex: number; // zero-based
layerIndex: number; // one-based
}Input returns an error if the device, profile, or layer is unavailable, or if the device rejects the change.
Changes lighting for API Smart Actions matched by identifier. deviceId must be a positive safe integer. Provide at least one lighting property.
interface SetKeyLightingOptions {
deviceId: number;
identifier: string;
isRegex: boolean;
color?: number; // integer from 0 to 0xFFFFFF
brightness?: number; // 0–1
effect?: number; // 0, 1, 3, 4, or 6
speed?: number; // 0–1
}When isRegex is false, identifier must be 1 to 128 UTF-16 code units with no surrounding whitespace, ASCII control characters, or Unicode line and paragraph separators. When isRegex is true, supply a regular expression of 1 to 256 UTF-16 code units. Requests with invalid or unsupported patterns can fail with InputApiError. A successful response confirms that the update was accepted. The Smart Action's visibility depends on the active layer.
Adds the supplied layer definition and its referenced actions to an existing profile on the device.
interface AddLayerOptions {
deviceId: number;
profileIndex: number; // zero-based
definition: LayerDefinition;
}definition is JSON-compatible and includes id, name, os, keyboardLayoutTypes, devices, layer, actions, actionGroups, multiactions, and multiactionGroups. Smart Action fields and descriptive metadata are optional. The definition must be compatible with the target device and profile. The request can fail if the key layout is malformed, keycodes are unknown, the definition contains command Smart Actions, the profile already has six layers, or another configuration change conflicts with the request.
To select a profile and layer after adding a layer, call setActiveProfileLayer with the desired indexes.
The server broadcasts events to all authenticated clients; there is no subscription request. Filter by deviceId in the application when needed.
InputClient provides on(event, listener), once(event, listener), off(event, listener), and emit(event, payload). Each event has a single typed payload, and each method except emit returns the client for chaining. InputClient is not a Node.js EventEmitter: events.once() and removeListener are not supported, and an emitted error event with no listener does not throw.
Emitted with { devices: DeviceSummary[] } when the ready-device list changes.
Emitted with { deviceId, deviceInfo } for the deviceInfoChanged wire notification. Use this complete value to replace any cached snapshot. The event can report profile and layer changes when deviceStatus is available.
Emitted for the keyTriggered wire notification with { deviceId, identifier, action } for mapped API Smart Actions. action is press, release, or click. The identifier follows the literal identifier rules for setKeyLighting.
Emitted with { method, params } for every valid server notification. This includes the three events above and unknown future notifications. It can be used while adopting newer server events before InputKit adds a typed event.
disconnectedprovides{ code, reason }when the current connection closes. A connection that fails or is aborted before it opens reports code1006.transportError: a WebSocket transportError. Standard WebSocket error events carry no detail, so the error message can be generic.protocolError: anInputProtocolErrorwhen Input sends malformed JSON-RPC or event data. If Input sends a binary frame, InputKit also closes the connection with application close code4003and reasonText messages only.
Request promises reject with these exported error classes:
InputApiError: Input returned an error code and message. The numeric protocol code is available ascode.InputConnectionError: the socket was unavailable, closed during a request, or could not send the request.InputRequestTimeoutErrorreports an expired response timeout. The operation outcome may be unknown because Input could still complete it. The error exposes the wiremethodandtimeoutMs.InputProtocolError: Input returned a response with an invalid shape.
Local request validation can also reject with TypeError or RangeError, including a RangeError for an oversized request.
Input's API error codes can vary by method. Preserve InputApiError.code and message when handling errors instead of relying only on the message text.
const input = new InputClient({ applicationName: 'My Integration' });
input.on('keyTriggered', ({ identifier, action }) => handleAction(identifier, action));
await input.connect();
await input.authenticate(savedCredentials);
try {
console.log(await input.getDevices());
} finally {
await input.close();
}See examples/cli.mjs for a runnable demonstration of authorization, authentication, discovery, and event handling.