-
-
Notifications
You must be signed in to change notification settings - Fork 178
feat: Device Name Display Implementation #2017
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tonybentley
wants to merge
12
commits into
SignalK:master
Choose a base branch
from
tonybentley:mdns
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
fbfed01
Add Active Clients feature to Security section
bc03a96
Add unit tests for Active Clients API endpoint
4b9d98c
Revert "Add unit tests for Active Clients API endpoint"
5f9d8cc
test
949f661
remove timers
4330fbe
publish v1
261b133
feat: Show device names instead of WebSocket IDs in Dashboard
32a5793
feat(device name display) Implemented a device name display feature
d754921
formatting for lint and ts
8103ab3
pr comments
b850731
lint prettier
3bd29f7
prettier
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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,96 @@ | ||
/* | ||
* Device Name Resolution Utility | ||
* Resolves WebSocket client IDs to user-friendly display names | ||
*/ | ||
|
||
interface Device { | ||
clientId: string | ||
description?: string | ||
} | ||
|
||
interface ClientInfo { | ||
skPrincipal?: { name?: string } | ||
userAgent?: string | ||
} | ||
|
||
/** | ||
* Resolves a WebSocket client ID to a user-friendly display name using a 4-level priority system. | ||
* | ||
* This function attempts to find the most descriptive name for a connected client by checking | ||
* multiple sources in order of preference. The goal is to provide meaningful device names | ||
* in the Dashboard instead of cryptic WebSocket IDs like "ws.85d5c860-d34f-42ba-b9f1-b4ba78de8e95". | ||
* | ||
* Resolution Priority (first match wins): | ||
* 1. **Device Description from Registry** - If the device is registered in the security system, | ||
* use its configured description (e.g., "SensESP device: esp32-wireless") | ||
* 2. **Principal Name from Authentication** - If the client is authenticated, use the principal's | ||
* name from the authentication context | ||
* 3. **Parsed User Agent** - Extract a meaningful name from the User-Agent header: | ||
* - "SensESP" → "SensESP Device" | ||
* - "SignalK" → "SignalK Client" | ||
* - "OpenCPN" → "OpenCPN" | ||
* - Browser agents → "Web Browser" | ||
* - Other agents → First meaningful part of the UA string | ||
* 4. **Client ID Fallback** - If no other information is available, return the original client ID | ||
* | ||
* @param clientId - The WebSocket client ID to resolve (e.g., "ws.123e4567-e89b-12d3-a456-426614174000") | ||
* @param devices - Array of registered devices from the device registry cache | ||
* @param clientInfo - Optional client information including authentication principal and user agent | ||
* @returns A user-friendly display name for the client | ||
* | ||
* @example | ||
* // With a registered device | ||
* resolveDeviceName('esp32-001', devices, clientInfo) | ||
* // Returns: "SensESP device: esp32-wireless" | ||
* | ||
* @example | ||
* // With only user agent | ||
* resolveDeviceName('ws.abc123', [], { userAgent: 'OpenCPN/5.6.2' }) | ||
* // Returns: "OpenCPN" | ||
* | ||
* @example | ||
* // Fallback case | ||
* resolveDeviceName('ws.xyz789', [], {}) | ||
* // Returns: "ws.xyz789" | ||
*/ | ||
export function resolveDeviceName( | ||
clientId: string, | ||
devices: Device[], | ||
clientInfo?: ClientInfo | ||
): string { | ||
// 1. Device description from registry | ||
const device = devices.find((d) => d.clientId === clientId) | ||
if (device?.description) { | ||
return device.description | ||
} | ||
|
||
// 2. Principal name from authentication | ||
if (clientInfo?.skPrincipal?.name) { | ||
return clientInfo.skPrincipal.name | ||
} | ||
|
||
// 3. User agent (shortened) | ||
if (clientInfo?.userAgent) { | ||
const ua = clientInfo.userAgent | ||
if (ua.includes('SensESP')) { | ||
return 'SensESP Device' | ||
} else if (ua.includes('SignalK')) { | ||
return 'SignalK Client' | ||
} else if (ua.includes('OpenCPN')) { | ||
return 'OpenCPN' | ||
} else if ( | ||
ua.includes('Chrome') || | ||
ua.includes('Firefox') || | ||
ua.includes('Safari') | ||
) { | ||
return 'Web Browser' | ||
} else { | ||
// Take first meaningful part of user agent | ||
const parts = ua.split(/[\s\/\(]/) | ||
return parts[0] || 'Unknown Client' | ||
} | ||
} | ||
|
||
// 4. Fall back to client ID | ||
return clientId | ||
} |
This file contains hidden or 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,112 @@ | ||
/* | ||
* Device Registry Cache Manager | ||
* Maintains an in-memory cache of device registry data with event-driven updates | ||
*/ | ||
|
||
import { EventEmitter } from 'events' | ||
import { createDebug } from './debug' | ||
|
||
const debug = createDebug('signalk-server:device-registry-cache') | ||
|
||
export interface Device { | ||
clientId: string | ||
permissions: string | ||
description?: string | ||
config?: Record<string, unknown> | ||
} | ||
|
||
interface CacheEventEmitter extends EventEmitter { | ||
on(event: 'updated', listener: () => void): this | ||
emit(event: 'updated'): boolean | ||
} | ||
|
||
export class DeviceRegistryCache { | ||
private devices: Map<string, Device> = new Map() | ||
private events: CacheEventEmitter = new EventEmitter() | ||
|
||
constructor() { | ||
debug('Device registry cache initialized') | ||
} | ||
|
||
/** | ||
* Initialize cache with device data | ||
*/ | ||
initialize(devices: Device[]): void { | ||
this.devices.clear() | ||
devices.forEach((device) => { | ||
this.devices.set(device.clientId, device) | ||
}) | ||
debug(`Cache initialized with ${this.devices.size} devices`) | ||
this.events.emit('updated') | ||
} | ||
|
||
/** | ||
* Update cache with new device data | ||
*/ | ||
update(devices: Device[]): void { | ||
const previousSize = this.devices.size | ||
this.initialize(devices) | ||
if (previousSize !== this.devices.size) { | ||
debug(`Cache updated: ${previousSize} -> ${this.devices.size} devices`) | ||
} | ||
} | ||
|
||
/** | ||
* Get device by client ID | ||
*/ | ||
getDevice(clientId: string): Device | undefined { | ||
return this.devices.get(clientId) | ||
} | ||
|
||
/** | ||
* Get all devices | ||
*/ | ||
getAllDevices(): Device[] { | ||
return Array.from(this.devices.values()) | ||
} | ||
|
||
/** | ||
* Add or update a single device | ||
*/ | ||
setDevice(device: Device): void { | ||
const isNew = !this.devices.has(device.clientId) | ||
this.devices.set(device.clientId, device) | ||
debug(`Device ${isNew ? 'added' : 'updated'}: ${device.clientId}`) | ||
this.events.emit('updated') | ||
} | ||
|
||
/** | ||
* Remove a device | ||
*/ | ||
removeDevice(clientId: string): boolean { | ||
const removed = this.devices.delete(clientId) | ||
if (removed) { | ||
debug(`Device removed: ${clientId}`) | ||
this.events.emit('updated') | ||
} | ||
return removed | ||
} | ||
|
||
/** | ||
* Subscribe to cache updates | ||
*/ | ||
onUpdate(listener: () => void): () => void { | ||
this.events.on('updated', listener) | ||
return () => { | ||
this.events.removeListener('updated', listener) | ||
} | ||
} | ||
|
||
/** | ||
* Get cache statistics | ||
*/ | ||
getStats(): { deviceCount: number; cacheSize: number } { | ||
return { | ||
deviceCount: this.devices.size, | ||
cacheSize: JSON.stringify(Array.from(this.devices.values())).length | ||
} | ||
} | ||
} | ||
|
||
// Singleton instance | ||
export const deviceRegistryCache = new DeviceRegistryCache() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.