Support browser.proxy.onRequest. Fix #1456.

This commit also refactors other implementations and moves them to
dedicated files, using feature detection to select one on runtime.
This commit is contained in:
FelisCatus 2018-07-08 21:34:20 -07:00
parent 7a6cc98ff7
commit 465c98f78a
10 changed files with 345 additions and 229 deletions

View File

@ -179,7 +179,10 @@ if chrome?.storage?.sync or browser?.storage?.sync
sync.enabled = false
sync.transformValue = OmegaTargetCurrent.Options.transformValueForSync
options = new OmegaTargetCurrent.Options(null, storage, state, Log, sync)
proxyImpl = OmegaTargetCurrent.proxy.getProxyImpl(Log)
state.set({proxyImplFeatures: proxyImpl.features})
options = new OmegaTargetCurrent.Options(null, storage, state, Log, sync,
proxyImpl)
options.externalApi = new OmegaTargetCurrent.ExternalApi(options)
options.externalApi.listen()
@ -218,7 +221,7 @@ options._inspect = new OmegaTargetCurrent.Inspect (url, tab) ->
options.setProxyNotControllable(null)
timeout = null
options.watchProxyChange (details) ->
proxyImpl.watchProxyChange (details) ->
return if options.externalApi.disabled
return unless details
notControllableBefore = options.proxyNotControllable()

View File

@ -7,6 +7,7 @@ module.exports =
WebRequestMonitor: require('./web_request_monitor')
Inspect: require('./inspect')
Url: require('url')
proxy: require('./proxy')
for name, value of require('omega-target')
module.exports[name] ?= value

View File

