@anyreplay/browser 0.2.1 → 0.2.3

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
@@ -4149,6 +4149,39 @@ function shouldMaskValue(text, node, policy) {
4149
4149
  return cardShaped(text);
4150
4150
  }
4151
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
+ });
4184
+ }
4152
4185
  var EDITABLE = '[contenteditable]:not([contenteditable="false"])';
4153
4186
  function insideEditable(node) {
4154
4187
  var _a;
@@ -4159,6 +4192,7 @@ function insideEditable(node) {
4159
4192
  var isEditableSerialised = (attributes) => attributes.contenteditable !== void 0 && attributes.contenteditable !== "false";
4160
4193
  var typedCardShaped = (text) => cardShaped(text.replace(/\u00a0/g, " ").trim());
4161
4194
  var maskText = (text) => text.replace(/\S/g, "*");
4195
+ var maskTyped = (text) => typedCardShaped(text) ? maskText(text) : maskCardRuns(text);
4162
4196
  var MUTATION = 0;
4163
4197
  var INPUT = 5;
4164
4198
  var ELEMENT_NODE = 2;
@@ -4173,14 +4207,20 @@ function maskTextChildren(node) {
4173
4207
  maskTextChildren(child);
4174
4208
  }
4175
4209
  }
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
+ }
4176
4216
  function scrubTree(node, policy, masked, editable = false) {
4177
4217
  var _a, _b;
4178
4218
  if (!node) return;
4179
4219
  const attributes = (_a = node.attributes) != null ? _a : {};
4180
4220
  const inheritedMask = masked || hasClass(attributes, policy.maskClass);
4181
4221
  const inheritedEditable = editable || node.type === ELEMENT_NODE && isEditableSerialised(attributes);
4182
- if (editable && node.type === TEXT_NODE && typeof node.textContent === "string" && typedCardShaped(node.textContent)) {
4183
- node.textContent = maskText(node.textContent);
4222
+ if (editable && node.type === TEXT_NODE && typeof node.textContent === "string") {
4223
+ node.textContent = maskTyped(node.textContent);
4184
4224
  }
4185
4225
  if (node.type === ELEMENT_NODE && node.tagName && FIELD_TAGS.has(node.tagName.toLowerCase())) {
4186
4226
  const value = attributes.value;
@@ -4188,6 +4228,9 @@ function scrubTree(node, policy, masked, editable = false) {
4188
4228
  if (shouldMask) {
4189
4229
  if (typeof value === "string" && value !== "") attributes.value = maskValue(value);
4190
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);
4191
4234
  }
4192
4235
  }
4193
4236
  for (const child of (_b = node.childNodes) != null ? _b : []) scrubTree(child, policy, inheritedMask, inheritedEditable);
@@ -4206,7 +4249,7 @@ function scrubEvent(event, getNode, policy) {
4206
4249
  const node = typeof data.id === "number" ? getNode(data.id) : null;
4207
4250
  const type = ((_b = (_a = asElement(node)) == null ? void 0 : _a.type) != null ? _b : "").toLowerCase();
4208
4251
  if (type === "checkbox" || type === "radio") return;
4209
- 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);
4210
4253
  return;
4211
4254
  }
4212
4255
  if (data.source !== MUTATION) return;
@@ -4214,15 +4257,15 @@ function scrubEvent(event, getNode, policy) {
4214
4257
  const value = (_d = entry == null ? void 0 : entry.attributes) == null ? void 0 : _d.value;
4215
4258
  if (typeof value !== "string" || value === "") continue;
4216
4259
  const node = typeof entry.id === "number" ? getNode(entry.id) : null;
4217
- if (shouldMaskValue(value, node, policy)) entry.attributes.value = maskValue(value);
4260
+ entry.attributes.value = shouldMaskValue(value, node, policy) ? maskValue(value) : maskCardRuns(value);
4218
4261
  }
4219
4262
  for (const add of (_e = data.adds) != null ? _e : []) {
4220
4263
  const parent = typeof (add == null ? void 0 : add.parentId) === "number" ? getNode(add.parentId) : null;
4221
4264
  scrubTree(add == null ? void 0 : add.node, policy, isMarkedByCustomer(parent, policy.maskClass), insideEditable(parent));
4222
4265
  }
4223
4266
  for (const change of (_f = data.texts) != null ? _f : []) {
4224
- if (typeof (change == null ? void 0 : change.value) !== "string" || !typedCardShaped(change.value)) continue;
4225
- if (typeof change.id === "number" && insideEditable(getNode(change.id))) change.value = maskText(change.value);
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);
4226
4269
  }
