import Constants from './lib/constants' ; import Config from './lib/config' ; export default class WebConfig extends Config { _splitContainerKeys : { [ key ] : string [ ] ; } ; audioWordlistId : number ; customAudioSites : { [ site ] : AudioRule [ ] ; } ; darkMode : boolean ; domains : { [ site ] : DomainCfg ; } ; enabledDomainsOnly : boolean ; fillerAudio : string ; muteAudio : boolean ; muteAudioOnly : boolean ; muteCueRequireShowing : boolean ; muteMethod : number ; password : string ; showSubtitles : number ; showUpdateNotification : boolean ; collectStats : boolean ; youTubeAutoSubsMax : number ; youTubeAutoSubsMin : number ; static readonly _classDefaults = { audioWordlistId : 0 , customAudioSites : null , darkMode : false , domains : { } , enabledDomainsOnly : false , fillerAudio : '' , muteAudio : false , muteAudioOnly : false , muteCueRequireShowing : false , muteMethod : Constants . MUTE_METHODS . TAB , password : null , showSubtitles : Constants . SHOW_SUBTITLES . ALL , collectStats : true , showUpdateNotification : false , youTubeAutoSubsMax : 0 , youTubeAutoSubsMin : 0 , } ; static readonly QUOTA_BYTES_PER_ITEM = 8192 ; static readonly _defaults = Object . assign ( { } , Config . _defaults , WebConfig . _classDefaults ) ; static readonly _splittingKeys = [ 'domains' , 'words' ] ; static readonly _maxBytes = 8000 ; static async build ( keys = [ ] ) { if ( typeof keys === 'string' ) { keys = [ keys ] ; } const asyncResult = await WebConfig . getConfig ( keys ) ; const instance = new WebConfig ( asyncResult ) ; return instance ; } constructor ( asyncParam ) { if ( typeof asyncParam === 'undefined' ) { throw new Error ( 'Cannot be called directly. call build()' ) ; } super ( ) ; this . _splitContainerKeys = { } ; Object . assign ( this , WebConfig . _classDefaults , asyncParam ) ; } static chromeStorageAvailable ( ) { return ! ! ( chrome && chrome . storage && chrome . storage . sync && chrome . storage . local ) ; } static combineData ( data , prop ) { data [ prop ] = { } ; if ( data [ `_${ prop }0` ] !== undefined ) { const dataKeys = WebConfig . getDataContainerKeys ( data , prop ) ; dataKeys . forEach ( ( key ) => { Object . assign ( data [ prop ] , data [ key ] ) ; delete data [ key ] ; } ) ; return dataKeys ; } } static getConfig ( keys ) { return new Promise ( ( resolve , reject ) => { if ( chrome . runtime . lastError ) { reject ( chrome . runtime . lastError . message ) ; } let request = null ; if ( keys . length > 0 && ! keys . some ( ( key ) => WebConfig . _splittingKeys . includes ( key ) ) ) { request = { } ; keys . forEach ( ( key ) => { request [ key ] = WebConfig . _defaults [ key ] ; } ) ; } chrome . storage . sync . get ( request , ( items ) => { items . _splitContainerKeys = { } ; Object . keys ( WebConfig . _defaults ) . forEach ( ( defaultKey ) => { if ( ( request == null || keys . includes ( defaultKey ) ) && items [ defaultKey ] === undefined ) { items [ defaultKey ] = WebConfig . _defaults [ defaultKey ] ; } } ) ; if ( keys . length === 0 || keys . includes ( 'words' ) ) { if ( items . _words0 === undefined || Object . keys ( items . _words0 ) . length == 0 ) { items . _words0 = Config . _defaultWords ; } } WebConfig . _splittingKeys . forEach ( ( splittingKey ) => { const splitKeys = WebConfig . combineData ( items , splittingKey ) ; if ( splitKeys ) { items . _splitContainerKeys [ splittingKey ] = splitKeys ; } } ) ; if ( request !== null && keys . length > 0 ) { Object . keys ( items ) . forEach ( ( item ) => { if ( ! keys . includes ( item ) ) { delete items [ item ] ; } } ) ; } resolve ( items ) ; } ) ; } ) ; } static getDataContainerKeys ( data , prop ) { const pattern = new RegExp ( `^_${ prop }\\d+` ) ; const containerKeys = Object . keys ( data ) . filter ( ( key ) => pattern . test ( key ) ) ; return containerKeys ; } static getLocalStoragePromise ( keys ) { if ( typeof keys === 'string' ) { keys = [ keys ] ; } return new Promise ( ( resolve , reject ) => { chrome . storage . local . get ( keys , ( data ) => { chrome . runtime . lastError ? reject ( chrome . runtime . lastError . message ) : resolve ( data ) ; } ) ; } ) ; } static removeLocalStoragePromise ( keys ) { if ( keys === 'ALL' ) { return new Promise ( ( resolve , reject ) => { chrome . storage . local . clear ( ( ) => { chrome . runtime . lastError ? reject ( chrome . runtime . lastError . message ) : resolve ( 0 ) ; } ) ; } ) ; } if ( typeof keys === 'string' ) { keys = [ keys ] ; } return new Promise ( ( resolve , reject ) => { chrome . storage . local . remove ( keys , ( ) => { chrome . runtime . lastError ? reject ( chrome . runtime . lastError . message ) : resolve ( 0 ) ; } ) ; } ) ; } static saveLocalStoragePromise ( data ) { return new Promise ( ( resolve , reject ) => { chrome . storage . local . set ( data , ( ) => { chrome . runtime . lastError ? reject ( chrome . runtime . lastError . message ) : resolve ( 0 ) ; } ) ; } ) ; } ordered ( ) { return Object . keys ( this ) . sort ( ) . reduce ( ( obj , key ) => { if ( key [ 0 ] != '_' ) { obj [ key ] = this [ key ] ; } return obj ; } , { } ) ; } remove ( props ) { if ( typeof props === 'string' ) { props = [ props ] ; } chrome . storage . sync . remove ( props ) ; props . forEach ( ( prop ) => { delete this [ prop ] ; } ) ; } reset ( ) { return new Promise ( ( resolve , reject ) => { chrome . storage . sync . clear ( ( ) => { chrome . runtime . lastError ? reject ( chrome . runtime . lastError . message ) : resolve ( 0 ) ; } ) ; } ) ; } save ( props = [ ] ) { if ( typeof props === 'string' ) { props = [ props ] ; } const data = { } ; if ( props . length === 0 ) { props = Object . keys ( WebConfig . _defaults ) ; props . push ( 'words' ) ; } props . forEach ( ( prop ) => { if ( WebConfig . _splittingKeys . includes ( prop ) ) { Object . assign ( data , this . splitData ( prop ) ) ; } else { data [ prop ] = this [ prop ] ; } } ) ; if ( Object . keys ( this . _splitContainerKeys ) . length !== 0 && props . some ( ( prop ) => WebConfig . _splittingKeys . includes ( prop ) ) ) { WebConfig . _splittingKeys . forEach ( ( splittingKey ) => { if ( props . includes ( splittingKey ) ) { const newContainerKeys = WebConfig . getDataContainerKeys ( data , splittingKey ) ; if ( this . _splitContainerKeys [ splittingKey ] ) { const containersToRemove = this . _splitContainerKeys [ splittingKey ] . filter ( ( oldKey ) => ! newContainerKeys . includes ( oldKey ) ) ; if ( containersToRemove . length !== 0 ) { this . remove ( containersToRemove ) ; this . _splitContainerKeys [ splittingKey ] = newContainerKeys ; } } } } ) ; } return new Promise ( ( resolve , reject ) => { chrome . storage . sync . set ( data , ( ) => { chrome . runtime . lastError ? reject ( chrome . runtime . lastError . message ) : resolve ( 0 ) ; } ) ; } ) ; } splitData ( key ) { const encoder = new TextEncoder ( ) ; let currentContainerNum = 0 ; let currentBytes = 2 ; const data = { } ; let currentContainer = `_${ key }${ currentContainerNum }` ; data [ currentContainer ] = { } ; currentBytes += encoder . encode ( `{"${ currentContainer }":{}}` ) . length ; Object . keys ( this [ key ] ) . sort ( ) . forEach ( ( item ) => { let newBytes = encoder . encode ( `",${ item }":` ) . length ; newBytes += encoder . encode ( JSON . stringify ( this [ key ] [ item ] ) ) . length ; if ( ( currentBytes + newBytes ) >= WebConfig . _maxBytes ) { currentContainerNum ++ ; currentContainer = `_${ key }${ currentContainerNum }` ; data [ currentContainer ] = { } ; currentBytes = encoder . encode ( `"${ currentContainer }":{}` ) . length ; } currentBytes += newBytes ; data [ currentContainer ] [ item ] = this [ key ] [ item ] ; } ) ; return data ; } }