From d249bcf61528a4645ac84dfcb48b053b4f87d78c Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Fri, 11 May 2018 08:06:08 -0400 Subject: [PATCH] Update 2018-05-11T12:06:04.050Z --- index.html | 14 +- js/app.min.js | 1215 +++-- js/fallback-styles.min.js | 2 +- js/lib.min.js | 9732 ++++++++++++++++++------------------- u2f-connector.html | 2 +- 5 files changed, 5465 insertions(+), 5500 deletions(-) diff --git a/index.html b/index.html index 51cb1900..c85289fc 100644 --- a/index.html +++ b/index.html @@ -15,9 +15,9 @@ - + - + @@ -35,11 +35,11 @@ integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"> - - + + - - - + + + diff --git a/js/app.min.js b/js/app.min.js index 214bcb58..1dbed523 100644 --- a/js/app.min.js +++ b/js/app.min.js @@ -43,19 +43,19 @@ angular .module('bit.organization', ['ui.bootstrap']); angular - .module('bit.reports', ['toastr', 'ngSanitize']); + .module('bit.services', ['ngResource', 'ngStorage', 'angular-jwt']); angular - .module('bit.services', ['ngResource', 'ngStorage', 'angular-jwt']); + .module('bit.reports', ['toastr', 'ngSanitize']); angular .module('bit.settings', ['ui.bootstrap', 'toastr']); angular - .module('bit.tools', ['ui.bootstrap', 'toastr']); + .module('bit.vault', ['ui.bootstrap', 'ngclipboard']); angular - .module('bit.vault', ['ui.bootstrap', 'ngclipboard']); + .module('bit.tools', ['ui.bootstrap', 'toastr']); angular .module('bit') @@ -5492,45 +5492,6 @@ angular } }]); -angular - .module('bit.tools') - - .controller('reportsBreachController', ["$scope", "apiService", "toastr", "authService", function ($scope, apiService, toastr, authService) { - $scope.loading = true; - $scope.error = false; - $scope.breachAccounts = []; - $scope.email = null; - - $scope.$on('$viewContentLoaded', function () { - authService.getUserProfile().then(function (userProfile) { - $scope.email = userProfile.email; - return apiService.hibp.get({ email: $scope.email }).$promise; - }).then(function (response) { - var breachAccounts = []; - for (var i = 0; i < response.length; i++) { - var breach = { - id: response[i].Name, - title: response[i].Title, - domain: response[i].Domain, - date: new Date(response[i].BreachDate), - reportedDate: new Date(response[i].AddedDate), - modifiedDate: new Date(response[i].ModifiedDate), - count: response[i].PwnCount, - description: response[i].Description, - classes: response[i].DataClasses, - image: 'https://haveibeenpwned.com/Content/Images/PwnedLogos/' + response[i].Name + '.' + response[i].LogoType - }; - breachAccounts.push(breach); - } - $scope.breachAccounts = breachAccounts; - $scope.loading = false; - }, function (response) { - $scope.error = response.status !== 404; - $scope.loading = false; - }); - }); - }]); - angular .module('bit.services') @@ -5552,7 +5513,6 @@ angular getAdmin: { url: _apiUri + '/ciphers/:id/admin', method: 'GET', params: { id: '@id' } }, getDetails: { url: _apiUri + '/ciphers/:id/details', method: 'GET', params: { id: '@id' } }, list: { method: 'GET', params: {} }, - listDetails: { url: _apiUri + '/ciphers/details', method: 'GET', params: {} }, listOrganizationDetails: { url: _apiUri + '/ciphers/organization-details', method: 'GET', params: {} }, post: { method: 'POST', params: {} }, postAdmin: { url: _apiUri + '/ciphers/admin', method: 'POST', params: {} }, @@ -6823,7 +6783,7 @@ angular }; _service.makeKey = function (password, salt) { - if (!$window.cryptoShimmed && $window.navigator.userAgent.indexOf('Edge') === -1) { + if (_subtle != null && !$window.cryptoShimmed && $window.navigator.userAgent.indexOf('Edge') === -1) { return pbkdf2WC(password, salt, 5000, 256).then(function (keyBuf) { return new SymmetricCryptoKey(bufToB64(keyBuf), true); }); @@ -6893,7 +6853,7 @@ angular throw 'Invalid parameters.'; } - if (!$window.cryptoShimmed && $window.navigator.userAgent.indexOf('Edge') === -1) { + if (_subtle != null && !$window.cryptoShimmed && $window.navigator.userAgent.indexOf('Edge') === -1) { var keyBuf = key.getBuffers(); return pbkdf2WC(new Uint8Array(keyBuf.key), password, 1, 256).then(function (hashBuf) { return bufToB64(hashBuf); @@ -7852,8 +7812,8 @@ angular case 'operacsv': importChromeCsv(file, success, error); break; - case 'firefoxpasswordexportercsvxml': - importFirefoxPasswordExporterCsvXml(file, success, error); + case 'firefoxpasswordexportercsv': + importFirefoxPasswordExporterCsv(file, success, error); break; case 'upmcsv': importUpmCsv(file, success, error); @@ -7985,6 +7945,12 @@ angular 'ort', 'adresse' ]; + function loginNameFromUrl(url) { + var a = document.createElement('a'); + a.href = url; + return a.hostname.startsWith('www.') ? a.hostname.replace('www.', '') : a.hostname; + } + function isField(fieldText, refFieldValues) { if (!fieldText || fieldText === '') { return false; @@ -9296,65 +9262,34 @@ angular }); } - function importFirefoxPasswordExporterCsvXml(file, success, error) { - var folders = [], - ciphers = []; + function importFirefoxPasswordExporterCsv(file, success, error) { + Papa.parse(file, { + header: true, + encoding: 'UTF-8', + complete: function (results) { + parseCsvErrors(results); - function getNameFromHost(host) { - var name = '--'; - try { - if (host && host !== '') { - var parser = document.createElement('a'); - parser.href = host; - if (parser.hostname) { - name = parser.hostname; - } - } - } - catch (e) { - // do nothing - } + var folders = [], + ciphers = []; - return name; - } - - function parseXml(xmlDoc) { - var xml = $(xmlDoc); - - var entries = xml.find('entry'); - for (var i = 0; i < entries.length; i++) { - var entry = $(entries[i]); - if (!entry) { - continue; - } - - var host = entry.attr('host'), - user = entry.attr('user'), - password = entry.attr('password'); - - ciphers.push({ - type: constants.cipherType.login, - favorite: false, - notes: null, - name: getNameFromHost(host), - login: { - uris: makeUriArray(host), - username: user && user !== '' ? user : null, - password: password && password !== '' ? password : null, - } + angular.forEach(results.data, function (value, key) { + + ciphers.push({ + type: constants.cipherType.login, + favorite: false, + notes: null, + name: value.hostname && value.hostname !== '' ? loginNameFromUrl(value.hostname) : '--', + login: { + uris: makeUriArray(value.hostname), + username: value.username && value.username !== '' ? value.username : null, + password: value.password && value.password !== '' ? value.password : null + } + }); }); + + success(folders, ciphers, []); } - - success(folders, ciphers, []); - } - - if (file.type && file.type === 'text/xml') { - getXmlFileContents(file, parseXml, error); - } - else { - error('Only .xml exports are supported.'); - return; - } + }); } function importUpmCsv(file, success, error) { @@ -10413,12 +10348,6 @@ angular } function importSaferPassCsv(file, success, error) { - function urlDomain(data) { - var a = document.createElement('a'); - a.href = data; - return a.hostname.startsWith('www.') ? a.hostname.replace('www.', '') : a.hostname; - } - var folders = [], ciphers = []; @@ -10433,7 +10362,7 @@ angular type: constants.cipherType.login, favorite: false, notes: value.notes && value.notes !== '' ? value.notes : null, - name: value.url && value.url !== '' ? urlDomain(value.url) : '--', + name: value.url && value.url !== '' ? loginNameFromUrl(value.url) : '--', login: { uris: makeUriArray(value.url), username: value.username && value.username !== '' ? value.username : null, @@ -11405,6 +11334,45 @@ angular return _service; }); +angular + .module('bit.tools') + + .controller('reportsBreachController', ["$scope", "apiService", "toastr", "authService", function ($scope, apiService, toastr, authService) { + $scope.loading = true; + $scope.error = false; + $scope.breachAccounts = []; + $scope.email = null; + + $scope.$on('$viewContentLoaded', function () { + authService.getUserProfile().then(function (userProfile) { + $scope.email = userProfile.email; + return apiService.hibp.get({ email: $scope.email }).$promise; + }).then(function (response) { + var breachAccounts = []; + for (var i = 0; i < response.length; i++) { + var breach = { + id: response[i].Name, + title: response[i].Title, + domain: response[i].Domain, + date: new Date(response[i].BreachDate), + reportedDate: new Date(response[i].AddedDate), + modifiedDate: new Date(response[i].ModifiedDate), + count: response[i].PwnCount, + description: response[i].Description, + classes: response[i].DataClasses, + image: 'https://haveibeenpwned.com/Content/Images/PwnedLogos/' + response[i].Name + '.' + response[i].LogoType + }; + breachAccounts.push(breach); + } + $scope.breachAccounts = breachAccounts; + $scope.loading = false; + }, function (response) { + $scope.error = response.status !== 404; + $scope.loading = false; + }); + }); + }]); + angular .module('bit.vault') @@ -13365,521 +13333,6 @@ angular }; }]); -angular - .module('bit.tools') - - .controller('toolsController', ["$scope", "$uibModal", "apiService", "toastr", "authService", function ($scope, $uibModal, apiService, toastr, authService) { - $scope.import = function () { - $uibModal.open({ - animation: true, - templateUrl: 'app/tools/views/toolsImport.html', - controller: 'toolsImportController' - }); - }; - - $scope.export = function () { - $uibModal.open({ - animation: true, - templateUrl: 'app/tools/views/toolsExport.html', - controller: 'toolsExportController' - }); - }; - }]); - -angular - .module('bit.tools') - - .controller('toolsExportController', ["$scope", "apiService", "$uibModalInstance", "cipherService", "$q", "toastr", "$analytics", "constants", function ($scope, apiService, $uibModalInstance, cipherService, $q, - toastr, $analytics, constants) { - $analytics.eventTrack('toolsExportController', { category: 'Modal' }); - $scope.export = function (model) { - $scope.startedExport = true; - var decCiphers = [], - decFolders = []; - - var folderPromise = apiService.folders.list({}, function (folders) { - decFolders = cipherService.decryptFolders(folders.Data); - }).$promise; - - var ciphersPromise = apiService.ciphers.list({}, function (ciphers) { - decCiphers = cipherService.decryptCiphers(ciphers.Data); - }).$promise; - - $q.all([folderPromise, ciphersPromise]).then(function () { - if (!decCiphers.length) { - toastr.error('Nothing to export.', 'Error!'); - $scope.close(); - return; - } - - var foldersDict = {}; - for (var i = 0; i < decFolders.length; i++) { - foldersDict[decFolders[i].id] = decFolders[i]; - } - - try { - var exportCiphers = []; - for (i = 0; i < decCiphers.length; i++) { - // only export logins and secure notes - if (decCiphers[i].type !== constants.cipherType.login && - decCiphers[i].type !== constants.cipherType.secureNote) { - continue; - } - - var cipher = { - folder: decCiphers[i].folderId && (decCiphers[i].folderId in foldersDict) ? - foldersDict[decCiphers[i].folderId].name : null, - favorite: decCiphers[i].favorite ? 1 : null, - type: null, - name: decCiphers[i].name, - notes: decCiphers[i].notes, - fields: null, - // Login props - login_uri: null, - login_username: null, - login_password: null, - login_totp: null - }; - - var j; - if (decCiphers[i].fields) { - for (j = 0; j < decCiphers[i].fields.length; j++) { - if (!cipher.fields) { - cipher.fields = ''; - } - else { - cipher.fields += '\n'; - } - - cipher.fields += ((decCiphers[i].fields[j].name || '') + ': ' + decCiphers[i].fields[j].value); - } - } - - switch (decCiphers[i].type) { - case constants.cipherType.login: - cipher.type = 'login'; - cipher.login_username = decCiphers[i].login.username; - cipher.login_password = decCiphers[i].login.password; - cipher.login_totp = decCiphers[i].login.totp; - - if (decCiphers[i].login.uris && decCiphers[i].login.uris.length) { - cipher.login_uri = []; - for (j = 0; j < decCiphers[i].login.uris.length; j++) { - cipher.login_uri.push(decCiphers[i].login.uris[j].uri); - } - } - break; - case constants.cipherType.secureNote: - cipher.type = 'note'; - break; - default: - continue; - } - - exportCiphers.push(cipher); - } - - var csvString = Papa.unparse(exportCiphers); - var csvBlob = new Blob([csvString]); - - // IE hack. ref http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx - if (window.navigator.msSaveOrOpenBlob) { - window.navigator.msSaveBlob(csvBlob, makeFileName()); - } - else { - var a = window.document.createElement('a'); - a.href = window.URL.createObjectURL(csvBlob, { type: 'text/plain' }); - a.download = makeFileName(); - document.body.appendChild(a); - // IE: "Access is denied". - // ref: https://connect.microsoft.com/IE/feedback/details/797361/ie-10-treats-blob-url-as-cross-origin-and-denies-access - a.click(); - document.body.removeChild(a); - } - - $analytics.eventTrack('Exported Data'); - toastr.success('Your data has been exported. Check your browser\'s downloads folder.', 'Success!'); - $scope.close(); - } - catch (err) { - toastr.error('Something went wrong. Please try again.', 'Error!'); - $scope.close(); - } - }, function () { - toastr.error('Something went wrong. Please try again.', 'Error!'); - $scope.close(); - }); - }; - - $scope.close = function () { - $uibModalInstance.dismiss('cancel'); - }; - - function makeFileName() { - var now = new Date(); - var dateString = - now.getFullYear() + '' + padNumber(now.getMonth() + 1, 2) + '' + padNumber(now.getDate(), 2) + - padNumber(now.getHours(), 2) + '' + padNumber(now.getMinutes(), 2) + - padNumber(now.getSeconds(), 2); - - return 'bitwarden_export_' + dateString + '.csv'; - } - - function padNumber(number, width, paddingCharacter) { - paddingCharacter = paddingCharacter || '0'; - number = number + ''; - return number.length >= width ? number : new Array(width - number.length + 1).join(paddingCharacter) + number; - } - }]); - -angular - .module('bit.tools') - - .controller('toolsImportController', ["$scope", "$state", "apiService", "$uibModalInstance", "cryptoService", "cipherService", "toastr", "importService", "$analytics", "$sce", "validationService", function ($scope, $state, apiService, $uibModalInstance, cryptoService, cipherService, - toastr, importService, $analytics, $sce, validationService) { - $analytics.eventTrack('toolsImportController', { category: 'Modal' }); - $scope.model = { source: '' }; - $scope.source = {}; - $scope.splitFeatured = true; - - $scope.options = [ - { - id: 'bitwardencsv', - name: 'Bitwarden (csv)', - featured: true, - sort: 1, - instructions: $sce.trustAsHtml('Export using the web vault (vault.bitwarden.com). ' + - 'Log into the web vault and navigate to "Tools" > "Export".') - }, - { - id: 'lastpass', - name: 'LastPass (csv)', - featured: true, - sort: 2, - instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + - '' + - 'https://help.bitwarden.com/article/import-from-lastpass/') - }, - { - id: 'chromecsv', - name: 'Chrome (csv)', - featured: true, - sort: 3, - instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + - '' + - 'https://help.bitwarden.com/article/import-from-chrome/') - }, - { - id: 'firefoxpasswordexportercsvxml', - name: 'Firefox Password Exporter (xml)', - featured: true, - sort: 4, - instructions: $sce.trustAsHtml('Use the ' + - '' + - 'Password Exporter addon for FireFox to export your passwords to a XML file. After installing ' + - 'the addon, type about:addons in your FireFox navigation bar. Locate the Password Exporter ' + - 'addon and click the "Options" button. In the dialog that pops up, click the "Export Passwords" button ' + - 'to save the XML file.') - }, - { - id: 'keepass2xml', - name: 'KeePass 2 (xml)', - featured: true, - sort: 5, - instructions: $sce.trustAsHtml('Using the KeePass 2 desktop application, navigate to "File" > "Export" and ' + - 'select the KeePass XML (2.x) option.') - }, - { - id: 'keepassxcsv', - name: 'KeePassX (csv)', - instructions: $sce.trustAsHtml('Using the KeePassX desktop application, navigate to "Database" > ' + - '"Export to CSV file" and save the CSV file.') - }, - { - id: 'dashlanecsv', - name: 'Dashlane (csv)', - featured: true, - sort: 7, - instructions: $sce.trustAsHtml('Using the Dashlane desktop application, navigate to "File" > "Export" > ' + - '"Unsecured archive (readable) in CSV format" and save the CSV file.') - }, - { - id: '1password1pif', - name: '1Password (1pif)', - featured: true, - sort: 6, - instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + - '' + - 'https://help.bitwarden.com/article/import-from-1password/') - }, - { - id: '1password6wincsv', - name: '1Password 6 Windows (csv)', - instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + - '' + - 'https://help.bitwarden.com/article/import-from-1password/') - }, - { - id: 'roboformhtml', - name: 'RoboForm (html)', - instructions: $sce.trustAsHtml('Using the RoboForm Editor desktop application, navigate to "RoboForm" ' + - '(top left) > "Print List" > "Logins". When the following print dialog pops up click on the "Save" button ' + - 'and save the HTML file.') - }, - { - id: 'keepercsv', - name: 'Keeper (csv)', - instructions: $sce.trustAsHtml('Log into the Keeper web vault (keepersecurity.com/vault). Navigate to "Backup" ' + - '(top right) and find the "Export to Text File" option. Click "Export Now" to save the TXT/CSV file.') - }, - { - id: 'enpasscsv', - name: 'Enpass (csv)', - instructions: $sce.trustAsHtml('Using the Enpass desktop application, navigate to "File" > "Export" > ' + - '"As CSV". Select "Yes" to the warning alert and save the CSV file. Note that the importer only fully ' + - 'supports files exported while Enpass is set to the English language, so adjust your settings accordingly.') - }, - { - id: 'safeincloudxml', - name: 'SafeInCloud (xml)', - instructions: $sce.trustAsHtml('Using the SaveInCloud desktop application, navigate to "File" > "Export" > ' + - '"As XML" and save the XML file.') - }, - { - id: 'pwsafexml', - name: 'Password Safe (xml)', - instructions: $sce.trustAsHtml('Using the Password Safe desktop application, navigate to "File" > ' + - '"Export To" > "XML format..." and save the XML file.') - }, - { - id: 'stickypasswordxml', - name: 'Sticky Password (xml)', - instructions: $sce.trustAsHtml('Using the Sticky Password desktop application, navigate to "Menu" ' + - '(top right) > "Export" > "Export all". Select the unencrypted format XML option and then the ' + - '"Save to file" button. Save the XML file.') - }, - { - id: 'msecurecsv', - name: 'mSecure (csv)', - instructions: $sce.trustAsHtml('Using the mSecure desktop application, navigate to "File" > ' + - '"Export" > "CSV File..." and save the CSV file.') - }, - { - id: 'truekeycsv', - name: 'True Key (csv)', - instructions: $sce.trustAsHtml('Using the True Key desktop application, click the gear icon (top right) and ' + - 'then navigate to "App Settings". Click the "Export" button, enter your password and save the CSV file.') - }, - { - id: 'passwordbossjson', - name: 'Password Boss (json)', - instructions: $sce.trustAsHtml('Using the Password Boss desktop application, navigate to "File" > ' + - '"Export data" > "Password Boss JSON - not encrypted" and save the JSON file.') - }, - { - id: 'zohovaultcsv', - name: 'Zoho Vault (csv)', - instructions: $sce.trustAsHtml('Log into the Zoho web vault (vault.zoho.com). Navigate to "Tools" > ' + - '"Export Secrets". Select "All Secrets" and click the "Zoho Vault Format CSV" button. Highlight ' + - 'and copy the data from the textarea. Open a text editor like Notepad and paste the data. Save the ' + - 'data from the text editor as zoho_export.csv.') - }, - { - id: 'splashidcsv', - name: 'SplashID (csv)', - instructions: $sce.trustAsHtml('Using the SplashID Safe desktop application, click on the SplashID ' + - 'blue lock logo in the top right corner. Navigate to "Export" > "Export as CSV" and save the CSV file.') - }, - { - id: 'passworddragonxml', - name: 'Password Dragon (xml)', - instructions: $sce.trustAsHtml('Using the Password Dragon desktop application, navigate to "File" > ' + - '"Export" > "To XML". In the dialog that pops up select "All Rows" and check all fields. Click ' + - 'the "Export" button and save the XML file.') - }, - { - id: 'padlockcsv', - name: 'Padlock (csv)', - instructions: $sce.trustAsHtml('Using the Padlock desktop application, click the hamburger icon ' + - 'in the top left corner and navigate to "Settings". Click the "Export Data" option. Ensure that ' + - 'the "CSV" option is selected from the dropdown. Highlight and copy the data from the textarea. ' + - 'Open a text editor like Notepad and paste the data. Save the data from the text editor as ' + - 'padlock_export.csv.') - }, - { - id: 'clipperzhtml', - name: 'Clipperz (html)', - instructions: $sce.trustAsHtml('Log into the Clipperz web application (clipperz.is/app). Click the ' + - 'hamburger menu icon in the top right to expand the navigation bar. Navigate to "Data" > ' + - '"Export". Click the "download HTML+JSON" button to save the HTML file.') - }, - { - id: 'avirajson', - name: 'Avira (json)', - instructions: $sce.trustAsHtml('Using the Avira browser extension, click your username in the top ' + - 'right corner and navigate to "Settings". Locate the "Export Data" section and click "Export". ' + - 'In the dialog that pops up, click the "Export Password Manager Data" button to save the ' + - 'TXT/JSON file.') - }, - { - id: 'saferpasscsv', - name: 'SaferPass (csv)', - instructions: $sce.trustAsHtml('Using the SaferPass browser extension, click the hamburger icon ' + - 'in the top left corner and navigate to "Settings". Click the "Export accounts" button to ' + - 'save the CSV file.') - }, - { - id: 'upmcsv', - name: 'Universal Password Manager (csv)', - instructions: $sce.trustAsHtml('Using the Universal Password Manager desktop application, navigate ' + - 'to "Database" > "Export" and save the CSV file.') - }, - { - id: 'ascendocsv', - name: 'Ascendo DataVault (csv)', - instructions: $sce.trustAsHtml('Using the Ascendo DataVault desktop application, navigate ' + - 'to "Tools" > "Export". In the dialog that pops up, select the "All Items (DVX, CSV)" ' + - 'option. Click the "Ok" button to save the CSV file.') - }, - { - id: 'meldiumcsv', - name: 'Meldium (csv)', - instructions: $sce.trustAsHtml('Using the Meldium web vault, navigate to "Settings". ' + - 'Locate the "Export data" function and click "Show me my data" to save the CSV file.') - }, - { - id: 'passkeepcsv', - name: 'PassKeep (csv)', - instructions: $sce.trustAsHtml('Using the PassKeep mobile app, navigate to "Backup/Restore". ' + - 'Locate the "CSV Backup/Restore" section and click "Backup to CSV" to save the CSV file.') - }, - { - id: 'operacsv', - name: 'Opera (csv)', - instructions: $sce.trustAsHtml('The process for importing from Opera is exactly the same as ' + - 'importing from Google Chrome. See detailed instructions on our help site at ' + - '' + - 'https://help.bitwarden.com/article/import-from-chrome/') - }, - { - id: 'vivaldicsv', - name: 'Vivaldi (csv)', - instructions: $sce.trustAsHtml('The process for importing from Vivaldi is exactly the same as ' + - 'importing from Google Chrome. See detailed instructions on our help site at ' + - '' + - 'https://help.bitwarden.com/article/import-from-chrome/') - }, - { - id: 'gnomejson', - name: 'GNOME Passwords and Keys/Seahorse (json)', - instructions: $sce.trustAsHtml('Make sure you have python-keyring and python-gnomekeyring installed. ' + - 'Save the GNOME Keyring Import/Export ' + - 'python script by Luke Plant to your desktop as pw_helper.py. Open terminal and run ' + - 'chmod +rx Desktop/pw_helper.py and then ' + - 'python Desktop/pw_helper.py export Desktop/my_passwords.json. Then upload ' + - 'the resulting my_passwords.json file here to Bitwarden.') - } - ]; - - $scope.setSource = function () { - for (var i = 0; i < $scope.options.length; i++) { - if ($scope.options[i].id === $scope.model.source) { - $scope.source = $scope.options[i]; - break; - } - } - }; - $scope.setSource(); - - $scope.import = function (model, form) { - if (!model.source || model.source === '') { - validationService.addError(form, 'source', 'Select the format of the import file.', true); - return; - } - - var file = document.getElementById('file').files[0]; - if (!file && (!model.fileContents || model.fileContents === '')) { - validationService.addError(form, 'file', 'Select the import file or copy/paste the import file contents.', true); - return; - } - - $scope.processing = true; - importService.import(model.source, file || model.fileContents, importSuccess, importError); - }; - - function importSuccess(folders, ciphers, folderRelationships) { - if (!folders.length && !ciphers.length) { - importError('Nothing was imported.'); - return; - } - else if (ciphers.length) { - var halfway = Math.floor(ciphers.length / 2); - var last = ciphers.length - 1; - if (cipherIsBadData(ciphers[0]) && cipherIsBadData(ciphers[halfway]) && cipherIsBadData(ciphers[last])) { - importError('Data is not formatted correctly. Please check your import file and try again.'); - return; - } - } - - apiService.ciphers.import({ - folders: cipherService.encryptFolders(folders), - ciphers: cipherService.encryptCiphers(ciphers), - folderRelationships: folderRelationships - }, function () { - $uibModalInstance.dismiss('cancel'); - $state.go('backend.user.vault', { refreshFromServer: true }).then(function () { - $analytics.eventTrack('Imported Data', { label: $scope.model.source }); - toastr.success('Data has been successfully imported into your vault.', 'Import Success'); - }); - }, importError); - } - - function cipherIsBadData(cipher) { - return (cipher.name === null || cipher.name === '--') && - (cipher.login && (cipher.login.password === null || cipher.login.password === '')); - } - - function importError(error) { - $analytics.eventTrack('Import Data Failed', { label: $scope.model.source }); - $uibModalInstance.dismiss('cancel'); - - if (error) { - var data = error.data; - if (data && data.ValidationErrors) { - var message = ''; - for (var key in data.ValidationErrors) { - if (!data.ValidationErrors.hasOwnProperty(key)) { - continue; - } - - for (var i = 0; i < data.ValidationErrors[key].length; i++) { - message += (key + ': ' + data.ValidationErrors[key][i] + ' '); - } - } - - if (message !== '') { - toastr.error(message); - return; - } - } - else if (data && data.Message) { - toastr.error(data.Message); - return; - } - else { - toastr.error(error); - return; - } - } - - toastr.error('Something went wrong. Try again.', 'Oh No!'); - } - - $scope.close = function () { - $uibModalInstance.dismiss('cancel'); - }; - }]); - angular .module('bit.vault') @@ -15260,3 +14713,515 @@ angular }); }; }]); + +angular + .module('bit.tools') + + .controller('toolsController', ["$scope", "$uibModal", "apiService", "toastr", "authService", function ($scope, $uibModal, apiService, toastr, authService) { + $scope.import = function () { + $uibModal.open({ + animation: true, + templateUrl: 'app/tools/views/toolsImport.html', + controller: 'toolsImportController' + }); + }; + + $scope.export = function () { + $uibModal.open({ + animation: true, + templateUrl: 'app/tools/views/toolsExport.html', + controller: 'toolsExportController' + }); + }; + }]); + +angular + .module('bit.tools') + + .controller('toolsExportController', ["$scope", "apiService", "$uibModalInstance", "cipherService", "$q", "toastr", "$analytics", "constants", function ($scope, apiService, $uibModalInstance, cipherService, $q, + toastr, $analytics, constants) { + $analytics.eventTrack('toolsExportController', { category: 'Modal' }); + $scope.export = function (model) { + $scope.startedExport = true; + var decCiphers = [], + decFolders = []; + + var folderPromise = apiService.folders.list({}, function (folders) { + decFolders = cipherService.decryptFolders(folders.Data); + }).$promise; + + var ciphersPromise = apiService.ciphers.list({}, function (ciphers) { + decCiphers = cipherService.decryptCiphers(ciphers.Data); + }).$promise; + + $q.all([folderPromise, ciphersPromise]).then(function () { + if (!decCiphers.length) { + toastr.error('Nothing to export.', 'Error!'); + $scope.close(); + return; + } + + var foldersDict = {}; + for (var i = 0; i < decFolders.length; i++) { + foldersDict[decFolders[i].id] = decFolders[i]; + } + + try { + var exportCiphers = []; + for (i = 0; i < decCiphers.length; i++) { + // only export logins and secure notes + if (decCiphers[i].type !== constants.cipherType.login && + decCiphers[i].type !== constants.cipherType.secureNote) { + continue; + } + + var cipher = { + folder: decCiphers[i].folderId && (decCiphers[i].folderId in foldersDict) ? + foldersDict[decCiphers[i].folderId].name : null, + favorite: decCiphers[i].favorite ? 1 : null, + type: null, + name: decCiphers[i].name, + notes: decCiphers[i].notes, + fields: null, + // Login props + login_uri: null, + login_username: null, + login_password: null, + login_totp: null + }; + + var j; + if (decCiphers[i].fields) { + for (j = 0; j < decCiphers[i].fields.length; j++) { + if (!cipher.fields) { + cipher.fields = ''; + } + else { + cipher.fields += '\n'; + } + + cipher.fields += ((decCiphers[i].fields[j].name || '') + ': ' + decCiphers[i].fields[j].value); + } + } + + switch (decCiphers[i].type) { + case constants.cipherType.login: + cipher.type = 'login'; + cipher.login_username = decCiphers[i].login.username; + cipher.login_password = decCiphers[i].login.password; + cipher.login_totp = decCiphers[i].login.totp; + + if (decCiphers[i].login.uris && decCiphers[i].login.uris.length) { + cipher.login_uri = []; + for (j = 0; j < decCiphers[i].login.uris.length; j++) { + cipher.login_uri.push(decCiphers[i].login.uris[j].uri); + } + } + break; + case constants.cipherType.secureNote: + cipher.type = 'note'; + break; + default: + continue; + } + + exportCiphers.push(cipher); + } + + var csvString = Papa.unparse(exportCiphers); + var csvBlob = new Blob([csvString]); + + // IE hack. ref http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx + if (window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveBlob(csvBlob, makeFileName()); + } + else { + var a = window.document.createElement('a'); + a.href = window.URL.createObjectURL(csvBlob, { type: 'text/plain' }); + a.download = makeFileName(); + document.body.appendChild(a); + // IE: "Access is denied". + // ref: https://connect.microsoft.com/IE/feedback/details/797361/ie-10-treats-blob-url-as-cross-origin-and-denies-access + a.click(); + document.body.removeChild(a); + } + + $analytics.eventTrack('Exported Data'); + toastr.success('Your data has been exported. Check your browser\'s downloads folder.', 'Success!'); + $scope.close(); + } + catch (err) { + toastr.error('Something went wrong. Please try again.', 'Error!'); + $scope.close(); + } + }, function () { + toastr.error('Something went wrong. Please try again.', 'Error!'); + $scope.close(); + }); + }; + + $scope.close = function () { + $uibModalInstance.dismiss('cancel'); + }; + + function makeFileName() { + var now = new Date(); + var dateString = + now.getFullYear() + '' + padNumber(now.getMonth() + 1, 2) + '' + padNumber(now.getDate(), 2) + + padNumber(now.getHours(), 2) + '' + padNumber(now.getMinutes(), 2) + + padNumber(now.getSeconds(), 2); + + return 'bitwarden_export_' + dateString + '.csv'; + } + + function padNumber(number, width, paddingCharacter) { + paddingCharacter = paddingCharacter || '0'; + number = number + ''; + return number.length >= width ? number : new Array(width - number.length + 1).join(paddingCharacter) + number; + } + }]); + +angular + .module('bit.tools') + + .controller('toolsImportController', ["$scope", "$state", "apiService", "$uibModalInstance", "cryptoService", "cipherService", "toastr", "importService", "$analytics", "$sce", "validationService", function ($scope, $state, apiService, $uibModalInstance, cryptoService, cipherService, + toastr, importService, $analytics, $sce, validationService) { + $analytics.eventTrack('toolsImportController', { category: 'Modal' }); + $scope.model = { source: '' }; + $scope.source = {}; + $scope.splitFeatured = true; + + $scope.options = [ + { + id: 'bitwardencsv', + name: 'Bitwarden (csv)', + featured: true, + sort: 1, + instructions: $sce.trustAsHtml('Export using the web vault (vault.bitwarden.com). ' + + 'Log into the web vault and navigate to "Tools" > "Export".') + }, + { + id: 'lastpass', + name: 'LastPass (csv)', + featured: true, + sort: 2, + instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + + '' + + 'https://help.bitwarden.com/article/import-from-lastpass/') + }, + { + id: 'chromecsv', + name: 'Chrome (csv)', + featured: true, + sort: 3, + instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + + '' + + 'https://help.bitwarden.com/article/import-from-chrome/') + }, + { + id: 'firefoxpasswordexportercsv', + name: 'Firefox Password Exporter (csv)', + featured: true, + sort: 4, + instructions: $sce.trustAsHtml('Use the ' + + '' + + 'FF Password Exporter application to export your passwords to a CSV file.') + }, + { + id: 'keepass2xml', + name: 'KeePass 2 (xml)', + featured: true, + sort: 5, + instructions: $sce.trustAsHtml('Using the KeePass 2 desktop application, navigate to "File" > "Export" and ' + + 'select the KeePass XML (2.x) option.') + }, + { + id: 'keepassxcsv', + name: 'KeePassX (csv)', + instructions: $sce.trustAsHtml('Using the KeePassX desktop application, navigate to "Database" > ' + + '"Export to CSV file" and save the CSV file.') + }, + { + id: 'dashlanecsv', + name: 'Dashlane (csv)', + featured: true, + sort: 7, + instructions: $sce.trustAsHtml('Using the Dashlane desktop application, navigate to "File" > "Export" > ' + + '"Unsecured archive (readable) in CSV format" and save the CSV file.') + }, + { + id: '1password1pif', + name: '1Password (1pif)', + featured: true, + sort: 6, + instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + + '' + + 'https://help.bitwarden.com/article/import-from-1password/') + }, + { + id: '1password6wincsv', + name: '1Password 6 Windows (csv)', + instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + + '' + + 'https://help.bitwarden.com/article/import-from-1password/') + }, + { + id: 'roboformhtml', + name: 'RoboForm (html)', + instructions: $sce.trustAsHtml('Using the RoboForm Editor desktop application, navigate to "RoboForm" ' + + '(top left) > "Print List" > "Logins". When the following print dialog pops up click on the "Save" button ' + + 'and save the HTML file.') + }, + { + id: 'keepercsv', + name: 'Keeper (csv)', + instructions: $sce.trustAsHtml('Log into the Keeper web vault (keepersecurity.com/vault). Navigate to "Backup" ' + + '(top right) and find the "Export to Text File" option. Click "Export Now" to save the TXT/CSV file.') + }, + { + id: 'enpasscsv', + name: 'Enpass (csv)', + instructions: $sce.trustAsHtml('Using the Enpass desktop application, navigate to "File" > "Export" > ' + + '"As CSV". Select "Yes" to the warning alert and save the CSV file. Note that the importer only fully ' + + 'supports files exported while Enpass is set to the English language, so adjust your settings accordingly.') + }, + { + id: 'safeincloudxml', + name: 'SafeInCloud (xml)', + instructions: $sce.trustAsHtml('Using the SaveInCloud desktop application, navigate to "File" > "Export" > ' + + '"As XML" and save the XML file.') + }, + { + id: 'pwsafexml', + name: 'Password Safe (xml)', + instructions: $sce.trustAsHtml('Using the Password Safe desktop application, navigate to "File" > ' + + '"Export To" > "XML format..." and save the XML file.') + }, + { + id: 'stickypasswordxml', + name: 'Sticky Password (xml)', + instructions: $sce.trustAsHtml('Using the Sticky Password desktop application, navigate to "Menu" ' + + '(top right) > "Export" > "Export all". Select the unencrypted format XML option and then the ' + + '"Save to file" button. Save the XML file.') + }, + { + id: 'msecurecsv', + name: 'mSecure (csv)', + instructions: $sce.trustAsHtml('Using the mSecure desktop application, navigate to "File" > ' + + '"Export" > "CSV File..." and save the CSV file.') + }, + { + id: 'truekeycsv', + name: 'True Key (csv)', + instructions: $sce.trustAsHtml('Using the True Key desktop application, click the gear icon (top right) and ' + + 'then navigate to "App Settings". Click the "Export" button, enter your password and save the CSV file.') + }, + { + id: 'passwordbossjson', + name: 'Password Boss (json)', + instructions: $sce.trustAsHtml('Using the Password Boss desktop application, navigate to "File" > ' + + '"Export data" > "Password Boss JSON - not encrypted" and save the JSON file.') + }, + { + id: 'zohovaultcsv', + name: 'Zoho Vault (csv)', + instructions: $sce.trustAsHtml('Log into the Zoho web vault (vault.zoho.com). Navigate to "Tools" > ' + + '"Export Secrets". Select "All Secrets" and click the "Zoho Vault Format CSV" button. Highlight ' + + 'and copy the data from the textarea. Open a text editor like Notepad and paste the data. Save the ' + + 'data from the text editor as zoho_export.csv.') + }, + { + id: 'splashidcsv', + name: 'SplashID (csv)', + instructions: $sce.trustAsHtml('Using the SplashID Safe desktop application, click on the SplashID ' + + 'blue lock logo in the top right corner. Navigate to "Export" > "Export as CSV" and save the CSV file.') + }, + { + id: 'passworddragonxml', + name: 'Password Dragon (xml)', + instructions: $sce.trustAsHtml('Using the Password Dragon desktop application, navigate to "File" > ' + + '"Export" > "To XML". In the dialog that pops up select "All Rows" and check all fields. Click ' + + 'the "Export" button and save the XML file.') + }, + { + id: 'padlockcsv', + name: 'Padlock (csv)', + instructions: $sce.trustAsHtml('Using the Padlock desktop application, click the hamburger icon ' + + 'in the top left corner and navigate to "Settings". Click the "Export Data" option. Ensure that ' + + 'the "CSV" option is selected from the dropdown. Highlight and copy the data from the textarea. ' + + 'Open a text editor like Notepad and paste the data. Save the data from the text editor as ' + + 'padlock_export.csv.') + }, + { + id: 'clipperzhtml', + name: 'Clipperz (html)', + instructions: $sce.trustAsHtml('Log into the Clipperz web application (clipperz.is/app). Click the ' + + 'hamburger menu icon in the top right to expand the navigation bar. Navigate to "Data" > ' + + '"Export". Click the "download HTML+JSON" button to save the HTML file.') + }, + { + id: 'avirajson', + name: 'Avira (json)', + instructions: $sce.trustAsHtml('Using the Avira browser extension, click your username in the top ' + + 'right corner and navigate to "Settings". Locate the "Export Data" section and click "Export". ' + + 'In the dialog that pops up, click the "Export Password Manager Data" button to save the ' + + 'TXT/JSON file.') + }, + { + id: 'saferpasscsv', + name: 'SaferPass (csv)', + instructions: $sce.trustAsHtml('Using the SaferPass browser extension, click the hamburger icon ' + + 'in the top left corner and navigate to "Settings". Click the "Export accounts" button to ' + + 'save the CSV file.') + }, + { + id: 'upmcsv', + name: 'Universal Password Manager (csv)', + instructions: $sce.trustAsHtml('Using the Universal Password Manager desktop application, navigate ' + + 'to "Database" > "Export" and save the CSV file.') + }, + { + id: 'ascendocsv', + name: 'Ascendo DataVault (csv)', + instructions: $sce.trustAsHtml('Using the Ascendo DataVault desktop application, navigate ' + + 'to "Tools" > "Export". In the dialog that pops up, select the "All Items (DVX, CSV)" ' + + 'option. Click the "Ok" button to save the CSV file.') + }, + { + id: 'meldiumcsv', + name: 'Meldium (csv)', + instructions: $sce.trustAsHtml('Using the Meldium web vault, navigate to "Settings". ' + + 'Locate the "Export data" function and click "Show me my data" to save the CSV file.') + }, + { + id: 'passkeepcsv', + name: 'PassKeep (csv)', + instructions: $sce.trustAsHtml('Using the PassKeep mobile app, navigate to "Backup/Restore". ' + + 'Locate the "CSV Backup/Restore" section and click "Backup to CSV" to save the CSV file.') + }, + { + id: 'operacsv', + name: 'Opera (csv)', + instructions: $sce.trustAsHtml('The process for importing from Opera is exactly the same as ' + + 'importing from Google Chrome. See detailed instructions on our help site at ' + + '' + + 'https://help.bitwarden.com/article/import-from-chrome/') + }, + { + id: 'vivaldicsv', + name: 'Vivaldi (csv)', + instructions: $sce.trustAsHtml('The process for importing from Vivaldi is exactly the same as ' + + 'importing from Google Chrome. See detailed instructions on our help site at ' + + '' + + 'https://help.bitwarden.com/article/import-from-chrome/') + }, + { + id: 'gnomejson', + name: 'GNOME Passwords and Keys/Seahorse (json)', + instructions: $sce.trustAsHtml('Make sure you have python-keyring and python-gnomekeyring installed. ' + + 'Save the GNOME Keyring Import/Export ' + + 'python script by Luke Plant to your desktop as pw_helper.py. Open terminal and run ' + + 'chmod +rx Desktop/pw_helper.py and then ' + + 'python Desktop/pw_helper.py export Desktop/my_passwords.json. Then upload ' + + 'the resulting my_passwords.json file here to Bitwarden.') + } + ]; + + $scope.setSource = function () { + for (var i = 0; i < $scope.options.length; i++) { + if ($scope.options[i].id === $scope.model.source) { + $scope.source = $scope.options[i]; + break; + } + } + }; + $scope.setSource(); + + $scope.import = function (model, form) { + if (!model.source || model.source === '') { + validationService.addError(form, 'source', 'Select the format of the import file.', true); + return; + } + + var file = document.getElementById('file').files[0]; + if (!file && (!model.fileContents || model.fileContents === '')) { + validationService.addError(form, 'file', 'Select the import file or copy/paste the import file contents.', true); + return; + } + + $scope.processing = true; + importService.import(model.source, file || model.fileContents, importSuccess, importError); + }; + + function importSuccess(folders, ciphers, folderRelationships) { + if (!folders.length && !ciphers.length) { + importError('Nothing was imported.'); + return; + } + else if (ciphers.length) { + var halfway = Math.floor(ciphers.length / 2); + var last = ciphers.length - 1; + if (cipherIsBadData(ciphers[0]) && cipherIsBadData(ciphers[halfway]) && cipherIsBadData(ciphers[last])) { + importError('Data is not formatted correctly. Please check your import file and try again.'); + return; + } + } + + apiService.ciphers.import({ + folders: cipherService.encryptFolders(folders), + ciphers: cipherService.encryptCiphers(ciphers), + folderRelationships: folderRelationships + }, function () { + $uibModalInstance.dismiss('cancel'); + $state.go('backend.user.vault', { refreshFromServer: true }).then(function () { + $analytics.eventTrack('Imported Data', { label: $scope.model.source }); + toastr.success('Data has been successfully imported into your vault.', 'Import Success'); + }); + }, importError); + } + + function cipherIsBadData(cipher) { + return (cipher.name === null || cipher.name === '--') && + (cipher.login && (cipher.login.password === null || cipher.login.password === '')); + } + + function importError(error) { + $analytics.eventTrack('Import Data Failed', { label: $scope.model.source }); + $uibModalInstance.dismiss('cancel'); + + if (error) { + var data = error.data; + if (data && data.ValidationErrors) { + var message = ''; + for (var key in data.ValidationErrors) { + if (!data.ValidationErrors.hasOwnProperty(key)) { + continue; + } + + for (var i = 0; i < data.ValidationErrors[key].length; i++) { + message += (key + ': ' + data.ValidationErrors[key][i] + ' '); + } + } + + if (message !== '') { + toastr.error(message); + return; + } + } + else if (data && data.Message) { + toastr.error(data.Message); + return; + } + else { + toastr.error(error); + return; + } + } + + toastr.error('Something went wrong. Try again.', 'Oh No!'); + } + + $scope.close = function () { + $uibModalInstance.dismiss('cancel'); + }; + }]); diff --git a/js/fallback-styles.min.js b/js/fallback-styles.min.js index 7da74cae..3a5f1136 100644 --- a/js/fallback-styles.min.js +++ b/js/fallback-styles.min.js @@ -1,4 +1,4 @@ -var cacheTag = 'u6pc0i' || ''; +var cacheTag = 'dlcwii' || ''; function loadStylesheetIfMissing(property, value, paths) { var scripts = document.getElementsByTagName('SCRIPT'), diff --git a/js/lib.min.js b/js/lib.min.js index 8cf1fd60..7806ac52 100644 --- a/js/lib.min.js +++ b/js/lib.min.js @@ -889,400 +889,6 @@ angular.module('ngCookies').provider('$$cookieWriter', /** @this */ function $$C })(window, window.angular); -(function() { - - -// Create all modules and define dependencies to make sure they exist -// and are loaded in the correct order to satisfy dependency injection -// before all nested files are concatenated by Grunt - -// Modules -angular.module('angular-jwt', - [ - 'angular-jwt.options', - 'angular-jwt.interceptor', - 'angular-jwt.jwt', - 'angular-jwt.authManager' - ]); - -angular.module('angular-jwt.authManager', []) - .provider('authManager', function () { - - this.$get = ["$rootScope", "$injector", "$location", "jwtHelper", "jwtInterceptor", "jwtOptions", function ($rootScope, $injector, $location, jwtHelper, jwtInterceptor, jwtOptions) { - - var config = jwtOptions.getConfig(); - - function invokeToken(tokenGetter) { - var token = null; - if (Array.isArray(tokenGetter)) { - token = $injector.invoke(tokenGetter, this, {options: null}); - } else { - token = tokenGetter(); - } - return token; - } - - function invokeRedirector(redirector) { - if (Array.isArray(redirector) || angular.isFunction(redirector)) { - return $injector.invoke(redirector, config, {}); - } else { - throw new Error('unauthenticatedRedirector must be a function'); - } - } - - function isAuthenticated() { - var token = invokeToken(config.tokenGetter); - if (token) { - return !jwtHelper.isTokenExpired(token); - } - } - - $rootScope.isAuthenticated = false; - - function authenticate() { - $rootScope.isAuthenticated = true; - } - - function unauthenticate() { - $rootScope.isAuthenticated = false; - } - - function checkAuthOnRefresh() { - $rootScope.$on('$locationChangeStart', function () { - var token = invokeToken(config.tokenGetter); - if (token) { - if (!jwtHelper.isTokenExpired(token)) { - authenticate(); - } else { - $rootScope.$broadcast('tokenHasExpired', token); - } - } - }); - } - - function redirectWhenUnauthenticated() { - $rootScope.$on('unauthenticated', function () { - invokeRedirector(config.unauthenticatedRedirector); - unauthenticate(); - }); - } - - function verifyRoute(event, next) { - if (!next) { - return false; - } - - var routeData = (next.$$route) ? next.$$route : next.data; - - if (routeData && routeData.requiresLogin === true) { - var token = invokeToken(config.tokenGetter); - if (!token || jwtHelper.isTokenExpired(token)) { - event.preventDefault(); - invokeRedirector(config.unauthenticatedRedirector); - } - } - } - - var eventName = ($injector.has('$state')) ? '$stateChangeStart' : '$routeChangeStart'; - $rootScope.$on(eventName, verifyRoute); - - return { - authenticate: authenticate, - unauthenticate: unauthenticate, - getToken: function(){ return invokeToken(config.tokenGetter); }, - redirect: function() { return invokeRedirector(config.unauthenticatedRedirector); }, - checkAuthOnRefresh: checkAuthOnRefresh, - redirectWhenUnauthenticated: redirectWhenUnauthenticated, - isAuthenticated: isAuthenticated - } - }] - }); - -angular.module('angular-jwt.interceptor', []) - .provider('jwtInterceptor', function() { - - this.urlParam; - this.authHeader; - this.authPrefix; - this.whiteListedDomains; - this.tokenGetter; - - var config = this; - - this.$get = ["$q", "$injector", "$rootScope", "urlUtils", "jwtOptions", function($q, $injector, $rootScope, urlUtils, jwtOptions) { - - var options = angular.extend({}, jwtOptions.getConfig(), config); - - function isSafe (url) { - if (!urlUtils.isSameOrigin(url) && !options.whiteListedDomains.length) { - throw new Error('As of v0.1.0, requests to domains other than the application\'s origin must be white listed. Use jwtOptionsProvider.config({ whiteListedDomains: [] }); to whitelist.') - } - var hostname = urlUtils.urlResolve(url).hostname.toLowerCase(); - for (var i = 0; i < options.whiteListedDomains.length; i++) { - var domain = options.whiteListedDomains[i]; - var regexp = domain instanceof RegExp ? domain : new RegExp(domain, 'i'); - if (hostname.match(regexp)) { - return true; - } - } - - if (urlUtils.isSameOrigin(url)) { - return true; - } - - return false; - } - - return { - request: function (request) { - if (request.skipAuthorization || !isSafe(request.url)) { - return request; - } - - if (options.urlParam) { - request.params = request.params || {}; - // Already has the token in the url itself - if (request.params[options.urlParam]) { - return request; - } - } else { - request.headers = request.headers || {}; - // Already has an Authorization header - if (request.headers[options.authHeader]) { - return request; - } - } - - var tokenPromise = $q.when($injector.invoke(options.tokenGetter, this, { - options: request - })); - - return tokenPromise.then(function(token) { - if (token) { - if (options.urlParam) { - request.params[options.urlParam] = token; - } else { - request.headers[options.authHeader] = options.authPrefix + token; - } - } - return request; - }); - }, - responseError: function (response) { - // handle the case where the user is not authenticated - if (response.status === 401) { - $rootScope.$broadcast('unauthenticated', response); - } - return $q.reject(response); - } - }; - }] - }); - - angular.module('angular-jwt.jwt', []) - .service('jwtHelper', ["$window", function($window) { - - this.urlBase64Decode = function(str) { - var output = str.replace(/-/g, '+').replace(/_/g, '/'); - switch (output.length % 4) { - case 0: { break; } - case 2: { output += '=='; break; } - case 3: { output += '='; break; } - default: { - throw 'Illegal base64url string!'; - } - } - return $window.decodeURIComponent(escape($window.atob(output))); //polyfill https://github.com/davidchambers/Base64.js - }; - - - this.decodeToken = function(token) { - var parts = token.split('.'); - - if (parts.length !== 3) { - throw new Error('JWT must have 3 parts'); - } - - var decoded = this.urlBase64Decode(parts[1]); - if (!decoded) { - throw new Error('Cannot decode the token'); - } - - return angular.fromJson(decoded); - }; - - this.getTokenExpirationDate = function(token) { - var decoded = this.decodeToken(token); - - if(typeof decoded.exp === "undefined") { - return null; - } - - var d = new Date(0); // The 0 here is the key, which sets the date to the epoch - d.setUTCSeconds(decoded.exp); - - return d; - }; - - this.isTokenExpired = function(token, offsetSeconds) { - var d = this.getTokenExpirationDate(token); - offsetSeconds = offsetSeconds || 0; - if (d === null) { - return false; - } - - // Token expired? - return !(d.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000))); - }; - }]); - -angular.module('angular-jwt.options', []) - .provider('jwtOptions', function() { - var globalConfig = {}; - this.config = function(value) { - globalConfig = value; - }; - this.$get = function() { - - var options = { - urlParam: null, - authHeader: 'Authorization', - authPrefix: 'Bearer ', - whiteListedDomains: [], - tokenGetter: function() { - return null; - }, - loginPath: '/', - unauthenticatedRedirectPath: '/', - unauthenticatedRedirector: ['$location', function($location) { - $location.path(this.unauthenticatedRedirectPath); - }] - }; - - function JwtOptions() { - var config = this.config = angular.extend({}, options, globalConfig); - } - - JwtOptions.prototype.getConfig = function() { - return this.config; - }; - - return new JwtOptions(); - } - }); - - /** - * The content from this file was directly lifted from Angular. It is - * unfortunately not a public API, so the best we can do is copy it. - * - * Angular References: - * https://github.com/angular/angular.js/issues/3299 - * https://github.com/angular/angular.js/blob/d077966ff1ac18262f4615ff1a533db24d4432a7/src/ng/urlUtils.js - */ - - angular.module('angular-jwt.interceptor') - .service('urlUtils', function () { - - // NOTE: The usage of window and document instead of $window and $document here is - // deliberate. This service depends on the specific behavior of anchor nodes created by the - // browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and - // cause us to break tests. In addition, when the browser resolves a URL for XHR, it - // doesn't know about mocked locations and resolves URLs to the real document - which is - // exactly the behavior needed here. There is little value is mocking these out for this - // service. - var urlParsingNode = document.createElement("a"); - var originUrl = urlResolve(window.location.href); - - /** - * - * Implementation Notes for non-IE browsers - * ---------------------------------------- - * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, - * results both in the normalizing and parsing of the URL. Normalizing means that a relative - * URL will be resolved into an absolute URL in the context of the application document. - * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related - * properties are all populated to reflect the normalized URL. This approach has wide - * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See - * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html - * - * Implementation Notes for IE - * --------------------------- - * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other - * browsers. However, the parsed components will not be set if the URL assigned did not specify - * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We - * work around that by performing the parsing in a 2nd step by taking a previously normalized - * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the - * properties such as protocol, hostname, port, etc. - * - * References: - * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement - * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html - * http://url.spec.whatwg.org/#urlutils - * https://github.com/angular/angular.js/pull/2902 - * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ - * - * @kind function - * @param {string} url The URL to be parsed. - * @description Normalizes and parses a URL. - * @returns {object} Returns the normalized URL as a dictionary. - * - * | member name | Description | - * |---------------|----------------| - * | href | A normalized version of the provided URL if it was not an absolute URL | - * | protocol | The protocol including the trailing colon | - * | host | The host and port (if the port is non-default) of the normalizedUrl | - * | search | The search params, minus the question mark | - * | hash | The hash string, minus the hash symbol - * | hostname | The hostname - * | port | The port, without ":" - * | pathname | The pathname, beginning with "/" - * - */ - function urlResolve(url) { - var href = url; - - // Normalize before parse. Refer Implementation Notes on why this is - // done in two steps on IE. - urlParsingNode.setAttribute("href", href); - href = urlParsingNode.href; - urlParsingNode.setAttribute('href', href); - - // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils - return { - href: urlParsingNode.href, - protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', - host: urlParsingNode.host, - search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', - hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', - hostname: urlParsingNode.hostname, - port: urlParsingNode.port, - pathname: (urlParsingNode.pathname.charAt(0) === '/') - ? urlParsingNode.pathname - : '/' + urlParsingNode.pathname - }; - } - - /** - * Parse a request URL and determine whether this is a same-origin request as the application document. - * - * @param {string|object} requestUrl The url of the request as a string that will be resolved - * or a parsed URL object. - * @returns {boolean} Whether the request is for the same origin as the application document. - */ - function urlIsSameOrigin(requestUrl) { - var parsed = (angular.isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; - return (parsed.protocol === originUrl.protocol && - parsed.host === originUrl.host); - } - - return { - urlResolve: urlResolve, - isSameOrigin: urlIsSameOrigin - }; - - }); - -}()); (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.angularCreditCards = f()}})(function(){var define,module,exports;return (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] }); to whitelist.') + } + var hostname = urlUtils.urlResolve(url).hostname.toLowerCase(); + for (var i = 0; i < options.whiteListedDomains.length; i++) { + var domain = options.whiteListedDomains[i]; + var regexp = domain instanceof RegExp ? domain : new RegExp(domain, 'i'); + if (hostname.match(regexp)) { + return true; + } + } + + if (urlUtils.isSameOrigin(url)) { + return true; + } + + return false; + } + + return { + request: function (request) { + if (request.skipAuthorization || !isSafe(request.url)) { + return request; + } + + if (options.urlParam) { + request.params = request.params || {}; + // Already has the token in the url itself + if (request.params[options.urlParam]) { + return request; + } + } else { + request.headers = request.headers || {}; + // Already has an Authorization header + if (request.headers[options.authHeader]) { + return request; + } + } + + var tokenPromise = $q.when($injector.invoke(options.tokenGetter, this, { + options: request + })); + + return tokenPromise.then(function(token) { + if (token) { + if (options.urlParam) { + request.params[options.urlParam] = token; + } else { + request.headers[options.authHeader] = options.authPrefix + token; + } + } + return request; + }); + }, + responseError: function (response) { + // handle the case where the user is not authenticated + if (response.status === 401) { + $rootScope.$broadcast('unauthenticated', response); + } + return $q.reject(response); + } + }; + }] + }); + + angular.module('angular-jwt.jwt', []) + .service('jwtHelper', ["$window", function($window) { + + this.urlBase64Decode = function(str) { + var output = str.replace(/-/g, '+').replace(/_/g, '/'); + switch (output.length % 4) { + case 0: { break; } + case 2: { output += '=='; break; } + case 3: { output += '='; break; } + default: { + throw 'Illegal base64url string!'; + } + } + return $window.decodeURIComponent(escape($window.atob(output))); //polyfill https://github.com/davidchambers/Base64.js + }; + + + this.decodeToken = function(token) { + var parts = token.split('.'); + + if (parts.length !== 3) { + throw new Error('JWT must have 3 parts'); + } + + var decoded = this.urlBase64Decode(parts[1]); + if (!decoded) { + throw new Error('Cannot decode the token'); + } + + return angular.fromJson(decoded); + }; + + this.getTokenExpirationDate = function(token) { + var decoded = this.decodeToken(token); + + if(typeof decoded.exp === "undefined") { + return null; + } + + var d = new Date(0); // The 0 here is the key, which sets the date to the epoch + d.setUTCSeconds(decoded.exp); + + return d; + }; + + this.isTokenExpired = function(token, offsetSeconds) { + var d = this.getTokenExpirationDate(token); + offsetSeconds = offsetSeconds || 0; + if (d === null) { + return false; + } + + // Token expired? + return !(d.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000))); }; }]); -if (typeof module === 'object') { - module.exports = app.name; -} +angular.module('angular-jwt.options', []) + .provider('jwtOptions', function() { + var globalConfig = {}; + this.config = function(value) { + globalConfig = value; + }; + this.$get = function() { + var options = { + urlParam: null, + authHeader: 'Authorization', + authPrefix: 'Bearer ', + whiteListedDomains: [], + tokenGetter: function() { + return null; + }, + loginPath: '/', + unauthenticatedRedirectPath: '/', + unauthenticatedRedirector: ['$location', function($location) { + $location.path(this.unauthenticatedRedirectPath); + }] + }; + + function JwtOptions() { + var config = this.config = angular.extend({}, options, globalConfig); + } + + JwtOptions.prototype.getConfig = function() { + return this.config; + }; + + return new JwtOptions(); + } + }); + + /** + * The content from this file was directly lifted from Angular. It is + * unfortunately not a public API, so the best we can do is copy it. + * + * Angular References: + * https://github.com/angular/angular.js/issues/3299 + * https://github.com/angular/angular.js/blob/d077966ff1ac18262f4615ff1a533db24d4432a7/src/ng/urlUtils.js + */ + + angular.module('angular-jwt.interceptor') + .service('urlUtils', function () { + + // NOTE: The usage of window and document instead of $window and $document here is + // deliberate. This service depends on the specific behavior of anchor nodes created by the + // browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and + // cause us to break tests. In addition, when the browser resolves a URL for XHR, it + // doesn't know about mocked locations and resolves URLs to the real document - which is + // exactly the behavior needed here. There is little value is mocking these out for this + // service. + var urlParsingNode = document.createElement("a"); + var originUrl = urlResolve(window.location.href); + + /** + * + * Implementation Notes for non-IE browsers + * ---------------------------------------- + * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, + * results both in the normalizing and parsing of the URL. Normalizing means that a relative + * URL will be resolved into an absolute URL in the context of the application document. + * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related + * properties are all populated to reflect the normalized URL. This approach has wide + * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See + * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html + * + * Implementation Notes for IE + * --------------------------- + * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other + * browsers. However, the parsed components will not be set if the URL assigned did not specify + * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We + * work around that by performing the parsing in a 2nd step by taking a previously normalized + * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the + * properties such as protocol, hostname, port, etc. + * + * References: + * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement + * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html + * http://url.spec.whatwg.org/#urlutils + * https://github.com/angular/angular.js/pull/2902 + * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ + * + * @kind function + * @param {string} url The URL to be parsed. + * @description Normalizes and parses a URL. + * @returns {object} Returns the normalized URL as a dictionary. + * + * | member name | Description | + * |---------------|----------------| + * | href | A normalized version of the provided URL if it was not an absolute URL | + * | protocol | The protocol including the trailing colon | + * | host | The host and port (if the port is non-default) of the normalizedUrl | + * | search | The search params, minus the question mark | + * | hash | The hash string, minus the hash symbol + * | hostname | The hostname + * | port | The port, without ":" + * | pathname | The pathname, beginning with "/" + * + */ + function urlResolve(url) { + var href = url; + + // Normalize before parse. Refer Implementation Notes on why this is + // done in two steps on IE. + urlParsingNode.setAttribute("href", href); + href = urlParsingNode.href; + urlParsingNode.setAttribute('href', href); + + // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils + return { + href: urlParsingNode.href, + protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', + host: urlParsingNode.host, + search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', + hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', + hostname: urlParsingNode.hostname, + port: urlParsingNode.port, + pathname: (urlParsingNode.pathname.charAt(0) === '/') + ? urlParsingNode.pathname + : '/' + urlParsingNode.pathname + }; + } + + /** + * Parse a request URL and determine whether this is a same-origin request as the application document. + * + * @param {string|object} requestUrl The url of the request as a string that will be resolved + * or a parsed URL object. + * @returns {boolean} Whether the request is for the same origin as the application document. + */ + function urlIsSameOrigin(requestUrl) { + var parsed = (angular.isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; + return (parsed.protocol === originUrl.protocol && + parsed.host === originUrl.host); + } + + return { + urlResolve: urlResolve, + isSameOrigin: urlIsSameOrigin + }; + + }); + +}()); /** * @license AngularJS v1.6.7 * (c) 2010-2017 Google, Inc. http://angularjs.org @@ -2965,6 +2925,46 @@ function ngMessageDirectiveFactory() { })(window, window.angular); +'use strict'; + +var app = angular + .module('angular-promise-polyfill', []) + .run(['$q', '$window', function($q, $window) { + $window.Promise = function(executor) { + return $q(executor); + }; + + $window.Promise.all = $q.all.bind($q); + $window.Promise.reject = $q.reject.bind($q); + $window.Promise.resolve = $q.when.bind($q); + + $window.Promise.race = function(promises) { + var promiseMgr = $q.defer(); + + for(var i = 0; i < promises.length; i++) { + promises[i].then(function(result) { + if(promiseMgr) { + promiseMgr.resolve(result); + promiseMgr = null; + } + }); + + promises[i].catch(function(result) { + if(promiseMgr) { + promiseMgr.reject(result); + promiseMgr = null; + } + }); + } + + return promiseMgr.promise; + }; + }]); + +if (typeof module === 'object') { + module.exports = app.name; +} + /** * @license AngularJS v1.6.7 * (c) 2010-2017 Google, Inc. http://angularjs.org @@ -4625,1637 +4625,6 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { })(window, window.angular); -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.angularStripe = f()}})(function(){var define,module,exports;return (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 - * - * Copyright (c) 2014 Jon Schlinkert, contributors. - * Licensed under the MIT license. - */ - -var isNumber = _dereq_('is-number'); -var slice = _dereq_('array-slice'); - -module.exports = function last(arr, num) { - if (!Array.isArray(arr)) { - throw new Error('array-last expects an array as the first argument.'); - } - - if (arr.length === 0) { - return null; - } - - var res = slice(arr, arr.length - (isNumber(num) ? +num : 1)); - if (+num === 1 || num == null) { - return res[0]; - } - return res; -}; - -},{"array-slice":8,"is-number":20}],8:[function(_dereq_,module,exports){ -/*! - * array-slice - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -module.exports = function slice(arr, start, end) { - var len = arr.length >>> 0; - var range = []; - - start = idx(arr, start); - end = idx(arr, end, len); - - while (start < end) { - range.push(arr[start++]); - } - return range; -}; - - -function idx(arr, pos, end) { - var len = arr.length >>> 0; - - if (pos == null) { - pos = end || 0; - } else if (pos < 0) { - pos = Math.max(len + pos, 0); - } else { - pos = Math.min(pos, len); - } - - return pos; -} -},{}],9:[function(_dereq_,module,exports){ -"use strict"; - -// rawAsap provides everything we need except exception management. -var rawAsap = _dereq_("./raw"); -// RawTasks are recycled to reduce GC churn. -var freeTasks = []; -// We queue errors to ensure they are thrown in right order (FIFO). -// Array-as-queue is good enough here, since we are just dealing with exceptions. -var pendingErrors = []; -var requestErrorThrow = rawAsap.makeRequestCallFromTimer(throwFirstError); - -function throwFirstError() { - if (pendingErrors.length) { - throw pendingErrors.shift(); - } -} - -/** - * Calls a task as soon as possible after returning, in its own event, with priority - * over other events like animation, reflow, and repaint. An error thrown from an - * event will not interrupt, nor even substantially slow down the processing of - * other events, but will be rather postponed to a lower priority event. - * @param {{call}} task A callable object, typically a function that takes no - * arguments. - */ -module.exports = asap; -function asap(task) { - var rawTask; - if (freeTasks.length) { - rawTask = freeTasks.pop(); - } else { - rawTask = new RawTask(); - } - rawTask.task = task; - rawAsap(rawTask); -} - -// We wrap tasks with recyclable task objects. A task object implements -// `call`, just like a function. -function RawTask() { - this.task = null; -} - -// The sole purpose of wrapping the task is to catch the exception and recycle -// the task object after its single use. -RawTask.prototype.call = function () { - try { - this.task.call(); - } catch (error) { - if (asap.onerror) { - // This hook exists purely for testing purposes. - // Its name will be periodically randomized to break any code that - // depends on its existence. - asap.onerror(error); - } else { - // In a web browser, exceptions are not fatal. However, to avoid - // slowing down the queue of pending tasks, we rethrow the error in a - // lower priority turn. - pendingErrors.push(error); - requestErrorThrow(); - } - } finally { - this.task = null; - freeTasks[freeTasks.length] = this; - } -}; - -},{"./raw":10}],10:[function(_dereq_,module,exports){ -(function (global){ -"use strict"; - -// Use the fastest means possible to execute a task in its own turn, with -// priority over other events including IO, animation, reflow, and redraw -// events in browsers. -// -// An exception thrown by a task will permanently interrupt the processing of -// subsequent tasks. The higher level `asap` function ensures that if an -// exception is thrown by a task, that the task queue will continue flushing as -// soon as possible, but if you use `rawAsap` directly, you are responsible to -// either ensure that no exceptions are thrown from your task, or to manually -// call `rawAsap.requestFlush` if an exception is thrown. -module.exports = rawAsap; -function rawAsap(task) { - if (!queue.length) { - requestFlush(); - flushing = true; - } - // Equivalent to push, but avoids a function call. - queue[queue.length] = task; -} - -var queue = []; -// Once a flush has been requested, no further calls to `requestFlush` are -// necessary until the next `flush` completes. -var flushing = false; -// `requestFlush` is an implementation-specific method that attempts to kick -// off a `flush` event as quickly as possible. `flush` will attempt to exhaust -// the event queue before yielding to the browser's own event loop. -var requestFlush; -// The position of the next task to execute in the task queue. This is -// preserved between calls to `flush` so that it can be resumed if -// a task throws an exception. -var index = 0; -// If a task schedules additional tasks recursively, the task queue can grow -// unbounded. To prevent memory exhaustion, the task queue will periodically -// truncate already-completed tasks. -var capacity = 1024; - -// The flush function processes all tasks that have been scheduled with -// `rawAsap` unless and until one of those tasks throws an exception. -// If a task throws an exception, `flush` ensures that its state will remain -// consistent and will resume where it left off when called again. -// However, `flush` does not make any arrangements to be called again if an -// exception is thrown. -function flush() { - while (index < queue.length) { - var currentIndex = index; - // Advance the index before calling the task. This ensures that we will - // begin flushing on the next task the task throws an error. - index = index + 1; - queue[currentIndex].call(); - // Prevent leaking memory for long chains of recursive calls to `asap`. - // If we call `asap` within tasks scheduled by `asap`, the queue will - // grow, but to avoid an O(n) walk for every task we execute, we don't - // shift tasks off the queue after they have been executed. - // Instead, we periodically shift 1024 tasks off the queue. - if (index > capacity) { - // Manually shift all values starting at the index back to the - // beginning of the queue. - for (var scan = 0, newLength = queue.length - index; scan < newLength; scan++) { - queue[scan] = queue[scan + index]; - } - queue.length -= index; - index = 0; - } - } - queue.length = 0; - index = 0; - flushing = false; -} - -// `requestFlush` is implemented using a strategy based on data collected from -// every available SauceLabs Selenium web driver worker at time of writing. -// https://docs.google.com/spreadsheets/d/1mG-5UYGup5qxGdEMWkhP6BWCz053NUb2E1QoUTU16uA/edit#gid=783724593 - -// Safari 6 and 6.1 for desktop, iPad, and iPhone are the only browsers that -// have WebKitMutationObserver but not un-prefixed MutationObserver. -// Must use `global` or `self` instead of `window` to work in both frames and web -// workers. `global` is a provision of Browserify, Mr, Mrs, or Mop. - -/* globals self */ -var scope = typeof global !== "undefined" ? global : self; -var BrowserMutationObserver = scope.MutationObserver || scope.WebKitMutationObserver; - -// MutationObservers are desirable because they have high priority and work -// reliably everywhere they are implemented. -// They are implemented in all modern browsers. -// -// - Android 4-4.3 -// - Chrome 26-34 -// - Firefox 14-29 -// - Internet Explorer 11 -// - iPad Safari 6-7.1 -// - iPhone Safari 7-7.1 -// - Safari 6-7 -if (typeof BrowserMutationObserver === "function") { - requestFlush = makeRequestCallFromMutationObserver(flush); - -// MessageChannels are desirable because they give direct access to the HTML -// task queue, are implemented in Internet Explorer 10, Safari 5.0-1, and Opera -// 11-12, and in web workers in many engines. -// Although message channels yield to any queued rendering and IO tasks, they -// would be better than imposing the 4ms delay of timers. -// However, they do not work reliably in Internet Explorer or Safari. - -// Internet Explorer 10 is the only browser that has setImmediate but does -// not have MutationObservers. -// Although setImmediate yields to the browser's renderer, it would be -// preferrable to falling back to setTimeout since it does not have -// the minimum 4ms penalty. -// Unfortunately there appears to be a bug in Internet Explorer 10 Mobile (and -// Desktop to a lesser extent) that renders both setImmediate and -// MessageChannel useless for the purposes of ASAP. -// https://github.com/kriskowal/q/issues/396 - -// Timers are implemented universally. -// We fall back to timers in workers in most engines, and in foreground -// contexts in the following browsers. -// However, note that even this simple case requires nuances to operate in a -// broad spectrum of browsers. -// -// - Firefox 3-13 -// - Internet Explorer 6-9 -// - iPad Safari 4.3 -// - Lynx 2.8.7 -} else { - requestFlush = makeRequestCallFromTimer(flush); -} - -// `requestFlush` requests that the high priority event queue be flushed as -// soon as possible. -// This is useful to prevent an error thrown in a task from stalling the event -// queue if the exception handled by Node.js’s -// `process.on("uncaughtException")` or by a domain. -rawAsap.requestFlush = requestFlush; - -// To request a high priority event, we induce a mutation observer by toggling -// the text of a text node between "1" and "-1". -function makeRequestCallFromMutationObserver(callback) { - var toggle = 1; - var observer = new BrowserMutationObserver(callback); - var node = document.createTextNode(""); - observer.observe(node, {characterData: true}); - return function requestCall() { - toggle = -toggle; - node.data = toggle; - }; -} - -// The message channel technique was discovered by Malte Ubl and was the -// original foundation for this library. -// http://www.nonblocking.io/2011/06/windownexttick.html - -// Safari 6.0.5 (at least) intermittently fails to create message ports on a -// page's first load. Thankfully, this version of Safari supports -// MutationObservers, so we don't need to fall back in that case. - -// function makeRequestCallFromMessageChannel(callback) { -// var channel = new MessageChannel(); -// channel.port1.onmessage = callback; -// return function requestCall() { -// channel.port2.postMessage(0); -// }; -// } - -// For reasons explained above, we are also unable to use `setImmediate` -// under any circumstances. -// Even if we were, there is another bug in Internet Explorer 10. -// It is not sufficient to assign `setImmediate` to `requestFlush` because -// `setImmediate` must be called *by name* and therefore must be wrapped in a -// closure. -// Never forget. - -// function makeRequestCallFromSetImmediate(callback) { -// return function requestCall() { -// setImmediate(callback); -// }; -// } - -// Safari 6.0 has a problem where timers will get lost while the user is -// scrolling. This problem does not impact ASAP because Safari 6.0 supports -// mutation observers, so that implementation is used instead. -// However, if we ever elect to use timers in Safari, the prevalent work-around -// is to add a scroll event listener that calls for a flush. - -// `setTimeout` does not call the passed callback if the delay is less than -// approximately 7 in web workers in Firefox 8 through 18, and sometimes not -// even then. - -function makeRequestCallFromTimer(callback) { - return function requestCall() { - // We dispatch a timeout with a specified delay of 0 for engines that - // can reliably accommodate that request. This will usually be snapped - // to a 4 milisecond delay, but once we're flushing, there's no delay - // between events. - var timeoutHandle = setTimeout(handleTimer, 0); - // However, since this timer gets frequently dropped in Firefox - // workers, we enlist an interval handle that will try to fire - // an event 20 times per second until it succeeds. - var intervalHandle = setInterval(handleTimer, 50); - - function handleTimer() { - // Whichever timer succeeds will cancel both timers and - // execute the callback. - clearTimeout(timeoutHandle); - clearInterval(intervalHandle); - callback(); - } - }; -} - -// This is for `asap.js` only. -// Its name will be periodically randomized to break any code that depends on -// its existence. -rawAsap.makeRequestCallFromTimer = makeRequestCallFromTimer; - -// ASAP was originally a nextTick shim included in Q. This was factored out -// into this ASAP package. It was later adapted to RSVP which made further -// amendments. These decisions, particularly to marginalize MessageChannel and -// to capture the MutationObserver implementation in a closure, were integrated -// back into ASAP proper. -// https://github.com/tildeio/rsvp.js/blob/cddf7232546a9cf858524b75cde6f9edf72620a7/lib/rsvp/asap.js - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],11:[function(_dereq_,module,exports){ -'use strict' - -var assert = _dereq_('assert-ok') -var format = _dereq_('simple-format') -var print = _dereq_('print-value') - -module.exports = function assertEqual (a, b) { - assert(a === b, format('expected `%s` to equal `%s`', print(a), print(b))) -} - -},{"assert-ok":13,"print-value":30,"simple-format":32}],12:[function(_dereq_,module,exports){ -'use strict' - -module.exports = function assertFunction (value) { - if (typeof value !== 'function') { - throw new TypeError('Expected function, got: ' + value) - } -} - -},{}],13:[function(_dereq_,module,exports){ -'use strict' - -module.exports = function assertOk (value, message) { - if (!value) { - throw new Error(message || 'Expected true, got ' + value) - } -} - -},{}],14:[function(_dereq_,module,exports){ -'use strict' - -module.exports = CallAll - -function CallAll (fns) { - fns = Array.isArray(fns) ? fns : arguments - return function callAll () { - var args = arguments - var ret = new Array(fns.length) - for (var i = 0, ii = fns.length; i < ii; i++) { - ret[i] = fns[i].apply(null, args) - } - return ret - } -} - -},{}],15:[function(_dereq_,module,exports){ -/** - * cuid.js - * Collision-resistant UID generator for browsers and node. - * Sequential for fast db lookups and recency sorting. - * Safe for element IDs and server-side lookups. - * - * Extracted from CLCTR - * - * Copyright (c) Eric Elliott 2012 - * MIT License - */ - -/*global window, navigator, document, require, process, module */ -(function (app) { - 'use strict'; - var namespace = 'cuid', - c = 0, - blockSize = 4, - base = 36, - discreteValues = Math.pow(base, blockSize), - - pad = function pad(num, size) { - var s = "000000000" + num; - return s.substr(s.length-size); - }, - - randomBlock = function randomBlock() { - return pad((Math.random() * - discreteValues << 0) - .toString(base), blockSize); - }, - - safeCounter = function () { - c = (c < discreteValues) ? c : 0; - c++; // this is not subliminal - return c - 1; - }, - - api = function cuid() { - // Starting with a lowercase letter makes - // it HTML element ID friendly. - var letter = 'c', // hard-coded allows for sequential access - - // timestamp - // warning: this exposes the exact date and time - // that the uid was created. - timestamp = (new Date().getTime()).toString(base), - - // Prevent same-machine collisions. - counter, - - // A few chars to generate distinct ids for different - // clients (so different computers are far less - // likely to generate the same id) - fingerprint = api.fingerprint(), - - // Grab some more chars from Math.random() - random = randomBlock() + randomBlock(); - - counter = pad(safeCounter().toString(base), blockSize); - - return (letter + timestamp + counter + fingerprint + random); - }; - - api.slug = function slug() { - var date = new Date().getTime().toString(36), - counter, - print = api.fingerprint().slice(0,1) + - api.fingerprint().slice(-1), - random = randomBlock().slice(-2); - - counter = safeCounter().toString(36).slice(-4); - - return date.slice(-2) + - counter + print + random; - }; - - api.globalCount = function globalCount() { - // We want to cache the results of this - var cache = (function calc() { - var i, - count = 0; - - for (i in window) { - count++; - } - - return count; - }()); - - api.globalCount = function () { return cache; }; - return cache; - }; - - api.fingerprint = function browserPrint() { - return pad((navigator.mimeTypes.length + - navigator.userAgent.length).toString(36) + - api.globalCount().toString(36), 4); - }; - - // don't change anything from here down. - if (app.register) { - app.register(namespace, api); - } else if (typeof module !== 'undefined') { - module.exports = api; - } else { - app[namespace] = api; - } - -}(this.applitude || this)); - -},{}],16:[function(_dereq_,module,exports){ -var wrappy = _dereq_('wrappy') -module.exports = wrappy(dezalgo) - -var asap = _dereq_('asap') - -function dezalgo (cb) { - var sync = true - asap(function () { - sync = false - }) - - return function zalgoSafe() { - var args = arguments - var me = this - if (sync) - asap(function() { - cb.apply(me, args) - }) - else - cb.apply(me, args) - } -} - -},{"asap":9,"wrappy":35}],17:[function(_dereq_,module,exports){ -'use strict'; -var isObj = _dereq_('is-obj'); - -module.exports.get = function (obj, path) { - if (!isObj(obj) || typeof path !== 'string') { - return obj; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - var descriptor = Object.getOwnPropertyDescriptor(obj, pathArr[i]) || Object.getOwnPropertyDescriptor(Object.prototype, pathArr[i]); - if (descriptor && !descriptor.enumerable) { - return; - } - - obj = obj[pathArr[i]]; - - if (obj === undefined || obj === null) { - // `obj` is either `undefined` or `null` so we want to stop the loop, and - // if this is not the last bit of the path, and - // if it did't return `undefined` - // it would return `null` if `obj` is `null` - // but we want `get({foo: null}, 'foo.bar')` to equal `undefined` not `null` - if (i !== pathArr.length - 1) { - return undefined; - } - - break; - } - } - - return obj; -}; - -module.exports.set = function (obj, path, value) { - if (!isObj(obj) || typeof path !== 'string') { - return; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - var p = pathArr[i]; - - if (!isObj(obj[p])) { - obj[p] = {}; - } - - if (i === pathArr.length - 1) { - obj[p] = value; - } - - obj = obj[p]; - } -}; - -module.exports.delete = function (obj, path) { - if (!isObj(obj) || typeof path !== 'string') { - return; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - var p = pathArr[i]; - - if (i === pathArr.length - 1) { - delete obj[p]; - return; - } - - obj = obj[p]; - } -}; - -module.exports.has = function (obj, path) { - if (!isObj(obj) || typeof path !== 'string') { - return false; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - obj = obj[pathArr[i]]; - - if (obj === undefined) { - return false; - } - } - - return true; -}; - -function getPathSegments(path) { - var pathArr = path.split('.'); - var parts = []; - - for (var i = 0; i < pathArr.length; i++) { - var p = pathArr[i]; - - while (p[p.length - 1] === '\\' && pathArr[i + 1] !== undefined) { - p = p.slice(0, -1) + '.'; - p += pathArr[++i]; - } - - parts.push(p); - } - - return parts; -} - -},{"is-obj":21}],18:[function(_dereq_,module,exports){ -'use strict' - -var assertFn = _dereq_('assert-function') - -module.exports = Ear - -function Ear () { - var callbacks = [] - - function listeners () { - var args = arguments - var i = 0 - var length = callbacks.length - for (; i < length; i++) { - var callback = callbacks[i] - callback.apply(null, args) - } - } - - listeners.add = function (listener) { - assertFn(listener) - callbacks.push(listener) - return function remove () { - var i = 0 - var length = callbacks.length - for (; i < length; i++) { - if (callbacks[i] === listener) { - callbacks.splice(i, 1) - return - } - } - } - } - - return listeners -} - -},{"assert-function":12}],19:[function(_dereq_,module,exports){ -(function (global){ -var win; - -if (typeof window !== "undefined") { - win = window; -} else if (typeof global !== "undefined") { - win = global; -} else if (typeof self !== "undefined"){ - win = self; -} else { - win = {}; -} - -module.exports = win; - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],20:[function(_dereq_,module,exports){ -/*! - * is-number - * - * Copyright (c) 2014 Jon Schlinkert, contributors. - * Licensed under the MIT License - */ - -'use strict'; - -module.exports = function isNumber(n) { - return !!(+n) || n === 0 || n === '0'; -}; - -},{}],21:[function(_dereq_,module,exports){ -'use strict'; -module.exports = function (x) { - var type = typeof x; - return x !== null && (type === 'object' || type === 'function'); -}; - -},{}],22:[function(_dereq_,module,exports){ -module.exports = Array.isArray || function (arr) { - return Object.prototype.toString.call(arr) == '[object Array]'; -}; - -},{}],23:[function(_dereq_,module,exports){ -/*! - * isobject - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var isArray = _dereq_('isarray'); - -module.exports = function isObject(o) { - return o != null && typeof o === 'object' && !isArray(o); -}; - -},{"isarray":22}],24:[function(_dereq_,module,exports){ -exports = module.exports = stringify -exports.getSerialize = serializer - -function stringify(obj, replacer, spaces, cycleReplacer) { - return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces) -} - -function serializer(replacer, cycleReplacer) { - var stack = [], keys = [] - - if (cycleReplacer == null) cycleReplacer = function(key, value) { - if (stack[0] === value) return "[Circular ~]" - return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]" - } - - return function(key, value) { - if (stack.length > 0) { - var thisPos = stack.indexOf(this) - ~thisPos ? stack.splice(thisPos + 1) : stack.push(this) - ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key) - if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value) - } - else stack.push(value) - - return replacer == null ? value : replacer.call(this, key, value) - } -} - -},{}],25:[function(_dereq_,module,exports){ -'use strict' - -var assert = _dereq_('assert-ok') -var assertEqual = _dereq_('assert-equal') -var dot = _dereq_('dot-prop') -var toArray = _dereq_('to-array') -var last = _dereq_('array-last') -var dezalgo = _dereq_('dezalgo') -var all = _dereq_('call-all-fns') - -module.exports = Lazy - -function Lazy (methods, load) { - assert(Array.isArray(methods), 'methods are required') - assertEqual(typeof load, 'function', 'load fn is required') - - var api = null - var error = null - var queue = [] - - load(function (err, lib) { - error = err - api = lib - all(queue)(err, lib) - queue = null - }) - - return methods.reduce(function (lazy, method) { - dot.set(lazy, method, Deferred(method)) - return lazy - }, {}) - - function Deferred (method) { - return function deferred () { - var args = arguments - onReady(function (err, api) { - if (!err) return dot.get(api, method).apply(null, args) - var callback = last(toArray(args)) - if (typeof callback === 'function') { - return callback(err) - } - }) - } - } - - function onReady (callback) { - callback = dezalgo(callback) - - if (api || error) return callback(error, api) - queue.push(callback) - } -} - -},{"array-last":7,"assert-equal":11,"assert-ok":13,"call-all-fns":14,"dezalgo":16,"dot-prop":26,"to-array":34}],26:[function(_dereq_,module,exports){ -'use strict'; -var isObj = _dereq_('is-obj'); - -module.exports.get = function (obj, path) { - if (!isObj(obj) || typeof path !== 'string') { - return obj; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - obj = obj[pathArr[i]]; - - if (obj === undefined) { - break; - } - } - - return obj; -}; - -module.exports.set = function (obj, path, value) { - if (!isObj(obj) || typeof path !== 'string') { - return; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - var p = pathArr[i]; - - if (!isObj(obj[p])) { - obj[p] = {}; - } - - if (i === pathArr.length - 1) { - obj[p] = value; - } - - obj = obj[p]; - } -}; - -module.exports.delete = function (obj, path) { - if (!isObj(obj) || typeof path !== 'string') { - return; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - var p = pathArr[i]; - - if (i === pathArr.length - 1) { - delete obj[p]; - return; - } - - obj = obj[p]; - } -}; - -module.exports.has = function (obj, path) { - if (!isObj(obj) || typeof path !== 'string') { - return false; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - obj = obj[pathArr[i]]; - - if (obj === undefined) { - return false; - } - } - - return true; -}; - -function getPathSegments(path) { - var pathArr = path.split('.'); - var parts = []; - - for (var i = 0; i < pathArr.length; i++) { - var p = pathArr[i]; - - while (p[p.length - 1] === '\\') { - p = p.slice(0, -1) + '.'; - p += pathArr[++i]; - } - - parts.push(p); - } - - return parts; -} - -},{"is-obj":21}],27:[function(_dereq_,module,exports){ -'use strict' - -var load = _dereq_('load-script') -var window = _dereq_('global/window') -var extend = _dereq_('xtend') -var assert = _dereq_('assert-ok') -var dezalgo = _dereq_('dezalgo') -var Listeners = _dereq_('ear') -var extendQuery = _dereq_('query-extend') -var cuid = _dereq_('cuid') - -module.exports = loadGlobal - -var listeners = {} - -function loadGlobal (options, callback) { - assert(options, 'options required') - assert(options.url, 'url required') - assert(options.global, 'global required') - assert(callback, 'callback required') - - options = extend(options) - callback = dezalgo(callback) - - if (getGlobal(options)) { - return callback(null, getGlobal(options)) - } - - callback = cache(options, callback) - if (!callback) return - - if (options.jsonp) { - var id = jsonpCallback(options, callback) - options.url = extendQuery(options.url, {callback: id}) - } - - load(options.url, options, function (err) { - if (err) return callback(err) - if (!options.jsonp) { - var library = getGlobal(options) - if (!library) return callback(new Error('expected: `window.' + options.global + '`, actual: `' + library + '`')) - callback(null, library) - } - }) -} - -function cache (options, callback) { - if (!get()) { - set(Listeners()) - get().add(callback) - return function onComplete (err, lib) { - get()(err, lib) - set(Listeners()) - } - } - - get().add(callback) - return undefined - - function get () { - return listeners[options.global] - } - - function set (value) { - listeners[options.global] = value - } -} - -function getGlobal (options) { - return window[options.global] -} - -function jsonpCallback (options, callback) { - var id = cuid() - window[id] = function jsonpCallback () { - callback(null, getGlobal(options)) - delete window[id] - } - return id -} - -},{"assert-ok":13,"cuid":15,"dezalgo":16,"ear":18,"global/window":19,"load-script":28,"query-extend":31,"xtend":36}],28:[function(_dereq_,module,exports){ - -module.exports = function load (src, opts, cb) { - var head = document.head || document.getElementsByTagName('head')[0] - var script = document.createElement('script') - - if (typeof opts === 'function') { - cb = opts - opts = {} - } - - opts = opts || {} - cb = cb || function() {} - - script.type = opts.type || 'text/javascript' - script.charset = opts.charset || 'utf8'; - script.async = 'async' in opts ? !!opts.async : true - script.src = src - - if (opts.attrs) { - setAttributes(script, opts.attrs) - } - - if (opts.text) { - script.text = '' + opts.text - } - - var onend = 'onload' in script ? stdOnEnd : ieOnEnd - onend(script, cb) - - // some good legacy browsers (firefox) fail the 'in' detection above - // so as a fallback we always set onload - // old IE will ignore this and new IE will set onload - if (!script.onload) { - stdOnEnd(script, cb); - } - - head.appendChild(script) -} - -function setAttributes(script, attrs) { - for (var attr in attrs) { - script.setAttribute(attr, attrs[attr]); - } -} - -function stdOnEnd (script, cb) { - script.onload = function () { - this.onerror = this.onload = null - cb(null, script) - } - script.onerror = function () { - // this.onload = null here is necessary - // because even IE9 works not like others - this.onerror = this.onload = null - cb(new Error('Failed to load ' + this.src), script) - } -} - -function ieOnEnd (script, cb) { - script.onreadystatechange = function () { - if (this.readyState != 'complete' && this.readyState != 'loaded') return - this.onreadystatechange = null - cb(null, script) // there is no way to catch loading errors in IE8 - } -} - -},{}],29:[function(_dereq_,module,exports){ -'use strict'; - -module.exports = function split(str) { - var a = 1, - res = ''; - - var parts = str.split('%'), - len = parts.length; - - if (len > 0) { res += parts[0]; } - - for (var i = 1; i < len; i++) { - if (parts[i][0] === 's' || parts[i][0] === 'd') { - var value = arguments[a++]; - res += parts[i][0] === 'd' ? Math.floor(value) : value; - } else if (parts[i][0]) { - res += '%' + parts[i][0]; - } else { - i++; - res += '%' + parts[i][0]; - } - - res += parts[i].substring(1); - } - - return res; -}; - -},{}],30:[function(_dereq_,module,exports){ -'use strict' - -var isObject = _dereq_('isobject') -var safeStringify = _dereq_('json-stringify-safe') - -module.exports = function print (value) { - var toString = isJson(value) ? stringify : String - return toString(value) -} - -function isJson (value) { - return isObject(value) || Array.isArray(value) -} - -function stringify (value) { - return safeStringify(value, null, '') -} - -},{"isobject":23,"json-stringify-safe":24}],31:[function(_dereq_,module,exports){ -!function(glob) { - - var queryToObject = function(query) { - var obj = {}; - if (!query) return obj; - each(query.split('&'), function(val) { - var pieces = val.split('='); - var key = parseKey(pieces[0]); - var keyDecoded = decodeURIComponent(key.val); - var valDecoded = pieces[1] && decodeURIComponent(pieces[1]); - - if (key.type === 'array') { - if (!obj[keyDecoded]) obj[keyDecoded] = []; - obj[keyDecoded].push(valDecoded); - } else if (key.type === 'string') { - obj[keyDecoded] = valDecoded; - } - }); - return obj; - }; - - var objectToQuery = function(obj) { - var pieces = [], encodedKey; - for (var k in obj) { - if (!obj.hasOwnProperty(k)) continue; - if (typeof obj[k] === 'undefined') { - pieces.push(encodeURIComponent(k)); - continue; - } - encodedKey = encodeURIComponent(k); - if (isArray(obj[k])) { - each(obj[k], function(val) { - pieces.push(encodedKey + '[]=' + encodeURIComponent(val)); - }); - continue; - } - pieces.push(encodedKey + '=' + encodeURIComponent(obj[k])); - } - return pieces.length ? ('?' + pieces.join('&')) : ''; - }; - - // for now we will only support string and arrays - var parseKey = function(key) { - var pos = key.indexOf('['); - if (pos === -1) return { type: 'string', val: key }; - return { type: 'array', val: key.substr(0, pos) }; - }; - - var isArray = function(val) { - return Object.prototype.toString.call(val) === '[object Array]'; - }; - - var extract = function(url) { - var pos = url.lastIndexOf('?'); - var hasQuery = pos !== -1; - var base = void 0; - - if (hasQuery && pos > 0) { - base = url.substring(0, pos); - } else if (!hasQuery && (url && url.length > 0)) { - base = url; - } - - return { - base: base, - query: hasQuery ? url.substring(pos+1) : void 0 - }; - }; - - // thanks raynos! - // https://github.com/Raynos/xtend - var extend = function() { - var target = {}; - for (var i = 0; i < arguments.length; i++) { - var source = arguments[i]; - for (var key in source) { - if (source.hasOwnProperty(key)) { - target[key] = source[key]; - } - } - } - return target; - }; - - var queryExtend = function() { - var args = Array.prototype.slice.call(arguments, 0); - var asObject = args[args.length-1] === true; - var base = ''; - - if (!args.length) { - return base; - } - - if (asObject) { - args.pop(); - } - - var normalized = map(args, function(param) { - if (typeof param === 'string') { - var extracted = extract(param); - if (extracted.base) base = extracted.base; - return queryToObject(extracted.query); - } - return param; - }); - - if (asObject) { - return extend.apply({}, normalized); - } else { - return base + objectToQuery(extend.apply({}, normalized)); - } - - }; - - var each = function(arr, fn) { - for (var i = 0, l = arr.length; i < l; i++) { - fn(arr[i], i); - } - }; - - var map = function(arr, fn) { - var res = []; - for (var i = 0, l = arr.length; i < l; i++) { - res.push( fn(arr[i], i) ); - } - return res; - }; - - if (typeof module !== 'undefined' && module.exports) { - // Node.js / browserify - module.exports = queryExtend; - } else if (typeof define === 'function' && define.amd) { - // require.js / AMD - define(function() { - return queryExtend; - }); - } else { - // +