import Dexie from "dexie" ; import { Config } from "./config" ; import { parseXML } from "./util" ; import { lazy } from "./lazy" ; export interface NmapLog { time : number ; devices : Set < string > ; } interface GottenFiles { filename : string ; result : "404" | "success" ; } export interface MacToInfo { mac : string ; type : "ip" | "hostname" | "vendor" ; info : string ; } export interface DeviceInfo { displayname : string | undefined ; vendor : string [ ] ; hostnames : string [ ] ; ips : string [ ] ; } export class Database extends Dexie { nmapLogs ! : Dexie . Table < NmapLog , number > ; gottenFiles ! : Dexie . Table < GottenFiles , string > ; macToInfo ! : Dexie . Table < MacToInfo , string > ; constructor ( private config ) { super ( "NmapLogDatabase" ) ; this . version ( 1 ) . stores ( { nmapLogs : "time" , gottenFiles : "filename" , macToInfo : "[mac+type+info], mac, info" , } ) ; } async getDeviceInfo ( mac ) { const infos = await this . macToInfo . where ( "mac" ) . equals ( mac ) . toArray ( ) ; return { displayname : this . config . deviceNames [ mac ] , vendor : infos . filter ( ( info ) => info . type === "vendor" ) . map ( ( info ) => info . info ) . filter ( ( x ) => x . length > 0 ) , hostnames : infos . filter ( ( info ) => info . type === "hostname" ) . map ( ( info ) => info . info ) , ips : infos . filter ( ( info ) => info . type === "ip" ) . map ( ( info ) => info . info ) , } ; } return async getForDate ( date ) { date . setUTCHours ( 0 , 0 , 0 , 0 ) ; const recentDate = new Date ( ) ; recentDate . setDate ( recentDate . getDate ( ) - 3 ) ; const dateFormatted = date . toISOString ( ) . substr ( 0 , 10 ) ; const filename = this . config . logFilesPath + dateFormatted + ".xml" ; return await this . getForFile ( filename , undefined , date >= recentDate ) ; } async getForFile ( filename , statusCallback ? , forceFetch = false ) { const gotDate = await this . gottenFiles . get ( filename ) . catch ( ( _e ) => null ) ; if ( ! forceFetch && gotDate ) return gotDate . result ; const response = await fetch ( filename , { credentials : "include" } ) ; if ( response . status == 404 ) { await this . gottenFiles . put ( { filename , result : "404" } ) ; return "404" ; } if ( response . status >= 300 ) { throw Error ( `Request Error: ${ response . status }: ${ response . statusText }` ) ; } const rawXMLs = ( await response . text ( ) ) . split ( "<?xml version" ) ; const total = rawXMLs . length ; console . log ( `loading ${ total } files` ) ; let i = 0 ; const createStatusCallback = ( ) => statusCallback ? [ new Promise < void > ( ( resolve ) => { statusCallback ( "loading" , i , total ) ; setTimeout ( resolve , 0 ) ; } ) , ] : [ ] ; await lazy ( rawXMLs ) . chunk ( 200 ) . map ( async ( rawXMLs ) => await this . transaction ( "rw" , [ this . gottenFiles , this . nmapLogs , this . macToInfo ] , ( ) => { for ( const rawXML of rawXMLs ) { if ( rawXML . length == 0 ) continue ; const scan = parseXML ( this . config , filename , "<?xml version" + rawXML ) ; if ( scan ) { void this . nmapLogs . put ( scan . online ) ; void this . macToInfo . bulkPut ( scan . newInfos ) ; } i ++ ; } } ) ) . intersplice ( createStatusCallback ) . awaitSequential ( ) ; await this . gottenFiles . put ( { filename , result : "success" } ) ; return "success" ; } async getAllDates ( progressCallback ) { const current = new Date ( ) ; let failures = 0 ; let gotten = 0 ; while ( current > new Date ( 2010 , 0 , 1 ) ) { const resp = await this . getForDate ( current ) ; if ( resp === "404" ) { failures ++ ; if ( failures >= this . config . maxMissingDays ) break ; } else { failures = 0 ; gotten ++ ; progressCallback ( gotten ) ; if ( gotten >= this . config . dayGetLimit ) break ; } current . setUTCDate ( current . getUTCDate ( ) - 1 ) ; } } }