2014-09-20 11:49:04 -04:00
|
|
|
### @module omega-target/options ###
|
|
|
|
Promise = require 'bluebird'
|
|
|
|
Log = require './log'
|
|
|
|
Storage = require './storage'
|
|
|
|
OmegaPac = require 'omega-pac'
|
|
|
|
jsondiffpatch = require 'jsondiffpatch'
|
|
|
|
|
|
|
|
class Options
|
|
|
|
###*
|
|
|
|
# The entire set of options including profiles and other settings.
|
|
|
|
# @typedef OmegaOptions
|
|
|
|
# @type {object}
|
|
|
|
###
|
|
|
|
|
|
|
|
###*
|
|
|
|
# All the options, in a map from key to value.
|
|
|
|
# @type OmegaOptions
|
|
|
|
###
|
|
|
|
_options: {}
|
|
|
|
_storage: null
|
|
|
|
_state: null
|
|
|
|
_currentProfileName: null
|
2015-01-01 07:17:46 -05:00
|
|
|
_revertToProfileName: null
|
2014-09-20 11:49:04 -04:00
|
|
|
_watchingProfiles: {}
|
|
|
|
_tempProfile: null
|
|
|
|
_tempProfileRules: {}
|
2014-10-04 04:35:03 -04:00
|
|
|
_tempProfileRulesByProfile: {}
|
2014-09-20 11:49:04 -04:00
|
|
|
fallbackProfileName: 'system'
|
|
|
|
_isSystem: false
|
|
|
|
debugStr: 'Options'
|
|
|
|
|
|
|
|
ready: null
|
|
|
|
|
|
|
|
ProfileNotExistError: class ProfileNotExistError extends Error
|
|
|
|
constructor: (@profileName) ->
|
|
|
|
super.constructor("Profile #{@profileName} does not exist!")
|
|
|
|
|
2014-11-28 07:45:20 -05:00
|
|
|
NoOptionsError: class NoOptionsError extends Error
|
|
|
|
|
2014-09-20 11:49:04 -04:00
|
|
|
constructor: (@_options, @_storage, @_state, @log) ->
|
|
|
|
@_storage ?= Storage()
|
|
|
|
@_state ?= Storage()
|
|
|
|
@log ?= Log
|
|
|
|
if @_options?
|
|
|
|
@ready = Promise.resolve(@_options)
|
|
|
|
else
|
|
|
|
@ready = @_storage.get(null)
|
|
|
|
@ready = @ready.then((options) =>
|
|
|
|
@upgrade(options).then(([options, changes]) =>
|
|
|
|
modified = {}
|
|
|
|
removed = []
|
|
|
|
for own key, value of changes
|
|
|
|
if typeof value == 'undefined'
|
|
|
|
removed.push(value)
|
|
|
|
else
|
|
|
|
modified[key] = value
|
|
|
|
@_storage.set(modified).then(=>
|
|
|
|
@_storage.remove(removed)
|
|
|
|
).return(options)
|
|
|
|
).catch (ex) =>
|
2014-11-28 07:45:20 -05:00
|
|
|
if not ex instanceof NoOptionsError
|
|
|
|
@log.error(ex.stack)
|
2014-11-27 06:36:51 -05:00
|
|
|
@reset().tap =>
|
2014-11-28 07:23:57 -05:00
|
|
|
@_state.set({'firstRun': 'new', 'web.switchGuide': 'showOnFirstUse'})
|
2014-09-20 11:49:04 -04:00
|
|
|
).then((options) =>
|
|
|
|
@_options = options
|
|
|
|
@_watch()
|
|
|
|
).then(=>
|
|
|
|
if @_options['-startupProfileName']
|
|
|
|
@applyProfile(@_options['-startupProfileName'])
|
|
|
|
else
|
|
|
|
@_state.get({
|
|
|
|
'currentProfileName': @fallbackProfileName
|
|
|
|
'isSystemProfile': false
|
|
|
|
}).then (st) =>
|
|
|
|
if st['isSystemProfile']
|
|
|
|
@applyProfile('system')
|
|
|
|
else
|
|
|
|
@applyProfile(st['currentProfileName'] || @fallbackProfileName)
|
2014-09-23 23:33:09 -04:00
|
|
|
).catch((err) =>
|
|
|
|
if not err instanceof ProfileNotExistError
|
|
|
|
@log.error(err)
|
2014-09-20 11:49:04 -04:00
|
|
|
@applyProfile(@fallbackProfileName)
|
2014-09-23 23:33:09 -04:00
|
|
|
).catch((err) =>
|
|
|
|
@log.error(err)
|
2014-09-20 11:49:04 -04:00
|
|
|
).then => @getAll()
|
|
|
|
|
|
|
|
@ready.then =>
|
2014-11-28 07:45:20 -05:00
|
|
|
@_state.get({'firstRun': ''}).then ({firstRun}) =>
|
|
|
|
if firstRun
|
|
|
|
@onFirstRun(firstRun)
|
|
|
|
|
2014-09-20 11:49:04 -04:00
|
|
|
if @_options['-downloadInterval'] > 0
|
|
|
|
@updateProfile()
|
|
|
|
|
|
|
|
toString: -> "<Options>"
|
|
|
|
|
2014-11-07 02:38:34 -05:00
|
|
|
###*
|
|
|
|
# Return a localized, human-readable description of the given profile.
|
|
|
|
# In base class, this method is not implemented and will always return null.
|
|
|
|
# @param {?{}} profile The profile to print
|
|
|
|
# @returns {string} Description of the profile with details
|
|
|
|
###
|
|
|
|
printProfile: (profile) -> null
|
|
|
|
|
2014-09-20 11:49:04 -04:00
|
|
|
###*
|
|
|
|
# Upgrade options from previous versions.
|
|
|
|
# For now, this method only supports schemaVersion 1 and 2. If so, it upgrades
|
|
|
|
# the options to version 2 (the latest version). Otherwise it rejects.
|
|
|
|
# It is recommended for the derived classes to call super() two times in the
|
|
|
|
# beginning and in the end of the implementation to check the schemaVersion
|
|
|
|
# and to apply future upgrades, respectively.
|
|
|
|
# Example: super(options).catch -> super(doCustomUpgrades(options), changes)
|
|
|
|
# @param {?OmegaOptions} options The legacy options to upgrade
|
|
|
|
# @param {{}={}} changes Previous pending changes to be applied. Default to
|
|
|
|
# an empty dictionary. Please provide this argument when calling super().
|
|
|
|
# @returns {Promise<[OmegaOptions, {}]>} The new options and the changes.
|
|
|
|
###
|
|
|
|
upgrade: (options, changes) ->
|
|
|
|
changes ?= {}
|
|
|
|
version = options?['schemaVersion']
|
|
|
|
if version == 1
|
|
|
|
autoDetectUsed = false
|
|
|
|
OmegaPac.Profiles.each options, (key, profile) ->
|
|
|
|
if not autoDetectUsed
|
|
|
|
refs = OmegaPac.Profiles.directReferenceSet(profile)
|
|
|
|
if refs['+auto_detect']
|
|
|
|
autoDetectUsed = true
|
|
|
|
if autoDetectUsed
|
|
|
|
options['+auto_detect'] = OmegaPac.Profiles.create(
|
|
|
|
name: 'auto_detect'
|
|
|
|
profileType: 'PacProfile'
|
|
|
|
pacUrl: 'http://wpad/wpad.dat'
|
|
|
|
color: '#00cccc'
|
|
|
|
)
|
|
|
|
version = changes['schemaVersion'] = options['schemaVersion'] = 2
|
|
|
|
if version == 2
|
|
|
|
# Current schemaVersion.
|
|
|
|
Promise.resolve([options, changes])
|
|
|
|
else
|
|
|
|
Promise.reject new Error("Invalid schemaVerion #{version}!")
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Reset the options to the given options or initial options.
|
|
|
|
# @param {?OmegaOptions} options The options to set. Defaults to initial.
|
|
|
|
# @returns {Promise<OmegaOptions>} The options just applied
|
|
|
|
###
|
|
|
|
reset: (options) ->
|
|
|
|
@log.method('Options#reset', this, arguments)
|
|
|
|
if not options
|
|
|
|
options = @getDefaultOptions()
|
|
|
|
if typeof options == 'string'
|
|
|
|
if options[0] != '{'
|
|
|
|
try
|
|
|
|
Buffer = require('buffer').Buffer
|
|
|
|
options = new Buffer(options, 'base64').toString('utf8')
|
|
|
|
catch
|
|
|
|
options = null
|
|
|
|
options = try JSON.parse(options)
|
|
|
|
if not options
|
|
|
|
return Promise.reject new Error('Invalid options!')
|
|
|
|
@upgrade(options).then ([opt]) =>
|
|
|
|
@_storage.remove().then(=>
|
|
|
|
@_storage.set(opt)
|
|
|
|
).then -> opt
|
|
|
|
|
2014-11-28 07:45:20 -05:00
|
|
|
###*
|
|
|
|
# Called on the first initialization of options.
|
|
|
|
# @param {reason} reason The value of 'firstRun' in state.
|
|
|
|
###
|
|
|
|
onFirstRun: (reason) -> null
|
|
|
|
|
2014-09-20 11:49:04 -04:00
|
|
|
###*
|
|
|
|
# Return the default options used initially and on resets.
|
|
|
|
# @returns {?OmegaOptions} The default options.
|
|
|
|
###
|
|
|
|
getDefaultOptions: -> require('./default_options')()
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Return all options.
|
|
|
|
# @returns {?OmegaOptions} The options.
|
|
|
|
###
|
|
|
|
getAll: -> @_options
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Get profile by name.
|
|
|
|
# @returns {?{}} The profile, or undefined if no such profile.
|
|
|
|
###
|
|
|
|
profile: (name) -> OmegaPac.Profiles.byName(name, @_options)
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Apply the patch to the current options.
|
|
|
|
# @param {jsondiffpatch} patch The patch to apply
|
|
|
|
# @returns {Promise<OmegaOptions>} The updated options
|
|
|
|
###
|
|
|
|
patch: (patch) ->
|
|
|
|
return unless patch
|
|
|
|
@log.method('Options#patch', this, arguments)
|
|
|
|
|
|
|
|
@_options = jsondiffpatch.patch(@_options, patch)
|
|
|
|
# Only set the keys whose values have changed.
|
|
|
|
changes = {}
|
|
|
|
removed = []
|
|
|
|
for own key, delta of patch
|
|
|
|
if delta.length == 3 and delta[1] == 0 and delta[2] == 0
|
|
|
|
# [previousValue, 0, 0] indicates that the key was removed.
|
|
|
|
changes[key] = undefined
|
|
|
|
else
|
|
|
|
changes[key] = @_options[key]
|
|
|
|
|
|
|
|
@_setOptions(changes)
|
|
|
|
|
|
|
|
_setOptions: (changes, args) =>
|
|
|
|
removed = []
|
|
|
|
checkRev = args?.checkRevision ? false
|
2014-09-20 23:19:16 -04:00
|
|
|
profilesChanged = false
|
2014-09-20 11:49:04 -04:00
|
|
|
currentProfileAffected = false
|
|
|
|
for own key, value of changes
|
|
|
|
if typeof value == 'undefined'
|
|
|
|
delete @_options[key]
|
|
|
|
removed.push(key)
|
2014-09-20 23:19:16 -04:00
|
|
|
if key[0] == '+'
|
|
|
|
profilesChanged = true
|
|
|
|
if key == '+' + @_currentProfileName
|
|
|
|
currentProfileAffected = 'removed'
|
2014-09-20 11:49:04 -04:00
|
|
|
else
|
2014-09-20 23:19:16 -04:00
|
|
|
if key[0] == '+'
|
|
|
|
if checkRev and @_options[key]
|
|
|
|
result = OmegaPac.Revision.compare(@_options[key].revision,
|
|
|
|
value.revision)
|
|
|
|
continue if result >= 0
|
|
|
|
profilesChanged = true
|
2014-09-20 11:49:04 -04:00
|
|
|
@_options[key] = value
|
|
|
|
if not currentProfileAffected and @_watchingProfiles[key]
|
|
|
|
currentProfileAffected = 'changed'
|
|
|
|
switch currentProfileAffected
|
|
|
|
when 'removed'
|
|
|
|
@applyProfile(@fallbackProfileName)
|
|
|
|
when 'changed'
|
|
|
|
@applyProfile(@_currentProfileName)
|
2014-09-20 23:19:16 -04:00
|
|
|
else
|
|
|
|
@_setAvailableProfiles() if profilesChanged
|
2014-09-20 11:49:04 -04:00
|
|
|
if args?.persist ? true
|
|
|
|
for key in removed
|
|
|
|
delete changes[key]
|
|
|
|
@_storage.set(changes).then =>
|
|
|
|
@_storage.remove(removed)
|
|
|
|
return @_options
|
|
|
|
|
|
|
|
_watch: ->
|
|
|
|
handler = (changes) =>
|
|
|
|
if changes
|
2014-10-08 07:00:27 -04:00
|
|
|
@_setOptions(changes, {checkRevision: true, persist: false})
|
2014-09-20 11:49:04 -04:00
|
|
|
else
|
|
|
|
# Initial update.
|
|
|
|
changes = @_options
|
|
|
|
|
|
|
|
refresh = changes['-refreshOnProfileChange']
|
|
|
|
if refresh?
|
|
|
|
@_state.set({'refreshOnProfileChange': refresh})
|
|
|
|
|
|
|
|
if changes['-enableQuickSwitch']? or changes['-quickSwitchProfiles']?
|
2014-12-06 07:34:49 -05:00
|
|
|
@reloadQuickSwitch()
|
2014-09-20 11:49:04 -04:00
|
|
|
if changes['-downloadInterval']?
|
|
|
|
@schedule 'updateProfile', @_options['-downloadInterval'], =>
|
|
|
|
@updateProfile()
|
2014-12-22 06:34:24 -05:00
|
|
|
if changes['-showInspectMenu']? or changes == @_options
|
|
|
|
showMenu = @_options['-showInspectMenu']
|
|
|
|
if not showMenu?
|
|
|
|
showMenu = true
|
|
|
|
@_setOptions({'-showInspectMenu': true}, {persist: true})
|
|
|
|
@setInspect(showMenu: showMenu)
|
2014-09-20 11:49:04 -04:00
|
|
|
|
|
|
|
handler()
|
|
|
|
@_storage.watch null, handler
|
|
|
|
|
2014-12-06 07:34:49 -05:00
|
|
|
###*
|
|
|
|
# Reload the quick switch according to settings.
|
|
|
|
# @returns {Promise} A promise which is fulfilled when the quick switch is set
|
|
|
|
###
|
|
|
|
reloadQuickSwitch: ->
|
|
|
|
if @_options['-enableQuickSwitch']
|
|
|
|
profiles = @_options['-quickSwitchProfiles']
|
|
|
|
if profiles.length >= 2
|
|
|
|
@setQuickSwitch(profiles)
|
|
|
|
else
|
|
|
|
@setQuickSwitch(null)
|
|
|
|
else
|
|
|
|
@setQuickSwitch(null)
|
|
|
|
|
2014-12-22 06:34:24 -05:00
|
|
|
###*
|
|
|
|
# Apply the settings related to element proxy inspection.
|
|
|
|
# In base class, this method is not implemented and will not do anything.
|
|
|
|
# @param {{}} settings
|
|
|
|
# @param {boolean} settings.showMenu Whether to show the menu or not
|
|
|
|
# @returns {Promise} A promise which is fulfilled when the settings apply
|
|
|
|
###
|
|
|
|
setInspect: -> Promise.resolve()
|
|
|
|
|
2014-09-20 11:49:04 -04:00
|
|
|
###*
|
|
|
|
# @callback watchCallback
|
|
|
|
# @param {Object.<string, {}>} changes A map from keys to values.
|
|
|
|
###
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Watch for any changes to the options
|
|
|
|
# @param {watchCallback} callback Called everytime the value of a key changes
|
|
|
|
# @returns {function} Calling the returned function will stop watching.
|
|
|
|
###
|
|
|
|
watch: (callback) -> @_storage.watch null, callback
|
|
|
|
|
2014-12-15 08:10:54 -05:00
|
|
|
_profileNotFound: (name) ->
|
|
|
|
@log.error("Profile #{name} not found! Things may go very, very wrong.")
|
|
|
|
return OmegaPac.Profiles.create({
|
|
|
|
name: name
|
|
|
|
profileType: 'VirtualProfile'
|
|
|
|
defaultProfileName: 'direct'
|
|
|
|
})
|
|
|
|
|
2014-09-20 11:49:04 -04:00
|
|
|
###*
|
|
|
|
# Get PAC script for profile.
|
|
|
|
# @param {?string|Object} profile The name of the profile, or the profile.
|
|
|
|
# @param {bool=false} compress Compress the script if true.
|
2014-11-07 02:38:34 -05:00
|
|
|
# @returns {string} The compiled
|
2014-09-20 11:49:04 -04:00
|
|
|
###
|
|
|
|
pacForProfile: (profile, compress = false) ->
|
2014-12-15 08:10:54 -05:00
|
|
|
ast = OmegaPac.PacGenerator.script(@_options, profile,
|
|
|
|
profileNotFound: @_profileNotFound.bind(this))
|
2014-09-20 11:49:04 -04:00
|
|
|
if compress
|
|
|
|
ast = OmegaPac.PacGenerator.compress(ast)
|
2014-09-23 23:33:09 -04:00
|
|
|
Promise.resolve OmegaPac.PacGenerator.ascii(ast.print_to_string())
|
2014-09-20 11:49:04 -04:00
|
|
|
|
2014-09-20 23:19:16 -04:00
|
|
|
_setAvailableProfiles: ->
|
|
|
|
profile = if @_currentProfileName then @currentProfile() else null
|
|
|
|
profiles = {}
|
|
|
|
currentIncludable = profile && OmegaPac.Profiles.isIncludable(profile)
|
2014-10-25 11:41:39 -04:00
|
|
|
allReferenceSet = null
|
2014-09-20 23:19:16 -04:00
|
|
|
if not profile or not OmegaPac.Profiles.isInclusive(profile)
|
|
|
|
results = []
|
2014-10-25 11:41:39 -04:00
|
|
|
OmegaPac.Profiles.each @_options, (key, p) =>
|
2014-09-20 23:19:16 -04:00
|
|
|
profiles[key] =
|
2014-10-25 11:41:39 -04:00
|
|
|
name: p.name
|
|
|
|
profileType: p.profileType
|
|
|
|
color: p.color
|
2014-11-07 02:38:34 -05:00
|
|
|
desc: @printProfile(p)
|
2014-10-25 11:41:39 -04:00
|
|
|
builtin: if p.builtin then true
|
2014-10-27 10:42:23 -04:00
|
|
|
if p.profileType == 'VirtualProfile'
|
2014-10-25 11:41:39 -04:00
|
|
|
profiles[key].defaultProfileName = p.defaultProfileName
|
|
|
|
if not allReferenceSet?
|
2014-11-07 02:38:34 -05:00
|
|
|
allReferenceSet =
|
|
|
|
if profile
|
2014-12-15 08:10:54 -05:00
|
|
|
OmegaPac.Profiles.allReferenceSet(profile, @_options,
|
|
|
|
profileNotFound: @_profileNotFound.bind(this))
|
2014-11-07 02:38:34 -05:00
|
|
|
else
|
|
|
|
{}
|
2014-10-25 11:41:39 -04:00
|
|
|
if allReferenceSet[key]
|
|
|
|
profiles[key].validResultProfiles =
|
|
|
|
OmegaPac.Profiles.validResultProfilesFor(p, @_options)
|
2014-10-26 00:28:38 -04:00
|
|
|
.map (result) -> result.name
|
2014-10-25 11:41:39 -04:00
|
|
|
if currentIncludable and OmegaPac.Profiles.isIncludable(p)
|
|
|
|
results?.push(p.name)
|
2014-09-20 23:19:16 -04:00
|
|
|
if profile and OmegaPac.Profiles.isInclusive(profile)
|
|
|
|
results = OmegaPac.Profiles.validResultProfilesFor(profile, @_options)
|
|
|
|
results = results.map (profile) -> profile.name
|
|
|
|
@_state.set({
|
|
|
|
'availableProfiles': profiles
|
|
|
|
'validResultProfiles': results
|
|
|
|
})
|
|
|
|
|
2014-09-20 11:49:04 -04:00
|
|
|
###*
|
|
|
|
# Apply the profile by name.
|
|
|
|
# @param {?string} name The name of the profile, or null for default.
|
|
|
|
# @param {?{}} options Some options
|
|
|
|
# @param {bool=true} options.proxy Set proxy for the applied profile if true
|
|
|
|
# @param {bool=false} options.system Whether options is in system mode.
|
|
|
|
# @param {{}=undefined} options.reason will be passed to currentProfileChanged
|
|
|
|
# @returns {Promise} A promise which is fulfilled when the profile is applied.
|
|
|
|
###
|
|
|
|
applyProfile: (name, options) ->
|
|
|
|
@log.method('Options#applyProfile', this, arguments)
|
|
|
|
profile = OmegaPac.Profiles.byName(name, @_options)
|
|
|
|
if not profile
|
|
|
|
return Promise.reject new ProfileNotExistError(name)
|
|
|
|
|
|
|
|
@_currentProfileName = profile.name
|
|
|
|
@_isSystem = options?.system || (profile.profileType == 'SystemProfile')
|
2014-12-15 08:10:54 -05:00
|
|
|
@_watchingProfiles = OmegaPac.Profiles.allReferenceSet(profile, @_options,
|
|
|
|
profileNotFound: @_profileNotFound.bind(this))
|
2014-09-20 11:49:04 -04:00
|
|
|
|
|
|
|
@_state.set({
|
|
|
|
'currentProfileName': @_currentProfileName
|
|
|
|
'isSystemProfile': @_isSystem
|
2014-10-27 10:42:23 -04:00
|
|
|
'currentProfileCanAddRule':
|
|
|
|
profile.rules? and profile.profileType != 'VirtualProfile'
|
2014-09-20 11:49:04 -04:00
|
|
|
})
|
2014-09-20 23:19:16 -04:00
|
|
|
@_setAvailableProfiles()
|
|
|
|
|
2014-09-20 11:49:04 -04:00
|
|
|
@currentProfileChanged(options?.reason)
|
|
|
|
if options? and options.proxy == false
|
|
|
|
return Promise.resolve()
|
|
|
|
if @_tempProfile?
|
|
|
|
if @_tempProfile.defaultProfileName != profile.name
|
|
|
|
@_tempProfile.defaultProfileName = profile.name
|
|
|
|
@_tempProfile.color = profile.color
|
|
|
|
OmegaPac.Profiles.updateRevision(@_tempProfile)
|
2014-10-04 04:35:03 -04:00
|
|
|
|
|
|
|
removedKeys = []
|
|
|
|
for own key, list of @_tempProfileRulesByProfile
|
|
|
|
if not OmegaPac.Profiles.byKey(key, @_options)
|
|
|
|
removedKeys.push(key)
|
|
|
|
for rule in list
|
|
|
|
rule.profileName = null
|
|
|
|
@_tempProfile.rules.splice(@_tempProfile.rules.indexOf(rule), 1)
|
|
|
|
if removedKeys.length > 0
|
|
|
|
for key in removedKeys
|
|
|
|
delete @_tempProfileRulesByProfile[key]
|
|
|
|
OmegaPac.Profiles.updateRevision(@_tempProfile)
|
|
|
|
|
|
|
|
@_watchingProfiles = OmegaPac.Profiles.allReferenceSet(@_tempProfile,
|
2014-12-15 08:10:54 -05:00
|
|
|
@_options, profileNotFound: @_profileNotFound.bind(this))
|
2014-11-07 02:38:34 -05:00
|
|
|
@applyProfileProxy(@_tempProfile, profile)
|
2014-09-20 11:49:04 -04:00
|
|
|
else
|
|
|
|
@applyProfileProxy(profile)
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Get the current applied profile.
|
|
|
|
# @returns {{}} The current profile
|
|
|
|
###
|
|
|
|
currentProfile: ->
|
|
|
|
if @_currentProfileName
|
|
|
|
OmegaPac.Profiles.byName(@_currentProfileName, @_options)
|
|
|
|
else
|
|
|
|
@_externalProfile
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Return true if in system mode.
|
|
|
|
# @returns {boolean} True if system mode is activated
|
|
|
|
###
|
|
|
|
isSystem: -> @_isSystem
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Set proxy settings based on the given profile.
|
|
|
|
# In base class, this method is not implemented and will always reject.
|
|
|
|
# @param {{}} profile The profile to apply
|
2014-11-07 02:38:34 -05:00
|
|
|
# @param {{}=profile} meta The metadata of the profile, like name and revision
|
2014-09-20 11:49:04 -04:00
|
|
|
# @returns {Promise} A promise which is fulfilled when the proxy is set.
|
|
|
|
###
|
2014-11-07 02:38:34 -05:00
|
|
|
applyProfileProxy: (profile, meta) ->
|
2014-09-20 11:49:04 -04:00
|
|
|
Promise.reject new Error('not implemented')
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Called when current profile has changed.
|
|
|
|
# In base class, this method is not implemented and will not do anything.
|
|
|
|
###
|
|
|
|
currentProfileChanged: -> null
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Set or disable the quick switch profiles.
|
|
|
|
# In base class, this method is not implemented and will not do anything.
|
|
|
|
# @param {string[]|null} quickSwitch The profile names, or null to disable
|
|
|
|
# @returns {Promise} A promise which is fulfilled when the quick switch is set
|
|
|
|
###
|
|
|
|
setQuickSwitch: (quickSwitch) ->
|
|
|
|
Promise.resolve()
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Schedule a task that runs every periodInMinutes.
|
|
|
|
# In base class, this method is not implemented and will not do anything.
|
|
|
|
# @param {string} name The name of the schedule. If there is a previous
|
|
|
|
# schedule with the same name, it will be replaced by the new one.
|
|
|
|
# @param {number} periodInMinutes The interval of the schedule
|
|
|
|
# @param {function} callback The callback to call when the task runs
|
|
|
|
# @returns {Promise} A promise which is fulfilled when the schedule is set
|
|
|
|
###
|
|
|
|
schedule: (name, periodInMinutes, callback) ->
|
|
|
|
Promise.resolve()
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Return true if the match result of current profile does not change with URLs
|
|
|
|
# @returns {bool} Whether @match always return the same result for requests
|
|
|
|
###
|
|
|
|
isCurrentProfileStatic: ->
|
|
|
|
return true if not @_currentProfileName
|
|
|
|
return false if @_tempProfile
|
|
|
|
currentProfile = @currentProfile()
|
|
|
|
return false if OmegaPac.Profiles.isInclusive(currentProfile)
|
|
|
|
return true
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Update the profile by name.
|
|
|
|
# @param {(string|string[]|null)} name The name of the profiles,
|
|
|
|
# or null for all.
|
|
|
|
# @param {?bool} opt_bypass_cache Do not read from the cache if true
|
|
|
|
# @returns {Promise<Object.<string,({}|Error)>>} A map from keys to updated
|
|
|
|
# profiles or errors.
|
|
|
|
# A value is an error if `value instanceof Error`. Otherwise the value is an
|
|
|
|
# updated profile.
|
|
|
|
###
|
|
|
|
updateProfile: (name, opt_bypass_cache) ->
|
|
|
|
@log.method('Options#updateProfile', this, arguments)
|
|
|
|
results = {}
|
|
|
|
OmegaPac.Profiles.each @_options, (key, profile) =>
|
|
|
|
return if name? and profile.name != name
|
|
|
|
url = OmegaPac.Profiles.updateUrl(profile)
|
|
|
|
if url
|
|
|
|
results[key] = @fetchUrl(url, opt_bypass_cache).then((data) =>
|
|
|
|
profile = OmegaPac.Profiles.byKey(key, @_options)
|
|
|
|
OmegaPac.Profiles.update(profile, data)
|
2015-01-02 07:28:08 -05:00
|
|
|
profile.lastUpdate = new Date().toISOString()
|
2014-12-16 06:44:39 -05:00
|
|
|
OmegaPac.Profiles.updateRevision(profile)
|
2014-09-20 11:49:04 -04:00
|
|
|
changes = {}
|
|
|
|
changes[key] = profile
|
|
|
|
@_setOptions(changes).return(profile)
|
|
|
|
).catch (reason) ->
|
|
|
|
if reason instanceof Error then reason else new Error(reason)
|
|
|
|
|
|
|
|
Promise.props(results)
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Make an HTTP GET request to fetch the content of the url.
|
|
|
|
# In base class, this method is not implemented and will always reject.
|
|
|
|
# @param {string} url The name of the profiles,
|
|
|
|
# @param {?bool} opt_bypass_cache Do not read from the cache if true
|
|
|
|
# @returns {Promise<String>} The text content fetched from the url
|
|
|
|
###
|
|
|
|
fetchUrl: (url, opt_bypass_cache) ->
|
|
|
|
Promise.reject new Error('not implemented')
|
|
|
|
|
2014-10-27 09:16:57 -04:00
|
|
|
_replaceRefChanges: (fromName, toName, changes) ->
|
|
|
|
changes ?= {}
|
|
|
|
|
|
|
|
OmegaPac.Profiles.each @_options, (key, p) ->
|
|
|
|
return if p.name == fromName or p.name == toName
|
|
|
|
if OmegaPac.Profiles.replaceRef(p, fromName, toName)
|
|
|
|
OmegaPac.Profiles.updateRevision(p)
|
|
|
|
changes[OmegaPac.Profiles.nameAsKey(p)] = p
|
|
|
|
|
|
|
|
if @_options['-startupProfileName'] == fromName
|
|
|
|
changes['-startupProfileName'] = toName
|
|
|
|
quickSwitch = @_options['-quickSwitchProfiles']
|
|
|
|
for i in [0...quickSwitch.length]
|
|
|
|
if quickSwitch[i] == fromName
|
|
|
|
quickSwitch[i] = toName
|
|
|
|
changes['-quickSwitchProfiles'] = quickSwitch
|
|
|
|
|
|
|
|
return changes
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Replace all references of profile fromName to toName.
|
|
|
|
# @param {String} fromName The original profile name
|
|
|
|
# @param {String} toname The target profile name
|
|
|
|
# @returns {Promise<OmegaOptions>} The updated options
|
|
|
|
###
|
|
|
|
replaceRef: (fromName, toName) ->
|
|
|
|
@log.method('Options#replaceRef', this, arguments)
|
|
|
|
profile = OmegaPac.Profiles.byName(fromName, @_options)
|
|
|
|
if not profile
|
|
|
|
return Promise.reject new ProfileNotExistError(fromName)
|
|
|
|
|
|
|
|
changes = @_replaceRefChanges(fromName, toName)
|
|
|
|
for own key, value of changes
|
|
|
|
@_options[key] = value
|
|
|
|
|
|
|
|
fromKey = OmegaPac.Profiles.nameAsKey(fromName)
|
|
|
|
if @_watchingProfiles[fromKey]
|
|
|
|
if @_currentProfileName == fromName
|
|
|
|
@_currentProfileName = toName
|
|
|
|
@applyProfile(@_currentProfileName)
|
|
|
|
|
|
|
|
@_setOptions(changes)
|
|
|
|
|
2014-09-20 11:49:04 -04:00
|
|
|
###*
|
|
|
|
# Rename a profile and update references and options
|
|
|
|
# @param {String} fromName The original profile name
|
|
|
|
# @param {String} toname The target profile name
|
|
|
|
# @returns {Promise<OmegaOptions>} The updated options
|
|
|
|
###
|
|
|
|
renameProfile: (fromName, toName) ->
|
|
|
|
@log.method('Options#renameProfile', this, arguments)
|
|
|
|
if OmegaPac.Profiles.byName(toName, @_options)
|
|
|
|
return Promise.reject new Error("Target name #{name} already taken!")
|
|
|
|
profile = OmegaPac.Profiles.byName(fromName, @_options)
|
|
|
|
if not profile
|
2014-10-27 09:16:57 -04:00
|
|
|
return Promise.reject new ProfileNotExistError(fromName)
|
2014-09-20 11:49:04 -04:00
|
|
|
|
|
|
|
profile.name = toName
|
|
|
|
changes = {}
|
|
|
|
changes[OmegaPac.Profiles.nameAsKey(profile)] = profile
|
|
|
|
|
2014-10-27 09:16:57 -04:00
|
|
|
@_replaceRefChanges(fromName, toName, changes)
|
2014-09-20 11:49:04 -04:00
|
|
|
for own key, value of changes
|
|
|
|
@_options[key] = value
|
|
|
|
|
|
|
|
fromKey = OmegaPac.Profiles.nameAsKey(fromName)
|
|
|
|
changes[fromKey] = undefined
|
|
|
|
delete @_options[fromKey]
|
|
|
|
|
|
|
|
if @_watchingProfiles[fromKey]
|
|
|
|
if @_currentProfileName == fromName
|
|
|
|
@_currentProfileName = toName
|
|
|
|
@applyProfile(@_currentProfileName)
|
|
|
|
|
|
|
|
@_setOptions(changes)
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Add a temp rule.
|
|
|
|
# @param {String} domain The domain for the temp rule.
|
|
|
|
# @param {String} profileName The profile to apply for the domain.
|
|
|
|
# @returns {Promise} A promise which is fulfilled when the rule is applied.
|
|
|
|
###
|
|
|
|
addTempRule: (domain, profileName) ->
|
|
|
|
@log.method('Options#addTempRule', this, arguments)
|
2014-10-25 11:41:39 -04:00
|
|
|
return Promise.resolve() if not @_currentProfileName
|
2014-09-20 11:49:04 -04:00
|
|
|
profile = OmegaPac.Profiles.byName(profileName, @_options)
|
|
|
|
if not profile
|
|
|
|
return Promise.reject new ProfileNotExistError(profileName)
|
|
|
|
if not @_tempProfile?
|
|
|
|
@_tempProfile = OmegaPac.Profiles.create('', 'SwitchProfile')
|
|
|
|
currentProfile = @currentProfile()
|
|
|
|
@_tempProfile.color = currentProfile.color
|
|
|
|
@_tempProfile.defaultProfileName = currentProfile.name
|
|
|
|
|
|
|
|
changed = false
|
|
|
|
rule = @_tempProfileRules[domain]
|
2014-10-04 04:35:03 -04:00
|
|
|
if rule and rule.profileName
|
2014-09-20 11:49:04 -04:00
|
|
|
if rule.profileName != profileName
|
2014-10-04 04:35:03 -04:00
|
|
|
key = OmegaPac.Profiles.nameAsKey(rule.profileName)
|
|
|
|
list = @_tempProfileRulesByProfile[key]
|
|
|
|
list.splice(list.indexOf(rule), 1)
|
|
|
|
|
2014-09-20 11:49:04 -04:00
|
|
|
rule.profileName = profileName
|
|
|
|
changed = true
|
|
|
|
else
|
|
|
|
rule =
|
|
|
|
condition:
|
|
|
|
conditionType: 'HostWildcardCondition'
|
|
|
|
pattern: '*.' + domain
|
|
|
|
profileName: profileName
|
|
|
|
isTempRule: true
|
|
|
|
@_tempProfile.rules.push(rule)
|
|
|
|
@_tempProfileRules[domain] = rule
|
|
|
|
changed = true
|
2014-10-04 04:35:03 -04:00
|
|
|
|
|
|
|
key = OmegaPac.Profiles.nameAsKey(profileName)
|
|
|
|
rulesByProfile = @_tempProfileRulesByProfile[key]
|
|
|
|
if not rulesByProfile?
|
|
|
|
rulesByProfile = @_tempProfileRulesByProfile[key] = []
|
|
|
|
rulesByProfile.push(rule)
|
|
|
|
|
2014-09-20 11:49:04 -04:00
|
|
|
if changed
|
|
|
|
OmegaPac.Profiles.updateRevision(@_tempProfile)
|
|
|
|
@applyProfile(@_currentProfileName)
|
|
|
|
else
|
|
|
|
Promise.resolve()
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Find a temp rule by domain.
|
|
|
|
# @param {String} domain The domain of the temp rule.
|
|
|
|
# @returns {Promise<?String>} The profile name for the domain, or null if such
|
|
|
|
# rule does not exist.
|
|
|
|
###
|
|
|
|
queryTempRule: (domain) ->
|
|
|
|
rule = @_tempProfileRules[domain]
|
|
|
|
if rule
|
2014-10-04 04:35:03 -04:00
|
|
|
if rule.profileName
|
|
|
|
return rule.profileName
|
|
|
|
else
|
|
|
|
delete @_tempProfileRules[domain]
|
|
|
|
return null
|
2014-09-20 11:49:04 -04:00
|
|
|
|
|
|
|
###*
|
|
|
|
# Add a condition to the current active switch profile.
|
|
|
|
# @param {Object.<String,{}>} cond The condition to add
|
2014-10-25 11:41:39 -04:00
|
|
|
# @param {string>} profileName The name of the result profile of the rule.
|
2014-09-20 11:49:04 -04:00
|
|
|
# @returns {Promise} A promise which is fulfilled when the condition is saved.
|
|
|
|
###
|
|
|
|
addCondition: (condition, profileName) ->
|
|
|
|
@log.method('Options#addCondition', this, arguments)
|
2014-10-25 11:41:39 -04:00
|
|
|
return Promise.resolve() if not @_currentProfileName
|
2014-09-20 11:49:04 -04:00
|
|
|
profile = OmegaPac.Profiles.byName(@_currentProfileName, @_options)
|
|
|
|
if not profile?.rules?
|
|
|
|
return Promise.reject new Error(
|
|
|
|
"Cannot add condition to Profile #{@profile.name} (@{profile.type})")
|
2014-10-25 11:41:39 -04:00
|
|
|
target = OmegaPac.Profiles.byName(profileName, @_options)
|
|
|
|
if not target?
|
|
|
|
return Promise.reject new ProfileNotExistError(profileName)
|
2014-09-20 11:49:04 -04:00
|
|
|
# Try to remove rules with the same condition first.
|
|
|
|
tag = OmegaPac.Conditions.tag(condition)
|
|
|
|
for i in [0...profile.rules.length]
|
|
|
|
if OmegaPac.Conditions.tag(profile.rules[i].condition) == tag
|
|
|
|
profile.rules.splice(i, 1)
|
|
|
|
break
|
|
|
|
|
|
|
|
# Add the new rule to the beginning so that it won't be shadowed by others.
|
|
|
|
profile.rules.unshift({
|
|
|
|
condition: condition
|
|
|
|
profileName: profileName
|
|
|
|
})
|
|
|
|
OmegaPac.Profiles.updateRevision(profile)
|
|
|
|
changes = {}
|
|
|
|
changes[OmegaPac.Profiles.nameAsKey(profile)] = profile
|
|
|
|
@_setOptions(changes)
|
|
|
|
|
2014-10-25 11:41:39 -04:00
|
|
|
###*
|
|
|
|
# Set the defaultProfileName of the profile.
|
|
|
|
# @param {string>} profileName The name of the profile to modify.
|
|
|
|
# @param {string>} defaultProfileName The defaultProfileName to set.
|
|
|
|
# @returns {Promise} A promise which is fulfilled when the profile is saved.
|
|
|
|
###
|
|
|
|
setDefaultProfile: (profileName, defaultProfileName) ->
|
|
|
|
@log.method('Options#setDefaultProfile', this, arguments)
|
|
|
|
profile = OmegaPac.Profiles.byName(profileName, @_options)
|
|
|
|
if not profile?
|
|
|
|
return Promise.reject new ProfileNotExistError(profileName)
|
|
|
|
else if not profile.defaultProfileName?
|
|
|
|
return Promise.reject new Error("Profile #{@profile.name} " +
|
|
|
|
"(@{profile.type}) does not have defaultProfileName!")
|
|
|
|
target = OmegaPac.Profiles.byName(defaultProfileName, @_options)
|
|
|
|
if not target?
|
|
|
|
return Promise.reject new ProfileNotExistError(defaultProfileName)
|
|
|
|
|
|
|
|
profile.defaultProfileName = defaultProfileName
|
|
|
|
OmegaPac.Profiles.updateRevision(profile)
|
|
|
|
changes = {}
|
|
|
|
changes[OmegaPac.Profiles.nameAsKey(profile)] = profile
|
|
|
|
@_setOptions(changes)
|
|
|
|
|
2014-09-20 11:49:04 -04:00
|
|
|
###*
|
|
|
|
# Add a profile to the options
|
|
|
|
# @param {{}} profile The profile to create
|
|
|
|
# @returns {Promise<{}>} The saved profile
|
|
|
|
###
|
|
|
|
addProfile: (profile) ->
|
|
|
|
@log.method('Options#addProfile', this, arguments)
|
|
|
|
if OmegaPac.Profiles.byName(profile.name, @_options)
|
|
|
|
return Promise.reject(
|
|
|
|
new Error("Target name #{profile.name} already taken!"))
|
|
|
|
else
|
|
|
|
changes = {}
|
|
|
|
changes[OmegaPac.Profiles.nameAsKey(profile)] = profile
|
|
|
|
@_setOptions(changes)
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Get the matching results of a request
|
|
|
|
# @param {{}} request The request to test
|
|
|
|
# @returns {Promise<{profile: {}, results: {}[]}>} The last matched profile
|
|
|
|
# and the matching details
|
|
|
|
###
|
|
|
|
matchProfile: (request) ->
|
|
|
|
if not @_currentProfileName
|
2014-10-25 11:41:39 -04:00
|
|
|
return Promise.resolve({profile: @_externalProfile, results: []})
|
2014-09-20 11:49:04 -04:00
|
|
|
results = []
|
|
|
|
profile = @_tempProfile
|
|
|
|
profile ?= OmegaPac.Profiles.byName(@_currentProfileName, @_options)
|
|
|
|
while profile
|
|
|
|
lastProfile = profile
|
|
|
|
result = OmegaPac.Profiles.match(profile, request)
|
|
|
|
break unless result?
|
|
|
|
results.push(result)
|
|
|
|
if Array.isArray(result)
|
|
|
|
next = result[0]
|
|
|
|
else if result.profileName
|
|
|
|
next = OmegaPac.Profiles.nameAsKey(result.profileName)
|
|
|
|
else
|
|
|
|
break
|
|
|
|
profile = OmegaPac.Profiles.byKey(next, @_options)
|
|
|
|
Promise.resolve(profile: lastProfile, results: results)
|
|
|
|
|
|
|
|
###*
|
|
|
|
# Notify Options that the proxy settings are set externally.
|
|
|
|
# @param {{}} profile The external profile
|
|
|
|
# @param {?{}} args Extra arguments
|
|
|
|
# @param {boolean=false} args.noRevert If true, do not revert changes.
|
2014-11-07 02:38:34 -05:00
|
|
|
# @param {boolean=false} args.internal If true, treat the profile change as
|
|
|
|
# caused by the options itself instead of external reasons.
|
2014-09-20 11:49:04 -04:00
|
|
|
# @returns {Promise} A promise which is fulfilled when the profile is set
|
|
|
|
###
|
|
|
|
setExternalProfile: (profile, args) ->
|
2015-01-01 07:17:46 -05:00
|
|
|
if @_options['-revertProxyChanges'] and not @_isSystem
|
2014-09-20 11:49:04 -04:00
|
|
|
if profile.name != @_currentProfileName and @_currentProfileName
|
2015-01-01 07:17:46 -05:00
|
|
|
if not args?.noRevert
|
|
|
|
@applyProfile(@_revertToProfileName)
|
|
|
|
@_revertToProfileName = null
|
2014-09-20 11:49:04 -04:00
|
|
|
return
|
2015-01-01 07:17:46 -05:00
|
|
|
else
|
|
|
|
@_revertToProfileName ?= @_currentProfileName
|
2014-09-20 11:49:04 -04:00
|
|
|
p = OmegaPac.Profiles.byName(profile.name, @_options)
|
|
|
|
if p
|
2014-11-07 02:38:34 -05:00
|
|
|
if args?.internal
|
|
|
|
@applyProfile(p.name, {proxy: false})
|
|
|
|
else
|
|
|
|
@applyProfile(p.name,
|
|
|
|
{proxy: false, system: @_isSystem, reason: 'external'})
|
2014-09-20 11:49:04 -04:00
|
|
|
else
|
|
|
|
@_currentProfileName = null
|
|
|
|
@_externalProfile = profile
|
|
|
|
profile.color ?= '#49afcd'
|
|
|
|
@_state.set({
|
|
|
|
'currentProfileName': ''
|
|
|
|
'externalProfile': profile
|
|
|
|
'validResultProfiles': []
|
|
|
|
'currentProfileCanAddRule': false
|
|
|
|
})
|
|
|
|
@currentProfileChanged('external')
|
|
|
|
return
|
|
|
|
|
|
|
|
module.exports = Options
|