import * as util from "./util" ; import type * as localforageT from "localforage" ; type LocalForage = typeof localforageT ; declare let localforage ; import sha512 from "sha512-es" ; let storePromise = Promise . all ( [ ] ) ; let fileStorage = null ; let storeCt = 0 ; /**
 * File metadata, as stored.
 */ export interface FileInfo { /**
     * A unique ID.
     */ id : string ; /**
     * File name.
     */ name : string ; /**
     * Key-exchange information. For authentication, we ask different-origin
     * hosts for a hash of the key with a random salt, and then only tell them
     * about files for which their hash is correct. The key itself is hashed
     * with a global salt.
     */ key : string ; /**
     * Date when the file was first created, as a JavaScript-style timestamp.
     */ cdate : number ; /**
     * Date when the file expires.
     */ edate : number ; /**
     * Length of each block. Sum is length of the file.
     */ len : number [ ] ; /**
     * Set to true if this file completed transfer.
     */ complete : boolean ; /**
     * MIME type.
     */ mimeType : string ; } /**
 * Get our file storage instance.
 */ export async function getFileStorage ( ) { if ( fileStorage ) return fileStorage ; if ( typeof localforage === "undefined" ) await util . loadLibrary ( "libs/localforage.min.js" ) ; fileStorage = localforage . createInstance ( { name : "ennuicastr-file-storage" } ) ; return fileStorage ; } /**
 * Get an array of all stored files.
 */ export async function getFiles ( ) { await getFileStorage ( ) ; return await Promise . all ( ( < FileInfo [ ] > await fileStorage . getItem ( "files" ) || [ ] ) . map ( async function ( x ) { return < FileInfo > await fileStorage . getItem ( "file-" + x ) ; } ) ) ; } /**
 * Clear out any files that are expired.
 */ export async function clearExpired ( ) { await getFileStorage ( ) ; let expiredP = storePromise . then ( async function ( ) { const now = Date . now ( ) ; const files = await fileStorage . getItem ( "files" ) || [ ] ; const expired = [ ] ; for ( let i = files . length - 1 ; i >= 0 ; i -- ) { const file = files [ i ] ; const info = await fileStorage . getItem ( "file-" + file ) ; if ( ! info || info . edate < now ) expired . push ( file ) ; } return expired ; } ) ; storePromise = expiredP ; const expired = await expiredP ; for ( const file of expired ) await deleteFile ( file ) ; } param id export async function deleteFile ( id ) { await getFileStorage ( ) ; const info = await fileStorage . getItem ( "file-" + id ) ; if ( info ) { for ( let i = 0 ; i <= info . len . length ; i ++ ) await fileStorage . removeItem ( "data-" + id + "-" + i ) ; } await fileStorage . removeItem ( "file-" + id ) ; storePromise = storePromise . then ( async function ( ) { const files = await fileStorage . getItem ( "files" ) || [ ] ; const idx = files . indexOf ( id ) ; if ( idx >= 0 ) files . splice ( idx , 1 ) ; await fileStorage . setItem ( "files" , files ) ; } ) ; await storePromise ; } param name param key param stream param opts export async function storeFile ( name , key , stream , opts = { } ) { await getFileStorage ( ) ; async function report ( ) { if ( opts . report ) { const e = await navigator . storage . estimate ( ) ; opts . report ( storeCt , e . usage , e . quota ) ; } } storeCt ++ ; report ( ) ; const cdate = Date . now ( ) ; const edate = cdate + ( ( typeof opts . expTime === "number" ) ? opts . expTime : 2678400000 ) ; const saltP = storePromise . then ( async function ( ) { let salt = await fileStorage . getItem ( "salt" ) ; if ( salt === null ) { salt = ~ ~ ( Math . random ( ) * 2000000000 ) ; await fileStorage . setItem ( "salt" , salt ) ; } return salt ; } ) ; storePromise = saltP ; const salt = await saltP ; const hashKey = sha512 . hash ( key . join ( ":" ) + ":" + salt ) ; const info = { id : "" , name , key : hashKey , cdate , edate , len : [ ] , complete : false , mimeType : opts . mimeType || "application/octet-stream" } ; const idP = storePromise . then ( async function ( ) { const files = await fileStorage . getItem ( "files" ) || [ ] ; let id ; do { id = "" ; while ( id . length < 12 ) id += Math . random ( ) . toString ( 36 ) . slice ( 2 ) ; id = id . slice ( 0 , 12 ) ; } while ( files . indexOf ( id ) >= 0 ) ; files . push ( id ) ; await fileStorage . setItem ( "files" , files ) ; return id ; } ) ; storePromise = idP ; const id = info . id = await idP ; await fileStorage . setItem ( "file-" + id , info ) ; const rdr = stream . getReader ( ) ; const bufSz = 1024 * 1024 ; let buf = new Uint8Array ( bufSz ) ; let bufUsed = 0 ; while ( true ) { const rd = await rdr . read ( ) ; if ( rd . done ) break ; let chunk = rd . value . slice ( 0 ) ; while ( chunk . length ) { const take = Math . min ( bufSz - bufUsed , chunk . length ) ; buf . set ( chunk . subarray ( 0 , take ) , bufUsed ) ; bufUsed += take ; chunk = chunk . subarray ( take ) ; if ( bufUsed >= bufSz ) { await fileStorage . setItem ( "data-" + id + "-" + info . len . length , buf ) ; info . len . push ( bufSz ) ; await fileStorage . setItem ( "file-" + id , info ) ; bufUsed = 0 ; report ( ) ; } } } if ( bufUsed ) { await fileStorage . setItem ( "data-" + id + "-" + info . len . length , buf . slice ( 0 , bufUsed ) ) ; info . len . push ( bufUsed ) ; } info . complete = true ; await fileStorage . setItem ( "file-" + id , info ) ; storeCt -- ; report ( ) ; }