1- export const bluetoothDevices = [
1+ import BuildApi from "../BuildApi.js" ;
2+ import { get as getConfig , set as setConfig } from "../ConfigStorage.js" ;
3+
4+ const STORAGE_KEY = "device-filters" ;
5+
6+ const defaultBluetoothDevices = [
27 {
38 name : "CC2541" ,
49 serviceUuid : "0000ffe0-0000-1000-8000-00805f9b34fb" ,
@@ -50,7 +55,7 @@ export const bluetoothDevices = [
5055 } ,
5156] ;
5257
53- export const serialDevices = [
58+ const defaultSerialDevices = [
5459 { vendorId : 1027 , productId : 24577 } , // FT232R USB UART
5560 { vendorId : 1155 , productId : 12886 } , // STM32 in HID mode
5661 { vendorId : 1155 , productId : 14158 } , // 0483:374e STM Electronics STLink Virtual COM Port (NUCLEO boards)
@@ -64,17 +69,15 @@ export const serialDevices = [
6469 { vendorId : 11914 , productId : 9 } , // Raspberry Pi Pico VCP
6570] ;
6671
67- export const usbDevices = {
68- filters : [
69- { vendorId : 1155 , productId : 57105 } , // STM Device in DFU Mode || Digital Radio in USB mode
70- { vendorId : 10473 , productId : 393 } , // GD32 DFU Bootloader
71- { vendorId : 11836 , productId : 57105 } , // AT32F435 DFU Bootloader
72- { vendorId : 12619 , productId : 262 } , // APM32 DFU Bootloader
73- { vendorId : 11914 , productId : 15 } , // Raspberry Pi Pico in Bootloader mode
74- ] ,
75- } ;
72+ const defaultUsbFilters = [
73+ { vendorId : 1155 , productId : 57105 } , // STM Device in DFU Mode || Digital Radio in USB mode
74+ { vendorId : 10473 , productId : 393 } , // GD32 DFU Bootloader
75+ { vendorId : 11836 , productId : 57105 } , // AT32F435 DFU Bootloader
76+ { vendorId : 12619 , productId : 262 } , // APM32 DFU Bootloader
77+ { vendorId : 11914 , productId : 15 } , // Raspberry Pi Pico in Bootloader mode
78+ ] ;
7679
77- export const vendorIdNames = {
80+ const defaultVendorIdNames = {
7881 1027 : "FTDI" ,
7982 1155 : "STM Electronics" ,
8083 4292 : "Silicon Labs" ,
@@ -83,7 +86,83 @@ export const vendorIdNames = {
8386 11914 : "Raspberry Pi Pico" ,
8487} ;
8588
89+ export const bluetoothDevices = [ ...defaultBluetoothDevices ] ;
90+ export const serialDevices = [ ...defaultSerialDevices ] ;
91+ export const usbDevices = { filters : [ ...defaultUsbFilters ] } ;
92+ export const vendorIdNames = { ...defaultVendorIdNames } ;
8693export const webSerialDevices = serialDevices . map ( ( { vendorId, productId } ) => ( {
8794 usbVendorId : vendorId ,
8895 usbProductId : productId ,
8996} ) ) ;
97+
98+ const UNSAFE_KEYS = new Set ( [ "__proto__" , "constructor" , "prototype" ] ) ;
99+
100+ function isPlainObject ( value ) {
101+ return value !== null && typeof value === "object" && ! Array . isArray ( value ) ;
102+ }
103+
104+ function sanitizeVidPidEntries ( arr ) {
105+ return arr . filter (
106+ ( entry ) => isPlainObject ( entry ) && typeof entry . vendorId === "number" && typeof entry . productId === "number" ,
107+ ) ;
108+ }
109+
110+ function applyFilters ( data ) {
111+ if ( Array . isArray ( data ?. bluetoothDevices ) ) {
112+ const sanitized = data . bluetoothDevices . filter ( ( d ) => isPlainObject ( d ) && typeof d . serviceUuid === "string" ) ;
113+ bluetoothDevices . splice ( 0 , bluetoothDevices . length , ...sanitized ) ;
114+ }
115+ if ( Array . isArray ( data ?. serialDevices ) ) {
116+ const sanitized = sanitizeVidPidEntries ( data . serialDevices ) ;
117+ serialDevices . splice ( 0 , serialDevices . length , ...sanitized ) ;
118+ webSerialDevices . splice (
119+ 0 ,
120+ webSerialDevices . length ,
121+ ...sanitized . map ( ( { vendorId, productId } ) => ( {
122+ usbVendorId : vendorId ,
123+ usbProductId : productId ,
124+ } ) ) ,
125+ ) ;
126+ }
127+ if ( Array . isArray ( data ?. usbDevices ?. filters ) ) {
128+ const sanitized = sanitizeVidPidEntries ( data . usbDevices . filters ) ;
129+ usbDevices . filters . splice ( 0 , usbDevices . filters . length , ...sanitized ) ;
130+ }
131+ if ( isPlainObject ( data ?. vendorIdNames ) ) {
132+ for ( const key of Object . keys ( vendorIdNames ) ) {
133+ delete vendorIdNames [ key ] ;
134+ }
135+ for ( const [ key , value ] of Object . entries ( data . vendorIdNames ) ) {
136+ if ( UNSAFE_KEYS . has ( key ) || typeof value !== "string" ) {
137+ continue ;
138+ }
139+ vendorIdNames [ key ] = value ;
140+ }
141+ }
142+ }
143+
144+ function isValidPayload ( data ) {
145+ if ( ! isPlainObject ( data ) ) {
146+ return false ;
147+ }
148+ return (
149+ Array . isArray ( data . bluetoothDevices ) ||
150+ Array . isArray ( data . serialDevices ) ||
151+ Array . isArray ( data . usbDevices ?. filters ) ||
152+ isPlainObject ( data . vendorIdNames )
153+ ) ;
154+ }
155+
156+ export async function loadDeviceFilters ( buildApi = new BuildApi ( ) ) {
157+ const remote = await buildApi . loadDeviceFilters ( ) ;
158+ if ( isValidPayload ( remote ) ) {
159+ applyFilters ( remote ) ;
160+ setConfig ( { [ STORAGE_KEY ] : remote } ) ;
161+ return ;
162+ }
163+
164+ const cached = getConfig ( STORAGE_KEY ) ?. [ STORAGE_KEY ] ;
165+ if ( cached ) {
166+ applyFilters ( cached ) ;
167+ }
168+ }
0 commit comments