HP Jarvis Data Collection Integration
Overview
The HP TV+ Desktop application integrates with HP Jarvis Data Collection SDK v3.13.1.710 for analytics and telemetry. This replaces the previous SQLite-based batching system with HP's enterprise SDK that handles persistence, batching, consent management, and network transport.
Architecture
Event Processing Pipeline
┌─────────────────────────────────────────────────────────────────────┐
│ 1. Renderer (elevate/src/providers/analytics) │
│ └─> analytics.log('event.type', { data }) │
│ │
│ 2. IPC Bridge │
│ └─> 'analytics:event' → Electron Main │
│ │
│ 3. Electron Main (electron/src/main/handlers/analytics.ts) │
│ └─> Routes to nativeAnalytics.publishEvent() │
│ │
│ 4. Filter & Sanitize (electron/src/main/analytics/adapter/) │
│ ├─> preDbAdapter.allowEvent() - Filter event types │
│ ├─> preDbAdapter.sanitizeEventPayload() - Remove unauthorized │
│ └─> Screen context enrichment (PAGE_VIEW → other events) │
│ │
│ 5. System Enrichment (electron/src/main/services/nativeAnalytics) │
│ └─> Add __sys object (deviceUUID, serial, platform, etc.) │
│ │
│ 6. CDM Transformation (electron/src/main/analytics/adapter/) │
│ └─> envelopeBuilder.buildTelemetryEnvelope() │
│ ├─> Event → CDM schema format │
│ ├─> Action derivation (ViewDisplayed, OnClick, etc.) │
│ ├─> View hierarchy building │
│ └─> Control name generation │
│ │
│ 7. C# Bridge (native/application/DataCollectionBridge.cs) │
│ └─> electron-edge-js → .NET 8 interop │
│ └─> Wrap in DataValveCDMEvent │
│ │
│ 8. HP Jarvis SDK (HP.Jarvis.DataCollection) │
│ ├─> Batching (configurable size/age) │
│ ├─> Persistence (SQLite queue) │
│ ├─> Consent checking (Valve Controller) │
│ └─> Network transport (Data Ingress API) │
└─────────────────────────────────────────────────────────────────────┘
Design Principles
✅ Adapter Pattern: All API-specific CDM mapping in adapter/ folder
✅ Single Source of Truth: Config values resolved once in analytics.ts
✅ Separation of Concerns: Renderer = events, Electron = enrichment, Adapter = CDM transformation
✅ Platform Independence: TypeScript handles all transformation, C# only wraps SDK
Key Components
TypeScript Layer
elevate/src/providers/analytics/analytics.ts
- Renderer-side analytics provider
- Sends minimal event data (no app metadata)
- Provides: screenResolution, sessionId, userId
electron/src/main/handlers/analytics.ts
- IPC handler and initialization
- Single source of truth for config values
- Resolves: appName (hardcoded), appVersion (from package.json), sysUuid, deviceSerial, country
electron/src/main/services/nativeAnalytics.ts
- Main orchestration service
- Calls filter → sanitize → enrich → transform → publish
- No transformation logic (delegates to adapter layer)
electron/src/main/analytics/adapter/preDbAdapter.ts
- Event filtering (SUPPORTED_EVENT_TYPES whitelist)
- Field sanitization (removes unauthorized fields)
- Screen context caching (PAGE_VIEW context persists to other events)
electron/src/main/analytics/adapter/envelopeBuilder.ts
- CDM schema transformation
- Action derivation (
screen.view→ViewDisplayed) - View hierarchy building (
base:/HPTV/Home/Library/) - Control name generation
- Event detail construction
C# Layer
native/application/DataCollectionBridge.cs
- Electron-edge-js bridge to .NET 8
- Methods: Initialize, PublishEvent, InvalidateCache, PauseEvents, ResumeEvents
- No transformation logic - only wraps and publishes
- Handles HP SDK initialization with retry/fallback
HP Jarvis SDK
Configuration
- Telemetry Stack: dev/qa/prod
- Valve Controller Stack: dev/qa/prod
- Data Ingress Client ID (required)
- Data Valve Controller Client ID (required)
- Batching parameters (size, age, frequency)
Features
- Automatic batching and retry
- SQLite-based persistent queue
- Exponential backoff on failure
- Consent management (opt-in/opt-out)
- Country-based data routing
Configuration
Environment Variables
Bash/Shell (macOS/Linux):
# Required (production)
DATA_INGRESS_CLIENT_ID=your-client-id
DATA_VALVE_CONTROLLER_CLIENT_ID=your-controller-id
# Stack selection
TELEMETRY_STACK=dev|qa|prod # Default: dev
VALVE_CONTROLLER_STACK=dev|qa|prod # Default: dev
# Debug logging
ANALYTICS_DEBUG=true|false # Enable debug logs
ANALYTICS_DEBUG_LEVEL=0|1|2 # 0=off, 1=basic, 2=verbose
CMD (Windows):
REM Required (production)
setx DATA_INGRESS_CLIENT_ID "your-client-id"
setx DATA_VALVE_CONTROLLER_CLIENT_ID "your-controller-id"
REM Stack selection
setx TELEMETRY_STACK "dev"
setx VALVE_CONTROLLER_STACK "dev"
REM Debug logging
setx ANALYTICS_DEBUG "true"
setx ANALYTICS_DEBUG_LEVEL "2"
REM Note: setx requires closing and reopening terminal to take effect
PowerShell (Windows):
# Required (production)
$env:DATA_INGRESS_CLIENT_ID = 'your-client-id'
$env:DATA_VALVE_CONTROLLER_CLIENT_ID = 'your-controller-id'
# Stack selection
$env:TELEMETRY_STACK = 'dev'
$env:VALVE_CONTROLLER_STACK = 'dev'
# Debug logging
$env:ANALYTICS_DEBUG = 'true'
$env:ANALYTICS_DEBUG_LEVEL = '2'
Initialization
// In electron/src/main/handlers/analytics.ts
const fullConfig = buildFullConfig(config, systemInfo, countryCode);
await nativeAnalytics.initialize(fullConfig);
Config Resolution (single source of truth):
appName: FromANALYTICS_CONFIG.APP_NAMEconstant ('hptvplus')appVersion: Fromapp.getVersion()(package.json)sysUuid: FromgetSystemInfo()(hardware UUID)sysSerialNumber: FromgetSystemInfo()(hardware serial)country: Auto-detected fromapp.getLocale()or fallback to'US'
Note: All analytics configuration constants are centralized in
/electron/src/main/analytics/constants/config.ts
Supported Event Types
// From preDbAdapter.ts
const SUPPORTED_EVENT_TYPES = [
'action.favorite.add',
'action.favorite.remove',
'action.login',
'action.logout',
'action.register',
'action.search',
'action.windowResize',
'screen.view',
'player.resize',
'player.play',
'player.stop',
'action.uiInteraction',
];
CDM Schema Compliance
Schema Version: 1.1.0
Event Detail Type: com.hp.cdm.domain.telemetry.type.eventDetail.category.simpleUi.version.2
Originator Type: com.hp.cdm.domain.telemetry.type.originatorDetail.originator.ucdeSoftware.version.1
Originator Context Type: com.hp.cdm.domain.telemetry.type.originatorContextDetail.originator.ucdeSoftware.version.1
Envelope Version Fields
Multiple version markers exist (they represent different schema components):
- Envelope
version:1.3.0(top-level telemetry body) - Originator Detail
version:1.1.0(originator metadata schema) - Originator Context Detail
version:1.5.0(originator context schema) - Event Detail
version:2.0.0(event payload schema)
Required Fields
Originator Detail (auto-populated from system info + config):
version(1.1.0)currentDateTime(ISO 8601 timestamp)sysUuid(hardware UUID from systeminformation, fallback to userId or sentinel)osPlatform(normalized: "Windows", "macOS", "Linux", etc.)appName(normalized PascalCase fromANALYTICS_CONFIG.APP_NAME, e.g., "Hptvplus")appVersion(4-part version: "1.0.0.0")appPackageId(format:{appName}/{major.minor})appPackageDeployedUuid(stable per-install instance ID, reuses sysUuid)
Originator Detail (Optional):
sysSerialNumber(hardware serial number, added when available)
Originator Context Detail:
version(1.5.0)
Event (per-event fields):
dateTime(ISO 8601 event timestamp)eventDetailType(CDM GUN identifier)eventDetail(object with action, controlName, viewName, etc.)eventCategory(always "telemetry")sequenceNumber(per-batch sequence)sequenceNumberGlobal(global sequence counter)
Event Detail Structure
Each eventDetail object contains these fields:
Required:
version(2.0.0 - event detail schema version)action(semantic action derived from eventType)controlName(UI element identifier)viewName(normalized PascalCase screen title)viewModule(screen module/section, fallback "Unknown")viewHierarchy(array of path segments)
Conditional:
actionAuxParams(query-string for selected events: search, resize, player)playerSize(only forplayer.resize: "full" | "regular")
Deliberately Omitted:
viewMode(deferred until product semantics defined)
System UUID (sysUuid) Resolution
Fallback order for sysUuid:
__sys.deviceUUID(raw hardware UUID from systeminformation)userIdfrom event payload- Static sentinel:
00000000-0000-4000-8000-000000000001
No hashing is applied. If privacy requirements change, add SHA-256 hashing at cache layer without impacting adapter API.
Action Mapping Rules
| Event Type | Derived Action | Control Name | Notes |
|---|---|---|---|
screen.view | ViewDisplayed | ScreenView | Standard page view |
action.uiInteraction | On<UiEvent> | <Component><UiEvent> | e.g., uiEvent="click" → "OnClick", component="Menu" → "MenuClick" |
action.windowResize | OnResize | WindowResize | Adds windowWidth/Height/breakVariant to actionAuxParams |
action.search | OnKeywordSearch | Search | Adds searchKeyword to actionAuxParams |
player.play | PlaybackStarted | PlayerPlay | Player started |
player.stop | PlaybackStopped | PlayerStop | Player stopped |
player.resize | PlayerFullscreen / PlayerWindowed | EnterFullScreen / ExitFullScreen | Based on videoFullscreen boolean flag |
action.favorite.add | None or Post<LastAction> | ActionFavoriteAdd | Falls back to Post action pattern |
action.favorite.remove | None or Post<LastAction> | ActionFavoriteRemove | Falls back to Post action pattern |
| Other | None or Post<LastAction> | PascalCase(eventType) | Post-action fallback |
Post Action Fallback
If an event's derived action would be None (doesn't match explicit rules):
- If a previous
action.uiInteractionoccurred, emitPost<ThatUiInteractionAction>(e.g.,PostOnClick) - Otherwise emit
None
Note: Only the last UI interaction action is considered for the Post- prefix. We don't chain arbitrary prior non-None actions.
Control Name Derivation
| Event Type | Control Name Logic |
|---|---|
| Default | PascalCase of eventType (e.g., screen.view → ScreenView, player.play → PlayerPlay) |
action.uiInteraction | <NormalizedUiComponent><UiEvent> (e.g., MenuClick, ButtonHover) |
player.resize | EnterFullScreen (videoFullscreen=true) or ExitFullScreen (videoFullscreen=false) |
Action Aux Params
Query-string formatted parameters for specific event types (stable key ordering):
| Event Type | Parameters | Example |
|---|---|---|
action.search | searchKeyword | searchKeyword=game%20of%20thrones |
action.windowResize | windowWidth, windowHeight, breakVariant | windowWidth=1920&windowHeight=1080&breakVariant=lg |
player.resize | playerSize | playerSize=full |
Player Resize Semantics
Player resize tracking has been simplified to fullscreen transitions only (no pixel dimensions):
Renderer emits:
{
eventType: 'player.resize',
videoFullscreen: true | false,
playerSize: 'full' | 'regular'
}
Adapter transforms to:
{
action: videoFullscreen ? 'PlayerFullscreen' : 'PlayerWindowed',
controlName: videoFullscreen ? 'EnterFullScreen' : 'ExitFullScreen',
eventDetail: {
playerSize: 'full' | 'regular', // Copied from payload
// ... other fields
}
}
View Hierarchy Format
viewHierarchy is an array of normalized path segments:
Format:
["base:/<AppName>/", "mfe:/<Segment1>/", "mfe:/<Segment2>/", ...]
Construction:
- Base segment:
base:/<AppName>/(fromANALYTICS_CONFIG.APP_NAME, e.g., "base:/hptvplus/") - Path segments: Normalized from
screenPath(e.g.,/home/library/details→["mfe:/Home/", "mfe:/Library/", "mfe:/Details/"]) - Optional UI component: If
uiComponentpresent, addmfe:/<Component>/
Normalization: All segments converted to PascalCase alphanumeric, empty segments filtered out.
Debug Logging
Environment Variables
ANALYTICS_DEBUG (Electron + Elevate)
- Set in
electron/.envfor Electron main process logging - Set in
elevate/.env.local(orelevate/.env) for renderer/Next.js logging - Values:
true,1(enabled) orfalse,0, unset (disabled)
ANALYTICS_DEBUG_LEVEL (Electron only)
- Set in
electron/.env - Values:
0(off),1(basic),2(verbose) - Only affects Electron main process and C# bridge
ANALYTICS_EVENTS_DEBUG (Electron only)
- Set in
electron/.env - Shows only event payloads being sent to HP Jarvis (no SDK lifecycle logs)
- Useful for verifying event data without verbose SDK output
- Values:
true,1(enabled) orfalse,0, unset (disabled) - Note:
ANALYTICS_DEBUG=trueoverrides this (shows everything)
ANALYTICS_CONSENT (Elevate only)
- Set in
elevate/.env.local(orelevate/.env) - Overrides user consent check with a local variable (does not modify localStorage)
- Does not interfere with the
useConsenthook's localStorage management - Values:
true,1: Force analytics enabled (bypass privacy check)false,0: Force analytics disabled (ignore localStorage consent)- Unset/undefined: Use localStorage
privacy-acceptedvalue (default)
- Useful for testing analytics behavior without modifying consent state
Enable Debug Mode
Configuration File Method (Recommended):
Create or edit electron/.env:
# Full debug logging (Electron + C# + SDK)
ANALYTICS_DEBUG=true
ANALYTICS_DEBUG_LEVEL=2
# Event-only logging (just event payloads, no SDK noise)
ANALYTICS_DEBUG=false
ANALYTICS_DEBUG_LEVEL=0
ANALYTICS_EVENTS_DEBUG=true
Create or edit elevate/.env.local:
# Enable renderer-side debug logs (IPC bridge status)
ANALYTICS_DEBUG=true
# Force analytics consent (bypass privacy dialog for testing)
ANALYTICS_CONSENT=true
Environment Variable Method:
Bash/Shell (macOS/Linux):
# Full debug (Electron + Elevate)
ANALYTICS_DEBUG=true ANALYTICS_DEBUG_LEVEL=2 pnpm start
# Event-only debug (Electron)
ANALYTICS_DEBUG=false ANALYTICS_DEBUG_LEVEL=0 ANALYTICS_EVENTS_DEBUG=true pnpm start
CMD (Windows):
REM Full debug
setx ANALYTICS_DEBUG "true"
setx ANALYTICS_DEBUG_LEVEL "2"
REM Event-only debug
setx ANALYTICS_DEBUG "false"
setx ANALYTICS_DEBUG_LEVEL "0"
setx ANALYTICS_EVENTS_DEBUG "true"
REM Note: Close and reopen terminal, then run:
pnpm start
PowerShell (Windows):
# Full debug
$env:ANALYTICS_DEBUG = 'true'
$env:ANALYTICS_DEBUG_LEVEL = '2'
# Event-only debug
$env:ANALYTICS_DEBUG = 'false'
$env:ANALYTICS_DEBUG_LEVEL = '0'
$env:ANALYTICS_EVENTS_DEBUG = 'true'
Debug Output Levels
ANALYTICS_DEBUG=false - Production default:
- Only errors and critical warnings
- No event logging
ANALYTICS_EVENTS_DEBUG=true (when ANALYTICS_DEBUG=false):
- Event payloads being sent to HP Jarvis backend
- No SDK lifecycle logs, no TypeScript transformation logs
- Useful for verifying event data in production-like conditions
ANALYTICS_DEBUG=true, LEVEL=1 - Basic debugging:
- Analytics initialization status
- Event types received and published
- Event queue operations (Electron main process - queues events until analytics engine ready)
- IPC bridge status (renderer side)
- Success/failure messages
- Configuration summary (credentials sanitized)
ANALYTICS_DEBUG=true, LEVEL=2 - Verbose debugging:
- Everything from Level 1
- Full configuration details
- System info extraction (deviceUUID, serial, platform)
- Event payloads before transformation
- CDM envelope structure after transformation
- TypeScript transformation logs (envelopeBuilder)
- HP Jarvis SDK internal logs
- C# bridge detailed traces
- Event tracking identifiers (verbose mode)
Running with Debug Logging
Bash/Shell (macOS/Linux):
# Development server (web + Electron)
ANALYTICS_DEBUG=true ANALYTICS_DEBUG_LEVEL=2 pnpm start
# Web-only development (renderer process)
cd elevate && ANALYTICS_DEBUG=true pnpm dev
# Electron with Node.js inspector
ANALYTICS_DEBUG=true pnpm dev -- --inspect=9229
# Production build testing
ANALYTICS_DEBUG=true pnpm run release:win
CMD (Windows):
REM Set environment variables first (requires new terminal)
setx ANALYTICS_DEBUG "true"
setx ANALYTICS_DEBUG_LEVEL "2"
REM Close this terminal, open new one, then:
pnpm start
REM Or set temporarily for this session only:
set ANALYTICS_DEBUG=true
set ANALYTICS_DEBUG_LEVEL=2
pnpm start
PowerShell (Windows):
# Development server (web + Electron)
$env:ANALYTICS_DEBUG = 'true'; $env:ANALYTICS_DEBUG_LEVEL = '2'; pnpm start
# Web-only development (renderer process)
cd elevate; $env:ANALYTICS_DEBUG = 'true'; pnpm dev
# Electron with Node.js inspector
$env:ANALYTICS_DEBUG = 'true'; pnpm dev -- --inspect=9229
# Production build testing
$env:ANALYTICS_DEBUG = 'true'; pnpm run release:win
What Gets Logged
TypeScript Layer (nativeAnalytics.ts, analytics.ts):
[analytics][main] Initializing HP Jarvis Data Collection...
[analytics][main] Configuration: { telemetryStack: 'dev', ... }
[analytics][main] HP Jarvis Data Collection initialized successfully ✓
[analytics][main] Received event: screen.view
[analytics][main] Event published successfully: screen.view
C# Bridge Layer (DataCollectionBridge.cs):
[DataCollectionBridge] Initialize called with config: { ... }
[DataCollectionBridge] HP SDK initialized successfully
[DataCollectionBridge] PublishEvent called for: ViewDisplayed
[DataCollectionBridge] Event published to HP Jarvis SDK
Adapter Layer (envelopeBuilder.ts, preDbAdapter.ts):
[EnvelopeBuilder] Building envelope for 1 event(s)
[EnvelopeBuilder] Derived action: ViewDisplayed, controlName: Home
[PreDbAdapter] Event filtered: screen.view (allowed)
[PreDbAdapter] Sanitized payload: { screenTitle: 'Home', ... }
C# Debug Environment Variables
The C# bridge respects the same environment variables:
// DataCollectionBridge.cs
private static bool IsDebugMode() {
var debugEnv = Environment.GetEnvironmentVariable("ANALYTICS_DEBUG");
return debugEnv == "true" || debugEnv == "1";
}
Testing
Manual Testing
// In renderer
analyticsProvider.log('screen.view', {
screenTitle: 'Home',
screenPath: '/home',
});
Clear Cache
// Clear HP Jarvis ValueStore and queue
ipcRenderer.invoke('analytics:clearCache');
This removes:
- ValueStore cache (
~/AppData/Local/HP/Jarvis/) - SQLite queue (
~/AppData/Local/HP/hptv/QueueDb.db) - Instance ID (
~/AppData/Local/HP/hptv/instance.id)
Known Issues & Limitations
1. SysSerialNumber Not Supported in HP SDK v3.13.1.710 ⚠️
Issue: HP Jarvis Data Collection SDK v3.13.1.710 does not support sysSerialNumber in the ControlledData.Item enum, even though it's specified as an optional field in CDM Schema v1.1.0 originatorDetail.
Impact:
- ✅ Device serial number is collected from hardware via
systeminformation - ✅ Serial number is passed to C# bridge in config
- ❌ Serial number cannot be set in ControlledData (SDK limitation)
- ❌ Serial number does not appear in the originator sent to HP servers
Log Output:
[DataCollectionBridge] ✓ Set SysUuid: 4c4c4544...
[DataCollectionBridge] ℹ SysSerialNumber not available in ControlledData.Item enum
[DataCollectionBridge] ✓ Set AppName: hptvplus
[DataCollectionBridge] ✓ Set AppVersion: 0.7.11-dev.0
[DataCollectionBridge] ✓ Set OsPlatform: Windows
Note:
AppNamevalue comes fromANALYTICS_CONFIG.APP_NAMEin/electron/src/main/analytics/constants/config.ts
Root Cause:
The SDK uses reflection to set originator metadata via ControlledData.Shared.Item enum. The enum values in v3.13.1.710 are:
- ✅
SysUuid(supported) - ✅
AppName(supported) - ✅
AppVersion(supported) - ✅
OsPlatform(supported) - ❌
SysSerialNumber(missing from enum)
Attempted Workarounds:
- ❌ Reflection to add enum value - Not possible, enums are sealed
- ❌ Pass full CDM envelope - SDK rebuilds originator from ControlledData, ignoring provided originator
- ❌ Modify notification in subscriber - Notification already built when subscriber receives it
Proper Solution:
This is an HP Jarvis SDK bug/limitation that should be reported to the SDK team. The CDM schema explicitly includes sysSerialNumber as an optional field, so the SDK should support setting it.
Action Required:
- 📧 Report to HP Jarvis SDK team: Request adding
SysSerialNumbertoControlledData.Itemenum in future SDK version - 📋 Track as SDK limitation: Document in release notes that device serial is not included in analytics until SDK is updated
- 🔄 Monitor SDK updates: Test with newer SDK versions (v3.14+) to see if support is added
Workaround Status: ⏸️ Deferred
A complex workaround (passing full CDM envelope from TypeScript) was considered but rejected because:
- It's not our bug to fix (SDK should support its own schema)
- Workaround adds significant complexity
- May break when SDK is updated
- Better to report to HP and wait for proper fix
CDM Schema Reference:
{
"originatorDetail": {
"version": "1.1.0",
"sysUuid": "required",
"appName": "required",
"appVersion": "required",
"sysSerialNumber": "optional" ← Specified but not supported by SDK
}
}
2. Single-Event Processing Overhead
Issue: envelopeBuilder was designed for batch processing. Using it for single events creates overhead (builds full envelope, we only use events[0]).
Status: Documented as optimization opportunity (see Architecture Optimization)
Impact: Minor CPU overhead per event, no functional issues
2. SysSerialNumber Not Available
Issue: HP Jarvis SDK v3.13.1.710 doesn't expose SysSerialNumber in ControlledData.Item enum.
Workaround: C# code has defensive try-catch, logs informational message (not an error)
Impact: Device serial collected but not set in HP SDK's ControlledData (still included in CDM originator)
3. Windows-Only DLL Loading
Issue: electron-edge-js requires .NET runtime, only works on Windows
Status: Expected behavior, gracefully skips on macOS/Linux
Impact: Development on macOS works, analytics won't send (can use mock mode)
Future Improvements
See Architecture Optimization for planned refactoring to single-event CDM builder.
Benefits:
- 30-50% faster event processing (estimated)
- Simpler code path
- Less memory allocation
Timeline: Deferred until performance profiling shows need
References
- HP Jarvis SDK: v3.13.1.710
- CDM Schema: v1.1.0
- electron-edge-js: Bridge for Node.js ↔ .NET interop
- Branch:
feat/HPCH-659-jarvis-data-collection
Related Documentation
- Architecture Optimization - Future refactoring plan
- New Relic - Application performance monitoring