mixpanel-browser 2.40.1 → 2.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/doc/build-docs.js CHANGED
@@ -97,6 +97,22 @@ function doxToMD(items) {
97
97
  });
98
98
  }
99
99
 
100
+ // Captures prototype bracket notation property assignment, e.g.:
101
+ // `MixpanelGroup.prototype['delete'] = addOptOutCheckMixpanelGroup(function(callback) {`
102
+ // Based on https://github.com/tj/dox/blob/9fe92e17dfcd31c9b6512f6e5bf0b52c2b6b84d4/lib/dox.js#L592
103
+ dox.contextPatternMatchers.push(function (str) {
104
+ if (/^\s*([\w$.]+)\s*\.\s*prototype\s*\['\s*([\w$]+)'\]\s*=\s*([^\n;]+)/.exec(str)) {
105
+ return {
106
+ type: 'property'
107
+ , constructor: RegExp.$1
108
+ , cons: RegExp.$1
109
+ , name: RegExp.$2
110
+ , value: RegExp.$3.trim()
111
+ , string: RegExp.$1 + '.prototype.' + RegExp.$2
112
+ };
113
+ }
114
+ });
115
+
100
116
  const rawCode = fs.readFileSync(SOURCE_FILE).toString().trim();
101
117
  const parsed = dox.parseComments(rawCode);
102
118
 
@@ -990,6 +990,24 @@ mixpanel.people.unset(['gender', 'Company']);
990
990
  # mixpanel.group
991
991
 
992
992
 
993
+ ___
994
+ ## mixpanel.group.delete
995
+ Permanently delete a group.
996
+
997
+
998
+ ### Usage:
999
+
1000
+ ```javascript
1001
+ mixpanel.get_group('company', 'mixpanel').delete();
1002
+ ```
1003
+
1004
+
1005
+
1006
+ | Argument | Type | Description |
1007
+ | ------------- | ------------- | ----- |
1008
+ | **callback** | <span class="mp-arg-type">Function</span></br></span><span class="mp-arg-optional">optional</span> | If provided, the callback will be called after the tracking event |
1009
+
1010
+
993
1011
  ___
994
1012
  ## mixpanel.group.remove
995
1013
  Remove a property from a group. The value will be ignored if doesn't exist.
