@macroui/event-tracker 1.3.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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +343 -0
  3. package/bin/event-tracker-suggest.mjs +141 -0
  4. package/bin/event-tracker.mjs +157 -0
  5. package/dist/event-tracker.cjs +3449 -0
  6. package/dist/event-tracker.js +3455 -0
  7. package/dist/event-tracker.min.js +1 -0
  8. package/dist/event-tracker.mjs +3402 -0
  9. package/dist/plugin/auto-track.cjs +201 -0
  10. package/dist/plugin/auto-track.mjs +199 -0
  11. package/dist/plugin/debug-sender.cjs +214 -0
  12. package/dist/plugin/debug-sender.mjs +211 -0
  13. package/dist/plugin/encrypt.cjs +284 -0
  14. package/dist/plugin/encrypt.mjs +275 -0
  15. package/dist/plugin/exposure.cjs +121 -0
  16. package/dist/plugin/exposure.mjs +119 -0
  17. package/dist/plugin/indexeddb-sender.cjs +94 -0
  18. package/dist/plugin/indexeddb-sender.mjs +92 -0
  19. package/dist/plugin/pageleave.cjs +74 -0
  20. package/dist/plugin/pageleave.mjs +72 -0
  21. package/dist/plugin/pageload.cjs +198 -0
  22. package/dist/plugin/pageload.mjs +196 -0
  23. package/dist/plugin/session-event.cjs +142 -0
  24. package/dist/plugin/session-event.mjs +140 -0
  25. package/dist/plugin/webview-bridge.cjs +111 -0
  26. package/dist/plugin/webview-bridge.mjs +109 -0
  27. package/dist/types/core/env.d.ts +10 -0
  28. package/dist/types/core/errors.d.ts +76 -0
  29. package/dist/types/core/failed-queue.d.ts +24 -0
  30. package/dist/types/core/identity-api.d.ts +6 -0
  31. package/dist/types/core/identity.d.ts +39 -0
  32. package/dist/types/core/instance-channel.d.ts +34 -0
  33. package/dist/types/core/lifecycle.d.ts +26 -0
  34. package/dist/types/core/logger.d.ts +13 -0
  35. package/dist/types/core/plugin.d.ts +60 -0
  36. package/dist/types/core/profile-api.d.ts +6 -0
  37. package/dist/types/core/profile-store.d.ts +23 -0
  38. package/dist/types/core/register-api.d.ts +22 -0
  39. package/dist/types/core/retry-policy.d.ts +33 -0
  40. package/dist/types/core/send-pipeline.d.ts +55 -0
  41. package/dist/types/core/signed-identity.d.ts +29 -0
  42. package/dist/types/core/stage.d.ts +48 -0
  43. package/dist/types/core/track-api.d.ts +6 -0
  44. package/dist/types/core/tracker.d.ts +130 -0
  45. package/dist/types/core/uuid.d.ts +4 -0
  46. package/dist/types/index.d.ts +40 -0
  47. package/dist/types/plugins/auto-track/index.d.ts +21 -0
  48. package/dist/types/plugins/debug-sender/index.d.ts +24 -0
  49. package/dist/types/plugins/encrypt/index.d.ts +56 -0
  50. package/dist/types/plugins/exposure/index.d.ts +18 -0
  51. package/dist/types/plugins/indexeddb-sender/index.d.ts +15 -0
  52. package/dist/types/plugins/pageleave/index.d.ts +12 -0
  53. package/dist/types/plugins/pageload/index.d.ts +14 -0
  54. package/dist/types/plugins/session-event/index.d.ts +14 -0
  55. package/dist/types/plugins/webview-bridge/index.d.ts +33 -0
  56. package/dist/types/presets/preset-properties.d.ts +25 -0
  57. package/dist/types/send/ajax-sender.d.ts +11 -0
  58. package/dist/types/send/batch-sender.d.ts +17 -0
  59. package/dist/types/send/beacon-sender.d.ts +10 -0
  60. package/dist/types/send/encode.d.ts +8 -0
  61. package/dist/types/send/image-sender.d.ts +10 -0
  62. package/dist/types/send/sender.d.ts +20 -0
  63. package/dist/types/storage/indexeddb-store.d.ts +14 -0
  64. package/dist/types/storage/store.d.ts +56 -0
  65. package/dist/types/types/common.d.ts +26 -0
  66. package/dist/types/types/config.d.ts +117 -0
  67. package/dist/types/types/constants.d.ts +110 -0
  68. package/dist/types/types/index.d.ts +25 -0
  69. package/dist/types/types.test-d.d.ts +5 -0
  70. package/dist/types.test-d.ts +103 -0
  71. package/package.json +218 -0
