<commit_before>/*
 * Copyright 2011 Google Inc. All Rights Reserved.

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Constructor
 *
 * @param {String} adapterName  name of the adapter to use for this OAuth 2
 * @param {Object} config Containing clientId, clientSecret and apiScope
 * @param {String} config Alternatively, OAuth2.FINISH for the finish flow
 */
var OAuth2 = function(adapterName, config) {
  this.adapterName = adapterName;
  var that = this;
  OAuth2.loadAdapter(adapterName, function() {
    that.adapter = OAuth2.adapters[adapterName];
    if (config == OAuth2.FINISH) {
      that.finishAuth();
    } else if (config) {
      that.updateLocalStorage();

      var data = that.get();
      data.clientId = config.client_id;
      data.clientSecret = config.client_secret;
      data.apiScope = config.api_scope;
      that.setSource(data);
    }
  });
};

/**
 * Pass instead of config to specify the finishing OAuth flow.
 */
OAuth2.FINISH = 'finish';

/**
 * OAuth 2.0 endpoint adapters known to the library
 */
OAuth2.adapters = {};
OAuth2.adapterReverse = localStorage.oauth2_adapterReverse &&
    JSON.parse(localStorage.oauth2_adapterReverse) || {};
// Update the persisted adapterReverse in localStorage.
if (localStorage.adapterReverse) {
  OAuth2.adapterReverse = JSON.parse(localStorage.adapterReverse);
  delete localStorage.adapterReverse;
}

/**
 * Consolidates the data stored in localStorage on the current adapter in to
 * a single JSON object.
 * The update should only ever happen once per adapter and will delete the old
 * obsolete entries in localStorage after copying their values.
 */
OAuth2.prototype.updateLocalStorage = function() {
  // Check if update is even required.
  if (this.getSource()) {
    return;
  }
  var data = {};
  var variables = [
    'accessToken', 'accessTokenDate', 'apiScope', 'clientId', 'clientSecret',
    'expiresIn', 'refreshToken'
  ];
  // Check if a variable has already been persisted and then copy them.
  var key;
  for (var i = 0; i < variables.length; i++) {
    key = this.adapterName + '_' + variables[i];
    if (localStorage.hasOwnProperty(key)) {
      data[variables[i]] = localStorage[key];
      delete localStorage[key];
    }
  }
  // Persist the new JSON object in localStorage.
  this.setSource(data);
};

/**
 * Opens up an authorization popup window. This starts the OAuth 2.0 flow.
 *
 * @param {Function} callback Method to call when the user finished auth.
 */
OAuth2.prototype.openAuthorizationCodePopup = function(callback, flg) {
  // Store a reference to the callback so that the newly opened window can call
  // it later.
  window['oauth-callback'] = callback;

  // Create a new tab with the OAuth 2.0 prompt
  var crwWidth = 0;
  var crwHeight = 0;
  var crwLeft = 0;
  var crwTop = 0;

  if (flg) {
    crwWidth = 500;
    crwHeight = 500;
    crwLeft = (screen.availWidth - crwWidth) / 2;
    crwTop = (screen.availHeight - crwHeight) / 2;
  } else {
    crwWidth = 1;
    crwHeight = 1;
    crwLeft = 0;
    crwTop = 0;
  }

  chrome.windows.create({
      width: crwWidth,
      height: crwHeight,
      left: crwLeft,
      top: crwTop,
      focused: flg,
      url: this.adapter.authorizationCodeURL(this.getConfig()),
      type: 'popup'
  }, function(window) {    // 1. user grants permission for the application to access the OAuth 2.0
    // endpoint
    // 2. the endpoint redirects to the redirect URL.
    // 3. the extension injects a script into that redirect URL
    // 4. the injected script redirects back to oauth2.html, also passing
    // the redirect URL
    // 5. oauth2.html uses redirect URL to know what OAuth 2.0 flow to finish
    // (if there are multiple OAuth 2.0 adapters)
    // 6. Finally, the flow is finished and client code can call
    // myAuth.getAccessToken() to get a valid access token.
  });
};

/**
 * Gets access and refresh (if provided by endpoint) tokens
 *
 * @param {String} authorizationCode Retrieved from the first step in the process
 * @param {Function} callback Called back with 3 params:
 *                            access token, refresh token and expiry time
 */
OAuth2.prototype.getAccessAndRefreshTokens = function(authorizationCode, callback) {
  var that = this;
  // Make an XHR to get the token
  var xhr = new XMLHttpRequest();
  xhr.addEventListener('readystatechange', function(event) {
    if (xhr.readyState == 4) {
      if (xhr.status == 200) {
        // Callback with the data (incl. tokens).
        callback(that.adapter.parseAccessToken(xhr.responseText));
      }
    }
  });

  var method = that.adapter.accessTokenMethod();
  var items = that.adapter.accessTokenParams(authorizationCode, that.getConfig());
  var key = null;
  if (method == 'POST') {
    var formData = new FormData();
    for (key in items) {
      formData.append(key, items[key]);
    }
    xhr.open(method, that.adapter.accessTokenURL(), true);
    xhr.send(formData);
  } else if (method == 'GET') {
    var url = that.adapter.accessTokenURL();
    var params = '?';
    for (key in items) {
      params += encodeURIComponent(key) + '=' +
                encodeURIComponent(items[key]) + '&';
    }
    xhr.open(method, url + params, true);
    xhr.send();
  } else {
    throw method + ' is an unknown method';
  }
};

/**
 * Refreshes the access token using the currently stored refresh token
 * Note: this only happens for the Google adapter since all other OAuth 2.0
 * endpoints don't implement refresh tokens.
 *
 * @param {String} refreshToken A valid refresh token
 * @param {Function} callback On success, called with access token and expiry time and refresh token
 */
OAuth2.prototype.refreshAccessToken = function(refreshToken, callback) {
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function(event) {
    if (xhr.readyState == 4) {
      if(xhr.status == 200) {
        console.log(xhr.responseText);
        // Parse response with JSON
        var obj = JSON.parse(xhr.responseText);
        // Callback with the tokens
        callback(obj.access_token, obj.expires_in, obj.refresh_token);
      }
    }
  };

  var data = this.get();
  var formData = new FormData();
  formData.append('client_id', data.clientId);
  formData.append('client_secret', data.clientSecret);
  formData.append('refresh_token', refreshToken);
  formData.append('grant_type', 'refresh_token');
  xhr.open('POST', this.adapter.accessTokenURL(), true);
  xhr.send(formData);
};

/**
 * Extracts authorizationCode from the URL and makes a request to the last
 * leg of the OAuth 2.0 process.
*/
OAuth2.prototype.finishAuth = function() {
  var authorizationCode = null;
  var that = this;

  // Loop through existing extension views and excute any stored callbacks.
  function callback(error) {
    var views = chrome.extension.getViews();
    for (var i = 0, view; view = views[i]; i++) {
      if (view['oauth-callback']) {
        view['oauth-callback'](error);
        // TODO: Decide whether it's worth it to scope the callback or not.
        // Currently, every provider will share the same callback address but
        // that's not such a big deal assuming that they check to see whether
        // the token exists instead of blindly trusting that it does.
      }
    }

    // Once we get here, close the current tab and we're good to go.
    // The following works around bug: crbug.com/84201
    chrome.tabs.getSelected(null, function(tab){
      chrome.windows.remove(tab.windowId, function (){});
    });
  }

  try {
    authorizationCode = that.adapter.parseAuthorizationCode(window.location.href);
    console.log(authorizationCode);
  } catch (e) {
    console.error(e);
    callback(e);
  }

  that.getAccessAndRefreshTokens(authorizationCode, function(response) {
    var data = that.get();
    data.accessTokenDate = new Date().valueOf();

    // Set all data returned by the OAuth 2.0 provider.
    for (var name in response) {
      if (response.hasOwnProperty(name) && response[name]) {
        data[name] = response[name];
      }
    }

    that.setSource(data);
    callback();
  });
};

/**
 * @return True iff the current access token has expired
 */
OAuth2.prototype.isAccessTokenExpired = function() {
  var data = this.get();
  return (new Date().valueOf() - data.accessTokenDate) > data.expiresIn * 1000;
};

/**
 * Get the persisted adapter data in localStorage. Optionally, provide a
 * property name to only retrieve its value.
 *
 * @param {String} [name] The name of the property to be retrieved.
 * @return The data object or property value if name was specified.
 */
OAuth2.prototype.get = function(name) {
  var src = this.getSource();
  var obj = src ? JSON.parse(src) : {};
  return name ? obj[name] : obj;
};

/**
 * Set the value of a named property on the persisted adapter data in
 * localStorage.
 *
 * @param {String} name The name of the property to change.
 * @param value The value to be set.
 */
OAuth2.prototype.set = function(name, value) {
  var obj = this.get();
  obj[name] = value;
  this.setSource(obj);
};

/**
 * Clear all persisted adapter data in localStorage. Optionally, provide a
 * property name to only clear its value.
 *
 * @param {String} [name] The name of the property to clear.
 */
OAuth2.prototype.clear = function(name) {
  if (name) {
    var obj = this.get();
    delete obj[name];
    this.setSource(obj);
  } else {
    delete localStorage['oauth2_' + this.adapterName];
  }
};

/**
 * Get the JSON string for the object stored in localStorage.
 *
 * @return {String} The source JSON string.
 */
OAuth2.prototype.getSource = function() {
  return localStorage['oauth2_' + this.adapterName];
};

/**
 * Set the JSON string for the object stored in localStorage.
 *
 * @param {Object|String} source The new JSON string/object to be set.
 */
OAuth2.prototype.setSource = function(source) {
  if (!source) {
    return;
  }
  if (typeof source !== 'string') {
    source = JSON.stringify(source);
  }
  localStorage['oauth2_' + this.adapterName] = source;
};

/**
 * Get the configuration parameters to be passed to the adapter.
 *
 * @returns {Object} Contains clientId, clientSecret and apiScope.
 */
OAuth2.prototype.getConfig = function() {
  var data = this.get();
  return {
    clientId: data.clientId,
    clientSecret: data.clientSecret,
    apiScope: data.apiScope
  };
};

/***********************************
 *
 * STATIC ADAPTER RELATED METHODS
 *
 ***********************************/

/**
 * Loads an OAuth 2.0 adapter and calls back when it's loaded
 *
 * @param adapterName {String} The name of the JS file
 * @param callback {Function} Called as soon as the adapter has been loaded
 */
OAuth2.loadAdapter = function(adapterName, callback) {
  // If it's already loaded, don't load it again
  if (OAuth2.adapters[adapterName]) {
    callback();
    return;
  }
  var head = document.querySelector('head');
  var script = document.createElement('script');
  script.type = 'text/javascript';
  script.src = '/src/lib/oauth2/adapters/' + adapterName + '.js';
  script.addEventListener('load', function() {
    callback();
  });
  head.appendChild(script);
};

