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/da.js b/build/da.js
new file mode 100644
index 000000000..dd952ca65
--- /dev/null
+++ b/build/da.js
@@ -0,0 +1 @@
+Auth0.registerLanguageDictionary("da", {"error":{"forgotPassword":{"too_many_requests":"Du har nået grænsen for forsøg på at skifte kodeord. Vent venligst før du prøver igen.","lock.fallback":"Vi beklager, men der skete en i forespørgslen efter nyt kodeord."},"login":{"blocked_user":"Denne bruger er blokeret.","invalid_user_password":"Forkerte loginoplysninger.","lock.fallback":"Vi beklager, men der skete en fejl i forbindelse med login.","lock.invalid_code":"Forkert kode.","lock.invalid_email_password":"Forkert email eller password.","lock.invalid_username_password":"Forkert brugernavn eller password.","lock.network":"Vi kunne for få forbindelse til serveren. Kontroller venligst din forbindelse og prøv igen.","lock.popup_closed":"Popup-vinduet er lukket. Prøv venligst igen.","lock.unauthorized":"Tilladelse blev ikke tildelt. Prøv igen.","password_change_required":"Du skal opdatere din adgangskode, fordi det er første gang du logger på, eller fordi din adgangskode er udløbet.","password_leaked":"Dette login er blevet blokeret, fordi din adgangskode er blevet lækket på en anden hjemmeside. Vi har sendt dig en e-mail med instruktioner om, hvordan du fjerner blokeringen.","too_many_attempts":"Din konto er blevet blokeret efter gentagne mislykkede login-forsøg."},"passwordless":{"bad.email":"Denne email er ugyldig","bad.phone_number":"Dette telefonnummer er ugyldigt","lock.fallback":"Vi beklager, men der skete en fejl"},"signUp":{"invalid_password":"Kodeordet er ugyldigt","lock.fallback":"Vi beklager, men der skete en fejl, da du forsøgte at oprette dig.","password_dictionary_error":"Kodeordet er for almindeligt.","password_no_user_info_error":"Kodeordet indeholder information om din bruger.","password_strength_error":"Kodeordet er for svagt.","user_exists":"Denne bruger eksisterer allerede.","username_exists":"Dette brugernavn eksisterer allerede."}},"success":{"logIn":"Tak fordi du loggede ind.","forgotPassword":"Vi har sendt den en mail med instruktioner til at nulstille dit kodeord.","magicLink":"Vi har sendt dig et link, som du kan bruge til at logge ind i %s.","signUp":"Tak fordi du oprettede en bruger."},"blankErrorHint":"Kan ikke være tom","codeInputPlaceholder":"din kode","databaseEnterpriseLoginInstructions":"","databaseEnterpriseAlternativeLoginInstructions":"eller","databaseSignUpInstructions":"","databaseAlternativeSignUpInstructions":"eller","emailInputPlaceholder":"dit@eksempel.dk","enterpriseLoginIntructions":"Log ind med dit login til din virksomhed.","enterpriseActiveLoginInstructions":"Indtast venligst dit login hos %s.","failedLabel":"Mislykkede!","forgotPasswordAction":"Har du glemt dit kodeord?","forgotPasswordInstructions":"Indtast venligst din email, så sender vi instruktioner til at nulstille dit kodeord.","forgotPasswordSubmitLabel":"Send email","invalidErrorHint":"Ugyldig","lastLoginInstructions":"Sidste gang loggede du ind med","loginAtLabel":"Log ind hos %s","loginLabel":"Log Ind","loginSubmitLabel":"Log Ind","loginWithLabel":"Log ind med %s","notYourAccountAction":"Er det ikke din konto?","passwordInputPlaceholder":"dit kodeord","passwordStrength":{"containsAtLeast":"Skal indeholde mindt %d af de følgende %d typer af karakterer:","identicalChars":"Ikke mere end %d identiske karakterer efter hinanden (f.eks., \"%s\" er ikke tilladt)","nonEmpty":"Ikke-tomt kodeord er påkrævet","numbers":"Numre (f.eks. 0-9)","lengthAtLeast":"Mindst %d karakterer langt","lowerCase":"Små bogstaver (a-z)","shouldContain":"Skal indeholde:","specialCharacters":"Specielle karakterer (f.eks. !@#$%^&*)","upperCase":"Store bogstaver (A-Z)"},"passwordlessEmailAlternativeInstructions":"Ellers, indtast din email for at logge ind eller oprette en konto","passwordlessEmailCodeInstructions":"En email med koden er sendt til %s.","passwordlessEmailInstructions":"Indtast din email for at logge ind eller oprette en konto","passwordlessSMSAlternativeInstructions":"Ellers, indtast dit telefonnummer for at logge ind eller oprette en konto","passwordlessSMSCodeInstructions":"En SMS med kode er sendt til %s.","passwordlessSMSInstructions":"Indtast dit telefonnummer for at logge ind eller oprette en konto","phoneNumberInputPlaceholder":"dit telefonnummer","resendCodeAction":"Har du ikke modtaget koden?","resendLabel":"Send igen","resendingLabel":"Sender igen...","retryLabel":"Prøv igen","sentLabel":"Sendt!","signUpLabel":"Opret dig","signUpSubmitLabel":"Opret dig","signUpTerms":"","signUpWithLabel":"Opret dig med %s","socialLoginInstructions":"","socialSignUpInstructions":"","ssoEnabled":"Single Sign-On aktiveret","submitLabel":"Send","unrecoverableError":"Der skete en fejl. Kontakt venligst den tekniske support.","usernameFormatErrorHint":"Brug %d-%d bogstaver, numre og \"_\"","usernameInputPlaceholder":"dit brugernavn","usernameOrEmailInputPlaceholder":"brugernavn/email","title":"Auth0","welcome":"Velkommen %s!","windowsAuthInstructions":"Du er forbundet fra din virksomheds netværk…","windowsAuthLabel":"Windows Authentication"});
\ No newline at end of file
diff --git a/build/de.js b/build/de.js
new file mode 100644
index 000000000..ac9eb8492
--- /dev/null
+++ b/build/de.js
@@ -0,0 +1 @@
+Auth0.registerLanguageDictionary("de", {"error":{"forgotPassword":{"too_many_requests":"Sie haben das Limit für die Rücksetzung des Passworts erreicht. Bitte warten Sie, bevor Sie es erneut versuchen.","lock.fallback":"Beim Zurücksetzen des Passworts ist ein Fehler aufgetreten."},"login":{"blocked_user":"Der Benutzer wird blockiert.","invalid_user_password":"Falsche Anmeldeinformationen.","lock.fallback":"Beim Verarbeiten der Anmeldung ist ein Fehler aufgetreten.","lock.invalid_code":"Falscher Code.","lock.invalid_email_password":"Falsche E-Mail oder Passwort.","lock.invalid_username_password":"Falscher Benutzername oder Passwort.","lock.network":"Der Server antwortet nicht. Bitte erneut versuchen.","lock.popup_closed":"Pop-up-Fenster geschlossen. Versuchen Sie es erneut.","lock.unauthorized":"Genehmigungen wurden nicht erteilt. Versuchen Sie es erneut.","password_change_required":"Sie müssen Ihr Passwort ändern, da Sie sich zum ersten Mal anmelden oder das Passwort abgelaufen ist.","password_leaked":"Sie müssen Ihr Passwort ändern, da Sie sich zum ersten Mal anmelden oder das Passwort abgelaufen ist.","too_many_attempts":"Ihr Konto wurde nach mehreren aufeinander folgenden Anmeldeversuche gesperrt."},"passwordless":{"bad.email":"Die E-Mail ist ungültig","bad.phone_number":"Die Telefonnummer ist ungültig","lock.fallback":"Es tut uns leid. Etwas ist schief gegangen."},"signUp":{"invalid_password":"Passwort ist ungültig.","lock.fallback":"Beim Verarbeiten der Registrierung ist ein Fehler aufgetreten.","password_dictionary_error":"Das Passwort ist zu allgemein.","password_no_user_info_error":"Passwort basiert auf Benutzerinformationen.","password_strength_error":"Passwort ist nicht sicher genug.","user_exists":"Der Nutzer existiert bereits.","username_exists":"Der Nutzername wird bereits verwendet."}},"success":{"logIn":"Danke für die Anmeldung.","forgotPassword":"Sie haben eine E-Mail erhalten, um Ihr Passwort zurückzusetzen.","magicLink":"Wir senden Ihnen einen Link zu anmelden um %s.","signUp":"Danke für's Registrieren."},"blankErrorHint":"Kann nicht leer sein","codeInputPlaceholder":"dein Code","databaseEnterpriseLoginInstructions":"","databaseEnterpriseAlternativeLoginInstructions":"oder","databaseSignUpInstructions":"","databaseAlternativeSignUpInstructions":"oder","emailInputPlaceholder":"yours@example.com","enterpriseLoginIntructions":"Einloggen mit Ihrem Firmenanmeldeinformationen.","enterpriseActiveLoginInstructions":"Bitte geben Sie Ihre Unternehmensanmeldeinformationen bei %s.","failedLabel":"Gescheitert!","forgotPasswordAction":"Passwort vergessen?","forgotPasswordInstructions":"Geben Sie bitte Ihre Email-Adresse ein. Wir werden Ihnen eine E-Mail senden um Ihr Passwort zurücksetzen zu können.","forgotPasswordSubmitLabel":"E-Mail senden","invalidErrorHint":"Ungültig","lastLoginInstructions":"Letztes Mal waren Sie angemeldet mit","loginAtLabel":"Anmelden bei %s","loginLabel":"Anmelden","loginSubmitLabel":"Anmelden","loginWithLabel":"Anmelden mit %s","notYourAccountAction":"Falscher Account?","passwordInputPlaceholder":"Ihr Passwort","passwordStrength":{"containsAtLeast":"Enthält mindestens %d der folgenden %d Arten der Zeichen:","identicalChars":"Nicht mehr als %d identische Zeichen in Folge (z. B. \"%s\" ist nicht erlaubt)","nonEmpty":"Das Passwort darf nicht leer sein","numbers":"Zahlen (z. B. 0-9)","lengthAtLeast":"Muss mindestens %d Zeichen lang sein","lowerCase":"Kleinbuchstaben (a-z)","shouldContain":"Sollte enthalten:","specialCharacters":"Sonderzeichen (z. B. !@#$%^&*)","upperCase":"Großbuchstaben (A-Z)"},"passwordlessEmailAlternativeInstructions":"Andernfalls geben Sie Ihre E-Mail in anmelden oder ein Konto erstellen","passwordlessEmailCodeInstructions":"Eine E-Mail mit dem Code wurde %s gesendet.","passwordlessEmailInstructions":"Geben Sie einfach Ihre E-Mail in anmelden oder ein Konto erstellen","passwordlessSMSAlternativeInstructions":"Andernfalls geben Sie Ihre Telefon in anmelden oder ein Konto erstellen","passwordlessSMSCodeInstructions":"Eine SMS mit dem Code wurde %s gesendet.","passwordlessSMSInstructions":"Geben Sie Ihre Telefonnummer in anmelden oder ein Konto erstellen","phoneNumberInputPlaceholder":"deine Telefonnummer","resendCodeAction":"Haben Sie nicht den Code bekommen?","resendLabel":"Erneut senden","resendingLabel":"Erneutes Senden...","retryLabel":"Wiederholen","sentLabel":"Senden","signUpLabel":"Registrieren","signUpSubmitLabel":"Registrieren","signUpTerms":"","signUpWithLabel":"Registrieren mit %s","socialLoginInstructions":"","socialSignUpInstructions":"","ssoEnabled":"Single Sign-On aktiviert","submitLabel":"Einreichen","unrecoverableError":"Etwas ist schief gelaufen. Bitte kontaktieren Sie den technischen Support.","usernameFormatErrorHint":"Verwenden Sie %d-%d Buchstaben, Zahlen und \"_\"","usernameInputPlaceholder":"dein Benutzername","usernameOrEmailInputPlaceholder":"Benutzername/E-Mail","title":"Auth0","welcome":"Willkommen %s!","windowsAuthInstructions":"Sie sind über Ihr Firmennetzwerk verbunden…","windowsAuthLabel":"Windows Authentication"});
\ No newline at end of file
diff --git a/build/en.js b/build/en.js
new file mode 100644
index 000000000..866e7e42b
--- /dev/null
+++ b/build/en.js
@@ -0,0 +1 @@
+Auth0.registerLanguageDictionary("en", {"error":{"forgotPassword":{"too_many_requests":"You have reached the limit on password change attempts. Please wait before trying again.","lock.fallback":"We're sorry, something went wrong when requesting the password change."},"login":{"blocked_user":"The user is blocked.","invalid_user_password":"Wrong credentials.","lock.fallback":"We're sorry, something went wrong when attempting to log in.","lock.invalid_code":"Wrong code.","lock.invalid_email_password":"Wrong email or password.","lock.invalid_username_password":"Wrong username or password.","lock.network":"We could not reach the server. Please check your connection and try again.","lock.popup_closed":"Popup window closed. Try again.","lock.unauthorized":"Permissions were not granted. Try again.","password_change_required":"You need to update your password because this is the first time you are logging in, or because your password has expired.","password_leaked":"This login has been blocked because your password has been leaked in another website. We’ve sent you an email with instructions on how to unblock it.","too_many_attempts":"Your account has been blocked after multiple consecutive login attempts."},"passwordless":{"bad.email":"The email is invalid","bad.phone_number":"The phone number is invalid","lock.fallback":"We're sorry, something went wrong"},"signUp":{"invalid_password":"Password is invalid.","lock.fallback":"We're sorry, something went wrong when attempting to sign up.","password_dictionary_error":"Password is too common.","password_no_user_info_error":"Password is based on user information.","password_strength_error":"Password is too weak.","user_exists":"The user already exists.","username_exists":"The username already exists."}},"success":{"logIn":"Thanks for logging in.","forgotPassword":"We've just sent you an email to reset your password.","magicLink":"We sent you a link to log in to %s.","signUp":"Thanks for signing up."},"blankErrorHint":"Can't be blank","codeInputPlaceholder":"your code","databaseEnterpriseLoginInstructions":"","databaseEnterpriseAlternativeLoginInstructions":"or","databaseSignUpInstructions":"","databaseAlternativeSignUpInstructions":"or","emailInputPlaceholder":"yours@example.com","enterpriseLoginIntructions":"Login with your corporate credentials.","enterpriseActiveLoginInstructions":"Please enter your coorporate credentials at %s.","failedLabel":"Failed!","forgotPasswordAction":"Don't remember your password?","forgotPasswordInstructions":"Please enter your email address. We will send you an email to reset your password.","forgotPasswordSubmitLabel":"Send email","invalidErrorHint":"Invalid","lastLoginInstructions":"Last time you logged in with","loginAtLabel":"Log in at %s","loginLabel":"Log In","loginSubmitLabel":"Log In","loginWithLabel":"Log in with %s","notYourAccountAction":"Not your account?","passwordInputPlaceholder":"your password","passwordStrength":{"containsAtLeast":"Contain at least %d of the following %d types of characters:","identicalChars":"No more than %d identical characters in a row (e.g., \"%s\" not allowed)","nonEmpty":"Non-empty password required","numbers":"Numbers (i.e. 0-9)","lengthAtLeast":"At least %d characters in length","lowerCase":"Lower case letters (a-z)","shouldContain":"Should contain:","specialCharacters":"Special characters (e.g. !@#$%^&*)","upperCase":"Upper case letters (A-Z)"},"passwordlessEmailAlternativeInstructions":"Otherwise, enter your email to sign in or create an account","passwordlessEmailCodeInstructions":"An email with the code has been sent to %s.","passwordlessEmailInstructions":"Enter your email to sign in or create an account","passwordlessSMSAlternativeInstructions":"Otherwise, enter your phone to sign in or create an account","passwordlessSMSCodeInstructions":"An SMS with the code has been sent to %s.","passwordlessSMSInstructions":"Enter your phone to sign in or create an account","phoneNumberInputPlaceholder":"your phone number","resendCodeAction":"Did not get the code?","resendLabel":"Resend","resendingLabel":"Resending...","retryLabel":"Retry","sentLabel":"Sent!","signUpLabel":"Sign Up","signUpSubmitLabel":"Sign Up","signUpTerms":"","signUpWithLabel":"Sign up with %s","socialLoginInstructions":"","socialSignUpInstructions":"","ssoEnabled":"Single Sign-On enabled","submitLabel":"Submit","unrecoverableError":"Something went wrong. Please contact technical support.","usernameFormatErrorHint":"Use %d-%d letters, numbers and \"_\"","usernameInputPlaceholder":"your username","usernameOrEmailInputPlaceholder":"username/email","title":"Auth0","welcome":"Welcome %s!","windowsAuthInstructions":"You are connected from your corporate network…","windowsAuthLabel":"Windows Authentication"});
\ No newline at end of file
diff --git a/build/es.js b/build/es.js
new file mode 100644
index 000000000..3e89e0d65
--- /dev/null
+++ b/build/es.js
@@ -0,0 +1 @@
+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.","too_many_attempts":"Su cuenta ha sido bloqueada luego de múltiples intentos de inicio de sesión consecutivos."},"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 inválida.","lock.fallback":"Ocurrió un error durante el registro.","password_dictionary_error":"La constraseña es muy común.","password_no_user_info_error":"La constraseña es similar a los datos del usuario.","password_strength_error":"La contraseña es muy débil.","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.","forgotPasswordSubmitLabel":"Enviar email","invalidErrorHint":"Inválido","lastLoginInstructions":"La última vez inició sesión con","loginAtLabel":"Iniciar en %s","loginLabel":"Iniciar sesión","loginSubmitLabel":"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","signUpSubmitLabel":"Registrarse","signUpTerms":"","signUpWithLabel":"Registrarse con %s","socialLoginInstructions":"","socialSignUpInstructions":"","ssoEnabled":"Inicio de sesión único activado","submitLabel":"Enviar","unrecoverableError":"Ocurrió un error. Por favor, contacte a soporte técnico.","usernameFormatErrorHint":"%d-%d 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"});
\ No newline at end of file
diff --git a/build/fr.js b/build/fr.js
new file mode 100644
index 000000000..40f9afd50
--- /dev/null
+++ b/build/fr.js
@@ -0,0 +1 @@
+Auth0.registerLanguageDictionary("fr", {"error":{"forgotPassword":{"too_many_requests":"Vous avez atteint la limite de tentatives de changement de mot de passe. Veuillez patienter avant de recommencer.","lock.fallback":"Nous sommes désolés, un problème est survenu lors de la demande de changement de mot de passe."},"login":{"blocked_user":"L’utilisateur est bloqué.","invalid_user_password":"Mauvais identifiants.","lock.fallback":"Nous sommes désolés, un problème est survenu lors de la tentative de connexion.","lock.invalid_code":"Mauvais code.","lock.invalid_email_password":"Mauvaise adresse de messagerie ou mot de passe.","lock.invalid_username_password":"Mauvais nom d’utilisateur ou mot de passe.","lock.network":"Nous ne pouvons pas joindre le serveur. Vérifiez votre connexion et réessayez.","lock.popup_closed":"La fenêtre popup a été fermée. Veuillez réessayer.","lock.unauthorized":"Les permissions n’ont pas été accordées. Veuillez réessayer.","password_change_required":"Vous devez mettre à jour votre mot de passe, soit parce qu’il s’agit de votre première connexion, soit parce que ce dernier a expiré.","password_leaked":"Cette connexion a été bloquée parce que votre mot de passe a été utilisé sur un autre site web. Nous vous avons envoyé un courriel avec des instructions pour la débloquer.","too_many_attempts":"Votre compte a été bloqué à la suite de trop nombreuses tentatives de connexion consécutives."},"passwordless":{"bad.email":"L’adresse de messagerie n’est pas valide","bad.phone_number":"Le numéro de téléphone n’est pas valide","lock.fallback":"Nous sommes désolés, un problème est survenu"},"signUp":{"invalid_password":"Le mot de passe n’est pas valide.","lock.fallback":"Nous sommes désolés, un problème est survenu lors de la tentative d’inscription.","password_dictionary_error":"Le mot de passe est trop commun.","password_no_user_info_error":"Le mot de passe est basé sur des informations utilisateur.","password_strength_error":"La force du mot de passe est trop faible.","user_exists":"Cet utilisateur existe déjà.","username_exists":"Ce nom d’utilisateur existe déjà."}},"success":{"logIn":"Merci de vous être connecté.","forgotPassword":"Nous venons de vous envoyer un courriel pour réinitialiser votre mot de passe.","magicLink":"Nous vous avons envoyé un lien pour vous connecter à %s.","signUp":"Merci de vous être inscrit."},"blankErrorHint":"Ne peut être vide","codeInputPlaceholder":"votre code","databaseEnterpriseLoginInstructions":"","databaseEnterpriseAlternativeLoginInstructions":"ou","databaseSignUpInstructions":"","databaseAlternativeSignUpInstructions":"ou","emailInputPlaceholder":"votreadresse@exemple.com","enterpriseLoginIntructions":"Connectez-vous avec vos identifiants d’entreprise.","enterpriseActiveLoginInstructions":"Veuillez entrer les identifiants de connexion de votre entreprise %s.","failedLabel":"A échoué !","forgotPasswordAction":"Mot de passe oublié ?","forgotPasswordInstructions":"Veuillez entrer votre adresse de messagerie. Nous vous enverrons un courriel pour réinitialiser votre mot de passe.","forgotPasswordSubmitLabel":"Envoyer le courriel","invalidErrorHint":"Invalide","lastLoginInstructions":"Dernière connexion avec","loginAtLabel":"Connexion à %s","loginLabel":"Connexion","loginSubmitLabel":"Connexion","loginWithLabel":"Se connecter avec %s","notYourAccountAction":"Ceci n’est pas votre compte ?","passwordInputPlaceholder":"Votre mot de passe","passwordStrength":{"containsAtLeast":"Doit contenir au moins %d des %d types de caractères :","identicalChars":"Pas plus de %d caractères identiques dans une ligne (par ex., « %s » n’est pas autorisé)","nonEmpty":"Mot de passe non vide requis","numbers":"Chiffres (i.e. 0-9)","lengthAtLeast":"Au moins %d caractères","lowerCase":"Lettres minuscules (a-z)","shouldContain":"Doit contenir :","specialCharacters":"Caractères spéciaux (par ex. !@#$%^&*)","upperCase":"Lettres majuscules (A-Z)"},"passwordlessEmailAlternativeInstructions":"Sinon entrez votre adresse de messagerie pour vous connecter ou créez un compte","passwordlessEmailCodeInstructions":"Un courriel avec le code a été envoyé à %s.","passwordlessEmailInstructions":"Entrez votre adresse de messagerie pour vous connecter ou créez un compte","passwordlessSMSAlternativeInstructions":"Sinon entrez votre numéro de téléphone pour vous connecter ou créez un compte","passwordlessSMSCodeInstructions":"Un SMS avec le code a été envoyé à %s.","passwordlessSMSInstructions":"Entrez votre numéro de téléphone pour vous connecter ou créez un compte","phoneNumberInputPlaceholder":"votre numéro de téléphone","resendCodeAction":"Vous n’avez pas reçu le code ?","resendLabel":"Envoyer une nouvelle fois","resendingLabel":"Nouvel envoi en cours…","retryLabel":"Réessayer","sentLabel":"Envoyé !","signUpLabel":"Inscription","signUpSubmitLabel":"Inscription","signUpTerms":"","signUpWithLabel":"S’inscrire avec %s","socialLoginInstructions":"","socialSignUpInstructions":"","ssoEnabled":"Authentification unique activée","submitLabel":"Envoyer","unrecoverableError":"Un problème est survenu. Veuillez contacter l’assistance technique.","usernameFormatErrorHint":"Utilisez entre %d-%d lettres, chiffres et « _ »","usernameInputPlaceholder":"votre nom d’utilisateur","usernameOrEmailInputPlaceholder":"nom d’utilisateur/adresse de messagerie","title":"Auth0","welcome":"Bienvenue %s !","windowsAuthInstructions":"Vous êtes connecté depuis votre réseau d’entreprise…","windowsAuthLabel":"Authentification Windows"});
\ No newline at end of file
diff --git a/build/hu.js b/build/hu.js
new file mode 100644
index 000000000..a59c99657
--- /dev/null
+++ b/build/hu.js
@@ -0,0 +1 @@
+Auth0.registerLanguageDictionary("hu", {"error":{"forgotPassword":{"too_many_requests":"Elérted a jelszóváltoztatási probálkozások engedélyezett számát. Kérlek, várj egy kicsit mielőtt újrapróbálnád!","lock.fallback":"Sajnáljuk, valami hiba történt a jelszóváltoztatás során."},"login":{"blocked_user":"A felhasználó nincsen engedélyezve.","invalid_user_password":"Hibás bejelentkezés.","lock.fallback":"Sajnáljuk, valami hiba történt a bejelentkezés során.","lock.invalid_code":"Hibás PIN.","lock.invalid_email_password":"Hibás e-mail vagy jelszó.","lock.invalid_username_password":"Hibás felhasználónév vagy jelszó.","lock.network":"A szerver nem elérhető. Kérlek, ellenőrizd az internetkapcsolatot, és próbáld újra.!","lock.popup_closed":"A felugró ablak be lett zárva. Próbáld újra!","lock.unauthorized":"Engedély megtagadva. Próbáld újra!","password_change_required":"A jelszavadat meg kell változtatnod, mert vagy most lépsz be először, vagy lejárt a jelszavad.","password_leaked":"Az azonosítót letiltottuk, mert a hozzá tartozó jelszó egy másik honlapon nyilvánosságra került. Küldtünk neked egy e-mailt az azonosító engedélyezésének menetéről.","too_many_attempts":"Több gyakori bejelentkezés után az azonosítódat letiltottuk."},"passwordless":{"bad.email":"Érvénytelen e-mailcím","bad.phone_number":"Érvénytelen telefonszám","lock.fallback":"Sajnáljuk, valami hiba történt"},"signUp":{"invalid_password":"Érvénytelen jelszó.","lock.fallback":"Sajnáljuk, a feliratkozás során valami hiba történt.","password_dictionary_error":"Túl gyakori jelszó.","password_no_user_info_error":"A jelszó a felhasználói adatokra támaszkodik.","password_strength_error":"Túl gyenge jelszó.","user_exists":"A felhasználó már létezik.","username_exists":"A felhasználónév már foglalt."}},"success":{"logIn":"Köszönjük a bejelentkezésed","forgotPassword":"Küldtünk neked egy e-mailt a jelszó visszaállításának menetéről.","magicLink":"Küldtünk neked egy bejelentkezési linket a %s honlaphoz.","signUp":"Köszönjük, hogy feliratkoztál."},"blankErrorHint":"Nem lehet üres","codeInputPlaceholder":"PIN","databaseEnterpriseLoginInstructions":"","databaseEnterpriseAlternativeLoginInstructions":"vagy","databaseSignUpInstructions":"","databaseAlternativeSignUpInstructions":"vagy","emailInputPlaceholder":"emailcim@example.com","enterpriseLoginIntructions":"Bejelentkezés céges azonosítóval.","enterpriseActiveLoginInstructions":"Kérlek, add meg a céges azonosítódat a %s honlapon.","failedLabel":"Sikertelen!","forgotPasswordAction":"Nem emlékszel a jelszavadra?","forgotPasswordInstructions":"Kérlek, add meg az e-mailcímedet! Küldünk neked egy e-mailt a jelszó helyreállításának menetéről.","forgotPasswordSubmitLabel":"E-mail küldése","invalidErrorHint":"Érvénytelen","lastLoginInstructions":"Utolsó bejelentkezés","loginAtLabel":"Bejelentkezés ideje: %s","loginLabel":"Belépés","loginSubmitLabel":"Belépés","loginWithLabel":"Belépés %s-val","notYourAccountAction":"Nem a te fiókod?","passwordInputPlaceholder":"jelszavad","passwordStrength":{"containsAtLeast":"Legalább %d alkalommal tartalmazza a következő %d karaktert:","identicalChars":"Legfeljebb %d azonos karakter szerepelhet egy sorban (pl. \"%s\" nem engedélyezett)","nonEmpty":"A jelszó nem lehet üres","numbers":"Számok (0-9)","lengthAtLeast":"Legalább %d hosszú","lowerCase":"Kisbetűk (a-z)","shouldContain":"Tartalmazzon:","specialCharacters":"Különleges karakterek (e.g. !@#$%^&*)","upperCase":"Nagybetűk (A-Z)"},"passwordlessEmailAlternativeInstructions":"Vagy, bejelentkezéshez vagy feliratkozáshoz add meg az e-mailcímed","passwordlessEmailCodeInstructions":"A PIN-t e-mailben elküldük a %s címre.","passwordlessEmailInstructions":"Bejelentkezéshez vagy feliratkozáshoz add meg az e-mailcímed","passwordlessSMSAlternativeInstructions":"Vagy, bejelentkezéshez vagy feliratkozáshoz add meg a telefonszámod","passwordlessSMSCodeInstructions":"A PIN-t SMS-ben elküldtük a %s számra.","passwordlessSMSInstructions":"Bejelentkezéshez vagy feliratkozáshoz add meg a telefonszámod","phoneNumberInputPlaceholder":"telefonszámod","resendCodeAction":"Nem kaptad meg a PIN-t?","resendLabel":"Újraküldés","resendingLabel":"Újraküldés...","retryLabel":"Próbáld újra","sentLabel":"Elküldve!","signUpLabel":"Feliratkozás","signUpSubmitLabel":"Feliratkozás","signUpTerms":"","signUpWithLabel":"Feliratkozás %s-val","socialLoginInstructions":"","socialSignUpInstructions":"","ssoEnabled":"Egyszeri bejelentkezés engedélyezve","submitLabel":"Mehet","unrecoverableError":"Valaim hiba történt. Kérlek, lépj kapcsolatba a műszaki ügyfélszolgálattal.","usernameFormatErrorHint":"Használj %d-%d betűt, számot és \"_\"-t","usernameInputPlaceholder":"felhasználóneved","usernameOrEmailInputPlaceholder":"felhasználónév/e-mail","title":"Auth0","welcome":"Üdvözöllek %s!","windowsAuthInstructions":"A céged hálózatoddal kapcsolódsz…","windowsAuthLabel":"Windows bejelentkezés"});
\ No newline at end of file
diff --git a/build/it.js b/build/it.js
new file mode 100644
index 000000000..656539d41
--- /dev/null
+++ b/build/it.js
@@ -0,0 +1 @@
+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.","too_many_attempts":"Il suo account è stato bloccato dopo vari tentativi di accesso consecutivi."},"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 non è valida.","lock.fallback":"Ci dispiace, qualcosa è andato storto quando si tenta di iscriversi.","password_dictionary_error":"La password è troppo comune.","password_no_user_info_error":"La password si basa sulle informazioni dell'utente.","password_strength_error":"La password è troppo debole.","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.","forgotPasswordSubmitLabel":"Inviare email","invalidErrorHint":"Non valido","lastLoginInstructions":"L’ultima volta Lei ha effettuato l’accesso con","loginAtLabel":"Accedere a %s","loginLabel":"Accesso","loginSubmitLabel":"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","signUpSubmitLabel":"Registrazione","signUpTerms":"","signUpWithLabel":"Registra con %s","socialLoginInstructions":"","socialSignUpInstructions":"","ssoEnabled":"Single Sign-On abilitati","submitLabel":"Invio","unrecoverableError":"Qualcosa è andato storto. Si prega di contattare il supporto tecnico.","usernameFormatErrorHint":"Si prega di utilizzare %d-%d lettere, numeri e \"_\"","usernameInputPlaceholder":"il Suo nome utente","usernameOrEmailInputPlaceholder":"il Suo nome utente or email","title":"Auth0","welcome":"Benvenuto %s!","windowsAuthInstructions":"Si è connessi dalla rete aziendale…","windowsAuthLabel":"Autenticazione Windows"});
\ No newline at end of file
diff --git a/build/lock.js b/build/lock.js
new file mode 100644
index 000000000..9aa38af41
--- /dev/null
+++ b/build/lock.js
@@ -0,0 +1,44819 @@
+(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._responseType = this._parseResponseType(options, true) || "code";
+ this._responseMode = this._parseResponseMode(options, true);
+ 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._getResponseType = function(opts) {
+ return this._parseResponseType(opts) || this._responseType;
+};
+
+Auth0.prototype._getCallbackOnLocationHash = function(options) {
+ return this._getResponseMode(options) !== "form_post"
+ && this._getResponseType(options) !== "code";
+};
+
+Auth0.prototype._getResponseMode = function(opts) {
+ var result = this._parseResponseMode(opts) || this._responseMode;
+ return result === "form_post"
+ ? "form_post"
+ : null;
+};
+
+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 this._sendClientInfo
+ ? { '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) {
+ var result = {
+ scope: 'openid',
+ response_type: this._getResponseType(options)
+ };
+
+ var responseMode = this._getResponseMode(options);
+ if (responseMode) {
+ result.response_mode = responseMode;
+ }
+
+ return result;
+};
+
+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;
+ hash = hash.substr(1).replace(/^\//, '');
+ var parsed_qs = qs.parse(hash);
+
+ if (parsed_qs.hasOwnProperty('error')) {
+ var err = {
+ error: parsed_qs.error,
+ error_description: parsed_qs.error_description
+ };
+
+ if (parsed_qs.state) {
+ err.state = parsed_qs.state;
+ }
+
+ return err;
+ }
+
+ if (!parsed_qs.hasOwnProperty('access_token')
+ && !parsed_qs.hasOwnProperty('id_token')
+ && !parsed_qs.hasOwnProperty('refresh_token')) {
+ return null;
+ }
+
+ var prof;
+
+ if (parsed_qs.id_token) {
+ var invalidJwt = function (error) {
+ var err = {
+ error: 'invalid_token',
+ error_description: error
+ };
+ return err;
+ };
+
+ prof = this.decodeJwt(parsed_qs.id_token);
+
+ // 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: parsed_qs.id_token,
+ idTokenPayload: prof,
+ refreshToken: parsed_qs.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
+ };
+}
+
+Auth0.prototype._parseResponseType = function(opts, setFlags) {
+ if (!opts) opts = {};
+
+ if (setFlags
+ && !this._providedResponseOptions
+ && opts.hasOwnProperty("callbackOnLocationHash")) {
+ this._providedCallbackOnLocationHash = true;
+ }
+
+ if (setFlags
+ && !this._providedCallbackOnLocationHash
+ && opts.hasOwnProperty("responseType")) {
+ this._providedResponseOptions = true;
+ }
+
+ if (!this._providedCallbackOnLocationHash
+ && !this._providedResponseOptions
+ && opts.hasOwnProperty("callbackOnLocationHash")
+ && opts.hasOwnProperty("responseType")) {
+ warn("The responseType option will be ignored. Both callbackOnLocationHash and responseType options were provided and they can't be used together.");
+ }
+
+ if (this._providedCallbackOnLocationHash
+ && opts.hasOwnProperty("responseType")) {
+ warn("The responseType option will be ignored. The callbackOnLocationHash option was provided to the constructor and they can't be mixed.");
+ }
+
+ if (this._providedResponseOptions
+ && opts.hasOwnProperty("callbackOnLocationHash")) {
+ warn("The callbackOnLocationHash option will be ignored. The responseType option was provided to the constructor and they can't be mixed.");
+ }
+
+ if (!this._providedCallbackOnLocationHash
+ && !opts.hasOwnProperty("callbackOnLocationHash")
+ && opts.responseType
+ && !validResponseType(opts.responseType)) {
+ warn("The responseType option will be ignored. Its valid values are \"code\", \"id_token\", \"token\" or any combination of them.");
+ }
+
+ var result = undefined;
+
+ if (!this._providedResponseOptions
+ && null != opts.callbackOnLocationHash) {
+ result = callbackOnLocationHashToResponseType(opts.callbackOnLocationHash);
+ }
+
+ if (!this._providedCallbackOnLocationHash
+ && !opts.hasOwnProperty("callbackOnLocationHash")
+ && opts.responseType
+ && validResponseType(opts.responseType)) {
+ result = opts.responseType;
+ }
+
+ return result;
+}
+
+Auth0.prototype._parseResponseMode = function(opts, setFlags) {
+ if (!opts) opts = {};
+
+ if (setFlags
+ && !this._providedCallbackOnLocationHash
+ && opts.hasOwnProperty("responseMode")) {
+ this._providedResponseOptions = true;
+ }
+
+ if (this._providedCallbackOnLocationHash
+ && opts.hasOwnProperty("responseMode")) {
+ warn("The responseMode option will be ignored. The callbackOnLocationHash option was provided to the constructor and they can't be mixed.");
+ }
+
+ if (!this._providedCallbackOnLocationHash
+ && !this._providedResponseOptions
+ && opts.hasOwnProperty("callbackOnLocationHash")
+ && opts.hasOwnProperty("responseMode")) {
+ warn("The responseMode option will be ignored. Both callbackOnLocationHash and responseMode options were provided and they can't be used together.");
+ }
+
+ var result = undefined;
+
+ if (!this._providedCallbackOnLocationHash
+ && opts.responseMode
+ && !validResponseMode(opts.responseMode)) {
+ warn("The responseMode option will be ignored. Its only valid value is \"form_post\".");
+ }
+
+ if (!this._providedCallbackOnLocationHash
+ && validResponseMode(opts.responseMode)) {
+ result = opts.responseMode;
+ }
+
+ return result;
+}
+
+function callbackOnLocationHashToResponseType(x) {
+ return x ? "token" : "code";
+}
+
+function validResponseType(str) {
+ if (typeof str !== "string") return false;
+
+ var RESPONSE_TYPES = ["code", "id_token", "token"];
+ var parts = str.split(" ");
+
+ for (var i = 0; i < parts.length; i++) {
+ if (RESPONSE_TYPES.indexOf(parts[i]) === -1) return false;
+ }
+
+ return parts.length >= 1;
+}
+
+function validResponseMode(str) {
+ return str === "form_post";
+}
+
+
+function warn(str) {
+ if (console && console.warn) {
+ console.warn(str);
+ }
+}
+
+/**
+ * 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":224,"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 '