@@ -9,25 +9,6 @@ var MIXPANEL_LIB_URL = '//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js';
9
9
  (function(document, mixpanel) {
10
10
  // Only stub out if this is the first time running the snippet.
11
11
  if (!mixpanel['__SV']) {
12
- var win = window;
13
-
14
- // grab the hash params for ce editor immediately in case
15
- // host website changes hash after init
16
- try {
17
- var getHashParam, matches, state, loc = win.location, hash = loc.hash;
18
- getHashParam = function(hash, param) {
19
- matches = hash.match(new RegExp(param + '=([^&]*)'));
20
- return matches ? matches[1] : null;
21
- };
22
- if (hash && getHashParam(hash, 'state')) {
23
- state = JSON.parse(decodeURIComponent(getHashParam(hash, 'state')));
24
- if (state['action'] === 'mpeditor') {
25
- win.sessionStorage.setItem('_mpcehash', hash);
26
- history.replaceState(state['desiredHash'] || '', document.title, loc.pathname + loc.search); // remove ce editor hash
27
- }
28
- }
29
- } catch (e) {}
30
-
31
12
  var script, first_script, gen_fn, functions, i, lib_name = "mixpanel";
32
13
  window[lib_name] = mixpanel;
33
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixpanel-browser",
3
- "version": "2.40.1",
3
+ "version": "2.43.0",
4
4
  "description": "The official Mixpanel JavaScript browser client library",
5
5
  "main": "dist/mixpanel.cjs.js",
6
6
  "directories": {
@@ -46,10 +46,10 @@
46
46
  "jsdom": "11.12.0",
47
47
  "jsdom-global": "3.0.2",
48
48
  "localStorage": "1.0.4",
49
- "lodash": "4.17.19",
49
+ "lodash": "4.17.21",
50
50
  "mocha": "7.1.1",
51
51
  "morgan": "1.9.1",
52
- "rdme": "3.0.0",
52
+ "rdme": "4.0.0",
53
53
  "request": "2.88.0",
54
54
  "rollup": "0.25.8",
55
55
  "rollup-plugin-npm": "1.4.0",
package/src/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  var Config = {
2
2
  DEBUG: false,
3
- LIB_VERSION: '2.40.1'
3
+ LIB_VERSION: '2.43.0'
4
4
  };
5
5
 
6
6
  export default Config;
@@ -1,7 +1,6 @@
1
1
  /* eslint camelcase: "off" */
2
2
  import Config from './config';
3
- import { _, console, userAgent, window, document, navigator, determine_eligibility, slice } from './utils';
4
- import { autotrack } from './autotrack';
3
+ import { _, console, userAgent, window, document, navigator, slice } from './utils';
5
4
  import { FormTracker, LinkTracker } from './dom-trackers';
6
5
  import { RequestBatcher } from './request-batcher';
7
6
  import { MixpanelGroup } from './mixpanel-group';
@@ -57,6 +56,8 @@ var IDENTITY_FUNC = function(x) {return x;};
57
56
  var NOOP_FUNC = function() {};
58
57
 
59
58
  /** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel';
59
+ /** @const */ var PAYLOAD_TYPE_BASE64 = 'base64';
60
+ /** @const */ var PAYLOAD_TYPE_JSON = 'json';
60
61
 
61
62
 
62
63
  /*
@@ -87,8 +88,8 @@ var DEFAULT_CONFIG = {
87
88
  'api_host': 'https://api-js.mixpanel.com',
88
89
  'api_method': 'POST',
89
90
  'api_transport': 'XHR',
91
+ 'api_payload_format': PAYLOAD_TYPE_BASE64,
90
92
  'app_host': 'https://mixpanel.com',
91
- 'autotrack': true,
92
93
  'cdn': 'https://cdn.mxpnl.com',
93
94
  'cross_site_cookie': false,
94
95
  'cross_subdomain_cookie': true,
@@ -119,7 +120,7 @@ var DEFAULT_CONFIG = {
119
120
  'inapp_protocol': '//',
120
121
  'inapp_link_new_window': false,
121
122
  'ignore_dnt': false,
122
- 'batch_requests': false, // for now
123
+ 'batch_requests': true,
123
124
  'batch_size': 50,
124
125
  'batch_flush_interval_ms': 5000,
125
126
  'batch_request_timeout_ms': 90000,
@@ -171,21 +172,6 @@ var create_mplib = function(token, config, name) {
171
172
  // global debug to be true
172
173
  Config.DEBUG = Config.DEBUG || instance.get_config('debug');
173
174
 
174
- instance['__autotrack_enabled'] = instance.get_config('autotrack');
175
- if (instance.get_config('autotrack')) {
176
- var num_buckets = 100;
177
- var num_enabled_buckets = 100;
178
- if (!autotrack.enabledForProject(instance.get_config('token'), num_buckets, num_enabled_buckets)) {
179
- instance['__autotrack_enabled'] = false;
180
- console.log('Not in active bucket: disabling Automatic Event Collection.');
181
- } else if (!autotrack.isBrowserSupported()) {
182
- instance['__autotrack_enabled'] = false;
183
- console.log('Disabling Automatic Event Collection because this browser is not supported');
184
- } else {
185
- autotrack.init(instance);
186
- }
187
- }
188
-
189
175
  // if target is not defined, we called init after the lib already
190
176
  // loaded, so there won't be an array of things to execute
191
177
  if (!_.isUndefined(target) && _.isArray(target)) {
@@ -198,12 +184,6 @@ var create_mplib = function(token, config, name) {
198
184
  return instance;
199
185
  };
200
186
 
201
- var encode_data_for_request = function(data) {
202
- var json_data = _.JSONEncode(data);
203
- var encoded_data = _.base64Encode(json_data);
204
- return {'data': encoded_data};
205
- };
206
-
207
187
  // Initialization methods
208
188
 
209
189
  /**
@@ -253,14 +233,14 @@ MixpanelLib.prototype._init = function(token, config, name) {
253
233
  this['config'] = {};
254
234
  this['_triggered_notifs'] = [];
255
235
 
256
- // rollout: enable batch_requests by default for 60% of projects
257
- // (only if they have not specified a value in their init config
258
- // and they aren't using a custom API host)
259
236
  var variable_features = {};
260
- var api_host = config['api_host'];
261
- var is_custom_api = !!api_host && !api_host.match(/\.mixpanel\.com$/);
262
- if (!('batch_requests' in config) && !is_custom_api && determine_eligibility(token, 'batch', 60)) {
263
- variable_features['batch_requests'] = true;
237
+
238
+ // default to JSON payload for standard mixpanel.com API hosts
239
+ if (!('api_payload_format' in config)) {
240
+ var api_host = config['api_host'] || DEFAULT_CONFIG['api_host'];
241
+ if (api_host.match(/\.mixpanel\.com$/)) {
242
+ variable_features['api_payload_format'] = PAYLOAD_TYPE_JSON;
243
+ }
264
244
  }
265
245
 
266
246
  this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, {
@@ -289,15 +269,32 @@ MixpanelLib.prototype._init = function(token, config, name) {
289
269
  } else {
290
270
  this.init_batchers();
291
271
  if (sendBeacon && window.addEventListener) {
292
- window.addEventListener('unload', _.bind(function() {
293
- // Before page closes, attempt to flush any events queued up via navigator.sendBeacon.
294
- // Since sendBeacon doesn't report success/failure, events will not be removed from
295
- // the persistent store; if the site is loaded again, the events will be flushed again
296
- // on startup and deduplicated on the Mixpanel server side.
272
+ // Before page closes or hides (user tabs away etc), attempt to flush any events
273
+ // queued up via navigator.sendBeacon. Since sendBeacon doesn't report success/failure,
274
+ // events will not be removed from the persistent store; if the site is loaded again,
275
+ // the events will be flushed again on startup and deduplicated on the Mixpanel server
276
+ // side.
277
+ // There is no reliable way to capture only page close events, so we lean on the
278
+ // visibilitychange and pagehide events as recommended at
279
+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event#usage_notes.
280
+ // These events fire when the user clicks away from the current page/tab, so will occur
281
+ // more frequently than page unload, but are the only mechanism currently for capturing
282
+ // this scenario somewhat reliably.
283
+ var flush_on_unload = _.bind(function() {
297
284
  if (!this.request_batchers.events.stopped) {
298
285
  this.request_batchers.events.flush({unloading: true});
299
286
  }
300
- }, this));
287
+ }, this);
288
+ window.addEventListener('pagehide', function(ev) {
289
+ if (ev['persisted']) {
290
+ flush_on_unload();
291
+ }
292
+ });
293
+ window.addEventListener('visibilitychange', function() {
294
+ if (document['visibilityState'] === 'hidden') {
295
+ flush_on_unload();
296
+ }
297
+ });
301
298
  }
302
299
  }
303
300
  }
@@ -622,7 +619,7 @@ MixpanelLib.prototype.init_batchers = function() {
622
619
  sendRequestFunc: _.bind(function(data, options, cb) {
623
620
  this._send_request(
624
621
  this.get_config('api_host') + attrs.endpoint,
625
- encode_data_for_request(data),
622
+ this._encode_data_for_request(data),
626
623
  options,
627
624
  this._prepare_callback(cb, data)
628
625
  );
@@ -696,6 +693,14 @@ MixpanelLib.prototype.disable = function(events) {
696
693
  }
697
694
  };
698
695
 
696
+ MixpanelLib.prototype._encode_data_for_request = function(data) {
697
+ var encoded_data = _.JSONEncode(data);
698
+ if (this.get_config('api_payload_format') === PAYLOAD_TYPE_BASE64) {
699
+ encoded_data = _.base64Encode(encoded_data);
700
+ }
701
+ return {'data': encoded_data};
702
+ };
703
+
699
704
  // internal method for handling track vs batch-enqueue logic
700
705
  MixpanelLib.prototype._track_or_batch = function(options, callback) {
701
706
  var truncated_data = _.truncate(options.data, 255);
@@ -715,7 +720,7 @@ MixpanelLib.prototype._track_or_batch = function(options, callback) {
715
720
  console.log(truncated_data);
716
721
  return this._send_request(
717
722
  endpoint,
718
- encode_data_for_request(truncated_data),
723
+ this._encode_data_for_request(truncated_data),
719
724
  send_request_options,
720
725
  this._prepare_callback(callback, truncated_data)
721
726
  );
@@ -110,9 +110,13 @@ MixpanelGroup.prototype.union = addOptOutCheckMixpanelGroup(function(list_name,
110
110
  * Permanently delete a group.
111
111
  *
112
112
  * ### Usage:
113
+ *
113
114
  * mixpanel.get_group('company', 'mixpanel').delete();
115
+ *
116
+ * @param {Function} [callback] If provided, the callback will be called after the tracking event
114
117
  */
115
118
  MixpanelGroup.prototype['delete'] = addOptOutCheckMixpanelGroup(function(callback) {
119
+ // bracket notation above prevents a minification error related to reserved words
116
120
  var data = this.delete_action();
117
121
  return this._send_request(data, callback);
118
122
  });
@@ -148,9 +148,9 @@ RequestBatcher.prototype.flush = function(options) {
148
148
  } else if (
149
149
  _.isObject(res) &&
150
150
  res.xhr_req &&
151
- (res.xhr_req['status'] >= 500 || res.xhr_req['status'] <= 0)
151
+ (res.xhr_req['status'] >= 500 || res.xhr_req['status'] === 429 || res.error === 'timeout')
152
152
  ) {
153
- // network or API error, retry
153
+ // network or API error, or 429 Too Many Requests, retry
154
154
  var retryMS = this.flushInterval * 2;
155
155
  var headers = res.xhr_req['responseHeaders'];
156
156
  if (headers) {
package/src/utils.js CHANGED
@@ -279,10 +279,6 @@ _.values = function(obj) {
279
279
  return results;
280
280
  };
281
281
 
282
- _.identity = function(value) {
283
- return value;
284
- };
285
-
286
282
  _.include = function(obj, target) {
287
283
  var found = false;
288
284
  if (obj === null) {
@@ -946,10 +942,12 @@ _.UUID = (function() {
946
942
  // This is to block various web spiders from executing our JS and
947
943
  // sending false tracking data
948
944
  var BLOCKED_UA_STRS = [
945
+ 'ahrefsbot',
949
946
  'baiduspider',
950
947
  'bingbot',
951
948
  'bingpreview',
952
949
  'facebookexternal',
950
+ 'petalbot',
953
951
  'pinterest',
954
952
  'screaming frog',
955
953
  'yahoo! slurp',
@@ -1020,10 +1018,6 @@ _.getQueryParam = function(url, param) {
1020
1018
  }
1021
1019
  };
1022
1020
 
1023
- _.getHashParam = function(hash, param) {
1024
- var matches = hash.match(new RegExp(param + '=([^&]*)'));
1025
- return matches ? matches[1] : null;
1026
- };
1027
1021
 
1028
1022
  // _.cookie
1029
1023
  // Methods partially borrowed from quirksmode.org/js/cookies.html
@@ -1690,28 +1684,6 @@ var cheap_guid = function(maxlen) {
1690
1684
  return maxlen ? guid.substring(0, maxlen) : guid;
1691
1685
  };
1692
1686
 
1693
- /**
1694
- * Check deterministically whether to include or exclude from a feature rollout/test based on the
1695
- * given string and the desired percentage to include.
1696
- * @param {String} str - string to run the check against (for instance a project's token)
1697
- * @param {String} feature - name of feature (for inclusion in hash, to ensure different results
1698
- * for different features)
1699
- * @param {Number} percent_allowed - percentage chance that a given string will be included
1700
- * @returns {Boolean} whether the given string should be included
1701
- */
1702
- var determine_eligibility = _.safewrap(function(str, feature, percent_allowed) {
1703
- str = str + feature;
1704
-
1705
- // Bernstein's hash: http://www.cse.yorku.ca/~oz/hash.html#djb2
1706
- var hash = 5381;
1707
- for (var i = 0; i < str.length; i++) {
1708
- hash = ((hash << 5) + hash) + str.charCodeAt(i);
1709
- hash = hash & hash;
1710
- }
1711
- var dart = (hash >>> 0) % 100;
1712
- return dart < percent_allowed;
1713
- });
1714
-
1715
1687
  // naive way to extract domain name (example.com) from full hostname (my.sub.example.com)
1716
1688
  var SIMPLE_DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]*\.[a-z]+$/i;
1717
1689
  // this next one attempts to account for some ccSLDs, e.g. extracting oxford.ac.uk from www.oxford.ac.uk
@@ -1770,7 +1742,6 @@ export {
1770
1742
  navigator,
1771
1743
  cheap_guid,
1772
1744
  console_with_prefix,
1773
- determine_eligibility,
1774
1745
  extract_domain,
1775
1746
  localStorageSupported,
1776
1747
  JSONStringify,
package/tunnel.log ADDED
File without changes
package/.travis.yml DELETED
@@ -1,6 +0,0 @@
1
- node_js:
2
- - '10'
3
- - '12'
4
- language: node_js
5
- script:
6
- - npm test
@@ -1,192 +0,0 @@
1
- import { _ } from './utils';
2
-
3
- /*
4
- * Get the className of an element, accounting for edge cases where element.className is an object
5
- * @param {Element} el - element to get the className of
6
- * @returns {string} the element's class
7
- */
8
- export function getClassName(el) {
9
- switch(typeof el.className) {
10
- case 'string':
11
- return el.className;
12
- case 'object': // handle cases where className might be SVGAnimatedString or some other type
13
- return el.className.baseVal || el.getAttribute('class') || '';
14
- default: // future proof
15
- return '';
16
- }
17
- }
18
-
19
- /*
20
- * Get the direct text content of an element, protecting against sensitive data collection.
21
- * Concats textContent of each of the element's text node children; this avoids potential
22
- * collection of sensitive data that could happen if we used element.textContent and the
23
- * element had sensitive child elements, since element.textContent includes child content.
24
- * Scrubs values that look like they could be sensitive (i.e. cc or ssn number).
25
- * @param {Element} el - element to get the text of
26
- * @returns {string} the element's direct text content
27
- */
28
- export function getSafeText(el) {
29
- var elText = '';
30
-
31
- if (shouldTrackElement(el) && el.childNodes && el.childNodes.length) {
32
- _.each(el.childNodes, function(child) {
33
- if (isTextNode(child) && child.textContent) {
34
- elText += _.trim(child.textContent)
35
- // scrub potentially sensitive values
36
- .split(/(\s+)/).filter(shouldTrackValue).join('')
37
- // normalize whitespace
38
- .replace(/[\r\n]/g, ' ').replace(/[ ]+/g, ' ')
39
- // truncate
40
- .substring(0, 255);
41
- }
42
- });
43
- }
44
-
45
- return _.trim(elText);
46
- }
47
-
48
- /*
49
- * Check whether an element has nodeType Node.ELEMENT_NODE
50
- * @param {Element} el - element to check
51
- * @returns {boolean} whether el is of the correct nodeType
52
- */
53
- export function isElementNode(el) {
54
- return el && el.nodeType === 1; // Node.ELEMENT_NODE - use integer constant for browser portability
55
- }
56
-
57
- /*
58
- * Check whether an element is of a given tag type.
59
- * Due to potential reference discrepancies (such as the webcomponents.js polyfill),
60
- * we want to match tagNames instead of specific references because something like
61
- * element === document.body won't always work because element might not be a native
62
- * element.
63
- * @param {Element} el - element to check
64
- * @param {string} tag - tag name (e.g., "div")
65
- * @returns {boolean} whether el is of the given tag type
66
- */
67
- export function isTag(el, tag) {
68
- return el && el.tagName && el.tagName.toLowerCase() === tag.toLowerCase();
69
- }
70
-
71
- /*
72
- * Check whether an element has nodeType Node.TEXT_NODE
73
- * @param {Element} el - element to check
74
- * @returns {boolean} whether el is of the correct nodeType
75
- */
76
- export function isTextNode(el) {
77
- return el && el.nodeType === 3; // Node.TEXT_NODE - use integer constant for browser portability
78
- }
79
-
80
- /*
81
- * Check whether a DOM event should be "tracked" or if it may contain sentitive data
82
- * using a variety of heuristics.
83
- * @param {Element} el - element to check
84
- * @param {Event} event - event to check
85
- * @returns {boolean} whether the event should be tracked
86
- */
87
- export function shouldTrackDomEvent(el, event) {
88
- if (!el || isTag(el, 'html') || !isElementNode(el)) {
89
- return false;
90
- }
91
- var tag = el.tagName.toLowerCase();
92
- switch (tag) {
93
- case 'html':
94
- return false;
95
- case 'form':
96
- return event.type === 'submit';
97
- case 'input':
98
- if (['button', 'submit'].indexOf(el.getAttribute('type')) === -1) {
99
- return event.type === 'change';
100
- } else {
101
- return event.type === 'click';
102
- }
103
- case 'select':
104
- case 'textarea':
105
- return event.type === 'change';
106
- default:
107
- return event.type === 'click';
108
- }
109
- }
110
-
111
- /*
112
- * Check whether a DOM element should be "tracked" or if it may contain sentitive data
113
- * using a variety of heuristics.
114
- * @param {Element} el - element to check
115
- * @returns {boolean} whether the element should be tracked
116
- */
117
- export function shouldTrackElement(el) {
118
- for (var curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode) {
119
- var classes = getClassName(curEl).split(' ');
120
- if (_.includes(classes, 'mp-sensitive') || _.includes(classes, 'mp-no-track')) {
121
- return false;
122
- }
123
- }
124
-
125
- if (_.includes(getClassName(el).split(' '), 'mp-include')) {
126
- return true;
127
- }
128
-
129
- // don't send data from inputs or similar elements since there will always be
130
- // a risk of clientside javascript placing sensitive data in attributes
131
- if (
132
- isTag(el, 'input') ||
133
- isTag(el, 'select') ||
134
- isTag(el, 'textarea') ||
135
- el.getAttribute('contenteditable') === 'true'
136
- ) {
137
- return false;
138
- }
139
-
140
- // don't include hidden or password fields
141
- var type = el.type || '';
142
- if (typeof type === 'string') { // it's possible for el.type to be a DOM element if el is a form with a child input[name="type"]
143
- switch(type.toLowerCase()) {
144
- case 'hidden':
145
- return false;
146
- case 'password':
147
- return false;
148
- }
149
- }
150
-
151
- // filter out data from fields that look like sensitive fields
152
- var name = el.name || el.id || '';
153
- if (typeof name === 'string') { // it's possible for el.name or el.id to be a DOM element if el is a form with a child input[name="name"]
154
- var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
155
- if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
156
- return false;
157
- }
158
- }
159
-
160
- return true;
161
- }
162
-
163
- /*
164
- * Check whether a string value should be "tracked" or if it may contain sentitive data
165
- * using a variety of heuristics.
166
- * @param {string} value - string value to check
167
- * @returns {boolean} whether the element should be tracked
168
- */
169
- export function shouldTrackValue(value) {
170
- if (value === null || _.isUndefined(value)) {
171
- return false;
172
- }
173
-
174
- if (typeof value === 'string') {
175
- value = _.trim(value);
176
-
177
- // check to see if input value looks like a credit card number
178
- // see: https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s20.html
179
- var ccRegex = /^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))$/;
180
- if (ccRegex.test((value || '').replace(/[- ]/g, ''))) {
181
- return false;
182
- }
183
-
184
- // check to see if input value looks like a social security number
185
- var ssnRegex = /(^\d{3}-?\d{2}-?\d{4}$)/;
186
- if (ssnRegex.test(value)) {
187
- return false;
188
- }
189
- }
190
-
191
- return true;
192
- }