/**
 * Registers an adapter with the library. This call is used by each adapter
 *
 * @param {String} name The adapter name
 * @param {Object} impl The adapter implementation
 *
 * @throws {String} If the specified adapter is invalid
 */
OAuth2.adapter = function(name, impl) {
  var implementing = 'authorizationCodeURL redirectURL accessTokenURL ' +
    'accessTokenMethod accessTokenParams accessToken';

  // Check for missing methods
  implementing.split(' ').forEach(function(method, index) {
    if (!method in impl) {
      throw 'Invalid adapter! Missing method: ' + method;
    }
  });

  // Save the adapter in the adapter registry
  OAuth2.adapters[name] = impl;
  // Make an entry in the adapter lookup table
  OAuth2.adapterReverse[impl.redirectURL()] = name;
  // Store the the adapter lookup table in localStorage
  localStorage.oauth2_adapterReverse = JSON.stringify(OAuth2.adapterReverse);
};

/**
 * Looks up the adapter name based on the redirect URL. Used by oauth2.html
 * in the second part of the OAuth 2.0 flow.
 *
 * @param {String} url The url that called oauth2.html
 * @return The adapter for the current page
 */
OAuth2.lookupAdapterName = function(url) {
  var adapterReverse = JSON.parse(localStorage.oauth2_adapterReverse);
  return adapterReverse[url];
};

/***********************************
 *
 * PUBLIC API
 *
 ***********************************/

/**
 * Authorizes the OAuth authenticator instance.
 *
 * @param {Function} callback Tries to callback when auth is successful
 *                            Note: does not callback if grant popup required
 */
OAuth2.prototype.authorize = function(callback, flg) {
  var that = this;
  OAuth2.loadAdapter(that.adapterName, function() {
    that.adapter = OAuth2.adapters[that.adapterName];
    var data = that.get();
    if (!data.accessToken) {
      // There's no access token yet. Start the authorizationCode flow
      that.openAuthorizationCodePopup(callback, flg);
    } else if (that.isAccessTokenExpired()) {
      // There's an existing access token but it's expired
      if (data.refreshToken) {
        that.refreshAccessToken(data.refreshToken, function(at, exp, re) {
          var newData = that.get();
          newData.accessTokenDate = new Date().valueOf();
          newData.accessToken = at;
          newData.expiresIn = exp;
          newData.refreshToken = re;
          that.setSource(newData);
          // Callback when we finish refreshing
          if (callback) {
            callback();
          }
        });
      } else {
        // No refresh token... just do the popup thing again
        that.openAuthorizationCodePopup(callback, flg);
      }
    } else {
      // We have an access token, and it's not expired yet
      if (callback) {
        callback();
      }
    }
  });
};

/**
 * @returns A valid access token.
 */
OAuth2.prototype.getAccessToken = function() {
  return this.get('accessToken');
};

OAuth2.prototype.getIdToken = function() {
	var authorizationCode = this.adapter.parseAuthorizationCode(window.location.href);
	console.log(authorizationCode);
	return authorizationCode;
};
/**
 * Indicate whether or not a valid access token exists.
 *
 * @returns {Boolean} True if an access token exists; otherwise false.
 */
OAuth2.prototype.hasAccessToken = function() {
  return !!this.get('accessToken');
};

/**
 * Clears an access token, effectively "logging out" of the service.
 */
OAuth2.prototype.clearAccessToken = function() {
  this.clear('accessToken');
};
<commit_msg>ウィンドウのフォーカス調整<commit_after>/*
 * Copyright 2011 Google Inc. All Rights Reserved.

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Constructor
 *
 * @param {String} adapterName  name of the adapter to use for this OAuth 2
 * @param {Object} config Containing clientId, clientSecret and apiScope
 * @param {String} config Alternatively, OAuth2.FINISH for the finish flow
 */
var OAuth2 = function(adapterName, config) {
  this.adapterName = adapterName;
  var that = this;
  OAuth2.loadAdapter(adapterName, function() {
    that.adapter = OAuth2.adapters[adapterName];
    if (config == OAuth2.FINISH) {
      that.finishAuth();
    } else if (config) {
      that.updateLocalStorage();

      var data = that.get();
      data.clientId = config.client_id;
      data.clientSecret = config.client_secret;
      data.apiScope = config.api_scope;
      that.setSource(data);
    }
  });
};

/**
 * Pass instead of config to specify the finishing OAuth flow.
 */
OAuth2.FINISH = 'finish';

/**
 * OAuth 2.0 endpoint adapters known to the library
 */
OAuth2.adapters = {};
OAuth2.adapterReverse = localStorage.oauth2_adapterReverse &&
    JSON.parse(localStorage.oauth2_adapterReverse) || {};
// Update the persisted adapterReverse in localStorage.
if (localStorage.adapterReverse) {
  OAuth2.adapterReverse = JSON.parse(localStorage.adapterReverse);
  delete localStorage.adapterReverse;
}

/**
 * Consolidates the data stored in localStorage on the current adapter in to
 * a single JSON object.
 * The update should only ever happen once per adapter and will delete the old
 * obsolete entries in localStorage after copying their values.
 */
OAuth2.prototype.updateLocalStorage = function() {
  // Check if update is even required.
  if (this.getSource()) {
    return;
  }
  var data = {};
  var variables = [
    'accessToken', 'accessTokenDate', 'apiScope', 'clientId', 'clientSecret',
    'expiresIn', 'refreshToken'
  ];
  // Check if a variable has already been persisted and then copy them.
  var key;
  for (var i = 0; i < variables.length; i++) {
    key = this.adapterName + '_' + variables[i];
    if (localStorage.hasOwnProperty(key)) {
      data[variables[i]] = localStorage[key];
      delete localStorage[key];
    }
  }
  // Persist the new JSON object in localStorage.
  this.setSource(data);
};

/**
 * Opens up an authorization popup window. This starts the OAuth 2.0 flow.
 *
 * @param {Function} callback Method to call when the user finished auth.
 */
OAuth2.prototype.openAuthorizationCodePopup = function(callback, flg) {
  // Store a reference to the callback so that the newly opened window can call
  // it later.
  window['oauth-callback'] = callback;

  // Create a new tab with the OAuth 2.0 prompt
  var crwWidth = 0;
  var crwHeight = 0;
  var crwLeft = 0;
  var crwTop = 0;

  if (flg) {
    crwWidth = 500;
    crwHeight = 500;
    crwLeft = (screen.availWidth - crwWidth) / 2;
    crwTop = (screen.availHeight - crwHeight) / 2;
  } else {
    crwWidth = 1;
    crwHeight = 1;
    crwLeft = 0;
    crwTop = 0;
  }

  chrome.windows.create({
      width: crwWidth,
      height: crwHeight,
      left: crwLeft,
      top: crwTop,
      focused : flg,
      url: this.adapter.authorizationCodeURL(this.getConfig()),
      type: 'popup'
  }, function(window) {    // 1. user grants permission for the application to access the OAuth 2.0
    // endpoint
    // 2. the endpoint redirects to the redirect URL.
    // 3. the extension injects a script into that redirect URL
    // 4. the injected script redirects back to oauth2.html, also passing
    // the redirect URL
    // 5. oauth2.html uses redirect URL to know what OAuth 2.0 flow to finish
    // (if there are multiple OAuth 2.0 adapters)
    // 6. Finally, the flow is finished and client code can call
    // myAuth.getAccessToken() to get a valid access token.
    chrome.windows.update(window.id, {focused : true}, function(){});
  });
};

/**
 * Gets access and refresh (if provided by endpoint) tokens
 *
 * @param {String} authorizationCode Retrieved from the first step in the process
 * @param {Function} callback Called back with 3 params:
 *                            access token, refresh token and expiry time
 */
OAuth2.prototype.getAccessAndRefreshTokens = function(authorizationCode, callback) {
  var that = this;
  // Make an XHR to get the token
  var xhr = new XMLHttpRequest();
  xhr.addEventListener('readystatechange', function(event) {
    if (xhr.readyState == 4) {
      if (xhr.status == 200) {
        // Callback with the data (incl. tokens).
        callback(that.adapter.parseAccessToken(xhr.responseText));
      }
    }
  });

  var method = that.adapter.accessTokenMethod();
  var items = that.adapter.accessTokenParams(authorizationCode, that.getConfig());
  var key = null;
  if (method == 'POST') {
    var formData = new FormData();
    for (key in items) {
      formData.append(key, items[key]);
    }
    xhr.open(method, that.adapter.accessTokenURL(), true);
    xhr.send(formData);
  } else if (method == 'GET') {
    var url = that.adapter.accessTokenURL();
    var params = '?';
    for (key in items) {
      params += encodeURIComponent(key) + '=' +
                encodeURIComponent(items[key]) + '&';
    }
    xhr.open(method, url + params, true);
    xhr.send();
  } else {
    throw method + ' is an unknown method';
  }
};

/**
 * Refreshes the access token using the currently stored refresh token
 * Note: this only happens for the Google adapter since all other OAuth 2.0
 * endpoints don't implement refresh tokens.
 *
 * @param {String} refreshToken A valid refresh token
 * @param {Function} callback On success, called with access token and expiry time and refresh token
 */
OAuth2.prototype.refreshAccessToken = function(refreshToken, callback) {
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function(event) {
    if (xhr.readyState == 4) {
      if(xhr.status == 200) {
        console.log(xhr.responseText);
        // Parse response with JSON
        var obj = JSON.parse(xhr.responseText);
        // Callback with the tokens
        callback(obj.access_token, obj.expires_in, obj.refresh_token);
      }
    }
  };

  var data = this.get();
  var formData = new FormData();
  formData.append('client_id', data.clientId);
  formData.append('client_secret', data.clientSecret);
  formData.append('refresh_token', refreshToken);
  formData.append('grant_type', 'refresh_token');
  xhr.open('POST', this.adapter.accessTokenURL(), true);
  xhr.send(formData);
};

