@anyreplay/browser 0.2.0 → 0.2.1

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/README.md CHANGED
@@ -77,7 +77,10 @@ Some fields are masked in every mode and no option records them: passwords and
77
77
  one-time codes; card numbers, security codes, expiry dates and cardholder names
78
78
  (from `autocomplete="cc-…"` or from a name, label or placeholder that reads like
79
79
  one); IBANs, routing numbers and sort codes; any value of 13–19 digits that
80
- passes the card-number check. Masking always happens in the page, before
80
+ passes the card-number check — and, because a number is typed one key at a
81
+ time, any value that could still become one: digits only, starting 2–6,
82
+ grouped like a card, more than six digits long. At most the first six digits
83
+ of a card number are ever recorded. Masking always happens in the page, before
81
84
  anything is sent.
82
85
 
83
86
  `type="email"` and `type="tel"` are not on that list. An address and a phone
package/dist/index.cjs CHANGED
@@ -3864,10 +3864,12 @@ var AssetCollector = class {
3864
3864
  body: JSON.stringify(body)
3865
3865
  });
3866
3866
  const known = await post("/v1/ingest/assets/known", { projectKey, hashes: [sha256], ...app });
3867
- if (known.ok) {
3868
- const body = await known.json();
3869
- if (Array.isArray(body.known) && body.known.includes(sha256)) return true;
3867
+ if (!known.ok) {
3868
+ if (known.status === 401 || known.status === 403) this.stop();
3869
+ return false;
3870
3870
  }
3871
+ const answer = await known.json();
3872
+ if (Array.isArray(answer.known) && answer.known.includes(sha256)) return true;
3871
3873
  const upload = await post("/v1/ingest/assets", { projectKey, sha256, data: toBase64(bytes), ...app });
3872
3874
  return upload.ok;
3873
3875
  }
@@ -4172,6 +4174,13 @@ function looksLikeCardNumber(text) {
4172
4174
  }
4173
4175
  return sum % 10 === 0;
4174
4176
  }
4177
+ function mayBecomeCardNumber(text) {
4178
+ if (!/^[2-6][0-9 -]*$/.test(text)) return false;
4179
+ const groups = text.split(/[ -]+/);
4180
+ if (groups.slice(0, -1).some((group) => group.length < 4)) return false;
4181
+ const digits = groups.join("").length;
4182
+ return digits > 6 && digits <= 19;
4183
+ }
4175
4184
  function isMarkedByCustomer(node, maskClass) {
4176
4185
  var _a;
4177
4186
  let element = asElement(node);
@@ -4186,8 +4195,19 @@ function shouldMaskValue(text, node, policy) {
4186
4195
  if (policy.maskEveryField) return true;
4187
4196
  if (isSensitiveField(node)) return true;
4188
4197
  if (isMarkedByCustomer(node, policy.maskClass)) return true;
4189
- return looksLikeCardNumber(text);
4198
+ return cardShaped(text);
4199
+ }
4200
+ var cardShaped = (text) => looksLikeCardNumber(text) || mayBecomeCardNumber(text);
4201
+ var EDITABLE = '[contenteditable]:not([contenteditable="false"])';
4202
+ function insideEditable(node) {
4203
+ var _a;
4204
+ const live = node;
4205
+ const element = (live == null ? void 0 : live.nodeType) === 3 ? (_a = live.parentElement) != null ? _a : null : asElement(live);
4206
+ return typeof (element == null ? void 0 : element.closest) === "function" && element.closest(EDITABLE) !== null;
4190
4207
  }
4208
+ var isEditableSerialised = (attributes) => attributes.contenteditable !== void 0 && attributes.contenteditable !== "false";
4209
+ var typedCardShaped = (text) => cardShaped(text.replace(/\u00a0/g, " ").trim());
4210
+ var maskText = (text) => text.replace(/\S/g, "*");
4191
4211
  var MUTATION = 0;
4192
4212
  var INPUT = 5;
4193
4213
  var ELEMENT_NODE = 2;
@@ -4202,23 +4222,27 @@ function maskTextChildren(node) {
4202
4222
  maskTextChildren(child);
4203
4223
  }
4204
4224
  }
