2018-12-30 23:16:09 +01:00
import Debug from '../conf/Debug' ;
import currentBrowser from '../conf/BrowserDetect' ;
import ExtensionConf from '../conf/ExtensionConf' ;
2021-02-08 23:04:54 +01:00
import ExtensionMode from '../../common/enums/ExtensionMode.enum' ;
2021-02-09 00:37:54 +01:00
import ObjectCopy from './ObjectCopy' ;
2021-02-08 23:04:54 +01:00
import StretchType from '../../common/enums/StretchType.enum' ;
import VideoAlignmentType from '../../common/enums/VideoAlignmentType.enum' ;
2019-07-05 23:45:29 +02:00
import ExtensionConfPatch from '../conf/ExtConfPatches' ;
2021-02-08 23:04:54 +01:00
import CropModePersistence from '../../common/enums/CropModePersistence.enum' ;
2020-12-23 02:01:52 +01:00
import BrowserDetect from '../conf/BrowserDetect' ;
2021-02-09 00:37:54 +01:00
import Logger from './Logger' ;
import SettingsInterface from '../../common/interfaces/SettingsInterface' ;
2021-11-23 01:32:42 +01:00
import AspectRatioType from '../../common/enums/AspectRatioType.enum' ;
2023-01-07 18:57:47 +01:00
import { SiteSettings } from './settings/SiteSettings' ;
2018-12-30 23:16:09 +01:00
2020-04-13 15:20:29 +02:00
if ( process . env . CHANNEL !== 'stable' ) {
2020-12-03 01:05:39 +01:00
console . info ( "Loading Settings" ) ;
2020-04-13 15:20:29 +02:00
}
2019-06-03 00:37:19 +02:00
2021-02-09 00:37:54 +01:00
2018-08-05 23:48:56 +02:00
class Settings {
2021-02-09 00:37:54 +01:00
//#region flags
useSync : boolean = false ;
version : string ;
//#endregion
//#region helper classes
logger : Logger ;
//#endregion
//#region data
default : SettingsInterface ; // default settings
active : SettingsInterface ; // currently active settings
//#endregion
//#region callbacks
onSettingsChanged : any ;
afterSettingsSaved : any ;
//#endregion
2018-08-05 23:48:56 +02:00
2019-09-03 00:28:35 +02:00
constructor ( options ) {
// Options: activeSettings, updateCallback, logger
2020-12-23 02:01:52 +01:00
this . logger = options ? . logger ;
this . onSettingsChanged = options ? . onSettingsChanged ;
this . afterSettingsSaved = options ? . afterSettingsSaved ;
this . active = options ? . activeSettings ? ? undefined ;
2018-08-05 23:48:56 +02:00
this . default = ExtensionConf ;
2019-08-25 01:52:04 +02:00
this . default [ 'version' ] = this . getExtensionVersion ( ) ;
2018-08-23 01:04:37 +02:00
2024-06-03 00:15:23 +02:00
chrome . storage . onChanged . addListener ( ( changes , area ) = > { this . storageChangeListener ( changes , area ) } ) ;
2019-10-24 00:44:27 +02:00
}
2023-01-07 03:06:37 +01:00
private storageChangeListener ( changes , area ) {
2020-01-29 01:36:09 +01:00
if ( ! changes . uwSettings ) {
return ;
}
2020-12-23 02:01:52 +01:00
this . logger ? . log ( 'info' , 'settings' , "[Settings::<storage/on change>] Settings have been changed outside of here. Updating active settings. Changes:" , changes , "storage area:" , area ) ;
2020-02-09 01:16:46 +01:00
// if (changes['uwSettings'] && changes['uwSettings'].newValue) {
2020-12-23 02:01:52 +01:00
// this.logger?.log('info', 'settings',"[Settings::<storage/on change>] new settings object:", JSON.parse(changes.uwSettings.newValue));
2020-02-09 01:16:46 +01:00
// }
2019-10-24 21:13:45 +02:00
const parsedSettings = JSON . parse ( changes . uwSettings . newValue ) ;
2020-02-09 01:16:46 +01:00
this . setActive ( parsedSettings ) ;
2020-12-23 02:01:52 +01:00
this . logger ? . log ( 'info' , 'debug' , 'Does parsedSettings.preventReload exist?' , parsedSettings . preventReload , "Does callback exist?" , ! ! this . onSettingsChanged ) ;
2019-10-24 00:44:27 +02:00
2020-02-09 01:16:46 +01:00
if ( ! parsedSettings . preventReload && this . onSettingsChanged ) {
2019-10-24 00:44:27 +02:00
try {
2020-02-09 01:16:46 +01:00
this . onSettingsChanged ( ) ;
2021-04-01 21:44:07 +02:00
2020-12-23 02:01:52 +01:00
this . logger ? . log ( 'info' , 'settings' , '[Settings] Update callback finished.' )
2019-10-24 00:44:27 +02:00
} catch ( e ) {
2020-12-23 02:01:52 +01:00
this . logger ? . log ( 'error' , 'settings' , "[Settings] CALLING UPDATE CALLBACK FAILED. Reason:" , e )
2019-10-24 00:44:27 +02:00
}
2018-08-23 01:04:37 +02:00
}
2020-12-21 23:27:45 +01:00
if ( this . afterSettingsSaved ) {
this . afterSettingsSaved ( ) ;
}
2018-08-05 23:48:56 +02:00
}
2021-02-09 00:37:54 +01:00
static getExtensionVersion ( ) : string {
2024-06-03 00:15:23 +02:00
return chrome . runtime . getManifest ( ) . version ;
2020-03-02 22:35:34 +01:00
}
2021-02-09 00:37:54 +01:00
getExtensionVersion ( ) : string {
2021-11-23 01:32:42 +01:00
return Settings . getExtensionVersion ( ) ;
2019-08-25 01:52:04 +02:00
}
2023-01-07 03:06:37 +01:00
private compareExtensionVersions ( a , b ) {
2019-09-17 22:14:42 +02:00
let aa = a . split ( '.' ) ;
let bb = b . split ( '.' ) ;
2021-11-23 01:32:42 +01:00
2019-09-03 00:28:35 +02:00
if ( + aa [ 0 ] !== + bb [ 0 ] ) {
// difference on first digit
2019-09-17 22:14:42 +02:00
return + aa [ 0 ] - + bb [ 0 ] ;
2019-09-03 00:28:35 +02:00
} if ( + aa [ 1 ] !== + bb [ 1 ] ) {
// first digit same, difference on second digit
2019-09-17 22:14:42 +02:00
return + aa [ 1 ] - + bb [ 1 ] ;
2019-09-03 00:28:35 +02:00
} if ( + aa [ 2 ] !== + bb [ 2 ] ) {
2019-09-17 22:14:42 +02:00
return + aa [ 2 ] - + bb [ 2 ] ;
2019-09-03 00:28:35 +02:00
// first two digits the same, let's check the third digit
} else {
// fourth digit is optional. When not specified, 0 is implied
// btw, ++(aa[3] || 0) - ++(bb[3] || 0) doesn't work
2019-09-17 22:14:42 +02:00
// Since some things are easier if we actually have a value for
// the fourth digit, we turn a possible undefined into a zero
aa [ 3 ] = aa [ 3 ] === undefined ? 0 : aa [ 3 ] ;
bb [ 3 ] = bb [ 3 ] === undefined ? 0 : bb [ 3 ] ;
2021-11-23 01:32:42 +01:00
// also, the fourth digit can start with a letter.
// versions that start with a letter are ranked lower than
2019-09-17 22:14:42 +02:00
// versions x.x.x.0
2021-02-09 00:37:54 +01:00
if (
( isNaN ( + aa [ 3 ] ) && ! isNaN ( + bb [ 3 ] ) )
|| ( ! isNaN ( + aa [ 3 ] ) && isNaN ( + bb [ 3 ] ) )
) {
2019-09-17 22:14:42 +02:00
return isNaN ( + aa [ 3 ] ) ? - 1 : 1 ;
}
2021-11-23 01:32:42 +01:00
// at this point, either both version numbers are a NaN or
2019-09-17 22:14:42 +02:00
// both versions are a number.
if ( ! isNaN ( + aa [ 3 ] ) ) {
return + aa [ 3 ] - + bb [ 3 ] ;
}
// letters have their own hierarchy:
// dev < a < b < rc
let av = this . getPrereleaseVersionHierarchy ( aa [ 3 ] ) ;
let bv = this . getPrereleaseVersionHierarchy ( bb [ 3 ] ) ;
if ( av !== bv ) {
return av - bv ;
} else {
return + ( aa [ 3 ] . replace ( /\D/g , '' ) ) - + ( bb [ 3 ] . replace ( /\D/g , '' ) ) ;
}
}
}
2023-01-07 03:06:37 +01:00
private getPrereleaseVersionHierarchy ( version ) {
2019-09-17 22:14:42 +02:00
if ( version . startsWith ( 'dev' ) ) {
return 0 ;
2019-09-03 00:28:35 +02:00
}
2019-09-17 22:14:42 +02:00
if ( version . startsWith ( 'a' ) ) {
return 1 ;
}
if ( version . startsWith ( 'b' ) ) {
return 2 ;
}
return 3 ;
2019-09-03 00:28:35 +02:00
}
2019-09-17 22:14:42 +02:00
2023-01-07 03:06:37 +01:00
private sortConfPatches ( patchesIn ) {
2019-09-17 22:14:42 +02:00
return patchesIn . sort ( ( a , b ) = > this . compareExtensionVersions ( a . forVersion , b . forVersion ) ) ;
2019-09-03 00:28:35 +02:00
}
2023-01-07 03:06:37 +01:00
private findFirstNecessaryPatch ( version , extconfPatches ) {
2019-09-03 00:28:35 +02:00
const sorted = this . sortConfPatches ( extconfPatches ) ;
2019-09-17 22:14:42 +02:00
return sorted . findIndex ( x = > this . compareExtensionVersions ( x . forVersion , version ) > 0 ) ;
2019-09-03 00:28:35 +02:00
}
2023-01-07 03:06:37 +01:00
private applySettingsPatches ( oldVersion , patches ) {
2019-09-03 00:28:35 +02:00
let index = this . findFirstNecessaryPatch ( oldVersion , patches ) ;
if ( index === - 1 ) {
2020-12-23 02:01:52 +01:00
this . logger ? . log ( 'info' , 'settings' , '[Settings::applySettingsPatches] There are no pending conf patches.' ) ;
2019-09-03 00:28:35 +02:00
return ;
}
// apply all remaining patches
2020-12-23 02:01:52 +01:00
this . logger ? . log ( 'info' , 'settings' , ` [Settings::applySettingsPatches] There are ${ patches . length - index } settings patches to apply ` ) ;
2019-09-17 22:14:42 +02:00
while ( index < patches . length ) {
2019-11-02 01:05:36 +01:00
const updateFn = patches [ index ] . updateFn ;
2019-09-03 00:28:35 +02:00
delete patches [ index ] . forVersion ;
2019-11-02 01:05:36 +01:00
delete patches [ index ] . updateFn ;
2021-11-23 01:32:42 +01:00
2019-11-02 01:05:36 +01:00
if ( Object . keys ( patches [ index ] ) . length > 0 ) {
ObjectCopy . overwrite ( this . active , patches [ index ] ) ;
}
if ( updateFn ) {
try {
updateFn ( this . active , this . getDefaultSettings ( ) ) ;
} catch ( e ) {
2020-12-23 02:01:52 +01:00
this . logger ? . log ( 'error' , 'settings' , '[Settings::applySettingsPatches] Failed to execute update function. Keeping settings object as-is. Error:' , e ) ;
2019-11-02 01:05:36 +01:00
}
}
2019-09-17 22:14:42 +02:00
index ++ ;
}
2019-09-03 00:28:35 +02:00
}
2018-08-05 23:48:56 +02:00
async init() {
2018-08-21 23:48:47 +02:00
const settings = await this . get ( ) ;
2019-09-17 22:14:42 +02:00
this . version = this . getExtensionVersion ( ) ;
2019-08-28 18:28:22 +02:00
// |—> on first setup, settings is undefined & settings.version is haram
2019-08-31 18:22:13 +02:00
// | since new installs ship with updates by default, no patching is
// | needed. In this case, we assume we're on the current version
2019-09-17 22:14:42 +02:00
const oldVersion = ( settings && settings . version ) || this . version ;
2018-08-05 23:48:56 +02:00
2019-10-27 00:10:49 +02:00
if ( settings ) {
2020-12-23 02:01:52 +01:00
this . logger ? . log ( 'info' , 'settings' , "[Settings::init] Configuration fetched from storage:" , settings ,
2019-09-03 22:17:10 +02:00
"\nlast saved with:" , settings . version ,
2019-09-17 22:14:42 +02:00
"\ncurrent version:" , this . version
2019-09-03 22:17:10 +02:00
) ;
2018-08-05 23:48:56 +02:00
}
2019-10-27 00:10:49 +02:00
// if (Debug.flushStoredSettings) {
2020-12-23 02:01:52 +01:00
// this.logger?.log('info', 'settings', "%c[Settings::init] Debug.flushStoredSettings is true. Using default settings", "background: #d00; color: #ffd");
2019-10-27 00:10:49 +02:00
// Debug.flushStoredSettings = false; // don't do it again this session
// this.active = this.getDefaultSettings();
// this.active.version = this.version;
// this.set(this.active);
// return this.active;
// }
2018-08-05 23:48:56 +02:00
// if there's no settings saved, return default settings.
2018-08-07 23:31:28 +02:00
if ( ! settings || ( Object . keys ( settings ) . length === 0 && settings . constructor === Object ) ) {
2020-12-23 02:01:52 +01:00
this . logger ? . log (
2021-11-23 01:32:42 +01:00
'info' ,
'settings' ,
'[Settings::init] settings don\'t exist. Using defaults.\n#keys:' ,
settings ? Object . keys ( settings ) . length : 0 ,
'\nsettings:' ,
2019-09-25 07:10:36 +02:00
settings
) ;
2018-08-05 23:48:56 +02:00
this . active = this . getDefaultSettings ( ) ;
2019-09-17 22:14:42 +02:00
this . active . version = this . version ;
await this . save ( ) ;
2018-08-05 23:48:56 +02:00
return this . active ;
}
2019-09-17 22:14:42 +02:00
// if there's settings, set saved object as active settings
this . active = settings ;
// if version number is undefined, we make it defined
// this should only happen on first extension initialization
if ( ! this . active . version ) {
this . active . version = this . version ;
await this . save ( ) ;
return this . active ;
}
2019-08-25 01:52:04 +02:00
2019-09-17 22:14:42 +02:00
// check if extension has been updated. If not, return settings as they were retrieved
if ( this . active . version === this . version ) {
2020-12-23 02:01:52 +01:00
this . logger ? . log ( 'info' , 'settings' , "[Settings::init] extension was saved with current version of ultrawidify. Returning object as-is." ) ;
2018-08-05 23:48:56 +02:00
return this . active ;
}
2019-09-17 22:14:42 +02:00
// This means extension update happened.
// btw fun fact — we can do version rollbacks, which might come in handy while testing
this . active . version = this . version ;
2018-08-05 23:48:56 +02:00
// if extension has been updated, update existing settings with any options added in the
// new version. In addition to that, we remove old keys that are no longer used.
2019-06-05 23:36:47 +02:00
const patched = ObjectCopy . addNew ( settings , this . default ) ;
2020-12-23 02:01:52 +01:00
this . logger ? . log ( 'info' , 'settings' , "[Settings.init] Results from ObjectCopy.addNew()?" , patched , "\n\nSettings from storage" , settings , "\ndefault?" , this . default ) ;
2019-06-05 23:36:47 +02:00
2019-09-17 22:14:42 +02:00
if ( patched ) {
2019-06-05 23:36:47 +02:00
this . active = patched ;
}
2018-08-22 22:34:07 +02:00
2019-07-05 23:45:29 +02:00
// in case settings in previous version contained a fucky wucky, we overwrite existing settings with a patch
2019-09-03 00:28:35 +02:00
this . applySettingsPatches ( oldVersion , ExtensionConfPatch ) ;
2019-07-05 23:45:29 +02:00
2019-09-03 00:28:35 +02:00
// set 'whatsNewChecked' flag to false when updating, always
2019-07-07 15:12:15 +02:00
this . active . whatsNewChecked = false ;
2019-08-25 01:52:04 +02:00
// update settings version to current
2021-11-23 01:32:42 +01:00
this . active . version = this . version ;
2019-07-07 15:12:15 +02:00
2019-09-17 22:14:42 +02:00
await this . save ( ) ;
2018-08-05 23:48:56 +02:00
return this . active ;
}
async get ( ) {
2019-05-10 19:21:17 +02:00
let ret ;
2021-11-23 01:32:42 +01:00
2024-06-03 00:15:23 +02:00
ret = await chrome . storage . local . get ( 'uwSettings' ) ;
2019-05-10 19:21:17 +02:00
2020-12-23 02:01:52 +01:00
this . logger ? . log ( 'info' , 'settings' , 'Got settings:' , ret && ret . uwSettings && JSON . parse ( ret . uwSettings ) ) ;
2019-06-08 03:45:35 +02:00
2019-05-10 19:21:17 +02:00
try {
return JSON . parse ( ret . uwSettings ) ;
} catch ( e ) {
return undefined ;
2018-08-05 23:48:56 +02:00
}
}
2021-02-09 00:37:54 +01:00
async set ( extensionConf , options ? ) {
2019-11-02 01:05:36 +01:00
if ( ! options || ! options . forcePreserveVersion ) {
extensionConf . version = this . version ;
}
2019-09-17 22:14:42 +02:00
2020-12-23 02:01:52 +01:00
this . logger ? . log ( 'info' , 'settings' , "[Settings::set] setting new settings:" , extensionConf )
2018-08-23 01:04:37 +02:00
2024-06-03 00:15:23 +02:00
return chrome . storage . local . set ( { 'uwSettings' : JSON . stringify ( extensionConf ) } ) ;
2018-08-05 23:48:56 +02:00
}
2018-08-07 23:31:28 +02:00
async setActive ( activeSettings ) {
this . active = activeSettings ;
}
async setProp ( prop , value ) {
this . active [ prop ] = value ;
}
2021-02-09 00:37:54 +01:00
async save ( options ? ) {
2019-06-08 03:45:35 +02:00
if ( Debug . debug && Debug . storage ) {
2018-08-23 01:04:37 +02:00
console . log ( "[Settings::save] Saving active settings:" , this . active ) ;
}
2019-10-24 21:13:45 +02:00
this . active . preventReload = undefined ;
2019-11-02 01:05:36 +01:00
await this . set ( this . active , options ) ;
2018-08-07 23:31:28 +02:00
}
2019-10-24 00:44:27 +02:00
async saveWithoutReload() {
2019-10-24 21:13:45 +02:00
this . active . preventReload = true ;
await this . set ( this . active ) ;
2019-10-24 00:44:27 +02:00
}
2019-04-12 00:49:56 +02:00
async rollback() {
this . active = await this . get ( ) ;
}
2018-08-05 23:48:56 +02:00
getDefaultSettings() {
return JSON . parse ( JSON . stringify ( this . default ) ) ;
}
2021-02-09 00:37:54 +01:00
getDefaultOption ( option ? ) {
2019-02-13 23:58:19 +01:00
const allDefault = {
mode : ExtensionMode.Default ,
autoar : ExtensionMode.Default ,
autoarFallback : ExtensionMode.Default ,
2021-02-08 23:04:54 +01:00
stretch : StretchType.Default ,
videoAlignment : VideoAlignmentType.Default ,
2019-02-13 23:58:19 +01:00
} ;
if ( ! option || allDefault [ option ] === undefined ) {
return allDefault ;
}
2021-11-23 01:32:42 +01:00
2019-02-13 23:58:19 +01:00
return allDefault [ option ] ;
}
2021-11-23 01:32:42 +01:00
/ * *
* Gets default site configuration . Only returns essential settings .
* @returns
* /
getDefaultSiteConfiguration() {
return {
type : 'user-added' ,
defaultCrop : {
type : AspectRatioType . Automatic , // AARD is enabled by default
} ,
defaultStretch : {
type : StretchType . NoStretch , // because we aren't uncultured savages
} ,
}
}
2023-01-07 18:57:47 +01:00
getSiteSettings ( site : string = window . location . hostname ) : SiteSettings {
return new SiteSettings ( this , site ) ;
2022-06-14 00:26:06 +02:00
}
2018-08-05 23:48:56 +02:00
}
2018-12-30 23:16:09 +01:00
2018-12-31 01:03:07 +01:00
export default Settings ;
2020-12-03 01:05:39 +01:00
if ( process . env . CHANNEL !== 'stable' ) {
console . info ( "Settings loaded" ) ;
}