/**
 * Extracts authorizationCode from the URL and makes a request to the last
 * leg of the OAuth 2.0 process.
*/
OAuth2.prototype.finishAuth = function() {
  var authorizationCode = null;
  var that = this;

  // Loop through existing extension views and excute any stored callbacks.
  function callback(error) {
    var views = chrome.extension.getViews();
    for (var i = 0, view; view = views[i]; i++) {
      if (view['oauth-callback']) {
        view['oauth-callback'](error);
        // TODO: Decide whether it's worth it to scope the callback or not.
        // Currently, every provider will share the same callback address but
        // that's not such a big deal assuming that they check to see whether
        // the token exists instead of blindly trusting that it does.
      }
    }

    // Once we get here, close the current tab and we're good to go.
    // The following works around bug: crbug.com/84201
    chrome.tabs.getSelected(null, function(tab){
      chrome.windows.remove(tab.windowId, function (){});
    });
  }

  try {
    authorizationCode = that.adapter.parseAuthorizationCode(window.location.href);
    console.log(authorizationCode);
  } catch (e) {
    console.error(e);
    callback(e);
  }

  that.getAccessAndRefreshTokens(authorizationCode, function(response) {
    var data = that.get();
    data.accessTokenDate = new Date().valueOf();

    // Set all data returned by the OAuth 2.0 provider.
    for (var name in response) {
      if (response.hasOwnProperty(name) && response[name]) {
        data[name] = response[name];
      }
    }

    that.setSource(data);
    callback();
  });
};

/**
 * @return True iff the current access token has expired
 */
OAuth2.prototype.isAccessTokenExpired = function() {
  var data = this.get();
  return (new Date().valueOf() - data.accessTokenDate) > data.expiresIn * 1000;
};

/**
 * Get the persisted adapter data in localStorage. Optionally, provide a
 * property name to only retrieve its value.
 *
 * @param {String} [name] The name of the property to be retrieved.
 * @return The data object or property value if name was specified.
 */
OAuth2.prototype.get = function(name) {
  var src = this.getSource();
  var obj = src ? JSON.parse(src) : {};
  return name ? obj[name] : obj;
};

/**
 * Set the value of a named property on the persisted adapter data in
 * localStorage.
 *
 * @param {String} name The name of the property to change.
 * @param value The value to be set.
 */
OAuth2.prototype.set = function(name, value) {
  var obj = this.get();
  obj[name] = value;
  this.setSource(obj);
};

/**
 * Clear all persisted adapter data in localStorage. Optionally, provide a
 * property name to only clear its value.
 *
 * @param {String} [name] The name of the property to clear.
 */
OAuth2.prototype.clear = function(name) {
  if (name) {
    var obj = this.get();
    delete obj[name];
    this.setSource(obj);
  } else {
    delete localStorage['oauth2_' + this.adapterName];
  }
};

/**
 * Get the JSON string for the object stored in localStorage.
 *
 * @return {String} The source JSON string.
 */
OAuth2.prototype.getSource = function() {
  return localStorage['oauth2_' + this.adapterName];
};

/**
 * Set the JSON string for the object stored in localStorage.
 *
 * @param {Object|String} source The new JSON string/object to be set.
 */
OAuth2.prototype.setSource = function(source) {
  if (!source) {
    return;
  }
  if (typeof source !== 'string') {
    source = JSON.stringify(source);
  }
  localStorage['oauth2_' + this.adapterName] = source;
};

/**
 * Get the configuration parameters to be passed to the adapter.
 *
 * @returns {Object} Contains clientId, clientSecret and apiScope.
 */
OAuth2.prototype.getConfig = function() {
  var data = this.get();
  return {
    clientId: data.clientId,
    clientSecret: data.clientSecret,
    apiScope: data.apiScope
  };
};

/***********************************
 *
 * STATIC ADAPTER RELATED METHODS
 *
 ***********************************/

/**
 * Loads an OAuth 2.0 adapter and calls back when it's loaded
 *
 * @param adapterName {String} The name of the JS file
 * @param callback {Function} Called as soon as the adapter has been loaded
 */
OAuth2.loadAdapter = function(adapterName, callback) {
  // If it's already loaded, don't load it again
  if (OAuth2.adapters[adapterName]) {
    callback();
    return;
  }
  var head = document.querySelector('head');
  var script = document.createElement('script');
  script.type = 'text/javascript';
  script.src = '/src/lib/oauth2/adapters/' + adapterName + '.js';
  script.addEventListener('load', function() {
    callback();
  });
  head.appendChild(script);
};

/**
 * Registers an adapter with the library. This call is used by each adapter
 *
 * @param {String} name The adapter name
 * @param {Object} impl The adapter implementation
 *
 * @throws {String} If the specified adapter is invalid
 */
OAuth2.adapter = function(name, impl) {
  var implementing = 'authorizationCodeURL redirectURL accessTokenURL ' +
    'accessTokenMethod accessTokenParams accessToken';

  // Check for missing methods
  implementing.split(' ').forEach(function(method, index) {
    if (!method in impl) {
      throw 'Invalid adapter! Missing method: ' + method;
    }
  });

  // Save the adapter in the adapter registry
  OAuth2.adapters[name] = impl;
  // Make an entry in the adapter lookup table
  OAuth2.adapterReverse[impl.redirectURL()] = name;
  // Store the the adapter lookup table in localStorage
  localStorage.oauth2_adapterReverse = JSON.stringify(OAuth2.adapterReverse);
};

/**
 * Looks up the adapter name based on the redirect URL. Used by oauth2.html
 * in the second part of the OAuth 2.0 flow.
 *
 * @param {String} url The url that called oauth2.html
 * @return The adapter for the current page
 */
OAuth2.lookupAdapterName = function(url) {
  var adapterReverse = JSON.parse(localStorage.oauth2_adapterReverse);
  return adapterReverse[url];
};

/***********************************
 *
 * PUBLIC API
 *
 ***********************************/

/**
 * Authorizes the OAuth authenticator instance.
 *
 * @param {Function} callback Tries to callback when auth is successful
 *                            Note: does not callback if grant popup required
 */
OAuth2.prototype.authorize = function(callback, flg) {
  var that = this;
  OAuth2.loadAdapter(that.adapterName, function() {
    that.adapter = OAuth2.adapters[that.adapterName];
    var data = that.get();
    if (!data.accessToken) {
      // There's no access token yet. Start the authorizationCode flow
      that.openAuthorizationCodePopup(callback, flg);
    } else if (that.isAccessTokenExpired()) {
      // There's an existing access token but it's expired
      if (data.refreshToken) {
        that.refreshAccessToken(data.refreshToken, function(at, exp, re) {
          var newData = that.get();
          newData.accessTokenDate = new Date().valueOf();
          newData.accessToken = at;
          newData.expiresIn = exp;
          newData.refreshToken = re;
          that.setSource(newData);
          // Callback when we finish refreshing
          if (callback) {
            callback();
          }
        });
      } else {
        // No refresh token... just do the popup thing again
        that.openAuthorizationCodePopup(callback, flg);
      }
    } else {
      // We have an access token, and it's not expired yet
      if (callback) {
        callback();
      }
    }
  });
};

/**
 * @returns A valid access token.
 */
OAuth2.prototype.getAccessToken = function() {
  return this.get('accessToken');
};

OAuth2.prototype.getIdToken = function() {
	var authorizationCode = this.adapter.parseAuthorizationCode(window.location.href);
	console.log(authorizationCode);
	return authorizationCode;
};
/**
 * Indicate whether or not a valid access token exists.
 *
 * @returns {Boolean} True if an access token exists; otherwise false.
 */
OAuth2.prototype.hasAccessToken = function() {
  return !!this.get('accessToken');
};

/**
 * Clears an access token, effectively "logging out" of the service.
 */
OAuth2.prototype.clearAccessToken = function() {
  this.clear('accessToken');
};
<|endoftext|>


<commit_before>'use strict'

const Boom = require('boom')
const Joi = require('joi')
const async = require('async')
const db = require('./../db')
const config = require('./../config')
const SQL = require('./../db/SQL')
const mapping = require('./../mapping')
const utils = require('./utils')
const uuidV4 = require('uuid/v4')
const validationRules = require('./validation').policies

function toArrayWithId (policies) {
  if (Array.isArray(policies)) {
    return policies.map((p) => {
      p.id = p.id || uuidV4()

      return p
    })
  }

  return Object.keys(policies).map((pName) => {
    let p = policies[pName]
    p.id = p.id || uuidV4()

    return p
  })
}

function insertPolicies (client, policies, cb) {
  policies = toArrayWithId(policies)

  const sql = SQL`INSERT INTO policies (id, version, name, org_id, statements) VALUES `
  sql.append(SQL`(${policies[0].id}, ${policies[0].version}, ${policies[0].name}, ${policies[0].org_id}, ${policies[0].statements})`)
  policies.slice(1).forEach((policy) => {
    sql.append(SQL`, (${policy.id}, ${policy.version}, ${policy.name}, ${policy.org_id}, ${policy.statements})`)
  })
  sql.append(SQL` RETURNING id`)

  client.query(sql, cb)
}

function deletePolicies (client, ids, orgId, cb) {
  client.query(SQL`DELETE FROM policies WHERE id = ANY (${ids}) AND org_id=${orgId}`, utils.boomErrorWrapper(cb))
}

function deleteTeamsAssociations (client, ids, orgId, cb) {
  client.query(SQL`
               DELETE FROM team_policies AS p
               USING teams AS t
               WHERE t.id = p.team_id
                 AND p.policy_id = ANY (${ids})
                 AND t.org_id = ${orgId}`,
               utils.boomErrorWrapper(cb))
}

function deleteUsersAssociations (client, ids, orgId, cb) {
  client.query(SQL`
               DELETE FROM user_policies AS p
               USING users AS u
               WHERE u.id = p.user_id
                 AND p.policy_id = ANY (${ids})
                 AND u.org_id = ${orgId}`,
               utils.boomErrorWrapper(cb))
}

function getNames (policies) {
  return Object.keys(policies).map((key) => {
    return policies[key].name
  })
}

function removePolicyFromUsers (job, next) {
  const { id, organizationId } = job
  deleteUsersAssociations(job.client, [id], organizationId, next)
}

function removePolicyFromTeams (job, next) {
  const { id, organizationId } = job
  deleteTeamsAssociations(job.client, [id], organizationId, next)
}

function removePolicy (job, next) {
  const { id, organizationId } = job

  const sqlQuery = SQL`
    DELETE FROM policies
    WHERE id = ${id}
    AND org_id = ${organizationId}
  `
  job.client.query(sqlQuery, (err, res) => {
    if (err) return next(Boom.badImplementation(err))
    if (res.rowCount === 0) return next(Boom.notFound(`Policy ${id} not found`))

    next()
  })
}

