@anyreplay/browser 0.2.0 → 0.2.2

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/index.d.cts CHANGED
@@ -504,6 +504,14 @@ interface ShellInfo {
504
504
  appVersion?: string;
505
505
  /** The hardware model (`iPhone15,2`, `Pixel 8`), ≤ 64. */
506
506
  deviceModel?: string;
507
+ /**
508
+ * Called the moment a chunk number is reserved, before the request that
509
+ * uses it leaves. A shell that copies the recorder's keys into native
510
+ * storage copies them then: an app killed between a send and the shell's
511
+ * next periodic copy came back with the old number, and sent its first
512
+ * chunk under a number the server already held (seen on an iOS simulator).
513
+ */
514
+ onStateChanged?: () => void;
507
515
  }
508
516
  type RecorderStatus = 'idle' | 'awaiting-consent' | 'recording' | 'sampled-out' | 'stopped';
509
517
  interface IdentifyTraits {
package/dist/index.d.ts CHANGED
@@ -504,6 +504,14 @@ interface ShellInfo {
504
504
  appVersion?: string;
505
505
  /** The hardware model (`iPhone15,2`, `Pixel 8`), ≤ 64. */
506
506
  deviceModel?: string;
507
+ /**
508
+ * Called the moment a chunk number is reserved, before the request that
509
+ * uses it leaves. A shell that copies the recorder's keys into native
510
+ * storage copies them then: an app killed between a send and the shell's
511
+ * next periodic copy came back with the old number, and sent its first
512
+ * chunk under a number the server already held (seen on an iOS simulator).
513
+ */
514
+ onStateChanged?: () => void;
507
515
  }
508
516
  type RecorderStatus = 'idle' | 'awaiting-consent' | 'recording' | 'sampled-out' | 'stopped';
509
517
  interface IdentifyTraits {
package/dist/index.js CHANGED
@@ -3815,10 +3815,12 @@ var AssetCollector = class {
3815
3815
  body: JSON.stringify(body)
3816
3816
  });
3817
3817
  const known = await post("/v1/ingest/assets/known", { projectKey, hashes: [sha256], ...app });
3818
- if (known.ok) {
3819
- const body = await known.json();
3820
- if (Array.isArray(body.known) && body.known.includes(sha256)) return true;
3818
+ if (!known.ok) {
3819
+ if (known.status === 401 || known.status === 403) this.stop();
3820
+ return false;
3821
3821
  }
3822
+ const answer = await known.json();
3823
+ if (Array.isArray(answer.known) && answer.known.includes(sha256)) return true;
3822
3824
  const upload = await post("/v1/ingest/assets", { projectKey, sha256, data: toBase64(bytes), ...app });
3823
3825
  return upload.ok;
3824
3826
  }
@@ -4123,6 +4125,13 @@ function looksLikeCardNumber(text) {
4123
4125
  }
4124
4126
  return sum % 10 === 0;
4125
4127
  }
