diff --git a/.gitignore b/.gitignore
index 1eece4614..4f32bd42c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
.DS_Store
-build/
css/index.css
lib/
node_modules/
diff --git a/build/i18n/es.js b/build/i18n/es.js
new file mode 100755
index 000000000..e1c0e4d1a
--- /dev/null
+++ b/build/i18n/es.js
@@ -0,0 +1,94 @@
+Auth0.registerLanguageDictionary("es", {
+ error: {
+ forgotPassword: {
+ "too_many_requests": "Se ha alcanzado el límite de intentos para restablecer su contraseña. Por favor, aguarde unos minutos.",
+ "lock.fallback": "Ocurrió un error al restablecer su contraseña."
+ },
+ login: {
+ "blocked_user": "El usuario se encuentra bloqueado.",
+ "invalid_user_password": "Credenciales inválidas.",
+ "lock.fallback": "Ocurrió un error al inciar sesión.",
+ "lock.invalid_code": "Código inválido.",
+ "lock.invalid_email_password": "Correo y contraseña inválidos.",
+ "lock.invalid_username_password": "Usuario y contraseña inválidos.",
+ "lock.network": "Ocurrió un error de red. Por favor, verifique su conexión.",
+ "lock.popup_closed": "Se ha cerrado la ventana emergente.",
+ "lock.unauthorized": "Acceso denegado. Por favor, intente nuevamente.",
+ "password_change_required": "Debe actualizar su contraseña porque es la primera vez que ingresa o porque la contraseña está vencida.",
+ "password_leaked": "Este intento ha sido bloqueado ya que usted utilizó la misma contraseña para registrarse en otra aplicación que tuvo una filtración reciente. Hemos enviado un email con las instrucciones."
+ },
+ passwordless: {
+ "bad.email": "Correo inválido",
+ "bad.phone_number": "Teléfono inválido",
+ "lock.fallback": "Ocurrió un error durante el envío"
+ },
+ signUp: {
+ "invalid_password": "La contraseña es muy débil.",
+ "lock.fallback": "Ocurrió un error durante el registro.",
+ "user_exists": "El usuario ya existe.",
+ "username_exists": "El nombre de usuario se encuentra en uso."
+ }
+ },
+ success: {
+ logIn: "Sesión iniciada con éxito.",
+ forgotPassword: "Hemos enviado un correo para completar el restablecimiento de su contraseña.",
+ magicLink: "Hemos enviado un correo para inciar sesión a
to %s.",
+ signUp: "Registro completado exitosamente."
+ },
+ blankErrorHint: "Requerido",
+ codeInputPlaceholder: "código",
+ databaseEnterpriseLoginInstructions: "",
+ databaseEnterpriseAlternativeLoginInstructions: "o",
+ databaseSignUpInstructions: "",
+ databaseAlternativeSignUpInstructions: "o",
+ emailInputPlaceholder: "correo@ejemplo.com",
+ enterpriseLoginIntructions: "Inicie sesión con sus credenciales corporativas.",
+ enterpriseActiveLoginInstructions: "Ingrese las credenciales corporativas de %s.",
+ failedLabel: "Error!",
+ forgotPasswordAction: "¿Olvidó su contraseña?",
+ forgotPasswordInstructions: "Por favor ingrese su dirección de correo. Le enviaremos las instrucciones para restablecer su contrseña.",
+ invalidErrorHint: "Inválido",
+ lastLoginInstructions: "La última vez inició sesión con",
+ loginAtLabel: "Iniciar en %s",
+ loginLabel: "Iniciar sesión",
+ loginWithLabel: "Iniciar con %s",
+ notYourAccountAction: "¿No es su cuenta?",
+ passwordInputPlaceholder: "su contraseña",
+ passwordStrength: {
+ containsAtLeast: "Contener al menos %d de los siguientes %d tipos de caracteres:",
+ identicalChars: "No más de %d caracteres idénticos juntos (ej., \"%s\" no está permitido)",
+ nonEmpty: "Se requiere una contraseña no vacía",
+ numbers: "Números (ej. 0-9)",
+ lengthAtLeast: "Como mínimo de %d caracteres de longitud",
+ lowerCase: "Letras minúsculas (a-z)",
+ shouldContain: "Debe contener:",
+ specialCharacters: "Caracteres especiales (ej. !@#$%^&*)",
+ upperCase: "Letras mayúsculas (A-Z)"
+ },
+ passwordlessEmailAlternativeInstructions: "También puede ingresar su email
para iniciar sesión o registrarse",
+ passwordlessEmailCodeInstructions: "Se ha enviado un correo con el código a %s.",
+ passwordlessEmailInstructions: "Ingrese su email para iniciar sesión
o registrarse",
+ passwordlessSMSAlternativeInstructions: "También puede ingresar su teléfono
para iniciar sesión o registrarse",
+ passwordlessSMSCodeInstructions: "Se ha enviado un SMS con el código
a %s.",
+ passwordlessSMSInstructions: "Ingrese su teléfono para iniciar sesión
o registrarse",
+ phoneNumberInputPlaceholder: "número de teléfono",
+ resendCodeAction: "¿No recibió el código?",
+ resendLabel: "Reenviar",
+ resendingLabel: "Reenviando...",
+ retryLabel: "Reintentar",
+ sentLabel: "Enviado!",
+ signUpLabel: "Registrarse",
+ signUpTerms: "",
+ signUpWithLabel: "Registrarse con %s",
+ socialLoginInstructions: "",
+ socialSignUpInstructions: "",
+ ssoEnabled: "Inicio de sesión único activado",
+ unrecoverableError: "Ocurrió un error.
Por favor, contacte a soporte técnico.",
+ usernameFormatErrorHint: "1-15 letras, números y \"_\"",
+ usernameInputPlaceholder: "su usuario",
+ usernameOrEmailInputPlaceholder: "usuario/correo electrónico",
+ title: "Auth0",
+ welcome: "Bienvenido %s!",
+ windowsAuthInstructions: "Usted se encuentra conectado desde su red corporativa…",
+ windowsAuthLabel: "Autenticación de Windows"
+});
diff --git a/build/i18n/it.js b/build/i18n/it.js
new file mode 100755
index 000000000..059f1a9ad
--- /dev/null
+++ b/build/i18n/it.js
@@ -0,0 +1,94 @@
+Auth0.registerLanguageDictionary("it", {
+ error: {
+ forgotPassword: {
+ "too_many_requests": "Lei è stato raggiunto il limite di tentativi di modifica della password . Si prega di attendere prima di riprovare.",
+ "lock.fallback": "Ci dispiace, qualcosa è andato storto quando si richiede la modifica della password."
+ },
+ login: {
+ "blocked_user": "L’utente è bloccato.",
+ "invalid_user_password": "Credenziali non corrette.",
+ "lock.fallback": "Ci dispiace, qualcosa è andato storto quando si tenta di accedere.",
+ "lock.invalid_code": "Codice errato.",
+ "lock.invalid_email_password": "email o password sbagliata.",
+ "lock.invalid_username_password": "Nome utente o password sbagliata.",
+ "lock.network": "Non siamo riusciti a raggiungere il server. Si prega di controllare la connessione e riprova.",
+ "lock.popup_closed": "Finestra popup chiusa. Riprova per favore.",
+ "lock.unauthorized": "Autorizzazioni non sono state concesse. Riprova per favore.",
+ "password_change_required": "È necessario aggiornare la password perché questa è la prima volta che si esegue il login, or perché la password è scaduta.",
+ "password_leaked": "Questo accesso è stato bloccato perché la password è trapelato in un altro sito . Ti abbiamo inviato una email con le istruzioni su come sbloccarla."
+ },
+ passwordless: {
+ "bad.email": "L’email non è valido ",
+ "bad.phone_number": "Il numero di telefono non è valido",
+ "lock.fallback": "Ci dispiace, qualcosa è andato storto"
+ },
+ signUp: {
+ "invalid_password": "La password è troppo debole.",
+ "lock.fallback": "Ci dispiace, qualcosa è andato storto quando si tenta di iscriversi.",
+ "user_exists": "L’utente esiste già.",
+ "username_exists": "Il nome utente esiste già."
+ }
+ },
+ success: {
+ logIn: "Grazie per il login.",
+ forgotPassword: "Abbiamo appena inviato un email per reimpostare la password.",
+ magicLink: "La abbiamo inviato un link per il login
a %s.",
+ signUp: "Grazie per esserti iscritto."
+ },
+ blankErrorHint: "Non può essere vuoto",
+ codeInputPlaceholder: "il Suo codice",
+ databaseEnterpriseLoginInstructions: "",
+ databaseEnterpriseAlternativeLoginInstructions: "o",
+ databaseSignUpInstructions: "",
+ databaseAlternativeSignUpInstructions: "or",
+ emailInputPlaceholder: "email@example.com",
+ enterpriseLoginIntructions: "Effettuare il login con le credenziali aziendali.",
+ enterpriseActiveLoginInstructions: "Si prega di inserire le credenziali aziendali a %s.",
+ failedLabel: "Fallito!",
+ forgotPasswordAction: "Non ricordo la password?",
+ forgotPasswordInstructions: "Si prega d’inserare il Suo indirizzo email. La invieremo una email per reimpostare la password.",
+ invalidErrorHint: "Non valido",
+ lastLoginInstructions: "L’ultima volta Lei ha effettuato l’accesso con",
+ loginAtLabel: "Accedere a %s",
+ loginLabel: "Accesso",
+ loginWithLabel: "Accede con %s",
+ notYourAccountAction: "Non è il suo account?",
+ passwordInputPlaceholder: "La sua password",
+ passwordStrength: {
+ containsAtLeast: "Essa deve contenere almeno %d dei seguenti %d tipi di caratteri:",
+ identicalChars: "Non più di %d caratteri identici in una fila (e.g., \"%s\" non autorizzato)",
+ nonEmpty: "La password non vuota richiesta",
+ numbers: "Numeri (i.e. 0-9)",
+ lengthAtLeast: "Almeno %d caratteri di lunghezza",
+ lowerCase: "Lettere minuscole (a-z)",
+ shouldContain: "Dovrebbe contenere:",
+ specialCharacters: "Caratteri speciali (e.g. !@#$%^&*)",
+ upperCase: "Caratteri maiuscoli (A-Z)"
+ },
+ passwordlessEmailAlternativeInstructions: "Altrimenti, si prega d’inserare la Sua email per accedere
o creare un account",
+ passwordlessEmailCodeInstructions: "Una email con il codice è stato inviato %s.",
+ passwordlessEmailInstructions: "Si prega d’inserare la Sua email
o creare un account",
+ passwordlessSMSAlternativeInstructions: "Altrimenti, si prega d’inserare il numero di telefono per accedere
o creare un account",
+ passwordlessSMSCodeInstructions: "Un SMS con il codice è stato inviato
a %s.",
+ passwordlessSMSInstructions: "Si prega d’inserare il numero di telefono
o creare un account",
+ phoneNumberInputPlaceholder: "il Suo numero di telefono",
+ resendCodeAction: "Non ha ottentuo il codice?",
+ resendLabel: "Inviare di nuovo",
+ resendingLabel: "Reinvio...",
+ retryLabel: "Riprovare per favore",
+ sentLabel: "Inviato!",
+ signUpLabel: "Registrazione",
+ signUpTerms: "",
+ signUpWithLabel: "Registra con %s",
+ socialLoginInstructions: "",
+ socialSignUpInstructions: "",
+ ssoEnabled: "Single Sign-On abilitati",
+ unrecoverableError: "Qualcosa è andato storto.
Si prega di contattare il supporto tecnico.",
+ usernameFormatErrorHint: "Si prega di utilizzare 1-15 lettere, numeri e \"_\"",
+ usernameInputPlaceholder: "il Suo nome utente",
+ usernameOrEmailInputPlaceholder: "il Suo nome utente or email", // TODO
+ title: "Auth0",
+ welcome: "Benvenuto %s!",
+ windowsAuthInstructions: "Si è connessi dalla rete aziendale…",
+ windowsAuthLabel: "Autenticazione Windows"
+});
diff --git a/build/i18n/pt-br.js b/build/i18n/pt-br.js
new file mode 100755
index 000000000..e43199391
--- /dev/null
+++ b/build/i18n/pt-br.js
@@ -0,0 +1,94 @@
+Auth0.registerLanguageDictionary("pt-br", {
+ error: {
+ forgotPassword: {
+ "too_many_requests": "Você atingiu o limite máximo de tentativas. Por favor aguarde antes de tentar novamente.",
+ "lock.fallback": "Sentimos muito, mas algo deu errado ao requisitar a mudança de senha."
+ },
+ login: {
+ "blocked_user": "O usuário está bloqueado.",
+ "invalid_user_password": "Credenciais inválidas.",
+ "lock.fallback": "Sentimos muito, mas algo deu errado ao tentar entrar.",
+ "lock.invalid_code": "Código inválido.",
+ "lock.invalid_email_password": "Email ou senha inválidos.",
+ "lock.invalid_username_password": "Usuário ou senha inválidos.",
+ "lock.network": "Não foi possível acessar o servidor. Por favor verifique sua conexão e tente novamente.",
+ "lock.popup_closed": "Popup fechada. Tente novamente.",
+ "lock.unauthorized": "Permissões não foram concedidas. Tente novamente.",
+ "password_change_required": "Você precisa atualizar sua senha porque é seu primeiro login, ou porque sua senha expirou.",
+ "password_leaked": "Esse login está bloqueado porque sua senha foi vazada em outro website. Nós lhe enviamos um email com instruções sobre como desbloqueá-lo."
+ },
+ passwordless: {
+ "bad.email": "O email é inválido",
+ "bad.phone_number": "O número de telefone é inválido",
+ "lock.fallback": "Sentimos muito, algo deu errado"
+ },
+ signUp: {
+ "invalid_password": "A senha é muito fraca.",
+ "lock.fallback": "Sentimos muito, algo deu errado ao tentar se inscrever.",
+ "user_exists": "O usuário já existe.",
+ "username_exists": "O nome de usuário já existe."
+ }
+ },
+ success: {
+ logIn: "Obrigado por fazer login.",
+ forgotPassword: "Acabamos de enviar um email para resetar sua senha.",
+ magicLink: "Nós enviamos um link para fazer login
em %s.",
+ signUp: "Obrigado por se inscrever."
+ },
+ blankErrorHint: "Não pode ser em branco",
+ codeInputPlaceholder: "seu código",
+ databaseEnterpriseLoginInstructions: "",
+ databaseEnterpriseAlternativeLoginInstructions: "ou",
+ databaseSignUpInstructions: "",
+ databaseAlternativeSignUpInstructions: "ou",
+ emailInputPlaceholder: "seu@exemplo.com",
+ enterpriseLoginIntructions: "Entre com suas credenciais corporativas.",
+ enterpriseActiveLoginInstructions: "Por favor entre com suas credenciais corporativas em %s.",
+ failedLabel: "Falha!",
+ forgotPasswordAction: "Esqueceu sua senha?",
+ forgotPasswordInstructions: "Por favor digite seu endereço de email. Enviarmos um email para resetar sua senha.",
+ invalidErrorHint: "Inválido",
+ lastLoginInstructions: "Na última vez você entrou com",
+ loginAtLabel: "Login em %s",
+ loginLabel: "Login",
+ loginWithLabel: "Login com %s",
+ notYourAccountAction: "Não é sua conta?",
+ passwordInputPlaceholder: "sua senha",
+ passwordStrength: {
+ containsAtLeast: "Contenha no mínimo %d dos seguintes %d tipos de caracteres:",
+ identicalChars: "Não mais de %d caracteres idênticos em sequência (ex: \"%s\" não é permitido)",
+ nonEmpty: "Senha não pode ser em branco",
+ numbers: "Números (0-9)",
+ lengthAtLeast: "No mínimo %d caracteres",
+ lowerCase: "Letras minúsculas (a-z)",
+ shouldContain: "Deve conter:",
+ specialCharacters: "Caracteres especiais (ex: !@#$%^&*)",
+ upperCase: "Letras maiúsculas (A-Z)"
+ },
+ passwordlessEmailAlternativeInstructions: "Senão, digite seu email para entrar
ou criar uma conta",
+ passwordlessEmailCodeInstructions: "Um email com o código foi enviado para %s.",
+ passwordlessEmailInstructions: "Digite seu email para entrar
ou criar uma conta",
+ passwordlessSMSAlternativeInstructions: "Senão, digite seu telefone para entrar
ou criar uma conta",
+ passwordlessSMSCodeInstructions: "Um SMS com o código foi enviado
para %s.",
+ passwordlessSMSInstructions: "Digite seu telefone para entrar
ou criar uma conta",
+ phoneNumberInputPlaceholder: "seu número de telefone",
+ resendCodeAction: "Não recebeu o código?",
+ resendLabel: "Reenviar",
+ resendingLabel: "Reenviando...",
+ retryLabel: "Tentar novamente",
+ sentLabel: "Enviado!",
+ signUpLabel: "Inscrever",
+ signUpTerms: "",
+ signUpWithLabel: "Inscreva-se com %s",
+ socialLoginInstructions: "",
+ socialSignUpInstructions: "",
+ ssoEnabled: "Single Sign-On habilitado",
+ unrecoverableError: "Algo deu errado.
Por favor entre em contato com o suporte.",
+ usernameFormatErrorHint: "Use 1-15 letras, números e \"_\"",
+ usernameInputPlaceholder: "seu nome de usuário",
+ usernameOrEmailInputPlaceholder: "usuário/email",
+ title: "Auth0",
+ welcome: "Bem-vindo a %s!",
+ windowsAuthInstructions: "Você está conectado da sua rede corporativa…",
+ windowsAuthLabel: "Autenticação Windows"
+});
diff --git a/build/i18n/ru.js b/build/i18n/ru.js
new file mode 100755
index 000000000..faeb1c9be
--- /dev/null
+++ b/build/i18n/ru.js
@@ -0,0 +1,94 @@
+Auth0.registerLanguageDictionary("ru", {
+ error: {
+ forgotPassword: {
+ "too_many_requests": "Вы достигли предельного числа запросов на восстановление пароля. Пожалуйста, подождите перед повторной попыткой.",
+ "lock.fallback": "Произошла непредвиденная ошибка при запросе на восстановление пароля. Приносим свои извинения."
+ },
+ login: {
+ "blocked_user": "Пользователь заблокирован.",
+ "invalid_user_password": "Неверный пароль.",
+ "lock.fallback": "Произошла непредвиденная ошибка при попытке авторизации. Приносим свои извинения.",
+ "lock.invalid_code": "Неверный код.",
+ "lock.invalid_email_password": "Неверный адрес электронной почти или пароль.",
+ "lock.invalid_username_password": "Неверное имя пользователя или пароль.",
+ "lock.network": "Нет доступа к серверу. Пожалуйста, проверьте подключение и повторите попытку.",
+ "lock.popup_closed": "Всплывающее окно закрыто, попробуйте еще раз.",
+ "lock.unauthorized": "Недостаточно полномочий, попробуйте еще раз.",
+ "password_change_required": "Необходимо обновить пароль, посколько Вы совершаете вход в первый раз, или срок действия пароля истек.", // TODO: verify error code
+ "password_leaked": "Эта учетная запись была заблокирована, поскольку произошла утечка Вашего пароля на другом веб-сайте. На Ваш электронный адрес отправлена инструкция по восстановлению учетной записи."
+ },
+ passwordless: {
+ "bad.email": "Недействительный адрес электронной почты",
+ "bad.phone_number": "Недействительный номер телефона",
+ "lock.fallback": "Произошла непредвиденная ошибка. Приносим свои извинения"
+ },
+ signUp: {
+ "invalid_password": "Пароль слишком слабый.", // NOTE: request is not made if pass doesn't satisfy policy
+ "lock.fallback": "Произошла непредвиденная ошибка при попытке создания учетной записи. Приносим свои извинения.",
+ "user_exists": "Пользователь уже существует.",
+ "username_exists": "Имя пользователя уже существует."
+ }
+ },
+ success: { // success messages show above the form or in a confirmation pane
+ logIn: "Спасибо, что вошли.",
+ forgotPassword: "Мы отправили Вам сообщение для восстановления пароля.",
+ magicLink: "Мы отправили Вам ссылку для входа
в %s.",
+ signUp: "Спасибо, что зарегистрировались."
+ },
+ blankErrorHint: "Поле не может быть пустым",
+ codeInputPlaceholder: "Ваш код",
+ databaseEnterpriseLoginInstructions: "",
+ databaseEnterpriseAlternativeLoginInstructions: "или",
+ databaseSignUpInstructions: "",
+ databaseAlternativeSignUpInstructions: "или",
+ emailInputPlaceholder: "yours@example.com",
+ enterpriseLoginIntructions: "Используйте учетные данные Вашей корпоративной учетной записи для входа.",
+ enterpriseActiveLoginInstructions: "Пожалуйста, введите Ваши корпоративные учетные данные по ссылке %s.",
+ failedLabel: "Не удалось совершить действие!",
+ forgotPasswordAction: "Не помните пароль?",
+ forgotPasswordInstructions: "Пожалуйста, введите Ваш адрес электронной почты. Мы отправим Вам сообщение для восстановления пароля.",
+ invalidErrorHint: "Недействительные данные",
+ lastLoginInstructions: "В прошлый раз Вы вошли под",
+ loginAtLabel: "Войдите по ссылке %s",
+ loginLabel: "Вход",
+ loginWithLabel: "Вход через %s",
+ notYourAccountAction: "Не Ваша учетная запись?",
+ passwordInputPlaceholder: "Ваш пароль",
+ passwordStrength: {
+ containsAtLeast: "Содержать как минимум %d вида символов из следующих %d-х:",
+ identicalChars: "Не более чем %d идентичных символа в ряд (например, \"%s\" не допускается)",
+ nonEmpty: "Пароль не может быть пустым",
+ numbers: "Цифры (0-9)",
+ lengthAtLeast: "Как минимум %d символов",
+ lowerCase: "Строчные буквы (a-z)",
+ shouldContain: "Должен содержать:",
+ specialCharacters: "Специальные символы (например !@#$%^&*)",
+ upperCase: "Прописные буквы (A-Z)"
+ },
+ passwordlessEmailAlternativeInstructions: "Либо введите адрес электронной почты, чтобы войти
или зарегистрироваться",
+ passwordlessEmailCodeInstructions: "Сообщение с кодом отправлено на %s.",
+ passwordlessEmailInstructions: "Введите адрес электронной почты, чтобы войти
или зарегистрироваться",
+ passwordlessSMSAlternativeInstructions: "Либо введите Ваш номер телефона, чтобы войти
или зарегистрироваться",
+ passwordlessSMSCodeInstructions: "СМС с кодом было отправлено
на %s.",
+ passwordlessSMSInstructions: "Введите Ваш номер телефона, чтобы войти
или зарегистрироваться",
+ phoneNumberInputPlaceholder: "Ваш номер телефона",
+ resendCodeAction: "Не получили код?",
+ resendLabel: "Отправить повторный запрос",
+ resendingLabel: "Повторная отправка...",
+ retryLabel: "Повторить попытку",
+ sentLabel: "Отправлено!",
+ signUpLabel: "Зарегистрироваться",
+ signUpTerms: "",
+ signUpWithLabel: "Зарегистрироваться через %s",
+ socialLoginInstructions: "",
+ socialSignUpInstructions: "",
+ ssoEnabled: "Единый вход включен",
+ unrecoverableError: "Произошла непредвиденная ошибка.
Пожалуйста, обратитесь в службу технической поддержки.",
+ usernameFormatErrorHint: "Используйте 1-15 букв, цифр и \"_\"",
+ usernameInputPlaceholder: "Ваше имя пользователя",
+ useranmeOrEmailInputPlaceholder: "электронной почты/пользователя", // TODO review
+ title: "Auth0",
+ welcome: "Добро пожаловать, %s!",
+ windowsAuthInstructions: "Вы подключены через корпоративную сеть…",
+ windowsAuthLabel: "Аутентификация Windows"
+});
diff --git a/build/lock.js b/build/lock.js
new file mode 100644
index 000000000..4e6190bc7
--- /dev/null
+++ b/build/lock.js
@@ -0,0 +1,43894 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 11
+ else if (ua.indexOf('Trident') > -1) {
+ re = new RegExp('rv:([0-9]{2,2}[\.0-9]{0,})');
+ if (re.exec(ua) !== null) {
+ rv = parseFloat(RegExp.$1);
+ }
+ }
+
+ return rv;
+}
+
+/**
+ * Stringify popup options object into
+ * `window.open` string options format
+ *
+ * @param {Object} popupOptions
+ * @private
+ */
+
+function stringifyPopupSettings(popupOptions) {
+ var settings = '';
+
+ for (var key in popupOptions) {
+ settings += key + '=' + popupOptions[key] + ',';
+ }
+
+ return settings.slice(0, -1);
+}
+
+
+/**
+ * Check that a key has been set to something different than null
+ * or undefined.
+ *
+ * @param {Object} obj
+ * @param {String} key
+ */
+function checkIfSet(obj, key) {
+ /*
+ * false != null -> true
+ * true != null -> true
+ * undefined != null -> false
+ * null != null -> false
+ */
+ return !!(obj && obj[key] != null);
+}
+
+function handleRequestError(err, callback) {
+ var status = err.status;
+ var responseText = 'string' === typeof err.responseText ? err.responseText : err;
+
+ var isAffectedIEVersion = isInternetExplorer() === 10 || isInternetExplorer() === 11;
+ var zeroStatus = (!status || status === 0);
+
+ var onLine = !!window.navigator.onLine;
+
+ // Request failed because we are offline.
+ if (zeroStatus && !onLine ) {
+ status = 0;
+ responseText = {
+ code: 'offline'
+ };
+ // http://stackoverflow.com/questions/23229723/ie-10-11-cors-status-0
+ // XXX IE10 when a request fails in CORS returns status code 0
+ // See: http://caniuse.com/#search=navigator.onLine
+ } else if (zeroStatus && isAffectedIEVersion) {
+ status = 401;
+ responseText = {
+ code: 'invalid_user_password'
+ };
+ // If not IE10/11 and not offline it means that Auth0 host is unreachable:
+ // Connection Timeout or Connection Refused.
+ } else if (zeroStatus) {
+ status = 0;
+ responseText = {
+ code: 'connection_refused_timeout'
+ };
+ }
+
+ var error = new LoginError(status, responseText);
+ callback(error);
+}
+
+/**
+ * join url from protocol
+ */
+
+function joinUrl(protocol, domain, endpoint) {
+ return protocol + '//' + domain + endpoint;
+}
+
+/**
+ * Create an `Auth0` instance with `options`
+ *
+ * @class Auth0
+ * @constructor
+ */
+function Auth0 (options) {
+ // XXX Deprecated: We prefer new Auth0(...)
+ if (!(this instanceof Auth0)) {
+ return new Auth0(options);
+ }
+
+ assert_required(options, 'clientID');
+ assert_required(options, 'domain');
+
+ this._useJSONP = null != options.forceJSONP ?
+ !!options.forceJSONP :
+ use_jsonp() && !same_origin('https:', options.domain);
+
+ this._clientID = options.clientID;
+ this._callbackURL = options.callbackURL || document.location.href;
+ this._shouldRedirect = !!options.callbackURL;
+ this._domain = options.domain;
+ this._callbackOnLocationHash = false || options.callbackOnLocationHash;
+ this._cordovaSocialPlugins = {
+ facebook: this._phonegapFacebookLogin
+ };
+ this._useCordovaSocialPlugins = false || options.useCordovaSocialPlugins;
+ this._sendClientInfo = null != options.sendSDKClientInfo ? options.sendSDKClientInfo : true;
+}
+
+/**
+ * Export version with `Auth0` constructor
+ *
+ * @property {String} version
+ */
+
+Auth0.version = require('./version').str;
+
+/**
+ * Export client info object
+ *
+ *
+ * @property {Hash}
+ */
+
+Auth0.clientInfo = { name: 'auth0.js', version: Auth0.version };
+
+
+/**
+ * Wraps calls to window.open so it can be overriden in Electron.
+ *
+ * In Electron, window.open returns an object which provides limited control
+ * over the opened window (see
+ * http://electron.atom.io/docs/v0.36.0/api/window-open/).
+ */
+Auth0.prototype.openWindow = function(url, name, options) {
+ return window.open(url, name, stringifyPopupSettings(options));
+}
+
+/**
+ * Redirect current location to `url`
+ *
+ * @param {String} url
+ * @private
+ */
+
+Auth0.prototype._redirect = function (url) {
+ global.window.location = url;
+};
+
+Auth0.prototype._getCallbackOnLocationHash = function(options) {
+ return (options && typeof options.callbackOnLocationHash !== 'undefined') ?
+ options.callbackOnLocationHash : this._callbackOnLocationHash;
+};
+
+Auth0.prototype._getCallbackURL = function(options) {
+ return (options && typeof options.callbackURL !== 'undefined') ?
+ options.callbackURL : this._callbackURL;
+};
+
+Auth0.prototype._getClientInfoString = function () {
+ var clientInfo = JSON.stringify(Auth0.clientInfo);
+ return Base64Url.encode(clientInfo);
+};
+
+Auth0.prototype._getClientInfoHeader = function () {
+ return {
+ 'Auth0-Client': this._getClientInfoString()
+ };
+};
+
+/**
+ * Renders and submits a WSFed form
+ *
+ * @param {Object} options
+ * @param {Function} formHtml
+ * @private
+ */
+
+Auth0.prototype._renderAndSubmitWSFedForm = function (options, formHtml) {
+ var div = document.createElement('div');
+ div.innerHTML = formHtml;
+ var form = document.body.appendChild(div).children[0];
+
+ if (options.popup && !this._getCallbackOnLocationHash(options)) {
+ form.target = 'auth0_signup_popup';
+ }
+
+ form.submit();
+};
+
+/**
+ * Resolve response type as `token` or `code`
+ *
+ * @return {Object} `scope` and `response_type` properties
+ * @private
+ */
+
+Auth0.prototype._getMode = function (options) {
+ return {
+ scope: 'openid',
+ response_type: this._getCallbackOnLocationHash(options) ? 'token' : 'code'
+ };
+};
+
+Auth0.prototype._configureOfflineMode = function(options) {
+ if (options.scope && options.scope.indexOf('offline_access') >= 0) {
+ options.device = options.device || 'Browser';
+ }
+};
+
+/**
+ * Get user information from API
+ *
+ * @param {Object} profile
+ * @param {String} id_token
+ * @param {Function} callback
+ * @private
+ */
+
+Auth0.prototype._getUserInfo = function (profile, id_token, callback) {
+
+ if (!(profile && !profile.user_id)) {
+ return callback(null, profile);
+ }
+
+ // the scope was just openid
+ var _this = this;
+ var protocol = 'https:';
+ var domain = this._domain;
+ var endpoint = '/tokeninfo';
+ var url = joinUrl(protocol, domain, endpoint);
+
+ var fail = function (status, description) {
+ var error = new Error(status + ': ' + (description || ''));
+
+ // These two properties are added for compatibility with old versions (no Error instance was returned)
+ error.error = status;
+ error.error_description = description;
+
+ callback(error);
+ };
+
+ if (this._useJSONP) {
+ return jsonp(url + '?' + qs.stringify({id_token: id_token}), jsonpOpts, function (err, resp) {
+ if (err) {
+ return fail(0, err.toString());
+ }
+
+ return resp.status === 200 ?
+ callback(null, resp.user) :
+ fail(resp.status, resp.err || resp.error);
+ });
+ }
+
+ return reqwest({
+ url: same_origin(protocol, domain) ? endpoint : url,
+ method: 'post',
+ type: 'json',
+ crossOrigin: !same_origin(protocol, domain),
+ data: {id_token: id_token}
+ }).fail(function (err) {
+ fail(err.status, err.responseText);
+ }).then(function (userinfo) {
+ callback(null, userinfo);
+ });
+
+};
+
+/**
+ * Get profile data by `id_token`
+ *
+ * @param {String} id_token
+ * @param {Function} callback
+ * @method getProfile
+ */
+
+Auth0.prototype.getProfile = function (id_token, callback) {
+ if ('function' !== typeof callback) {
+ throw new Error('A callback function is required');
+ }
+ if (!id_token || typeof id_token !== 'string') {
+ return callback(new Error('Invalid token'));
+ }
+
+ this._getUserInfo(this.decodeJwt(id_token), id_token, callback);
+};
+
+/**
+ * Validate a user
+ *
+ * @param {Object} options
+ * @param {Function} callback
+ * @method validateUser
+ */
+
+Auth0.prototype.validateUser = function (options, callback) {
+ var protocol = 'https:';
+ var domain = this._domain;
+ var endpoint = '/public/api/users/validate_userpassword';
+ var url = joinUrl(protocol, domain, endpoint);
+
+ var query = xtend(
+ options,
+ {
+ client_id: this._clientID,
+ username: trim(options.username || options.email || '')
+ });
+
+ if (this._useJSONP) {
+ return jsonp(url + '?' + qs.stringify(query), jsonpOpts, function (err, resp) {
+ if (err) {
+ return callback(err);
+ }
+ if('error' in resp && resp.status !== 404) {
+ return callback(new Error(resp.error));
+ }
+ callback(null, resp.status === 200);
+ });
+ }
+
+ reqwest({
+ url: same_origin(protocol, domain) ? endpoint : url,
+ method: 'post',
+ type: 'text',
+ data: query,
+ crossOrigin: !same_origin(protocol, domain),
+ error: function (err) {
+ if (err.status !== 404) { return callback(new Error(err.responseText)); }
+ callback(null, false);
+ },
+ success: function (resp) {
+ callback(null, resp.status === 200);
+ }
+ });
+};
+
+/**
+ * Decode Json Web Token
+ *
+ * @param {String} jwt
+ * @method decodeJwt
+ */
+
+Auth0.prototype.decodeJwt = function (jwt) {
+ var encoded = jwt && jwt.split('.')[1];
+ return json_parse(Base64Url.decode(encoded));
+};
+
+/**
+ * Given the hash (or a query) of an URL returns a dictionary with only relevant
+ * authentication information. If succeeds it will return the following fields:
+ * `profile`, `id_token`, `access_token` and `state`. In case of error, it will
+ * return `error` and `error_description`.
+ *
+ * @method parseHash
+ * @param {String} [hash=window.location.hash] URL to be parsed
+ * @example
+ * var auth0 = new Auth0({...});
+ *
+ * // Returns {profile: {** decoded id token **}, state: "good"}
+ * auth0.parseHash('#id_token=.....&state=good&foo=bar');
+ *
+ * // Returns {error: "invalid_credentials", error_description: undefined}
+ * auth0.parseHash('#error=invalid_credentials');
+ *
+ * // Returns {error: "invalid_credentials", error_description: undefined}
+ * auth0.parseHash('?error=invalid_credentials');
+ *
+ */
+
+Auth0.prototype.parseHash = function (hash) {
+ hash = hash || window.location.hash;
+ var parsed_qs;
+ if (hash.match(/error/)) {
+ hash = hash.substr(1).replace(/^\//, '');
+ parsed_qs = qs.parse(hash);
+ var err = {
+ error: parsed_qs.error,
+ error_description: parsed_qs.error_description
+ };
+ return err;
+ }
+ if(!hash.match(/access_token/)) {
+ // Invalid hash URL
+ return null;
+ }
+ hash = hash.substr(1).replace(/^\//, '');
+ parsed_qs = qs.parse(hash);
+ var id_token = parsed_qs.id_token;
+ var refresh_token = parsed_qs.refresh_token;
+ var prof = this.decodeJwt(id_token);
+ var invalidJwt = function (error) {
+ var err = {
+ error: 'invalid_token',
+ error_description: error
+ };
+ return err;
+ };
+
+ // aud should be the clientID
+ var audiences = is_array(prof.aud) ? prof.aud : [ prof.aud ];
+ if (index_of(audiences, this._clientID) === -1) {
+ return invalidJwt(
+ 'The clientID configured (' + this._clientID + ') does not match with the clientID set in the token (' + audiences.join(', ') + ').');
+ }
+
+ // iss should be the Auth0 domain (i.e.: https://contoso.auth0.com/)
+ if (prof.iss && prof.iss !== 'https://' + this._domain + '/') {
+ return invalidJwt(
+ 'The domain configured (https://' + this._domain + '/) does not match with the domain set in the token (' + prof.iss + ').');
+ }
+
+ return {
+ accessToken: parsed_qs.access_token,
+ idToken: id_token,
+ idTokenPayload: prof,
+ refreshToken: refresh_token,
+ state: parsed_qs.state
+ };
+};
+
+/**
+ * Signup
+ *
+ * @param {Object} options Signup Options
+ * @param {String} email New user email
+ * @param {String} password New user password
+ *
+ * @param {Function} callback
+ * @method signup
+ */
+
+Auth0.prototype.signup = function (options, callback) {
+ var _this = this;
+
+ var opts = {
+ client_id: this._clientID,
+ redirect_uri: this._getCallbackURL(options),
+ email: trim(options.email || options.username || ''),
+ tenant: this._domain.split('.')[0]
+ };
+
+ if (typeof options.username === 'string') {
+ opts.username = trim(options.username);
+ }
+
+ var query = xtend(this._getMode(options), options, opts);
+
+ this._configureOfflineMode(query);
+
+ // TODO Change this to a property named 'disableSSO' for consistency.
+ // By default, options.sso is true
+ if (!checkIfSet(options, 'sso')) {
+ options.sso = true;
+ }
+
+ if (!checkIfSet(options, 'auto_login')) {
+ options.auto_login = true;
+ }
+
+ var popup;
+
+ var will_popup = options.auto_login && options.popup
+ && (!this._getCallbackOnLocationHash(options) || options.sso);
+
+ if (will_popup) {
+ popup = this._buildPopupWindow(options);
+ }
+
+ function success () {
+ if (options.auto_login) {
+ return _this.login(options, callback);
+ }
+
+ if ('function' === typeof callback) {
+ return callback();
+ }
+ }
+
+ function fail (status, resp) {
+ var error = new LoginError(status, resp);
+
+ // when failed we want the popup closed if opened
+ if (popup && 'function' === typeof popup.kill) {
+ popup.kill();
+ }
+
+ if ('function' === typeof callback) {
+ return callback(error);
+ }
+
+ throw error;
+ }
+
+ var protocol = 'https:';
+ var domain = this._domain;
+ var endpoint = '/dbconnections/signup';
+ var url = joinUrl(protocol, domain, endpoint);
+
+ if (this._useJSONP) {
+ return jsonp(url + '?' + qs.stringify(query), jsonpOpts, function (err, resp) {
+ if (err) {
+ return fail(0, err);
+ }
+
+ return resp.status == 200 ? success() :
+ fail(resp.status, resp.err || resp.error);
+ });
+ }
+
+ reqwest({
+ url: same_origin(protocol, domain) ? endpoint : url,
+ method: 'post',
+ type: 'html',
+ data: query,
+ success: success,
+ crossOrigin: !same_origin(protocol, domain),
+ error: function (err) {
+ fail(err.status, err.responseText);
+ }
+ });
+};
+
+/**
+ * Change password
+ *
+ * @param {Object} options
+ * @param {Function} callback
+ * @method changePassword
+ */
+
+Auth0.prototype.changePassword = function (options, callback) {
+ var query = {
+ tenant: this._domain.split('.')[0],
+ client_id: this._clientID,
+ connection: options.connection,
+ email: trim(options.email || '')
+ };
+
+ if (typeof options.password === "string") {
+ query.password = options.password;
+ }
+
+ function fail (status, resp) {
+ var error = new LoginError(status, resp);
+ if (callback) {
+ return callback(error);
+ }
+ }
+
+ var protocol = 'https:';
+ var domain = this._domain;
+ var endpoint = '/dbconnections/change_password';
+ var url = joinUrl(protocol, domain, endpoint);
+
+ if (this._useJSONP) {
+ return jsonp(url + '?' + qs.stringify(query), jsonpOpts, function (err, resp) {
+ if (err) {
+ return fail(0, err);
+ }
+ return resp.status == 200 ?
+ callback(null, resp.message) :
+ fail(resp.status, resp.err || resp.error);
+ });
+ }
+
+ reqwest({
+ url: same_origin(protocol, domain) ? endpoint : url,
+ method: 'post',
+ type: 'html',
+ data: query,
+ crossOrigin: !same_origin(protocol, domain),
+ error: function (err) {
+ fail(err.status, err.responseText);
+ },
+ success: function (r) {
+ callback(null, r);
+ }
+ });
+};
+
+/**
+ * Builds query string to be passed to /authorize based on dict key and values.
+ *
+ * @param {Array} args
+ * @param {Array} blacklist
+ * @private
+ */
+
+Auth0.prototype._buildAuthorizeQueryString = function (args, blacklist) {
+ var query = this._buildAuthorizationParameters(args, blacklist);
+ return qs.stringify(query);
+};
+
+/**
+ * Builds parameter dictionary to be passed to /authorize based on dict key and values.
+ *
+ * @param {Array} args
+ * @param {Array} blacklist
+ * @private
+ */
+
+Auth0.prototype._buildAuthorizationParameters = function(args, blacklist) {
+ var query = xtend.apply(null, args);
+
+ // Adds offline mode to the query
+ this._configureOfflineMode(query);
+
+ // Adds client SDK information (when enabled)
+ if ( this._sendClientInfo ) query['auth0Client'] = this._getClientInfoString();
+
+ // Elements to filter from query string
+ blacklist = blacklist || ['popup', 'popupOptions'];
+
+ var i, key;
+
+ for (i = 0; i < blacklist.length; i++) {
+ key = blacklist[i];
+ delete query[key];
+ }
+
+ if (query.connection_scope && is_array(query.connection_scope)){
+ query.connection_scope = query.connection_scope.join(',');
+ }
+
+ return query;
+};
+
+/**
+ * Login user
+ *
+ * @param {Object} options
+ * @param {Function} callback
+ * @method login
+ */
+
+Auth0.prototype.login = Auth0.prototype.signin = function (options, callback) {
+ // TODO Change this to a property named 'disableSSO' for consistency.
+ // By default, options.sso is true
+ if (!checkIfSet(options, 'sso')) {
+ options.sso = true;
+ }
+
+ if (typeof options.passcode !== 'undefined') {
+ return this.loginWithPasscode(options, callback);
+ }
+
+ if (typeof options.username !== 'undefined' ||
+ typeof options.email !== 'undefined') {
+ return this.loginWithUsernamePassword(options, callback);
+ }
+
+ if (!!window.cordova || !!window.electron) {
+ return this.loginPhonegap(options, callback);
+ }
+
+ if (!!options.popup && this._getCallbackOnLocationHash(options)) {
+ return this.loginWithPopup(options, callback);
+ }
+
+ this._authorize(options);
+};
+
+Auth0.prototype._authorize = function(options) {
+ var qs = [
+ this._getMode(options),
+ options,
+ {
+ client_id: this._clientID,
+ redirect_uri: this._getCallbackURL(options)
+ }
+ ];
+
+ var query = this._buildAuthorizeQueryString(qs);
+
+ var url = joinUrl('https:', this._domain, '/authorize?' + query);
+
+ if (options.popup) {
+ this._buildPopupWindow(options, url);
+ } else {
+ this._redirect(url);
+ }
+};
+
+/**
+ * Compute `options.width` and `options.height` for the popup to
+ * open and return and extended object with optimal `top` and `left`
+ * position arguments for the popup windows
+ *
+ * @param {Object} options
+ * @private
+ */
+
+Auth0.prototype._computePopupPosition = function (options) {
+ options = options || {};
+ var width = options.width || 500;
+ var height = options.height || 600;
+
+ var screenX = typeof window.screenX !== 'undefined' ? window.screenX : window.screenLeft;
+ var screenY = typeof window.screenY !== 'undefined' ? window.screenY : window.screenTop;
+ var outerWidth = typeof window.outerWidth !== 'undefined' ? window.outerWidth : document.body.clientWidth;
+ var outerHeight = typeof window.outerHeight !== 'undefined' ? window.outerHeight : (document.body.clientHeight - 22);
+ // XXX: what is the 22?
+
+ // Use `outerWidth - width` and `outerHeight - height` for help in
+ // positioning the popup centered relative to the current window
+ var left = screenX + (outerWidth - width) / 2;
+ var top = screenY + (outerHeight - height) / 2;
+
+ return { width: width, height: height, left: left, top: top };
+};
+
+/**
+ * loginPhonegap method is triggered when !!window.cordova is true.
+ *
+ * @method loginPhonegap
+ * @private
+ * @param {Object} options Login options.
+ * @param {Function} callback To be called after login happened. Callback arguments
+ * should be:
+ * function (err, profile, idToken, accessToken, state)
+ *
+ * @example
+ * var auth0 = new Auth0({ clientId: '...', domain: '...'});
+ *
+ * auth0.signin({}, function (err, profile, idToken, accessToken, state) {
+ * if (err) {
+ * alert(err);
+ * return;
+ * }
+ *
+ * alert('Welcome ' + profile.name);
+ * });
+ */
+
+Auth0.prototype.loginPhonegap = function (options, callback) {
+ if (this._shouldAuthenticateWithCordovaPlugin(options.connection)) {
+ this._socialPhonegapLogin(options, callback);
+ return;
+ }
+
+ var mobileCallbackURL = joinUrl('https:', this._domain, '/mobile');
+ var _this = this;
+ var qs = [
+ this._getMode(options),
+ options,
+ {
+ client_id: this._clientID,
+ redirect_uri: mobileCallbackURL
+ }
+ ];
+
+ if ( this._sendClientInfo ) {
+ qs.push({ auth0Client: this._getClientInfoString() });
+ }
+
+ var query = this._buildAuthorizeQueryString(qs);
+
+ var popupUrl = joinUrl('https:', this._domain, '/authorize?' + query);
+
+ var popupOptions = xtend({location: 'yes'} ,
+ options.popupOptions);
+
+ // This wasn't send before so we don't send it now either
+ delete popupOptions.width;
+ delete popupOptions.height;
+
+ var ref = this.openWindow(popupUrl, '_blank', popupOptions);
+ var answered = false;
+
+ function errorHandler(event) {
+ if (answered) { return; }
+ answered = true;
+ ref.close();
+ callback(new Error(event.message), null);
+ }
+
+ function startHandler(event) {
+ if (answered) { return; }
+
+ if ( event.url && !(event.url.indexOf(mobileCallbackURL + '#') === 0 ||
+ event.url.indexOf(mobileCallbackURL + '?') === 0)) { return; }
+
+ var result = _this.parseHash(event.url.slice(mobileCallbackURL.length));
+
+ if (!result) {
+ answered = true;
+ ref.close();
+ callback(new Error('Error parsing hash'), null);
+ return;
+ }
+
+ if (result.idToken) {
+ answered = true;
+ ref.close();
+ callback(null, result);
+ return;
+ }
+
+
+ // Case where we've found an error
+ answered = true;
+ ref.close();
+ callback(new Error(result.err || result.error || 'Something went wrong'), null);
+ }
+
+ function exitHandler() {
+ if (answered) { return; }
+
+ ref.removeEventListener('loaderror', errorHandler);
+ ref.removeEventListener('loadstart', startHandler);
+ ref.removeEventListener('exit', exitHandler);
+
+ callback(new Error('Browser window closed'), null);
+ }
+
+ ref.addEventListener('loaderror', errorHandler);
+ ref.addEventListener('loadstart', startHandler);
+ ref.addEventListener('exit', exitHandler);
+
+};
+
+/**
+ * loginWithPopup method is triggered when login method receives a {popup: true} in
+ * the login options.
+ *
+ * @method loginWithPopup
+ * @param {Object} options Login options.
+ * @param {function} callback To be called after login happened (whether
+ * success or failure). This parameter is mandatory when
+ * option callbackOnLocationHash is truthy but should not
+ * be used when falsy.
+ * @example
+ * var auth0 = new Auth0({ clientId: '...', domain: '...', callbackOnLocationHash: true });
+ *
+ * // Error! No callback
+ * auth0.login({popup: true});
+ *
+ * // Ok!
+ * auth0.login({popup: true}, function () { });
+ *
+ * @example
+ * var auth0 = new Auth0({ clientId: '...', domain: '...'});
+ *
+ * // Ok!
+ * auth0.login({popup: true});
+ *
+ * // Error! No callback will be executed on response_type=code
+ * auth0.login({popup: true}, function () { });
+ * @private
+ */
+
+Auth0.prototype.loginWithPopup = function(options, callback) {
+ var _this = this;
+
+ if (!callback) {
+ throw new Error('popup mode should receive a mandatory callback');
+ }
+
+ var qs = [this._getMode(options), options, { client_id: this._clientID, owp: true }];
+
+ if (this._sendClientInfo) {
+ qs.push({ auth0Client: this._getClientInfoString() });
+ }
+
+ var query = this._buildAuthorizeQueryString(qs);
+ var popupUrl = joinUrl('https:', this._domain, '/authorize?' + query);
+
+ var popupPosition = this._computePopupPosition(options.popupOptions);
+ var popupOptions = xtend(popupPosition, options.popupOptions);
+
+ var popup = WinChan.open({
+ url: popupUrl,
+ relay_url: 'https://' + this._domain + '/relay.html',
+ window_features: stringifyPopupSettings(popupOptions)
+ }, function (err, result) {
+ // Eliminate `_current_popup` reference manually because
+ // Winchan removes `.kill()` method from window and also
+ // doesn't call `.kill()` by itself
+ _this._current_popup = null;
+
+ // Winchan always returns string errors, we wrap them inside Error objects
+ if (err) {
+ return callback(new LoginError(err), null, null, null, null, null);
+ }
+
+ // Handle edge case with generic error
+ if (!result) {
+ return callback(new LoginError('Something went wrong'), null, null, null, null, null);
+ }
+
+ // Handle profile retrieval from id_token and respond
+ if (result.id_token) {
+ return callback(null, _this._prepareResult(result));
+ }
+
+ // Case where the error is returned at an `err` property from the result
+ if (result.err) {
+ return callback(new LoginError(result.err.status, result.err.details || result.err), null, null, null, null, null);
+ }
+
+ // Case for sso_dbconnection_popup returning error at result.error instead of result.err
+ if (result.error) {
+ return callback(new LoginError(result.status, result.details || result), null, null, null, null, null);
+ }
+
+ // Case we couldn't match any error, we return a generic one
+ return callback(new LoginError('Something went wrong'), null, null, null, null, null);
+ });
+
+ popup.focus();
+};
+
+/**
+ * _shouldAuthenticateWithCordovaPlugin method checks whether Auth0 is properly configured to
+ * handle authentication of a social connnection using a phonegap plugin.
+ *
+ * @param {String} connection Name of the connection.
+ * @private
+ */
+
+Auth0.prototype._shouldAuthenticateWithCordovaPlugin = function(connection) {
+ var socialPlugin = this._cordovaSocialPlugins[connection];
+ return this._useCordovaSocialPlugins && !!socialPlugin;
+};
+
+/**
+ * _socialPhonegapLogin performs social authentication using a phonegap plugin
+ *
+ * @param {String} connection Name of the connection.
+ * @param {function} callback To be called after login happened (whether
+ * success or failure).
+ * @private
+ */
+
+Auth0.prototype._socialPhonegapLogin = function(options, callback) {
+ var socialAuthentication = this._cordovaSocialPlugins[options.connection];
+ var _this = this;
+ socialAuthentication(options.connection_scope, function(error, accessToken, extras) {
+ if (error) {
+ callback(error, null, null, null, null);
+ return;
+ }
+ var loginOptions = xtend({ access_token: accessToken }, options, extras);
+ _this.loginWithSocialAccessToken(loginOptions, callback);
+ });
+};
+
+/**
+ * _phonegapFacebookLogin performs social authentication with Facebook using phonegap-facebook-plugin
+ *
+ * @param {Object} scopes FB scopes used to login. It can be an Array of String or a single String.
+ * By default is ["public_profile"]
+ * @param {function} callback To be called after login happened (whether success or failure). It will
+ * yield the accessToken and any extra information neeeded by Auth0 API
+ * or an Error if the authentication fails. Callback should be:
+ * function (err, accessToken, extras) { }
+ * @private
+ */
+
+Auth0.prototype._phonegapFacebookLogin = function(scopes, callback) {
+ if (!window.facebookConnectPlugin || !window.facebookConnectPlugin.login) {
+ callback(new Error('missing plugin phonegap-facebook-plugin'), null, null);
+ return;
+ }
+
+ var fbScopes;
+ if (scopes && is_array(scopes)){
+ fbScopes = scopes;
+ } else if (scopes) {
+ fbScopes = [scopes];
+ } else {
+ fbScopes = ['public_profile'];
+ }
+ window.facebookConnectPlugin.login(fbScopes, function (state) {
+ callback(null, state.authResponse.accessToken, {});
+ }, function(error) {
+ callback(new Error(error), null, null);
+ });
+};
+
+/**
+ * This method handles the scenario where a db connection is used with
+ * popup: true and sso: true.
+ *
+ * @private
+ */
+Auth0.prototype.loginWithUsernamePasswordAndSSO = function (options, callback) {
+ var _this = this;
+ var popupPosition = this._computePopupPosition(options.popupOptions);
+ var popupOptions = xtend(popupPosition, options.popupOptions);
+
+ var popup = WinChan.open({
+ url: 'https://' + this._domain + '/sso_dbconnection_popup/' + this._clientID,
+ relay_url: 'https://' + this._domain + '/relay.html',
+ window_features: stringifyPopupSettings(popupOptions),
+ popup: this._current_popup,
+ params: {
+ domain: this._domain,
+ clientID: this._clientID,
+ options: {
+ // TODO What happens with i18n?
+ username: trim(options.username || options.email || ''),
+ password: options.password,
+ connection: options.connection,
+ state: options.state,
+ scope: options.scope
+ }
+ }
+ }, function (err, result) {
+ // Eliminate `_current_popup` reference manually because
+ // Winchan removes `.kill()` method from window and also
+ // doesn't call `.kill()` by itself
+ _this._current_popup = null;
+
+ // Winchan always returns string errors, we wrap them inside Error objects
+ if (err) {
+ return callback(new LoginError(err), null, null, null, null, null);
+ }
+
+ // Handle edge case with generic error
+ if (!result) {
+ return callback(new LoginError('Something went wrong'), null, null, null, null, null);
+ }
+
+ // Handle profile retrieval from id_token and respond
+ if (result.id_token) {
+ return callback(null, _this._prepareResult(result));
+ }
+
+ // Case where the error is returned at an `err` property from the result
+ if (result.err) {
+ return callback(new LoginError(result.err.status, result.err.details || result.err), null, null, null, null, null);
+ }
+
+ // Case for sso_dbconnection_popup returning error at result.error instead of result.err
+ if (result.error) {
+ return callback(new LoginError(result.status, result.details || result), null, null, null, null, null);
+ }
+
+ // Case we couldn't match any error, we return a generic one
+ return callback(new LoginError('Something went wrong'), null, null, null, null, null);
+ });
+
+ popup.focus();
+};
+
+/**
+ * Login with Resource Owner (RO)
+ *
+ * @param {Object} options
+ * @param {Function} callback
+ * @method loginWithResourceOwner
+ */
+
+Auth0.prototype.loginWithResourceOwner = function (options, callback) {
+ var _this = this;
+ var query = xtend(
+ this._getMode(options),
+ options,
+ {
+ client_id: this._clientID,
+ username: trim(options.username || options.email || ''),
+ grant_type: 'password'
+ });
+
+ this._configureOfflineMode(query);
+
+ var protocol = 'https:';
+ var domain = this._domain;
+ var endpoint = '/oauth/ro';
+ var url = joinUrl(protocol, domain, endpoint);
+
+ if ( this._sendClientInfo && this._useJSONP ) {
+ query['auth0Client'] = this._getClientInfoString();
+ }
+
+ if (this._useJSONP) {
+ return jsonp(url + '?' + qs.stringify(query), jsonpOpts, function (err, resp) {
+ if (err) {
+ return callback(err);
+ }
+ if('error' in resp) {
+ var error = new LoginError(resp.status, resp.error);
+ return callback(error);
+ }
+ callback(null, _this._prepareResult(resp));
+ });
+ }
+
+ reqwest({
+ url: same_origin(protocol, domain) ? endpoint : url,
+ method: 'post',
+ type: 'json',
+ data: query,
+ headers: this._getClientInfoHeader(),
+ crossOrigin: !same_origin(protocol, domain),
+ success: function (resp) {
+ callback(null, _this._prepareResult(resp));
+ },
+ error: function (err) {
+ handleRequestError(err, callback);
+ }
+ });
+};
+
+/**
+ * Login with Social Access Token
+ *
+ * @param {Object} options
+ * @param {Function} callback
+ * @method loginWithSocialAccessToken
+ */
+
+Auth0.prototype.loginWithSocialAccessToken = function (options, callback) {
+ var _this = this;
+ var query = this._buildAuthorizationParameters([
+ { scope: 'openid' },
+ options,
+ { client_id: this._clientID }
+ ]);
+
+ var protocol = 'https:';
+ var domain = this._domain;
+ var endpoint = '/oauth/access_token';
+ var url = joinUrl(protocol, domain, endpoint);
+
+ if (this._useJSONP) {
+ return jsonp(url + '?' + qs.stringify(query), jsonpOpts, function (err, resp) {
+ if (err) {
+ return callback(err);
+ }
+ if('error' in resp) {
+ var error = new LoginError(resp.status, resp.error);
+ return callback(error);
+ }
+ callback(null, _this._prepareResult(resp));
+ });
+ }
+
+ reqwest({
+ url: same_origin(protocol, domain) ? endpoint : url,
+ method: 'post',
+ type: 'json',
+ data: query,
+ headers: this._getClientInfoHeader(),
+ crossOrigin: !same_origin(protocol, domain),
+ success: function (resp) {
+ callback(null, _this._prepareResult(resp));
+ },
+ error: function (err) {
+ handleRequestError(err, callback);
+ }
+ });
+};
+
+/**
+ * Open a popup, store the winref in the instance and return it.
+ *
+ * We usually need to call this method before any ajax transaction in order
+ * to prevent the browser to block the popup.
+ *
+ * @param {[type]} options [description]
+ * @param {Function} callback [description]
+ * @return {[type]} [description]
+ * @private
+ */
+
+Auth0.prototype._buildPopupWindow = function (options, url) {
+ if (this._current_popup && !this._current_popup.closed) {
+ return this._current_popup;
+ }
+
+ url = url || 'about:blank'
+
+ var _this = this;
+ var defaults = { width: 500, height: 600 };
+ var opts = xtend(defaults, options.popupOptions || {});
+ var popupOptions = stringifyPopupSettings(opts);
+
+ this._current_popup = window.open(url, 'auth0_signup_popup', popupOptions);
+
+ if (!this._current_popup) {
+ throw new Error('Popup window cannot not been created. Disable popup blocker or make sure to call Auth0 login or singup on an UI event.');
+ }
+
+ this._current_popup.kill = function () {
+ this.close();
+ _this._current_popup = null;
+ };
+
+ return this._current_popup;
+};
+
+/**
+ * Login with Username and Password
+ *
+ * @param {Object} options
+ * @param {Function} callback
+ * @method loginWithUsernamePassword
+ */
+
+Auth0.prototype.loginWithUsernamePassword = function (options, callback) {
+ // XXX: Warning: This check is whether callback arguments are
+ // fn(err) case callback.length === 1 (a redirect should be performed) vs.
+ // fn(err, profile, id_token, access_token, state) callback.length > 1 (no
+ // redirect should be performed)
+ //
+ // Note: Phonegap/Cordova:
+ // As the popup is launched using the InAppBrowser plugin the SSO cookie will
+ // be set on the InAppBrowser browser. That's why the browser where the app runs
+ // won't get the sso cookie. Therefore, we don't allow username password using
+ // popup with sso: true in Cordova/Phonegap and we default to resource owner auth.
+ if (callback && callback.length > 1 && (!options.sso || window.cordova)) {
+ return this.loginWithResourceOwner(options, callback);
+ }
+
+ var _this = this;
+ var popup;
+
+ // TODO We should deprecate this, really hacky and confuses people.
+ if (options.popup && !this._getCallbackOnLocationHash(options)) {
+ popup = this._buildPopupWindow(options);
+ }
+
+ // When a callback with more than one argument is specified and sso: true then
+ // we open a popup and do authentication there.
+ if (callback && callback.length > 1 && options.sso ) {
+ return this.loginWithUsernamePasswordAndSSO(options, callback);
+ }
+
+ var query = xtend(
+ this._getMode(options),
+ options,
+ {
+ client_id: this._clientID,
+ redirect_uri: this._getCallbackURL(options),
+ username: trim(options.username || options.email || ''),
+ tenant: this._domain.split('.')[0]
+ });
+
+ this._configureOfflineMode(query);
+
+ var protocol = 'https:';
+ var domain = this._domain;
+ var endpoint = '/usernamepassword/login';
+ var url = joinUrl(protocol, domain, endpoint);
+
+ if (this._useJSONP) {
+ return jsonp(url + '?' + qs.stringify(query), jsonpOpts, function (err, resp) {
+ if (err) {
+ if (popup && popup.kill) { popup.kill(); }
+ return callback(err);
+ }
+ if('error' in resp) {
+ if (popup && popup.kill) { popup.kill(); }
+ var error = new LoginError(resp.status, resp.error);
+ return callback(error);
+ }
+ _this._renderAndSubmitWSFedForm(options, resp.form);
+ });
+ }
+
+ function return_error (error) {
+ if (callback) {
+ return callback(error);
+ }
+ throw error;
+ }
+
+ reqwest({
+ url: same_origin(protocol, domain) ? endpoint : url,
+ method: 'post',
+ type: 'html',
+ data: query,
+ headers: this._getClientInfoHeader(),
+ crossOrigin: !same_origin(protocol, domain),
+ success: function (resp) {
+ _this._renderAndSubmitWSFedForm(options, resp);
+ },
+ error: function (err) {
+ if (popup && popup.kill) {
+ popup.kill();
+ }
+ handleRequestError(err, return_error);
+ }
+ });
+};
+
+/**
+ * Login with phone number and passcode
+ *
+ * @param {Object} options
+ * @param {Function} callback
+ * @method loginWithPhoneNumber
+ */
+Auth0.prototype.loginWithPasscode = function (options, callback) {
+
+ if (options.email == null && options.phoneNumber == null) {
+ throw new Error('email or phoneNumber is required for authentication');
+ }
+
+ if (options.passcode == null) {
+ throw new Error('passcode is required for authentication');
+ }
+
+ options.connection = options.email == null ? 'sms' : 'email';
+
+ if (!this._shouldRedirect) {
+ options = xtend(options, {
+ username: options.email == null ? options.phoneNumber : options.email,
+ password: options.passcode,
+ sso: false
+ });
+
+ delete options.email;
+ delete options.phoneNumber;
+ delete options.passcode;
+
+ return this.loginWithResourceOwner(options, callback);
+ }
+
+ var verifyOptions = {connection: options.connection};
+
+ if (options.phoneNumber) {
+ options.phone_number = options.phoneNumber;
+ delete options.phoneNumber;
+
+ verifyOptions.phone_number = options.phone_number;
+ }
+
+ if (options.email) {
+ verifyOptions.email = options.email;
+ }
+
+ options.verification_code = options.passcode;
+ delete options.passcode;
+
+ verifyOptions.verification_code = options.verification_code;
+
+ var _this = this;
+ this._verify(verifyOptions, function(error) {
+ if (error) {
+ return callback(error);
+ }
+ _this._verify_redirect(options);
+ });
+};
+
+Auth0.prototype._verify = function(options, callback) {
+ var protocol = 'https:';
+ var domain = this._domain;
+ var endpoint = '/passwordless/verify';
+ var url = joinUrl(protocol, domain, endpoint);
+
+ var data = options;
+
+ if (this._useJSONP) {
+ if (this._sendClientInfo) {
+ data['auth0Client'] = this._getClientInfoString();
+ }
+
+ return jsonp(url + '?' + qs.stringify(data), jsonpOpts, function (err, resp) {
+ if (err) {
+ return callback(new Error(0 + ': ' + err.toString()));
+ }
+ // /**/ typeof __auth0jp0 === 'function' && __auth0jp0({"status":400});
+ return resp.status === 200 ? callback(null, true) : callback({status: resp.status});
+ });
+ }
+
+ return reqwest({
+ url: same_origin(protocol, domain) ? endpoint : url,
+ method: 'post',
+ headers: this._getClientInfoHeader(),
+ crossOrigin: !same_origin(protocol, domain),
+ data: data
+ })
+ .fail(function (err) {
+ try {
+ callback(JSON.parse(err.responseText));
+ } catch (e) {
+ var error = new Error(err.status + '(' + err.statusText + '): ' + err.responseText);
+ error.statusCode = err.status;
+ error.error = err.statusText;
+ error.message = err.responseText;
+ callback(error);
+ }
+ })
+ .then(function (result) {
+ callback(null, result);
+ });
+}
+
+Auth0.prototype._verify_redirect = function(options) {
+ var qs = [
+ this._getMode(options),
+ options,
+ {
+ client_id: this._clientID,
+ redirect_uri: this._getCallbackURL(options)
+ }
+ ];
+
+ var query = this._buildAuthorizeQueryString(qs);
+ var url = joinUrl('https:', this._domain, '/passwordless/verify_redirect?' + query);
+
+ this._redirect(url);
+};
+
+// TODO Document me
+Auth0.prototype.renewIdToken = function (id_token, callback) {
+ this.getDelegationToken({
+ id_token: id_token,
+ scope: 'passthrough',
+ api: 'auth0'
+ }, callback);
+};
+
+// TODO Document me
+Auth0.prototype.refreshToken = function (refresh_token, callback) {
+ this.getDelegationToken({
+ refresh_token: refresh_token,
+ scope: 'passthrough',
+ api: 'auth0'
+ }, callback);
+};
+
+/**
+ * Get delegation token for certain addon or certain other clientId
+ *
+ * @example
+ *
+ * auth0.getDelegationToken({
+ * id_token: '',
+ * target: ''
+ * api_type: 'auth0'
+ * }, function (err, delegationResult) {
+ * if (err) return console.log(err.message);
+ * // Do stuff with delegation token
+ * expect(delegationResult.id_token).to.exist;
+ * expect(delegationResult.token_type).to.eql('Bearer');
+ * expect(delegationResult.expires_in).to.eql(36000);
+ * });
+ *
+ * @example
+ *
+ * // get a delegation token from a Firebase API App
+ * auth0.getDelegationToken({
+ * id_token: '',
+ * target: ''
+ * api_type: 'firebase'
+ * }, function (err, delegationResult) {
+ * // Use your firebase token here
+ * });
+ *
+ * @method getDelegationToken
+ * @param {Object} [options]
+ * @param {String} [id_token]
+ * @param {String} [target]
+ * @param {String} [api_type]
+ * @param {Function} [callback]
+ */
+Auth0.prototype.getDelegationToken = function (options, callback) {
+ options = options || {};
+
+ if (!options.id_token && !options.refresh_token ) {
+ throw new Error('You must send either an id_token or a refresh_token to get a delegation token.');
+ }
+
+ var query = xtend({
+ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
+ client_id: this._clientID,
+ target: options.targetClientId || this._clientID,
+ api_type: options.api
+ }, options);
+
+ delete query.hasOwnProperty;
+ delete query.targetClientId;
+ delete query.api;
+
+ var protocol = 'https:';
+ var domain = this._domain;
+ var endpoint = '/delegation';
+ var url = joinUrl(protocol, domain, endpoint);
+
+ if (this._useJSONP) {
+ return jsonp(url + '?' + qs.stringify(query), jsonpOpts, function (err, resp) {
+ if (err) {
+ return callback(err);
+ }
+ if('error' in resp) {
+ var error = new LoginError(resp.status, resp.error_description || resp.error);
+ return callback(error);
+ }
+ callback(null, resp);
+ });
+ }
+
+ reqwest({
+ url: same_origin(protocol, domain) ? endpoint : url,
+ method: 'post',
+ type: 'json',
+ data: query,
+ crossOrigin: !same_origin(protocol, domain),
+ success: function (resp) {
+ callback(null, resp);
+ },
+ error: function (err) {
+ try {
+ callback(JSON.parse(err.responseText));
+ }
+ catch (e) {
+ var er = err;
+ var isAffectedIEVersion = isInternetExplorer() === 10 || isInternetExplorer() === 11;
+ var zeroStatus = (!er.status || er.status === 0);
+
+ // Request failed because we are offline.
+ // See: http://caniuse.com/#search=navigator.onLine
+ if (zeroStatus && !window.navigator.onLine) {
+ er = {};
+ er.status = 0;
+ er.responseText = {
+ code: 'offline'
+ };
+ // http://stackoverflow.com/questions/23229723/ie-10-11-cors-status-0
+ // XXX IE10 when a request fails in CORS returns status code 0
+ // XXX This is not handled by handleRequestError as the errors are different
+ } else if (zeroStatus && isAffectedIEVersion) {
+ er = {};
+ er.status = 401;
+ er.responseText = {
+ code: 'invalid_operation'
+ };
+ // If not IE10/11 and not offline it means that Auth0 host is unreachable:
+ // Connection Timeout or Connection Refused.
+ } else if (zeroStatus) {
+ er = {};
+ er.status = 0;
+ er.responseText = {
+ code: 'connection_refused_timeout'
+ };
+ } else {
+ er.responseText = err;
+ }
+ callback(new LoginError(er.status, er.responseText));
+ }
+ }
+ });
+};
+
+/**
+ * Trigger logout redirect with
+ * params from `query` object
+ *
+ * @example
+ *
+ * auth0.logout();
+ * // redirects to -> 'https://yourapp.auth0.com/logout'
+ *
+ * @example
+ *
+ * auth0.logout({returnTo: 'http://logout'});
+ * // redirects to -> 'https://yourapp.auth0.com/logout?returnTo=http://logout'
+ *
+ * @method logout
+ * @param {Object} query
+ */
+
+Auth0.prototype.logout = function (query) {
+ var url = joinUrl('https:', this._domain, '/logout');
+ if (query) {
+ url += '?' + qs.stringify(query);
+ }
+ this._redirect(url);
+};
+
+/**
+ * Get single sign on Data
+ *
+ * @example
+ *
+ * auth0.getSSOData(function (err, ssoData) {
+ * if (err) return console.log(err.message);
+ * expect(ssoData.sso).to.exist;
+ * });
+ *
+ * @example
+ *
+ * auth0.getSSOData(false, fn);
+ *
+ * @method getSSOData
+ * @param {Boolean} withActiveDirectories
+ * @param {Function} cb
+ */
+
+Auth0.prototype.getSSOData = function (withActiveDirectories, cb) {
+ if (typeof withActiveDirectories === 'function') {
+ cb = withActiveDirectories;
+ withActiveDirectories = false;
+ }
+
+ var noResult = {sso: false};
+
+ if (this._useJSONP) {
+ var error = new Error("The SSO data can't be obtained using JSONP");
+ setTimeout(function() { cb(error, noResult) }, 0);
+ return;
+ }
+
+ var protocol = 'https:';
+ var domain = this._domain;
+ var endpoint = '/user/ssodata';
+ var url = joinUrl(protocol, domain, endpoint);
+ var sameOrigin = same_origin(protocol, domain);
+ var data = {};
+
+ if (withActiveDirectories) {
+ data = {ldaps: 1, client_id: this._clientID};
+ }
+
+ return reqwest({
+ url: sameOrigin ? endpoint : url,
+ method: 'get',
+ type: 'json',
+ data: data,
+ crossOrigin: !sameOrigin,
+ withCredentials: !sameOrigin,
+ timeout: 3000
+ }).fail(function(err) {
+ var error = new Error("There was an error in the request that obtains the user's SSO data.");
+ error.cause = err;
+ cb(error, noResult);
+ }).then(function(resp) {
+ cb(null, resp);
+ });
+};
+
+/**
+ * Get all configured connections for a client
+ *
+ * @example
+ *
+ * auth0.getConnections(function (err, conns) {
+ * if (err) return console.log(err.message);
+ * expect(conns.length).to.be.above(0);
+ * expect(conns[0].name).to.eql('Apprenda.com');
+ * expect(conns[0].strategy).to.eql('adfs');
+ * expect(conns[0].status).to.eql(false);
+ * expect(conns[0].domain).to.eql('Apprenda.com');
+ * expect(conns[0].domain_aliases).to.eql(['Apprenda.com', 'foo.com', 'bar.com']);
+ * });
+ *
+ * @method getConnections
+ * @param {Function} callback
+ */
+// XXX We may change the way this method works in the future to use client's s3 file.
+
+Auth0.prototype.getConnections = function (callback) {
+ return jsonp('https://' + this._domain + '/public/api/' + this._clientID + '/connections', jsonpOpts, callback);
+};
+
+/**
+ * Send email or SMS to do passwordless authentication
+ *
+ * @example
+ * // To send an email
+ * auth0.startPasswordless({email: 'foo@bar.com'}, function (err, result) {
+ * if (err) return console.log(err.error_description);
+ * console.log(result);
+ * });
+ *
+ * @example
+ * // To send a SMS
+ * auth0.startPasswordless({phoneNumber: '+14251112222'}, function (err, result) {
+ * if (err) return console.log(err.error_description);
+ * console.log(result);
+ * });
+ *
+ * @method startPasswordless
+ * @param {Object} options
+ * @param {Function} callback
+ */
+
+Auth0.prototype.startPasswordless = function (options, callback) {
+ if ('object' !== typeof options) {
+ throw new Error('An options object is required');
+ }
+ if ('function' !== typeof callback) {
+ throw new Error('A callback function is required');
+ }
+ if (!options.email && !options.phoneNumber) {
+ throw new Error('An `email` or a `phoneNumber` is required.');
+ }
+
+ var protocol = 'https:';
+ var domain = this._domain;
+ var endpoint = '/passwordless/start';
+ var url = joinUrl(protocol, domain, endpoint);
+
+ var data = {client_id: this._clientID};
+ if (options.email) {
+ data.email = options.email;
+ data.connection = 'email';
+ if (options.authParams) {
+ data.authParams = options.authParams;
+ }
+
+ if (!options.send || options.send === "link") {
+ if (!data.authParams) {
+ data.authParams = {};
+ }
+
+ data.authParams.redirect_uri = this._callbackURL;
+ data.authParams.response_type = this._shouldRedirect && !this._callbackOnLocationHash ?
+ "code" : "token";
+ }
+
+ if (options.send) {
+ data.send = options.send;
+ }
+ } else {
+ data.phone_number = options.phoneNumber;
+ data.connection = 'sms';
+ }
+
+ if (this._useJSONP) {
+ if (this._sendClientInfo) {
+ data['auth0Client'] = this._getClientInfoString();
+ }
+
+ return jsonp(url + '?' + qs.stringify(data), jsonpOpts, function (err, resp) {
+ if (err) {
+ return callback(new Error(0 + ': ' + err.toString()));
+ }
+ return resp.status === 200 ? callback(null, true) : callback(resp.err || resp.error);
+ });
+ }
+
+ return reqwest({
+ url: same_origin(protocol, domain) ? endpoint : url,
+ method: 'post',
+ type: 'json',
+ headers: this._getClientInfoHeader(),
+ crossOrigin: !same_origin(protocol, domain),
+ data: data
+ })
+ .fail(function (err) {
+ try {
+ callback(JSON.parse(err.responseText));
+ } catch (e) {
+ var error = new Error(err.status + '(' + err.statusText + '): ' + err.responseText);
+ error.statusCode = err.status;
+ error.error = err.statusText;
+ error.message = err.responseText;
+ callback(error);
+ }
+ })
+ .then(function (result) {
+ callback(null, result);
+ });
+};
+
+Auth0.prototype.requestMagicLink = function(attrs, cb) {
+ return this.startPasswordless(attrs, cb);
+};
+
+Auth0.prototype.requestEmailCode = function(attrs, cb) {
+ attrs.send = "code";
+ return this.startPasswordless(attrs, cb);
+};
+
+Auth0.prototype.verifyEmailCode = function(attrs, cb) {
+ attrs.passcode = attrs.code;
+ delete attrs.code;
+ return this.login(attrs, cb);
+};
+
+Auth0.prototype.requestSMSCode = function(attrs, cb) {
+ return this.startPasswordless(attrs, cb);
+};
+
+Auth0.prototype.verifySMSCode = function(attrs, cb) {
+ attrs.passcode = attrs.code;
+ delete attrs.code;
+ return this.login(attrs, cb);
+};
+
+/**
+ * Returns the ISO 3166-1 code for the country where the request is
+ * originating.
+ *
+ * Fails if the request has to be made using JSONP.
+ *
+ * @private
+ */
+Auth0.prototype.getUserCountry = function(cb) {
+ var protocol = 'https:';
+ var domain = this._domain;
+ var endpoint = "/user/geoloc/country";
+ var url = joinUrl(protocol, domain, endpoint);
+
+ if (this._useJSONP) {
+ var error = new Error("The user's country can't be obtained using JSONP");
+ setTimeout(function() { cb(error) }, 0);
+ return;
+ }
+
+ reqwest({
+ url: same_origin(protocol, domain) ? endpoint : url,
+ method: "get",
+ type: "json",
+ headers: this._getClientInfoHeader(),
+ crossOrigin: !same_origin(protocol, domain),
+ success: function(resp) {
+ cb(null, resp.country_code)
+ },
+ error: function(err) {
+ var error = new Error("There was an error in the request that obtains the user's country");
+ error.cause = err;
+ cb(error);
+ }
+ });
+}
+
+Auth0.prototype._prepareResult = function(result) {
+ if (!result || typeof result !== "object") {
+ return;
+ }
+
+ var idTokenPayload = result.profile
+ ? result.profile
+ : this.decodeJwt(result.id_token);
+
+ return {
+ accessToken: result.access_token,
+ idToken: result.id_token,
+ idTokenPayload: idTokenPayload,
+ refreshToken: result.refresh_token,
+ state: result.state
+ };
+}
+
+/**
+ * Expose `Auth0` constructor
+ */
+
+module.exports = Auth0;
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./lib/LoginError":2,"./lib/assert_required":3,"./lib/base64_url":4,"./lib/index-of":5,"./lib/is-array":6,"./lib/json-parse":7,"./lib/same-origin":8,"./lib/use_jsonp":9,"./version":25,"jsonp":12,"qs":16,"reqwest":17,"trim":223,"winchan":18,"xtend":20}],2:[function(require,module,exports){
+/**
+ * Module dependencies.
+ */
+
+var json_parse = require('./json-parse');
+
+/**
+ * Expose `LoginError`
+ */
+
+module.exports = LoginError;
+
+/**
+ * Create a `LoginError` by extend of `Error`
+ *
+ * @param {Number} status
+ * @param {String} details
+ * @public
+ */
+
+function LoginError(status, details) {
+ var obj;
+
+ if (typeof details == 'string') {
+ try {
+ obj = json_parse(details);
+ } catch (er) {
+ obj = { message: details };
+ }
+ } else {
+ obj = details || { description: 'server error' };
+ }
+
+ if (!obj.code) {
+ obj.code = obj.error;
+ }
+
+ if ('unauthorized' === obj.code) {
+ status = 401;
+ }
+
+ var message = obj.description || obj.message || obj.error;
+
+ if ('PasswordStrengthError' === obj.name) {
+ message = "Password is not strong enough.";
+ }
+
+ var err = Error.call(this, message);
+
+ err.status = status;
+ err.name = obj.code;
+ err.code = obj.code;
+ err.details = obj;
+
+ if (status === 0) {
+ if (!err.code || err.code !== 'offline') {
+ err.code = 'Unknown';
+ err.message = 'Unknown error.';
+ }
+ }
+
+ return err;
+}
+
+/**
+ * Extend `LoginError.prototype` with `Error.prototype`
+ * and `LoginError` as constructor
+ */
+
+if (Object && Object.create) {
+ LoginError.prototype = Object.create(Error.prototype, {
+ constructor: { value: LoginError }
+ });
+}
+
+},{"./json-parse":7}],3:[function(require,module,exports){
+/**
+ * Expose `required`
+ */
+
+module.exports = required;
+
+/**
+ * Assert `prop` as requirement of `obj`
+ *
+ * @param {Object} obj
+ * @param {prop} prop
+ * @public
+ */
+
+function required (obj, prop) {
+ if (!obj[prop]) {
+ throw new Error(prop + ' is required.');
+ }
+}
+
+},{}],4:[function(require,module,exports){
+/**
+ * Module dependencies.
+ */
+
+var Base64 = require('Base64');
+
+/**
+ * Expose `base64_url_decode`
+ */
+
+module.exports = {
+ encode: encode,
+ decode: decode
+};
+
+/**
+ * Encode a `base64` `encodeURIComponent` string
+ *
+ * @param {string} str
+ * @public
+ */
+
+function encode(str) {
+ return Base64.btoa(str)
+ .replace(/\+/g, '-') // Convert '+' to '-'
+ .replace(/\//g, '_') // Convert '/' to '_'
+ .replace(/=+$/, ''); // Remove ending '='
+}
+
+/**
+ * Decode a `base64` `encodeURIComponent` string
+ *
+ * @param {string} str
+ * @public
+ */
+
+function decode(str) {
+ // Add removed at end '='
+ str += Array(5 - str.length % 4).join('=');
+
+ str = str
+ .replace(/\-/g, '+') // Convert '-' to '+'
+ .replace(/\_/g, '/'); // Convert '_' to '/'
+
+ return Base64.atob(str);
+}
+},{"Base64":10}],5:[function(require,module,exports){
+/**
+ * Resolve `isArray` as native or fallback
+ */
+
+module.exports = Array.prototype.indexOf
+ ? nativeIndexOf
+ : polyfillIndexOf;
+
+
+function nativeIndexOf(array, searchElement, fromIndex) {
+ return array.indexOf(searchElement, fromIndex);
+}
+
+
+function polyfillIndexOf(array, searchElement, fromIndex) {
+ // Production steps of ECMA-262, Edition 5, 15.4.4.14
+ // Reference: http://es5.github.io/#x15.4.4.14
+
+ var k;
+
+ // 1. Let O be the result of calling ToObject passing
+ // the array value as the argument.
+ if (array == null) {
+ throw new TypeError('"array" is null or not defined');
+ }
+
+ var O = Object(array);
+
+ // 2. Let lenValue be the result of calling the Get
+ // internal method of O with the argument "length".
+ // 3. Let len be ToUint32(lenValue).
+ var len = O.length >>> 0;
+
+ // 4. If len is 0, return -1.
+ if (len === 0) {
+ return -1;
+ }
+
+ // 5. If argument fromIndex was passed let n be
+ // ToInteger(fromIndex); else let n be 0.
+ var n = +fromIndex || 0;
+
+ if (Math.abs(n) === Infinity) {
+ n = 0;
+ }
+
+ // 6. If n >= len, return -1.
+ if (n >= len) {
+ return -1;
+ }
+
+ // 7. If n >= 0, then Let k be n.
+ // 8. Else, n<0, Let k be len - abs(n).
+ // If k is less than 0, then let k be 0.
+ k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
+
+ // 9. Repeat, while k < len
+ while (k < len) {
+ // a. Let Pk be ToString(k).
+ // This is implicit for LHS operands of the in operator
+ // b. Let kPresent be the result of calling the
+ // HasProperty internal method of O with argument Pk.
+ // This step can be combined with c
+ // c. If kPresent is true, then
+ // i. Let elementK be the result of calling the Get
+ // internal method of O with the argument ToString(k).
+ // ii. Let same be the result of applying the
+ // Strict Equality Comparison Algorithm to
+ // searchElement and elementK.
+ // iii. If same is true, return k.
+ if (k in O && O[k] === searchElement) {
+ return k;
+ }
+ k++;
+ }
+ return -1;
+};
+
+},{}],6:[function(require,module,exports){
+/**
+ * Module dependencies.
+ */
+
+var toString = Object.prototype.toString;
+
+/**
+ * Resolve `isArray` as native or fallback
+ */
+
+module.exports = null != Array.isArray
+ ? Array.isArray
+ : isArray;
+
+/**
+ * Wrap `Array.isArray` Polyfill for IE9
+ * source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
+ *
+ * @param {Array} array
+ * @public
+ */
+
+function isArray (array) {
+ return toString.call(array) === '[object Array]';
+};
+
+},{}],7:[function(require,module,exports){
+/**
+ * Expose `JSON.parse` method or fallback if not
+ * exists on `window`
+ */
+
+module.exports = 'undefined' === typeof window.JSON
+ ? require('json-fallback').parse
+ : window.JSON.parse;
+
+},{"json-fallback":11}],8:[function(require,module,exports){
+/**
+ * Check for same origin policy
+ */
+
+var protocol = window.location.protocol;
+var domain = window.location.hostname;
+var port = window.location.port;
+
+module.exports = same_origin;
+
+function same_origin (tprotocol, tdomain, tport) {
+ tport = tport || '';
+ return protocol === tprotocol && domain === tdomain && port === tport;
+}
+
+},{}],9:[function(require,module,exports){
+/**
+ * Expose `use_jsonp`
+ */
+
+module.exports = use_jsonp;
+
+/**
+ * Return true if `jsonp` is required
+ *
+ * @return {Boolean}
+ * @public
+ */
+
+function use_jsonp() {
+ var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : null;
+
+ if (xhr && 'withCredentials' in xhr) {
+ return false;
+ }
+
+ // We no longer support XDomainRequest for IE8 and IE9 for CORS because it has many quirks.
+ // if ('XDomainRequest' in window && window.location.protocol === 'https:') {
+ // return false;
+ // }
+
+ return true;
+}
+},{}],10:[function(require,module,exports){
+;(function () {
+
+ var
+ object = typeof exports != 'undefined' ? exports : this, // #8: web workers
+ chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
+ INVALID_CHARACTER_ERR = (function () {
+ // fabricate a suitable error object
+ try { document.createElement('$'); }
+ catch (error) { return error; }}());
+
+ // encoder
+ // [https://gist.github.com/999166] by [https://github.com/nignag]
+ object.btoa || (
+ object.btoa = function (input) {
+ for (
+ // initialize result and counter
+ var block, charCode, idx = 0, map = chars, output = '';
+ // if the next input index does not exist:
+ // change the mapping table to "="
+ // check if d has no fractional digits
+ input.charAt(idx | 0) || (map = '=', idx % 1);
+ // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8
+ output += map.charAt(63 & block >> 8 - idx % 1 * 8)
+ ) {
+ charCode = input.charCodeAt(idx += 3/4);
+ if (charCode > 0xFF) throw INVALID_CHARACTER_ERR;
+ block = block << 8 | charCode;
+ }
+ return output;
+ });
+
+ // decoder
+ // [https://gist.github.com/1020396] by [https://github.com/atk]
+ object.atob || (
+ object.atob = function (input) {
+ input = input.replace(/=+$/, '')
+ if (input.length % 4 == 1) throw INVALID_CHARACTER_ERR;
+ for (
+ // initialize result and counters
+ var bc = 0, bs, buffer, idx = 0, output = '';
+ // get next character
+ buffer = input.charAt(idx++);
+ // character found in table? initialize bit storage and add its ascii value;
+ ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
+ // and if not first of each 4 characters,
+ // convert the first 8 bits to one ascii character
+ bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
+ ) {
+ // try to find character in table (0-63, not found => -1)
+ buffer = chars.indexOf(buffer);
+ }
+ return output;
+ });
+
+}());
+
+},{}],11:[function(require,module,exports){
+/*
+ json2.js
+ 2011-10-19
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or ' '),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the value
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+*/
+
+/*jslint evil: true, regexp: true */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+var JSON = {};
+
+(function () {
+ 'use strict';
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return isFinite(this.valueOf())
+ ? this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z'
+ : null;
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string'
+ ? c
+ : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' : '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0
+ ? '[]'
+ : gap
+ ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
+ : '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ if (typeof rep[i] === 'string') {
+ k = rep[i];
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0
+ ? '{}'
+ : gap
+ ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
+ : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ text = String(text);
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/
+ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
+ .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
+ .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function'
+ ? walk({'': j}, '')
+ : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+}());
+
+module.exports = JSON
+},{}],12:[function(require,module,exports){
+/**
+ * Module dependencies
+ */
+
+var debug = require('debug')('jsonp');
+
+/**
+ * Module exports.
+ */
+
+module.exports = jsonp;
+
+/**
+ * Callback index.
+ */
+
+var count = 0;
+
+/**
+ * Noop function.
+ */
+
+function noop(){}
+
+/**
+ * JSONP handler
+ *
+ * Options:
+ * - param {String} qs parameter (`callback`)
+ * - timeout {Number} how long after a timeout error is emitted (`60000`)
+ *
+ * @param {String} url
+ * @param {Object|Function} optional options / callback
+ * @param {Function} optional callback
+ */
+
+function jsonp(url, opts, fn){
+ if ('function' == typeof opts) {
+ fn = opts;
+ opts = {};
+ }
+ if (!opts) opts = {};
+
+ var prefix = opts.prefix || '__jp';
+ var param = opts.param || 'callback';
+ var timeout = null != opts.timeout ? opts.timeout : 60000;
+ var enc = encodeURIComponent;
+ var target = document.getElementsByTagName('script')[0] || document.head;
+ var script;
+ var timer;
+
+ // generate a unique id for this request
+ var id = prefix + (count++);
+
+ if (timeout) {
+ timer = setTimeout(function(){
+ cleanup();
+ if (fn) fn(new Error('Timeout'));
+ }, timeout);
+ }
+
+ function cleanup(){
+ script.parentNode.removeChild(script);
+ window[id] = noop;
+ }
+
+ window[id] = function(data){
+ debug('jsonp got', data);
+ if (timer) clearTimeout(timer);
+ cleanup();
+ if (fn) fn(null, data);
+ };
+
+ // add qs component
+ url += (~url.indexOf('?') ? '&' : '?') + param + '=' + enc(id);
+ url = url.replace('?&', '?');
+
+ debug('jsonp req "%s"', url);
+
+ // create script
+ script = document.createElement('script');
+ script.src = url;
+ target.parentNode.insertBefore(script, target);
+}
+
+},{"debug":13}],13:[function(require,module,exports){
+
+/**
+ * This is the web browser implementation of `debug()`.
+ *
+ * Expose `debug()` as the module.
+ */
+
+exports = module.exports = require('./debug');
+exports.log = log;
+exports.formatArgs = formatArgs;
+exports.save = save;
+exports.load = load;
+exports.useColors = useColors;
+exports.storage = 'undefined' != typeof chrome
+ && 'undefined' != typeof chrome.storage
+ ? chrome.storage.local
+ : localstorage();
+
+/**
+ * Colors.
+ */
+
+exports.colors = [
+ 'lightseagreen',
+ 'forestgreen',
+ 'goldenrod',
+ 'dodgerblue',
+ 'darkorchid',
+ 'crimson'
+];
+
+/**
+ * Currently only WebKit-based Web Inspectors, Firefox >= v31,
+ * and the Firebug extension (any Firefox version) are known
+ * to support "%c" CSS customizations.
+ *
+ * TODO: add a `localStorage` variable to explicitly enable/disable colors
+ */
+
+function useColors() {
+ // is webkit? http://stackoverflow.com/a/16459606/376773
+ return ('WebkitAppearance' in document.documentElement.style) ||
+ // is firebug? http://stackoverflow.com/a/398120/376773
+ (window.console && (console.firebug || (console.exception && console.table))) ||
+ // is firefox >= v31?
+ // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
+ (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31);
+}
+
+/**
+ * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
+ */
+
+exports.formatters.j = function(v) {
+ return JSON.stringify(v);
+};
+
+
+/**
+ * Colorize log arguments if enabled.
+ *
+ * @api public
+ */
+
+function formatArgs() {
+ var args = arguments;
+ var useColors = this.useColors;
+
+ args[0] = (useColors ? '%c' : '')
+ + this.namespace
+ + (useColors ? ' %c' : ' ')
+ + args[0]
+ + (useColors ? '%c ' : ' ')
+ + '+' + exports.humanize(this.diff);
+
+ if (!useColors) return args;
+
+ var c = 'color: ' + this.color;
+ args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1));
+
+ // the final "%c" is somewhat tricky, because there could be other
+ // arguments passed either before or after the %c, so we need to
+ // figure out the correct index to insert the CSS into
+ var index = 0;
+ var lastC = 0;
+ args[0].replace(/%[a-z%]/g, function(match) {
+ if ('%%' === match) return;
+ index++;
+ if ('%c' === match) {
+ // we only are interested in the *last* %c
+ // (the user may have provided their own)
+ lastC = index;
+ }
+ });
+
+ args.splice(lastC, 0, c);
+ return args;
+}
+
+/**
+ * Invokes `console.log()` when available.
+ * No-op when `console.log` is not a "function".
+ *
+ * @api public
+ */
+
+function log() {
+ // this hackery is required for IE8/9, where
+ // the `console.log` function doesn't have 'apply'
+ return 'object' === typeof console
+ && console.log
+ && Function.prototype.apply.call(console.log, console, arguments);
+}
+
+/**
+ * Save `namespaces`.
+ *
+ * @param {String} namespaces
+ * @api private
+ */
+
+function save(namespaces) {
+ try {
+ if (null == namespaces) {
+ exports.storage.removeItem('debug');
+ } else {
+ exports.storage.debug = namespaces;
+ }
+ } catch(e) {}
+}
+
+/**
+ * Load `namespaces`.
+ *
+ * @return {String} returns the previously persisted debug modes
+ * @api private
+ */
+
+function load() {
+ var r;
+ try {
+ r = exports.storage.debug;
+ } catch(e) {}
+ return r;
+}
+
+/**
+ * Enable namespaces listed in `localStorage.debug` initially.
+ */
+
+exports.enable(load());
+
+/**
+ * Localstorage attempts to return the localstorage.
+ *
+ * This is necessary because safari throws
+ * when a user disables cookies/localstorage
+ * and you attempt to access it.
+ *
+ * @return {LocalStorage}
+ * @api private
+ */
+
+function localstorage(){
+ try {
+ return window.localStorage;
+ } catch (e) {}
+}
+
+},{"./debug":14}],14:[function(require,module,exports){
+
+/**
+ * This is the common logic for both the Node.js and web browser
+ * implementations of `debug()`.
+ *
+ * Expose `debug()` as the module.
+ */
+
+exports = module.exports = debug;
+exports.coerce = coerce;
+exports.disable = disable;
+exports.enable = enable;
+exports.enabled = enabled;
+exports.humanize = require('ms');
+
+/**
+ * The currently active debug mode names, and names to skip.
+ */
+
+exports.names = [];
+exports.skips = [];
+
+/**
+ * Map of special "%n" handling functions, for the debug "format" argument.
+ *
+ * Valid key names are a single, lowercased letter, i.e. "n".
+ */
+
+exports.formatters = {};
+
+/**
+ * Previously assigned color.
+ */
+
+var prevColor = 0;
+
+/**
+ * Previous log timestamp.
+ */
+
+var prevTime;
+
+/**
+ * Select a color.
+ *
+ * @return {Number}
+ * @api private
+ */
+
+function selectColor() {
+ return exports.colors[prevColor++ % exports.colors.length];
+}
+
+/**
+ * Create a debugger with the given `namespace`.
+ *
+ * @param {String} namespace
+ * @return {Function}
+ * @api public
+ */
+
+function debug(namespace) {
+
+ // define the `disabled` version
+ function disabled() {
+ }
+ disabled.enabled = false;
+
+ // define the `enabled` version
+ function enabled() {
+
+ var self = enabled;
+
+ // set `diff` timestamp
+ var curr = +new Date();
+ var ms = curr - (prevTime || curr);
+ self.diff = ms;
+ self.prev = prevTime;
+ self.curr = curr;
+ prevTime = curr;
+
+ // add the `color` if not set
+ if (null == self.useColors) self.useColors = exports.useColors();
+ if (null == self.color && self.useColors) self.color = selectColor();
+
+ var args = Array.prototype.slice.call(arguments);
+
+ args[0] = exports.coerce(args[0]);
+
+ if ('string' !== typeof args[0]) {
+ // anything else let's inspect with %o
+ args = ['%o'].concat(args);
+ }
+
+ // apply any `formatters` transformations
+ var index = 0;
+ args[0] = args[0].replace(/%([a-z%])/g, function(match, format) {
+ // if we encounter an escaped % then don't increase the array index
+ if (match === '%%') return match;
+ index++;
+ var formatter = exports.formatters[format];
+ if ('function' === typeof formatter) {
+ var val = args[index];
+ match = formatter.call(self, val);
+
+ // now we need to remove `args[index]` since it's inlined in the `format`
+ args.splice(index, 1);
+ index--;
+ }
+ return match;
+ });
+
+ if ('function' === typeof exports.formatArgs) {
+ args = exports.formatArgs.apply(self, args);
+ }
+ var logFn = enabled.log || exports.log || console.log.bind(console);
+ logFn.apply(self, args);
+ }
+ enabled.enabled = true;
+
+ var fn = exports.enabled(namespace) ? enabled : disabled;
+
+ fn.namespace = namespace;
+
+ return fn;
+}
+
+/**
+ * Enables a debug mode by namespaces. This can include modes
+ * separated by a colon and wildcards.
+ *
+ * @param {String} namespaces
+ * @api public
+ */
+
+function enable(namespaces) {
+ exports.save(namespaces);
+
+ var split = (namespaces || '').split(/[\s,]+/);
+ var len = split.length;
+
+ for (var i = 0; i < len; i++) {
+ if (!split[i]) continue; // ignore empty strings
+ namespaces = split[i].replace(/\*/g, '.*?');
+ if (namespaces[0] === '-') {
+ exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
+ } else {
+ exports.names.push(new RegExp('^' + namespaces + '$'));
+ }
+ }
+}
+
+/**
+ * Disable debug output.
+ *
+ * @api public
+ */
+
+function disable() {
+ exports.enable('');
+}
+
+/**
+ * Returns true if the given mode name is enabled, false otherwise.
+ *
+ * @param {String} name
+ * @return {Boolean}
+ * @api public
+ */
+
+function enabled(name) {
+ var i, len;
+ for (i = 0, len = exports.skips.length; i < len; i++) {
+ if (exports.skips[i].test(name)) {
+ return false;
+ }
+ }
+ for (i = 0, len = exports.names.length; i < len; i++) {
+ if (exports.names[i].test(name)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Coerce `val`.
+ *
+ * @param {Mixed} val
+ * @return {Mixed}
+ * @api private
+ */
+
+function coerce(val) {
+ if (val instanceof Error) return val.stack || val.message;
+ return val;
+}
+
+},{"ms":15}],15:[function(require,module,exports){
+/**
+ * Helpers.
+ */
+
+var s = 1000;
+var m = s * 60;
+var h = m * 60;
+var d = h * 24;
+var y = d * 365.25;
+
+/**
+ * Parse or format the given `val`.
+ *
+ * Options:
+ *
+ * - `long` verbose formatting [false]
+ *
+ * @param {String|Number} val
+ * @param {Object} options
+ * @return {String|Number}
+ * @api public
+ */
+
+module.exports = function(val, options){
+ options = options || {};
+ if ('string' == typeof val) return parse(val);
+ return options.long
+ ? long(val)
+ : short(val);
+};
+
+/**
+ * Parse the given `str` and return milliseconds.
+ *
+ * @param {String} str
+ * @return {Number}
+ * @api private
+ */
+
+function parse(str) {
+ str = '' + str;
+ if (str.length > 10000) return;
+ var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str);
+ if (!match) return;
+ var n = parseFloat(match[1]);
+ var type = (match[2] || 'ms').toLowerCase();
+ switch (type) {
+ case 'years':
+ case 'year':
+ case 'yrs':
+ case 'yr':
+ case 'y':
+ return n * y;
+ case 'days':
+ case 'day':
+ case 'd':
+ return n * d;
+ case 'hours':
+ case 'hour':
+ case 'hrs':
+ case 'hr':
+ case 'h':
+ return n * h;
+ case 'minutes':
+ case 'minute':
+ case 'mins':
+ case 'min':
+ case 'm':
+ return n * m;
+ case 'seconds':
+ case 'second':
+ case 'secs':
+ case 'sec':
+ case 's':
+ return n * s;
+ case 'milliseconds':
+ case 'millisecond':
+ case 'msecs':
+ case 'msec':
+ case 'ms':
+ return n;
+ }
+}
+
+/**
+ * Short format for `ms`.
+ *
+ * @param {Number} ms
+ * @return {String}
+ * @api private
+ */
+
+function short(ms) {
+ if (ms >= d) return Math.round(ms / d) + 'd';
+ if (ms >= h) return Math.round(ms / h) + 'h';
+ if (ms >= m) return Math.round(ms / m) + 'm';
+ if (ms >= s) return Math.round(ms / s) + 's';
+ return ms + 'ms';
+}
+
+/**
+ * Long format for `ms`.
+ *
+ * @param {Number} ms
+ * @return {String}
+ * @api private
+ */
+
+function long(ms) {
+ return plural(ms, d, 'day')
+ || plural(ms, h, 'hour')
+ || plural(ms, m, 'minute')
+ || plural(ms, s, 'second')
+ || ms + ' ms';
+}
+
+/**
+ * Pluralization helper.
+ */
+
+function plural(ms, n, name) {
+ if (ms < n) return;
+ if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name;
+ return Math.ceil(ms / n) + ' ' + name + 's';
+}
+
+},{}],16:[function(require,module,exports){
+/**
+ * Object#toString() ref for stringify().
+ */
+
+var toString = Object.prototype.toString;
+
+/**
+ * Object#hasOwnProperty ref
+ */
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+/**
+ * Array#indexOf shim.
+ */
+
+var indexOf = typeof Array.prototype.indexOf === 'function'
+ ? function(arr, el) { return arr.indexOf(el); }
+ : function(arr, el) {
+ if (typeof arr == 'string' && typeof "a"[0] == 'undefined') {
+ arr = arr.split('');
+ }
+ for (var i = 0; i < arr.length; i++) {
+ if (arr[i] === el) return i;
+ }
+ return -1;
+ };
+
+/**
+ * Array.isArray shim.
+ */
+
+var isArray = Array.isArray || function(arr) {
+ return toString.call(arr) == '[object Array]';
+};
+
+/**
+ * Object.keys shim.
+ */
+
+var objectKeys = Object.keys || function(obj) {
+ var ret = [];
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ ret.push(key);
+ }
+ }
+ return ret;
+};
+
+/**
+ * Array#forEach shim.
+ */
+
+var forEach = typeof Array.prototype.forEach === 'function'
+ ? function(arr, fn) { return arr.forEach(fn); }
+ : function(arr, fn) {
+ for (var i = 0; i < arr.length; i++) fn(arr[i]);
+ };
+
+/**
+ * Array#reduce shim.
+ */
+
+var reduce = function(arr, fn, initial) {
+ if (typeof arr.reduce === 'function') return arr.reduce(fn, initial);
+ var res = initial;
+ for (var i = 0; i < arr.length; i++) res = fn(res, arr[i]);
+ return res;
+};
+
+/**
+ * Cache non-integer test regexp.
+ */
+
+var isint = /^[0-9]+$/;
+
+function promote(parent, key) {
+ if (parent[key].length == 0) return parent[key] = {}
+ var t = {};
+ for (var i in parent[key]) {
+ if (hasOwnProperty.call(parent[key], i)) {
+ t[i] = parent[key][i];
+ }
+ }
+ parent[key] = t;
+ return t;
+}
+
+function parse(parts, parent, key, val) {
+ var part = parts.shift();
+
+ // illegal
+ if (hasOwnProperty.call(Object.prototype, key)) return;
+
+ // end
+ if (!part) {
+ if (isArray(parent[key])) {
+ parent[key].push(val);
+ } else if ('object' == typeof parent[key]) {
+ parent[key] = val;
+ } else if ('undefined' == typeof parent[key]) {
+ parent[key] = val;
+ } else {
+ parent[key] = [parent[key], val];
+ }
+ // array
+ } else {
+ var obj = parent[key] = parent[key] || [];
+ if (']' == part) {
+ if (isArray(obj)) {
+ if ('' != val) obj.push(val);
+ } else if ('object' == typeof obj) {
+ obj[objectKeys(obj).length] = val;
+ } else {
+ obj = parent[key] = [parent[key], val];
+ }
+ // prop
+ } else if (~indexOf(part, ']')) {
+ part = part.substr(0, part.length - 1);
+ if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
+ parse(parts, obj, part, val);
+ // key
+ } else {
+ if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
+ parse(parts, obj, part, val);
+ }
+ }
+}
+
+/**
+ * Merge parent key/val pair.
+ */
+
+function merge(parent, key, val){
+ if (~indexOf(key, ']')) {
+ var parts = key.split('[')
+ , len = parts.length
+ , last = len - 1;
+ parse(parts, parent, 'base', val);
+ // optimize
+ } else {
+ if (!isint.test(key) && isArray(parent.base)) {
+ var t = {};
+ for (var k in parent.base) t[k] = parent.base[k];
+ parent.base = t;
+ }
+ set(parent.base, key, val);
+ }
+
+ return parent;
+}
+
+/**
+ * Compact sparse arrays.
+ */
+
+function compact(obj) {
+ if ('object' != typeof obj) return obj;
+
+ if (isArray(obj)) {
+ var ret = [];
+
+ for (var i in obj) {
+ if (hasOwnProperty.call(obj, i)) {
+ ret.push(obj[i]);
+ }
+ }
+
+ return ret;
+ }
+
+ for (var key in obj) {
+ obj[key] = compact(obj[key]);
+ }
+
+ return obj;
+}
+
+/**
+ * Parse the given obj.
+ */
+
+function parseObject(obj){
+ var ret = { base: {} };
+
+ forEach(objectKeys(obj), function(name){
+ merge(ret, name, obj[name]);
+ });
+
+ return compact(ret.base);
+}
+
+/**
+ * Parse the given str.
+ */
+
+function parseString(str, options){
+ var ret = reduce(String(str).split(options.separator), function(ret, pair){
+ var eql = indexOf(pair, '=')
+ , brace = lastBraceInKey(pair)
+ , key = pair.substr(0, brace || eql)
+ , val = pair.substr(brace || eql, pair.length)
+ , val = val.substr(indexOf(val, '=') + 1, val.length);
+
+ // ?foo
+ if ('' == key) key = pair, val = '';
+ if ('' == key) return ret;
+
+ return merge(ret, decode(key), decode(val));
+ }, { base: {} }).base;
+
+ return compact(ret);
+}
+
+/**
+ * Parse the given query `str` or `obj`, returning an object.
+ *
+ * @param {String} str | {Object} obj
+ * @return {Object}
+ * @api public
+ */
+
+exports.parse = function(str, options){
+ if (null == str || '' == str) return {};
+ options = options || {};
+ options.separator = options.separator || '&';
+ return 'object' == typeof str
+ ? parseObject(str)
+ : parseString(str, options);
+};
+
+/**
+ * Turn the given `obj` into a query string
+ *
+ * @param {Object} obj
+ * @return {String}
+ * @api public
+ */
+
+var stringify = exports.stringify = function(obj, prefix) {
+ if (isArray(obj)) {
+ return stringifyArray(obj, prefix);
+ } else if ('[object Object]' == toString.call(obj)) {
+ return stringifyObject(obj, prefix);
+ } else if ('string' == typeof obj) {
+ return stringifyString(obj, prefix);
+ } else {
+ return prefix + '=' + encodeURIComponent(String(obj));
+ }
+};
+
+/**
+ * Stringify the given `str`.
+ *
+ * @param {String} str
+ * @param {String} prefix
+ * @return {String}
+ * @api private
+ */
+
+function stringifyString(str, prefix) {
+ if (!prefix) throw new TypeError('stringify expects an object');
+ return prefix + '=' + encodeURIComponent(str);
+}
+
+/**
+ * Stringify the given `arr`.
+ *
+ * @param {Array} arr
+ * @param {String} prefix
+ * @return {String}
+ * @api private
+ */
+
+function stringifyArray(arr, prefix) {
+ var ret = [];
+ if (!prefix) throw new TypeError('stringify expects an object');
+ for (var i = 0; i < arr.length; i++) {
+ ret.push(stringify(arr[i], prefix + '[' + i + ']'));
+ }
+ return ret.join('&');
+}
+
+/**
+ * Stringify the given `obj`.
+ *
+ * @param {Object} obj
+ * @param {String} prefix
+ * @return {String}
+ * @api private
+ */
+
+function stringifyObject(obj, prefix) {
+ var ret = []
+ , keys = objectKeys(obj)
+ , key;
+
+ for (var i = 0, len = keys.length; i < len; ++i) {
+ key = keys[i];
+ if ('' == key) continue;
+ if (null == obj[key]) {
+ ret.push(encodeURIComponent(key) + '=');
+ } else {
+ ret.push(stringify(obj[key], prefix
+ ? prefix + '[' + encodeURIComponent(key) + ']'
+ : encodeURIComponent(key)));
+ }
+ }
+
+ return ret.join('&');
+}
+
+/**
+ * Set `obj`'s `key` to `val` respecting
+ * the weird and wonderful syntax of a qs,
+ * where "foo=bar&foo=baz" becomes an array.
+ *
+ * @param {Object} obj
+ * @param {String} key
+ * @param {String} val
+ * @api private
+ */
+
+function set(obj, key, val) {
+ var v = obj[key];
+ if (hasOwnProperty.call(Object.prototype, key)) return;
+ if (undefined === v) {
+ obj[key] = val;
+ } else if (isArray(v)) {
+ v.push(val);
+ } else {
+ obj[key] = [v, val];
+ }
+}
+
+/**
+ * Locate last brace in `str` within the key.
+ *
+ * @param {String} str
+ * @return {Number}
+ * @api private
+ */
+
+function lastBraceInKey(str) {
+ var len = str.length
+ , brace
+ , c;
+ for (var i = 0; i < len; ++i) {
+ c = str[i];
+ if (']' == c) brace = false;
+ if ('[' == c) brace = true;
+ if ('=' == c && !brace) return i;
+ }
+}
+
+/**
+ * Decode `str`.
+ *
+ * @param {String} str
+ * @return {String}
+ * @api private
+ */
+
+function decode(str) {
+ try {
+ return decodeURIComponent(str.replace(/\+/g, ' '));
+ } catch (err) {
+ return str;
+ }
+}
+
+},{}],17:[function(require,module,exports){
+/*!
+ * Reqwest! A general purpose XHR connection manager
+ * license MIT (c) Dustin Diaz 2014
+ * https://github.com/ded/reqwest
+ */
+
+!function (name, context, definition) {
+ if (typeof module != 'undefined' && module.exports) module.exports = definition()
+ else if (typeof define == 'function' && define.amd) define(definition)
+ else context[name] = definition()
+}('reqwest', this, function () {
+
+ var win = window
+ , doc = document
+ , httpsRe = /^http/
+ , protocolRe = /(^\w+):\/\//
+ , twoHundo = /^(20\d|1223)$/ //http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request
+ , byTag = 'getElementsByTagName'
+ , readyState = 'readyState'
+ , contentType = 'Content-Type'
+ , requestedWith = 'X-Requested-With'
+ , head = doc[byTag]('head')[0]
+ , uniqid = 0
+ , callbackPrefix = 'reqwest_' + (+new Date())
+ , lastValue // data stored by the most recent JSONP callback
+ , xmlHttpRequest = 'XMLHttpRequest'
+ , xDomainRequest = 'XDomainRequest'
+ , noop = function () {}
+
+ , isArray = typeof Array.isArray == 'function'
+ ? Array.isArray
+ : function (a) {
+ return a instanceof Array
+ }
+
+ , defaultHeaders = {
+ 'contentType': 'application/x-www-form-urlencoded'
+ , 'requestedWith': xmlHttpRequest
+ , 'accept': {
+ '*': 'text/javascript, text/html, application/xml, text/xml, */*'
+ , 'xml': 'application/xml, text/xml'
+ , 'html': 'text/html'
+ , 'text': 'text/plain'
+ , 'json': 'application/json, text/javascript'
+ , 'js': 'application/javascript, text/javascript'
+ }
+ }
+
+ , xhr = function(o) {
+ // is it x-domain
+ if (o['crossOrigin'] === true) {
+ var xhr = win[xmlHttpRequest] ? new XMLHttpRequest() : null
+ if (xhr && 'withCredentials' in xhr) {
+ return xhr
+ } else if (win[xDomainRequest]) {
+ return new XDomainRequest()
+ } else {
+ throw new Error('Browser does not support cross-origin requests')
+ }
+ } else if (win[xmlHttpRequest]) {
+ return new XMLHttpRequest()
+ } else {
+ return new ActiveXObject('Microsoft.XMLHTTP')
+ }
+ }
+ , globalSetupOptions = {
+ dataFilter: function (data) {
+ return data
+ }
+ }
+
+ function succeed(r) {
+ var protocol = protocolRe.exec(r.url);
+ protocol = (protocol && protocol[1]) || window.location.protocol;
+ return httpsRe.test(protocol) ? twoHundo.test(r.request.status) : !!r.request.response;
+ }
+
+ function handleReadyState(r, success, error) {
+ return function () {
+ // use _aborted to mitigate against IE err c00c023f
+ // (can't read props on aborted request objects)
+ if (r._aborted) return error(r.request)
+ if (r._timedOut) return error(r.request, 'Request is aborted: timeout')
+ if (r.request && r.request[readyState] == 4) {
+ r.request.onreadystatechange = noop
+ if (succeed(r)) success(r.request)
+ else
+ error(r.request)
+ }
+ }
+ }
+
+ function setHeaders(http, o) {
+ var headers = o['headers'] || {}
+ , h
+
+ headers['Accept'] = headers['Accept']
+ || defaultHeaders['accept'][o['type']]
+ || defaultHeaders['accept']['*']
+
+ var isAFormData = typeof FormData === 'function' && (o['data'] instanceof FormData);
+ // breaks cross-origin requests with legacy browsers
+ if (!o['crossOrigin'] && !headers[requestedWith]) headers[requestedWith] = defaultHeaders['requestedWith']
+ if (!headers[contentType] && !isAFormData) headers[contentType] = o['contentType'] || defaultHeaders['contentType']
+ for (h in headers)
+ headers.hasOwnProperty(h) && 'setRequestHeader' in http && http.setRequestHeader(h, headers[h])
+ }
+
+ function setCredentials(http, o) {
+ if (typeof o['withCredentials'] !== 'undefined' && typeof http.withCredentials !== 'undefined') {
+ http.withCredentials = !!o['withCredentials']
+ }
+ }
+
+ function generalCallback(data) {
+ lastValue = data
+ }
+
+ function urlappend (url, s) {
+ return url + (/\?/.test(url) ? '&' : '?') + s
+ }
+
+ function handleJsonp(o, fn, err, url) {
+ var reqId = uniqid++
+ , cbkey = o['jsonpCallback'] || 'callback' // the 'callback' key
+ , cbval = o['jsonpCallbackName'] || reqwest.getcallbackPrefix(reqId)
+ , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)')
+ , match = url.match(cbreg)
+ , script = doc.createElement('script')
+ , loaded = 0
+ , isIE10 = navigator.userAgent.indexOf('MSIE 10.0') !== -1
+
+ if (match) {
+ if (match[3] === '?') {
+ url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name
+ } else {
+ cbval = match[3] // provided callback func name
+ }
+ } else {
+ url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em
+ }
+
+ win[cbval] = generalCallback
+
+ script.type = 'text/javascript'
+ script.src = url
+ script.async = true
+ if (typeof script.onreadystatechange !== 'undefined' && !isIE10) {
+ // need this for IE due to out-of-order onreadystatechange(), binding script
+ // execution to an event listener gives us control over when the script
+ // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
+ script.htmlFor = script.id = '_reqwest_' + reqId
+ }
+
+ script.onload = script.onreadystatechange = function () {
+ if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) {
+ return false
+ }
+ script.onload = script.onreadystatechange = null
+ script.onclick && script.onclick()
+ // Call the user callback with the last value stored and clean up values and scripts.
+ fn(lastValue)
+ lastValue = undefined
+ head.removeChild(script)
+ loaded = 1
+ }
+
+ // Add the script to the DOM head
+ head.appendChild(script)
+
+ // Enable JSONP timeout
+ return {
+ abort: function () {
+ script.onload = script.onreadystatechange = null
+ err({}, 'Request is aborted: timeout', {})
+ lastValue = undefined
+ head.removeChild(script)
+ loaded = 1
+ }
+ }
+ }
+
+ function getRequest(fn, err) {
+ var o = this.o
+ , method = (o['method'] || 'GET').toUpperCase()
+ , url = typeof o === 'string' ? o : o['url']
+ // convert non-string objects to query-string form unless o['processData'] is false
+ , data = (o['processData'] !== false && o['data'] && typeof o['data'] !== 'string')
+ ? reqwest.toQueryString(o['data'])
+ : (o['data'] || null)
+ , http
+ , sendWait = false
+
+ // if we're working on a GET request and we have data then we should append
+ // query string to end of URL and not post data
+ if ((o['type'] == 'jsonp' || method == 'GET') && data) {
+ url = urlappend(url, data)
+ data = null
+ }
+
+ if (o['type'] == 'jsonp') return handleJsonp(o, fn, err, url)
+
+ // get the xhr from the factory if passed
+ // if the factory returns null, fall-back to ours
+ http = (o.xhr && o.xhr(o)) || xhr(o)
+
+ http.open(method, url, o['async'] === false ? false : true)
+ setHeaders(http, o)
+ setCredentials(http, o)
+ if (win[xDomainRequest] && http instanceof win[xDomainRequest]) {
+ http.onload = fn
+ http.onerror = err
+ // NOTE: see
+ // http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/30ef3add-767c-4436-b8a9-f1ca19b4812e
+ http.onprogress = function() {}
+ sendWait = true
+ } else {
+ http.onreadystatechange = handleReadyState(this, fn, err)
+ }
+ o['before'] && o['before'](http)
+ if (sendWait) {
+ setTimeout(function () {
+ http.send(data)
+ }, 200)
+ } else {
+ http.send(data)
+ }
+ return http
+ }
+
+ function Reqwest(o, fn) {
+ this.o = o
+ this.fn = fn
+
+ init.apply(this, arguments)
+ }
+
+ function setType(header) {
+ // json, javascript, text/plain, text/html, xml
+ if (header.match('json')) return 'json'
+ if (header.match('javascript')) return 'js'
+ if (header.match('text')) return 'html'
+ if (header.match('xml')) return 'xml'
+ }
+
+ function init(o, fn) {
+
+ this.url = typeof o == 'string' ? o : o['url']
+ this.timeout = null
+
+ // whether request has been fulfilled for purpose
+ // of tracking the Promises
+ this._fulfilled = false
+ // success handlers
+ this._successHandler = function(){}
+ this._fulfillmentHandlers = []
+ // error handlers
+ this._errorHandlers = []
+ // complete (both success and fail) handlers
+ this._completeHandlers = []
+ this._erred = false
+ this._responseArgs = {}
+
+ var self = this
+
+ fn = fn || function () {}
+
+ if (o['timeout']) {
+ this.timeout = setTimeout(function () {
+ timedOut()
+ }, o['timeout'])
+ }
+
+ if (o['success']) {
+ this._successHandler = function () {
+ o['success'].apply(o, arguments)
+ }
+ }
+
+ if (o['error']) {
+ this._errorHandlers.push(function () {
+ o['error'].apply(o, arguments)
+ })
+ }
+
+ if (o['complete']) {
+ this._completeHandlers.push(function () {
+ o['complete'].apply(o, arguments)
+ })
+ }
+
+ function complete (resp) {
+ o['timeout'] && clearTimeout(self.timeout)
+ self.timeout = null
+ while (self._completeHandlers.length > 0) {
+ self._completeHandlers.shift()(resp)
+ }
+ }
+
+ function success (resp) {
+ var type = o['type'] || resp && setType(resp.getResponseHeader('Content-Type')) // resp can be undefined in IE
+ resp = (type !== 'jsonp') ? self.request : resp
+ // use global data filter on response text
+ var filteredResponse = globalSetupOptions.dataFilter(resp.responseText, type)
+ , r = filteredResponse
+ try {
+ resp.responseText = r
+ } catch (e) {
+ // can't assign this in IE<=8, just ignore
+ }
+ if (r) {
+ switch (type) {
+ case 'json':
+ try {
+ resp = win.JSON ? win.JSON.parse(r) : eval('(' + r + ')')
+ } catch (err) {
+ return error(resp, 'Could not parse JSON in response', err)
+ }
+ break
+ case 'js':
+ resp = eval(r)
+ break
+ case 'html':
+ resp = r
+ break
+ case 'xml':
+ resp = resp.responseXML
+ && resp.responseXML.parseError // IE trololo
+ && resp.responseXML.parseError.errorCode
+ && resp.responseXML.parseError.reason
+ ? null
+ : resp.responseXML
+ break
+ }
+ }
+
+ self._responseArgs.resp = resp
+ self._fulfilled = true
+ fn(resp)
+ self._successHandler(resp)
+ while (self._fulfillmentHandlers.length > 0) {
+ resp = self._fulfillmentHandlers.shift()(resp)
+ }
+
+ complete(resp)
+ }
+
+ function timedOut() {
+ self._timedOut = true
+ self.request.abort()
+ }
+
+ function error(resp, msg, t) {
+ resp = self.request
+ self._responseArgs.resp = resp
+ self._responseArgs.msg = msg
+ self._responseArgs.t = t
+ self._erred = true
+ while (self._errorHandlers.length > 0) {
+ self._errorHandlers.shift()(resp, msg, t)
+ }
+ complete(resp)
+ }
+
+ this.request = getRequest.call(this, success, error)
+ }
+
+ Reqwest.prototype = {
+ abort: function () {
+ this._aborted = true
+ this.request.abort()
+ }
+
+ , retry: function () {
+ init.call(this, this.o, this.fn)
+ }
+
+ /**
+ * Small deviation from the Promises A CommonJs specification
+ * http://wiki.commonjs.org/wiki/Promises/A
+ */
+
+ /**
+ * `then` will execute upon successful requests
+ */
+ , then: function (success, fail) {
+ success = success || function () {}
+ fail = fail || function () {}
+ if (this._fulfilled) {
+ this._responseArgs.resp = success(this._responseArgs.resp)
+ } else if (this._erred) {
+ fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
+ } else {
+ this._fulfillmentHandlers.push(success)
+ this._errorHandlers.push(fail)
+ }
+ return this
+ }
+
+ /**
+ * `always` will execute whether the request succeeds or fails
+ */
+ , always: function (fn) {
+ if (this._fulfilled || this._erred) {
+ fn(this._responseArgs.resp)
+ } else {
+ this._completeHandlers.push(fn)
+ }
+ return this
+ }
+
+ /**
+ * `fail` will execute when the request fails
+ */
+ , fail: function (fn) {
+ if (this._erred) {
+ fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
+ } else {
+ this._errorHandlers.push(fn)
+ }
+ return this
+ }
+ , 'catch': function (fn) {
+ return this.fail(fn)
+ }
+ }
+
+ function reqwest(o, fn) {
+ return new Reqwest(o, fn)
+ }
+
+ // normalize newline variants according to spec -> CRLF
+ function normalize(s) {
+ return s ? s.replace(/\r?\n/g, '\r\n') : ''
+ }
+
+ function serial(el, cb) {
+ var n = el.name
+ , t = el.tagName.toLowerCase()
+ , optCb = function (o) {
+ // IE gives value="" even where there is no value attribute
+ // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273
+ if (o && !o['disabled'])
+ cb(n, normalize(o['attributes']['value'] && o['attributes']['value']['specified'] ? o['value'] : o['text']))
+ }
+ , ch, ra, val, i
+
+ // don't serialize elements that are disabled or without a name
+ if (el.disabled || !n) return
+
+ switch (t) {
+ case 'input':
+ if (!/reset|button|image|file/i.test(el.type)) {
+ ch = /checkbox/i.test(el.type)
+ ra = /radio/i.test(el.type)
+ val = el.value
+ // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here
+ ;(!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val))
+ }
+ break
+ case 'textarea':
+ cb(n, normalize(el.value))
+ break
+ case 'select':
+ if (el.type.toLowerCase() === 'select-one') {
+ optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null)
+ } else {
+ for (i = 0; el.length && i < el.length; i++) {
+ el.options[i].selected && optCb(el.options[i])
+ }
+ }
+ break
+ }
+ }
+
+ // collect up all form elements found from the passed argument elements all
+ // the way down to child elements; pass a '