const policyOps = {
  /**
   * List all the policies related to a specific organization
   *
   * @param  {Object}   params { organizationId, limit, page }
   * @param  {Function} cb
   */
  listByOrganization: function listByOrganization (params, cb) {
    const { organizationId, limit, page } = params

    Joi.validate({ organizationId, page, limit }, validationRules.listByOrganization, function (err) {
      if (err) return cb(Boom.badRequest(err))

      const sqlQuery = SQL`
        WITH total AS (
          SELECT COUNT(*) AS cnt FROM policies
          WHERE org_id = ${organizationId}
        )
        SELECT
          p.id,
          p.version,
          p.name,
          p.statements,
          t.cnt::INTEGER AS total
        FROM policies AS p
        INNER JOIN total AS t ON 1=1
        WHERE p.org_id = ${organizationId}
        ORDER BY UPPER(p.name)
      `
      if (limit) {
        sqlQuery.append(SQL` LIMIT ${limit}`)
      }
      if (limit && page) {
        const offset = (page - 1) * limit
        sqlQuery.append(SQL` OFFSET ${offset}`)
      }
      db.query(sqlQuery, function (err, result) {
        if (err) return cb(Boom.badImplementation(err))
        let total = result.rows.length > 0 ? result.rows[0].total : 0
        return cb(null, result.rows.map(mapping.policy), total)
      })
    })
  },

  /**
   * fetch specific policy
   *
   * @param  {Object}   params { id, organizationId }
   * @param  {Function} cb
   */
  readPolicy: function readPolicy ({ id, organizationId }, cb) {
    Joi.validate({ id, organizationId }, validationRules.readPolicy, function (err) {
      if (err) return cb(Boom.badRequest(err))

      const sqlQuery = SQL`
        SELECT  *
        FROM policies
        WHERE id = ${id}
        AND org_id = ${organizationId}
      `
      db.query(sqlQuery, function (err, result) {
        if (err) return cb(Boom.badImplementation(err))
        if (result.rowCount === 0) return cb(Boom.notFound())

        return cb(null, mapping.policy(result.rows[0]))
      })
    })
  },

  /**
   * Creates a new policy
   *
   * @param  {Object}   params { id, version, name, organizationId, statements }
   * @param  {Function} cb
   */
  createPolicy: function createPolicy (params, cb) {
    const { id, version, name, organizationId, statements } = params

    Joi.validate({ id, version, name, organizationId, statements }, validationRules.createPolicy, function (err) {
      if (err) return cb(Boom.badRequest(err))

      insertPolicies(db, [{
        id: id,
        version: version,
        name: name,
        org_id: organizationId,
        statements: statements
      }], (err, result) => {
        if (utils.isUniqueViolationError(err)) {
          return cb(Boom.badRequest(`Policy with id ${id} already present`))
        }
        if (err) return cb(Boom.badImplementation(err))

        policyOps.readPolicy({ id: result.rows[0].id, organizationId }, cb)
      })
    })
  },

  /**
   * Update policy values
   *
   * @param  {Object}   params { id, organizationId, version, name, statements }
   * @param  {Function} cb
   */
  updatePolicy: function updatePolicy (params, cb) {
    const { id, organizationId, version, name, statements } = params

    Joi.validate({ id, organizationId, version, name, statements }, validationRules.updatePolicy, function (err) {
      if (err) return cb(Boom.badRequest(err))

      const sqlQuery = SQL`
        UPDATE policies
        SET
          version = ${version},
          name = ${name},
          statements = ${statements}
        WHERE
          id = ${id}
          AND org_id = ${organizationId}
      `
      db.query(sqlQuery, function (err, result) {
        if (err) return cb(Boom.badImplementation(err))
        if (result.rowCount === 0) return cb(Boom.notFound())

        policyOps.readPolicy({ id, organizationId }, cb)
      })
    })
  },

  /**
   * Delete a specific policy
   *
   * @param  {Object}   params { id, organizationId }
   * @param  {Function} cb
   */
  deletePolicy: function deletePolicy (params, cb) {
    const { id, organizationId } = params

    const tasks = [
      (job, next) => {
        Joi.validate({ id, organizationId }, validationRules.deletePolicy, (err) => {
          if (err) return next(Boom.badRequest(err))

          next()
        })
      },
      (job, next) => {
        job.id = id
        job.organizationId = organizationId
        next()
      },
      removePolicyFromUsers,
      removePolicyFromTeams,
      removePolicy
    ]

    db.withTransaction(tasks, (err, res) => {
      if (err) return cb(err)
      cb()
    })
  },

  /**
   * List all user policies
   *
   * @param  {Object}   params { userId, organizationId }
   * @param  {Function} cb
   */
  listAllUserPolicies: function listAllUserPolicies ({ userId, organizationId }, cb) {
    const sql = SQL`
      WITH user_teams AS (
        SELECT id FROM teams WHERE path @> (
          SELECT array_agg(path) FROM teams
          INNER JOIN team_members tm
          ON tm.team_id = teams.id
          WHERE tm.user_id = ${userId}
        )
      ),
      policies_from_teams AS (
        SELECT
          policy_id
        FROM
          team_policies
        WHERE
          team_id IN (SELECT id FROM user_teams)
      ),
      policies_from_user AS (
        SELECT
          policy_id
        FROM
          user_policies
        WHERE
          user_id = ${userId}
      ),
      policies_from_organization AS (
        SELECT
          policy_id
        FROM
          organization_policies
        WHERE
          org_id = ${organizationId}
      )
      SELECT
        id,
        version,
        name,
        statements
      FROM
        policies
      WHERE
        id IN (SELECT policy_id FROM policies_from_user)
      OR
        id IN (SELECT policy_id FROM policies_from_teams)
      OR
        id IN (SELECT policy_id FROM policies_from_organization)
    `

    db.query(sql, function (err, result) {
      if (err) return cb(Boom.badImplementation(err))

      cb(null, result.rows.map(mapping.policy.iam))
    })
  },

  deleteAllPolicyByIds: function deleteAllPolicyByIds (client, ids, orgId, next) {
    async.applyEachSeries([
      deleteTeamsAssociations,
      deleteUsersAssociations,
      deletePolicies
    ], client, ids, orgId, next)
  },

  createOrgDefaultPolicies: function createOrgDefaultPolicies (client, organizationId, cb) {
    const defaultPolicies = config.get('authorization.organizations.defaultPolicies', {'organizationId': organizationId})
    insertPolicies(client, defaultPolicies, function (err, result) {
      if (err) return cb(Boom.badImplementation(err))

      const name = config.get('authorization.organizations.defaultPolicies.orgAdmin.name', {'organizationId': organizationId})
      const sqlQuery = SQL`
        SELECT id
        FROM policies
        WHERE org_id = ${organizationId}
          AND name = ${name}
      `
      client.query(sqlQuery, function (err, result) {
        if (err) return cb(Boom.badImplementation(err))
        if (result.rowCount === 0) return cb(Boom.notFound(`No policy found for org ${organizationId} with name ${name}`))

        cb(null, result.rows[0].id)
      })
    })
  },

  createTeamDefaultPolicies: function createTeamDefaultPolicies (client, organizationId, teamId, cb) {
    const defaultPolicies = config.get('authorization.teams.defaultPolicies', { organizationId, teamId })
    insertPolicies(client, defaultPolicies, cb)
  },

  readTeamDefaultPolicies: function readTeamDefaultPolicies (client, organizationId, teamId, cb) {
    const defaultPolicies = config.get('authorization.teams.defaultPolicies', {organizationId, teamId})
    const names = getNames(defaultPolicies)
    client.query(SQL`SELECT id FROM policies WHERE name = ANY(${names}) AND org_id = ${organizationId}`, utils.boomErrorWrapper(cb))
  }
}

policyOps.listByOrganization.validate = validationRules.listByOrganization
policyOps.readPolicy.validate = validationRules.readPolicy
policyOps.createPolicy.validate = validationRules.createPolicy
policyOps.updatePolicy.validate = validationRules.updatePolicy
policyOps.deletePolicy.validate = validationRules.deletePolicy

module.exports = policyOps
<commit_msg>do not scope policy removal from users and teams by org<commit_after>'use strict'

const Boom = require('boom')
const Joi = require('joi')
const async = require('async')
const db = require('./../db')
const config = require('./../config')
const SQL = require('./../db/SQL')
const mapping = require('./../mapping')
const utils = require('./utils')
const uuidV4 = require('uuid/v4')
const validationRules = require('./validation').policies

function toArrayWithId (policies) {
  if (Array.isArray(policies)) {
    return policies.map((p) => {
      p.id = p.id || uuidV4()

      return p
    })
  }

  return Object.keys(policies).map((pName) => {
    let p = policies[pName]
    p.id = p.id || uuidV4()

    return p
  })
}

function insertPolicies (client, policies, cb) {
  policies = toArrayWithId(policies)

  const sql = SQL`INSERT INTO policies (id, version, name, org_id, statements) VALUES `
  sql.append(SQL`(${policies[0].id}, ${policies[0].version}, ${policies[0].name}, ${policies[0].org_id}, ${policies[0].statements})`)
  policies.slice(1).forEach((policy) => {
    sql.append(SQL`, (${policy.id}, ${policy.version}, ${policy.name}, ${policy.org_id}, ${policy.statements})`)
  })
  sql.append(SQL` RETURNING id`)

  client.query(sql, cb)
}

function deletePolicies (client, ids, orgId, cb) {
  client.query(SQL`DELETE FROM policies WHERE id = ANY (${ids}) AND org_id=${orgId}`, utils.boomErrorWrapper(cb))
}

function deleteTeamsAssociations (client, ids, orgId, cb) {
  client.query(SQL`
               DELETE FROM team_policies AS p
               USING teams AS t
               WHERE t.id = p.team_id
                 AND p.policy_id = ANY (${ids})`,
               utils.boomErrorWrapper(cb))
}

function deleteUsersAssociations (client, ids, orgId, cb) {
  client.query(SQL`
               DELETE FROM user_policies AS p
               USING users AS u
               WHERE u.id = p.user_id
                 AND p.policy_id = ANY (${ids})`,
               utils.boomErrorWrapper(cb))
}

function getNames (policies) {
  return Object.keys(policies).map((key) => {
    return policies[key].name
  })
}

function removePolicyFromUsers (job, next) {
  const { id, organizationId } = job
  deleteUsersAssociations(job.client, [id], organizationId, next)
}

function removePolicyFromTeams (job, next) {
  const { id, organizationId } = job
  deleteTeamsAssociations(job.client, [id], organizationId, next)
}

function removePolicy (job, next) {
  const { id, organizationId } = job

  const sqlQuery = SQL`
    DELETE FROM policies
    WHERE id = ${id}
    AND org_id = ${organizationId}
  `
  job.client.query(sqlQuery, (err, res) => {
    if (err) return next(Boom.badImplementation(err))
    if (res.rowCount === 0) return next(Boom.notFound(`Policy ${id} not found`))

    next()
  })
}

