mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 22:59:59 -07:00
- Extract signal-timeline into configurable activity-timeline.js - Add visual modes: compact, enriched, summary - Create data adapters for RF, Bluetooth, WiFi normalization - Integrate timeline into Listening Post, Bluetooth, WiFi modes - Preserve backward compatibility for existing TSCM code - Add mode-specific configuration presets via adapters Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
289 lines
9.0 KiB
JavaScript
289 lines
9.0 KiB
JavaScript
/**
|
|
* Bluetooth Timeline Adapter
|
|
* Normalizes Bluetooth device data for the Activity Timeline component
|
|
* Used by: Bluetooth mode, TSCM (Bluetooth detections)
|
|
*/
|
|
|
|
const BluetoothTimelineAdapter = (function() {
|
|
'use strict';
|
|
|
|
/**
|
|
* RSSI to strength category mapping for Bluetooth
|
|
* Bluetooth RSSI typically ranges from -30 (very close) to -100 (far)
|
|
*/
|
|
const RSSI_THRESHOLDS = {
|
|
VERY_STRONG: -45, // 5 - device likely within 1m
|
|
STRONG: -60, // 4 - device likely within 3m
|
|
MODERATE: -75, // 3 - device likely within 10m
|
|
WEAK: -90, // 2 - device at edge of range
|
|
MINIMAL: -100 // 1 - barely detectable
|
|
};
|
|
|
|
/**
|
|
* Known device type patterns
|
|
*/
|
|
const DEVICE_PATTERNS = {
|
|
// Apple devices
|
|
AIRPODS: /airpods/i,
|
|
IPHONE: /iphone/i,
|
|
IPAD: /ipad/i,
|
|
MACBOOK: /macbook|mac\s*pro|imac/i,
|
|
APPLE_WATCH: /apple\s*watch/i,
|
|
AIRTAG: /airtag/i,
|
|
|
|
// Trackers
|
|
TILE: /tile/i,
|
|
CHIPOLO: /chipolo/i,
|
|
SAMSUNG_TAG: /smarttag|galaxy\s*tag/i,
|
|
|
|
// Audio
|
|
HEADPHONES: /headphone|earphone|earbud|bose|sony|beats|jabra|sennheiser/i,
|
|
SPEAKER: /speaker|soundbar|echo|homepod|sonos/i,
|
|
|
|
// Wearables
|
|
FITBIT: /fitbit/i,
|
|
GARMIN: /garmin/i,
|
|
SMARTWATCH: /watch|band|mi\s*band|galaxy\s*fit/i,
|
|
|
|
// Input devices
|
|
KEYBOARD: /keyboard/i,
|
|
MOUSE: /mouse|trackpad|magic/i,
|
|
CONTROLLER: /controller|gamepad|xbox|playstation|dualshock/i,
|
|
|
|
// Vehicles
|
|
CAR: /car\s*kit|handsfree|obd|vehicle|toyota|honda|ford|bmw|mercedes/i
|
|
};
|
|
|
|
/**
|
|
* Convert RSSI to strength category
|
|
*/
|
|
function rssiToStrength(rssi) {
|
|
if (rssi === null || rssi === undefined) return 3;
|
|
|
|
const r = parseFloat(rssi);
|
|
if (isNaN(r)) return 3;
|
|
|
|
if (r > RSSI_THRESHOLDS.VERY_STRONG) return 5;
|
|
if (r > RSSI_THRESHOLDS.STRONG) return 4;
|
|
if (r > RSSI_THRESHOLDS.MODERATE) return 3;
|
|
if (r > RSSI_THRESHOLDS.WEAK) return 2;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Classify device type from name
|
|
*/
|
|
function classifyDevice(name) {
|
|
if (!name) return { type: 'unknown', category: 'device' };
|
|
|
|
for (const [pattern, regex] of Object.entries(DEVICE_PATTERNS)) {
|
|
if (regex.test(name)) {
|
|
return {
|
|
type: pattern.toLowerCase(),
|
|
category: getCategoryForType(pattern)
|
|
};
|
|
}
|
|
}
|
|
|
|
return { type: 'unknown', category: 'device' };
|
|
}
|
|
|
|
/**
|
|
* Get category for device type
|
|
*/
|
|
function getCategoryForType(type) {
|
|
const categories = {
|
|
AIRPODS: 'audio',
|
|
IPHONE: 'phone',
|
|
IPAD: 'tablet',
|
|
MACBOOK: 'computer',
|
|
APPLE_WATCH: 'wearable',
|
|
AIRTAG: 'tracker',
|
|
TILE: 'tracker',
|
|
CHIPOLO: 'tracker',
|
|
SAMSUNG_TAG: 'tracker',
|
|
HEADPHONES: 'audio',
|
|
SPEAKER: 'audio',
|
|
FITBIT: 'wearable',
|
|
GARMIN: 'wearable',
|
|
SMARTWATCH: 'wearable',
|
|
KEYBOARD: 'input',
|
|
MOUSE: 'input',
|
|
CONTROLLER: 'input',
|
|
CAR: 'vehicle'
|
|
};
|
|
return categories[type] || 'device';
|
|
}
|
|
|
|
/**
|
|
* Format MAC address for display (truncated)
|
|
*/
|
|
function formatMac(mac, full = false) {
|
|
if (!mac) return 'Unknown';
|
|
if (full) return mac.toUpperCase();
|
|
return mac.substring(0, 8).toUpperCase() + '...';
|
|
}
|
|
|
|
/**
|
|
* Determine if device is a tracker type
|
|
*/
|
|
function isTracker(device) {
|
|
if (device.is_tracker) return true;
|
|
|
|
const name = device.name || '';
|
|
return /airtag|tile|chipolo|smarttag|tracker/i.test(name);
|
|
}
|
|
|
|
/**
|
|
* Normalize a Bluetooth device detection for the timeline
|
|
*/
|
|
function normalizeDevice(device) {
|
|
const mac = device.mac || device.address || device.id;
|
|
const name = device.name || device.device_name || formatMac(mac);
|
|
const classification = classifyDevice(name);
|
|
|
|
const tags = [device.type || 'ble'];
|
|
tags.push(classification.category);
|
|
|
|
if (isTracker(device)) tags.push('tracker');
|
|
if (device.is_beacon) tags.push('beacon');
|
|
if (device.is_connectable) tags.push('connectable');
|
|
if (device.manufacturer) tags.push('identified');
|
|
|
|
return {
|
|
id: mac,
|
|
label: name,
|
|
strength: rssiToStrength(device.rssi),
|
|
duration: device.scan_duration || device.duration || 1000,
|
|
type: classification.type,
|
|
tags: tags,
|
|
metadata: {
|
|
mac: mac,
|
|
rssi: device.rssi,
|
|
device_type: device.type,
|
|
manufacturer: device.manufacturer,
|
|
services: device.services,
|
|
is_tracker: isTracker(device),
|
|
classification: classification
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Normalize for TSCM context (includes threat assessment)
|
|
*/
|
|
function normalizeTscmDevice(device) {
|
|
const normalized = normalizeDevice(device);
|
|
|
|
// Add TSCM-specific tags
|
|
if (device.is_new) normalized.tags.push('new');
|
|
if (device.threat_level) normalized.tags.push(`threat-${device.threat_level}`);
|
|
if (device.baseline_known === false) normalized.tags.push('unknown');
|
|
|
|
normalized.metadata.threat_level = device.threat_level;
|
|
normalized.metadata.first_seen = device.first_seen;
|
|
normalized.metadata.appearance_count = device.appearance_count;
|
|
|
|
return normalized;
|
|
}
|
|
|
|
/**
|
|
* Batch normalize multiple devices
|
|
*/
|
|
function normalizeDevices(devices, context = 'scan') {
|
|
const normalizer = context === 'tscm' ? normalizeTscmDevice : normalizeDevice;
|
|
return devices.map(normalizer);
|
|
}
|
|
|
|
/**
|
|
* Create timeline configuration for Bluetooth mode
|
|
*/
|
|
function getBluetoothConfig() {
|
|
return {
|
|
title: 'Device Activity',
|
|
mode: 'bluetooth',
|
|
visualMode: 'enriched',
|
|
collapsed: false,
|
|
showAnnotations: true,
|
|
showLegend: true,
|
|
defaultWindow: '15m',
|
|
availableWindows: ['5m', '15m', '30m', '1h'],
|
|
filters: {
|
|
hideBaseline: { enabled: true, label: 'Hide Known', default: false },
|
|
showOnlyNew: { enabled: true, label: 'New Only', default: false },
|
|
showOnlyBurst: { enabled: false, label: 'Bursts', default: false }
|
|
},
|
|
customFilters: [
|
|
{
|
|
key: 'showOnlyTrackers',
|
|
label: 'Trackers Only',
|
|
default: false,
|
|
predicate: (item) => item.tags.includes('tracker')
|
|
},
|
|
{
|
|
key: 'hideWearables',
|
|
label: 'Hide Wearables',
|
|
default: false,
|
|
predicate: (item) => !item.tags.includes('wearable')
|
|
}
|
|
],
|
|
maxItems: 75,
|
|
maxDisplayedLanes: 12,
|
|
labelGenerator: (id) => formatMac(id)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create compact timeline configuration (for sidebar use)
|
|
*/
|
|
function getCompactConfig() {
|
|
return {
|
|
title: 'BT Devices',
|
|
mode: 'bluetooth',
|
|
visualMode: 'compact',
|
|
collapsed: false,
|
|
showAnnotations: false,
|
|
showLegend: false,
|
|
defaultWindow: '15m',
|
|
availableWindows: ['5m', '15m', '30m'],
|
|
filters: {
|
|
hideBaseline: { enabled: false },
|
|
showOnlyNew: { enabled: true, label: 'New', default: false },
|
|
showOnlyBurst: { enabled: false }
|
|
},
|
|
customFilters: [],
|
|
maxItems: 30,
|
|
maxDisplayedLanes: 8
|
|
};
|
|
}
|
|
|
|
// Public API
|
|
return {
|
|
// Normalization
|
|
normalizeDevice: normalizeDevice,
|
|
normalizeTscmDevice: normalizeTscmDevice,
|
|
normalizeDevices: normalizeDevices,
|
|
|
|
// Utilities
|
|
rssiToStrength: rssiToStrength,
|
|
classifyDevice: classifyDevice,
|
|
formatMac: formatMac,
|
|
isTracker: isTracker,
|
|
|
|
// Configuration presets
|
|
getBluetoothConfig: getBluetoothConfig,
|
|
getCompactConfig: getCompactConfig,
|
|
|
|
// Constants
|
|
RSSI_THRESHOLDS: RSSI_THRESHOLDS,
|
|
DEVICE_PATTERNS: DEVICE_PATTERNS
|
|
};
|
|
})();
|
|
|
|
// Export for module systems
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = BluetoothTimelineAdapter;
|
|
}
|
|
|
|
window.BluetoothTimelineAdapter = BluetoothTimelineAdapter;
|