@ -2,9 +2,7 @@ OmegaTarget = require('omega-target')
OmegaPac = OmegaTarget.OmegaPac
Promise = OmegaTarget.Promise
querystring = require('querystring')
chromeApiPromisify = require('./chrome_api').chromeApiPromisify
parseExternalProfile = require('./parse_external_profile')
ProxyAuth = require('./proxy_auth')
WebRequestMonitor = require('./web_request_monitor')
ChromePort = require('./chrome_port')
fetchUrl = require('./fetch_url')
@ -73,202 +71,6 @@ class ChromeOptions extends OmegaTarget.Options
chrome.browserAction.setBadgeText?(text: '')
return
_formatBypassItem: (condition) ->
str = OmegaPac.Conditions.str(condition)
i = str.indexOf(' ')
return str.substr(i + 1)
_fixedProfileConfig: (profile) ->
config = {}
config['mode'] = 'fixed_servers'
rules = {}
protocols = ['proxyForHttp', 'proxyForHttps', 'proxyForFtp']
protocolProxySet = false
for protocol in protocols when profile[protocol]?
rules[protocol] = profile[protocol]
protocolProxySet = true
if profile.fallbackProxy
if profile.fallbackProxy.scheme == 'http'
# Chromium does not allow HTTP proxies in 'fallbackProxy'.
if not protocolProxySet
# Use 'singleProxy' if no proxy is configured for other protocols.
rules['singleProxy'] = profile.fallbackProxy
else
# Try to set the proxies of all possible protocols.
for protocol in protocols
rules[protocol] ?= JSON.parse(JSON.stringify(profile.fallbackProxy))
else
rules['fallbackProxy'] = profile.fallbackProxy
else if not protocolProxySet
config['mode'] = 'direct'
if config['mode'] != 'direct'
rules['bypassList'] = bypassList = []
for condition in profile.bypassList
bypassList.push(@_formatBypassItem(condition))
config['rules'] = rules
return config
_proxyChangeWatchers: null
_proxyChangeListener: null
watchProxyChange: (callback) ->
@_proxyChangeWatchers = []
if not @_proxyChangeListener?
@_proxyChangeListener = (details) =>
for watcher in @_proxyChangeWatchers
watcher(details)
if chrome?.proxy?.settings?.onChange?
chrome.proxy.settings.onChange.addListener @_proxyChangeListener
@_proxyChangeWatchers.push(callback)
applyProfileProxy: (profile, meta) ->
if browser?.proxy?.register? or browser?.proxy?.registerProxyScript?
return @applyProfileProxyScript(profile, meta)
else if chrome?.proxy?.settings?
return @applyProfileProxySettings(profile, meta)
else
ex = new Error('Your browser does not support proxy settings!')
return Promise.reject ex
applyProfileProxySettings: (profile, meta) ->
meta ?= profile
if profile.profileType == 'SystemProfile'
# Clear proxy settings, returning proxy control to Chromium.
return chromeApiPromisify(chrome.proxy.settings, 'clear')({}).then =>
chrome.proxy.settings.get {}, @_proxyChangeListener
return
config = {}
if profile.profileType == 'DirectProfile'
config['mode'] = 'direct'
else if profile.profileType == 'PacProfile'
config['mode'] = 'pac_script'
config['pacScript'] =
if !profile.pacScript || OmegaPac.Profiles.isFileUrl(profile.pacUrl)
url: profile.pacUrl
mandatory: true
else
data: OmegaPac.PacGenerator.ascii(profile.pacScript)
mandatory: true
else if profile.profileType == 'FixedProfile'
config = @_fixedProfileConfig(profile)
else
config['mode'] = 'pac_script'
config['pacScript'] =
data: null
mandatory: true
setPacScript = @pacForProfile(profile).then (script) ->
profileName = OmegaPac.PacGenerator.ascii(JSON.stringify(meta.name))
profileName = profileName.replace(/\*/g, '\\u002a')
profileName = profileName.replace(/\\/g, '\\u002f')
prefix = "/*OmegaProfile*#{profileName}*#{meta.revision}*/"
config['pacScript'].data = prefix + script
return
setPacScript ?= Promise.resolve()
setPacScript.then(=>
@_proxyAuth ?= new ProxyAuth(this)
@_proxyAuth.listen()
@_proxyAuth.setProxies(@_watchingProfiles)
chromeApiPromisify(chrome.proxy.settings, 'set')({value: config})
).then =>
chrome.proxy.settings.get {}, @_proxyChangeListener
return
_proxyScriptUrl: 'js/omega_webext_proxy_script.min.js'
_proxyScriptDisabled: false
applyProfileProxyScript: (profile, state) ->
state = state ? {}
state.currentProfileName = profile.name
if profile.name == ''
state.tempProfile = @_tempProfile
if profile.profileType == 'SystemProfile'
# MOZ: SystemProfile cannot be done now due to lack of "PASS" support.
# https://bugzilla.mozilla.org/show_bug.cgi?id=1319634
# In the mean time, let's just unregister the script.
if browser.proxy.unregister?
browser.proxy.unregister()
else
# Some older browers may not ship with .unregister API.
# In that case, let's just set an invalid script to unregister it.
browser.proxy.registerProxyScript('js/omega_invalid_proxy_script.js')
@_proxyScriptDisabled = true
else
@_proxyScriptState = state
Promise.all([
browser.runtime.getBrowserInfo(),
@_initWebextProxyScript(),
]).then ([info]) =>
if info.vendor == 'Mozilla' and info.buildID < '20170918220054'
# MOZ: Legacy proxy support expects PAC-like string return type.
# TODO(catus): Remove support for string return type.
@log.error(
'Your browser is outdated! SOCKS5 DNS/Auth unsupported! ' +
"Please update your browser ASAP! (Current Build #{info.buildID})")
@_proxyScriptState.useLegacyStringReturn = true
@_proxyScriptStateChanged()
@_proxyAuth ?= new ProxyAuth(this)
@_proxyAuth.listen()
@_proxyAuth.setProxies(@_watchingProfiles)
return Promise.resolve()
_proxyScriptInitialized: false
_proxyScriptState: {}
_initWebextProxyScript: ->
if not @_proxyScriptInitialized
browser.proxy.onProxyError.addListener (err) =>
if err?.message?
if err.message.indexOf('Invalid Proxy Rule: DIRECT') >= 0
# DIRECT cannot be parsed in Mozilla earlier due to a bug. Even
# though it throws, it actually falls back to direct connection
# so it works.
# https://bugzilla.mozilla.org/show_bug.cgi?id=1355198
return
if err.message.indexOf('Return type must be a string') >= 0
# MOZ: Legacy proxy support expects PAC-like string return type.
# TODO(catus): Remove support for string return type.
#
@log.error(
'Your browser is outdated! SOCKS5 DNS/Auth unsupported! ' +
'Please update your browser ASAP!')
@_proxyScriptState.useLegacyStringReturn = true
@_proxyScriptStateChanged()
return
@log.error(err)
browser.runtime.onMessage.addListener (message) =>
return unless message.event == 'proxyScriptLog'
if message.level == 'error'
@log.error(message)
else if message.level == 'warn'
@log.error(message)
else
@log.log(message)
if not @_proxyScriptInitialized or @_proxyScriptDisabled
promise = new Promise (resolve) ->
onMessage = (message) ->
return unless message.event == 'proxyScriptLoaded'
resolve()
browser.runtime.onMessage.removeListener onMessage
return
browser.runtime.onMessage.addListener onMessage
# The API has been renamed to .register but for some old browsers' sake:
if browser.proxy.register?
browser.proxy.register(@_proxyScriptUrl)
else
browser.proxy.registerProxyScript(@_proxyScriptUrl)
@_proxyScriptDisabled = false
else
promise = Promise.resolve()
@_proxyScriptInitialized = true
return promise
_proxyScriptStateChanged: ->
browser.runtime.sendMessage({
event: 'proxyScriptStateChanged'
state: @_proxyScriptState
options: @_options
}, {
toProxyScript: true
})
_quickSwitchInit: false
_quickSwitchHandlerReady: false
_quickSwitchCanEnable: false
@ -478,4 +280,3 @@ class ChromeOptions extends OmegaTarget.Options
}
module.exports = ChromeOptions