const policyOps = {
  /**
   * List all the policies related to a specific organization
   *
   * @param  {Object}   params { organizationId, limit, page }
   * @param  {Function} cb
   */
  listByOrganization: function listByOrganization (params, cb) {
    const { organizationId, limit, page } = params

    Joi.validate({ organizationId, page, limit }, validationRules.listByOrganization, function (err) {
      if (err) return cb(Boom.badRequest(err))

      const sqlQuery = SQL`
        WITH total AS (
          SELECT COUNT(*) AS cnt FROM policies
          WHERE org_id = ${organizationId}
        )
        SELECT
          p.id,
          p.version,
          p.name,
          p.statements,
          t.cnt::INTEGER AS total
        FROM policies AS p
        INNER JOIN total AS t ON 1=1
        WHERE p.org_id = ${organizationId}
        ORDER BY UPPER(p.name)
      `
      if (limit) {
        sqlQuery.append(SQL` LIMIT ${limit}`)
      }
      if (limit && page) {
        const offset = (page - 1) * limit
        sqlQuery.append(SQL` OFFSET ${offset}`)
      }
      db.query(sqlQuery, function (err, result) {
        if (err) return cb(Boom.badImplementation(err))
        let total = result.rows.length > 0 ? result.rows[0].total : 0
        return cb(null, result.rows.map(mapping.policy), total)
      })
    })
  },

  /**
   * fetch specific policy
   *
   * @param  {Object}   params { id, organizationId }
   * @param  {Function} cb
   */
  readPolicy: function readPolicy ({ id, organizationId }, cb) {
    Joi.validate({ id, organizationId }, validationRules.readPolicy, function (err) {
      if (err) return cb(Boom.badRequest(err))

      const sqlQuery = SQL`
        SELECT  *
        FROM policies
        WHERE id = ${id}
        AND org_id = ${organizationId}
      `
      db.query(sqlQuery, function (err, result) {
        if (err) return cb(Boom.badImplementation(err))
        if (result.rowCount === 0) return cb(Boom.notFound())

        return cb(null, mapping.policy(result.rows[0]))
      })
    })
  },

  /**
   * Creates a new policy
   *
   * @param  {Object}   params { id, version, name, organizationId, statements }
   * @param  {Function} cb
   */
  createPolicy: function createPolicy (params, cb) {
    const { id, version, name, organizationId, statements } = params

    Joi.validate({ id, version, name, organizationId, statements }, validationRules.createPolicy, function (err) {
      if (err) return cb(Boom.badRequest(err))

      insertPolicies(db, [{
        id: id,
        version: version,
        name: name,
        org_id: organizationId,
        statements: statements
      }], (err, result) => {
        if (utils.isUniqueViolationError(err)) {
          return cb(Boom.badRequest(`Policy with id ${id} already present`))
        }
        if (err) return cb(Boom.badImplementation(err))

        policyOps.readPolicy({ id: result.rows[0].id, organizationId }, cb)
      })
    })
  },

  /**
   * Update policy values
   *
   * @param  {Object}   params { id, organizationId, version, name, statements }
   * @param  {Function} cb
   */
  updatePolicy: function updatePolicy (params, cb) {
    const { id, organizationId, version, name, statements } = params

    Joi.validate({ id, organizationId, version, name, statements }, validationRules.updatePolicy, function (err) {
      if (err) return cb(Boom.badRequest(err))

      const sqlQuery = SQL`
        UPDATE policies
        SET
          version = ${version},
          name = ${name},
          statements = ${statements}
        WHERE
          id = ${id}
          AND org_id = ${organizationId}
      `
      db.query(sqlQuery, function (err, result) {
        if (err) return cb(Boom.badImplementation(err))
        if (result.rowCount === 0) return cb(Boom.notFound())

        policyOps.readPolicy({ id, organizationId }, cb)
      })
    })
  },

  /**
   * Delete a specific policy
   *
   * @param  {Object}   params { id, organizationId }
   * @param  {Function} cb
   */
  deletePolicy: function deletePolicy (params, cb) {
    const { id, organizationId } = params

    const tasks = [
      (job, next) => {
        Joi.validate({ id, organizationId }, validationRules.deletePolicy, (err) => {
          if (err) return next(Boom.badRequest(err))

          next()
        })
      },
      (job, next) => {
        job.id = id
        job.organizationId = organizationId
        next()
      },
      removePolicyFromUsers,
      removePolicyFromTeams,
      removePolicy
    ]

    db.withTransaction(tasks, (err, res) => {
      if (err) return cb(err)
      cb()
    })
  },

  /**
   * List all user policies
   *
   * @param  {Object}   params { userId, organizationId }
   * @param  {Function} cb
   */
  listAllUserPolicies: function listAllUserPolicies ({ userId, organizationId }, cb) {
    const sql = SQL`
      WITH user_teams AS (
        SELECT id FROM teams WHERE path @> (
          SELECT array_agg(path) FROM teams
          INNER JOIN team_members tm
          ON tm.team_id = teams.id
          WHERE tm.user_id = ${userId}
        )
      ),
      policies_from_teams AS (
        SELECT
          policy_id
        FROM
          team_policies
        WHERE
          team_id IN (SELECT id FROM user_teams)
      ),
      policies_from_user AS (
        SELECT
          policy_id
        FROM
          user_policies
        WHERE
          user_id = ${userId}
      ),
      policies_from_organization AS (
        SELECT
          policy_id
        FROM
          organization_policies
        WHERE
          org_id = ${organizationId}
      )
      SELECT
        id,
        version,
        name,
        statements
      FROM
        policies
      WHERE
        id IN (SELECT policy_id FROM policies_from_user)
      OR
        id IN (SELECT policy_id FROM policies_from_teams)
      OR
        id IN (SELECT policy_id FROM policies_from_organization)
    `

    db.query(sql, function (err, result) {
      if (err) return cb(Boom.badImplementation(err))

      cb(null, result.rows.map(mapping.policy.iam))
    })
  },

  deleteAllPolicyByIds: function deleteAllPolicyByIds (client, ids, orgId, next) {
    async.applyEachSeries([
      deleteTeamsAssociations,
      deleteUsersAssociations,
      deletePolicies
    ], client, ids, orgId, next)
  },

  createOrgDefaultPolicies: function createOrgDefaultPolicies (client, organizationId, cb) {
    const defaultPolicies = config.get('authorization.organizations.defaultPolicies', {'organizationId': organizationId})
    insertPolicies(client, defaultPolicies, function (err, result) {
      if (err) return cb(Boom.badImplementation(err))

      const name = config.get('authorization.organizations.defaultPolicies.orgAdmin.name', {'organizationId': organizationId})
      const sqlQuery = SQL`
        SELECT id
        FROM policies
        WHERE org_id = ${organizationId}
          AND name = ${name}
      `
      client.query(sqlQuery, function (err, result) {
        if (err) return cb(Boom.badImplementation(err))
        if (result.rowCount === 0) return cb(Boom.notFound(`No policy found for org ${organizationId} with name ${name}`))

        cb(null, result.rows[0].id)
      })
    })
  },

  createTeamDefaultPolicies: function createTeamDefaultPolicies (client, organizationId, teamId, cb) {
    const defaultPolicies = config.get('authorization.teams.defaultPolicies', { organizationId, teamId })
    insertPolicies(client, defaultPolicies, cb)
  },

  readTeamDefaultPolicies: function readTeamDefaultPolicies (client, organizationId, teamId, cb) {
    const defaultPolicies = config.get('authorization.teams.defaultPolicies', {organizationId, teamId})
    const names = getNames(defaultPolicies)
    client.query(SQL`SELECT id FROM policies WHERE name = ANY(${names}) AND org_id = ${organizationId}`, utils.boomErrorWrapper(cb))
  }
}

policyOps.listByOrganization.validate = validationRules.listByOrganization
policyOps.readPolicy.validate = validationRules.readPolicy
policyOps.createPolicy.validate = validationRules.createPolicy
policyOps.updatePolicy.validate = validationRules.updatePolicy
policyOps.deletePolicy.validate = validationRules.deletePolicy

module.exports = policyOps
<|endoftext|>


<commit_before>dojo.provide("dojox.widget.FileInputAuto");

dojo.require("dojox.widget.FileInput");
dojo.require("dojo.io.iframe");

dojo.declare("dojox.widget.FileInputAuto",
	dojox.widget.FileInput,
	{
	// summary: An extension on dojox.widget.FileInput providing background upload progress
	//
	// description: An extended version of FileInput - when the user focuses away from the input
	//	the selected file is posted via dojo.io.iframe to the url. example implementation
	//	comes with PHP solution for handling upload, and returning required data.
	//
	// notes: the return data from the io.iframe is used to populate the input element with
	//	data regarding the results. it will be a JSON object, like:
	//
	//	results = { size: "1024", filename: "file.txt" }
	//
	//	all the parameters allowed to dojox.widget.FileInput apply

	// url: String
	// 	the URL where our background FileUpload will be sent
	url: "",

	// blurDelay: Integer
	//	time in ms before an un-focused widget will wait before uploading the file to the url="" specified
	//	default: 2 seconds
	blurDelay: 2000,

	// duration: Integer
	//	The time in ms to use as the generic timing mechanism for the animations
	//	set to 1 or 0 for "immediate respose"
	duration: 500,

	// uploadMessage: String
	//
	//	FIXME: i18n somehow?
	uploadMessage: "Uploading ...",

	_sent: false,

	// small template changes, new attachpoint: overlay
	templatePath: dojo.moduleUrl("dojox.widget","FileInput/FileInputAuto.html"),

	startup: function(){
		// summary: add our extra blur listeners
		this._blurListener = dojo.connect(this.fileInput,"onblur",this,"_onBlur");
		this._focusListener = dojo.connect(this.fileInput,"onfocus",this,"_onFocus");
		this.inherited("startup",arguments);
	},

	_onFocus: function(){
		// summary: clear the upload timer
		if(this._blurTimer){ clearTimeout(this._blurTimer); }
	},

	_onBlur: function(){
		// summary: start the upload timer
		if(this._blurTimer){ clearTimeout(this._blurTimer); }
		if(!this._sent){
			this._blurTimer = setTimeout(dojo.hitch(this,"_sendFile"),this.blurDelay);
		}
	},

	setMessage: function(/*String*/title){
		// summary: set the text of the progressbar

		// innerHTML throws errors in IE! so use DOM manipulation instead
		//this.overlay.innerHTML = title;
		this.overlay.removeChild(this.overlay.firstChild);
		this.overlay.appendChild(document.createTextNode(title));
	},

	_sendFile: function(/* Event */e){
		// summary: triggers the chain of events needed to upload a file in the background.
		if(!this.fileInput.value || this._sent){ return; }

		dojo.style(this.fakeNodeHolder,"display","none");
		dojo.style(this.overlay,"opacity","0");
		dojo.style(this.overlay,"display","block");

		this.setMessage(this.uploadMessage);

		dojo.fadeIn({ node: this.overlay, duration:this.duration }).play();

		var _newForm;
		if(dojo.isIE){
			// just to reiterate, IE is a steaming pile of code.
			_newForm = document.createElement('<form enctype="multipart/form-data" method="post">');
			_newForm.encoding = "multipart/form-data";

		}else{
			// this is how all other sane browsers do it
			_newForm = document.createElement('form');
			_newForm.setAttribute("enctype","multipart/form-data");
		}
		_newForm.appendChild(this.fileInput);
		dojo.body().appendChild(_newForm);

		dojo.io.iframe.send({
			url: this.url+"?name="+this.name,
			form: _newForm,
			handleAs: "json",
			handle: dojo.hitch(this,"_handleSend")
		});
	},

	_handleSend: function(data,ioArgs){
		// summary: The callback to toggle the progressbar, and fire the user-defined callback

		// innerHTML throws errors in IE! so use DOM manipulation instead
		this.overlay.removeChild(this.overlay.firstChild);

		this._sent = true;
		dojo.style(this.overlay,"opacity","0");
		dojo.style(this.overlay,"border","none");
		dojo.style(this.overlay,"background","none");

		this.overlay.style.backgroundImage = "none";
		this.fileInput.style.display = "none";
		this.fakeNodeHolder.style.display = "none";
		dojo.fadeIn({ node:this.overlay, duration:this.duration }).play(250);

		dojo.disconnect(this._blurListener);
		dojo.disconnect(this._focusListener);

		this.onComplete(data,ioArgs,this);
	},

	_onClick: function(e){
		// summary: accomodate our extra focusListeners
		if(this._blurTimer){ clearTimeout(this._blurTimer); }

		dojo.disconnect(this._blurListener);
		dojo.disconnect(this._focusListener);

		this.inherited("_onClick",arguments);

		this._blurListener = dojo.connect(this.fileInput,"onblur",this,"_onBlur");
		this._focusListener = dojo.connect(this.fileInput,"onfocus",this,"_onFocus");
	},

	onComplete: function(data,ioArgs,widgetRef){
		// summary: stub function fired when an upload has finished.
		// data: the raw data found in the first [TEXTAREA] tag of the post url
		// ioArgs: the dojo.Deferred data being passed from the handle: callback
		// widgetRef: this widget pointer, so you can set this.overlay to a completed/error message easily
	}
});

