mixpanel-browser 2.74.0 → 2.75.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/.github/workflows/unit-tests.yml +1 -1
- package/CHANGELOG.md +5 -0
- package/README.md +2 -2
- package/dist/mixpanel-core.cjs.js +318 -20
- package/dist/mixpanel-recorder.js +127 -15
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-targeting.js +2576 -0
- package/dist/mixpanel-targeting.min.js +2 -0
- package/dist/mixpanel-targeting.min.js.map +1 -0
- package/dist/mixpanel-with-async-modules.cjs.d.ts +522 -0
- package/dist/mixpanel-with-async-modules.cjs.js +9700 -0
- package/dist/mixpanel-with-async-recorder.cjs.js +318 -20
- package/dist/mixpanel-with-recorder.js +435 -26
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.js +1020 -28
- package/dist/mixpanel.cjs.js +1020 -28
- package/dist/mixpanel.globals.js +318 -20
- package/dist/mixpanel.min.js +179 -172
- package/dist/mixpanel.module.js +1020 -28
- package/dist/mixpanel.umd.js +1020 -28
- package/dist/rrweb-bundled.js +119 -5
- package/dist/rrweb-compiled.js +116 -5
- package/package.json +4 -3
- package/rollup.config.mjs +34 -2
- package/src/config.js +1 -1
- package/src/flags/index.js +269 -8
- package/src/globals.js +14 -0
- package/src/loaders/loader-module.js +1 -0
- package/src/mixpanel-core.js +12 -3
- package/src/recorder/index.js +2 -1
- package/src/targeting/event-matcher.js +97 -0
- package/src/targeting/index.js +11 -0
- package/src/targeting/loader.js +36 -0
- package/src/utils.js +1 -8
- package/.claude/settings.local.json +0 -12
- /package/src/loaders/{loader-module-with-async-recorder.js → loader-module-with-async-modules.js} +0 -0
package/dist/mixpanel.umd.js
CHANGED
|
@@ -29,6 +29,16 @@
|
|
|
29
29
|
win = window;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Shared global window property names used across modules
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// Targeting library global (used by flags and targeting modules)
|
|
37
|
+
var TARGETING_GLOBAL_NAME = '__mp_targeting';
|
|
38
|
+
|
|
39
|
+
// Recorder library global (used by recorder and mixpanel-core)
|
|
40
|
+
var RECORDER_GLOBAL_NAME = '__mp_recorder';
|
|
41
|
+
|
|
32
42
|
function _array_like_to_array(arr, len) {
|
|
33
43
|
if (len == null || len > arr.length) len = arr.length;
|
|
34
44
|
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
@@ -643,14 +653,16 @@
|
|
|
643
653
|
return this.nodeMetaMap.get(n2) || null;
|
|
644
654
|
};
|
|
645
655
|
// removes the node from idNodeMap
|
|
646
|
-
//
|
|
647
|
-
_proto.removeNodeFromMap = function removeNodeFromMap(n2) {
|
|
656
|
+
// if permanent is true, also removes from nodeMetaMap
|
|
657
|
+
_proto.removeNodeFromMap = function removeNodeFromMap(n2, permanent) {
|
|
648
658
|
var _this = this;
|
|
659
|
+
if (permanent === void 0) permanent = false;
|
|
649
660
|
var id = this.getId(n2);
|
|
650
661
|
this.idNodeMap.delete(id);
|
|
662
|
+
if (permanent) this.nodeMetaMap.delete(n2);
|
|
651
663
|
if (n2.childNodes) {
|
|
652
664
|
n2.childNodes.forEach(function(childNode) {
|
|
653
|
-
return _this.removeNodeFromMap(childNode);
|
|
665
|
+
return _this.removeNodeFromMap(childNode, permanent);
|
|
654
666
|
});
|
|
655
667
|
}
|
|
656
668
|
};
|
|
@@ -10392,6 +10404,15 @@
|
|
|
10392
10404
|
_proto.generateId = function generateId() {
|
|
10393
10405
|
return this.id++;
|
|
10394
10406
|
};
|
|
10407
|
+
_proto.remove = function remove(stylesheet) {
|
|
10408
|
+
var id = this.styleIDMap.get(stylesheet);
|
|
10409
|
+
if (id !== void 0) {
|
|
10410
|
+
this.styleIDMap.delete(stylesheet);
|
|
10411
|
+
this.idStyleMap.delete(id);
|
|
10412
|
+
return true;
|
|
10413
|
+
}
|
|
10414
|
+
return false;
|
|
10415
|
+
};
|
|
10395
10416
|
return StyleSheetMirror;
|
|
10396
10417
|
}();
|
|
10397
10418
|
function getShadowHost(n2) {
|
|
@@ -10714,7 +10735,15 @@
|
|
|
10714
10735
|
}
|
|
10715
10736
|
};
|
|
10716
10737
|
while(_this.mapRemoves.length){
|
|
10717
|
-
_this.
|
|
10738
|
+
var removedNode = _this.mapRemoves.shift();
|
|
10739
|
+
if (removedNode.nodeName === "IFRAME") {
|
|
10740
|
+
try {
|
|
10741
|
+
_this.iframeManager.removeIframe(removedNode);
|
|
10742
|
+
} catch (e2) {}
|
|
10743
|
+
} else {
|
|
10744
|
+
_this.stylesheetManager.cleanupStylesheetsForRemovedNode(removedNode);
|
|
10745
|
+
}
|
|
10746
|
+
_this.mirror.removeNodeFromMap(removedNode);
|
|
10718
10747
|
}
|
|
10719
10748
|
for(var _iterator = _create_for_of_iterator_helper_loose(_this.movedSet), _step; !(_step = _iterator()).done;){
|
|
10720
10749
|
var n2 = _step.value;
|
|
@@ -11088,6 +11117,9 @@
|
|
|
11088
11117
|
this.shadowDomManager.reset();
|
|
11089
11118
|
this.canvasManager.reset();
|
|
11090
11119
|
};
|
|
11120
|
+
_proto.getDoc = function getDoc() {
|
|
11121
|
+
return this.doc;
|
|
11122
|
+
};
|
|
11091
11123
|
return MutationBuffer;
|
|
11092
11124
|
}();
|
|
11093
11125
|
function deepDelete(addsSet, n2) {
|
|
@@ -11188,6 +11220,14 @@
|
|
|
11188
11220
|
});
|
|
11189
11221
|
return observer;
|
|
11190
11222
|
}
|
|
11223
|
+
function removeMutationBufferForDoc(doc) {
|
|
11224
|
+
for(var i2 = mutationBuffers.length - 1; i2 >= 0; i2--){
|
|
11225
|
+
var buffer = mutationBuffers[i2];
|
|
11226
|
+
if (buffer.getDoc() === doc) {
|
|
11227
|
+
mutationBuffers.splice(i2, 1);
|
|
11228
|
+
}
|
|
11229
|
+
}
|
|
11230
|
+
}
|
|
11191
11231
|
function initMoveObserver(param) {
|
|
11192
11232
|
var mousemoveCb = param.mousemoveCb, sampling = param.sampling, doc = param.doc, mirror2 = param.mirror;
|
|
11193
11233
|
if (sampling.mousemove === false) {
|
|
@@ -12203,6 +12243,8 @@
|
|
|
12203
12243
|
__publicField$1(this, "crossOriginIframeMirror", new CrossOriginIframeMirror(genId));
|
|
12204
12244
|
__publicField$1(this, "crossOriginIframeStyleMirror");
|
|
12205
12245
|
__publicField$1(this, "crossOriginIframeRootIdMap", /* @__PURE__ */ new WeakMap());
|
|
12246
|
+
__publicField$1(this, "iframeContentDocumentMap", /* @__PURE__ */ new WeakMap());
|
|
12247
|
+
__publicField$1(this, "iframeObserverCleanupMap", /* @__PURE__ */ new WeakMap());
|
|
12206
12248
|
__publicField$1(this, "mirror");
|
|
12207
12249
|
__publicField$1(this, "mutationCb");
|
|
12208
12250
|
__publicField$1(this, "wrappedEmit");
|
|
@@ -12224,6 +12266,31 @@
|
|
|
12224
12266
|
this.iframes.set(iframeEl, true);
|
|
12225
12267
|
if (iframeEl.contentWindow) this.crossOriginIframeMap.set(iframeEl.contentWindow, iframeEl);
|
|
12226
12268
|
};
|
|
12269
|
+
_proto.getIframeContentDocument = function getIframeContentDocument(iframeEl) {
|
|
12270
|
+
return this.iframeContentDocumentMap.get(iframeEl);
|
|
12271
|
+
};
|
|
12272
|
+
_proto.setObserverCleanup = function setObserverCleanup(iframeEl, cleanup) {
|
|
12273
|
+
this.iframeObserverCleanupMap.set(iframeEl, cleanup);
|
|
12274
|
+
};
|
|
12275
|
+
_proto.getObserverCleanup = function getObserverCleanup(iframeEl) {
|
|
12276
|
+
return this.iframeObserverCleanupMap.get(iframeEl);
|
|
12277
|
+
};
|
|
12278
|
+
_proto.removeIframe = function removeIframe(iframeEl) {
|
|
12279
|
+
var storedDoc = this.iframeContentDocumentMap.get(iframeEl);
|
|
12280
|
+
if (storedDoc) {
|
|
12281
|
+
this.stylesheetManager.cleanupStylesheetsForRemovedNode(storedDoc);
|
|
12282
|
+
this.mirror.removeNodeFromMap(storedDoc, true);
|
|
12283
|
+
}
|
|
12284
|
+
this.iframes.delete(iframeEl);
|
|
12285
|
+
this.iframeContentDocumentMap.delete(iframeEl);
|
|
12286
|
+
var observerCleanup = this.iframeObserverCleanupMap.get(iframeEl);
|
|
12287
|
+
if (observerCleanup) {
|
|
12288
|
+
try {
|
|
12289
|
+
observerCleanup();
|
|
12290
|
+
} catch (e2) {}
|
|
12291
|
+
this.iframeObserverCleanupMap.delete(iframeEl);
|
|
12292
|
+
}
|
|
12293
|
+
};
|
|
12227
12294
|
_proto.addLoadListener = function addLoadListener(cb) {
|
|
12228
12295
|
this.loadListener = cb;
|
|
12229
12296
|
};
|
|
@@ -12242,6 +12309,9 @@
|
|
|
12242
12309
|
attributes: [],
|
|
12243
12310
|
isAttachIframe: true
|
|
12244
12311
|
});
|
|
12312
|
+
if (iframeEl.contentDocument) {
|
|
12313
|
+
this.iframeContentDocumentMap.set(iframeEl, iframeEl.contentDocument);
|
|
12314
|
+
}
|
|
12245
12315
|
if (this.recordCrossOriginIframes) (_a2 = iframeEl.contentWindow) == null ? void 0 : _a2.addEventListener("message", this.handleMessage.bind(this));
|
|
12246
12316
|
(_b = this.loadListener) == null ? void 0 : _b.call(this, iframeEl);
|
|
12247
12317
|
if (iframeEl.contentDocument && iframeEl.contentDocument.adoptedStyleSheets && iframeEl.contentDocument.adoptedStyleSheets.length > 0) this.stylesheetManager.adoptStyleSheets(iframeEl.contentDocument.adoptedStyleSheets, this.mirror.getId(iframeEl.contentDocument));
|
|
@@ -13154,6 +13224,41 @@
|
|
|
13154
13224
|
this.styleMirror.reset();
|
|
13155
13225
|
this.trackedLinkElements = /* @__PURE__ */ new WeakSet();
|
|
13156
13226
|
};
|
|
13227
|
+
/**
|
|
13228
|
+
* Cleans up stylesheets associated with a removed node.
|
|
13229
|
+
*
|
|
13230
|
+
* @param removedNode - The node that was removed from the DOM.
|
|
13231
|
+
*/ _proto.cleanupStylesheetsForRemovedNode = function cleanupStylesheetsForRemovedNode(removedNode) {
|
|
13232
|
+
var _this = this;
|
|
13233
|
+
try {
|
|
13234
|
+
if (removedNode.nodeType === Node.DOCUMENT_NODE) {
|
|
13235
|
+
var doc = removedNode;
|
|
13236
|
+
if (doc.adoptedStyleSheets) {
|
|
13237
|
+
for(var _iterator = _create_for_of_iterator_helper_loose(doc.adoptedStyleSheets), _step; !(_step = _iterator()).done;){
|
|
13238
|
+
var sheet = _step.value;
|
|
13239
|
+
this.styleMirror.remove(sheet);
|
|
13240
|
+
}
|
|
13241
|
+
}
|
|
13242
|
+
}
|
|
13243
|
+
if (removedNode.nodeName === "STYLE") {
|
|
13244
|
+
var styleEl = removedNode;
|
|
13245
|
+
if (styleEl.sheet) {
|
|
13246
|
+
this.styleMirror.remove(styleEl.sheet);
|
|
13247
|
+
}
|
|
13248
|
+
}
|
|
13249
|
+
if (removedNode.nodeName === "LINK" && removedNode.rel === "stylesheet") {
|
|
13250
|
+
var linkEl = removedNode;
|
|
13251
|
+
if (linkEl.sheet) {
|
|
13252
|
+
this.styleMirror.remove(linkEl.sheet);
|
|
13253
|
+
}
|
|
13254
|
+
}
|
|
13255
|
+
if (removedNode.childNodes) {
|
|
13256
|
+
removedNode.childNodes.forEach(function(child) {
|
|
13257
|
+
_this.cleanupStylesheetsForRemovedNode(child);
|
|
13258
|
+
});
|
|
13259
|
+
}
|
|
13260
|
+
} catch (e2) {}
|
|
13261
|
+
};
|
|
13157
13262
|
// TODO: take snapshot on stylesheet reload by applying event listener
|
|
13158
13263
|
_proto.trackStylesheetInLinkElement = function trackStylesheetInLinkElement(_linkEl) {};
|
|
13159
13264
|
return StylesheetManager;
|
|
@@ -13608,7 +13713,23 @@
|
|
|
13608
13713
|
};
|
|
13609
13714
|
iframeManager.addLoadListener(function(iframeEl) {
|
|
13610
13715
|
try {
|
|
13611
|
-
|
|
13716
|
+
var iframeDoc = iframeEl.contentDocument;
|
|
13717
|
+
var iframeHandler = observe(iframeDoc);
|
|
13718
|
+
handlers.push(iframeHandler);
|
|
13719
|
+
var existingCleanup = iframeManager.getObserverCleanup(iframeEl);
|
|
13720
|
+
iframeManager.setObserverCleanup(iframeEl, function() {
|
|
13721
|
+
if (existingCleanup) {
|
|
13722
|
+
try {
|
|
13723
|
+
existingCleanup();
|
|
13724
|
+
} catch (e2) {}
|
|
13725
|
+
}
|
|
13726
|
+
try {
|
|
13727
|
+
iframeHandler();
|
|
13728
|
+
var idx = handlers.indexOf(iframeHandler);
|
|
13729
|
+
if (idx !== -1) handlers.splice(idx, 1);
|
|
13730
|
+
removeMutationBufferForDoc(iframeDoc);
|
|
13731
|
+
} catch (e2) {}
|
|
13732
|
+
});
|
|
13612
13733
|
} catch (error) {
|
|
13613
13734
|
console.warn(error);
|
|
13614
13735
|
}
|
|
@@ -13865,7 +13986,7 @@
|
|
|
13865
13986
|
}
|
|
13866
13987
|
return classMatchesRegex(index.parentNode(node2), regex);
|
|
13867
13988
|
}
|
|
13868
|
-
function getDefaultExportFromCjs(x2) {
|
|
13989
|
+
function getDefaultExportFromCjs$3(x2) {
|
|
13869
13990
|
return x2 && x2.__esModule && Object.prototype.hasOwnProperty.call(x2, "default") ? x2["default"] : x2;
|
|
13870
13991
|
}
|
|
13871
13992
|
function getAugmentedNamespace(n) {
|
|
@@ -17980,7 +18101,7 @@
|
|
|
17980
18101
|
LazyResult2.registerPostcss(postcss);
|
|
17981
18102
|
var postcss_1 = postcss;
|
|
17982
18103
|
postcss.default = postcss;
|
|
17983
|
-
var postcss$1 = /* @__PURE__ */ getDefaultExportFromCjs(postcss_1);
|
|
18104
|
+
var postcss$1 = /* @__PURE__ */ getDefaultExportFromCjs$3(postcss_1);
|
|
17984
18105
|
postcss$1.stringify;
|
|
17985
18106
|
postcss$1.fromJSON;
|
|
17986
18107
|
postcss$1.plugin;
|
|
@@ -18857,7 +18978,7 @@
|
|
|
18857
18978
|
|
|
18858
18979
|
var Config = {
|
|
18859
18980
|
DEBUG: false,
|
|
18860
|
-
LIB_VERSION: '2.
|
|
18981
|
+
LIB_VERSION: '2.75.0'
|
|
18861
18982
|
};
|
|
18862
18983
|
|
|
18863
18984
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
@@ -19063,15 +19184,8 @@
|
|
|
19063
19184
|
return toString.call(obj) === '[object Array]';
|
|
19064
19185
|
};
|
|
19065
19186
|
|
|
19066
|
-
// from a comment on http://dbj.org/dbj/?p=286
|
|
19067
|
-
// fails on only one very rare and deliberate custom object:
|
|
19068
|
-
// var bomb = { toString : undefined, valueOf: function(o) { return "function BOMBA!"; }};
|
|
19069
19187
|
_.isFunction = function(f) {
|
|
19070
|
-
|
|
19071
|
-
return /^\s*\bfunction\b/.test(f);
|
|
19072
|
-
} catch (x) {
|
|
19073
|
-
return false;
|
|
19074
|
-
}
|
|
19188
|
+
return typeof f === 'function';
|
|
19075
19189
|
};
|
|
19076
19190
|
|
|
19077
19191
|
_.isArguments = function(obj) {
|
|
@@ -23765,7 +23879,590 @@
|
|
|
23765
23879
|
}
|
|
23766
23880
|
});
|
|
23767
23881
|
|
|
23768
|
-
win[
|
|
23882
|
+
win[RECORDER_GLOBAL_NAME] = MixpanelRecorder;
|
|
23883
|
+
|
|
23884
|
+
function getDefaultExportFromCjs (x) {
|
|
23885
|
+
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
23886
|
+
}
|
|
23887
|
+
|
|
23888
|
+
var logic$1 = {exports: {}};
|
|
23889
|
+
|
|
23890
|
+
/* globals define,module */
|
|
23891
|
+
var logic = logic$1.exports;
|
|
23892
|
+
|
|
23893
|
+
var hasRequiredLogic;
|
|
23894
|
+
|
|
23895
|
+
function requireLogic () {
|
|
23896
|
+
if (hasRequiredLogic) return logic$1.exports;
|
|
23897
|
+
hasRequiredLogic = 1;
|
|
23898
|
+
(function (module, exports) {
|
|
23899
|
+
(function(root, factory) {
|
|
23900
|
+
{
|
|
23901
|
+
module.exports = factory();
|
|
23902
|
+
}
|
|
23903
|
+
}(logic, function() {
|
|
23904
|
+
/* globals console:false */
|
|
23905
|
+
|
|
23906
|
+
if ( ! Array.isArray) {
|
|
23907
|
+
Array.isArray = function(arg) {
|
|
23908
|
+
return Object.prototype.toString.call(arg) === "[object Array]";
|
|
23909
|
+
};
|
|
23910
|
+
}
|
|
23911
|
+
|
|
23912
|
+
/**
|
|
23913
|
+
* Return an array that contains no duplicates (original not modified)
|
|
23914
|
+
* @param {array} array Original reference array
|
|
23915
|
+
* @return {array} New array with no duplicates
|
|
23916
|
+
*/
|
|
23917
|
+
function arrayUnique(array) {
|
|
23918
|
+
var a = [];
|
|
23919
|
+
for (var i=0, l=array.length; i<l; i++) {
|
|
23920
|
+
if (a.indexOf(array[i]) === -1) {
|
|
23921
|
+
a.push(array[i]);
|
|
23922
|
+
}
|
|
23923
|
+
}
|
|
23924
|
+
return a;
|
|
23925
|
+
}
|
|
23926
|
+
|
|
23927
|
+
var jsonLogic = {};
|
|
23928
|
+
var operations = {
|
|
23929
|
+
"==": function(a, b) {
|
|
23930
|
+
return a == b;
|
|
23931
|
+
},
|
|
23932
|
+
"===": function(a, b) {
|
|
23933
|
+
return a === b;
|
|
23934
|
+
},
|
|
23935
|
+
"!=": function(a, b) {
|
|
23936
|
+
return a != b;
|
|
23937
|
+
},
|
|
23938
|
+
"!==": function(a, b) {
|
|
23939
|
+
return a !== b;
|
|
23940
|
+
},
|
|
23941
|
+
">": function(a, b) {
|
|
23942
|
+
return a > b;
|
|
23943
|
+
},
|
|
23944
|
+
">=": function(a, b) {
|
|
23945
|
+
return a >= b;
|
|
23946
|
+
},
|
|
23947
|
+
"<": function(a, b, c) {
|
|
23948
|
+
return (c === undefined) ? a < b : (a < b) && (b < c);
|
|
23949
|
+
},
|
|
23950
|
+
"<=": function(a, b, c) {
|
|
23951
|
+
return (c === undefined) ? a <= b : (a <= b) && (b <= c);
|
|
23952
|
+
},
|
|
23953
|
+
"!!": function(a) {
|
|
23954
|
+
return jsonLogic.truthy(a);
|
|
23955
|
+
},
|
|
23956
|
+
"!": function(a) {
|
|
23957
|
+
return !jsonLogic.truthy(a);
|
|
23958
|
+
},
|
|
23959
|
+
"%": function(a, b) {
|
|
23960
|
+
return a % b;
|
|
23961
|
+
},
|
|
23962
|
+
"log": function(a) {
|
|
23963
|
+
console.log(a); return a;
|
|
23964
|
+
},
|
|
23965
|
+
"in": function(a, b) {
|
|
23966
|
+
if (!b || typeof b.indexOf === "undefined") return false;
|
|
23967
|
+
return (b.indexOf(a) !== -1);
|
|
23968
|
+
},
|
|
23969
|
+
"cat": function() {
|
|
23970
|
+
return Array.prototype.join.call(arguments, "");
|
|
23971
|
+
},
|
|
23972
|
+
"substr": function(source, start, end) {
|
|
23973
|
+
if (end < 0) {
|
|
23974
|
+
// JavaScript doesn't support negative end, this emulates PHP behavior
|
|
23975
|
+
var temp = String(source).substr(start);
|
|
23976
|
+
return temp.substr(0, temp.length + end);
|
|
23977
|
+
}
|
|
23978
|
+
return String(source).substr(start, end);
|
|
23979
|
+
},
|
|
23980
|
+
"+": function() {
|
|
23981
|
+
return Array.prototype.reduce.call(arguments, function(a, b) {
|
|
23982
|
+
return parseFloat(a, 10) + parseFloat(b, 10);
|
|
23983
|
+
}, 0);
|
|
23984
|
+
},
|
|
23985
|
+
"*": function() {
|
|
23986
|
+
return Array.prototype.reduce.call(arguments, function(a, b) {
|
|
23987
|
+
return parseFloat(a, 10) * parseFloat(b, 10);
|
|
23988
|
+
});
|
|
23989
|
+
},
|
|
23990
|
+
"-": function(a, b) {
|
|
23991
|
+
if (b === undefined) {
|
|
23992
|
+
return -a;
|
|
23993
|
+
} else {
|
|
23994
|
+
return a - b;
|
|
23995
|
+
}
|
|
23996
|
+
},
|
|
23997
|
+
"/": function(a, b) {
|
|
23998
|
+
return a / b;
|
|
23999
|
+
},
|
|
24000
|
+
"min": function() {
|
|
24001
|
+
return Math.min.apply(this, arguments);
|
|
24002
|
+
},
|
|
24003
|
+
"max": function() {
|
|
24004
|
+
return Math.max.apply(this, arguments);
|
|
24005
|
+
},
|
|
24006
|
+
"merge": function() {
|
|
24007
|
+
return Array.prototype.reduce.call(arguments, function(a, b) {
|
|
24008
|
+
return a.concat(b);
|
|
24009
|
+
}, []);
|
|
24010
|
+
},
|
|
24011
|
+
"var": function(a, b) {
|
|
24012
|
+
var not_found = (b === undefined) ? null : b;
|
|
24013
|
+
var data = this;
|
|
24014
|
+
if (typeof a === "undefined" || a==="" || a===null) {
|
|
24015
|
+
return data;
|
|
24016
|
+
}
|
|
24017
|
+
var sub_props = String(a).split(".");
|
|
24018
|
+
for (var i = 0; i < sub_props.length; i++) {
|
|
24019
|
+
if (data === null || data === undefined) {
|
|
24020
|
+
return not_found;
|
|
24021
|
+
}
|
|
24022
|
+
// Descending into data
|
|
24023
|
+
data = data[sub_props[i]];
|
|
24024
|
+
if (data === undefined) {
|
|
24025
|
+
return not_found;
|
|
24026
|
+
}
|
|
24027
|
+
}
|
|
24028
|
+
return data;
|
|
24029
|
+
},
|
|
24030
|
+
"missing": function() {
|
|
24031
|
+
/*
|
|
24032
|
+
Missing can receive many keys as many arguments, like {"missing:[1,2]}
|
|
24033
|
+
Missing can also receive *one* argument that is an array of keys,
|
|
24034
|
+
which typically happens if it's actually acting on the output of another command
|
|
24035
|
+
(like 'if' or 'merge')
|
|
24036
|
+
*/
|
|
24037
|
+
|
|
24038
|
+
var missing = [];
|
|
24039
|
+
var keys = Array.isArray(arguments[0]) ? arguments[0] : arguments;
|
|
24040
|
+
|
|
24041
|
+
for (var i = 0; i < keys.length; i++) {
|
|
24042
|
+
var key = keys[i];
|
|
24043
|
+
var value = jsonLogic.apply({"var": key}, this);
|
|
24044
|
+
if (value === null || value === "") {
|
|
24045
|
+
missing.push(key);
|
|
24046
|
+
}
|
|
24047
|
+
}
|
|
24048
|
+
|
|
24049
|
+
return missing;
|
|
24050
|
+
},
|
|
24051
|
+
"missing_some": function(need_count, options) {
|
|
24052
|
+
// missing_some takes two arguments, how many (minimum) items must be present, and an array of keys (just like 'missing') to check for presence.
|
|
24053
|
+
var are_missing = jsonLogic.apply({"missing": options}, this);
|
|
24054
|
+
|
|
24055
|
+
if (options.length - are_missing.length >= need_count) {
|
|
24056
|
+
return [];
|
|
24057
|
+
} else {
|
|
24058
|
+
return are_missing;
|
|
24059
|
+
}
|
|
24060
|
+
},
|
|
24061
|
+
};
|
|
24062
|
+
|
|
24063
|
+
jsonLogic.is_logic = function(logic) {
|
|
24064
|
+
return (
|
|
24065
|
+
typeof logic === "object" && // An object
|
|
24066
|
+
logic !== null && // but not null
|
|
24067
|
+
! Array.isArray(logic) && // and not an array
|
|
24068
|
+
Object.keys(logic).length === 1 // with exactly one key
|
|
24069
|
+
);
|
|
24070
|
+
};
|
|
24071
|
+
|
|
24072
|
+
/*
|
|
24073
|
+
This helper will defer to the JsonLogic spec as a tie-breaker when different language interpreters define different behavior for the truthiness of primitives. E.g., PHP considers empty arrays to be falsy, but Javascript considers them to be truthy. JsonLogic, as an ecosystem, needs one consistent answer.
|
|
24074
|
+
|
|
24075
|
+
Spec and rationale here: http://jsonlogic.com/truthy
|
|
24076
|
+
*/
|
|
24077
|
+
jsonLogic.truthy = function(value) {
|
|
24078
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
24079
|
+
return false;
|
|
24080
|
+
}
|
|
24081
|
+
return !! value;
|
|
24082
|
+
};
|
|
24083
|
+
|
|
24084
|
+
|
|
24085
|
+
jsonLogic.get_operator = function(logic) {
|
|
24086
|
+
return Object.keys(logic)[0];
|
|
24087
|
+
};
|
|
24088
|
+
|
|
24089
|
+
jsonLogic.get_values = function(logic) {
|
|
24090
|
+
return logic[jsonLogic.get_operator(logic)];
|
|
24091
|
+
};
|
|
24092
|
+
|
|
24093
|
+
jsonLogic.apply = function(logic, data) {
|
|
24094
|
+
// Does this array contain logic? Only one way to find out.
|
|
24095
|
+
if (Array.isArray(logic)) {
|
|
24096
|
+
return logic.map(function(l) {
|
|
24097
|
+
return jsonLogic.apply(l, data);
|
|
24098
|
+
});
|
|
24099
|
+
}
|
|
24100
|
+
// You've recursed to a primitive, stop!
|
|
24101
|
+
if ( ! jsonLogic.is_logic(logic) ) {
|
|
24102
|
+
return logic;
|
|
24103
|
+
}
|
|
24104
|
+
|
|
24105
|
+
var op = jsonLogic.get_operator(logic);
|
|
24106
|
+
var values = logic[op];
|
|
24107
|
+
var i;
|
|
24108
|
+
var current;
|
|
24109
|
+
var scopedLogic;
|
|
24110
|
+
var scopedData;
|
|
24111
|
+
var initial;
|
|
24112
|
+
|
|
24113
|
+
// easy syntax for unary operators, like {"var" : "x"} instead of strict {"var" : ["x"]}
|
|
24114
|
+
if ( ! Array.isArray(values)) {
|
|
24115
|
+
values = [values];
|
|
24116
|
+
}
|
|
24117
|
+
|
|
24118
|
+
// 'if', 'and', and 'or' violate the normal rule of depth-first calculating consequents, let each manage recursion as needed.
|
|
24119
|
+
if (op === "if" || op == "?:") {
|
|
24120
|
+
/* 'if' should be called with a odd number of parameters, 3 or greater
|
|
24121
|
+
This works on the pattern:
|
|
24122
|
+
if( 0 ){ 1 }else{ 2 };
|
|
24123
|
+
if( 0 ){ 1 }else if( 2 ){ 3 }else{ 4 };
|
|
24124
|
+
if( 0 ){ 1 }else if( 2 ){ 3 }else if( 4 ){ 5 }else{ 6 };
|
|
24125
|
+
|
|
24126
|
+
The implementation is:
|
|
24127
|
+
For pairs of values (0,1 then 2,3 then 4,5 etc)
|
|
24128
|
+
If the first evaluates truthy, evaluate and return the second
|
|
24129
|
+
If the first evaluates falsy, jump to the next pair (e.g, 0,1 to 2,3)
|
|
24130
|
+
given one parameter, evaluate and return it. (it's an Else and all the If/ElseIf were false)
|
|
24131
|
+
given 0 parameters, return NULL (not great practice, but there was no Else)
|
|
24132
|
+
*/
|
|
24133
|
+
for (i = 0; i < values.length - 1; i += 2) {
|
|
24134
|
+
if ( jsonLogic.truthy( jsonLogic.apply(values[i], data) ) ) {
|
|
24135
|
+
return jsonLogic.apply(values[i+1], data);
|
|
24136
|
+
}
|
|
24137
|
+
}
|
|
24138
|
+
if (values.length === i+1) {
|
|
24139
|
+
return jsonLogic.apply(values[i], data);
|
|
24140
|
+
}
|
|
24141
|
+
return null;
|
|
24142
|
+
} else if (op === "and") { // Return first falsy, or last
|
|
24143
|
+
for (i=0; i < values.length; i+=1) {
|
|
24144
|
+
current = jsonLogic.apply(values[i], data);
|
|
24145
|
+
if ( ! jsonLogic.truthy(current)) {
|
|
24146
|
+
return current;
|
|
24147
|
+
}
|
|
24148
|
+
}
|
|
24149
|
+
return current; // Last
|
|
24150
|
+
} else if (op === "or") {// Return first truthy, or last
|
|
24151
|
+
for (i=0; i < values.length; i+=1) {
|
|
24152
|
+
current = jsonLogic.apply(values[i], data);
|
|
24153
|
+
if ( jsonLogic.truthy(current) ) {
|
|
24154
|
+
return current;
|
|
24155
|
+
}
|
|
24156
|
+
}
|
|
24157
|
+
return current; // Last
|
|
24158
|
+
} else if (op === "filter") {
|
|
24159
|
+
scopedData = jsonLogic.apply(values[0], data);
|
|
24160
|
+
scopedLogic = values[1];
|
|
24161
|
+
|
|
24162
|
+
if ( ! Array.isArray(scopedData)) {
|
|
24163
|
+
return [];
|
|
24164
|
+
}
|
|
24165
|
+
// Return only the elements from the array in the first argument,
|
|
24166
|
+
// that return truthy when passed to the logic in the second argument.
|
|
24167
|
+
// For parity with JavaScript, reindex the returned array
|
|
24168
|
+
return scopedData.filter(function(datum) {
|
|
24169
|
+
return jsonLogic.truthy( jsonLogic.apply(scopedLogic, datum));
|
|
24170
|
+
});
|
|
24171
|
+
} else if (op === "map") {
|
|
24172
|
+
scopedData = jsonLogic.apply(values[0], data);
|
|
24173
|
+
scopedLogic = values[1];
|
|
24174
|
+
|
|
24175
|
+
if ( ! Array.isArray(scopedData)) {
|
|
24176
|
+
return [];
|
|
24177
|
+
}
|
|
24178
|
+
|
|
24179
|
+
return scopedData.map(function(datum) {
|
|
24180
|
+
return jsonLogic.apply(scopedLogic, datum);
|
|
24181
|
+
});
|
|
24182
|
+
} else if (op === "reduce") {
|
|
24183
|
+
scopedData = jsonLogic.apply(values[0], data);
|
|
24184
|
+
scopedLogic = values[1];
|
|
24185
|
+
initial = typeof values[2] !== "undefined" ? jsonLogic.apply(values[2], data) : null;
|
|
24186
|
+
|
|
24187
|
+
if ( ! Array.isArray(scopedData)) {
|
|
24188
|
+
return initial;
|
|
24189
|
+
}
|
|
24190
|
+
|
|
24191
|
+
return scopedData.reduce(
|
|
24192
|
+
function(accumulator, current) {
|
|
24193
|
+
return jsonLogic.apply(
|
|
24194
|
+
scopedLogic,
|
|
24195
|
+
{current: current, accumulator: accumulator}
|
|
24196
|
+
);
|
|
24197
|
+
},
|
|
24198
|
+
initial
|
|
24199
|
+
);
|
|
24200
|
+
} else if (op === "all") {
|
|
24201
|
+
scopedData = jsonLogic.apply(values[0], data);
|
|
24202
|
+
scopedLogic = values[1];
|
|
24203
|
+
// All of an empty set is false. Note, some and none have correct fallback after the for loop
|
|
24204
|
+
if ( ! Array.isArray(scopedData) || ! scopedData.length) {
|
|
24205
|
+
return false;
|
|
24206
|
+
}
|
|
24207
|
+
for (i=0; i < scopedData.length; i+=1) {
|
|
24208
|
+
if ( ! jsonLogic.truthy( jsonLogic.apply(scopedLogic, scopedData[i]) )) {
|
|
24209
|
+
return false; // First falsy, short circuit
|
|
24210
|
+
}
|
|
24211
|
+
}
|
|
24212
|
+
return true; // All were truthy
|
|
24213
|
+
} else if (op === "none") {
|
|
24214
|
+
scopedData = jsonLogic.apply(values[0], data);
|
|
24215
|
+
scopedLogic = values[1];
|
|
24216
|
+
|
|
24217
|
+
if ( ! Array.isArray(scopedData) || ! scopedData.length) {
|
|
24218
|
+
return true;
|
|
24219
|
+
}
|
|
24220
|
+
for (i=0; i < scopedData.length; i+=1) {
|
|
24221
|
+
if ( jsonLogic.truthy( jsonLogic.apply(scopedLogic, scopedData[i]) )) {
|
|
24222
|
+
return false; // First truthy, short circuit
|
|
24223
|
+
}
|
|
24224
|
+
}
|
|
24225
|
+
return true; // None were truthy
|
|
24226
|
+
} else if (op === "some") {
|
|
24227
|
+
scopedData = jsonLogic.apply(values[0], data);
|
|
24228
|
+
scopedLogic = values[1];
|
|
24229
|
+
|
|
24230
|
+
if ( ! Array.isArray(scopedData) || ! scopedData.length) {
|
|
24231
|
+
return false;
|
|
24232
|
+
}
|
|
24233
|
+
for (i=0; i < scopedData.length; i+=1) {
|
|
24234
|
+
if ( jsonLogic.truthy( jsonLogic.apply(scopedLogic, scopedData[i]) )) {
|
|
24235
|
+
return true; // First truthy, short circuit
|
|
24236
|
+
}
|
|
24237
|
+
}
|
|
24238
|
+
return false; // None were truthy
|
|
24239
|
+
}
|
|
24240
|
+
|
|
24241
|
+
// Everyone else gets immediate depth-first recursion
|
|
24242
|
+
values = values.map(function(val) {
|
|
24243
|
+
return jsonLogic.apply(val, data);
|
|
24244
|
+
});
|
|
24245
|
+
|
|
24246
|
+
|
|
24247
|
+
// The operation is called with "data" bound to its "this" and "values" passed as arguments.
|
|
24248
|
+
// Structured commands like % or > can name formal arguments while flexible commands (like missing or merge) can operate on the pseudo-array arguments
|
|
24249
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
|
|
24250
|
+
if (operations.hasOwnProperty(op) && typeof operations[op] === "function") {
|
|
24251
|
+
return operations[op].apply(data, values);
|
|
24252
|
+
} else if (op.indexOf(".") > 0) { // Contains a dot, and not in the 0th position
|
|
24253
|
+
var sub_ops = String(op).split(".");
|
|
24254
|
+
var operation = operations;
|
|
24255
|
+
for (i = 0; i < sub_ops.length; i++) {
|
|
24256
|
+
if (!operation.hasOwnProperty(sub_ops[i])) {
|
|
24257
|
+
throw new Error("Unrecognized operation " + op +
|
|
24258
|
+
" (failed at " + sub_ops.slice(0, i+1).join(".") + ")");
|
|
24259
|
+
}
|
|
24260
|
+
// Descending into operations
|
|
24261
|
+
operation = operation[sub_ops[i]];
|
|
24262
|
+
}
|
|
24263
|
+
|
|
24264
|
+
return operation.apply(data, values);
|
|
24265
|
+
}
|
|
24266
|
+
|
|
24267
|
+
throw new Error("Unrecognized operation " + op );
|
|
24268
|
+
};
|
|
24269
|
+
|
|
24270
|
+
jsonLogic.uses_data = function(logic) {
|
|
24271
|
+
var collection = [];
|
|
24272
|
+
|
|
24273
|
+
if (jsonLogic.is_logic(logic)) {
|
|
24274
|
+
var op = jsonLogic.get_operator(logic);
|
|
24275
|
+
var values = logic[op];
|
|
24276
|
+
|
|
24277
|
+
if ( ! Array.isArray(values)) {
|
|
24278
|
+
values = [values];
|
|
24279
|
+
}
|
|
24280
|
+
|
|
24281
|
+
if (op === "var") {
|
|
24282
|
+
// This doesn't cover the case where the arg to var is itself a rule.
|
|
24283
|
+
collection.push(values[0]);
|
|
24284
|
+
} else {
|
|
24285
|
+
// Recursion!
|
|
24286
|
+
values.forEach(function(val) {
|
|
24287
|
+
collection.push.apply(collection, jsonLogic.uses_data(val) );
|
|
24288
|
+
});
|
|
24289
|
+
}
|
|
24290
|
+
}
|
|
24291
|
+
|
|
24292
|
+
return arrayUnique(collection);
|
|
24293
|
+
};
|
|
24294
|
+
|
|
24295
|
+
jsonLogic.add_operation = function(name, code) {
|
|
24296
|
+
operations[name] = code;
|
|
24297
|
+
};
|
|
24298
|
+
|
|
24299
|
+
jsonLogic.rm_operation = function(name) {
|
|
24300
|
+
delete operations[name];
|
|
24301
|
+
};
|
|
24302
|
+
|
|
24303
|
+
jsonLogic.rule_like = function(rule, pattern) {
|
|
24304
|
+
// console.log("Is ". JSON.stringify(rule) . " like " . JSON.stringify(pattern) . "?");
|
|
24305
|
+
if (pattern === rule) {
|
|
24306
|
+
return true;
|
|
24307
|
+
} // TODO : Deep object equivalency?
|
|
24308
|
+
if (pattern === "@") {
|
|
24309
|
+
return true;
|
|
24310
|
+
} // Wildcard!
|
|
24311
|
+
if (pattern === "number") {
|
|
24312
|
+
return (typeof rule === "number");
|
|
24313
|
+
}
|
|
24314
|
+
if (pattern === "string") {
|
|
24315
|
+
return (typeof rule === "string");
|
|
24316
|
+
}
|
|
24317
|
+
if (pattern === "array") {
|
|
24318
|
+
// !logic test might be superfluous in JavaScript
|
|
24319
|
+
return Array.isArray(rule) && ! jsonLogic.is_logic(rule);
|
|
24320
|
+
}
|
|
24321
|
+
|
|
24322
|
+
if (jsonLogic.is_logic(pattern)) {
|
|
24323
|
+
if (jsonLogic.is_logic(rule)) {
|
|
24324
|
+
var pattern_op = jsonLogic.get_operator(pattern);
|
|
24325
|
+
var rule_op = jsonLogic.get_operator(rule);
|
|
24326
|
+
|
|
24327
|
+
if (pattern_op === "@" || pattern_op === rule_op) {
|
|
24328
|
+
// echo "\nOperators match, go deeper\n";
|
|
24329
|
+
return jsonLogic.rule_like(
|
|
24330
|
+
jsonLogic.get_values(rule, false),
|
|
24331
|
+
jsonLogic.get_values(pattern, false)
|
|
24332
|
+
);
|
|
24333
|
+
}
|
|
24334
|
+
}
|
|
24335
|
+
return false; // pattern is logic, rule isn't, can't be eq
|
|
24336
|
+
}
|
|
24337
|
+
|
|
24338
|
+
if (Array.isArray(pattern)) {
|
|
24339
|
+
if (Array.isArray(rule)) {
|
|
24340
|
+
if (pattern.length !== rule.length) {
|
|
24341
|
+
return false;
|
|
24342
|
+
}
|
|
24343
|
+
/*
|
|
24344
|
+
Note, array order MATTERS, because we're using this array test logic to consider arguments, where order can matter. (e.g., + is commutative, but '-' or 'if' or 'var' are NOT)
|
|
24345
|
+
*/
|
|
24346
|
+
for (var i = 0; i < pattern.length; i += 1) {
|
|
24347
|
+
// If any fail, we fail
|
|
24348
|
+
if ( ! jsonLogic.rule_like(rule[i], pattern[i])) {
|
|
24349
|
+
return false;
|
|
24350
|
+
}
|
|
24351
|
+
}
|
|
24352
|
+
return true; // If they *all* passed, we pass
|
|
24353
|
+
} else {
|
|
24354
|
+
return false; // Pattern is array, rule isn't
|
|
24355
|
+
}
|
|
24356
|
+
}
|
|
24357
|
+
|
|
24358
|
+
// Not logic, not array, not a === match for rule.
|
|
24359
|
+
return false;
|
|
24360
|
+
};
|
|
24361
|
+
|
|
24362
|
+
return jsonLogic;
|
|
24363
|
+
}));
|
|
24364
|
+
} (logic$1));
|
|
24365
|
+
return logic$1.exports;
|
|
24366
|
+
}
|
|
24367
|
+
|
|
24368
|
+
var logicExports = requireLogic();
|
|
24369
|
+
var jsonLogic = /*@__PURE__*/getDefaultExportFromCjs(logicExports);
|
|
24370
|
+
|
|
24371
|
+
/**
|
|
24372
|
+
* Shared helper to recursively lowercase strings in nested structures
|
|
24373
|
+
* @param {*} obj - Value to process
|
|
24374
|
+
* @param {boolean} lowercaseKeys - Whether to lowercase object keys
|
|
24375
|
+
* @returns {*} Processed value with lowercased strings
|
|
24376
|
+
*/
|
|
24377
|
+
var lowercaseJson = function(obj, lowercaseKeys) {
|
|
24378
|
+
if (obj === null || obj === undefined) {
|
|
24379
|
+
return obj;
|
|
24380
|
+
} else if (typeof obj === 'string') {
|
|
24381
|
+
return obj.toLowerCase();
|
|
24382
|
+
} else if (Array.isArray(obj)) {
|
|
24383
|
+
return obj.map(function(item) {
|
|
24384
|
+
return lowercaseJson(item, lowercaseKeys);
|
|
24385
|
+
});
|
|
24386
|
+
} else if (obj === Object(obj)) {
|
|
24387
|
+
var result = {};
|
|
24388
|
+
for (var key in obj) {
|
|
24389
|
+
if (obj.hasOwnProperty(key)) {
|
|
24390
|
+
var newKey = lowercaseKeys && typeof key === 'string' ? key.toLowerCase() : key;
|
|
24391
|
+
result[newKey] = lowercaseJson(obj[key], lowercaseKeys);
|
|
24392
|
+
}
|
|
24393
|
+
}
|
|
24394
|
+
return result;
|
|
24395
|
+
} else {
|
|
24396
|
+
return obj;
|
|
24397
|
+
}
|
|
24398
|
+
};
|
|
24399
|
+
|
|
24400
|
+
/**
|
|
24401
|
+
* Lowercase all string keys and values in a nested structure
|
|
24402
|
+
* @param {*} val - Value to process
|
|
24403
|
+
* @returns {*} Processed value with lowercased strings
|
|
24404
|
+
*/
|
|
24405
|
+
var lowercaseKeysAndValues = function(val) {
|
|
24406
|
+
return lowercaseJson(val, true);
|
|
24407
|
+
};
|
|
24408
|
+
|
|
24409
|
+
/**
|
|
24410
|
+
* Lowercase only leaf node string values in a nested structure (keys unchanged)
|
|
24411
|
+
* @param {*} val - Value to process
|
|
24412
|
+
* @returns {*} Processed value with lowercased leaf strings
|
|
24413
|
+
*/
|
|
24414
|
+
var lowercaseOnlyLeafNodes = function(val) {
|
|
24415
|
+
return lowercaseJson(val, false);
|
|
24416
|
+
};
|
|
24417
|
+
|
|
24418
|
+
/**
|
|
24419
|
+
* Check if an event matches the given criteria
|
|
24420
|
+
* @param {string} eventName - The name of the event being checked
|
|
24421
|
+
* @param {Object} properties - Event properties to evaluate against property filters
|
|
24422
|
+
* @param {Object} criteria - Criteria to match against, with:
|
|
24423
|
+
* - event_name: string - Required event name (case-sensitive match)
|
|
24424
|
+
* - property_filters: Object - Optional JsonLogic filters for properties
|
|
24425
|
+
* @returns {Object} Result object with:
|
|
24426
|
+
* - matches: boolean - Whether the event matches the criteria
|
|
24427
|
+
* - error: string|undefined - Error message if evaluation failed
|
|
24428
|
+
*/
|
|
24429
|
+
var eventMatchesCriteria = function(eventName, properties, criteria) {
|
|
24430
|
+
// Check exact event name match (case-sensitive)
|
|
24431
|
+
if (eventName !== criteria.event_name) {
|
|
24432
|
+
return { matches: false };
|
|
24433
|
+
}
|
|
24434
|
+
|
|
24435
|
+
// Evaluate property filters using JsonLogic
|
|
24436
|
+
var propertyFilters = criteria.property_filters;
|
|
24437
|
+
var filtersMatch = true; // default to true if no filters
|
|
24438
|
+
|
|
24439
|
+
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
24440
|
+
try {
|
|
24441
|
+
// Lowercase all keys and values in event properties for case-insensitive matching
|
|
24442
|
+
var lowercasedProperties = lowercaseKeysAndValues(properties || {});
|
|
24443
|
+
|
|
24444
|
+
// Lowercase only leaf nodes in JsonLogic filters (keep operators intact)
|
|
24445
|
+
var lowercasedFilters = lowercaseOnlyLeafNodes(propertyFilters);
|
|
24446
|
+
|
|
24447
|
+
filtersMatch = jsonLogic.apply(lowercasedFilters, lowercasedProperties);
|
|
24448
|
+
} catch (error) {
|
|
24449
|
+
return {
|
|
24450
|
+
matches: false,
|
|
24451
|
+
error: error.toString()
|
|
24452
|
+
};
|
|
24453
|
+
}
|
|
24454
|
+
}
|
|
24455
|
+
|
|
24456
|
+
return { matches: filtersMatch };
|
|
24457
|
+
};
|
|
24458
|
+
|
|
24459
|
+
// Create targeting library object
|
|
24460
|
+
var targetingLibrary = {};
|
|
24461
|
+
targetingLibrary['eventMatchesCriteria'] = eventMatchesCriteria;
|
|
24462
|
+
|
|
24463
|
+
// Set global Promise (use bracket notation to prevent minification)
|
|
24464
|
+
// This is the ONE AND ONLY global - matches recorder pattern
|
|
24465
|
+
win[TARGETING_GLOBAL_NAME] = Promise.resolve(targetingLibrary);
|
|
23769
24466
|
|
|
23770
24467
|
/** @const */ var DEFAULT_RAGE_CLICK_THRESHOLD_PX = 30;
|
|
23771
24468
|
/** @const */ var DEFAULT_RAGE_CLICK_TIMEOUT_MS = 1000;
|
|
@@ -24736,14 +25433,62 @@
|
|
|
24736
25433
|
// TODO integrate error_reporter from mixpanel instance
|
|
24737
25434
|
safewrapClass(Autocapture);
|
|
24738
25435
|
|
|
24739
|
-
|
|
25436
|
+
/**
|
|
25437
|
+
* Get the promise-based targeting loader
|
|
25438
|
+
* @param {Function} loadExtraBundle - Function to load external bundle (callback-based)
|
|
25439
|
+
* @param {string} targetingSrc - URL to targeting bundle
|
|
25440
|
+
* @returns {Promise} Promise that resolves with targeting library
|
|
25441
|
+
*/
|
|
25442
|
+
var getTargetingPromise = function(loadExtraBundle, targetingSrc) {
|
|
25443
|
+
// Return existing promise if already initialized or loading
|
|
25444
|
+
if (win[TARGETING_GLOBAL_NAME] && typeof win[TARGETING_GLOBAL_NAME].then === 'function') {
|
|
25445
|
+
return win[TARGETING_GLOBAL_NAME];
|
|
25446
|
+
}
|
|
25447
|
+
|
|
25448
|
+
// Create loading promise and set it as the global immediately
|
|
25449
|
+
// This makes minified build behavior consistent with dev/CJS builds
|
|
25450
|
+
win[TARGETING_GLOBAL_NAME] = new Promise(function (resolve) {
|
|
25451
|
+
loadExtraBundle(targetingSrc, resolve);
|
|
25452
|
+
}).then(function () {
|
|
25453
|
+
var p = win[TARGETING_GLOBAL_NAME];
|
|
25454
|
+
if (p && typeof p.then === 'function') {
|
|
25455
|
+
return p;
|
|
25456
|
+
}
|
|
25457
|
+
throw new Error('targeting failed to load');
|
|
25458
|
+
}).catch(function (err) {
|
|
25459
|
+
delete win[TARGETING_GLOBAL_NAME];
|
|
25460
|
+
throw err;
|
|
25461
|
+
});
|
|
24740
25462
|
|
|
25463
|
+
return win[TARGETING_GLOBAL_NAME];
|
|
25464
|
+
};
|
|
25465
|
+
|
|
25466
|
+
var logger = console_with_prefix('flags');
|
|
24741
25467
|
var FLAGS_CONFIG_KEY = 'flags';
|
|
24742
25468
|
|
|
24743
25469
|
var CONFIG_CONTEXT = 'context';
|
|
24744
25470
|
var CONFIG_DEFAULTS = {};
|
|
24745
25471
|
CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
|
|
24746
25472
|
|
|
25473
|
+
/**
|
|
25474
|
+
* Generate a unique key for a pending first-time event
|
|
25475
|
+
* @param {string} flagKey - The flag key
|
|
25476
|
+
* @param {string} firstTimeEventHash - The first_time_event_hash from the pending event definition
|
|
25477
|
+
* @returns {string} Composite key in format "flagKey:firstTimeEventHash"
|
|
25478
|
+
*/
|
|
25479
|
+
var getPendingEventKey = function(flagKey, firstTimeEventHash) {
|
|
25480
|
+
return flagKey + ':' + firstTimeEventHash;
|
|
25481
|
+
};
|
|
25482
|
+
|
|
25483
|
+
/**
|
|
25484
|
+
* Extract the flag key from a pending event key
|
|
25485
|
+
* @param {string} eventKey - The composite event key in format "flagKey:firstTimeEventHash"
|
|
25486
|
+
* @returns {string} The flag key portion
|
|
25487
|
+
*/
|
|
25488
|
+
var getFlagKeyFromPendingEventKey = function(eventKey) {
|
|
25489
|
+
return eventKey.split(':')[0];
|
|
25490
|
+
};
|
|
25491
|
+
|
|
24747
25492
|
/**
|
|
24748
25493
|
* FeatureFlagManager: support for Mixpanel's feature flagging product
|
|
24749
25494
|
* @constructor
|
|
@@ -24755,6 +25500,8 @@
|
|
|
24755
25500
|
this.setMpConfig = initOptions.setConfigFunc;
|
|
24756
25501
|
this.getMpProperty = initOptions.getPropertyFunc;
|
|
24757
25502
|
this.track = initOptions.trackingFunc;
|
|
25503
|
+
this.loadExtraBundle = initOptions.loadExtraBundle || function() {};
|
|
25504
|
+
this.targetingSrc = initOptions.targetingSrc || '';
|
|
24758
25505
|
};
|
|
24759
25506
|
|
|
24760
25507
|
FeatureFlagManager.prototype.init = function() {
|
|
@@ -24767,6 +25514,8 @@
|
|
|
24767
25514
|
this.fetchFlags();
|
|
24768
25515
|
|
|
24769
25516
|
this.trackedFeatures = new Set();
|
|
25517
|
+
this.pendingFirstTimeEvents = {};
|
|
25518
|
+
this.activatedFirstTimeEvents = {};
|
|
24770
25519
|
};
|
|
24771
25520
|
|
|
24772
25521
|
FeatureFlagManager.prototype.getFullConfig = function() {
|
|
@@ -24847,17 +25596,78 @@
|
|
|
24847
25596
|
throw new Error('No flags in API response');
|
|
24848
25597
|
}
|
|
24849
25598
|
var flags = new Map();
|
|
25599
|
+
var pendingFirstTimeEvents = {};
|
|
25600
|
+
|
|
25601
|
+
// Process flags from response
|
|
24850
25602
|
_.each(responseFlags, function(data, key) {
|
|
24851
|
-
|
|
24852
|
-
|
|
24853
|
-
|
|
24854
|
-
|
|
24855
|
-
|
|
24856
|
-
|
|
25603
|
+
// Check if this flag has any activated first-time events this session
|
|
25604
|
+
var hasActivatedEvent = false;
|
|
25605
|
+
var prefix = key + ':';
|
|
25606
|
+
_.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
|
|
25607
|
+
if (eventKey.startsWith(prefix)) {
|
|
25608
|
+
hasActivatedEvent = true;
|
|
25609
|
+
}
|
|
24857
25610
|
});
|
|
24858
|
-
|
|
25611
|
+
|
|
25612
|
+
if (hasActivatedEvent) {
|
|
25613
|
+
// Preserve the activated variant, don't overwrite with server's current variant
|
|
25614
|
+
var currentFlag = this.flags && this.flags.get(key);
|
|
25615
|
+
if (currentFlag) {
|
|
25616
|
+
flags.set(key, currentFlag);
|
|
25617
|
+
}
|
|
25618
|
+
} else {
|
|
25619
|
+
// Use server's current variant
|
|
25620
|
+
flags.set(key, {
|
|
25621
|
+
'key': data['variant_key'],
|
|
25622
|
+
'value': data['variant_value'],
|
|
25623
|
+
'experiment_id': data['experiment_id'],
|
|
25624
|
+
'is_experiment_active': data['is_experiment_active'],
|
|
25625
|
+
'is_qa_tester': data['is_qa_tester']
|
|
25626
|
+
});
|
|
25627
|
+
}
|
|
25628
|
+
}, this);
|
|
25629
|
+
|
|
25630
|
+
// Process top-level pending_first_time_events array
|
|
25631
|
+
var topLevelDefinitions = responseBody['pending_first_time_events'];
|
|
25632
|
+
if (topLevelDefinitions && topLevelDefinitions.length > 0) {
|
|
25633
|
+
_.each(topLevelDefinitions, function(def) {
|
|
25634
|
+
var flagKey = def['flag_key'];
|
|
25635
|
+
var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
|
|
25636
|
+
|
|
25637
|
+
// Skip if this specific event has already been activated this session
|
|
25638
|
+
if (this.activatedFirstTimeEvents[eventKey]) {
|
|
25639
|
+
return;
|
|
25640
|
+
}
|
|
25641
|
+
|
|
25642
|
+
// Store pending event definition using composite key
|
|
25643
|
+
pendingFirstTimeEvents[eventKey] = {
|
|
25644
|
+
'flag_key': flagKey,
|
|
25645
|
+
'flag_id': def['flag_id'],
|
|
25646
|
+
'project_id': def['project_id'],
|
|
25647
|
+
'first_time_event_hash': def['first_time_event_hash'],
|
|
25648
|
+
'event_name': def['event_name'],
|
|
25649
|
+
'property_filters': def['property_filters'],
|
|
25650
|
+
'pending_variant': def['pending_variant']
|
|
25651
|
+
};
|
|
25652
|
+
}, this);
|
|
25653
|
+
}
|
|
25654
|
+
|
|
25655
|
+
// Preserve any activated orphaned flags (flags that were activated but are no longer in response)
|
|
25656
|
+
if (this.activatedFirstTimeEvents) {
|
|
25657
|
+
_.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
|
|
25658
|
+
var flagKey = getFlagKeyFromPendingEventKey(eventKey);
|
|
25659
|
+
if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
|
|
25660
|
+
// Keep the activated flag even though it's not in the new response
|
|
25661
|
+
flags.set(flagKey, this.flags.get(flagKey));
|
|
25662
|
+
}
|
|
25663
|
+
}, this);
|
|
25664
|
+
}
|
|
25665
|
+
|
|
24859
25666
|
this.flags = flags;
|
|
25667
|
+
this.pendingFirstTimeEvents = pendingFirstTimeEvents;
|
|
24860
25668
|
this._traceparent = traceparent;
|
|
25669
|
+
|
|
25670
|
+
this._loadTargetingIfNeeded();
|
|
24861
25671
|
}.bind(this)).catch(function(error) {
|
|
24862
25672
|
this.markFetchComplete();
|
|
24863
25673
|
logger.error(error);
|
|
@@ -24881,6 +25691,177 @@
|
|
|
24881
25691
|
this._fetchInProgressStartTime = null;
|
|
24882
25692
|
};
|
|
24883
25693
|
|
|
25694
|
+
/**
|
|
25695
|
+
* Proactively load targeting bundle if any pending events have property filters
|
|
25696
|
+
*/
|
|
25697
|
+
FeatureFlagManager.prototype._loadTargetingIfNeeded = function() {
|
|
25698
|
+
var hasPropertyFilters = false;
|
|
25699
|
+
_.each(this.pendingFirstTimeEvents, function(evt) {
|
|
25700
|
+
if (evt['property_filters'] && !_.isEmptyObject(evt['property_filters'])) {
|
|
25701
|
+
hasPropertyFilters = true;
|
|
25702
|
+
}
|
|
25703
|
+
});
|
|
25704
|
+
|
|
25705
|
+
if (hasPropertyFilters) {
|
|
25706
|
+
this.getTargeting().then(function() {
|
|
25707
|
+
logger.log('targeting loaded for property filter evaluation');
|
|
25708
|
+
});
|
|
25709
|
+
}
|
|
25710
|
+
};
|
|
25711
|
+
|
|
25712
|
+
/**
|
|
25713
|
+
* Get the targeting library (initializes if not already loaded)
|
|
25714
|
+
* This method is primarily for testing - production code should rely on automatic loading
|
|
25715
|
+
* @returns {Promise} Promise that resolves with targeting library
|
|
25716
|
+
*/
|
|
25717
|
+
FeatureFlagManager.prototype.getTargeting = function() {
|
|
25718
|
+
return getTargetingPromise(
|
|
25719
|
+
this.loadExtraBundle.bind(this),
|
|
25720
|
+
this.targetingSrc
|
|
25721
|
+
).catch(function(error) {
|
|
25722
|
+
logger.error('Failed to load targeting: ' + error);
|
|
25723
|
+
}.bind(this));
|
|
25724
|
+
};
|
|
25725
|
+
|
|
25726
|
+
/**
|
|
25727
|
+
* Check if a tracked event matches any pending first-time events and activate the corresponding flag variant
|
|
25728
|
+
* @param {string} eventName - The name of the event being tracked
|
|
25729
|
+
* @param {Object} properties - Event properties to evaluate against property filters
|
|
25730
|
+
*
|
|
25731
|
+
* When a match is found (event name matches and property filters pass), this method:
|
|
25732
|
+
* - Switches the flag to the pending variant
|
|
25733
|
+
* - Marks the event as activated for this session
|
|
25734
|
+
* - Records the activation via the API (fire-and-forget)
|
|
25735
|
+
*/
|
|
25736
|
+
FeatureFlagManager.prototype.checkFirstTimeEvents = function(eventName, properties) {
|
|
25737
|
+
if (!this.pendingFirstTimeEvents || _.isEmptyObject(this.pendingFirstTimeEvents)) {
|
|
25738
|
+
return;
|
|
25739
|
+
}
|
|
25740
|
+
|
|
25741
|
+
// Check if targeting promise exists (either bundled or async loaded)
|
|
25742
|
+
if (win[TARGETING_GLOBAL_NAME] && _.isFunction(win[TARGETING_GLOBAL_NAME].then)) {
|
|
25743
|
+
win[TARGETING_GLOBAL_NAME].then(function(library) {
|
|
25744
|
+
this._processFirstTimeEventCheck(eventName, properties, library);
|
|
25745
|
+
}.bind(this)).catch(function() {
|
|
25746
|
+
// If targeting failed to load, process with null
|
|
25747
|
+
// Events without property filters will still match
|
|
25748
|
+
this._processFirstTimeEventCheck(eventName, properties, null);
|
|
25749
|
+
}.bind(this));
|
|
25750
|
+
} else {
|
|
25751
|
+
// No targeting available, process with null
|
|
25752
|
+
// Events without property filters will still match
|
|
25753
|
+
this._processFirstTimeEventCheck(eventName, properties, null);
|
|
25754
|
+
}
|
|
25755
|
+
};
|
|
25756
|
+
|
|
25757
|
+
/**
|
|
25758
|
+
* Internal method to process first-time event checks with loaded targeting library
|
|
25759
|
+
* @param {string} eventName - The name of the event being tracked
|
|
25760
|
+
* @param {Object} properties - Event properties to evaluate against property filters
|
|
25761
|
+
* @param {Object} targeting - The loaded targeting library
|
|
25762
|
+
*/
|
|
25763
|
+
FeatureFlagManager.prototype._processFirstTimeEventCheck = function(eventName, properties, targeting) {
|
|
25764
|
+
_.each(this.pendingFirstTimeEvents, function(pendingEvent, eventKey) {
|
|
25765
|
+
if (this.activatedFirstTimeEvents[eventKey]) {
|
|
25766
|
+
return;
|
|
25767
|
+
}
|
|
25768
|
+
|
|
25769
|
+
var flagKey = pendingEvent['flag_key'];
|
|
25770
|
+
|
|
25771
|
+
// Use targeting module to check if event matches
|
|
25772
|
+
var matchResult;
|
|
25773
|
+
|
|
25774
|
+
// If no targeting library and event has property filters, skip it
|
|
25775
|
+
if (!targeting && pendingEvent['property_filters'] && !_.isEmptyObject(pendingEvent['property_filters'])) {
|
|
25776
|
+
logger.warn('Skipping event check for "' + flagKey + '" - property filters require targeting library');
|
|
25777
|
+
return;
|
|
25778
|
+
}
|
|
25779
|
+
|
|
25780
|
+
// For simple events (no property filters), just check event name
|
|
25781
|
+
if (!targeting) {
|
|
25782
|
+
matchResult = {
|
|
25783
|
+
matches: eventName === pendingEvent['event_name'],
|
|
25784
|
+
error: null
|
|
25785
|
+
};
|
|
25786
|
+
} else {
|
|
25787
|
+
var criteria = {
|
|
25788
|
+
'event_name': pendingEvent['event_name'],
|
|
25789
|
+
'property_filters': pendingEvent['property_filters']
|
|
25790
|
+
};
|
|
25791
|
+
matchResult = targeting['eventMatchesCriteria'](
|
|
25792
|
+
eventName,
|
|
25793
|
+
properties,
|
|
25794
|
+
criteria
|
|
25795
|
+
);
|
|
25796
|
+
}
|
|
25797
|
+
|
|
25798
|
+
if (matchResult.error) {
|
|
25799
|
+
logger.error('Error checking first-time event for flag "' + flagKey + '": ' + matchResult.error);
|
|
25800
|
+
return;
|
|
25801
|
+
}
|
|
25802
|
+
|
|
25803
|
+
if (!matchResult.matches) {
|
|
25804
|
+
return;
|
|
25805
|
+
}
|
|
25806
|
+
|
|
25807
|
+
logger.log('First-time event matched for flag "' + flagKey + '": ' + eventName);
|
|
25808
|
+
|
|
25809
|
+
var newVariant = {
|
|
25810
|
+
'key': pendingEvent['pending_variant']['variant_key'],
|
|
25811
|
+
'value': pendingEvent['pending_variant']['variant_value'],
|
|
25812
|
+
'experiment_id': pendingEvent['pending_variant']['experiment_id'],
|
|
25813
|
+
'is_experiment_active': pendingEvent['pending_variant']['is_experiment_active']
|
|
25814
|
+
};
|
|
25815
|
+
|
|
25816
|
+
this.flags.set(flagKey, newVariant);
|
|
25817
|
+
this.activatedFirstTimeEvents[eventKey] = true;
|
|
25818
|
+
|
|
25819
|
+
this.recordFirstTimeEvent(
|
|
25820
|
+
pendingEvent['flag_id'],
|
|
25821
|
+
pendingEvent['project_id'],
|
|
25822
|
+
pendingEvent['first_time_event_hash']
|
|
25823
|
+
);
|
|
25824
|
+
}, this);
|
|
25825
|
+
};
|
|
25826
|
+
|
|
25827
|
+
FeatureFlagManager.prototype.getFirstTimeEventApiRoute = function(flagId) {
|
|
25828
|
+
// Construct URL: {api_host}/flags/{flagId}/first-time-events
|
|
25829
|
+
return this.getFullApiRoute() + '/' + flagId + '/first-time-events';
|
|
25830
|
+
};
|
|
25831
|
+
|
|
25832
|
+
FeatureFlagManager.prototype.recordFirstTimeEvent = function(flagId, projectId, firstTimeEventHash) {
|
|
25833
|
+
var distinctId = this.getMpProperty('distinct_id');
|
|
25834
|
+
var traceparent = generateTraceparent();
|
|
25835
|
+
|
|
25836
|
+
// Build URL with query string parameters
|
|
25837
|
+
var searchParams = new URLSearchParams();
|
|
25838
|
+
searchParams.set('mp_lib', 'web');
|
|
25839
|
+
searchParams.set('$lib_version', Config.LIB_VERSION);
|
|
25840
|
+
var url = this.getFirstTimeEventApiRoute(flagId) + '?' + searchParams.toString();
|
|
25841
|
+
|
|
25842
|
+
var payload = {
|
|
25843
|
+
'distinct_id': distinctId,
|
|
25844
|
+
'project_id': projectId,
|
|
25845
|
+
'first_time_event_hash': firstTimeEventHash
|
|
25846
|
+
};
|
|
25847
|
+
|
|
25848
|
+
logger.log('Recording first-time event for flag: ' + flagId);
|
|
25849
|
+
|
|
25850
|
+
// Fire-and-forget POST request
|
|
25851
|
+
this.fetch.call(win, url, {
|
|
25852
|
+
'method': 'POST',
|
|
25853
|
+
'headers': {
|
|
25854
|
+
'Content-Type': 'application/json',
|
|
25855
|
+
'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
|
|
25856
|
+
'traceparent': traceparent
|
|
25857
|
+
},
|
|
25858
|
+
'body': JSON.stringify(payload)
|
|
25859
|
+
}).catch(function(error) {
|
|
25860
|
+
// Silent failure - cohort sync will catch up
|
|
25861
|
+
logger.error('Failed to record first-time event for flag ' + flagId + ': ' + error);
|
|
25862
|
+
});
|
|
25863
|
+
};
|
|
25864
|
+
|
|
24884
25865
|
FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
|
|
24885
25866
|
if (!this.fetchPromise) {
|
|
24886
25867
|
return new Promise(function(resolve) {
|
|
@@ -24999,6 +25980,9 @@
|
|
|
24999
25980
|
// Deprecated method
|
|
25000
25981
|
FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
|
|
25001
25982
|
|
|
25983
|
+
// Exports intended only for testing
|
|
25984
|
+
FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
|
|
25985
|
+
|
|
25002
25986
|
/* eslint camelcase: "off" */
|
|
25003
25987
|
|
|
25004
25988
|
|
|
@@ -26468,6 +27452,7 @@
|
|
|
26468
27452
|
'record_min_ms': 0,
|
|
26469
27453
|
'record_sessions_percent': 0,
|
|
26470
27454
|
'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js',
|
|
27455
|
+
'targeting_src': 'https://cdn.mxpnl.com/libs/mixpanel-targeting.min.js',
|
|
26471
27456
|
'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
|
|
26472
27457
|
};
|
|
26473
27458
|
|
|
@@ -26697,7 +27682,9 @@
|
|
|
26697
27682
|
getConfigFunc: _.bind(this.get_config, this),
|
|
26698
27683
|
setConfigFunc: _.bind(this.set_config, this),
|
|
26699
27684
|
getPropertyFunc: _.bind(this.get_property, this),
|
|
26700
|
-
trackingFunc: _.bind(this.track, this)
|
|
27685
|
+
trackingFunc: _.bind(this.track, this),
|
|
27686
|
+
loadExtraBundle: load_extra_bundle,
|
|
27687
|
+
targetingSrc: this.get_config('targeting_src')
|
|
26701
27688
|
});
|
|
26702
27689
|
this.flags.init();
|
|
26703
27690
|
this['flags'] = this.flags;
|
|
@@ -26793,11 +27780,11 @@
|
|
|
26793
27780
|
|
|
26794
27781
|
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
26795
27782
|
var handleLoadedRecorder = _.bind(function() {
|
|
26796
|
-
this._recorder = this._recorder || new win[
|
|
27783
|
+
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this);
|
|
26797
27784
|
this._recorder['resumeRecording'](startNewIfInactive);
|
|
26798
27785
|
}, this);
|
|
26799
27786
|
|
|
26800
|
-
if (_.isUndefined(win[
|
|
27787
|
+
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
26801
27788
|
load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
|
|
26802
27789
|
} else {
|
|
26803
27790
|
handleLoadedRecorder();
|
|
@@ -27539,6 +28526,11 @@
|
|
|
27539
28526
|
send_request_options: options
|
|
27540
28527
|
}, callback);
|
|
27541
28528
|
|
|
28529
|
+
// Check for first-time event matches
|
|
28530
|
+
if (this.flags && this.flags.checkFirstTimeEvents) {
|
|
28531
|
+
this.flags.checkFirstTimeEvents(event_name, properties);
|
|
28532
|
+
}
|
|
28533
|
+
|
|
27542
28534
|
return ret;
|
|
27543
28535
|
});
|
|
27544
28536
|
|