View File

@ -0,0 +1,10 @@
ListenerProxyImpl = require('./proxy_impl_listener')
SettingsProxyImpl = require('./proxy_impl_settings')
ScriptProxyImpl = require('./proxy_impl_script')
exports.proxyImpls = [ListenerProxyImpl, ScriptProxyImpl, SettingsProxyImpl]
exports.getProxyImpl = (log) ->
for Impl in exports.proxyImpls
if Impl.isSupported()
return new Impl(log)
throw new Error('Your browser does not support proxy settings!')

View File

@ -3,18 +3,18 @@ OmegaPac = OmegaTarget.OmegaPac
Promise = OmegaTarget.Promise
module.exports = class ProxyAuth
constructor: (options) ->
constructor: (log) ->
@_requests = {}
@options = options
@log = log
listening: false
listen: ->
return if @listening
if not chrome.webRequest
@options.log.error('Proxy auth disabled! No webRequest permission.')
@log.error('Proxy auth disabled! No webRequest permission.')
return
if not chrome.webRequest.onAuthRequired
@options.log.error('Proxy auth disabled! onAuthRequired not available.')
@log.error('Proxy auth disabled! onAuthRequired not available.')
return
chrome.webRequest.onAuthRequired.addListener(
@authHandler.bind(this)
@ -35,9 +35,7 @@ module.exports = class ProxyAuth
setProxies: (profiles) ->
@_proxies = {}
@_fallbacks = []
processProfile = (profile) =>
profile = @options.profile(profile)
return unless profile?.auth
for profile in profiles when profile.auth
for scheme in OmegaPac.Profiles.schemes when profile[scheme.prop]
auth = profile.auth?[scheme.prop]
continue unless auth
@ -59,13 +57,6 @@ module.exports = class ProxyAuth
name: profile.name + '.' + 'all'
})
if Array.isArray(profiles)
for profile in profiles
processProfile(profile)
else
for _, profile of profiles
processProfile(profile)
_proxies: {}
_fallbacks: []
_requests: null
@ -86,7 +77,7 @@ module.exports = class ProxyAuth
proxy = list[req.authTries]
else
proxy = @_fallbacks[req.authTries - listLen]
@options.log.log('ProxyAuth', key, req.authTries, proxy?.name)
@log.log('ProxyAuth', key, req.authTries, proxy?.name)
return {} unless proxy?
req.authTries++

View File

