humanbehavior-js 0.0.5 → 0.0.8
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/dist/cjs/index.js +912 -596
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/index.js +38 -82
- package/dist/cjs/react/index.js.map +1 -1
- package/dist/esm/index.js +911 -597
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/index.js +38 -82
- package/dist/esm/react/index.js.map +1 -1
- package/dist/index.min.js +2 -2
- package/dist/index.min.js.map +1 -1
- package/dist/types/index.d.ts +111 -3
- package/dist/types/react/index.d.ts +1 -1
- package/package.json +3 -14
- package/readme.md +116 -28
- package/rollup.config.js +106 -0
- package/src/api.ts +360 -0
- package/src/index.ts +22 -0
- package/src/react/index.tsx +189 -0
- package/src/redact.ts +474 -0
- package/src/tracker.ts +361 -0
- package/tsconfig.json +24 -0
- package/index.d.ts +0 -2
- package/index.js +0 -3
- package/react.d.ts +0 -2
- package/react.js +0 -2
package/dist/cjs/index.js
CHANGED
|
@@ -19,17 +19,6 @@ PERFORMANCE OF THIS SOFTWARE.
|
|
|
19
19
|
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
var __assign = function() {
|
|
23
|
-
__assign = Object.assign || function __assign(t) {
|
|
24
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
25
|
-
s = arguments[i];
|
|
26
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
|
|
27
|
-
}
|
|
28
|
-
return t;
|
|
29
|
-
};
|
|
30
|
-
return __assign.apply(this, arguments);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
22
|
function __awaiter$1(thisArg, _arguments, P, generator) {
|
|
34
23
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
35
24
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -40,44 +29,6 @@ function __awaiter$1(thisArg, _arguments, P, generator) {
|
|
|
40
29
|
});
|
|
41
30
|
}
|
|
42
31
|
|
|
43
|
-
function __generator(thisArg, body) {
|
|
44
|
-
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);
|
|
45
|
-
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
46
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
47
|
-
function step(op) {
|
|
48
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
49
|
-
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
50
|
-
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;
|
|
51
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
52
|
-
switch (op[0]) {
|
|
53
|
-
case 0: case 1: t = op; break;
|
|
54
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
55
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
56
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
57
|
-
default:
|
|
58
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
59
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
60
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
61
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
62
|
-
if (t[2]) _.ops.pop();
|
|
63
|
-
_.trys.pop(); continue;
|
|
64
|
-
}
|
|
65
|
-
op = body.call(thisArg, _);
|
|
66
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
67
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function __spreadArray(to, from, pack) {
|
|
72
|
-
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
73
|
-
if (ar || !(i in from)) {
|
|
74
|
-
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
75
|
-
ar[i] = from[i];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return to.concat(ar || Array.prototype.slice.call(from));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
32
|
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
82
33
|
var e = new Error(message);
|
|
83
34
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
@@ -4102,336 +4053,281 @@ function v1Bytes(rnds, msecs, nsecs, clockseq, node, buf, offset = 0) {
|
|
|
4102
4053
|
return buf;
|
|
4103
4054
|
}
|
|
4104
4055
|
|
|
4105
|
-
|
|
4056
|
+
const MAX_CHUNK_SIZE_BYTES = 1024 * 1024 * 10; // 10MB chunk size
|
|
4106
4057
|
function isChunkSizeExceeded(currentChunk, newEvent, sessionId) {
|
|
4107
|
-
|
|
4108
|
-
sessionId
|
|
4109
|
-
events:
|
|
4058
|
+
const nextChunkSize = new TextEncoder().encode(JSON.stringify({
|
|
4059
|
+
sessionId,
|
|
4060
|
+
events: [...currentChunk, newEvent]
|
|
4110
4061
|
})).length;
|
|
4111
4062
|
return nextChunkSize > MAX_CHUNK_SIZE_BYTES;
|
|
4112
4063
|
}
|
|
4113
4064
|
function validateSingleEventSize(event, sessionId) {
|
|
4114
|
-
|
|
4115
|
-
sessionId
|
|
4065
|
+
const singleEventSize = new TextEncoder().encode(JSON.stringify({
|
|
4066
|
+
sessionId,
|
|
4116
4067
|
events: [event]
|
|
4117
4068
|
})).length;
|
|
4118
4069
|
if (singleEventSize > MAX_CHUNK_SIZE_BYTES) {
|
|
4119
|
-
throw new Error(
|
|
4070
|
+
throw new Error(`Single event size (${singleEventSize} bytes) exceeds maximum chunk size (${MAX_CHUNK_SIZE_BYTES} bytes)`);
|
|
4120
4071
|
}
|
|
4121
4072
|
}
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
var apiKey = _a.apiKey, ingestionUrl = _a.ingestionUrl;
|
|
4073
|
+
class HumanBehaviorAPI {
|
|
4074
|
+
constructor({ apiKey, ingestionUrl }) {
|
|
4125
4075
|
this.apiKey = apiKey;
|
|
4126
4076
|
this.baseUrl = ingestionUrl;
|
|
4127
4077
|
}
|
|
4128
|
-
|
|
4129
|
-
return __awaiter$1(this, void 0, void 0, function () {
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
case 2:
|
|
4151
|
-
responseJson = _a.sent();
|
|
4152
|
-
return [2 /*return*/, {
|
|
4153
|
-
sessionId: responseJson.sessionId,
|
|
4154
|
-
endUserId: responseJson.endUserId
|
|
4155
|
-
}];
|
|
4156
|
-
}
|
|
4078
|
+
init(sessionId, userId) {
|
|
4079
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4080
|
+
// Get current page URL and referrer if in browser environment
|
|
4081
|
+
let entryURL = null;
|
|
4082
|
+
let referrer = null;
|
|
4083
|
+
if (typeof window !== 'undefined') {
|
|
4084
|
+
entryURL = window.location.href;
|
|
4085
|
+
referrer = document.referrer;
|
|
4086
|
+
}
|
|
4087
|
+
const response = yield fetch(`${this.baseUrl}/api/ingestion/init`, {
|
|
4088
|
+
method: 'POST',
|
|
4089
|
+
headers: {
|
|
4090
|
+
'Content-Type': 'application/json',
|
|
4091
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
4092
|
+
'Referer': referrer || ''
|
|
4093
|
+
},
|
|
4094
|
+
body: JSON.stringify({
|
|
4095
|
+
sessionId: sessionId,
|
|
4096
|
+
endUserId: userId,
|
|
4097
|
+
entryURL: entryURL,
|
|
4098
|
+
referrer: referrer
|
|
4099
|
+
})
|
|
4157
4100
|
});
|
|
4101
|
+
if (!response.ok) {
|
|
4102
|
+
throw new Error(`Failed to initialize ingestion: ${response.statusText}`);
|
|
4103
|
+
}
|
|
4104
|
+
const responseJson = yield response.json();
|
|
4105
|
+
return {
|
|
4106
|
+
sessionId: responseJson.sessionId,
|
|
4107
|
+
endUserId: responseJson.endUserId
|
|
4108
|
+
};
|
|
4158
4109
|
});
|
|
4159
|
-
}
|
|
4160
|
-
|
|
4161
|
-
return __awaiter$1(this, void 0, void 0, function () {
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
events: events,
|
|
4174
|
-
endUserId: userId
|
|
4175
|
-
})
|
|
4176
|
-
})];
|
|
4177
|
-
case 1:
|
|
4178
|
-
response = _a.sent();
|
|
4179
|
-
if (!response.ok) {
|
|
4180
|
-
throw new Error("Failed to send events: ".concat(response.statusText));
|
|
4181
|
-
}
|
|
4182
|
-
return [2 /*return*/];
|
|
4183
|
-
}
|
|
4110
|
+
}
|
|
4111
|
+
sendEvents(events, sessionId, userId) {
|
|
4112
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4113
|
+
const response = yield fetch(`${this.baseUrl}/api/ingestion/events`, {
|
|
4114
|
+
method: 'POST',
|
|
4115
|
+
headers: {
|
|
4116
|
+
'Content-Type': 'application/json',
|
|
4117
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
4118
|
+
},
|
|
4119
|
+
body: JSON.stringify({
|
|
4120
|
+
sessionId,
|
|
4121
|
+
events: events,
|
|
4122
|
+
endUserId: userId
|
|
4123
|
+
})
|
|
4184
4124
|
});
|
|
4125
|
+
if (!response.ok) {
|
|
4126
|
+
throw new Error(`Failed to send events: ${response.statusText}`);
|
|
4127
|
+
}
|
|
4185
4128
|
});
|
|
4186
|
-
}
|
|
4187
|
-
|
|
4188
|
-
return __awaiter$1(this, void 0, void 0, function () {
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
currentChunk
|
|
4196
|
-
|
|
4197
|
-
_e.label = 1;
|
|
4198
|
-
case 1:
|
|
4199
|
-
if (!(_i < events_1.length)) return [3 /*break*/, 7];
|
|
4200
|
-
event_1 = events_1[_i];
|
|
4201
|
-
if (!isChunkSizeExceeded(currentChunk, event_1, sessionId)) return [3 /*break*/, 5];
|
|
4202
|
-
if (!(currentChunk.length > 0)) return [3 /*break*/, 4];
|
|
4203
|
-
return [4 /*yield*/, fetch("".concat(this.baseUrl, "/api/ingestion/events"), {
|
|
4129
|
+
}
|
|
4130
|
+
sendEventsChunked(events, sessionId) {
|
|
4131
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4132
|
+
try {
|
|
4133
|
+
const results = [];
|
|
4134
|
+
let currentChunk = [];
|
|
4135
|
+
for (const event of events) {
|
|
4136
|
+
if (isChunkSizeExceeded(currentChunk, event, sessionId)) {
|
|
4137
|
+
// If current chunk is not empty, send it first
|
|
4138
|
+
if (currentChunk.length > 0) {
|
|
4139
|
+
const response = yield fetch(`${this.baseUrl}/api/ingestion/events`, {
|
|
4204
4140
|
method: 'POST',
|
|
4205
4141
|
headers: {
|
|
4206
4142
|
'Content-Type': 'application/json',
|
|
4207
|
-
'Authorization':
|
|
4143
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
4208
4144
|
},
|
|
4209
4145
|
body: JSON.stringify({
|
|
4210
|
-
sessionId
|
|
4146
|
+
sessionId,
|
|
4211
4147
|
events: currentChunk
|
|
4212
4148
|
})
|
|
4213
|
-
})
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4149
|
+
});
|
|
4150
|
+
if (!response.ok) {
|
|
4151
|
+
throw new Error(`Failed to send events: ${response.statusText}`);
|
|
4152
|
+
}
|
|
4153
|
+
results.push(yield response.json());
|
|
4154
|
+
currentChunk = [];
|
|
4218
4155
|
}
|
|
4219
|
-
_b = (_a = results).push;
|
|
4220
|
-
return [4 /*yield*/, response.json()];
|
|
4221
|
-
case 3:
|
|
4222
|
-
_b.apply(_a, [_e.sent()]);
|
|
4223
|
-
currentChunk = [];
|
|
4224
|
-
_e.label = 4;
|
|
4225
|
-
case 4:
|
|
4226
4156
|
// Validate single event size
|
|
4227
|
-
validateSingleEventSize(
|
|
4157
|
+
validateSingleEventSize(event, sessionId);
|
|
4228
4158
|
// Start new chunk with this event
|
|
4229
|
-
currentChunk = [
|
|
4230
|
-
|
|
4231
|
-
|
|
4159
|
+
currentChunk = [event];
|
|
4160
|
+
}
|
|
4161
|
+
else {
|
|
4232
4162
|
// Add event to current chunk
|
|
4233
|
-
currentChunk.push(
|
|
4234
|
-
|
|
4235
|
-
case 6:
|
|
4236
|
-
_i++;
|
|
4237
|
-
return [3 /*break*/, 1];
|
|
4238
|
-
case 7:
|
|
4239
|
-
if (!(currentChunk.length > 0)) return [3 /*break*/, 10];
|
|
4240
|
-
return [4 /*yield*/, fetch("".concat(this.baseUrl, "/api/ingestion/events"), {
|
|
4241
|
-
method: 'POST',
|
|
4242
|
-
headers: {
|
|
4243
|
-
'Content-Type': 'application/json',
|
|
4244
|
-
'Authorization': "Bearer ".concat(this.apiKey)
|
|
4245
|
-
},
|
|
4246
|
-
body: JSON.stringify({
|
|
4247
|
-
sessionId: sessionId,
|
|
4248
|
-
events: currentChunk
|
|
4249
|
-
})
|
|
4250
|
-
})];
|
|
4251
|
-
case 8:
|
|
4252
|
-
response = _e.sent();
|
|
4253
|
-
if (!response.ok) {
|
|
4254
|
-
throw new Error("Failed to send events: ".concat(response.statusText));
|
|
4255
|
-
}
|
|
4256
|
-
_d = (_c = results).push;
|
|
4257
|
-
return [4 /*yield*/, response.json()];
|
|
4258
|
-
case 9:
|
|
4259
|
-
_d.apply(_c, [_e.sent()]);
|
|
4260
|
-
_e.label = 10;
|
|
4261
|
-
case 10: return [2 /*return*/, results.flat()];
|
|
4262
|
-
case 11:
|
|
4263
|
-
error_1 = _e.sent();
|
|
4264
|
-
console.error('Error sending events:', error_1);
|
|
4265
|
-
throw error_1;
|
|
4266
|
-
case 12: return [2 /*return*/];
|
|
4163
|
+
currentChunk.push(event);
|
|
4164
|
+
}
|
|
4267
4165
|
}
|
|
4268
|
-
|
|
4166
|
+
// Send any remaining events
|
|
4167
|
+
if (currentChunk.length > 0) {
|
|
4168
|
+
const response = yield fetch(`${this.baseUrl}/api/ingestion/events`, {
|
|
4169
|
+
method: 'POST',
|
|
4170
|
+
headers: {
|
|
4171
|
+
'Content-Type': 'application/json',
|
|
4172
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
4173
|
+
},
|
|
4174
|
+
body: JSON.stringify({
|
|
4175
|
+
sessionId,
|
|
4176
|
+
events: currentChunk
|
|
4177
|
+
})
|
|
4178
|
+
});
|
|
4179
|
+
if (!response.ok) {
|
|
4180
|
+
throw new Error(`Failed to send events: ${response.statusText}`);
|
|
4181
|
+
}
|
|
4182
|
+
results.push(yield response.json());
|
|
4183
|
+
}
|
|
4184
|
+
return results.flat();
|
|
4185
|
+
}
|
|
4186
|
+
catch (error) {
|
|
4187
|
+
console.error('Error sending events:', error);
|
|
4188
|
+
throw error;
|
|
4189
|
+
}
|
|
4269
4190
|
});
|
|
4270
|
-
}
|
|
4271
|
-
|
|
4272
|
-
return __awaiter$1(this, void 0, void 0, function () {
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
})
|
|
4289
|
-
})];
|
|
4290
|
-
case 1:
|
|
4291
|
-
response = _a.sent();
|
|
4292
|
-
if (!response.ok) {
|
|
4293
|
-
throw new Error("Failed to send user data: ".concat(response.statusText, " with API key: ").concat(this.apiKey));
|
|
4294
|
-
}
|
|
4295
|
-
return [4 /*yield*/, response.json()];
|
|
4296
|
-
case 2: return [2 /*return*/, _a.sent()];
|
|
4297
|
-
case 3:
|
|
4298
|
-
error_2 = _a.sent();
|
|
4299
|
-
console.error('Error sending user data:', error_2);
|
|
4300
|
-
throw error_2;
|
|
4301
|
-
case 4: return [2 /*return*/];
|
|
4191
|
+
}
|
|
4192
|
+
sendUserData(userId, userData, sessionId) {
|
|
4193
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4194
|
+
try {
|
|
4195
|
+
const response = yield fetch(`${this.baseUrl}/api/ingestion/user`, {
|
|
4196
|
+
method: 'POST',
|
|
4197
|
+
headers: {
|
|
4198
|
+
'Content-Type': 'application/json',
|
|
4199
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
4200
|
+
},
|
|
4201
|
+
body: JSON.stringify({
|
|
4202
|
+
userId: userId,
|
|
4203
|
+
userAttributes: userData,
|
|
4204
|
+
sessionId: sessionId
|
|
4205
|
+
})
|
|
4206
|
+
});
|
|
4207
|
+
if (!response.ok) {
|
|
4208
|
+
throw new Error(`Failed to send user data: ${response.statusText} with API key: ${this.apiKey}`);
|
|
4302
4209
|
}
|
|
4303
|
-
|
|
4210
|
+
return yield response.json();
|
|
4211
|
+
}
|
|
4212
|
+
catch (error) {
|
|
4213
|
+
console.error('Error sending user data:', error);
|
|
4214
|
+
throw error;
|
|
4215
|
+
}
|
|
4304
4216
|
});
|
|
4305
|
-
}
|
|
4306
|
-
|
|
4307
|
-
return __awaiter$1(this, void 0, void 0, function () {
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
return [2 /*return*/];
|
|
4217
|
+
}
|
|
4218
|
+
sendUserAuth(userId, userData, sessionId, authFields) {
|
|
4219
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4220
|
+
try {
|
|
4221
|
+
const response = yield fetch(`${this.baseUrl}/api/ingestion/user/auth`, {
|
|
4222
|
+
method: 'POST',
|
|
4223
|
+
headers: {
|
|
4224
|
+
'Content-Type': 'application/json',
|
|
4225
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
4226
|
+
},
|
|
4227
|
+
body: JSON.stringify({
|
|
4228
|
+
userId: userId,
|
|
4229
|
+
userAttributes: userData,
|
|
4230
|
+
sessionId: sessionId,
|
|
4231
|
+
authFields: authFields
|
|
4232
|
+
})
|
|
4233
|
+
});
|
|
4234
|
+
if (!response.ok) {
|
|
4235
|
+
throw new Error(`Failed to authenticate user: ${response.statusText} with API key: ${this.apiKey}`);
|
|
4325
4236
|
}
|
|
4237
|
+
return yield response.json();
|
|
4238
|
+
}
|
|
4239
|
+
catch (error) {
|
|
4240
|
+
console.error('Error authenticating user:', error);
|
|
4241
|
+
throw error;
|
|
4242
|
+
}
|
|
4243
|
+
});
|
|
4244
|
+
}
|
|
4245
|
+
sendSessionComplete(sessionId) {
|
|
4246
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4247
|
+
const response = yield fetch(`${this.baseUrl}/api/ingestion/sessionComplete`, {
|
|
4248
|
+
method: 'POST',
|
|
4249
|
+
headers: {
|
|
4250
|
+
'Content-Type': 'application/json',
|
|
4251
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
4252
|
+
},
|
|
4253
|
+
body: JSON.stringify({ sessionId })
|
|
4326
4254
|
});
|
|
4255
|
+
if (!response.ok) {
|
|
4256
|
+
throw new Error(`Failed to send session complete: ${response.statusText}`);
|
|
4257
|
+
}
|
|
4327
4258
|
});
|
|
4328
|
-
}
|
|
4329
|
-
|
|
4330
|
-
return __awaiter$1(this, void 0, void 0, function () {
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
sessionId: sessionId,
|
|
4353
|
-
timestamp: new Date().toISOString()
|
|
4354
|
-
})
|
|
4355
|
-
})];
|
|
4356
|
-
case 3:
|
|
4357
|
-
response = _a.sent();
|
|
4358
|
-
if (!response.ok) {
|
|
4359
|
-
throw new Error("Failed to send custom event: ".concat(response.statusText));
|
|
4360
|
-
}
|
|
4361
|
-
return [4 /*yield*/, response.json()];
|
|
4362
|
-
case 4: return [2 /*return*/, _a.sent()];
|
|
4363
|
-
case 5:
|
|
4364
|
-
error_3 = _a.sent();
|
|
4365
|
-
retryCount++;
|
|
4366
|
-
if (retryCount === maxRetries) {
|
|
4367
|
-
console.error('Error sending custom event after max retries:', error_3);
|
|
4368
|
-
throw error_3;
|
|
4369
|
-
}
|
|
4370
|
-
// Exponential backoff
|
|
4371
|
-
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, Math.pow(2, retryCount) * 1000); })];
|
|
4372
|
-
case 6:
|
|
4373
|
-
// Exponential backoff
|
|
4374
|
-
_a.sent();
|
|
4375
|
-
return [3 /*break*/, 7];
|
|
4376
|
-
case 7: return [3 /*break*/, 1];
|
|
4377
|
-
case 8: return [2 /*return*/];
|
|
4259
|
+
}
|
|
4260
|
+
sendCustomEvent(eventName, eventProperties, sessionId) {
|
|
4261
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4262
|
+
const maxRetries = 3;
|
|
4263
|
+
let retryCount = 0;
|
|
4264
|
+
while (retryCount < maxRetries) {
|
|
4265
|
+
try {
|
|
4266
|
+
const response = yield fetch(`${this.baseUrl}/api/ingestion/customEvent`, {
|
|
4267
|
+
method: 'POST',
|
|
4268
|
+
headers: {
|
|
4269
|
+
'Content-Type': 'application/json',
|
|
4270
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
4271
|
+
},
|
|
4272
|
+
body: JSON.stringify({
|
|
4273
|
+
name: eventName,
|
|
4274
|
+
properties: eventProperties,
|
|
4275
|
+
sessionId: sessionId,
|
|
4276
|
+
timestamp: new Date().toISOString()
|
|
4277
|
+
})
|
|
4278
|
+
});
|
|
4279
|
+
if (!response.ok) {
|
|
4280
|
+
throw new Error(`Failed to send custom event: ${response.statusText}`);
|
|
4281
|
+
}
|
|
4282
|
+
return yield response.json();
|
|
4378
4283
|
}
|
|
4379
|
-
|
|
4284
|
+
catch (error) {
|
|
4285
|
+
retryCount++;
|
|
4286
|
+
if (retryCount === maxRetries) {
|
|
4287
|
+
console.error('Error sending custom event after max retries:', error);
|
|
4288
|
+
throw error;
|
|
4289
|
+
}
|
|
4290
|
+
// Exponential backoff
|
|
4291
|
+
yield new Promise(resolve => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4380
4294
|
});
|
|
4381
|
-
}
|
|
4382
|
-
|
|
4383
|
-
return __awaiter$1(this, void 0, void 0, function () {
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
body: JSON.stringify({
|
|
4403
|
-
events: events.map(function (event) { return (__assign(__assign({}, event), { sessionId: sessionId })); })
|
|
4404
|
-
})
|
|
4405
|
-
})];
|
|
4406
|
-
case 3:
|
|
4407
|
-
response = _a.sent();
|
|
4408
|
-
if (!response.ok) {
|
|
4409
|
-
throw new Error("Failed to send custom events: ".concat(response.statusText));
|
|
4410
|
-
}
|
|
4411
|
-
return [4 /*yield*/, response.json()];
|
|
4412
|
-
case 4: return [2 /*return*/, _a.sent()];
|
|
4413
|
-
case 5:
|
|
4414
|
-
error_4 = _a.sent();
|
|
4415
|
-
retryCount++;
|
|
4416
|
-
if (retryCount === maxRetries) {
|
|
4417
|
-
console.error('Error sending custom events after max retries:', error_4);
|
|
4418
|
-
throw error_4;
|
|
4419
|
-
}
|
|
4420
|
-
// Exponential backoff
|
|
4421
|
-
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, Math.pow(2, retryCount) * 1000); })];
|
|
4422
|
-
case 6:
|
|
4423
|
-
// Exponential backoff
|
|
4424
|
-
_a.sent();
|
|
4425
|
-
return [3 /*break*/, 7];
|
|
4426
|
-
case 7: return [3 /*break*/, 1];
|
|
4427
|
-
case 8: return [2 /*return*/];
|
|
4295
|
+
}
|
|
4296
|
+
sendCustomEvents(events, sessionId) {
|
|
4297
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4298
|
+
const maxRetries = 3;
|
|
4299
|
+
let retryCount = 0;
|
|
4300
|
+
while (retryCount < maxRetries) {
|
|
4301
|
+
try {
|
|
4302
|
+
const response = yield fetch(`${this.baseUrl}/api/ingestion/customEvent/batch`, {
|
|
4303
|
+
method: 'POST',
|
|
4304
|
+
headers: {
|
|
4305
|
+
'Content-Type': 'application/json',
|
|
4306
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
4307
|
+
},
|
|
4308
|
+
body: JSON.stringify({
|
|
4309
|
+
events: events.map(event => (Object.assign(Object.assign({}, event), { sessionId: sessionId })))
|
|
4310
|
+
})
|
|
4311
|
+
});
|
|
4312
|
+
if (!response.ok) {
|
|
4313
|
+
throw new Error(`Failed to send custom events: ${response.statusText}`);
|
|
4314
|
+
}
|
|
4315
|
+
return yield response.json();
|
|
4428
4316
|
}
|
|
4429
|
-
|
|
4317
|
+
catch (error) {
|
|
4318
|
+
retryCount++;
|
|
4319
|
+
if (retryCount === maxRetries) {
|
|
4320
|
+
console.error('Error sending custom events after max retries:', error);
|
|
4321
|
+
throw error;
|
|
4322
|
+
}
|
|
4323
|
+
// Exponential backoff
|
|
4324
|
+
yield new Promise(resolve => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4430
4327
|
});
|
|
4431
|
-
}
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
var data = new URLSearchParams();
|
|
4328
|
+
}
|
|
4329
|
+
sendBeaconEvents(events, sessionId, isSessionComplete = false) {
|
|
4330
|
+
const data = new URLSearchParams();
|
|
4435
4331
|
data.append('events', encodeURIComponent(JSON.stringify(events)));
|
|
4436
4332
|
data.append('sessionId', encodeURIComponent(sessionId));
|
|
4437
4333
|
data.append('timestamp', encodeURIComponent(Date.now().toString()));
|
|
@@ -4441,41 +4337,453 @@ var HumanBehaviorAPI = /** @class */ (function () {
|
|
|
4441
4337
|
localStorage.setItem('koalaware_session_complete', Date.now().toString());
|
|
4442
4338
|
data.append('sessionComplete', encodeURIComponent('true'));
|
|
4443
4339
|
}
|
|
4444
|
-
navigator.sendBeacon(
|
|
4340
|
+
navigator.sendBeacon(`${this.baseUrl}/api/ingestion/events`, data);
|
|
4445
4341
|
// KoalawareTracker.logToStorage(`Sending events beacon: ${this.baseUrl}/api/ingestion/events`);
|
|
4446
4342
|
// KoalawareTracker.logToStorage(`Events beacon success: ${success}`);
|
|
4447
|
-
}
|
|
4448
|
-
|
|
4449
|
-
|
|
4343
|
+
}
|
|
4344
|
+
sendBeaconSessionComplete(sessionId) {
|
|
4345
|
+
const data = new URLSearchParams();
|
|
4450
4346
|
data.append('sessionId', sessionId);
|
|
4451
4347
|
data.append('apiKey', this.apiKey);
|
|
4452
4348
|
data.append('sessionComplete', 'true');
|
|
4453
|
-
navigator.sendBeacon(
|
|
4349
|
+
navigator.sendBeacon(`${this.baseUrl}/api/ingestion/sessionComplete`, data);
|
|
4454
4350
|
// KoalawareTracker.logToStorage(`Sending completion beacon: ${this.baseUrl}/api/ingestion/sessionComplete`);
|
|
4455
4351
|
// KoalawareTracker.logToStorage(`Complete beacon success: ${success}`);
|
|
4456
|
-
}
|
|
4457
|
-
|
|
4458
|
-
|
|
4352
|
+
}
|
|
4353
|
+
sendBeaconCustomEvent(eventName, eventProperties, sessionId) {
|
|
4354
|
+
const data = new URLSearchParams();
|
|
4459
4355
|
data.append('name', encodeURIComponent(eventName));
|
|
4460
4356
|
data.append('properties', encodeURIComponent(JSON.stringify(eventProperties)));
|
|
4461
4357
|
data.append('sessionId', encodeURIComponent(sessionId));
|
|
4462
4358
|
data.append('timestamp', encodeURIComponent(new Date().toISOString()));
|
|
4463
4359
|
data.append('apiKey', encodeURIComponent(this.apiKey));
|
|
4464
|
-
return navigator.sendBeacon(
|
|
4465
|
-
}
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
data.append('events', encodeURIComponent(JSON.stringify(events.map(
|
|
4360
|
+
return navigator.sendBeacon(`${this.baseUrl}/api/ingestion/customEvent`, data);
|
|
4361
|
+
}
|
|
4362
|
+
sendBeaconCustomEvents(events, sessionId) {
|
|
4363
|
+
const data = new URLSearchParams();
|
|
4364
|
+
data.append('events', encodeURIComponent(JSON.stringify(events.map(event => (Object.assign(Object.assign({}, event), { sessionId: sessionId }))))));
|
|
4469
4365
|
data.append('apiKey', encodeURIComponent(this.apiKey));
|
|
4470
|
-
return navigator.sendBeacon(
|
|
4471
|
-
}
|
|
4472
|
-
|
|
4473
|
-
|
|
4366
|
+
return navigator.sendBeacon(`${this.baseUrl}/api/ingestion/customEvent/batch`, data);
|
|
4367
|
+
}
|
|
4368
|
+
}
|
|
4369
|
+
|
|
4370
|
+
// Redaction functionality for sensitive input fields
|
|
4371
|
+
// This module provides methods to redact sensitive input fields in event recordings
|
|
4372
|
+
// Check if we're in a browser environment
|
|
4373
|
+
const isBrowser$1 = typeof window !== 'undefined';
|
|
4374
|
+
class RedactionManager {
|
|
4375
|
+
constructor(options) {
|
|
4376
|
+
this.redactedText = '[REDACTED]';
|
|
4377
|
+
this.userSelectedFields = new Set(); // User-selected fields to redact
|
|
4378
|
+
this.excludeSelectors = [
|
|
4379
|
+
'[data-no-redact="true"]',
|
|
4380
|
+
'.human-behavior-no-redact'
|
|
4381
|
+
];
|
|
4382
|
+
if (options === null || options === void 0 ? void 0 : options.redactedText) {
|
|
4383
|
+
this.redactedText = options.redactedText;
|
|
4384
|
+
}
|
|
4385
|
+
if (options === null || options === void 0 ? void 0 : options.excludeSelectors) {
|
|
4386
|
+
this.excludeSelectors = [...this.excludeSelectors, ...options.excludeSelectors];
|
|
4387
|
+
}
|
|
4388
|
+
if (options === null || options === void 0 ? void 0 : options.userFields) {
|
|
4389
|
+
this.setFieldsToRedact(options.userFields);
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
/**
|
|
4393
|
+
* Set specific fields to be redacted
|
|
4394
|
+
* @param fields Array of CSS selectors for fields to redact
|
|
4395
|
+
*/
|
|
4396
|
+
setFieldsToRedact(fields) {
|
|
4397
|
+
this.userSelectedFields.clear();
|
|
4398
|
+
fields.forEach(field => this.userSelectedFields.add(field));
|
|
4399
|
+
if (fields.length > 0) {
|
|
4400
|
+
console.log(`Redaction: Active for ${fields.length} field(s):`, fields);
|
|
4401
|
+
// Debug: Check if elements exist
|
|
4402
|
+
fields.forEach(selector => {
|
|
4403
|
+
const elements = document.querySelectorAll(selector);
|
|
4404
|
+
console.log(`Redaction: Found ${elements.length} element(s) for selector '${selector}'`);
|
|
4405
|
+
elements.forEach((el, index) => {
|
|
4406
|
+
console.log(`Redaction: Element ${index} for '${selector}':`, el);
|
|
4407
|
+
});
|
|
4408
|
+
});
|
|
4409
|
+
}
|
|
4410
|
+
else {
|
|
4411
|
+
console.log('Redaction: Disabled - no fields selected');
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
/**
|
|
4415
|
+
* Check if redaction is currently active (has fields selected)
|
|
4416
|
+
*/
|
|
4417
|
+
isActive() {
|
|
4418
|
+
return this.userSelectedFields.size > 0;
|
|
4419
|
+
}
|
|
4420
|
+
/**
|
|
4421
|
+
* Get the currently selected fields for redaction
|
|
4422
|
+
*/
|
|
4423
|
+
getSelectedFields() {
|
|
4424
|
+
return Array.from(this.userSelectedFields);
|
|
4425
|
+
}
|
|
4426
|
+
/**
|
|
4427
|
+
* Process an event and redact sensitive data if needed
|
|
4428
|
+
*/
|
|
4429
|
+
processEvent(event) {
|
|
4430
|
+
// Only process if we have fields selected for redaction
|
|
4431
|
+
if (this.userSelectedFields.size === 0) {
|
|
4432
|
+
return event;
|
|
4433
|
+
}
|
|
4434
|
+
// Clone the event to avoid modifying the original
|
|
4435
|
+
const processedEvent = JSON.parse(JSON.stringify(event));
|
|
4436
|
+
// Handle different event types
|
|
4437
|
+
if (processedEvent.type === 3) { // IncrementalSnapshot
|
|
4438
|
+
if (processedEvent.data.source === 5) { // Input event
|
|
4439
|
+
const shouldRedact = this.isFieldSelected(processedEvent.data);
|
|
4440
|
+
if (shouldRedact) {
|
|
4441
|
+
console.log('Redaction: Processing input event for redaction');
|
|
4442
|
+
this.redactInputEvent(processedEvent.data);
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
// Also check for other sources that might contain text changes
|
|
4446
|
+
else if (processedEvent.data.source === 0) { // DOM mutations
|
|
4447
|
+
this.redactDOMEvent(processedEvent.data);
|
|
4448
|
+
}
|
|
4449
|
+
// Handle other sources that might contain text
|
|
4450
|
+
else if (processedEvent.data.source === 2) { // Mouse/Touch interaction
|
|
4451
|
+
this.redactMouseEvent(processedEvent.data);
|
|
4452
|
+
}
|
|
4453
|
+
}
|
|
4454
|
+
else if (processedEvent.type === 2) { // FullSnapshot
|
|
4455
|
+
this.redactFullSnapshot(processedEvent.data);
|
|
4456
|
+
}
|
|
4457
|
+
return processedEvent;
|
|
4458
|
+
}
|
|
4459
|
+
/**
|
|
4460
|
+
* Redact sensitive data in input events
|
|
4461
|
+
*/
|
|
4462
|
+
redactInputEvent(inputData) {
|
|
4463
|
+
// Check if this input event is from a field we want to redact
|
|
4464
|
+
if (!this.isFieldSelected(inputData)) {
|
|
4465
|
+
return;
|
|
4466
|
+
}
|
|
4467
|
+
console.log('Redaction: Redacting input event with text:', inputData.text);
|
|
4468
|
+
// Redact all text-related properties that could contain input data
|
|
4469
|
+
const textProperties = ['text', 'value', 'content', 'data', 'input', 'textContent'];
|
|
4470
|
+
textProperties.forEach(prop => {
|
|
4471
|
+
if (inputData[prop] !== undefined && typeof inputData[prop] === 'string') {
|
|
4472
|
+
inputData[prop] = this.redactedText;
|
|
4473
|
+
console.log(`Redaction: Redacted property '${prop}'`);
|
|
4474
|
+
}
|
|
4475
|
+
});
|
|
4476
|
+
// Also check for any other string properties that might contain input data
|
|
4477
|
+
Object.keys(inputData).forEach(key => {
|
|
4478
|
+
if (typeof inputData[key] === 'string' && inputData[key].length > 0) {
|
|
4479
|
+
inputData[key] = this.redactedText;
|
|
4480
|
+
console.log(`Redaction: Redacted additional property '${key}'`);
|
|
4481
|
+
}
|
|
4482
|
+
});
|
|
4483
|
+
// Handle nested objects that might contain text data
|
|
4484
|
+
if (inputData.attributes && typeof inputData.attributes === 'object') {
|
|
4485
|
+
if (inputData.attributes.value && typeof inputData.attributes.value === 'string') {
|
|
4486
|
+
inputData.attributes.value = this.redactedText;
|
|
4487
|
+
console.log('Redaction: Redacted nested value attribute');
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
console.log('Redaction: Input event redaction complete');
|
|
4491
|
+
}
|
|
4492
|
+
/**
|
|
4493
|
+
* Redact sensitive data in DOM mutation events
|
|
4494
|
+
*/
|
|
4495
|
+
redactDOMEvent(domData) {
|
|
4496
|
+
// Check for text changes in DOM mutations
|
|
4497
|
+
if (domData.texts && Array.isArray(domData.texts)) {
|
|
4498
|
+
domData.texts.forEach((textChange) => {
|
|
4499
|
+
if (textChange.text && typeof textChange.text === 'string' &&
|
|
4500
|
+
this.shouldRedactDOMChange(textChange)) {
|
|
4501
|
+
textChange.text = this.redactedText;
|
|
4502
|
+
}
|
|
4503
|
+
});
|
|
4504
|
+
}
|
|
4505
|
+
// Also check for attribute changes that might contain input data
|
|
4506
|
+
if (domData.attributes && Array.isArray(domData.attributes)) {
|
|
4507
|
+
domData.attributes.forEach((attrChange) => {
|
|
4508
|
+
if (attrChange.attributes && attrChange.attributes.value &&
|
|
4509
|
+
typeof attrChange.attributes.value === 'string' &&
|
|
4510
|
+
this.shouldRedactDOMChange(attrChange)) {
|
|
4511
|
+
attrChange.attributes.value = this.redactedText;
|
|
4512
|
+
}
|
|
4513
|
+
});
|
|
4514
|
+
}
|
|
4515
|
+
// Check for any other properties that might contain text data
|
|
4516
|
+
if (domData.adds && Array.isArray(domData.adds)) {
|
|
4517
|
+
domData.adds.forEach((add) => {
|
|
4518
|
+
if (add.node && add.node.textContent && typeof add.node.textContent === 'string' &&
|
|
4519
|
+
this.shouldRedactDOMChange(add)) {
|
|
4520
|
+
add.node.textContent = this.redactedText;
|
|
4521
|
+
}
|
|
4522
|
+
});
|
|
4523
|
+
}
|
|
4524
|
+
}
|
|
4525
|
+
/**
|
|
4526
|
+
* Check if a DOM change should be redacted based on its ID
|
|
4527
|
+
*/
|
|
4528
|
+
shouldRedactDOMChange(changeData) {
|
|
4529
|
+
if (!isBrowser$1)
|
|
4530
|
+
return false;
|
|
4531
|
+
try {
|
|
4532
|
+
// Check if this change has an ID that we can use to find the element
|
|
4533
|
+
const elementId = changeData.id;
|
|
4534
|
+
if (elementId !== undefined) {
|
|
4535
|
+
// Try to find the element by data-rrweb-id attribute
|
|
4536
|
+
let element = document.querySelector(`[data-rrweb-id="${elementId}"]`);
|
|
4537
|
+
if (element) {
|
|
4538
|
+
return this.shouldRedactElement(element);
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
// Also check for nodeId which is another way rrweb identifies elements
|
|
4542
|
+
const nodeId = changeData.nodeId;
|
|
4543
|
+
if (nodeId !== undefined) {
|
|
4544
|
+
const element = document.querySelector(`[data-rrweb-id="${nodeId}"]`);
|
|
4545
|
+
if (element) {
|
|
4546
|
+
return this.shouldRedactElement(element);
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4549
|
+
return false;
|
|
4550
|
+
}
|
|
4551
|
+
catch (e) {
|
|
4552
|
+
console.warn('Error checking if DOM change should be redacted:', e);
|
|
4553
|
+
return false;
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
/**
|
|
4557
|
+
* Redact sensitive data in mouse/touch interaction events
|
|
4558
|
+
*/
|
|
4559
|
+
redactMouseEvent(mouseData) {
|
|
4560
|
+
// Mouse events typically don't contain text data, but check for any text properties
|
|
4561
|
+
if (mouseData.text && typeof mouseData.text === 'string' &&
|
|
4562
|
+
this.isFieldSelected(mouseData)) {
|
|
4563
|
+
mouseData.text = this.redactedText;
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
/**
|
|
4567
|
+
* Redact sensitive data in full snapshot events
|
|
4568
|
+
*/
|
|
4569
|
+
redactFullSnapshot(snapshotData) {
|
|
4570
|
+
if (snapshotData.node && snapshotData.node.type === 2) { // Element node
|
|
4571
|
+
this.redactNode(snapshotData.node);
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
4574
|
+
/**
|
|
4575
|
+
* Recursively redact sensitive data in DOM nodes
|
|
4576
|
+
*/
|
|
4577
|
+
redactNode(node) {
|
|
4578
|
+
if (!node)
|
|
4579
|
+
return;
|
|
4580
|
+
// Check if this node should be redacted
|
|
4581
|
+
if (node.type === 2 && node.tagName &&
|
|
4582
|
+
(node.tagName.toLowerCase() === 'input' || node.tagName.toLowerCase() === 'textarea')) {
|
|
4583
|
+
// Check if this input/textarea should be redacted
|
|
4584
|
+
if (this.shouldRedactNode(node)) {
|
|
4585
|
+
// Redact value attribute
|
|
4586
|
+
if (node.attributes && node.attributes.value) {
|
|
4587
|
+
node.attributes.value = this.redactedText;
|
|
4588
|
+
}
|
|
4589
|
+
// Redact text content
|
|
4590
|
+
if (node.textContent) {
|
|
4591
|
+
node.textContent = this.redactedText;
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
// Recursively process child nodes
|
|
4596
|
+
if (node.childNodes && Array.isArray(node.childNodes)) {
|
|
4597
|
+
node.childNodes.forEach((childNode) => {
|
|
4598
|
+
this.redactNode(childNode);
|
|
4599
|
+
});
|
|
4600
|
+
}
|
|
4601
|
+
}
|
|
4602
|
+
/**
|
|
4603
|
+
* Check if a node should be redacted based on its attributes
|
|
4604
|
+
*/
|
|
4605
|
+
shouldRedactNode(node) {
|
|
4606
|
+
if (!node.attributes)
|
|
4607
|
+
return false;
|
|
4608
|
+
// Check if any of our selectors would match this node
|
|
4609
|
+
for (const selector of this.userSelectedFields) {
|
|
4610
|
+
if (this.selectorMatchesNode(selector, node)) {
|
|
4611
|
+
return true;
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4614
|
+
return false;
|
|
4615
|
+
}
|
|
4616
|
+
/**
|
|
4617
|
+
* Check if a CSS selector would match a node based on its attributes
|
|
4618
|
+
*/
|
|
4619
|
+
selectorMatchesNode(selector, node) {
|
|
4620
|
+
if (!node.attributes)
|
|
4621
|
+
return false;
|
|
4622
|
+
// Create a temporary element to test the selector
|
|
4623
|
+
try {
|
|
4624
|
+
const tempElement = document.createElement(node.tagName || 'div');
|
|
4625
|
+
// Copy attributes from the node to the temp element
|
|
4626
|
+
if (node.attributes) {
|
|
4627
|
+
Object.keys(node.attributes).forEach(key => {
|
|
4628
|
+
tempElement.setAttribute(key, node.attributes[key]);
|
|
4629
|
+
});
|
|
4630
|
+
}
|
|
4631
|
+
// Test if the selector matches this element
|
|
4632
|
+
return tempElement.matches(selector);
|
|
4633
|
+
}
|
|
4634
|
+
catch (e) {
|
|
4635
|
+
// If matches() is not supported or fails, fall back to basic attribute checking
|
|
4636
|
+
return this.basicSelectorMatch(selector, node);
|
|
4637
|
+
}
|
|
4638
|
+
}
|
|
4639
|
+
/**
|
|
4640
|
+
* Basic selector matching for environments where matches() is not available
|
|
4641
|
+
*/
|
|
4642
|
+
basicSelectorMatch(selector, node) {
|
|
4643
|
+
if (!node.attributes)
|
|
4644
|
+
return false;
|
|
4645
|
+
// Handle simple selectors like 'input[type="password"]'
|
|
4646
|
+
if (selector.includes('input[type=')) {
|
|
4647
|
+
const typeMatch = selector.match(/input\[type="([^"]+)"\]/);
|
|
4648
|
+
if (typeMatch && node.tagName === 'input' && node.attributes.type === typeMatch[1]) {
|
|
4649
|
+
return true;
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
// Handle ID selectors like '#email'
|
|
4653
|
+
if (selector.startsWith('#')) {
|
|
4654
|
+
const id = selector.substring(1);
|
|
4655
|
+
return node.attributes.id === id;
|
|
4656
|
+
}
|
|
4657
|
+
// Handle class selectors like '.sensitive-field'
|
|
4658
|
+
if (selector.startsWith('.')) {
|
|
4659
|
+
const className = selector.substring(1);
|
|
4660
|
+
return node.attributes.class && node.attributes.class.includes(className);
|
|
4661
|
+
}
|
|
4662
|
+
// Handle tag selectors like 'input'
|
|
4663
|
+
if (!selector.includes('[') && !selector.includes('.')) {
|
|
4664
|
+
return node.tagName && node.tagName.toLowerCase() === selector.toLowerCase();
|
|
4665
|
+
}
|
|
4666
|
+
return false;
|
|
4667
|
+
}
|
|
4668
|
+
/**
|
|
4669
|
+
* Check if an event is from a field that should be redacted
|
|
4670
|
+
*/
|
|
4671
|
+
isFieldSelected(eventData) {
|
|
4672
|
+
if (!isBrowser$1)
|
|
4673
|
+
return false;
|
|
4674
|
+
try {
|
|
4675
|
+
// For input events (source 5), we need to determine if this is a sensitive field
|
|
4676
|
+
if (eventData.source === 5) { // Input event
|
|
4677
|
+
const elementId = eventData.id;
|
|
4678
|
+
if (elementId !== undefined) {
|
|
4679
|
+
// Try to find the element by data-rrweb-id attribute
|
|
4680
|
+
let element = document.querySelector(`[data-rrweb-id="${elementId}"]`);
|
|
4681
|
+
if (element) {
|
|
4682
|
+
return this.shouldRedactElement(element);
|
|
4683
|
+
}
|
|
4684
|
+
// Fallback: Try to find by nodeId if available
|
|
4685
|
+
if (eventData.nodeId !== undefined) {
|
|
4686
|
+
element = document.querySelector(`[data-rrweb-id="${eventData.nodeId}"]`);
|
|
4687
|
+
if (element) {
|
|
4688
|
+
return this.shouldRedactElement(element);
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4691
|
+
// More aggressive approach: Check all elements that match our selectors
|
|
4692
|
+
// and see if any of them are currently focused or have the same ID
|
|
4693
|
+
for (const selector of this.userSelectedFields) {
|
|
4694
|
+
const matchingElements = document.querySelectorAll(selector);
|
|
4695
|
+
if (matchingElements.length > 0) {
|
|
4696
|
+
// Check if any of these elements are currently focused
|
|
4697
|
+
for (const el of matchingElements) {
|
|
4698
|
+
if (el === document.activeElement) {
|
|
4699
|
+
console.log('Redaction: Found focused element matching selector:', selector);
|
|
4700
|
+
return true;
|
|
4701
|
+
}
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
// If we still can't find it, try a more direct approach
|
|
4706
|
+
// Look for any input element that might be the active one
|
|
4707
|
+
const activeElement = document.activeElement;
|
|
4708
|
+
if (activeElement && this.shouldRedactElement(activeElement)) {
|
|
4709
|
+
console.log('Redaction: Active element should be redacted');
|
|
4710
|
+
return true;
|
|
4711
|
+
}
|
|
4712
|
+
return false;
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
// For other event types, try to find the element
|
|
4716
|
+
const elementId = eventData.id;
|
|
4717
|
+
if (elementId !== undefined) {
|
|
4718
|
+
// First try to find by data-rrweb-id attribute
|
|
4719
|
+
let element = document.querySelector(`[data-rrweb-id="${elementId}"]`);
|
|
4720
|
+
if (element) {
|
|
4721
|
+
return this.shouldRedactElement(element);
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
// Also check for nodeId which is another way rrweb identifies elements
|
|
4725
|
+
const nodeId = eventData.nodeId;
|
|
4726
|
+
if (nodeId !== undefined) {
|
|
4727
|
+
const element = document.querySelector(`[data-rrweb-id="${nodeId}"]`);
|
|
4728
|
+
if (element) {
|
|
4729
|
+
return this.shouldRedactElement(element);
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4732
|
+
// For DOM mutations, check if the target element should be redacted
|
|
4733
|
+
if (eventData.target && eventData.target.id) {
|
|
4734
|
+
const element = document.querySelector(`[data-rrweb-id="${eventData.target.id}"]`);
|
|
4735
|
+
if (element) {
|
|
4736
|
+
return this.shouldRedactElement(element);
|
|
4737
|
+
}
|
|
4738
|
+
}
|
|
4739
|
+
return false;
|
|
4740
|
+
}
|
|
4741
|
+
catch (e) {
|
|
4742
|
+
console.warn('Error checking if field should be redacted:', e);
|
|
4743
|
+
return false;
|
|
4744
|
+
}
|
|
4745
|
+
}
|
|
4746
|
+
/**
|
|
4747
|
+
* Check if an element should be redacted based on user-selected fields
|
|
4748
|
+
*/
|
|
4749
|
+
shouldRedactElement(element) {
|
|
4750
|
+
// Check if element is excluded from redaction
|
|
4751
|
+
for (const excludeSelector of this.excludeSelectors) {
|
|
4752
|
+
if (element.matches(excludeSelector) || element.closest(excludeSelector)) {
|
|
4753
|
+
return false;
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
// Check if element matches any of the user-selected fields
|
|
4757
|
+
for (const selector of this.userSelectedFields) {
|
|
4758
|
+
if (element.matches(selector)) {
|
|
4759
|
+
return true;
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
return false;
|
|
4763
|
+
}
|
|
4764
|
+
/**
|
|
4765
|
+
* Get the original value of a redacted element (for debugging)
|
|
4766
|
+
*/
|
|
4767
|
+
getOriginalValue(element) {
|
|
4768
|
+
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
|
|
4769
|
+
return element.value;
|
|
4770
|
+
}
|
|
4771
|
+
return undefined;
|
|
4772
|
+
}
|
|
4773
|
+
/**
|
|
4774
|
+
* Check if an element is currently being redacted
|
|
4775
|
+
*/
|
|
4776
|
+
isElementRedacted(element) {
|
|
4777
|
+
return this.shouldRedactElement(element);
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
4780
|
+
// Export a default instance
|
|
4781
|
+
const redactionManager = new RedactionManager();
|
|
4474
4782
|
|
|
4475
4783
|
// Check if we're in a browser environment
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4784
|
+
const isBrowser = typeof window !== 'undefined';
|
|
4785
|
+
class HumanBehaviorTracker {
|
|
4786
|
+
constructor(apiKey, ingestionUrl) {
|
|
4479
4787
|
this.eventIngestionQueue = [];
|
|
4480
4788
|
this.queueSizeBytes = 0;
|
|
4481
4789
|
this.rejectedEvents = [];
|
|
@@ -4490,20 +4798,28 @@ var HumanBehaviorTracker = /** @class */ (function () {
|
|
|
4490
4798
|
if (!apiKey) {
|
|
4491
4799
|
throw new Error('Human Behavior API Key is required');
|
|
4492
4800
|
}
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4801
|
+
// ========================================
|
|
4802
|
+
// DEVELOPER: Choose your ingestion server
|
|
4803
|
+
// ========================================
|
|
4804
|
+
// Uncomment ONE of the following lines to select your server:
|
|
4805
|
+
// AWS Development Server
|
|
4806
|
+
const defaultIngestionUrl = 'http://3.137.217.33:3000';
|
|
4807
|
+
// Vercel Production Server
|
|
4808
|
+
// const defaultIngestionUrl = 'https://ingestion-server.vercel.app';
|
|
4809
|
+
// Local Development Server
|
|
4810
|
+
// const defaultIngestionUrl = 'http://localhost:3000';
|
|
4496
4811
|
this.api = new HumanBehaviorAPI({
|
|
4497
4812
|
apiKey: apiKey,
|
|
4498
|
-
ingestionUrl: ingestionUrl
|
|
4813
|
+
ingestionUrl: ingestionUrl || defaultIngestionUrl
|
|
4499
4814
|
});
|
|
4500
4815
|
this.apiKey = apiKey;
|
|
4816
|
+
this.redactionManager = new RedactionManager();
|
|
4501
4817
|
// Check for existing session ID and last activity time in localStorage
|
|
4502
|
-
|
|
4503
|
-
|
|
4818
|
+
const existingSessionId = isBrowser ? localStorage.getItem('human_behavior_session_id') : null;
|
|
4819
|
+
const lastActivity = isBrowser ? localStorage.getItem('human_behavior_last_activity') : null;
|
|
4504
4820
|
// If we have a last activity time, check if it's within 30 minutes
|
|
4505
|
-
|
|
4506
|
-
|
|
4821
|
+
const thirtyMinutesAgo = Date.now() - (30 * 60 * 1000);
|
|
4822
|
+
const shouldUseExistingSession = lastActivity && parseInt(lastActivity) > thirtyMinutesAgo;
|
|
4507
4823
|
this.sessionId = (existingSessionId && shouldUseExistingSession) ? existingSessionId : v1();
|
|
4508
4824
|
// Store the session ID if it's new
|
|
4509
4825
|
if ((!existingSessionId || !shouldUseExistingSession) && isBrowser) {
|
|
@@ -4512,305 +4828,305 @@ var HumanBehaviorTracker = /** @class */ (function () {
|
|
|
4512
4828
|
// Start initialization immediately
|
|
4513
4829
|
this.initializationPromise = this.init();
|
|
4514
4830
|
}
|
|
4515
|
-
|
|
4516
|
-
return __awaiter$1(this, void 0, void 0, function () {
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
this.setCookie("human_behavior_end_user_id_".concat(this.apiKey), endUserId, 365);
|
|
4529
|
-
// Only setup browser-specific handlers when in browser environment
|
|
4530
|
-
if (isBrowser) {
|
|
4531
|
-
this.setupPageUnloadHandler();
|
|
4532
|
-
this.start();
|
|
4533
|
-
this.processRejectedEvents();
|
|
4534
|
-
}
|
|
4535
|
-
else {
|
|
4536
|
-
console.warn('HumanBehaviorTracker initialized in a non-browser environment. Session tracking is disabled.');
|
|
4537
|
-
}
|
|
4538
|
-
this.initialized = true;
|
|
4539
|
-
console.log('HumanBehaviorTracker initialized');
|
|
4540
|
-
return [3 /*break*/, 3];
|
|
4541
|
-
case 2:
|
|
4542
|
-
error_1 = _b.sent();
|
|
4543
|
-
console.error('Failed to initialize HumanBehaviorTracker:', error_1);
|
|
4544
|
-
throw error_1;
|
|
4545
|
-
case 3: return [2 /*return*/];
|
|
4831
|
+
init() {
|
|
4832
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4833
|
+
try {
|
|
4834
|
+
const userId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);
|
|
4835
|
+
const { sessionId, endUserId } = yield this.api.init(this.sessionId, userId);
|
|
4836
|
+
this.sessionId = sessionId;
|
|
4837
|
+
this.endUserId = endUserId;
|
|
4838
|
+
this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, endUserId, 365);
|
|
4839
|
+
// Only setup browser-specific handlers when in browser environment
|
|
4840
|
+
if (isBrowser) {
|
|
4841
|
+
this.setupPageUnloadHandler();
|
|
4842
|
+
this.start();
|
|
4843
|
+
this.processRejectedEvents();
|
|
4546
4844
|
}
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
};
|
|
4550
|
-
HumanBehaviorTracker.prototype.ensureInitialized = function () {
|
|
4551
|
-
return __awaiter$1(this, void 0, void 0, function () {
|
|
4552
|
-
return __generator(this, function (_a) {
|
|
4553
|
-
switch (_a.label) {
|
|
4554
|
-
case 0:
|
|
4555
|
-
if (!this.initializationPromise) {
|
|
4556
|
-
throw new Error('HumanBehaviorTracker initialization failed');
|
|
4557
|
-
}
|
|
4558
|
-
return [4 /*yield*/, this.initializationPromise];
|
|
4559
|
-
case 1:
|
|
4560
|
-
_a.sent();
|
|
4561
|
-
return [2 /*return*/];
|
|
4845
|
+
else {
|
|
4846
|
+
console.warn('HumanBehaviorTracker initialized in a non-browser environment. Session tracking is disabled.');
|
|
4562
4847
|
}
|
|
4563
|
-
|
|
4848
|
+
this.initialized = true;
|
|
4849
|
+
console.log('HumanBehaviorTracker initialized');
|
|
4850
|
+
}
|
|
4851
|
+
catch (error) {
|
|
4852
|
+
console.error('Failed to initialize HumanBehaviorTracker:', error);
|
|
4853
|
+
throw error;
|
|
4854
|
+
}
|
|
4564
4855
|
});
|
|
4565
|
-
}
|
|
4566
|
-
|
|
4856
|
+
}
|
|
4857
|
+
ensureInitialized() {
|
|
4858
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4859
|
+
if (!this.initializationPromise) {
|
|
4860
|
+
throw new Error('HumanBehaviorTracker initialization failed');
|
|
4861
|
+
}
|
|
4862
|
+
yield this.initializationPromise;
|
|
4863
|
+
});
|
|
4864
|
+
}
|
|
4865
|
+
static logToStorage(message) {
|
|
4567
4866
|
try {
|
|
4568
|
-
|
|
4569
|
-
logs.push(
|
|
4867
|
+
const logs = JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');
|
|
4868
|
+
logs.push(`${new Date().toISOString()}: ${message}`);
|
|
4570
4869
|
localStorage.setItem('human_behavior_logs', JSON.stringify(logs));
|
|
4571
4870
|
}
|
|
4572
4871
|
catch (e) {
|
|
4573
4872
|
console.error('Failed to log to storage:', e);
|
|
4574
4873
|
}
|
|
4575
|
-
}
|
|
4576
|
-
|
|
4577
|
-
var _this = this;
|
|
4874
|
+
}
|
|
4875
|
+
setupPageUnloadHandler() {
|
|
4578
4876
|
if (!isBrowser)
|
|
4579
4877
|
return;
|
|
4580
4878
|
console.log('Setting up page unload handler');
|
|
4581
4879
|
// Handle visibility changes for sending events
|
|
4582
|
-
window.addEventListener('visibilitychange',
|
|
4880
|
+
window.addEventListener('visibilitychange', () => {
|
|
4583
4881
|
// Only send events when page becomes hidden
|
|
4584
4882
|
if (document.visibilityState === 'hidden') {
|
|
4585
4883
|
console.log('Page hidden - sending pending events');
|
|
4586
|
-
|
|
4884
|
+
this.api.sendBeaconEvents(this.eventIngestionQueue, this.sessionId);
|
|
4587
4885
|
}
|
|
4588
4886
|
});
|
|
4589
4887
|
// Handle actual page unload/close
|
|
4590
|
-
window.addEventListener('beforeunload',
|
|
4888
|
+
window.addEventListener('beforeunload', () => {
|
|
4591
4889
|
// Update last activity time
|
|
4592
4890
|
localStorage.setItem('human_behavior_last_activity', Date.now().toString());
|
|
4593
4891
|
// Send final events
|
|
4594
|
-
|
|
4892
|
+
this.api.sendBeaconEvents(this.eventIngestionQueue, this.sessionId);
|
|
4595
4893
|
});
|
|
4596
4894
|
// Update activity timestamp periodically
|
|
4597
|
-
setInterval(
|
|
4895
|
+
setInterval(() => {
|
|
4598
4896
|
localStorage.setItem('human_behavior_last_activity', Date.now().toString());
|
|
4599
4897
|
}, 60000); // Update every minute
|
|
4600
|
-
}
|
|
4601
|
-
|
|
4898
|
+
}
|
|
4899
|
+
viewLogs() {
|
|
4602
4900
|
try {
|
|
4603
|
-
|
|
4901
|
+
const logs = JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');
|
|
4604
4902
|
console.log('HumanBehavior Logs:', logs);
|
|
4605
4903
|
localStorage.removeItem('human_behavior_logs'); // Clear logs after viewing
|
|
4606
4904
|
}
|
|
4607
4905
|
catch (e) {
|
|
4608
4906
|
console.error('Failed to read logs:', e);
|
|
4609
4907
|
}
|
|
4610
|
-
}
|
|
4611
|
-
|
|
4612
|
-
return __awaiter$1(this, void 0, void 0, function () {
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
return [4 /*yield*/, this.api.sendUserData(this.endUserId, userProperties, this.sessionId)];
|
|
4620
|
-
case 2:
|
|
4621
|
-
_a.sent();
|
|
4622
|
-
return [2 /*return*/];
|
|
4623
|
-
}
|
|
4624
|
-
});
|
|
4908
|
+
}
|
|
4909
|
+
addUserInfo(userProperties) {
|
|
4910
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4911
|
+
yield this.ensureInitialized();
|
|
4912
|
+
if (!this.endUserId) {
|
|
4913
|
+
throw new Error('Cannot add user info before tracker initialization');
|
|
4914
|
+
}
|
|
4915
|
+
this.userProperties = userProperties;
|
|
4916
|
+
yield this.api.sendUserData(this.endUserId, userProperties, this.sessionId);
|
|
4625
4917
|
});
|
|
4626
|
-
}
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4918
|
+
}
|
|
4919
|
+
/**
|
|
4920
|
+
* Authenticate user using existing userInfo data
|
|
4921
|
+
* @param authFields Array of field names to check for existing users (e.g., ['email', 'phoneNumber'])
|
|
4922
|
+
*/
|
|
4923
|
+
auth(authFields) {
|
|
4924
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4925
|
+
yield this.ensureInitialized();
|
|
4926
|
+
if (!this.endUserId) {
|
|
4927
|
+
throw new Error('Cannot authenticate before tracker initialization');
|
|
4928
|
+
}
|
|
4929
|
+
if (!this.userProperties || Object.keys(this.userProperties).length === 0) {
|
|
4930
|
+
throw new Error('No user info available. Call addUserInfo() first.');
|
|
4931
|
+
}
|
|
4932
|
+
yield this.api.sendUserAuth(this.endUserId, this.userProperties, this.sessionId, authFields);
|
|
4639
4933
|
});
|
|
4640
|
-
}
|
|
4641
|
-
|
|
4642
|
-
return __awaiter$1(this,
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
switch (_a.label) {
|
|
4646
|
-
case 0: return [4 /*yield*/, this.ensureInitialized()];
|
|
4647
|
-
case 1:
|
|
4648
|
-
_a.sent();
|
|
4649
|
-
if (!isBrowser)
|
|
4650
|
-
return [2 /*return*/];
|
|
4651
|
-
// Start periodic flushing
|
|
4652
|
-
this.flushInterval = window.setInterval(function () {
|
|
4653
|
-
_this.flush();
|
|
4654
|
-
}, this.FLUSH_INTERVAL_MS);
|
|
4655
|
-
// Start recording
|
|
4656
|
-
record({
|
|
4657
|
-
emit: function (event) {
|
|
4658
|
-
_this.addEvent(event);
|
|
4659
|
-
},
|
|
4660
|
-
});
|
|
4661
|
-
return [2 /*return*/];
|
|
4662
|
-
}
|
|
4663
|
-
});
|
|
4934
|
+
}
|
|
4935
|
+
customEvent(eventName_1) {
|
|
4936
|
+
return __awaiter$1(this, arguments, void 0, function* (eventName, eventProperties = {}) {
|
|
4937
|
+
yield this.ensureInitialized();
|
|
4938
|
+
this.api.sendBeaconCustomEvent(eventName, eventProperties, this.sessionId);
|
|
4664
4939
|
});
|
|
4665
|
-
}
|
|
4666
|
-
|
|
4667
|
-
return __awaiter$1(this, void 0, void 0, function () {
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4940
|
+
}
|
|
4941
|
+
start() {
|
|
4942
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4943
|
+
yield this.ensureInitialized();
|
|
4944
|
+
if (!isBrowser)
|
|
4945
|
+
return;
|
|
4946
|
+
// Start periodic flushing
|
|
4947
|
+
this.flushInterval = window.setInterval(() => {
|
|
4948
|
+
this.flush();
|
|
4949
|
+
}, this.FLUSH_INTERVAL_MS);
|
|
4950
|
+
// Start recording with redaction enabled
|
|
4951
|
+
record({
|
|
4952
|
+
emit: (event) => {
|
|
4953
|
+
this.addEvent(event);
|
|
4954
|
+
},
|
|
4955
|
+
inlineStylesheet: true,
|
|
4956
|
+
recordCanvas: true,
|
|
4957
|
+
collectFonts: true,
|
|
4958
|
+
blockClass: 'rr-block',
|
|
4959
|
+
ignoreClass: 'rr-ignore',
|
|
4960
|
+
maskTextClass: 'rr-ignore'
|
|
4681
4961
|
});
|
|
4682
4962
|
});
|
|
4683
|
-
}
|
|
4684
|
-
|
|
4685
|
-
return __awaiter$1(this, void 0, void 0, function () {
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
this.eventIngestionQueue.push(event);
|
|
4694
|
-
this.queueSizeBytes += eventSize;
|
|
4695
|
-
return [2 /*return*/];
|
|
4696
|
-
}
|
|
4697
|
-
});
|
|
4963
|
+
}
|
|
4964
|
+
stop() {
|
|
4965
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4966
|
+
yield this.ensureInitialized();
|
|
4967
|
+
if (!isBrowser)
|
|
4968
|
+
return;
|
|
4969
|
+
if (this.flushInterval) {
|
|
4970
|
+
clearInterval(this.flushInterval);
|
|
4971
|
+
this.flushInterval = null;
|
|
4972
|
+
}
|
|
4698
4973
|
});
|
|
4699
|
-
}
|
|
4700
|
-
|
|
4701
|
-
return __awaiter$1(this, void 0, void 0, function () {
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
}
|
|
4721
|
-
catch (error) {
|
|
4722
|
-
console.error('Failed to process rejected events:', error);
|
|
4723
|
-
}
|
|
4724
|
-
finally {
|
|
4725
|
-
this.isProcessingRejectedEvents = false;
|
|
4974
|
+
}
|
|
4975
|
+
addEvent(event) {
|
|
4976
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4977
|
+
yield this.ensureInitialized();
|
|
4978
|
+
// Process event through redaction manager if active
|
|
4979
|
+
const processedEvent = this.redactionManager.processEvent(event);
|
|
4980
|
+
const eventSize = new TextEncoder().encode(JSON.stringify(processedEvent)).length;
|
|
4981
|
+
this.eventIngestionQueue.push(processedEvent);
|
|
4982
|
+
this.queueSizeBytes += eventSize;
|
|
4983
|
+
});
|
|
4984
|
+
}
|
|
4985
|
+
processRejectedEvents() {
|
|
4986
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4987
|
+
if (this.isProcessingRejectedEvents || this.rejectedEvents.length === 0)
|
|
4988
|
+
return;
|
|
4989
|
+
this.isProcessingRejectedEvents = true;
|
|
4990
|
+
try {
|
|
4991
|
+
// Create a new session ID for rejected events
|
|
4992
|
+
const newSessionId = v1();
|
|
4993
|
+
if (isBrowser) {
|
|
4994
|
+
localStorage.setItem('human_behavior_session_id', newSessionId);
|
|
4726
4995
|
}
|
|
4727
|
-
|
|
4728
|
-
|
|
4996
|
+
// Try to send rejected events with new session ID using beacon
|
|
4997
|
+
// sendBeacon returns true if the request was queued successfully
|
|
4998
|
+
this.api.sendBeaconEvents(this.rejectedEvents, newSessionId);
|
|
4999
|
+
// Clear rejected events and update session ID
|
|
5000
|
+
// Note: We can't verify if the beacon data was actually sent,
|
|
5001
|
+
// but we clear the events to prevent duplicate sending attempts
|
|
5002
|
+
this.rejectedEvents = [];
|
|
5003
|
+
this.sessionId = newSessionId;
|
|
5004
|
+
}
|
|
5005
|
+
catch (error) {
|
|
5006
|
+
console.error('Failed to process rejected events:', error);
|
|
5007
|
+
}
|
|
5008
|
+
finally {
|
|
5009
|
+
this.isProcessingRejectedEvents = false;
|
|
5010
|
+
}
|
|
4729
5011
|
});
|
|
4730
|
-
}
|
|
4731
|
-
|
|
4732
|
-
return __awaiter$1(this, void 0, void 0, function () {
|
|
4733
|
-
var eventsToProcess, error_2;
|
|
5012
|
+
}
|
|
5013
|
+
flush() {
|
|
5014
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4734
5015
|
var _a;
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
this.
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
console.log('Flushing events:', eventsToProcess);
|
|
4752
|
-
_c.label = 2;
|
|
4753
|
-
case 2:
|
|
4754
|
-
_c.trys.push([2, 4, , 5]);
|
|
4755
|
-
return [4 /*yield*/, this.api.sendEvents(eventsToProcess, this.sessionId, this.endUserId)];
|
|
4756
|
-
case 3:
|
|
4757
|
-
_c.sent();
|
|
4758
|
-
return [3 /*break*/, 5];
|
|
4759
|
-
case 4:
|
|
4760
|
-
error_2 = _c.sent();
|
|
5016
|
+
// Prevent concurrent flushes
|
|
5017
|
+
if (this.isProcessing || !this.initialized) {
|
|
5018
|
+
return;
|
|
5019
|
+
}
|
|
5020
|
+
this.isProcessing = true;
|
|
5021
|
+
try {
|
|
5022
|
+
// Swap the current queue with an empty one atomically
|
|
5023
|
+
const eventsToProcess = this.eventIngestionQueue;
|
|
5024
|
+
this.eventIngestionQueue = [];
|
|
5025
|
+
this.queueSizeBytes = 0;
|
|
5026
|
+
if (eventsToProcess.length > 0) {
|
|
5027
|
+
console.log('Flushing events:', eventsToProcess);
|
|
5028
|
+
try {
|
|
5029
|
+
yield this.api.sendEvents(eventsToProcess, this.sessionId, this.endUserId);
|
|
5030
|
+
}
|
|
5031
|
+
catch (error) {
|
|
4761
5032
|
// If we get a 400 error, store events for retry
|
|
4762
|
-
if ((
|
|
5033
|
+
if ((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('ERROR: Session already completed')) {
|
|
4763
5034
|
console.log('Session expired, storing events for retry');
|
|
4764
|
-
|
|
5035
|
+
this.rejectedEvents.push(...eventsToProcess);
|
|
4765
5036
|
this.processRejectedEvents();
|
|
4766
5037
|
}
|
|
4767
5038
|
else {
|
|
4768
|
-
throw
|
|
5039
|
+
throw error;
|
|
4769
5040
|
}
|
|
4770
|
-
|
|
4771
|
-
case 5: return [3 /*break*/, 7];
|
|
4772
|
-
case 6:
|
|
4773
|
-
this.isProcessing = false;
|
|
4774
|
-
return [7 /*endfinally*/];
|
|
4775
|
-
case 7: return [2 /*return*/];
|
|
5041
|
+
}
|
|
4776
5042
|
}
|
|
4777
|
-
}
|
|
5043
|
+
}
|
|
5044
|
+
finally {
|
|
5045
|
+
this.isProcessing = false;
|
|
5046
|
+
}
|
|
4778
5047
|
});
|
|
4779
|
-
}
|
|
5048
|
+
}
|
|
4780
5049
|
// Add helper methods for cookie management
|
|
4781
|
-
|
|
5050
|
+
setCookie(name, value, daysToExpire) {
|
|
4782
5051
|
if (!isBrowser)
|
|
4783
5052
|
return;
|
|
4784
|
-
|
|
5053
|
+
const date = new Date();
|
|
4785
5054
|
date.setTime(date.getTime() + (daysToExpire * 24 * 60 * 60 * 1000));
|
|
4786
|
-
|
|
4787
|
-
document.cookie =
|
|
4788
|
-
}
|
|
4789
|
-
|
|
5055
|
+
const expires = `expires=${date.toUTCString()}`;
|
|
5056
|
+
document.cookie = `${name}=${value};${expires};path=/;SameSite=Lax`;
|
|
5057
|
+
}
|
|
5058
|
+
getCookie(name) {
|
|
4790
5059
|
if (!isBrowser)
|
|
4791
5060
|
return null;
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
for (
|
|
4795
|
-
|
|
5061
|
+
const nameEQ = name + "=";
|
|
5062
|
+
const ca = document.cookie.split(';');
|
|
5063
|
+
for (let i = 0; i < ca.length; i++) {
|
|
5064
|
+
let c = ca[i];
|
|
4796
5065
|
while (c.charAt(0) === ' ')
|
|
4797
5066
|
c = c.substring(1, c.length);
|
|
4798
5067
|
if (c.indexOf(nameEQ) === 0)
|
|
4799
5068
|
return c.substring(nameEQ.length, c.length);
|
|
4800
5069
|
}
|
|
4801
5070
|
return null;
|
|
4802
|
-
}
|
|
4803
|
-
|
|
4804
|
-
|
|
5071
|
+
}
|
|
5072
|
+
/**
|
|
5073
|
+
* Start redaction functionality for sensitive input fields
|
|
5074
|
+
* @param options Optional configuration for redaction behavior
|
|
5075
|
+
*/
|
|
5076
|
+
redact(options) {
|
|
5077
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5078
|
+
yield this.ensureInitialized();
|
|
5079
|
+
if (!isBrowser) {
|
|
5080
|
+
console.warn('Redaction is only available in browser environments');
|
|
5081
|
+
return;
|
|
5082
|
+
}
|
|
5083
|
+
// Create a new redaction manager with the provided options
|
|
5084
|
+
this.redactionManager = new RedactionManager(options);
|
|
5085
|
+
});
|
|
5086
|
+
}
|
|
5087
|
+
/**
|
|
5088
|
+
* Set specific fields to be redacted during session recording
|
|
5089
|
+
* @param fields Array of CSS selectors for fields to redact (e.g., ['input[type="password"]', '#email-field'])
|
|
5090
|
+
*/
|
|
5091
|
+
setRedactedFields(fields) {
|
|
5092
|
+
if (!isBrowser) {
|
|
5093
|
+
console.warn('Redaction is only available in browser environments');
|
|
5094
|
+
return;
|
|
5095
|
+
}
|
|
5096
|
+
this.redactionManager.setFieldsToRedact(fields);
|
|
5097
|
+
}
|
|
5098
|
+
/**
|
|
5099
|
+
* Check if redaction is currently active
|
|
5100
|
+
*/
|
|
5101
|
+
isRedactionActive() {
|
|
5102
|
+
return this.redactionManager.isActive();
|
|
5103
|
+
}
|
|
5104
|
+
/**
|
|
5105
|
+
* Get the currently selected fields for redaction
|
|
5106
|
+
*/
|
|
5107
|
+
getRedactedFields() {
|
|
5108
|
+
return this.redactionManager.getSelectedFields();
|
|
5109
|
+
}
|
|
5110
|
+
}
|
|
4805
5111
|
// Only expose to window object in browser environments
|
|
4806
5112
|
if (isBrowser) {
|
|
4807
5113
|
window.HumanBehaviorTracker = HumanBehaviorTracker;
|
|
4808
5114
|
}
|
|
4809
5115
|
|
|
5116
|
+
/**
|
|
5117
|
+
* Main entry point for the HumanBehavior SDK
|
|
5118
|
+
*/
|
|
5119
|
+
// For UMD builds, expose the main class globally
|
|
5120
|
+
if (typeof window !== 'undefined') {
|
|
5121
|
+
window.HumanBehaviorTracker = HumanBehaviorTracker;
|
|
5122
|
+
}
|
|
5123
|
+
|
|
4810
5124
|
exports.HumanBehaviorAPI = HumanBehaviorAPI;
|
|
4811
5125
|
exports.HumanBehaviorTracker = HumanBehaviorTracker;
|
|
4812
5126
|
exports.MAX_CHUNK_SIZE_BYTES = MAX_CHUNK_SIZE_BYTES;
|
|
5127
|
+
exports.RedactionManager = RedactionManager;
|
|
4813
5128
|
exports.default = HumanBehaviorTracker;
|
|
4814
5129
|
exports.isChunkSizeExceeded = isChunkSizeExceeded;
|
|
5130
|
+
exports.redactionManager = redactionManager;
|
|
4815
5131
|
exports.validateSingleEventSize = validateSingleEventSize;
|
|
4816
5132
|
//# sourceMappingURL=index.js.map
|