import EventEmitter from "eventemitter3" ; import { ChannelQueue } from "@buttercup/channel-queue" ; import { HOTP , URI as OTPURI } from "otpauth" ; import { getAsyncStorage } from "./storage" ; import { notifyError } from "../library/notifications" ; import { OTP } from "../types" ; import { VaultSourceID } from "buttercup" ; const STORAGE_PREFIX_UNATTACHED = "otp:all:unattached" ; const STORAGE_PREFIX_VAULTS = "otp:all:vaults" ; const __queue = new ChannelQueue ( ) ; const __emitter = new EventEmitter ( ) ; let __otps = [ ] ; export async function addPendingOTP ( uri ) { const existing = __otps . find ( otp => otp . otpURL === uri && ! otp . sourceID ) ; if ( existing ) { return false ; } try { const otp = OTPURI . parse ( uri ) ; if ( otp instanceof HOTP ) { throw new Error ( "HOTP is not supported - only TOTP items can be added" ) ; } __otps . push ( { otpTitle : otp . label , otpURL : uri , sourceID : null , entryTitle : null , entryID : null , entryProperty : null } ) ; __emitter . emit ( "pending" ) ; await storeOTPs ( ) ; } catch ( err ) { notifyError ( "OTP Failure" , err . message ) ; return false ; } return true ; } export function getEmitter ( ) { return __emitter ; } export function getOTPs ( ) { return [ ... __otps ] ; } export function getPendingOTPs ( ) { return __otps . filter ( otp => ! otp . sourceID ) ; } export async function initialise ( ) { const storage = getAsyncStorage ( ) ; await __queue . channel ( "store" ) . enqueue ( async ( ) => { const [ unattachedRaw , vaultsRaw ] = await Promise . all ( [ storage . getValue ( STORAGE_PREFIX_UNATTACHED ) , storage . getValue ( STORAGE_PREFIX_VAULTS ) ] ) ; const unattachedOTPs = unattachedRaw ? JSON . parse ( unattachedRaw ) : [ ] ; const vaultOTPs = vaultsRaw ? JSON . parse ( vaultsRaw ) : [ ] ; __otps = [ ... unattachedOTPs , ... vaultOTPs ] ; sortOTPs ( ) ; } ) ; __emitter . emit ( "stored" ) ; } export async function removeMissingSources ( availableSourceIDs ) { __otps = __otps . filter ( otp => { if ( ! otp . sourceID ) return true ; return availableSourceIDs . includes ( otp . sourceID ) ; } ) ; await storeOTPs ( ) ; } export async function removePendingOTP ( uri ) { const ind = __otps . findIndex ( otp => ! otp . sourceID && otp . otpURL === uri ) ; if ( ind >= 0 ) { __otps . splice ( ind , 1 ) ; } await storeOTPs ( ) ; } export async function removeSourceOTPs ( sourceID , store = true ) { __otps = __otps . filter ( otp => otp . sourceID !== sourceID ) ; if ( store ) { await storeOTPs ( ) ; } } export async function setSourceOTPs ( sourceID , otps ) { await removeSourceOTPs ( sourceID , false ) ; __otps . push ( ... otps ) ; await storeOTPs ( ) ; } function sortOTPs ( ) { __otps = __otps . sort ( ( a , b ) => { if ( a . otpTitle < b . otpTitle ) return - 1 ; if ( a . otpTitle > b . otpTitle ) return 1 ; return 0 ; } ) ; } export async function storeOTPs ( ) { const storage = getAsyncStorage ( ) ; await __queue . channel ( "store" ) . enqueue ( async ( ) => { const vaultOTPs = [ ] ; const unattachedOTPs = [ ] ; for ( const otp of __otps ) { if ( ! otp . sourceID ) { unattachedOTPs . push ( otp ) ; } else { vaultOTPs . push ( otp ) ; } } await storage . setValue ( STORAGE_PREFIX_UNATTACHED , JSON . stringify ( unattachedOTPs ) ) ; await storage . setValue ( STORAGE_PREFIX_VAULTS , JSON . stringify ( vaultOTPs ) ) ; sortOTPs ( ) ; } ) ; __emitter . emit ( "stored" ) ; }