11import { useEffect , useRef , useState } from 'react'
2+ import { addBreadcrumb , setTags , setTag , setContext , captureSessionSummary } from '../utils/telemetry'
23
34import { FlashManager , StepCode , ErrorCode , DeviceType } from '../utils/manager'
45import { useImageManager } from '../utils/image'
@@ -38,9 +39,9 @@ function ImagePreloader() {
3839 )
3940}
4041
41- // Capture console logs for debug reports
42+ // Capture console logs for debug reports and telemetry
4243const consoleLogs = [ ]
43- const MAX_LOGS = 100
44+ const MAX_LOGS = 500
4445const originalConsole = { log : console . log , warn : console . warn , error : console . error , info : console . info , debug : console . debug }
4546; [ 'log' , 'warn' , 'error' , 'info' , 'debug' ] . forEach ( level => {
4647 console [ level ] = ( ...args ) => {
@@ -50,6 +51,41 @@ const originalConsole = { log: console.log, warn: console.warn, error: console.e
5051 }
5152} )
5253
54+ // Unique per-page session id for correlating events
55+ const SESSION_ID = ( crypto && 'randomUUID' in crypto ) ? crypto . randomUUID ( ) : String ( Math . random ( ) ) . slice ( 2 )
56+
57+ // Helper for building environment metadata
58+ function buildEnvMeta ( ) {
59+ const ua = navigator . userAgent
60+ let os = 'Unknown'
61+ if ( ua . includes ( 'Windows NT 10.0' ) ) os = 'Windows 10/11'
62+ else if ( ua . includes ( 'Windows NT 6.3' ) ) os = 'Windows 8.1'
63+ else if ( ua . includes ( 'Windows NT 6.2' ) ) os = 'Windows 8'
64+ else if ( ua . includes ( 'Windows NT 6.1' ) ) os = 'Windows 7'
65+ else if ( ua . includes ( 'Mac OS X' ) ) {
66+ const match = ua . match ( / M a c O S X ( \d + [ . _ ] \d + [ . _ ] ? \d * ) / )
67+ os = match ? `macOS ${ match [ 1 ] . replace ( / _ / g, '.' ) } ` : 'macOS'
68+ } else if ( ua . includes ( 'Linux' ) ) {
69+ os = 'Linux'
70+ if ( ua . includes ( 'Ubuntu' ) ) os += ' (Ubuntu)'
71+ else if ( ua . includes ( 'Fedora' ) ) os += ' (Fedora)'
72+ else if ( ua . includes ( 'Debian' ) ) os += ' (Debian)'
73+ } else if ( ua . includes ( 'CrOS' ) ) os = 'ChromeOS'
74+
75+ const sandboxHints = [ ]
76+ if ( ua . includes ( 'snap' ) ) sandboxHints . push ( 'Snap' )
77+ if ( ua . includes ( 'Flatpak' ) ) sandboxHints . push ( 'Flatpak' )
78+ if ( navigator . userAgentData ?. brands ?. some ( b => b . brand . includes ( 'snap' ) ) ) sandboxHints . push ( 'Snap' )
79+
80+ return {
81+ os,
82+ sandbox : sandboxHints . length ? sandboxHints . join ( ', ' ) : 'None detected' ,
83+ browser : navigator . userAgent ,
84+ url : window . location . href ,
85+ version : import . meta. env . VITE_PUBLIC_GIT_SHA || 'dev' ,
86+ }
87+ }
88+
5389// Debug info component for error reporting
5490function DebugInfo ( { error, step, selectedDevice, serial, message, onClose } ) {
5591 const [ copied , setCopied ] = useState ( false )
@@ -622,6 +658,7 @@ export default function Flash() {
622658 const [ serial , setSerial ] = useState ( null )
623659 const [ selectedDevice , setSelectedDevice ] = useState ( null )
624660 const [ wizardScreen , setWizardScreen ] = useState ( 'landing' ) // 'landing', 'device', 'zadig', 'connect', 'unbind', 'webusb', 'flash'
661+ const reportSentRef = useRef ( false )
625662
626663 const qdlManager = useRef ( null )
627664 const imageManager = useImageManager ( )
@@ -638,12 +675,34 @@ export default function Flash() {
638675 . then ( ( programmer ) => {
639676 // Create QDL manager with callbacks that update React state
640677 qdlManager . current = new FlashManager ( programmer , {
641- onStepChange : setStep ,
642- onMessageChange : setMessage ,
643- onProgressChange : setProgress ,
644- onErrorChange : setError ,
645- onConnectionChange : setConnected ,
646- onSerialChange : setSerial
678+ onStepChange : ( s ) => {
679+ setStep ( s )
680+ addBreadcrumb ( { category : 'flash' , message : `step:${ s } ` , level : 'info' , data : { step : s } } )
681+ setTag ( 'last_step' , String ( s ) )
682+ } ,
683+ onMessageChange : ( m ) => {
684+ setMessage ( m )
685+ if ( m ) addBreadcrumb ( { category : 'flash' , message : m , level : 'info' } )
686+ } ,
687+ onProgressChange : ( p ) => {
688+ setProgress ( p )
689+ } ,
690+ onErrorChange : ( e ) => {
691+ setError ( e )
692+ if ( e !== ErrorCode . NONE ) {
693+ addBreadcrumb ( { category : 'flash' , message : 'error' , level : 'error' , data : { errorCode : e } } )
694+ setTag ( 'error_code' , String ( e ) )
695+ }
696+ } ,
697+ onConnectionChange : ( c ) => {
698+ setConnected ( c )
699+ addBreadcrumb ( { category : 'flash' , message : c ? 'connected' : 'disconnected' , level : c ? 'info' : 'warning' } )
700+ } ,
701+ onSerialChange : ( sn ) => {
702+ setSerial ( sn )
703+ // Avoid tagging the raw serial; keep in context only
704+ setContext ( 'device' , { serial_present : ! ! sn } )
705+ }
647706 } )
648707
649708 // Initialize the manager
@@ -655,6 +714,62 @@ export default function Flash() {
655714 } )
656715 } , [ config , imageManager . current ] )
657716
717+ // Telemetry: set static tags/context once
718+ useEffect ( ( ) => {
719+ setTags ( { session_id : SESSION_ID } )
720+ setContext ( 'env' , buildEnvMeta ( ) )
721+ } , [ ] )
722+
723+ // Telemetry: tag device selection
724+ useEffect ( ( ) => {
725+ if ( selectedDevice ) {
726+ setTag ( 'device_type' , selectedDevice )
727+ addBreadcrumb ( { category : 'flash' , message : `device:${ selectedDevice } ` , level : 'info' } )
728+ }
729+ } , [ selectedDevice ] )
730+
731+ // Telemetry: wizard screen transitions
732+ useEffect ( ( ) => {
733+ if ( wizardScreen ) addBreadcrumb ( { category : 'wizard' , message : wizardScreen , level : 'info' } )
734+ } , [ wizardScreen ] )
735+
736+ // Helper to send a single pass/fail summary
737+ function sendSessionSummary ( result ) {
738+ if ( reportSentRef . current ) return
739+ reportSentRef . current = true
740+ const meta = {
741+ ...buildEnvMeta ( ) ,
742+ selectedDevice,
743+ connected,
744+ serial_present : ! ! serial ,
745+ step,
746+ message,
747+ }
748+ const tail = consoleLogs . slice ( - 200 )
749+ captureSessionSummary ( {
750+ sessionId : SESSION_ID ,
751+ result,
752+ errorCode : error ,
753+ step,
754+ meta,
755+ consoleTail : tail ,
756+ } )
757+ }
758+
759+ // Send fail on first error
760+ useEffect ( ( ) => {
761+ if ( error !== ErrorCode . NONE && ! reportSentRef . current ) {
762+ sendSessionSummary ( 'fail' )
763+ }
764+ } , [ error ] )
765+
766+ // Send pass when done without error
767+ useEffect ( ( ) => {
768+ if ( step === StepCode . DONE && error === ErrorCode . NONE && ! reportSentRef . current ) {
769+ sendSessionSummary ( 'pass' )
770+ }
771+ } , [ step , error ] )
772+
658773 // Transition to flash screen when connected
659774 useEffect ( ( ) => {
660775 if ( connected && wizardScreen === 'webusb' ) {
0 commit comments