@ -0,0 +1,41 @@
OmegaTarget = require('omega-target')
Promise = OmegaTarget.Promise
ProxyAuth = require('./proxy_auth')
class ProxyImpl
constructor: (log) ->
@log = log
@isSupported: -> false
applyProfile: (profile, meta) -> Promise.reject()
watchProxyChange: (callback) -> null
_profileNotFound: (name) ->
@log.error("Profile #{name} not found! Things may go very, very wrong.")
return OmegaPac.Profiles.create({
name: name
profileType: 'VirtualProfile'
defaultProfileName: 'direct'
})
setProxyAuth: (profile, options) ->
return Promise.try(=>
@_proxyAuth ?= new ProxyAuth(@log)
@_proxyAuth.listen()
referenced_profiles = []
ref_set = OmegaPac.Profiles.allReferenceSet(profile,
options, profileNotFound: @_profileNotFound.bind(this))
for own _, name of ref_set
referenced_profiles.push(OmegaPac.Profiles.byName(name, options))
@_proxyAuth.setProxies(referenced_profiles)
)
getProfilePacScript: (profile, meta, options) ->
meta ?= profile
ast = OmegaPac.PacGenerator.script(options, profile,
profileNotFound: @_profileNotFound.bind(this))
ast = OmegaPac.PacGenerator.compress(ast)
script = OmegaPac.PacGenerator.ascii(ast.print_to_string())
profileName = OmegaPac.PacGenerator.ascii(JSON.stringify(meta.name))
profileName = profileName.replace(/\*/g, '\\u002a')
profileName = profileName.replace(/\\/g, '\\u002f')
prefix = "/*OmegaProfile*#{profileName}*#{meta.revision}*/"
return prefix + script
module.exports = ProxyImpl

View File

@ -0,0 +1,81 @@
OmegaTarget = require('omega-target')
# The browser only accepts native promises as onRequest return values.
# DO NOT USE Bluebird Promises here!
NativePromise = Promise
ProxyImpl = require('./proxy_impl')
class ListenerProxyImpl extends ProxyImpl
@isSupported: -> browser?.proxy?.onRequest?
features: ['fullUrl', 'socks5Auth']
constructor: ->
super(arguments...)
@_optionsReady = new NativePromise (resolve) =>
@_optionsReadyCallback = resolve
# We want to register listeners early so that it can start blocking requests
# when starting the browser & extension, returning correct results later.
@_initRequestListeners()
_initRequestListeners: ->
browser.proxy.onRequest.addListener(@onRequest.bind(this),
{urls: ["<all_urls>"]})
browser.proxy.onError.addListener(@onError.bind(this))
watchProxyChange: (callback) -> null
applyProfile: (profile, state, options) ->
@_options = options
@_profile = profile
@_optionsReadyCallback?()
@_optionsReadyCallback = null
return @setProxyAuth(profile, options)
onRequest: (requestDetails) ->
# The browser only recognizes native promises return values, not Bluebird.
return NativePromise.resolve(@_optionsReady.then(=>
request = OmegaPac.Conditions.requestFromUrl(requestDetails.url)
profile = @_profile
while profile
result = OmegaPac.Profiles.match(profile, request)
if not result
switch profile.profileType
when 'DirectProfile'
return {type: 'direct'}
when 'SystemProfile'
# Returning undefined means using the default proxy from previous.
# https://hg.mozilla.org/mozilla-central/rev/9f0ee2f582a2#l1.337
return undefined
else
throw new Error('Unsupported profile: ' + profile.profileType)
if Array.isArray(result)
proxy = result[2]
auth = result[3]
return @proxyInfo(proxy, auth) if proxy
next = result[0]
else if result.profileName
next = OmegaPac.Profiles.nameAsKey(result.profileName)
else
break
profile = OmegaPac.Profiles.byKey(next, @_options)
throw new Error('Profile not found: ' + next)
))
onError: (error) ->
@log.error(error)
proxyInfo: (proxy, auth) ->
proxyInfo =
type: proxy.scheme
host: proxy.host
port: proxy.port
if proxyInfo.type == 'socks5'
# MOZ: SOCKS5 proxies should be specified as "type": "socks".
# https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/proxy/ProxyInfo
proxyInfo.type = 'socks'
if auth
# Username & password here are only available for SOCKS5.
# https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/proxy/ProxyInfo
# HTTP proxy auth must be handled via webRequest.onAuthRequired.
proxyInfo.username = auth.username
proxyInfo.password = auth.password
if proxyInfo.type == 'socks' or proxyInfo.type == 'socks4'
# Enable SOCKS remote DNS.
# TODO(catus): Maybe allow the users to configure this?
proxyInfo.proxyDNS = true
return [proxyInfo]
module.exports = ListenerProxyImpl

View File