dojo.declare("dojox.widget.FileInputBlind",
	dojox.widget.FileInputAuto,
	{
	// summary: An extended version of dojox.widget.FileInputAuto
	//	that does not display an input node, but rather only a button
	// 	and otherwise behaves just like FileInputAuto

	startup: function(){
		// summary: hide our fileInput input field
		this.inherited("startup",arguments);
		this._off = dojo.style(this.inputNode,"width");
		this.inputNode.style.display = "none";
		this._fixPosition();
	},

	_fixPosition: function(){
		// summary: in this case, set the button under where the visible button is
		if(dojo.isIE){
			dojo.style(this.fileInput,"width","1px");
		}else{
			dojo.style(this.fileInput,"left","-"+(this._off)+"px");
		}
	},

	_onClick: function(e){
		// summary: onclick, we need to reposition our newly created input type="file"
		this.inherited("_onClick",arguments);
		this._fixPosition();
	}
});
<commit_msg>remove the form used to upload a file when uploading is done simplified inherited call<commit_after>dojo.provide("dojox.widget.FileInputAuto");

dojo.require("dojox.widget.FileInput");
dojo.require("dojo.io.iframe");

dojo.declare("dojox.widget.FileInputAuto",
	dojox.widget.FileInput,
	{
	// summary: An extension on dojox.widget.FileInput providing background upload progress
	//
	// description: An extended version of FileInput - when the user focuses away from the input
	//	the selected file is posted via dojo.io.iframe to the url. example implementation
	//	comes with PHP solution for handling upload, and returning required data.
	//
	// notes: the return data from the io.iframe is used to populate the input element with
	//	data regarding the results. it will be a JSON object, like:
	//
	//	results = { size: "1024", filename: "file.txt" }
	//
	//	all the parameters allowed to dojox.widget.FileInput apply

	// url: String
	// 	the URL where our background FileUpload will be sent
	url: "",

	// blurDelay: Integer
	//	time in ms before an un-focused widget will wait before uploading the file to the url="" specified
	//	default: 2 seconds
	blurDelay: 2000,

	// duration: Integer
	//	The time in ms to use as the generic timing mechanism for the animations
	//	set to 1 or 0 for "immediate respose"
	duration: 500,

	// uploadMessage: String
	//
	//	FIXME: i18n somehow?
	uploadMessage: "Uploading ...",

	_sent: false,

	// small template changes, new attachpoint: overlay
	templatePath: dojo.moduleUrl("dojox.widget","FileInput/FileInputAuto.html"),

	startup: function(){
		// summary: add our extra blur listeners
		this._blurListener = dojo.connect(this.fileInput,"onblur",this,"_onBlur");
		this._focusListener = dojo.connect(this.fileInput,"onfocus",this,"_onFocus");
		this.inherited(arguments);
	},

	_onFocus: function(){
		// summary: clear the upload timer
		if(this._blurTimer){ clearTimeout(this._blurTimer); }
	},

	_onBlur: function(){
		// summary: start the upload timer
		if(this._blurTimer){ clearTimeout(this._blurTimer); }
		if(!this._sent){
			this._blurTimer = setTimeout(dojo.hitch(this,"_sendFile"),this.blurDelay);
		}
	},

	setMessage: function(/*String*/title){
		// summary: set the text of the progressbar

		// innerHTML throws errors in IE! so use DOM manipulation instead
		//this.overlay.innerHTML = title;
		this.overlay.removeChild(this.overlay.firstChild);
		this.overlay.appendChild(document.createTextNode(title));
	},

	_sendFile: function(/* Event */e){
		// summary: triggers the chain of events needed to upload a file in the background.
		if(!this.fileInput.value || this._sent){ return; }

		dojo.style(this.fakeNodeHolder,"display","none");
		dojo.style(this.overlay,"opacity","0");
		dojo.style(this.overlay,"display","block");

		this.setMessage(this.uploadMessage);

		dojo.fadeIn({ node: this.overlay, duration:this.duration }).play();

		var _newForm;
		if(dojo.isIE){
			// just to reiterate, IE is a steaming pile of code.
			_newForm = document.createElement('<form enctype="multipart/form-data" method="post">');
			_newForm.encoding = "multipart/form-data";

		}else{
			// this is how all other sane browsers do it
			_newForm = document.createElement('form');
			_newForm.setAttribute("enctype","multipart/form-data");
		}
		_newForm.appendChild(this.fileInput);
		dojo.body().appendChild(_newForm);

		dojo.io.iframe.send({
			url: this.url+"?name="+this.name,
			form: _newForm,
			handleAs: "json",
			handle: dojo.hitch(this,"_handleSend")
		});
	},

	_handleSend: function(data,ioArgs){
		// summary: The callback to toggle the progressbar, and fire the user-defined callback

		// innerHTML throws errors in IE! so use DOM manipulation instead
		this.overlay.removeChild(this.overlay.firstChild);

		this._sent = true;
		dojo.style(this.overlay,"opacity","0");
		dojo.style(this.overlay,"border","none");
		dojo.style(this.overlay,"background","none");

		this.overlay.style.backgroundImage = "none";
		this.fileInput.style.display = "none";
		this.fakeNodeHolder.style.display = "none";
		dojo.fadeIn({ node:this.overlay, duration:this.duration }).play(250);

		dojo.disconnect(this._blurListener);
		dojo.disconnect(this._focusListener);

		//remove the form used to send the request
		dojo.body().removeChild(ioArgs.args.form);

		this.onComplete(data,ioArgs,this);
	},

	_onClick: function(e){
		// summary: accomodate our extra focusListeners
		if(this._blurTimer){ clearTimeout(this._blurTimer); }

		dojo.disconnect(this._blurListener);
		dojo.disconnect(this._focusListener);

		this.inherited(arguments);

		this._blurListener = dojo.connect(this.fileInput,"onblur",this,"_onBlur");
		this._focusListener = dojo.connect(this.fileInput,"onfocus",this,"_onFocus");
	},

	onComplete: function(data,ioArgs,widgetRef){
		// summary: stub function fired when an upload has finished.
		// data: the raw data found in the first [TEXTAREA] tag of the post url
		// ioArgs: the dojo.Deferred data being passed from the handle: callback
		// widgetRef: this widget pointer, so you can set this.overlay to a completed/error message easily
	}
});

dojo.declare("dojox.widget.FileInputBlind",
	dojox.widget.FileInputAuto,
	{
	// summary: An extended version of dojox.widget.FileInputAuto
	//	that does not display an input node, but rather only a button
	// 	and otherwise behaves just like FileInputAuto

	startup: function(){
		// summary: hide our fileInput input field
		this.inherited(arguments);
		this._off = dojo.style(this.inputNode,"width");
		this.inputNode.style.display = "none";
		this._fixPosition();
	},

	_fixPosition: function(){
		// summary: in this case, set the button under where the visible button is
		if(dojo.isIE){
			dojo.style(this.fileInput,"width","1px");
		}else{
			dojo.style(this.fileInput,"left","-"+(this._off)+"px");
		}
	},

	_onClick: function(e){
		// summary: onclick, we need to reposition our newly created input type="file"
		this.inherited(arguments);
		this._fixPosition();
	}
});
<|endoftext|>


<commit_before>/* global analytics, dataLayer */

import Cookies from 'js-cookie';
import uuid from 'uuid';

function ensureSetup() {
  window.dataLayer = window.dataLayer || [];
  window.analytics = window.analytics || {
    track: function() {},
    page: function() {},
    identify: function() {}
  };
}

function getClientUuid() {
  var foundCookie = Cookies.get('clientUuid');
  if (foundCookie) {
    return foundCookie;
  }

  var newUuid = uuid.v4();
  Cookies.set('clientUuid', newUuid, { expires: 365 });
  return newUuid;
}

function getGaCookieId() {
  return Cookies.get('_ga') || 'not-set';
}

function eventMetaData() {
  return {
    sentTimestamp: (+new Date()).toString(),
    clientUuid: getClientUuid().toString(),
    gaCookieId: getGaCookieId().toString()
  };
}

/**
 * Analytics wrapper for the Segment to GTM integration
 */
