Skip to main content

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.viewViewDisplayed)
  • 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: From ANALYTICS_CONFIG.APP_NAME constant ('hptvplus')
  • appVersion: From app.getVersion() (package.json)
  • sysUuid: From getSystemInfo() (hardware UUID)
  • sysSerialNumber: From getSystemInfo() (hardware serial)
  • country: Auto-detected from app.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 from ANALYTICS_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 for player.resize: "full" | "regular")

Deliberately Omitted:

  • viewMode (deferred until product semantics defined)

System UUID (sysUuid) Resolution

Fallback order for sysUuid:

  1. __sys.deviceUUID (raw hardware UUID from systeminformation)
  2. userId from event payload
  3. 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 TypeDerived ActionControl NameNotes
screen.viewViewDisplayedScreenViewStandard page view
action.uiInteractionOn<UiEvent><Component><UiEvent>e.g., uiEvent="click" → "OnClick", component="Menu" → "MenuClick"
action.windowResizeOnResizeWindowResizeAdds windowWidth/Height/breakVariant to actionAuxParams
action.searchOnKeywordSearchSearchAdds searchKeyword to actionAuxParams
player.playPlaybackStartedPlayerPlayPlayer started
player.stopPlaybackStoppedPlayerStopPlayer stopped
player.resizePlayerFullscreen / PlayerWindowedEnterFullScreen / ExitFullScreenBased on videoFullscreen boolean flag
action.favorite.addNone or Post<LastAction>ActionFavoriteAddFalls back to Post action pattern
action.favorite.removeNone or Post<LastAction>ActionFavoriteRemoveFalls back to Post action pattern
OtherNone 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):

  1. If a previous action.uiInteraction occurred, emit Post<ThatUiInteractionAction> (e.g., PostOnClick)
  2. 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 TypeControl Name Logic
DefaultPascalCase of eventType (e.g., screen.viewScreenView, player.playPlayerPlay)
action.uiInteraction<NormalizedUiComponent><UiEvent> (e.g., MenuClick, ButtonHover)
player.resizeEnterFullScreen (videoFullscreen=true) or ExitFullScreen (videoFullscreen=false)

Action Aux Params

Query-string formatted parameters for specific event types (stable key ordering):

Event TypeParametersExample
action.searchsearchKeywordsearchKeyword=game%20of%20thrones
action.windowResizewindowWidth, windowHeight, breakVariantwindowWidth=1920&windowHeight=1080&breakVariant=lg
player.resizeplayerSizeplayerSize=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:

  1. Base segment: base:/<AppName>/ (from ANALYTICS_CONFIG.APP_NAME, e.g., "base:/hptvplus/")
  2. Path segments: Normalized from screenPath (e.g., /home/library/details["mfe:/Home/", "mfe:/Library/", "mfe:/Details/"])
  3. Optional UI component: If uiComponent present, add mfe:/<Component>/

Normalization: All segments converted to PascalCase alphanumeric, empty segments filtered out.

Debug Logging

Environment Variables

ANALYTICS_DEBUG (Electron + Elevate)

  • Set in electron/.env for Electron main process logging
  • Set in elevate/.env.local (or elevate/.env) for renderer/Next.js logging
  • Values: true, 1 (enabled) or false, 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) or false, 0, unset (disabled)
  • Note: ANALYTICS_DEBUG=true overrides this (shows everything)

ANALYTICS_CONSENT (Elevate only)

  • Set in elevate/.env.local (or elevate/.env)
  • Overrides user consent check with a local variable (does not modify localStorage)
  • Does not interfere with the useConsent hook'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-accepted value (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: AppName value comes from ANALYTICS_CONFIG.APP_NAME in /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:

  1. Reflection to add enum value - Not possible, enums are sealed
  2. Pass full CDM envelope - SDK rebuilds originator from ControlledData, ignoring provided originator
  3. 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 SysSerialNumber to ControlledData.Item enum 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