@ -0,0 +1,107 @@
OmegaTarget = require('omega-target')
Promise = OmegaTarget.Promise
ProxyImpl = require('./proxy_impl')
class ScriptProxyImpl extends ProxyImpl
@isSupported: ->
return browser?.proxy?.register? or browser?.proxy?.registerProxyScript?
features: ['socks5Auth']
_proxyScriptUrl: 'js/omega_webext_proxy_script.min.js'
_proxyScriptDisabled: false
_proxyScriptInitialized: false
_proxyScriptState: {}
watchProxyChange: (callback) -> null
applyProfile: (profile, state, options) ->
@log.error(
'Your browser is outdated! Full-URL based matching, etc. unsupported! ' +
"Please update your browser ASAP!")
state = state ? {}
@_options = options
state.currentProfileName = profile.name
if profile.name == ''
state.tempProfile = profile
if profile.profileType == 'SystemProfile'
# MOZ: SystemProfile cannot be done now due to lack of "PASS" support.
# https://bugzilla.mozilla.org/show_bug.cgi?id=1319634
# In the mean time, let's just unregister the script.
if browser.proxy.unregister?
browser.proxy.unregister()
else
# Some older browers may not ship with .unregister API.
# In that case, let's just set an invalid script to unregister it.
browser.proxy.registerProxyScript('js/omega_invalid_proxy_script.js')
@_proxyScriptDisabled = true
else
@_proxyScriptState = state
Promise.all([
browser.runtime.getBrowserInfo(),
@_initWebextProxyScript(),
]).then ([info]) =>
if info.vendor == 'Mozilla' and info.buildID < '20170918220054'
# MOZ: Legacy proxy support expects PAC-like string return type.
# TODO(catus): Remove support for string return type.
@log.error(
'Your browser is outdated! SOCKS5 DNS/Auth unsupported! ' +
"Please update your browser ASAP! (Current Build #{info.buildID})")
@_proxyScriptState.useLegacyStringReturn = true
@_proxyScriptStateChanged()
return @setProxyAuth(profile, options)
_initWebextProxyScript: ->
if not @_proxyScriptInitialized
browser.proxy.onProxyError.addListener (err) =>
if err?.message?
if err.message.indexOf('Invalid Proxy Rule: DIRECT') >= 0
# DIRECT cannot be parsed in Mozilla earlier due to a bug. Even
# though it throws, it actually falls back to direct connection
# so it works.
# https://bugzilla.mozilla.org/show_bug.cgi?id=1355198
return
if err.message.indexOf('Return type must be a string') >= 0
# MOZ: Legacy proxy support expects PAC-like string return type.
# TODO(catus): Remove support for string return type.
#
@log.error(
'Your browser is outdated! SOCKS5 DNS/Auth unsupported! ' +
'Please update your browser ASAP!')
@_proxyScriptState.useLegacyStringReturn = true
@_proxyScriptStateChanged()
return
@log.error(err)
browser.runtime.onMessage.addListener (message) =>
return unless message.event == 'proxyScriptLog'
if message.level == 'error'
@log.error(message)
else if message.level == 'warn'
@log.error(message)
else
@log.log(message)
if not @_proxyScriptInitialized or @_proxyScriptDisabled
promise = new Promise (resolve) ->
onMessage = (message) ->
return unless message.event == 'proxyScriptLoaded'
resolve()
browser.runtime.onMessage.removeListener onMessage
return
browser.runtime.onMessage.addListener onMessage
# The API has been renamed to .register but for some old browsers' sake:
if browser.proxy.register?
browser.proxy.register(@_proxyScriptUrl)
else
browser.proxy.registerProxyScript(@_proxyScriptUrl)
@_proxyScriptDisabled = false
else
promise = Promise.resolve()
@_proxyScriptInitialized = true
return promise
_proxyScriptStateChanged: ->
browser.runtime.sendMessage({
event: 'proxyScriptStateChanged'
state: @_proxyScriptState
options: @_options
}, {
toProxyScript: true
})
module.exports = ScriptProxyImpl

View File