const lmnAnalytics = {
  track: function (action, properties, options, callback) {
    ensureSetup();

    if (typeof options === 'function') {
      callback = options;
      options = null;
    }
    if (typeof properties === 'function') {
      callback = properties;
      options = null;
      properties = {};
    }
    if (!properties) {
      properties = {};
    }
    if (!properties.category) {
      properties.category = 'All';
    }

    Object.assign(properties, eventMetaData());
    // reconstruct arguments after sanitisation to proxy through
    const args = [action, properties, options, callback].filter(x => x);
    analytics.track.apply(this, args);

    dataLayer.push(
      Object.assign(properties, {
        event: action,
        action: action,
        category: properties.category,
        label: properties.label,
        value: properties.value
      })
    );
    if (callback) {
      callback();
    }
  },
  page: function (category, name, properties, options, callback) {
    ensureSetup();

    if (typeof options === 'function') {
      callback = options;
      options = null;
    }
    if (typeof properties === 'function') {
      callback = properties;
      options = null;
      properties = {};
    }
    if (typeof name === 'function') {
      callback = name;
      options = null;
      properties = {};
      name = null;
    }
    if (typeof name === 'object') {
      options = properties;
      properties = name;
      name = null;
    }
    if (typeof category === 'string' && typeof name !== 'string') {
      name = null;
    }
    if (!properties) {
      properties = {};
    }

    Object.assign(properties, eventMetaData());
    // reconstruct arguments after sanitisation to proxy through
    const args = [category, name, properties, options, callback].filter(x => x);
    analytics.page.apply(this, args);

    dataLayer.push(Object.assign(properties, {
      event: category,
      pageName: name
    }));

    if (callback) {
      callback();
    }
  },
  identify: function (id, traits, options, callback) {
    ensureSetup();

    if (typeof options === 'function') {
      callback = options;
      options = null;
    }
    if (typeof traits === 'function') {
      callback = traits;
      options = null;
      traits = null;
    }

    traits = Object.assign(traits || {}, eventMetaData());
    // reconstruct arguments after sanitisation to proxy through
    const args = [id, traits, options, callback].filter(x => x);
    analytics.identify.apply(this, args);

    dataLayer.push(
      Object.assign(
        traits,
        { user: { userId: id } }
      )
    );

    if (traits && typeof traits !== 'function') {
      Object.keys(traits).forEach(trait => {
        if (trait.startsWith('Experiment:')) {
          dataLayer.push(
            Object.assign(eventMetaData(),
            { experimentName: trait, experimentVariant: traits[trait] }
          ));
        }
      });
    }

    if (callback) {
      callback();
    }
    return this;
  },
  impression: function (impressions) {
    ensureSetup();
    impressions.forEach(impression => {
      analytics.track(
        'Viewed Impression',
        Object.assign(impression, eventMetaData())
      );
    });

    dataLayer.push(
      Object.assign(eventMetaData(),
      { ecommerce: { impressions: impressions } }
    ));
  },
  ready: function (callback) {
    if (callback) {
      callback();
    }
    return this;
  }
};

export default lmnAnalytics;
<commit_msg>Just linter things<commit_after>/* global analytics, dataLayer */

import Cookies from 'js-cookie';
import uuid from 'uuid';

function ensureSetup() {
  window.dataLayer = window.dataLayer || [];
  window.analytics = window.analytics || {
    track: function () {},
    page: function () {},
    identify: function () {}
  };
}

function getClientUuid() {
  var foundCookie = Cookies.get('clientUuid');
  if (foundCookie) {
    return foundCookie;
  }

  var newUuid = uuid.v4();
  Cookies.set('clientUuid', newUuid, { expires: 365 });
  return newUuid;
}

function getGaCookieId() {
  return Cookies.get('_ga') || 'not-set';
}

function eventMetaData() {
  return {
    sentTimestamp: (+new Date()).toString(),
    clientUuid: getClientUuid().toString(),
    gaCookieId: getGaCookieId().toString()
  };
}

/**
 * Analytics wrapper for the Segment to GTM integration
 */
const lmnAnalytics = {
  track: function (action, properties, options, callback) {
    ensureSetup();

    if (typeof options === 'function') {
      callback = options;
      options = null;
    }
    if (typeof properties === 'function') {
      callback = properties;
      options = null;
      properties = {};
    }
    if (!properties) {
      properties = {};
    }
    if (!properties.category) {
      properties.category = 'All';
    }

    Object.assign(properties, eventMetaData());
    // reconstruct arguments after sanitisation to proxy through
    const args = [action, properties, options, callback].filter(x => x);
    analytics.track.apply(this, args);

    dataLayer.push(
      Object.assign(properties, {
        event: action,
        action: action,
        category: properties.category,
        label: properties.label,
        value: properties.value
      })
    );
    if (callback) {
      callback();
    }
  },
  page: function (category, name, properties, options, callback) {
    ensureSetup();

    if (typeof options === 'function') {
      callback = options;
      options = null;
    }
    if (typeof properties === 'function') {
      callback = properties;
      options = null;
      properties = {};
    }
    if (typeof name === 'function') {
      callback = name;
      options = null;
      properties = {};
      name = null;
    }
    if (typeof name === 'object') {
      options = properties;
      properties = name;
      name = null;
    }
    if (typeof category === 'string' && typeof name !== 'string') {
      name = null;
    }
    if (!properties) {
      properties = {};
    }

    Object.assign(properties, eventMetaData());
    // reconstruct arguments after sanitisation to proxy through
    const args = [category, name, properties, options, callback].filter(x => x);
    analytics.page.apply(this, args);

    dataLayer.push(Object.assign(properties, {
      event: category,
      pageName: name
    }));

    if (callback) {
      callback();
    }
  },
  identify: function (id, traits, options, callback) {
    ensureSetup();

    if (typeof options === 'function') {
      callback = options;
      options = null;
    }
    if (typeof traits === 'function') {
      callback = traits;
      options = null;
      traits = null;
    }

    traits = Object.assign(traits || {}, eventMetaData());
    // reconstruct arguments after sanitisation to proxy through
    const args = [id, traits, options, callback].filter(x => x);
    analytics.identify.apply(this, args);

    dataLayer.push(
      Object.assign(
        traits,
        { user: { userId: id } }
      )
    );

    if (traits && typeof traits !== 'function') {
      Object.keys(traits).forEach(trait => {
        if (trait.startsWith('Experiment:')) {
          dataLayer.push(
            Object.assign(eventMetaData(),
            { experimentName: trait, experimentVariant: traits[trait] }
          ));
        }
      });
    }

    if (callback) {
      callback();
    }
    return this;
  },
  impression: function (impressions) {
    ensureSetup();
    impressions.forEach(impression => {
      analytics.track(
        'Viewed Impression',
        Object.assign(impression, eventMetaData())
      );
    });

    dataLayer.push(
      Object.assign(eventMetaData(),
      { ecommerce: { impressions: impressions } }
    ));
  },
  ready: function (callback) {
    if (callback) {
      callback();
    }
    return this;
  }
};

export default lmnAnalytics;
<|endoftext|>


<commit_before>'use strict';

/**
 * Cues:
 * endingSoon is called a little before auto ending is triggered or just before manual ending is called
 * ending is called when auto ending is triggered or just before slot ending
 *
 * @param {{entry: Entry, cues: {endingSoon: function, ending: function}, fetchVideo: function(Entry):Video, playersPool: PlayersPool, transitionDuration: number}} config
 * @returns {PlaybackSlot}
 */
function playbackSlot(config) {

  var _config = config,
    _playersPool = _config.playersPool,

    _loaded = false,
    _started = false,
    _ended = false,
    _error = false,

    _player = null,
    _loadPromise = null,
    _endPromise = null;

  function getVideo() {
    return _config.fetchVideo(_config.entry);
  }

  function checkStage(predicate, method, requiredStage) {
    if (!predicate) {
      throw new Error('The slot "' + method + '" method should be called only when the ' + requiredStage
      + ' stage is completed');
    }
  }

  function dispose() {
    _playersPool.releasePlayer(_player);
  }

  /**
   * Tries to load the video associated to the slot's entry.
   *
   * If it succeed the slot can be started. If it fails, the slot is automatically "ended" and can not be started.
   *
   * @return {Promise}
   */
  function load() {
    if (!_loadPromise) {
      var video = getVideo();

      _player = _playersPool.getPlayer(video.provider);

      _loadPromise =
        _player.loadById(video.id)
          .then(function() {
            _loaded = true;
          })
          .catch(function(e) {
            _error = true;
            end();
            // propagate the rejection
            return Promise.reject(e);
          });
    }

    return _loadPromise;
  }

  /**
   * Kicks the slot in by starting the player and fading it in.
   *
   * This method can only be called once the loading stage successfully completed.
   *
   * @param {{audioGain: number}} config
   */
  function start(config) {
    checkStage(_loaded, 'start', 'loading');
    _started = true;

    _player.play(config);
    _player.fadeIn({duration: _config.transitionDuration});
  }

  /**
   * Initiate slot termination, unregister the cues callbacks and frees the underlying resources (player etc.).
   *
   * @return {Promise}
   */
  function end() {
    checkStage((_loaded || _started) && !_error, 'end', 'loading or playing');

    if (!_endPromise) {
      if (!_started) {
        _endPromise = Promise.resolve();
      } else {
        _endPromise =
          _player
            .fadeOut({duration: _config.transitionDuration})
            .then(function() {
              _player.stop();
            });
      }

      _endPromise
        .then(function() {
          dispose();
          _ended = true;
        });
    }

    return _endPromise;
  }

  /**
   * @typedef PlaybackSlot
   * @name PlaybackSlot
   */
  var PlaybackSlot = {
    /**
     * @return {Entry}
     */
    get entry() {
      return _config.entry
    },
    /**
     * @returns {Video}
     */
    get video() {
      return getVideo();
    },
    load: load,
    start: start,
    end: end
  };

  return Object.freeze(PlaybackSlot);
}

module.exports = playbackSlot;
<commit_msg>- added ending callback execution<commit_after>'use strict';

/**
 * Cues:
 * endingSoon is called a little before auto ending is triggered or just before manual ending is called
 * ending is called when auto ending is triggered or just before slot ending
 *
 * @param {{entry: Entry, cues: {endingSoon: function, ending: function}, fetchVideo: function(Entry):Video, playersPool: PlayersPool, transitionDuration: number}} config
 * @returns {PlaybackSlot}
 */
