diff --git a/index.html b/index.html index 2dd7281c..ad127cbd 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 5f4a2403..d2a0683e 100644 --- a/js/app.min.js +++ b/js/app.min.js @@ -25,7 +25,7 @@ angular ]); angular.module("bit") -.constant("appSettings", {"apiUri":"/api","identityUri":"/identity","iconsUri":"https://icons.bitwarden.com","stripeKey":"pk_live_bpN0P37nMxrMQkcaHXtAybJk","braintreeKey":"production_qfbsv8kc_njj2zjtyngtjmbjd","selfHosted":false,"version":"1.24.2","environment":"Production"}); +.constant("appSettings", {"apiUri":"/api","identityUri":"/identity","iconsUri":"https://icons.bitwarden.com","stripeKey":"pk_live_bpN0P37nMxrMQkcaHXtAybJk","braintreeKey":"production_qfbsv8kc_njj2zjtyngtjmbjd","selfHosted":false,"version":"1.25.0","environment":"Production"}); angular .module('bit.accounts', ['ui.bootstrap', 'ngCookies']); @@ -40,10 +40,10 @@ angular .module('bit.global', []); angular - .module('bit.organization', ['ui.bootstrap']); + .module('bit.reports', ['toastr', 'ngSanitize']); angular - .module('bit.reports', ['toastr', 'ngSanitize']); + .module('bit.organization', ['ui.bootstrap']); angular .module('bit.services', ['ngResource', 'ngStorage', 'angular-jwt']); @@ -2243,6 +2243,45 @@ 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.organization') @@ -5410,45 +5449,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') diff --git a/js/fallback-styles.min.js b/js/fallback-styles.min.js index 2b96abff..6c5ff74a 100644 --- a/js/fallback-styles.min.js +++ b/js/fallback-styles.min.js @@ -1,4 +1,4 @@ -var cacheTag = 'nbpi6' || ''; +var cacheTag = 'ucqrsv' || ''; function loadStylesheetIfMissing(property, value, paths) { var scripts = document.getElementsByTagName('SCRIPT'), diff --git a/js/lib.min.js b/js/lib.min.js index d358679c..521113d0 100644 --- a/js/lib.min.js +++ b/js/lib.min.js @@ -2065,6 +2065,906 @@ if (typeof module === 'object') { module.exports = app.name; } +(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= new Date(year, month) +} + +function parseMonth (month) { + return parseIntStrict(month) +} + +function formatExpYear (year, strip) { + year = year.toString() + return strip ? year.substr(2, 4) : year +} + +function isExpYearValid (year) { + if (typeof year !== 'number') return false + year = parseIntStrict(year) + return year > 0 +} + +function isExpYearPast (year) { + return new Date().getFullYear() > year +} + +},{"is-valid-month":21,"parse-int":24,"parse-year":25}],13:[function(_dereq_,module,exports){ +'use strict' + +module.exports = { + card: _dereq_('./card'), + cvc: _dereq_('./cvc'), + expiration: _dereq_('./expiration') +} + +},{"./card":10,"./cvc":11,"./expiration":12}],14:[function(_dereq_,module,exports){ +'use strict' + +var ccTypes = _dereq_('creditcards-types') +var camel = _dereq_('to-camel-case') +var extend = _dereq_('xtend') + +module.exports = extend(ccTypes, { + get: function getTypeByName (name) { + return ccTypes.types[camel(name)] + } +}) + +},{"creditcards-types":7,"to-camel-case":26,"xtend":29}],15:[function(_dereq_,module,exports){ +'use strict' + +var zeroFill = _dereq_('zero-fill') +var parseIntStrict = _dereq_('parse-int') + +var pad = zeroFill(2) + +module.exports = function expandYear (year, now) { + now = now || new Date() + var base = now.getFullYear().toString().substr(0, 2) + year = parseIntStrict(year) + return parseIntStrict(base + pad(year)) +} + +},{"parse-int":24,"zero-fill":31}],16:[function(_dereq_,module,exports){ +'use strict' + +module.exports = (function (array) { + return function luhn (number) { + if (typeof number !== 'string') throw new TypeError('Expected string input') + if (!number) return false + var length = number.length + var bit = 1 + var sum = 0 + var value + + while (length) { + value = parseInt(number.charAt(--length), 10) + sum += (bit ^= 1) ? array[value] : value + } + + return !!sum && sum % 10 === 0 + } +}([0, 2, 4, 6, 8, 1, 3, 5, 7, 9])) + +},{}],17:[function(_dereq_,module,exports){ +'use strict'; + +/* eslint no-invalid-this: 1 */ + +var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible '; +var slice = Array.prototype.slice; +var toStr = Object.prototype.toString; +var funcType = '[object Function]'; + +module.exports = function bind(that) { + var target = this; + if (typeof target !== 'function' || toStr.call(target) !== funcType) { + throw new TypeError(ERROR_MESSAGE + target); + } + var args = slice.call(arguments, 1); + + var bound; + var binder = function () { + if (this instanceof bound) { + var result = target.apply( + this, + args.concat(slice.call(arguments)) + ); + if (Object(result) === result) { + return result; + } + return this; + } else { + return target.apply( + that, + args.concat(slice.call(arguments)) + ); + } + }; + + var boundLength = Math.max(0, target.length - args.length); + var boundArgs = []; + for (var i = 0; i < boundLength; i++) { + boundArgs.push('$' + i); + } + + bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder); + + if (target.prototype) { + var Empty = function Empty() {}; + Empty.prototype = target.prototype; + bound.prototype = new Empty(); + Empty.prototype = null; + } + + return bound; +}; + +},{}],18:[function(_dereq_,module,exports){ +'use strict'; + +var implementation = _dereq_('./implementation'); + +module.exports = Function.prototype.bind || implementation; + +},{"./implementation":17}],19:[function(_dereq_,module,exports){ +'use strict'; +var numberIsNan = _dereq_('number-is-nan'); + +module.exports = Number.isFinite || function (val) { + return !(typeof val !== 'number' || numberIsNan(val) || val === Infinity || val === -Infinity); +}; + +},{"number-is-nan":23}],20:[function(_dereq_,module,exports){ +// https://github.com/paulmillr/es6-shim +// http://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.isinteger +var isFinite = _dereq_("is-finite"); +module.exports = Number.isInteger || function(val) { + return typeof val === "number" && + isFinite(val) && + Math.floor(val) === val; +}; + +},{"is-finite":19}],21:[function(_dereq_,module,exports){ +'use strict' + +var isInteger = _dereq_('is-integer') + +module.exports = function isValidMonth (month) { + if (typeof month !== 'number' || !isInteger(month)) return false + return month >= 1 && month <= 12 +} + +},{"is-integer":20}],22:[function(_dereq_,module,exports){ +module.exports = Array.isArray || function (arr) { + return Object.prototype.toString.call(arr) == '[object Array]'; +}; + +},{}],23:[function(_dereq_,module,exports){ +'use strict'; +module.exports = Number.isNaN || function (x) { + return x !== x; +}; + +},{}],24:[function(_dereq_,module,exports){ +'use strict' + +var isInteger = _dereq_('is-integer') + +module.exports = function parseIntStrict (integer) { + if (typeof integer === 'number') { + return isInteger(integer) ? integer : undefined + } + if (typeof integer === 'string') { + return /^-?\d+$/.test(integer) ? parseInt(integer, 10) : undefined + } +} + +},{"is-integer":20}],25:[function(_dereq_,module,exports){ +'use strict' + +var parseIntStrict = _dereq_('parse-int') +var expandYear = _dereq_('expand-year') + +module.exports = function parseYear (year, expand, now) { + year = parseIntStrict(year) + if (year == null) return + if (!expand) return year + return expandYear(year, now) +} + +},{"expand-year":15,"parse-int":24}],26:[function(_dereq_,module,exports){ + +var space = _dereq_('to-space-case') + +/** + * Export. + */ + +module.exports = toCamelCase + +/** + * Convert a `string` to camel case. + * + * @param {String} string + * @return {String} + */ + +function toCamelCase(string) { + return space(string).replace(/\s(\w)/g, function (matches, letter) { + return letter.toUpperCase() + }) +} + +},{"to-space-case":28}],27:[function(_dereq_,module,exports){ + +/** + * Export. + */ + +module.exports = toNoCase + +/** + * Test whether a string is camel-case. + */ + +var hasSpace = /\s/ +var hasSeparator = /(_|-|\.|:)/ +var hasCamel = /([a-z][A-Z]|[A-Z][a-z])/ + +/** + * Remove any starting case from a `string`, like camel or snake, but keep + * spaces and punctuation that may be important otherwise. + * + * @param {String} string + * @return {String} + */ + +function toNoCase(string) { + if (hasSpace.test(string)) return string.toLowerCase() + if (hasSeparator.test(string)) return (unseparate(string) || string).toLowerCase() + if (hasCamel.test(string)) return uncamelize(string).toLowerCase() + return string.toLowerCase() +} + +/** + * Separator splitter. + */ + +var separatorSplitter = /[\W_]+(.|$)/g + +/** + * Un-separate a `string`. + * + * @param {String} string + * @return {String} + */ + +function unseparate(string) { + return string.replace(separatorSplitter, function (m, next) { + return next ? ' ' + next : '' + }) +} + +/** + * Camelcase splitter. + */ + +var camelSplitter = /(.)([A-Z]+)/g + +/** + * Un-camelcase a `string`. + * + * @param {String} string + * @return {String} + */ + +function uncamelize(string) { + return string.replace(camelSplitter, function (m, previous, uppers) { + return previous + ' ' + uppers.toLowerCase().split('').join(' ') + }) +} + +},{}],28:[function(_dereq_,module,exports){ + +var clean = _dereq_('to-no-case') + +/** + * Export. + */ + +module.exports = toSpaceCase + +/** + * Convert a `string` to space case. + * + * @param {String} string + * @return {String} + */ + +function toSpaceCase(string) { + return clean(string).replace(/[\W_]+(.|$)/g, function (matches, match) { + return match ? ' ' + match : '' + }).trim() +} + +},{"to-no-case":27}],29:[function(_dereq_,module,exports){ +module.exports = extend + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +function extend() { + var target = {} + + for (var i = 0; i < arguments.length; i++) { + var source = arguments[i] + + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } + } + + return target +} + +},{}],30:[function(_dereq_,module,exports){ +module.exports = extend + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +function extend(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] + + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } + } + + return target +} + +},{}],31:[function(_dereq_,module,exports){ +/** + * Given a number, return a zero-filled string. + * From http://stackoverflow.com/questions/1267283/ + * @param {number} width + * @param {number} number + * @return {string} + */ +module.exports = function zeroFill (width, number, pad) { + if (number === undefined) { + return function (number, pad) { + return zeroFill(width, number, pad) + } + } + if (pad === undefined) pad = '0' + width -= number.toString().length + if (width > 0) return new Array(width + (/\./.test(number) ? 2 : 1)).join(pad) + number + return number + '' +} + +},{}]},{},[3])(3) +}); /** * @license AngularJS v1.6.7 * (c) 2010-2017 Google, Inc. http://angularjs.org @@ -2865,864 +3765,6 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { }]); -})(window, window.angular); - -/** - * @license AngularJS v1.6.7 - * (c) 2010-2017 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, angular) {'use strict'; - -var $resourceMinErr = angular.$$minErr('$resource'); - -// Helper functions and regex to lookup a dotted path on an object -// stopping at undefined/null. The path must be composed of ASCII -// identifiers (just like $parse) -var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/; - -function isValidDottedPath(path) { - return (path != null && path !== '' && path !== 'hasOwnProperty' && - MEMBER_NAME_REGEX.test('.' + path)); -} - -function lookupDottedPath(obj, path) { - if (!isValidDottedPath(path)) { - throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); - } - var keys = path.split('.'); - for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) { - var key = keys[i]; - obj = (obj !== null) ? obj[key] : undefined; - } - return obj; -} - -/** - * Create a shallow copy of an object and clear other fields from the destination - */ -function shallowClearAndCopy(src, dst) { - dst = dst || {}; - - angular.forEach(dst, function(value, key) { - delete dst[key]; - }); - - for (var key in src) { - if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { - dst[key] = src[key]; - } - } - - return dst; -} - -/** - * @ngdoc module - * @name ngResource - * @description - * - * The `ngResource` module provides interaction support with RESTful services - * via the $resource service. - * - * See {@link ngResource.$resourceProvider} and {@link ngResource.$resource} for usage. - */ - -/** - * @ngdoc provider - * @name $resourceProvider - * - * @description - * - * Use `$resourceProvider` to change the default behavior of the {@link ngResource.$resource} - * service. - * - * ## Dependencies - * Requires the {@link ngResource } module to be installed. - * - */ - -/** - * @ngdoc service - * @name $resource - * @requires $http - * @requires ng.$log - * @requires $q - * @requires ng.$timeout - * - * @description - * A factory which creates a resource object that lets you interact with - * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. - * - * The returned resource object has action methods which provide high-level behaviors without - * the need to interact with the low level {@link ng.$http $http} service. - * - * Requires the {@link ngResource `ngResource`} module to be installed. - * - * By default, trailing slashes will be stripped from the calculated URLs, - * which can pose problems with server backends that do not expect that - * behavior. This can be disabled by configuring the `$resourceProvider` like - * this: - * - * ```js - app.config(['$resourceProvider', function($resourceProvider) { - // Don't strip trailing slashes from calculated URLs - $resourceProvider.defaults.stripTrailingSlashes = false; - }]); - * ``` - * - * @param {string} url A parameterized URL template with parameters prefixed by `:` as in - * `/user/:username`. If you are using a URL with a port number (e.g. - * `http://example.com:8080/api`), it will be respected. - * - * If you are using a url with a suffix, just add the suffix, like this: - * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` - * or even `$resource('http://example.com/resource/:resource_id.:format')` - * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be - * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you - * can escape it with `/\.`. - * - * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in - * `actions` methods. If a parameter value is a function, it will be called every time - * a param value needs to be obtained for a request (unless the param was overridden). The function - * will be passed the current data value as an argument. - * - * Each key value in the parameter object is first bound to url template if present and then any - * excess keys are appended to the url search query after the `?`. - * - * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in - * URL `/path/greet?salutation=Hello`. - * - * If the parameter value is prefixed with `@`, then the value for that parameter will be - * extracted from the corresponding property on the `data` object (provided when calling actions - * with a request body). - * For example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of - * `someParam` will be `data.someProp`. - * Note that the parameter will be ignored, when calling a "GET" action method (i.e. an action - * method that does not accept a request body) - * - * @param {Object.=} actions Hash with declaration of custom actions that will be available - * in addition to the default set of resource actions (see below). If a custom action has the same - * key as a default action (e.g. `save`), then the default action will be *overwritten*, and not - * extended. - * - * The declaration should be created in the format of {@link ng.$http#usage $http.config}: - * - * {action1: {method:?, params:?, isArray:?, headers:?, ...}, - * action2: {method:?, params:?, isArray:?, headers:?, ...}, - * ...} - * - * Where: - * - * - **`action`** – {string} – The name of action. This name becomes the name of the method on - * your resource object. - * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`, - * `DELETE`, `JSONP`, etc). - * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of - * the parameter value is a function, it will be called every time when a param value needs to - * be obtained for a request (unless the param was overridden). The function will be passed the - * current data value as an argument. - * - **`url`** – {string} – action specific `url` override. The url templating is supported just - * like for the resource-level urls. - * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, - * see `returns` section. - * - **`transformRequest`** – - * `{function(data, headersGetter)|Array.}` – - * transform function or an array of such functions. The transform function takes the http - * request body and headers and returns its transformed (typically serialized) version. - * By default, transformRequest will contain one function that checks if the request data is - * an object and serializes it using `angular.toJson`. To prevent this behavior, set - * `transformRequest` to an empty array: `transformRequest: []` - * - **`transformResponse`** – - * `{function(data, headersGetter, status)|Array.}` – - * transform function or an array of such functions. The transform function takes the http - * response body, headers and status and returns its transformed (typically deserialized) - * version. - * By default, transformResponse will contain one function that checks if the response looks - * like a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, - * set `transformResponse` to an empty array: `transformResponse: []` - * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the - * GET request, otherwise if a cache instance built with - * {@link ng.$cacheFactory $cacheFactory} is supplied, this cache will be used for - * caching. - * - **`timeout`** – `{number}` – timeout in milliseconds.
- * **Note:** In contrast to {@link ng.$http#usage $http.config}, {@link ng.$q promises} are - * **not** supported in $resource, because the same value would be used for multiple requests. - * If you are looking for a way to cancel requests, you should use the `cancellable` option. - * - **`cancellable`** – `{boolean}` – if set to true, the request made by a "non-instance" call - * will be cancelled (if not already completed) by calling `$cancelRequest()` on the call's - * return value. Calling `$cancelRequest()` for a non-cancellable or an already - * completed/cancelled request will have no effect.
- * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the - * XHR object. See - * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5) - * for more information. - * - **`responseType`** - `{string}` - see - * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). - * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - - * `response` and `responseError`. Both `response` and `responseError` interceptors get called - * with `http response` object. See {@link ng.$http $http interceptors}. In addition, the - * resource instance or array object is accessible by the `resource` property of the - * `http response` object. - * Keep in mind that the associated promise will be resolved with the value returned by the - * response interceptor, if one is specified. The default response interceptor returns - * `response.resource` (i.e. the resource instance or array). - * - **`hasBody`** - `{boolean}` - allows to specify if a request body should be included or not. - * If not specified only POST, PUT and PATCH requests will have a body. - * - * @param {Object} options Hash with custom settings that should extend the - * default `$resourceProvider` behavior. The supported options are: - * - * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing - * slashes from any calculated URL will be stripped. (Defaults to true.) - * - **`cancellable`** – {boolean} – If true, the request made by a "non-instance" call will be - * cancelled (if not already completed) by calling `$cancelRequest()` on the call's return value. - * This can be overwritten per action. (Defaults to false.) - * - * @returns {Object} A resource "class" object with methods for the default set of resource actions - * optionally extended with custom `actions`. The default set contains these actions: - * ```js - * { 'get': {method:'GET'}, - * 'save': {method:'POST'}, - * 'query': {method:'GET', isArray:true}, - * 'remove': {method:'DELETE'}, - * 'delete': {method:'DELETE'} }; - * ``` - * - * Calling these methods invoke an {@link ng.$http} with the specified http method, - * destination and parameters. When the data is returned from the server then the object is an - * instance of the resource class. The actions `save`, `remove` and `delete` are available on it - * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, - * read, update, delete) on server-side data like this: - * ```js - * var User = $resource('/user/:userId', {userId:'@id'}); - * var user = User.get({userId:123}, function() { - * user.abc = true; - * user.$save(); - * }); - * ``` - * - * It is important to realize that invoking a $resource object method immediately returns an - * empty reference (object or array depending on `isArray`). Once the data is returned from the - * server the existing reference is populated with the actual data. This is a useful trick since - * usually the resource is assigned to a model which is then rendered by the view. Having an empty - * object results in no rendering, once the data arrives from the server then the object is - * populated with the data and the view automatically re-renders itself showing the new data. This - * means that in most cases one never has to write a callback function for the action methods. - * - * The action methods on the class object or instance object can be invoked with the following - * parameters: - * - * - "class" actions without a body: `Resource.action([parameters], [success], [error])` - * - "class" actions with a body: `Resource.action([parameters], postData, [success], [error])` - * - instance actions: `instance.$action([parameters], [success], [error])` - * - * - * When calling instance methods, the instance itself is used as the request body (if the action - * should have a body). By default, only actions using `POST`, `PUT` or `PATCH` have request - * bodies, but you can use the `hasBody` configuration option to specify whether an action - * should have a body or not (regardless of its HTTP method). - * - * - * Success callback is called with (value (Object|Array), responseHeaders (Function), - * status (number), statusText (string)) arguments, where the value is the populated resource - * instance or collection object. The error callback is called with (httpResponse) argument. - * - * Class actions return empty instance (with additional properties below). - * Instance actions return promise of the action. - * - * The Resource instances and collections have these additional properties: - * - * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this - * instance or collection. - * - * On success, the promise is resolved with the same resource instance or collection object, - * updated with data from server. This makes it easy to use in - * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view - * rendering until the resource(s) are loaded. - * - * On failure, the promise is rejected with the {@link ng.$http http response} object. - * - * If an interceptor object was provided, the promise will instead be resolved with the value - * returned by the interceptor. - * - * - `$resolved`: `true` after first server interaction is completed (either with success or - * rejection), `false` before that. Knowing if the Resource has been resolved is useful in - * data-binding. - * - * The Resource instances and collections have these additional methods: - * - * - `$cancelRequest`: If there is a cancellable, pending request related to the instance or - * collection, calling this method will abort the request. - * - * The Resource instances have these additional methods: - * - * - `toJSON`: It returns a simple object without any of the extra properties added as part of - * the Resource API. This object can be serialized through {@link angular.toJson} safely - * without attaching Angular-specific fields. Notice that `JSON.stringify` (and - * `angular.toJson`) automatically use this method when serializing a Resource instance - * (see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON%28%29_behavior)). - * - * @example - * - * ### Credit card resource - * - * ```js - // Define CreditCard class - var CreditCard = $resource('/user/:userId/card/:cardId', - {userId:123, cardId:'@id'}, { - charge: {method:'POST', params:{charge:true}} - }); - - // We can retrieve a collection from the server - var cards = CreditCard.query(function() { - // GET: /user/123/card - // server returns: [ {id:456, number:'1234', name:'Smith'} ]; - - var card = cards[0]; - // each item is an instance of CreditCard - expect(card instanceof CreditCard).toEqual(true); - card.name = "J. Smith"; - // non GET methods are mapped onto the instances - card.$save(); - // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'} - // server returns: {id:456, number:'1234', name: 'J. Smith'}; - - // our custom method is mapped as well. - card.$charge({amount:9.99}); - // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'} - }); - - // we can create an instance as well - var newCard = new CreditCard({number:'0123'}); - newCard.name = "Mike Smith"; - newCard.$save(); - // POST: /user/123/card {number:'0123', name:'Mike Smith'} - // server returns: {id:789, number:'0123', name: 'Mike Smith'}; - expect(newCard.id).toEqual(789); - * ``` - * - * The object returned from this function execution is a resource "class" which has "static" method - * for each action in the definition. - * - * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and - * `headers`. - * - * @example - * - * ### User resource - * - * When the data is returned from the server then the object is an instance of the resource type and - * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD - * operations (create, read, update, delete) on server-side data. - - ```js - var User = $resource('/user/:userId', {userId:'@id'}); - User.get({userId:123}, function(user) { - user.abc = true; - user.$save(); - }); - ``` - * - * It's worth noting that the success callback for `get`, `query` and other methods gets passed - * in the response that came from the server as well as $http header getter function, so one - * could rewrite the above example and get access to http headers as: - * - ```js - var User = $resource('/user/:userId', {userId:'@id'}); - User.get({userId:123}, function(user, getResponseHeaders){ - user.abc = true; - user.$save(function(user, putResponseHeaders) { - //user => saved user object - //putResponseHeaders => $http header getter - }); - }); - ``` - * - * You can also access the raw `$http` promise via the `$promise` property on the object returned - * - ``` - var User = $resource('/user/:userId', {userId:'@id'}); - User.get({userId:123}) - .$promise.then(function(user) { - $scope.user = user; - }); - ``` - * - * @example - * - * ### Creating a custom 'PUT' request - * - * In this example we create a custom method on our resource to make a PUT request - * ```js - * var app = angular.module('app', ['ngResource', 'ngRoute']); - * - * // Some APIs expect a PUT request in the format URL/object/ID - * // Here we are creating an 'update' method - * app.factory('Notes', ['$resource', function($resource) { - * return $resource('/notes/:id', null, - * { - * 'update': { method:'PUT' } - * }); - * }]); - * - * // In our controller we get the ID from the URL using ngRoute and $routeParams - * // We pass in $routeParams and our Notes factory along with $scope - * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes', - function($scope, $routeParams, Notes) { - * // First get a note object from the factory - * var note = Notes.get({ id:$routeParams.id }); - * $id = note.id; - * - * // Now call update passing in the ID first then the object you are updating - * Notes.update({ id:$id }, note); - * - * // This will PUT /notes/ID with the note object in the request payload - * }]); - * ``` - * - * @example - * - * ### Cancelling requests - * - * If an action's configuration specifies that it is cancellable, you can cancel the request related - * to an instance or collection (as long as it is a result of a "non-instance" call): - * - ```js - // ...defining the `Hotel` resource... - var Hotel = $resource('/api/hotel/:id', {id: '@id'}, { - // Let's make the `query()` method cancellable - query: {method: 'get', isArray: true, cancellable: true} - }); - - // ...somewhere in the PlanVacationController... - ... - this.onDestinationChanged = function onDestinationChanged(destination) { - // We don't care about any pending request for hotels - // in a different destination any more - this.availableHotels.$cancelRequest(); - - // Let's query for hotels in '' - // (calls: /api/hotel?location=) - this.availableHotels = Hotel.query({location: destination}); - }; - ``` - * - */ -angular.module('ngResource', ['ng']). - info({ angularVersion: '1.6.7' }). - provider('$resource', function ResourceProvider() { - var PROTOCOL_AND_IPV6_REGEX = /^https?:\/\/\[[^\]]*][^/]*/; - - var provider = this; - - /** - * @ngdoc property - * @name $resourceProvider#defaults - * @description - * Object containing default options used when creating `$resource` instances. - * - * The default values satisfy a wide range of usecases, but you may choose to overwrite any of - * them to further customize your instances. The available properties are: - * - * - **stripTrailingSlashes** – `{boolean}` – If true, then the trailing slashes from any - * calculated URL will be stripped.
- * (Defaults to true.) - * - **cancellable** – `{boolean}` – If true, the request made by a "non-instance" call will be - * cancelled (if not already completed) by calling `$cancelRequest()` on the call's return - * value. For more details, see {@link ngResource.$resource}. This can be overwritten per - * resource class or action.
- * (Defaults to false.) - * - **actions** - `{Object.}` - A hash with default actions declarations. Actions are - * high-level methods corresponding to RESTful actions/methods on resources. An action may - * specify what HTTP method to use, what URL to hit, if the return value will be a single - * object or a collection (array) of objects etc. For more details, see - * {@link ngResource.$resource}. The actions can also be enhanced or overwritten per resource - * class.
- * The default actions are: - * ```js - * { - * get: {method: 'GET'}, - * save: {method: 'POST'}, - * query: {method: 'GET', isArray: true}, - * remove: {method: 'DELETE'}, - * delete: {method: 'DELETE'} - * } - * ``` - * - * #### Example - * - * For example, you can specify a new `update` action that uses the `PUT` HTTP verb: - * - * ```js - * angular. - * module('myApp'). - * config(['$resourceProvider', function ($resourceProvider) { - * $resourceProvider.defaults.actions.update = { - * method: 'PUT' - * }; - * }]); - * ``` - * - * Or you can even overwrite the whole `actions` list and specify your own: - * - * ```js - * angular. - * module('myApp'). - * config(['$resourceProvider', function ($resourceProvider) { - * $resourceProvider.defaults.actions = { - * create: {method: 'POST'}, - * get: {method: 'GET'}, - * getAll: {method: 'GET', isArray:true}, - * update: {method: 'PUT'}, - * delete: {method: 'DELETE'} - * }; - * }); - * ``` - * - */ - this.defaults = { - // Strip slashes by default - stripTrailingSlashes: true, - - // Make non-instance requests cancellable (via `$cancelRequest()`) - cancellable: false, - - // Default actions configuration - actions: { - 'get': {method: 'GET'}, - 'save': {method: 'POST'}, - 'query': {method: 'GET', isArray: true}, - 'remove': {method: 'DELETE'}, - 'delete': {method: 'DELETE'} - } - }; - - this.$get = ['$http', '$log', '$q', '$timeout', function($http, $log, $q, $timeout) { - - var noop = angular.noop, - forEach = angular.forEach, - extend = angular.extend, - copy = angular.copy, - isArray = angular.isArray, - isDefined = angular.isDefined, - isFunction = angular.isFunction, - isNumber = angular.isNumber, - encodeUriQuery = angular.$$encodeUriQuery, - encodeUriSegment = angular.$$encodeUriSegment; - - function Route(template, defaults) { - this.template = template; - this.defaults = extend({}, provider.defaults, defaults); - this.urlParams = {}; - } - - Route.prototype = { - setUrlParams: function(config, params, actionUrl) { - var self = this, - url = actionUrl || self.template, - val, - encodedVal, - protocolAndIpv6 = ''; - - var urlParams = self.urlParams = Object.create(null); - forEach(url.split(/\W/), function(param) { - if (param === 'hasOwnProperty') { - throw $resourceMinErr('badname', 'hasOwnProperty is not a valid parameter name.'); - } - if (!(new RegExp('^\\d+$').test(param)) && param && - (new RegExp('(^|[^\\\\]):' + param + '(\\W|$)').test(url))) { - urlParams[param] = { - isQueryParamValue: (new RegExp('\\?.*=:' + param + '(?:\\W|$)')).test(url) - }; - } - }); - url = url.replace(/\\:/g, ':'); - url = url.replace(PROTOCOL_AND_IPV6_REGEX, function(match) { - protocolAndIpv6 = match; - return ''; - }); - - params = params || {}; - forEach(self.urlParams, function(paramInfo, urlParam) { - val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; - if (isDefined(val) && val !== null) { - if (paramInfo.isQueryParamValue) { - encodedVal = encodeUriQuery(val, true); - } else { - encodedVal = encodeUriSegment(val); - } - url = url.replace(new RegExp(':' + urlParam + '(\\W|$)', 'g'), function(match, p1) { - return encodedVal + p1; - }); - } else { - url = url.replace(new RegExp('(/?):' + urlParam + '(\\W|$)', 'g'), function(match, - leadingSlashes, tail) { - if (tail.charAt(0) === '/') { - return tail; - } else { - return leadingSlashes + tail; - } - }); - } - }); - - // strip trailing slashes and set the url (unless this behavior is specifically disabled) - if (self.defaults.stripTrailingSlashes) { - url = url.replace(/\/+$/, '') || '/'; - } - - // Collapse `/.` if found in the last URL path segment before the query. - // E.g. `http://url.com/id/.format?q=x` becomes `http://url.com/id.format?q=x`. - url = url.replace(/\/\.(?=\w+($|\?))/, '.'); - // Replace escaped `/\.` with `/.`. - // (If `\.` comes from a param value, it will be encoded as `%5C.`.) - config.url = protocolAndIpv6 + url.replace(/\/(\\|%5C)\./, '/.'); - - - // set params - delegate param encoding to $http - forEach(params, function(value, key) { - if (!self.urlParams[key]) { - config.params = config.params || {}; - config.params[key] = value; - } - }); - } - }; - - - function resourceFactory(url, paramDefaults, actions, options) { - var route = new Route(url, options); - - actions = extend({}, provider.defaults.actions, actions); - - function extractParams(data, actionParams) { - var ids = {}; - actionParams = extend({}, paramDefaults, actionParams); - forEach(actionParams, function(value, key) { - if (isFunction(value)) { value = value(data); } - ids[key] = value && value.charAt && value.charAt(0) === '@' ? - lookupDottedPath(data, value.substr(1)) : value; - }); - return ids; - } - - function defaultResponseInterceptor(response) { - return response.resource; - } - - function Resource(value) { - shallowClearAndCopy(value || {}, this); - } - - Resource.prototype.toJSON = function() { - var data = extend({}, this); - delete data.$promise; - delete data.$resolved; - delete data.$cancelRequest; - return data; - }; - - forEach(actions, function(action, name) { - var hasBody = action.hasBody === true || (action.hasBody !== false && /^(POST|PUT|PATCH)$/i.test(action.method)); - var numericTimeout = action.timeout; - var cancellable = isDefined(action.cancellable) ? - action.cancellable : route.defaults.cancellable; - - if (numericTimeout && !isNumber(numericTimeout)) { - $log.debug('ngResource:\n' + - ' Only numeric values are allowed as `timeout`.\n' + - ' Promises are not supported in $resource, because the same value would ' + - 'be used for multiple requests. If you are looking for a way to cancel ' + - 'requests, you should use the `cancellable` option.'); - delete action.timeout; - numericTimeout = null; - } - - Resource[name] = function(a1, a2, a3, a4) { - var params = {}, data, success, error; - - switch (arguments.length) { - case 4: - error = a4; - success = a3; - // falls through - case 3: - case 2: - if (isFunction(a2)) { - if (isFunction(a1)) { - success = a1; - error = a2; - break; - } - - success = a2; - error = a3; - // falls through - } else { - params = a1; - data = a2; - success = a3; - break; - } - // falls through - case 1: - if (isFunction(a1)) success = a1; - else if (hasBody) data = a1; - else params = a1; - break; - case 0: break; - default: - throw $resourceMinErr('badargs', - 'Expected up to 4 arguments [params, data, success, error], got {0} arguments', - arguments.length); - } - - var isInstanceCall = this instanceof Resource; - var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); - var httpConfig = {}; - var responseInterceptor = action.interceptor && action.interceptor.response || - defaultResponseInterceptor; - var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || - undefined; - var hasError = !!error; - var hasResponseErrorInterceptor = !!responseErrorInterceptor; - var timeoutDeferred; - var numericTimeoutPromise; - - forEach(action, function(value, key) { - switch (key) { - default: - httpConfig[key] = copy(value); - break; - case 'params': - case 'isArray': - case 'interceptor': - case 'cancellable': - break; - } - }); - - if (!isInstanceCall && cancellable) { - timeoutDeferred = $q.defer(); - httpConfig.timeout = timeoutDeferred.promise; - - if (numericTimeout) { - numericTimeoutPromise = $timeout(timeoutDeferred.resolve, numericTimeout); - } - } - - if (hasBody) httpConfig.data = data; - route.setUrlParams(httpConfig, - extend({}, extractParams(data, action.params || {}), params), - action.url); - - var promise = $http(httpConfig).then(function(response) { - var data = response.data; - - if (data) { - // Need to convert action.isArray to boolean in case it is undefined - if (isArray(data) !== (!!action.isArray)) { - throw $resourceMinErr('badcfg', - 'Error in resource configuration for action `{0}`. Expected response to ' + - 'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object', - isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url); - } - if (action.isArray) { - value.length = 0; - forEach(data, function(item) { - if (typeof item === 'object') { - value.push(new Resource(item)); - } else { - // Valid JSON values may be string literals, and these should not be converted - // into objects. These items will not have access to the Resource prototype - // methods, but unfortunately there - value.push(item); - } - }); - } else { - var promise = value.$promise; // Save the promise - shallowClearAndCopy(data, value); - value.$promise = promise; // Restore the promise - } - } - response.resource = value; - - return response; - }, function(response) { - response.resource = value; - return $q.reject(response); - }); - - promise = promise['finally'](function() { - value.$resolved = true; - if (!isInstanceCall && cancellable) { - value.$cancelRequest = noop; - $timeout.cancel(numericTimeoutPromise); - timeoutDeferred = numericTimeoutPromise = httpConfig.timeout = null; - } - }); - - promise = promise.then( - function(response) { - var value = responseInterceptor(response); - (success || noop)(value, response.headers, response.status, response.statusText); - return value; - }, - (hasError || hasResponseErrorInterceptor) ? - function(response) { - if (hasError && !hasResponseErrorInterceptor) { - // Avoid `Possibly Unhandled Rejection` error, - // but still fulfill the returned promise with a rejection - promise.catch(noop); - } - if (hasError) error(response); - return hasResponseErrorInterceptor ? - responseErrorInterceptor(response) : - $q.reject(response); - } : - undefined); - - if (!isInstanceCall) { - // we are creating instance / collection - // - set the initial promise - // - return the instance / collection - value.$promise = promise; - value.$resolved = false; - if (cancellable) value.$cancelRequest = cancelRequest; - - return value; - } - - // instance call - return promise; - - function cancelRequest(value) { - promise.catch(noop); - if (timeoutDeferred !== null) { - timeoutDeferred.resolve(value); - } - } - }; - - - Resource.prototype['$' + name] = function(params, success, error) { - if (isFunction(params)) { - error = success; success = params; params = {}; - } - var result = Resource[name].call(this, params, this, success, error); - return result.$promise || result; - }; - }); - - return Resource; - } - - return resourceFactory; - }]; - }); - - })(window, window.angular); (function() { @@ -12544,619 +12586,1637 @@ angular.module('ui.bootstrap.datepickerPopup').run(function() {!angular.$$csp(). angular.module('ui.bootstrap.tooltip').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTooltipCss && angular.element(document).find('head').prepend(''); angular.$$uibTooltipCss = true; }); angular.module('ui.bootstrap.timepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTimepickerCss && angular.element(document).find('head').prepend(''); angular.$$uibTimepickerCss = true; }); angular.module('ui.bootstrap.typeahead').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTypeaheadCss && angular.element(document).find('head').prepend(''); angular.$$uibTypeaheadCss = true; }); -(function(window, angular, undefined) {'use strict'; +(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(); + } +} /** - * @ngdoc overview - * @name angulartics.google.analytics - * Enables analytics support for Google Analytics (http://google.com/analytics) + * 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. */ -angular.module('angulartics.google.analytics', ['angulartics']) -.config(['$analyticsProvider', function ($analyticsProvider) { +module.exports = asap; +function asap(task) { + var rawTask; + if (freeTasks.length) { + rawTask = freeTasks.pop(); + } else { + rawTask = new RawTask(); + } + rawTask.task = task; + rawAsap(rawTask); +} - // GA already supports buffered invocations so we don't need - // to wrap these inside angulartics.waitForVendorApi - $analyticsProvider.settings.pageTracking.trackRelativePath = true; - - // Set the default settings for this module - $analyticsProvider.settings.ga = { - additionalAccountNames: undefined, - // Select hits to send to all additional accounts - additionalAccountHitTypes: { - pageview: true, - event: true, - exception: false, - ecommerce: false, - userTiming: false, - setUserProperties: false, - userId: false +// 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); }, - disableEventTracking: null, - disablePageTracking: null, - enhancedEcommerce: false, - // GA supports transporting data via gif requests, XHRs, or sendBeacons - // @link https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#transport - transport: null, - userId: null + + 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; }; - /** - * Track Pageview in GA - * @name pageTrack - * - * @param {string} path value of Page dimension stored with hit e.g. '/home' - * @param {object} properties Object with optional addtional Custom Dimensions/Metrics - * - * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/pages - * @link https://developers.google.com/analytics/devguides/collection/gajs/ - */ - $analyticsProvider.registerPageTrack(function (path, properties) { - - properties = properties || {}; + api.globalCount = function globalCount() { + // We want to cache the results of this + var cache = (function calc() { + var i, + count = 0; - // Do nothing if page tracking is disabled - if ($analyticsProvider.settings.ga.disablePageTracking) return; + for (i in window) { + count++; + } - dispatchToGa('pageview', 'send', angular.extend({}, properties, { - hitType: 'pageview', - page: path - })); + return count; + }()); - }); + api.globalCount = function () { return cache; }; + return cache; + }; - /** - * Track Event in GA - * @name eventTrack - * - * @param {string} action Required 'action' (string) associated with the event - * @param {object} properties Comprised of the mandatory field 'category' (string) and optional fields 'label' (string), 'value' (integer) and 'nonInteraction' (boolean) - * - * @link https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide#SettingUpEventTracking - * - * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/events - */ - $analyticsProvider.registerEventTrack(function(action, properties) { - - // Do nothing if event tracking is disabled - if ($analyticsProvider.settings.ga.disableEventTracking) return; - - if (!action && action + '' !== '0') { - return; - } - - // Sets default properties - properties = properties || {}; - properties.category = properties.category || 'Event'; - - // GA requires that eventValue be an integer, see: - // https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#eventValue - // https://github.com/luisfarzati/angulartics/issues/81 - if (properties.value) { - var parsed = parseInt(properties.value, 10); - properties.value = isNaN(parsed) ? 0 : parsed; - } - - // GA requires that hitCallback be an function, see: - // https://developers.google.com/analytics/devguides/collection/analyticsjs/sending-hits#hitcallback - if (!angular.isFunction(properties.hitCallback)) { - properties.hitCallback = null; - } - - // Making nonInteraction parameter more intuitive, includes backwards compatibilty - // https://github.com/angulartics/angulartics-google-analytics/issues/49 - properties.nonInteraction = properties.nonInteraction || properties.noninteraction; - - dispatchToGa('event', 'send', angular.extend({}, properties, { - hitType: 'event', - eventCategory: properties.category, - eventAction: action, - eventLabel: properties.label, - eventValue: properties.value, - nonInteraction: properties.nonInteraction, - page: properties.page || window.location.hash.substring(1) || window.location.pathname, - hitCallback: properties.hitCallback, - })); - - }); - - /** - * Exception Track Event in GA - * @name exceptionTrack - * Sugar on top of the eventTrack method for easily handling errors - * - * @param {object} error An Error object to track: error.toString() used for event 'action', error.stack used for event 'label'. - * @param {object} cause The cause of the error given from $exceptionHandler, not used. - * - * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/events - */ - $analyticsProvider.registerExceptionTrack(function (error, cause) { - dispatchToGa('exception', 'send', { - hitType: 'event', - eventCategory: 'Exceptions', - eventAction: error.toString(), - eventLabel: error.stack, - nonInteraction: true, - page: window.location.hash.substring(1) || window.location.pathname, - isException: true - }); - }); - - /** - * Set Username - * @name setUsername - * - * @param {string} userId Registers User ID of user for use with other hits - * - * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/cookies-user-id#user_id - */ - $analyticsProvider.registerSetUsername(function (userId) { - $analyticsProvider.settings.ga.userId = userId; - }); - - /** - * Set User Properties - * @name setUserProperties - * - * @param {object} properties Sets all properties with dimensionN or metricN to their respective values - * - * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#customs - */ - $analyticsProvider.registerSetUserProperties(function (properties) { - - if (properties) { - dispatchToGa('setUserProperties', 'set', dimensionsAndMetrics(properties)); - } - - }); - - /** - * User Timings Event in GA - * @name userTimings - * - * @param {object} properties Comprised of the mandatory fields: - * 'timingCategory' (string), - * 'timingVar' (string), - * 'timingValue' (number) - * Properties can also have the optional fields: - * 'timingLabel' (string) - * 'optSampleRate' (number) Classic Analytics only - determines % of users to collect data from, handled serverside by UA - * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/user-timings#sampling_considerations - * - * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/user-timings - */ - $analyticsProvider.registerUserTimings(function (properties) { - - if (!angular.isObject(properties) || angular.isArray(properties)) { - return console.log('Required argument properties is missing or not an object'); - } - - angular.forEach(['timingCategory', 'timingVar', 'timingValue'], function(prop) { - if (angular.isUndefined(properties[prop])) { - return console.log('Argument properties missing required property ' + prop); - } - }); - - dispatchToGa('userTiming', 'send', { - hitType: 'timing', - timingCategory: properties.timingCategory, - timingVar: properties.timingVar, - timingValue: properties.timingValue, - timingLabel: properties.timingLabel, - optSampleRate: properties.optSampleRate, // Classic Analytics only - page: properties.page || window.location.hash.substring(1) || window.location.pathname, - }); - - }); - - /** - * Ecommerce Tracking in GA - * @name transactionTrack - * - * @param {object} transaction comprised of following fields: - * 'id': 'T12345', // Transaction ID. Required for purchases and refunds. - * 'affiliation': 'Online Store', - * 'revenue': '35.43', // Total transaction value (incl. tax and shipping) - * 'tax':'4.90', - * 'shipping': '5.99', - * 'coupon': 'SUMMER_SALE', // Enhanced Ecommerce Only - * 'dimension1': 'Card ID #1234', // Hit, Session, or User-level Custom Dimension(s) - * 'metric1': 1, // Custom Metric(s) - * 'currencyCode': 'EUR', // Currency Code to track the transaction with. Recognized codes: https://support.google.com/analytics/answer/6205902?hl=en#supported-currencies - * 'billingCity': 'San Francisco', // Classic Analytics only - * 'billingRegion': 'California', // Classic Analytics only - * 'billingCountry': 'USA', // Classic Analytics only - * 'products': [{ // List of products - * 'name': 'Triblend Android T-Shirt', // Name or ID is required. - * 'id': '12345', // Product SKU - * 'price': '15.25', - * 'brand': 'Google', // Enhanced Ecommerce only - * 'category': 'Apparel', - * 'variant': 'Gray', // Enhanced Ecommerce only - * 'quantity': 1, - * 'coupon': '', // Enhanced Ecommerce only. - * 'currencyCode': 'BRL', // Product-level currency code, Enhanced Ecommerce only - * 'dimension2': 'Clearance', // Product-level Custom Dimension - * 'metric2': 1 // Product-level Custom Metric - * }, - * ... - * ] - * - * @param {object] properties comprised of custom dimensions and metrics to - * send with the transaction hit - * Utilizes traditional ecommerce tracking by default. To used Enhanced Ecommerce, - * set the $analytics.settings.ga.enhancedEcommerce flag to true - * - * Docs on traditional ecommerce (UA): - * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce - * - * Docs on Enhanced Ecommerce - * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce - * - * Docs on Classic Ecommerce (_gaq) - * @link https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingEcommerce - **/ - $analyticsProvider.registerTransactionTrack(function(transaction) { - - var product; - var i; - - // Universal Analytics splits off the ecommerce code into a separate - // library we must include by using the "require" command - dispatchToGa('ecommerce', 'require', 'ecommerce'); - dispatchToGa('ecommerce', 'ecommerce:addTransaction', transaction); - - if (transaction.products) { - for (i = 0; i < transaction.products.length; i++) { - - product = transaction.products[i]; - - // GA uses a SKU property and stores the transaction ID in the ID Field - product.sku = product.id; - product.id = transaction.id; - - dispatchToGa('ecommerce', 'ecommerce:addItem', transaction.products[i]); - - } - } - - if (transaction.currencyCode) { - - dispatchToGa('ecommerce', '_set', transaction.currencyCode); // Classic Anayltics only - UA uses fieldsObj.currencyCode instead - - } - - dispatchToGa('ecommerce', 'ecommerce:send', angular.copy(transaction)); - - }); - - /** - * Detects if Universal Analytics is installed - * - * @name detectUniversalAnalytics - */ - function detectUniversalAnalytics() { - - // Use the GoogleAnalyticsObject property set by the default GA snippet - // to correctly determine the namespace of the GA global - var gaNamespace = window.GoogleAnalyticsObject; - return gaNamespace && window[gaNamespace]; + 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; } - /** - * Detects if Classic Analytics is installed - * - * @name detectClassicAnalytics - */ - function detectClassicAnalytics() { +}(this.applitude || this)); - // If _gaq is undefined, we're trusting Classic Analytics to be there - return !angular.isUndefined(window._gaq); +},{}],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) + } } - /** - * Extract Custom Data for a hit - * @name dimensionsAndMetrics - * - * @param {object} properties properties object from an API call that is filtered for Custom Dimensions & Metrics - * - * @returns {object} customData object with only Custom Dimensions/Metrics from properties argument - */ - function dimensionsAndMetrics(properties) { - // add custom dimensions and metrics - var customData = {}; - var key; - - for (key in properties) { - // Keys must be dimensionXX or metricXX, e.g. dimension1, metric155, so - // if those strings aren't at zero (which evaluates to falsey), ignore - // the key - if (!key.indexOf('dimension') || !key.indexOf('metric')) { - customData[key] = properties[key]; + 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 customData; } - /** - * Handler for hits to GA. Dynamically adjusts syntax for - * targeted version based on global detection. - * - * @name dispatchToGa - * - * @param {string} method Name of angulartics method for checking if hits should be duplicated - * @param {string} command Standard Universal Analytics command (create, send, set) - * @param {object} fieldsObj object with hit-specific fields. Fields are whitelisted in handler - non-supported fields are ignored. - * - */ - var dispatchToGa = (function() { + return listeners +} - var handler; +},{"assert-function":12}],19:[function(_dereq_,module,exports){ +(function (global){ +var win; - if (detectClassicAnalytics()) { - handler = dispatchToClassic_; +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) - if (detectUniversalAnalytics()) { - handler = dispatchToUniversal_; - } + return replacer == null ? value : replacer.call(this, key, value) + } +} - // If neither has been detected, GA is not above the angular code - if (!handler) { - return angular.noop; - } +},{}],25:[function(_dereq_,module,exports){ +'use strict' - return function(method, command, fieldsObj) { +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') - var shouldCopyHit = $analyticsProvider.settings.ga.additionalAccountHitTypes[method]; - handler(command, fieldsObj, shouldCopyHit); +module.exports = Lazy - } +function Lazy (methods, load) { + assert(Array.isArray(methods), 'methods are required') + assertEqual(typeof load, 'function', 'load fn is required') - /** - * Dispatches a hit using Universal syntax - * - * @name dispatchToUniversal_ - * @private - * - * @param {string} command Standard Universal Analytics command (create, send, set) - * @param {object} fieldsObj object with hit-specific fields. Fields are whitelisted in handler - non-supported fields are ignored. - * @param {boolean} shouldCopyHit should hit be propogated to all trackers - */ - function dispatchToUniversal_(command, fieldsObj, shouldCopyHit) { + var api = null + var error = null + var queue = [] - var userId = $analyticsProvider.settings.ga.userId; - var uaCommand, - pluginName; + load(function (err, lib) { + error = err + api = lib + all(queue)(err, lib) + queue = null + }) - if (command === 'require' && fieldsObj === 'ecommerce') { - - pluginName = fieldsObj; - - if ($analyticsProvider.settings.ga.enhancedEcommerce) { - - pluginName = 'ec'; + 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) } - - // Exit here - require calls don't have fieldsObjs - return applyUniversalCall_([command, pluginName], shouldCopyHit); + }) + } + } - } + function onReady (callback) { + callback = dezalgo(callback) - // If our User ID is set, set it on the hit - if (userId && angular.isObject(fieldsObj)) fieldsObj.userId = userId; - // If a transport preference is specified, set it on the hit - if ($analyticsProvider.settings.ga.transport) { + if (api || error) return callback(error, api) + queue.push(callback) + } +} - fieldsObj.transport = $analyticsProvider.settings.ga.transport; +},{"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; + } - if (command.indexOf('ecommerce:') > -1 && $analyticsProvider.settings.ga.enhancedEcommerce) { + var pathArr = getPathSegments(path); - switch (command) { - case 'ecommerce:addTransaction': - command = ['ec:setAction', 'purchase']; - break; - case 'ecommerce:addItem': - command = 'ec:addProduct'; - // Enhanced Ecommerce reverts to using the ID property for the SKU, - // so we swap them back here - fieldsObj.id = fieldsObj.sku; - break; - case 'ecommerce:send': - command = 'send'; - fieldsObj.hitType = 'event'; - fieldsObj.eventCategory = 'Angulartics Enhanced Ecommerce'; - fieldsObj.eventAction = 'Purchase'; - fieldsObj.nonInteraction = true; - break; + 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]; } - } - - - uaCommand = command instanceof Array ? command.concat(fieldsObj) : [command, fieldsObj]; - - applyUniversalCall_(uaCommand, shouldCopyHit); - + res += parts[i].substring(1); } - /** - * Handles applying a constructed call to the global Universal GA object - * This exists primarily so calls within dispatchToUa_ can short circuit - * out of the function to handle specific edge cases, e.g. require commands - * @name applyUniversalCall_ - * @private - * - * @param commandArray {array} command to be .apply()'d - * @param shouldCopyHit {boolean} should the command be applied to all accts - */ - function applyUniversalCall_(commandArray, shouldCopyHit) { + return res; +}; - var userId = $analyticsProvider.settings.ga.userId; - var gaNamespace = window.GoogleAnalyticsObject; - var commandClone; +},{}],30:[function(_dereq_,module,exports){ +'use strict' - // Perform our initial call - window[gaNamespace].apply(this, commandArray); +var isObject = _dereq_('isobject') +var safeStringify = _dereq_('json-stringify-safe') - if (shouldCopyHit) { +module.exports = function print (value) { + var toString = isJson(value) ? stringify : String + return toString(value) +} - commandClone = angular.copy(commandArray); +function isJson (value) { + return isObject(value) || Array.isArray(value) +} - // If the userId shouldn't be duplicated, remove from the fieldsObj - if (userId && !$analyticsProvider.settings.ga.additionalAccountHitTypes.userId) { - - if (commandClone[2] && typeof commandClone[2] === 'object') { +function stringify (value) { + return safeStringify(value, null, '') +} - delete commandClone[2].userId; +},{"isobject":23,"json-stringify-safe":24}],31:[function(_dereq_,module,exports){ +!function(glob) { - } - - } - - angular.forEach($analyticsProvider.settings.ga.additionalAccountNames, function (accountName){ - - commandClone[0] = accountName + '.' + commandClone[0]; - - window[gaNamespace].apply(this, commandClone); - - }); + 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; + }; - } - - /** - * Dispatches a hit using Classic syntax - * Translates Universal Syntax to Classic syntax - * - * @name dispatchToClassic_ - * @private - * - * @param {string} command Standard Universal Analytics command (create, send, set) - * @param {object} fieldsObj object with hit-specific fields. Fields are whitelisted in handler - non-supported fields are ignored. - * @param {boolean} shouldCopyHit should hit be propogated to all trackers - */ - function dispatchToClassic_(command, fieldsObj, shouldCopyHit) { - - if (command === 'set') { - return console.log('Classic Analytics does not support the "set" command or Custom Dimensions. Command ignored.'); + 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; } - - var classicCommand; - - // Transpose our syntax from Universal Analytics to Classic Analytics - // Currently we only support 'send' style commands - if (command === 'send') { - - switch(fieldsObj.hitType) { - case 'pageview': - classicCommand = ['_trackPageview', fieldsObj.page]; - break; - case 'event': - classicCommand = [ - '_trackEvent', - fieldsObj.category, - fieldsObj.action, - fieldsObj.label, - fieldsObj.value, - fieldsObj.nonInteraction - ]; - break; - case 'timing': - classicCommand = [ - '_trackTiming', - fieldsObj.timingCategory, - fieldsObj.timingVar, - fieldsObj.timingValue, - fieldsObj.timingLabel, - fieldsObj.optSampleRate - ]; - break; - } - - } - - if (command === 'ecommerce:addTransaction') { - - classicCommand = [ - '_addTrans', - fieldsObj.id, - fieldsObj.affiliation, - fieldsObj.revenue, - fieldsObj.tax, - fieldsObj.shipping, - fieldsObj.billingCity, - fieldsObj.billingRegion, - fieldsObj.billingCountry - ]; - - } - - if (command === 'ecommerce:addItem') { - - classicCommand = [ - '_addItem', - fieldsObj.id, - fieldsObj.sku, - fieldsObj.name, - fieldsObj.category, - fieldsObj.price, - fieldsObj.quantity - ]; - - } - - if (command === '_set') { - - classicCommand = [ - '_set', - 'currencyCode', - fieldsObj - ]; - - } - - if (command === 'ecommerce:send') { - - classicCommand = [ - '_trackTrans' - ]; - - } - - if (!classicCommand) { - return console.log('Unable to find command ' + command + ' or fieldsObj missing required properties. Command ignored.'); - } - - // Issue our command to GA - window._gaq.push(classicCommand); - - if (shouldCopyHit) { - - angular.forEach($analyticsProvider.settings.ga.additionalAccountNames, function (accountName){ - - var classicCommandClone = [].slice.call(classicCommand); - // Namespace the command as required - classicCommandClone[0] = accountName + '.' + classicCommandClone[0]; - - window._gaq.push(classicCommandClone); - + 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 + }; + }; -}]); -})(window, window.angular); + // 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 { + // + diff --git a/version.json b/version.json index a50fcf23..cf9b178a 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "1.24.2" + "version": "1.25.0" } \ No newline at end of file