@ -0,0 +1,90 @@
sOmegaTarget = require('omega-target')
Promise = OmegaTarget.Promise
chromeApiPromisify = require('../chrome_api').chromeApiPromisify
ProxyImpl = require('./proxy_impl')
class SettingsProxyImpl extends ProxyImpl
@isSupported: -> chrome?.proxy?.settings?
features: ['fullUrlHttp', 'pacScript', 'watchProxyChange']
applyProfile: (profile, meta, options) ->
meta ?= profile
if profile.profileType == 'SystemProfile'
# Clear proxy settings, returning proxy control to Chromium.
return chromeApiPromisify(chrome.proxy.settings, 'clear')({}).then =>
chrome.proxy.settings.get {}, @_proxyChangeListener
return
config = {}
if profile.profileType == 'DirectProfile'
config['mode'] = 'direct'
else if profile.profileType == 'PacProfile'
config['mode'] = 'pac_script'
config['pacScript'] =
if !profile.pacScript || OmegaPac.Profiles.isFileUrl(profile.pacUrl)
url: profile.pacUrl
mandatory: true
else
data: OmegaPac.PacGenerator.ascii(profile.pacScript)
mandatory: true
else if profile.profileType == 'FixedProfile'
config = @_fixedProfileConfig(profile)
else
config['mode'] = 'pac_script'
config['pacScript'] =
mandatory: true
data: @getProfilePacScript(profile, meta, options)
return @setProxyAuth(profile, options).then(->
return chromeApiPromisify(chrome.proxy.settings, 'set')({value: config})
).then(=>
chrome.proxy.settings.get {}, @_proxyChangeListener
return
)
_fixedProfileConfig: (profile) ->
config = {}
config['mode'] = 'fixed_servers'
rules = {}
protocols = ['proxyForHttp', 'proxyForHttps', 'proxyForFtp']
protocolProxySet = false
for protocol in protocols when profile[protocol]?
rules[protocol] = profile[protocol]
protocolProxySet = true
if profile.fallbackProxy
if profile.fallbackProxy.scheme == 'http'
# Chromium does not allow HTTP proxies in 'fallbackProxy'.
if not protocolProxySet
# Use 'singleProxy' if no proxy is configured for other protocols.
rules['singleProxy'] = profile.fallbackProxy
else
# Try to set the proxies of all possible protocols.
for protocol in protocols
rules[protocol] ?= JSON.parse(JSON.stringify(profile.fallbackProxy))
else
rules['fallbackProxy'] = profile.fallbackProxy
else if not protocolProxySet
config['mode'] = 'direct'
if config['mode'] != 'direct'
rules['bypassList'] = bypassList = []
for condition in profile.bypassList
bypassList.push(@_formatBypassItem(condition))
config['rules'] = rules
return config
_formatBypassItem: (condition) ->
str = OmegaPac.Conditions.str(condition)
i = str.indexOf(' ')
return str.substr(i + 1)
_proxyChangeWatchers: null
_proxyChangeListener: (details) ->
for watcher in (@_proxyChangeWatchers ? [])
watcher(details)
watchProxyChange: (callback) ->
if not @_proxyChangeWatchers?
@_proxyChangeWatchers = []
if chrome?.proxy?.settings?.onChange?
chrome.proxy.settings.onChange.addListener @_proxyChangeListener
@_proxyChangeWatchers.push(callback)
return
module.exports = SettingsProxyImpl

View File

@ -54,7 +54,7 @@ class Options
value = profile
return value
constructor: (options, @_storage, @_state, @log, @sync) ->
constructor: (options, @_storage, @_state, @log, @sync, @proxyImpl) ->
@_options = {}
@_tempProfileRules = {}
@_tempProfileRulesByProfile = {}
@ -566,9 +566,10 @@ class Options
@_watchingProfiles = OmegaPac.Profiles.allReferenceSet(@_tempProfile,
@_options, profileNotFound: @_profileNotFound.bind(this))
applyProxy = @applyProfileProxy(@_tempProfile, profile)
applyProxy = @proxyImpl.applyProfile(@_tempProfile, profile, @_options)
else
applyProxy = @applyProfileProxy(profile)
applyProxy = @proxyImpl.applyProfile(profile, profile, @_options)
return applyProxy if options? and options.update == false
@ -598,16 +599,6 @@ class Options
###
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
# @param {{}=profile} meta The metadata of the profile, like name and revision
# @returns {Promise} A promise which is fulfilled when the proxy is set.
###
applyProfileProxy: (profile, meta) ->
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.