function playbackSlot(config) {

  var _config = config,
    _playersPool = _config.playersPool,

    _endingSoonCb = function() {
      _endingSoonCb.called = true;
      sandBoxedExecute(_config.cues.endingSoon);
    },
    _endingCb = function() {
      sandBoxedExecute(_config.cues.ending);
    },

    _loaded = false,
    _started = false,
    _ended = false,
    _error = false,

    _player = null,
    _loadPromise = null,
    _endPromise = null;

  function getVideo() {
    return _config.fetchVideo(_config.entry);
  }

  function checkStage(predicate, method, requiredStage) {
    if (!predicate) {
      throw new Error('The slot "' + method + '" method should be called only when the ' + requiredStage
      + ' stage is completed');
    }
  }

  /**
   * Essentially a try / catch around the given function.
   *
   * @param {function} fn
   */
  function sandBoxedExecute(fn) {
    try {
      fn();
    } catch (e) {
      console.error('An error occurred while executing the sand-boxed function %s', fn);
      console.error(e);
    }
  }

  function dispose() {
    _playersPool.releasePlayer(_player);
  }

  /**
   * Tries to load the video associated to the slot's entry.
   *
   * If it succeed the slot can be started. If it fails, the slot is automatically "ended" and can not be started.
   *
   * @return {Promise}
   */
  function load() {
    if (!_loadPromise) {
      var video = getVideo();

      _player = _playersPool.getPlayer(video.provider);

      _loadPromise =
        _player.loadById(video.id)
          .then(function() {
            _loaded = true;
          })
          .catch(function(e) {
            _error = true;
            end();
            // propagate the rejection
            return Promise.reject(e);
          });
    }

    return _loadPromise;
  }

  /**
   * Kicks the slot in by starting the player and fading it in.
   *
   * This method can only be called once the loading stage successfully completed.
   *
   * @param {{audioGain: number}} config
   */
  function start(config) {
    checkStage(_loaded, 'start', 'loading');
    _started = true;

    _player.play(config);
    _player.fadeIn({duration: _config.transitionDuration});
  }

  /**
   * Initiate slot termination, unregister the cues callbacks and frees the underlying resources (player etc.).
   *
   * @return {Promise}
   */
  function end() {
    checkStage((_loaded || _started) && !_error, 'end', 'loading or playing');

    if (!_endPromise) {
      if (!_started) {
        _endPromise = Promise.resolve();
      } else {
        _endPromise =
          _player
            .fadeOut({duration: _config.transitionDuration})
            .then(function() {
              _player.stop();
            });
      }

      _endPromise
        .then(function() {
          if (!_endingSoonCb.called) {
            _endingSoonCb();
          }
          _endingCb();
          dispose();
          _ended = true;
        });
    }

    return _endPromise;
  }

  /**
   * @typedef PlaybackSlot
   * @name PlaybackSlot
   */
  var PlaybackSlot = {
    /**
     * @return {Entry}
     */
    get entry() {
      return _config.entry
    },
    /**
     * @returns {Video}
     */
    get video() {
      return getVideo();
    },
    load: load,
    start: start,
    end: end
  };

  return Object.freeze(PlaybackSlot);
}

module.exports = playbackSlot;
<|endoftext|>


<commit_before>
/*
  Utility to make a sub-tree of the DOM inert. Inert means the elements cannot be interacted
  with and they cannot be focused via script, pointer or keyboard.

  inert attribute was [removed](https://html5.org/r/8536) [tweet by steve](https://twitter.com/stevefaulkner/status/443075900201259008)
  but definition of [inert subtrees](http://www.w3.org/html/wg/drafts/html/master/editing.html#inert-subtrees) remains.

  [implementation idea by Vasilis](http://codepen.io/vasilisvg/pen/scowI)
  [inert attribute polyfill by GoogleChrome](https://github.com/GoogleChrome/inert-polyfill)

  [Gecko Bug: Inert Attribute](https://bugzilla.mozilla.org/show_bug.cgi?id=921504)
  [Chromium Bug: Inert Attribute](https://code.google.com/p/chromium/issues/detail?id=269846)
  [Chromium Bug: Inert Subtree](https://code.google.com/p/chromium/issues/detail?id=241699)
  [WebKit Bug: Inert Subtree](https://bugs.webkit.org/show_bug.cgi?id=110952)
*/

import nodeArray from '../util/node-array';
import queryFocusable from '../query/focusable';
import elementDisabled from '../element/disabled';
import {getParentComparator} from '../util/compare-position';

function makeElementInert(element) {
  return elementDisabled(element, true);
}

function undoElementInert(element) {
  return elementDisabled(element, false);
}

const observerConfig = {
  attributes: true,
  childList: true,
  subtree: true,
  attributeFilter: ['tabindex'],
};

class InertSubtree {
  constructor({context, filter} = {}) {
    this._context = nodeArray(context || document.documentElement)[0];
    this._filter = nodeArray(filter);

    this.disengage = this.disengage.bind(this);
    this.handleMutation = this.handleMutation.bind(this);
    this.renderInert = this.renderInert.bind(this);
    this.filterElements = this.filterElements.bind(this);

    const focusable = queryFocusable({
      context: this._context,
      includeContext: true,
      strategy: 'all',
    });

    this.renderInert(focusable);
    this.startObserver();
  }

  disengage() {
    if (!this._context) {
      return;
    }

    undoElementInert(this._context);
    [].forEach.call(this._context.querySelectorAll('[data-ally-disabled], :disabled'), undoElementInert);

    this._filter = null;
    this._context = null;
    this._observer && this._observer.disconnect();
    this._observer = null;
  }

  listQueryFocusable(list) {
    return list
      // find all focusable elements within the given contexts
      .map(element => queryFocusable({context: element, includeContext: true, strategy: 'all'}))
      // flatten nested arrays
      .reduce((previous, current) => previous.concat(current), []);
  }

  renderInert(elements) {
    elements.filter(this.filterElements).forEach(makeElementInert);
  }

  filterElements(element) {
    // ignore elements within the exempted sub-trees
    const isParentOfElement = getParentComparator({element, includeSelf: true});
    return !this._filter.some(isParentOfElement);
  }

  startObserver() {
    if (!window.MutationObserver) {
      // not supporting IE10 via Mutation Events, because they're too expensive
      // https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Mutation_events
      return;
    }
    // http://caniuse.com/#search=mutation
    // https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
    this._observer = new MutationObserver(mutations => mutations.forEach(this.handleMutation));
    this._observer.observe(this._context, observerConfig);
  }

  handleMutation(mutation) {
    if (mutation.type === 'childList') {
      const addedElements = nodeArray(mutation.addedNodes).filter(element => element.nodeType === Node.ELEMENT_NODE);
      if (!addedElements.length) {
        return;
      }

      const addedFocusableElements = this.listQueryFocusable(addedElements);
      this.renderInert(addedFocusableElements);
    } else if (mutation.type === 'attributes' && this.filterElements(mutation.target)) {
      makeElementInert(mutation.target);
    }
  }
}

export default function({context, filter} = {}) {
  const service = new InertSubtree({context, filter});
  return { disengage: service.disengage };
}
<commit_msg>fix(maintain/disabled): remember disabled elements for disengage<commit_after>
/*
  Utility to make a sub-tree of the DOM inert. Inert means the elements cannot be interacted
  with and they cannot be focused via script, pointer or keyboard.

  inert attribute was [removed](https://html5.org/r/8536) [tweet by steve](https://twitter.com/stevefaulkner/status/443075900201259008)
  but definition of [inert subtrees](http://www.w3.org/html/wg/drafts/html/master/editing.html#inert-subtrees) remains.

  [implementation idea by Vasilis](http://codepen.io/vasilisvg/pen/scowI)
  [inert attribute polyfill by GoogleChrome](https://github.com/GoogleChrome/inert-polyfill)

  [Gecko Bug: Inert Attribute](https://bugzilla.mozilla.org/show_bug.cgi?id=921504)
  [Chromium Bug: Inert Attribute](https://code.google.com/p/chromium/issues/detail?id=269846)
  [Chromium Bug: Inert Subtree](https://code.google.com/p/chromium/issues/detail?id=241699)
  [WebKit Bug: Inert Subtree](https://bugs.webkit.org/show_bug.cgi?id=110952)
*/

import nodeArray from '../util/node-array';
import queryFocusable from '../query/focusable';
import elementDisabled from '../element/disabled';
import {getParentComparator} from '../util/compare-position';

function makeElementInert(element) {
  return elementDisabled(element, true);
}

function undoElementInert(element) {
  return elementDisabled(element, false);
}

const observerConfig = {
  attributes: true,
  childList: true,
  subtree: true,
  attributeFilter: ['tabindex'],
};

class InertSubtree {
  constructor({context, filter} = {}) {
    this._context = nodeArray(context || document.documentElement)[0];
    this._filter = nodeArray(filter);
    this._inertElementCache = [];

    this.disengage = this.disengage.bind(this);
    this.handleMutation = this.handleMutation.bind(this);
    this.renderInert = this.renderInert.bind(this);
    this.filterElements = this.filterElements.bind(this);

    const focusable = queryFocusable({
      context: this._context,
      includeContext: true,
      strategy: 'all',
    });

    this.renderInert(focusable);
    this.startObserver();
  }

  disengage() {
    if (!this._context) {
      return;
    }

    undoElementInert(this._context);
    this._inertElementCache.forEach((element) => {
      const index = this._inertElementCache.indexOf(element);

      if (index > -1) {
        this._inertElementCache = this._inertElementCache.splice(index, 1);
      }

      undoElementInert(element);
    });

    this._filter = null;
    this._context = null;
    this._observer && this._observer.disconnect();
    this._observer = null;
  }

  listQueryFocusable(list) {
    return list
      // find all focusable elements within the given contexts
      .map(element => queryFocusable({context: element, includeContext: true, strategy: 'all'}))
      // flatten nested arrays
      .reduce((previous, current) => previous.concat(current), []);
  }

  renderInert(elements) {
    elements.filter(this.filterElements).forEach((element) => {
      this._inertElementCache.push(element);
      makeElementInert(element);
    });
  }

  filterElements(element) {
    // ignore elements within the exempted sub-trees
    const isParentOfElement = getParentComparator({element, includeSelf: true});
    return !this._filter.some(isParentOfElement);
  }

  startObserver() {
    if (!window.MutationObserver) {
      // not supporting IE10 via Mutation Events, because they're too expensive
      // https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Mutation_events
      return;
    }
    // http://caniuse.com/#search=mutation
    // https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
    this._observer = new MutationObserver(mutations => mutations.forEach(this.handleMutation));
    this._observer.observe(this._context, observerConfig);
  }

  handleMutation(mutation) {
    if (mutation.type === 'childList') {
      const addedElements = nodeArray(mutation.addedNodes).filter(element => element.nodeType === Node.ELEMENT_NODE);
      if (!addedElements.length) {
        return;
      }

      const addedFocusableElements = this.listQueryFocusable(addedElements);
      this.renderInert(addedFocusableElements);
    } else if (mutation.type === 'attributes' && this.filterElements(mutation.target)) {
      this._inertElementCache.push(mutation.target);
      makeElementInert(mutation.target);
    }
  }
}

export default function({context, filter} = {}) {
  const service = new InertSubtree({context, filter});
  return { disengage: service.disengage };
}
<|endoftext|>