@@ -0,0 +1,275 @@
1
+ /******************************************************************************
2
+ Copyright (c) Microsoft Corporation.
3
+
4
+ Permission to use, copy, modify, and/or distribute this software for any
5
+ purpose with or without fee is hereby granted.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13
+ PERFORMANCE OF THIS SOFTWARE.
14
+ ***************************************************************************** */
15
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
16
+
17
+
18
+ function __awaiter(thisArg, _arguments, P, generator) {
19
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
20
+ return new (P || (P = Promise))(function (resolve, reject) {
21
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
22
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
23
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
24
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
25
+ });
26
+ }
27
+
28
+ function __generator(thisArg, body) {
29
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
30
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
31
+ function verb(n) { return function (v) { return step([n, v]); }; }
32
+ function step(op) {
33
+ if (f) throw new TypeError("Generator is already executing.");
34
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
35
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
36
+ if (y = 0, t) op = [op[0] & 2, t.value];
37
+ switch (op[0]) {
38
+ case 0: case 1: t = op; break;
39
+ case 4: _.label++; return { value: op[1], done: false };
40
+ case 5: _.label++; y = op[1]; op = [0]; continue;
41
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
42
+ default:
43
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
44
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
45
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
46
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
47
+ if (t[2]) _.ops.pop();
48
+ _.trys.pop(); continue;
49
+ }
50
+ op = body.call(thisArg, _);
51
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
52
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
53
+ }
54
+ }
55
+
56
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
57
+ var e = new Error(message);
58
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
59
+ };
60
+
61
+ /**
62
+ * encrypt 插件(v1.1 增强版)
63
+ *
64
+ * 三个能力:
65
+ * 1. 加密 + 解密配对(神策风格)
66
+ * - config.encryptor / config.decoder
67
+ * - 加密函数接收 JSON 字符串返回字符串;解密函数反向
68
+ *
69
+ * 2. Web Crypto AES-GCM
70
+ * - aesGcmEncrypt / aesGcmDecrypt
71
+ * - 基于 Web Crypto API,256-bit key,输出 base64(iv||ciphertext)
72
+ *
73
+ * 3. 钩子:encryptEvent / decryptEvent
74
+ * - 用于在事件被发送前/接收后做二次处理(脱敏、补字段等)
75
+ *
76
+ * 用法示例:
77
+ * import { encryptor, aesGcmEncrypt, aesGcmDecrypt } from '@macroui/event-tracker/plugin/encrypt';
78
+ * const key = 'my-32-byte-secret-key-1234567890ab';
79
+ * sdk.use(encryptor({
80
+ * encrypt: aesGcmEncrypt(key),
81
+ * decrypt: aesGcmDecrypt(key),
82
+ * encryptEvent: (e) => scrubPII(e),
83
+ * decryptEvent: (e) => e,
84
+ * }));
85
+ */
86
+ // ============================================================
87
+ // 1. XOR + Base64(兼容 v1.0,作为默认简易实现)
88
+ // ============================================================
89
+ function xorBase64(input, key) {
90
+ var k = key || 'default';
91
+ var out = '';
92
+ for (var i = 0; i < input.length; i++) {
93
+ out += String.fromCharCode(input.charCodeAt(i) ^ k.charCodeAt(i % k.length));
94
+ }
95
+ if (typeof btoa !== 'undefined')
96
+ return btoa(out);
97
+ return Buffer.from(out, 'binary').toString('base64');
98
+ }
99
+ function defaultEncryptor(key) {
100
+ return function (data) { return xorBase64(data, key); };
101
+ }
102
+ function defaultDecryptor(key) {
103
+ return function (data) {
104
+ var bin = typeof atob !== 'undefined' ? atob(data) : Buffer.from(data, 'base64').toString('binary');
105
+ var out = '';
106
+ var k = key || 'default';
107
+ for (var i = 0; i < bin.length; i++) {
108
+ out += String.fromCharCode(bin.charCodeAt(i) ^ k.charCodeAt(i % k.length));
109
+ }
110
+ return out;
111
+ };
112
+ }
113
+ // ============================================================
114
+ // 2. AES-GCM(Web Crypto API;需要 https / localhost)
115
+ // ============================================================
116
+ var ENC = new TextEncoder();
117
+ var DEC = new TextDecoder();
118
+ function bytesToB64(bytes) {
119
+ if (typeof btoa !== 'undefined') {
120
+ var s = '';
121
+ for (var i = 0; i < bytes.length; i++)
122
+ s += String.fromCharCode(bytes[i]);
123
+ return btoa(s);
124
+ }
125
+ return Buffer.from(bytes).toString('base64');
126
+ }
127
+ function b64ToBytes(b64) {
128
+ if (typeof atob !== 'undefined') {
129
+ var bin = atob(b64);
130
+ var out = new Uint8Array(bin.length);
131
+ for (var i = 0; i < bin.length; i++)
132
+ out[i] = bin.charCodeAt(i);
133
+ return out;
134
+ }
135
+ return new Uint8Array(Buffer.from(b64, 'base64'));
136
+ }
137
+ function importAesKey(rawKey) {
138
+ return __awaiter(this, void 0, void 0, function () {
139
+ var subtle, hash;
140
+ return __generator(this, function (_a) {
141
+ switch (_a.label) {
142
+ case 0:
143
+ subtle = getSubtle();
144
+ return [4 /*yield*/, subtle.digest('SHA-256', ENC.encode(rawKey))];
145
+ case 1:
146
+ hash = _a.sent();
147
+ return [2 /*return*/, subtle.importKey('raw', hash, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt'])];
148
+ }
149
+ });
150
+ });
151
+ }
152
+ function getSubtle() {
153
+ var _a;
154
+ if (typeof globalThis !== 'undefined' && ((_a = globalThis.crypto) === null || _a === void 0 ? void 0 : _a.subtle)) {
155
+ return globalThis.crypto.subtle;
156
+ }
157
+ // node 18+ 的 webcrypto
158
+ try {
159
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
160
+ var webcrypto = require('crypto').webcrypto;
161
+ globalThis.crypto = webcrypto;
162
+ return webcrypto.subtle;
163
+ }
164
+ catch (_b) {
165
+ throw new Error('[encrypt] Web Crypto API not available (need https/localhost or Node 18+)');
166
+ }
167
+ }
168
+ /** AES-GCM 加密(同步 API 抛错;改用 Async 版本)
169
+ * @deprecated Web Crypto API 是异步的,同步签名不现实;
170
+ * 改用 aesGcmEncryptAsync + asyncSend helper,或预计算密文。
171
+ */
172
+ function aesGcmEncrypt(_rawKey) {
173
+ return function () {
174
+ throw new Error('[encrypt] Web Crypto subtle.encrypt is async-only; use aesGcmEncryptAsync and send via asyncSend()');
175
+ };
176
+ }
177
+ /** AES-GCM 加密(异步版):返回 Promise<string> */
178
+ function aesGcmEncryptAsync(rawKey) {
179
+ var _this = this;
180
+ var keyPromise = null;
181
+ return function (data) { return __awaiter(_this, void 0, void 0, function () {
182
+ var key, subtle, iv, cipher, _a, out;
183
+ return __generator(this, function (_b) {
184
+ switch (_b.label) {
185
+ case 0:
186
+ if (!keyPromise)
187
+ keyPromise = importAesKey(rawKey);
188
+ return [4 /*yield*/, keyPromise];
189
+ case 1:
190
+ key = _b.sent();
191
+ subtle = getSubtle();
192
+ iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
193
+ _a = Uint8Array.bind;
194
+ return [4 /*yield*/, subtle.encrypt({ name: 'AES-GCM', iv: iv }, key, ENC.encode(data))];
195
+ case 2:
196
+ cipher = new (_a.apply(Uint8Array, [void 0, _b.sent()]))();
197
+ out = new Uint8Array(iv.length + cipher.length);
198
+ out.set(iv, 0);
199
+ out.set(cipher, iv.length);
200
+ return [2 /*return*/, bytesToB64(out)];
201
+ }
202
+ });
203
+ }); };
204
+ }
205
+ /** AES-GCM 解密(异步版) */
206
+ function aesGcmDecryptAsync(rawKey) {
207
+ var _this = this;
208
+ var keyPromise = null;
209
+ return function (data) { return __awaiter(_this, void 0, void 0, function () {
210
+ var key, subtle, raw, iv, cipher, plain;
211
+ return __generator(this, function (_a) {
212
+ switch (_a.label) {
213
+ case 0:
214
+ if (!keyPromise)
215
+ keyPromise = importAesKey(rawKey);
216
+ return [4 /*yield*/, keyPromise];
217
+ case 1:
218
+ key = _a.sent();
219
+ subtle = getSubtle();
220
+ raw = b64ToBytes(data);
221
+ iv = raw.slice(0, 12);
222
+ cipher = raw.slice(12);
223
+ return [4 /*yield*/, subtle.decrypt({ name: 'AES-GCM', iv: iv }, key, cipher)];
224
+ case 2:
225
+ plain = _a.sent();
226
+ return [2 /*return*/, DEC.decode(plain)];
227
+ }
228
+ });
229
+ }); };
230
+ }
231
+ // ============================================================
232
+ // 3. 插件
233
+ // ============================================================
234
+ function encryptor(opts) {
235
+ return {
236
+ name: 'encryptor',
237
+ install: function (_a) {
238
+ var sdk = _a.sdk, config = _a.config;
239
+ // 写入 config;tracker.sendEvent 会自动调用
240
+ config.encryptor = opts.encrypt;
241
+ config.decoder = opts.decrypt;
242
+ // config.encryptEvent/decryptEvent 在 TrackerConfig 里是宽松的 Record<string,unknown> 函数
243
+ var cfg = config;
244
+ cfg.encryptEvent = opts.encryptEvent;
245
+ cfg.decryptEvent = opts.decryptEvent;
246
+ // 保留到 sdk 上,供调试 / 二次使用
247
+ var ref = sdk;
248
+ ref.__encryptor = opts.encrypt;
249
+ ref.__decoder = opts.decrypt;
250
+ ref.__encryptEvent = opts.encryptEvent;
251
+ ref.__decryptEvent = opts.decryptEvent;
252
+ },
253
+ uninstall: function (ctx) {
254
+ if (!ctx)
255
+ return;
256
+ var config = ctx.config, sdk = ctx.sdk;
257
+ delete config.encryptor;
258
+ delete config.decoder;
259
+ var cfg = config;
260
+ delete cfg.encryptEvent;
261
+ delete cfg.decryptEvent;
262
+ var ref = sdk;
263
+ delete ref.__encryptor;
264
+ delete ref.__decoder;
265
+ delete ref.__encryptEvent;
266
+ delete ref.__decryptEvent;
267
+ },
268
+ };
269
+ }
270
+ /** 便捷工厂:XOR 配对 */
271
+ function xorEncryptor(key) {
272
+ return encryptor({ encrypt: defaultEncryptor(key), decrypt: defaultDecryptor(key) });
273
+ }
274
+
275
+ export { aesGcmDecryptAsync, aesGcmEncrypt, aesGcmEncryptAsync, defaultDecryptor, defaultEncryptor, encryptor, xorBase64, xorEncryptor };
@@ -0,0 +1,121 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 浏览器端辅助方法
5
+ */
6
+ function isBrowser() {
7
+ return typeof window !== 'undefined' && typeof window.document !== 'undefined';
8
+ }
9
+
10
+ /**
11
+ * SDK 预置事件名与预置属性 key(v1.1:完整对齐神策)
12
+ * 命名风格对齐神策,方便跨平台对接
13
+ */
14
+ /** 预置事件名 */
15
+ var PresetEvent = {
16
+ ElementExposure: '$element_exposure'};
17
+ /** 预置属性 key */
18
+ var PresetProperty = {
19
+ // 全埋点元素属性
20
+ ElementSelector: '$element_selector',
21
+ ElementType: '$element_type'};
22
+
23
+ /**
24
+ * exposure 插件:元素曝光(IntersectionObserver)
25
+ * - $element_exposure 事件
26
+ * - 元素进入视口超过阈值持续 X ms 才上报(默认 500ms)
27
+ * - 同元素同阈值只上报一次
28
+ */
29
+ function exposure(opts) {
30
+ if (opts === void 0) { opts = {}; }
31
+ var sdkRef = null;
32
+ var observer = null;
33
+ var reported = new WeakSet();
34
+ function cssPath(el) {
35
+ if (el.id)
36
+ return "#".concat(el.id);
37
+ var parts = [];
38
+ var cur = el;
39
+ while (cur && cur !== document.body && parts.length < 5) {
40
+ var part = cur.tagName.toLowerCase();
41
+ if (cur.classList && cur.classList.length) {
42
+ part += '.' + Array.from(cur.classList).slice(0, 2).join('.');
43
+ }
44
+ parts.unshift(part);
45
+ cur = cur.parentElement;
46
+ }
47
+ return parts.join(' > ');
48
+ }
49
+ function setupObserver() {
50
+ var _a, _b;
51
+ if (!('IntersectionObserver' in window))
52
+ return;
53
+ var threshold = (_a = opts.threshold) !== null && _a !== void 0 ? _a : 0.5;
54
+ var duration = (_b = opts.duration) !== null && _b !== void 0 ? _b : 500;
55
+ observer = new IntersectionObserver(function (entries) {
56
+ var _loop_1 = function (entry) {
57
+ if (entry.intersectionRatio >= threshold) {
58
+ var el_1 = entry.target;
59
+ var timer_1 = setTimeout(function () {
60
+ var _a;
61
+ if (reported.has(el_1))
62
+ return;
63
+ reported.add(el_1);
64
+ if (!sdkRef)
65
+ return;
66
+ var props = (_a = {},
67
+ _a[PresetProperty.ElementSelector] = cssPath(el_1),
68
+ _a[PresetProperty.ElementType] = el_1.tagName.toLowerCase(),
69
+ _a.$intersection_ratio = entry.intersectionRatio,
70
+ _a.$exposure_duration = duration,
71
+ _a);
72
+ if (el_1 instanceof HTMLElement && el_1.dataset) {
73
+ var prefix = opts.attrPrefix || 'data-exp-';
74
+ for (var _i = 0, _b = Object.keys(el_1.dataset); _i < _b.length; _i++) {
75
+ var k = _b[_i];
76
+ if (k.startsWith(prefix)) {
77
+ props[k] = el_1.dataset[k];
78
+ }
79
+ }
80
+ }
81
+ sdkRef.track(PresetEvent.ElementExposure, props);
82
+ }, duration);
83
+ // 离开时取消未上报
84
+ var off_1 = function () {
85
+ clearTimeout(timer_1);
86
+ el_1.removeEventListener('mouseleave', off_1);
87
+ };
88
+ el_1.addEventListener('mouseleave', off_1);
89
+ }
90
+ };
91
+ for (var _i = 0, entries_1 = entries; _i < entries_1.length; _i++) {
92
+ var entry = entries_1[_i];
93
+ _loop_1(entry);
94
+ }
95
+ }, { threshold: [threshold] });
96
+ if (opts.selector) {
97
+ var targets = document.querySelectorAll(opts.selector);
98
+ targets.forEach(function (t) { return observer.observe(t); });
99
+ }
100
+ }
101
+ return {
102
+ name: 'exposure',
103
+ install: function (_a) {
104
+ var sdk = _a.sdk;
105
+ if (!isBrowser())
106
+ return;
107
+ sdkRef = sdk;
108
+ if (document.readyState === 'complete')
109
+ setupObserver();
110
+ else
111
+ window.addEventListener('load', setupObserver, { once: true });
112
+ },
113
+ uninstall: function () {
114
+ observer === null || observer === void 0 ? void 0 : observer.disconnect();
115
+ observer = null;
116
+ sdkRef = null;
117
+ },
118
+ };
119
+ }
120
+
121
+ exports.exposure = exposure;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * 浏览器端辅助方法
3
+ */
4
+ function isBrowser() {
5
+ return typeof window !== 'undefined' && typeof window.document !== 'undefined';
6
+ }
7
+
8
+ /**
9
+ * SDK 预置事件名与预置属性 key(v1.1:完整对齐神策)
10
+ * 命名风格对齐神策,方便跨平台对接
11
+ */
12
+ /** 预置事件名 */
13
+ var PresetEvent = {
14
+ ElementExposure: '$element_exposure'};
15
+ /** 预置属性 key */
16
+ var PresetProperty = {
17
+ // 全埋点元素属性
18
+ ElementSelector: '$element_selector',
19
+ ElementType: '$element_type'};
20
+
21
+ /**
22
+ * exposure 插件:元素曝光(IntersectionObserver)
23
+ * - $element_exposure 事件
24
+ * - 元素进入视口超过阈值持续 X ms 才上报(默认 500ms)
25
+ * - 同元素同阈值只上报一次
26
+ */
27
+ function exposure(opts) {
28
+ if (opts === void 0) { opts = {}; }
29
+ var sdkRef = null;
30
+ var observer = null;
31
+ var reported = new WeakSet();
32
+ function cssPath(el) {
33
+ if (el.id)
34
+ return "#".concat(el.id);
35
+ var parts = [];
36
+ var cur = el;
37
+ while (cur && cur !== document.body && parts.length < 5) {
38
+ var part = cur.tagName.toLowerCase();
39
+ if (cur.classList && cur.classList.length) {
40
+ part += '.' + Array.from(cur.classList).slice(0, 2).join('.');
41
+ }
42
+ parts.unshift(part);
43
+ cur = cur.parentElement;
44
+ }
45
+ return parts.join(' > ');
46
+ }
47
+ function setupObserver() {
48
+ var _a, _b;
49
+ if (!('IntersectionObserver' in window))
50
+ return;
51
+ var threshold = (_a = opts.threshold) !== null && _a !== void 0 ? _a : 0.5;
52
+ var duration = (_b = opts.duration) !== null && _b !== void 0 ? _b : 500;
53
+ observer = new IntersectionObserver(function (entries) {
54
+ var _loop_1 = function (entry) {
55
+ if (entry.intersectionRatio >= threshold) {
56
+ var el_1 = entry.target;
57
+ var timer_1 = setTimeout(function () {
58
+ var _a;
59
+ if (reported.has(el_1))
60
+ return;
61
+ reported.add(el_1);
62
+ if (!sdkRef)
63
+ return;
64
+ var props = (_a = {},
65
+ _a[PresetProperty.ElementSelector] = cssPath(el_1),
66
+ _a[PresetProperty.ElementType] = el_1.tagName.toLowerCase(),
67
+ _a.$intersection_ratio = entry.intersectionRatio,
68
+ _a.$exposure_duration = duration,
69
+ _a);
70
+ if (el_1 instanceof HTMLElement && el_1.dataset) {
71
+ var prefix = opts.attrPrefix || 'data-exp-';
72
+ for (var _i = 0, _b = Object.keys(el_1.dataset); _i < _b.length; _i++) {
73
+ var k = _b[_i];
74
+ if (k.startsWith(prefix)) {
75
+ props[k] = el_1.dataset[k];
76
+ }
77
+ }
78
+ }
79
+ sdkRef.track(PresetEvent.ElementExposure, props);
80
+ }, duration);
81
+ // 离开时取消未上报
82
+ var off_1 = function () {
83
+ clearTimeout(timer_1);
84
+ el_1.removeEventListener('mouseleave', off_1);
85
+ };
86
+ el_1.addEventListener('mouseleave', off_1);
87
+ }
88
+ };
89
+ for (var _i = 0, entries_1 = entries; _i < entries_1.length; _i++) {
90
+ var entry = entries_1[_i];
91
+ _loop_1(entry);
92
+ }
93
+ }, { threshold: [threshold] });
94
+ if (opts.selector) {
95
+ var targets = document.querySelectorAll(opts.selector);
96
+ targets.forEach(function (t) { return observer.observe(t); });
97
+ }
98
+ }
99
+ return {
100
+ name: 'exposure',
101
+ install: function (_a) {
102
+ var sdk = _a.sdk;
103
+ if (!isBrowser())
104
+ return;
105
+ sdkRef = sdk;
106
+ if (document.readyState === 'complete')
107
+ setupObserver();
108
+ else
109
+ window.addEventListener('load', setupObserver, { once: true });
110
+ },
111
+ uninstall: function () {
112
+ observer === null || observer === void 0 ? void 0 : observer.disconnect();
113
+ observer = null;
114
+ sdkRef = null;
115
+ },
116
+ };
117
+ }
118
+
119
+ export { exposure };
@@ -0,0 +1,94 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 浏览器端辅助方法
5
+ */
6
+ function isBrowser() {
7
+ return typeof window !== 'undefined' && typeof window.document !== 'undefined';
8
+ }
9
+
10
+ /**
11
+ * 序列化 & 编码
12
+ */
13
+ function encodeEvent(event, encryptor) {
14
+ var json = JSON.stringify(event);
15
+ return json;
16
+ }
17
+
18
+ /**
19
+ * indexeddb-sender 插件:把事件写到 IndexedDB(用于离线缓存 / 批量消费)
20
+ * - 不实际发送,只把序列化事件追加到指定 IDB store
21
+ * - 适合后续被 webview bridge / native / service worker 消费
22
+ */
23
+ function indexedDBSender(opts) {
24
+ if (opts === void 0) { opts = {}; }
25
+ var dbName = opts.dbName || 'event-tracker-outbox';
26
+ var storeName = opts.storeName || 'outbox';
27
+ var max = opts.max || 500;
28
+ var dbPromise = null;
29
+ function openDB() {
30
+ if (!isBrowser() || typeof indexedDB === 'undefined')
31
+ return Promise.resolve(null);
32
+ return new Promise(function (resolve) {
33
+ try {
34
+ var req_1 = indexedDB.open(dbName, 1);
35
+ req_1.onupgradeneeded = function () {
36
+ var db = req_1.result;
37
+ if (!db.objectStoreNames.contains(storeName)) {
38
+ db.createObjectStore(storeName, { autoIncrement: true });
39
+ }
40
+ };
41
+ req_1.onsuccess = function () { return resolve(req_1.result); };
42
+ req_1.onerror = function () { return resolve(null); };
43
+ }
44
+ catch (_a) {
45
+ resolve(null);
46
+ }
47
+ });
48
+ }
49
+ var interceptor = {
50
+ name: 'indexeddb-sender',
51
+ priority: 60,
52
+ entry: function (ctx) {
53
+ if (ctx.meta.stage !== 'sendDataStage')
54
+ return;
55
+ var ev = ctx.data;
56
+ dbPromise !== null && dbPromise !== void 0 ? dbPromise : (dbPromise = openDB());
57
+ dbPromise.then(function (db) {
58
+ if (!db)
59
+ return;
60
+ try {
61
+ var tx = db.transaction(storeName, 'readwrite');
62
+ var store_1 = tx.objectStore(storeName);
63
+ var payload_1 = encodeEvent(ev);
64
+ // 限制大小:> max 则删头
65
+ var cntReq_1 = store_1.count();
66
+ cntReq_1.onsuccess = function () {
67
+ if (cntReq_1.result >= max) {
68
+ store_1.delete(IDBKeyRange.lowerBound(0, true));
69
+ }
70
+ store_1.add({ time: Date.now(), payload: payload_1 });
71
+ };
72
+ }
73
+ catch (_a) {
74
+ /* ignore */
75
+ }
76
+ }).catch(function () { });
77
+ return ev;
78
+ },
79
+ };
80
+ return {
81
+ name: 'indexeddb-sender',
82
+ install: function (_a) {
83
+ var stages = _a.stages;
84
+ if (!isBrowser())
85
+ return;
86
+ stages.sendDataStage.register(interceptor);
87
+ },
88
+ uninstall: function () {
89
+ dbPromise = null;
90
+ },
91
+ };
92
+ }
93
+
94
+ exports.indexedDBSender = indexedDBSender;