4128
+ function mayBecomeCardNumber(text) {
4129
+ if (!/^[2-6][0-9 -]*$/.test(text)) return false;
4130
+ const groups = text.split(/[ -]+/);
4131
+ if (groups.slice(0, -1).some((group) => group.length < 4)) return false;
4132
+ const digits = groups.join("").length;
4133
+ return digits > 6 && digits <= 19;
4134
+ }
4126
4135
  function isMarkedByCustomer(node, maskClass) {
4127
4136
  var _a;
4128
4137
  let element = asElement(node);
@@ -4137,8 +4146,53 @@ function shouldMaskValue(text, node, policy) {
4137
4146
  if (policy.maskEveryField) return true;
4138
4147
  if (isSensitiveField(node)) return true;
4139
4148
  if (isMarkedByCustomer(node, policy.maskClass)) return true;
4140
- return looksLikeCardNumber(text);
4149
+ return cardShaped(text);
4150
+ }
4151
+ var cardShaped = (text) => looksLikeCardNumber(text) || mayBecomeCardNumber(text);
4152
+ var DIGIT_RUN = /[0-9]+(?:[  -][0-9]+)*/g;
4153
+ function maskCardRuns(text) {
4154
+ if (!/[0-9]/.test(text)) return text;
4155
+ return text.replace(DIGIT_RUN, (run) => {
4156
+ const groups = run.split(/[  -]/);
4157
+ const starts = [];
4158
+ let at = 0;
4159
+ for (const group of groups) {
4160
+ starts.push(at);
4161
+ at += group.length + 1;
4162
+ }
4163
+ const chars2 = run.split("");
4164
+ let i = 0;
4165
+ while (i < groups.length) {
4166
+ let matched = -1;
4167
+ for (let j = groups.length - 1; j >= i; j -= 1) {
4168
+ const end2 = starts[j] + groups[j].length;
4169
+ if (cardShaped(run.slice(starts[i], end2).replace(/ /g, " "))) {
4170
+ matched = j;
4171
+ break;
4172
+ }
4173
+ }
4174
+ if (matched < 0) {
4175
+ i += 1;
4176
+ continue;
4177
+ }
4178
+ const end = starts[matched] + groups[matched].length;
4179
+ for (let k = starts[i]; k < end; k += 1) if (chars2[k] !== " " && chars2[k] !== "\xA0" && chars2[k] !== "-") chars2[k] = "*";
4180
+ i = matched + 1;
4181
+ }
4182
+ return chars2.join("");
4183
+ });
4141
4184
  }
4185
+ var EDITABLE = '[contenteditable]:not([contenteditable="false"])';
4186
+ function insideEditable(node) {
4187
+ var _a;
4188
+ const live = node;
4189
+ const element = (live == null ? void 0 : live.nodeType) === 3 ? (_a = live.parentElement) != null ? _a : null : asElement(live);
4190
+ return typeof (element == null ? void 0 : element.closest) === "function" && element.closest(EDITABLE) !== null;
4191
+ }
4192
+ var isEditableSerialised = (attributes) => attributes.contenteditable !== void 0 && attributes.contenteditable !== "false";
4193
+ var typedCardShaped = (text) => cardShaped(text.replace(/\u00a0/g, " ").trim());
4194
+ var maskText = (text) => text.replace(/\S/g, "*");
4195
+ var maskTyped = (text) => typedCardShaped(text) ? maskText(text) : maskCardRuns(text);
4142
4196
  var MUTATION = 0;
4143
4197
  var INPUT = 5;
4144
4198
  var ELEMENT_NODE = 2;
@@ -4153,23 +4207,36 @@ function maskTextChildren(node) {
4153
4207
  maskTextChildren(child);
4154
4208
  }
4155
4209
  }