4205
- function scrubTree(node, policy, masked) {
4225
+ function scrubTree(node, policy, masked, editable = false) {
4206
4226
  var _a, _b;
4207
4227
  if (!node) return;
4208
4228
  const attributes = (_a = node.attributes) != null ? _a : {};
4209
4229
  const inheritedMask = masked || hasClass(attributes, policy.maskClass);
4230
+ const inheritedEditable = editable || node.type === ELEMENT_NODE && isEditableSerialised(attributes);
4231
+ if (editable && node.type === TEXT_NODE && typeof node.textContent === "string" && typedCardShaped(node.textContent)) {
4232
+ node.textContent = maskText(node.textContent);
4233
+ }
4210
4234
  if (node.type === ELEMENT_NODE && node.tagName && FIELD_TAGS.has(node.tagName.toLowerCase())) {
4211
4235
  const value = attributes.value;
4212
- const shouldMask = policy.maskEveryField || inheritedMask || isSensitiveSerialised(attributes) || typeof value === "string" && looksLikeCardNumber(value);
4236
+ const shouldMask = policy.maskEveryField || inheritedMask || isSensitiveSerialised(attributes) || typeof value === "string" && cardShaped(value);
4213
4237
  if (shouldMask) {
4214
4238
  if (typeof value === "string" && value !== "") attributes.value = maskValue(value);
4215
4239
  if (node.tagName.toLowerCase() === "textarea") maskTextChildren(node);
4216
4240
  }
4217
4241
  }
4218
- for (const child of (_b = node.childNodes) != null ? _b : []) scrubTree(child, policy, inheritedMask);
4242
+ for (const child of (_b = node.childNodes) != null ? _b : []) scrubTree(child, policy, inheritedMask, inheritedEditable);
4219
4243
  }
4220
4244
  function scrubEvent(event, getNode, policy) {
4221
- var _a, _b, _c, _d, _e;
4245
+ var _a, _b, _c, _d, _e, _f;
4222
4246
  if (!event.data) return;
4223
4247
  if (event.type === 2) {
4224
4248
  scrubTree(event.data.node, policy, false);
@@ -4243,7 +4267,11 @@ function scrubEvent(event, getNode, policy) {
4243
4267
  }
4244
4268
  for (const add of (_e = data.adds) != null ? _e : []) {
4245
4269
  const parent = typeof (add == null ? void 0 : add.parentId) === "number" ? getNode(add.parentId) : null;
4246
- scrubTree(add == null ? void 0 : add.node, policy, isMarkedByCustomer(parent, policy.maskClass));
4270
+ scrubTree(add == null ? void 0 : add.node, policy, isMarkedByCustomer(parent, policy.maskClass), insideEditable(parent));
4271
+ }
4272
+ for (const change of (_f = data.texts) != null ? _f : []) {
4273
+ if (typeof (change == null ? void 0 : change.value) !== "string" || !typedCardShaped(change.value)) continue;
4274
+ if (typeof change.id === "number" && insideEditable(getNode(change.id))) change.value = maskText(change.value);
4247
4275
  }
4248
4276
  }
4249
4277
 
@@ -5049,7 +5077,7 @@ var Transport = class {
5049
5077
 
5050
5078
  // src/version.ts
5051
5079
  var SDK_NAME = "@anyreplay/browser";
5052
- var SDK_VERSION = true ? "0.2.0" : "0.0.0-dev";
5080
+ var SDK_VERSION = true ? "0.2.1" : "0.0.0-dev";
5053
5081
  var PLATFORM = "web";
5054
5082
 
5055
5083
  // src/visitor.ts
@@ -5156,6 +5184,8 @@ function forgetVisitor(local, session) {
5156
5184
  }
5157
5185
 
5158
5186
  // src/recorder.ts
5187
+ var RRWEB_DOM_CONTENT_LOADED = 0;
5188
+ var RRWEB_LOAD = 1;
5159
5189
  var RAGE_CLICK_COUNT = 3;
5160
5190
  var RAGE_CLICK_WINDOW_MS = 1e3;
5161
5191
  var RAGE_CLICK_RADIUS_PX = 30;
@@ -5353,6 +5383,7 @@ function createRecorder(rawOptions, shell) {
5353
5383
  stopRecording = record({
5354
5384
  emit: (event) => {
5355
5385
  const typed = event;
5386
+ if (typed.type === RRWEB_DOM_CONTENT_LOADED || typed.type === RRWEB_LOAD) return;
5356
5387
  scrubEvent(typed, (id) => record.mirror.getNode(id), policy);
5357
5388
  assets == null ? void 0 : assets.scan(typed);
5358
5389
  noteInteraction(typed, channel);