4227
4270
  }
4228
4271
 
@@ -5028,7 +5071,7 @@ var Transport = class {
5028
5071
 
5029
5072
  // src/version.ts
5030
5073
  var SDK_NAME = "@anyreplay/browser";
5031
- var SDK_VERSION = true ? "0.2.1" : "0.0.0-dev";
5074
+ var SDK_VERSION = true ? "0.2.3" : "0.0.0-dev";
5032
5075
  var PLATFORM = "web";
5033
5076
 
5034
5077
  // src/visitor.ts
@@ -5154,7 +5197,10 @@ function createRecorder(rawOptions, shell) {
5154
5197
  let assets;
5155
5198
  let pageCount = 0;
5156
5199
  let pendingTraits;
5157
- const sendIdentity = (who, traits) => {
5200
+ let traitsAwaitingSession;
5201
+ let traitsToRetry;
5202
+ let sessionDelivered = false;
5203
+ const postIdentity = (who, traits, retryIfUnapplied) => {
5158
5204
  void fetch(`${options.ingestUrl}/v1/ingest/identify`, {
5159
5205
  method: "POST",
5160
5206
  headers: { "Content-Type": "application/json" },
@@ -5165,9 +5211,33 @@ function createRecorder(rawOptions, shell) {
5165
5211
  ...traits,
5166
5212
  ...options.appId ? { appId: options.appId } : {}
5167
5213
  })
5214
+ }).then(async (response) => {
5215
+ if (!retryIfUnapplied || response.status !== 202) return;
5216
+ const reply = await response.json();
5217
+ if (reply.applied === false && identity === who && !traitsAwaitingSession && !traitsToRetry) {
5218
+ traitsToRetry = traits;
5219
+ }
5168
5220
  }).catch(() => {
5169
5221
  });
5170
5222
  };
5223
+ const sendIdentity = (who, traits) => {
5224
+ traitsToRetry = void 0;
5225
+ if (!sessionDelivered) {
5226
+ traitsAwaitingSession = { ...traitsAwaitingSession, ...traits };
5227
+ return;
5228
+ }
5229
+ postIdentity(who, traits, true);
5230
+ };
5231
+ const noteDelivered = (channel) => {
5232
+ if (channel !== transport || !identity) return;
5233
+ sessionDelivered = true;
5234
+ const waiting = traitsAwaitingSession;
5235
+ const retry = traitsToRetry;
5236
+ traitsAwaitingSession = void 0;
5237
+ traitsToRetry = void 0;
5238
+ if (waiting) postIdentity(identity, waiting, true);
5239
+ else if (retry) postIdentity(identity, retry, false);
5240
+ };
5171
5241
  const collectMeta = (who) => {
5172
5242
  var _a, _b;
5173
5243
  return {
@@ -5276,7 +5346,15 @@ function createRecorder(rawOptions, shell) {
5276
5346
  initialSeq: who.nextSeq
5277
5347
  },
5278
5348
  {
5279
- onSeqReserved: (next) => reserveSeq(session, next),
5349
+ onSent: () => noteDelivered(channel),
5350
+ onSeqReserved: (next) => {
5351
+ var _a;
5352
+ reserveSeq(session, next);
5353
+ try {
5354
+ (_a = shell == null ? void 0 : shell.onStateChanged) == null ? void 0 : _a.call(shell);
5355
+ } catch (e) {
5356
+ }
5357
+ },
5280
5358
  onStopped: (reason) => {
5281
5359
  status = "stopped";
5282
5360
  stopRecording == null ? void 0 : stopRecording();
@@ -5412,6 +5490,8 @@ function createRecorder(rawOptions, shell) {
5412
5490
  identity = void 0;
5413
5491
  }
5414
5492
  pendingTraits = void 0;
5493
+ traitsAwaitingSession = void 0;
5494
+ traitsToRetry = void 0;
5415
5495
  pending.length = 0;
5416
5496
  forgetVisitor(local, session);
5417
5497
  },