4156
- function scrubTree(node, policy, masked) {
4210
+ function maskCardRunsInChildren(node) {
4211
+ var _a;
4212
+ for (const child of (_a = node.childNodes) != null ? _a : []) {
4213
+ if (child.type === TEXT_NODE && typeof child.textContent === "string") child.textContent = maskCardRuns(child.textContent);
4214
+ }
4215
+ }
4216
+ function scrubTree(node, policy, masked, editable = false) {
4157
4217
  var _a, _b;
4158
4218
  if (!node) return;
4159
4219
  const attributes = (_a = node.attributes) != null ? _a : {};
4160
4220
  const inheritedMask = masked || hasClass(attributes, policy.maskClass);
4221
+ const inheritedEditable = editable || node.type === ELEMENT_NODE && isEditableSerialised(attributes);
4222
+ if (editable && node.type === TEXT_NODE && typeof node.textContent === "string") {
4223
+ node.textContent = maskTyped(node.textContent);
4224
+ }
4161
4225
  if (node.type === ELEMENT_NODE && node.tagName && FIELD_TAGS.has(node.tagName.toLowerCase())) {
4162
4226
  const value = attributes.value;
4163
- const shouldMask = policy.maskEveryField || inheritedMask || isSensitiveSerialised(attributes) || typeof value === "string" && looksLikeCardNumber(value);
4227
+ const shouldMask = policy.maskEveryField || inheritedMask || isSensitiveSerialised(attributes) || typeof value === "string" && cardShaped(value);
4164
4228
  if (shouldMask) {
4165
4229
  if (typeof value === "string" && value !== "") attributes.value = maskValue(value);
4166
4230
  if (node.tagName.toLowerCase() === "textarea") maskTextChildren(node);
4231
+ } else {
4232
+ if (typeof value === "string") attributes.value = maskCardRuns(value);
4233
+ if (node.tagName.toLowerCase() === "textarea") maskCardRunsInChildren(node);
4167
4234
  }
4168
4235
  }
4169
- for (const child of (_b = node.childNodes) != null ? _b : []) scrubTree(child, policy, inheritedMask);
4236
+ for (const child of (_b = node.childNodes) != null ? _b : []) scrubTree(child, policy, inheritedMask, inheritedEditable);
4170
4237
  }
4171
4238
  function scrubEvent(event, getNode, policy) {
4172
- var _a, _b, _c, _d, _e;
4239
+ var _a, _b, _c, _d, _e, _f;
4173
4240
  if (!event.data) return;
4174
4241
  if (event.type === 2) {
4175
4242
  scrubTree(event.data.node, policy, false);
@@ -4182,7 +4249,7 @@ function scrubEvent(event, getNode, policy) {
4182
4249
  const node = typeof data.id === "number" ? getNode(data.id) : null;
4183
4250
  const type = ((_b = (_a = asElement(node)) == null ? void 0 : _a.type) != null ? _b : "").toLowerCase();
4184
4251
  if (type === "checkbox" || type === "radio") return;
4185
- if (shouldMaskValue(data.text, node, policy)) data.text = maskValue(data.text);
4252
+ data.text = shouldMaskValue(data.text, node, policy) ? maskValue(data.text) : maskCardRuns(data.text);
4186
4253
  return;
4187
4254
  }
4188
4255
  if (data.source !== MUTATION) return;
@@ -4190,11 +4257,15 @@ function scrubEvent(event, getNode, policy) {
4190
4257
  const value = (_d = entry == null ? void 0 : entry.attributes) == null ? void 0 : _d.value;
4191
4258
  if (typeof value !== "string" || value === "") continue;
4192
4259
  const node = typeof entry.id === "number" ? getNode(entry.id) : null;
4193
- if (shouldMaskValue(value, node, policy)) entry.attributes.value = maskValue(value);
4260
+ entry.attributes.value = shouldMaskValue(value, node, policy) ? maskValue(value) : maskCardRuns(value);
4194
4261
  }
4195
4262
  for (const add of (_e = data.adds) != null ? _e : []) {
4196
4263
  const parent = typeof (add == null ? void 0 : add.parentId) === "number" ? getNode(add.parentId) : null;
4197
- scrubTree(add == null ? void 0 : add.node, policy, isMarkedByCustomer(parent, policy.maskClass));
4264
+ scrubTree(add == null ? void 0 : add.node, policy, isMarkedByCustomer(parent, policy.maskClass), insideEditable(parent));
4265
+ }
4266
+ for (const change of (_f = data.texts) != null ? _f : []) {
4267
+ if (typeof (change == null ? void 0 : change.value) !== "string" || !/[0-9]/.test(change.value)) continue;
4268
+ if (typeof change.id === "number" && insideEditable(getNode(change.id))) change.value = maskTyped(change.value);
4198
4269
  }
4199
4270
  }
4200
4271
 
@@ -5000,7 +5071,7 @@ var Transport = class {
5000
5071
 
5001
5072
  // src/version.ts
5002
5073
  var SDK_NAME = "@anyreplay/browser";
5003
- var SDK_VERSION = true ? "0.2.0" : "0.0.0-dev";
5074
+ var SDK_VERSION = true ? "0.2.2" : "0.0.0-dev";
5004
5075
  var PLATFORM = "web";
5005
5076
 
5006
5077
  // src/visitor.ts
@@ -5107,6 +5178,8 @@ function forgetVisitor(local, session) {
5107
5178
  }
5108
5179
 
5109
5180
  // src/recorder.ts
5181
+ var RRWEB_DOM_CONTENT_LOADED = 0;
5182
+ var RRWEB_LOAD = 1;
5110
5183
  var RAGE_CLICK_COUNT = 3;
5111
5184
  var RAGE_CLICK_WINDOW_MS = 1e3;
5112
5185
  var RAGE_CLICK_RADIUS_PX = 30;
@@ -5246,7 +5319,14 @@ function createRecorder(rawOptions, shell) {
5246
5319
  initialSeq: who.nextSeq
5247
5320
  },
5248
5321
  {
5249
- onSeqReserved: (next) => reserveSeq(session, next),
5322
+ onSeqReserved: (next) => {
5323
+ var _a;
5324
+ reserveSeq(session, next);
5325
+ try {
5326
+ (_a = shell == null ? void 0 : shell.onStateChanged) == null ? void 0 : _a.call(shell);
5327
+ } catch (e) {
5328
+ }
5329
+ },
5250
5330
  onStopped: (reason) => {
5251
5331
  status = "stopped";
5252
5332
  stopRecording == null ? void 0 : stopRecording();
@@ -5304,6 +5384,7 @@ function createRecorder(rawOptions, shell) {
5304
5384
  stopRecording = record({
5305
5385
  emit: (event) => {
5306
5386
  const typed = event;
5387
+ if (typed.type === RRWEB_DOM_CONTENT_LOADED || typed.type === RRWEB_LOAD) return;
5307
5388
  scrubEvent(typed, (id) => record.mirror.getNode(id), policy);
5308
5389
  assets == null ? void 0 : assets.scan(typed);
5309
5390
  noteInteraction(typed, channel);