@@ -77,6 +77,7 @@ interface DecodedMessage {
7777 } ;
7878 } ;
7979 rawData : string ;
80+ rawBytes : number [ ] ; // Original byte array — preserved for IndexedDB storage
8081}
8182
8283// Type for batch processing results
@@ -398,6 +399,7 @@ export function decodeCanMessage(
398399 time : time ,
399400 signals : { } ,
400401 rawData : rawDataStr ,
402+ rawBytes : messageData ,
401403 } ;
402404 }
403405
@@ -420,6 +422,7 @@ export function decodeCanMessage(
420422 time : time ,
421423 signals,
422424 rawData : rawDataStr ,
425+ rawBytes : messageData ,
423426 } ;
424427 } catch ( error ) {
425428 console . error ( `Error decoding message ${ canId } :` , error ) ;
@@ -626,6 +629,115 @@ export async function createCanProcessor(): Promise<any> {
626629 } ;
627630}
628631
632+ const GITHUB_REPO = 'Western-Formula-Racing/DBC' ;
633+ // Fine-grained PAT injected at build time via VITE_GITHUB_TOKEN env var.
634+ // Needs Contents:read on Western-Formula-Racing/DBC.
635+ const GITHUB_TOKEN = ( import . meta as any ) . env ?. VITE_GITHUB_DBC_READONLY_TOKEN ?? '' ;
636+
637+ function githubHeaders ( raw = false ) : HeadersInit {
638+ const h : HeadersInit = {
639+ Accept : raw ? 'application/vnd.github.v3.raw' : 'application/vnd.github.v3+json' ,
640+ 'X-GitHub-Api-Version' : '2022-11-28' ,
641+ } ;
642+ if ( GITHUB_TOKEN ) ( h as Record < string , string > ) [ 'Authorization' ] = `Bearer ${ GITHUB_TOKEN } ` ;
643+ return h ;
644+ }
645+
646+ export interface DBCFileInfo {
647+ name : string ;
648+ path : string ;
649+ sha : string ;
650+ size : number ;
651+ }
652+
653+ export interface DBCApplyResult {
654+ ok : boolean ;
655+ message : string ;
656+ commitSha ?: string ;
657+ commitMessage ?: string ;
658+ }
659+
660+ /** List all .dbc files in the Western-Formula-Racing/DBC repo root. */
661+ export async function listDBCFiles ( ) : Promise < { ok : boolean ; files ?: DBCFileInfo [ ] ; message ?: string } > {
662+ try {
663+ const res = await fetch ( `https://api.github.com/repos/${ GITHUB_REPO } /contents/` , {
664+ headers : githubHeaders ( ) ,
665+ } ) ;
666+ if ( ! res . ok ) {
667+ return { ok : false , message : `GitHub ${ res . status } : ${ res . statusText } ` } ;
668+ }
669+ const items : any [ ] = await res . json ( ) ;
670+ const files : DBCFileInfo [ ] = items
671+ . filter ( f => f . type === 'file' && f . name . toLowerCase ( ) . endsWith ( '.dbc' ) )
672+ . map ( f => ( { name : f . name , path : f . path , sha : f . sha , size : f . size } ) ) ;
673+ return { ok : true , files } ;
674+ } catch ( err ) {
675+ return { ok : false , message : err instanceof Error ? err . message : String ( err ) } ;
676+ }
677+ }
678+
679+ /**
680+ * Fetch a specific DBC file from the repo, cache it locally, and update the
681+ * in-memory dbcFile so the next createCanProcessor() call uses it.
682+ * Also fetches the latest commit that touched the file for display.
683+ */
684+ export async function fetchAndApplyDBC ( filename : string ) : Promise < DBCApplyResult > {
685+ // Fetch raw file content
686+ let fileRes : Response ;
687+ try {
688+ fileRes = await fetch (
689+ `https://api.github.com/repos/${ GITHUB_REPO } /contents/${ encodeURIComponent ( filename ) } ` ,
690+ { headers : githubHeaders ( true ) }
691+ ) ;
692+ } catch ( err ) {
693+ return { ok : false , message : `Network error: ${ err instanceof Error ? err . message : String ( err ) } ` } ;
694+ }
695+
696+ if ( ! fileRes . ok ) {
697+ return { ok : false , message : `GitHub ${ fileRes . status } : ${ fileRes . statusText } ` } ;
698+ }
699+
700+ const dbcText = await fileRes . text ( ) ;
701+ if ( ! dbcText . trim ( ) ) {
702+ return { ok : false , message : 'Fetched DBC is empty' } ;
703+ }
704+
705+ // Fetch the most recent commit that touched this file
706+ let commitSha = '' ;
707+ let commitMessage = '' ;
708+ try {
709+ const commitRes = await fetch (
710+ `https://api.github.com/repos/${ GITHUB_REPO } /commits?path=${ encodeURIComponent ( filename ) } &per_page=1` ,
711+ { headers : githubHeaders ( ) }
712+ ) ;
713+ if ( commitRes . ok ) {
714+ const commits : any [ ] = await commitRes . json ( ) ;
715+ if ( commits . length > 0 ) {
716+ commitSha = commits [ 0 ] . sha . slice ( 0 , 7 ) ;
717+ commitMessage = commits [ 0 ] . commit . message . split ( '\n' ) [ 0 ] ;
718+ }
719+ }
720+ } catch { /* non-fatal */ }
721+
722+ // Update in-memory state immediately
723+ dbcFile = dbcText ;
724+ usingCache = true ;
725+ localStorage . setItem ( 'dbc-selected-file' , filename ) ;
726+
727+ // Persist to Cache API, fall back to localStorage
728+ try {
729+ const cache = await caches . open ( 'dbc-files' ) ;
730+ await cache . put ( 'cache.dbc' , new Response ( dbcText , {
731+ headers : { 'Content-Type' : 'text/plain; charset=utf-8' } ,
732+ } ) ) ;
733+ } catch {
734+ try { localStorage . setItem ( 'dbc-file-content' , dbcText ) ; } catch { /* ignore */ }
735+ }
736+
737+ const sizeKb = ( dbcText . length / 1024 ) . toFixed ( 1 ) ;
738+ return { ok : true , message : `${ filename } — ${ sizeKb } KB` , commitSha, commitMessage } ;
739+ }
740+
629741/**
630742 * Example: Setup WebSocket listener with CAN processor
631743 * Usage in your browser app:
0 commit comments