@corbado/observe 1.7.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/analysis/client.mjs +1851 -0
  2. package/dist/analysis/config-manager.mjs +83 -0
  3. package/dist/analysis/config.mjs +216 -0
  4. package/dist/analysis/device-info-manager.mjs +153 -0
  5. package/dist/analysis/device.mjs +562 -0
  6. package/dist/analysis/index.mjs +118 -0
  7. package/dist/analysis/logger.mjs +50 -0
  8. package/dist/analysis/low-events/capture-sequence.mjs +11 -0
  9. package/dist/analysis/low-events/ceremony-initiator.mjs +26 -0
  10. package/dist/analysis/low-events/form-input-binding.mjs +46 -0
  11. package/dist/analysis/low-events/grouped-input-tracker.mjs +133 -0
  12. package/dist/analysis/low-events/input-change-recorder.mjs +200 -0
  13. package/dist/analysis/low-events/input-paste-observer.mjs +91 -0
  14. package/dist/analysis/low-events/input-tracker.mjs +223 -0
  15. package/dist/analysis/low-events/otp-input-binding.mjs +67 -0
  16. package/dist/analysis/low-events/shared-window-observer.mjs +225 -0
  17. package/dist/analysis/low-events/sink.mjs +0 -0
  18. package/dist/analysis/low-events/viewport-batcher.mjs +42 -0
  19. package/dist/analysis/low-events/window-tracker.mjs +40 -0
  20. package/dist/analysis/queue.mjs +887 -0
  21. package/dist/analysis/readers/device.mjs +73 -0
  22. package/dist/analysis/readers/meta.mjs +18 -0
  23. package/dist/analysis/readers/password-manager-dom.mjs +112 -0
  24. package/dist/analysis/readers/password-manager-extensions.mjs +247 -0
  25. package/dist/analysis/readers/private-mode.mjs +9 -0
  26. package/dist/analysis/storage/index.mjs +197 -0
  27. package/dist/analysis/storage/migration.mjs +56 -0
  28. package/dist/analysis/store/event-store.mjs +114 -0
  29. package/dist/analysis/transport.mjs +0 -0
  30. package/dist/analysis/transports/fetch.mjs +108 -0
  31. package/dist/analysis/types.mjs +25 -0
  32. package/dist/analysis/utils/appconfirmation-operation-full.mjs +19 -0
  33. package/dist/analysis/utils/blocked-user-agents.mjs +37 -0
  34. package/dist/analysis/utils/captcha-operation-full.mjs +17 -0
  35. package/dist/analysis/utils/email-link-continuity.mjs +50 -0
  36. package/dist/analysis/utils/email-link-operation-full.mjs +142 -0
  37. package/dist/analysis/utils/email-otp-operation-full.mjs +39 -0
  38. package/dist/analysis/utils/form-operation-full.mjs +81 -0
  39. package/dist/analysis/utils/key-operation-full.mjs +38 -0
  40. package/dist/analysis/utils/normalize-error.mjs +29 -0
  41. package/dist/analysis/utils/operation-full-provide-identifier-with-cui.mjs +46 -0
  42. package/dist/analysis/utils/operation-full.mjs +235 -0
  43. package/dist/analysis/utils/passkey-deletion-operation-full.mjs +17 -0
  44. package/dist/analysis/utils/passkey-enrollment-operation-full.mjs +32 -0
  45. package/dist/analysis/utils/passkey-login-operation-full.mjs +34 -0
  46. package/dist/analysis/utils/password-enrollment-operation-full.mjs +27 -0
  47. package/dist/analysis/utils/password-login-operation-full.mjs +61 -0
  48. package/dist/analysis/utils/password-manager-sampler.mjs +130 -0
  49. package/dist/analysis/utils/provide-data-operation-full.mjs +28 -0
  50. package/dist/analysis/utils/serialize-raw-error.mjs +205 -0
  51. package/dist/analysis/utils/sms-otp-operation-full.mjs +39 -0
  52. package/dist/analysis/utils/social-operation-login.mjs +17 -0
  53. package/dist/analysis/utils/source-path.mjs +39 -0
  54. package/dist/analysis/utils/sso-operation-login.mjs +37 -0
  55. package/dist/analysis/utils/totp-operation-full.mjs +35 -0
  56. package/dist/analysis/utils/trusted-device-operation-full.mjs +100 -0
  57. package/dist/analysis/utils/web-lock.mjs +52 -0
  58. package/dist/analysis/utils/webauthn-payloads.mjs +101 -0
  59. package/dist/analysis/version.mjs +7 -0
  60. package/dist/cdn/script.global.js +4 -4
  61. package/dist/index.d.mts +164 -11
  62. package/dist/index.d.ts +164 -11
  63. package/dist/index.js +4 -4
  64. package/dist/index.mjs +4 -4
  65. package/package.json +7 -2
@@ -0,0 +1,50 @@
1
+ // src/logger.ts
2
+ function createLogger(options) {
3
+ const debugEnabled = options.debug;
4
+ let telemetrySink;
5
+ const withPrefix = (level, message) => `[Corbado Observe SDK ${level}] ${message}`;
6
+ return {
7
+ reportTelemetry(level, message) {
8
+ if (!telemetrySink) {
9
+ return;
10
+ }
11
+ try {
12
+ telemetrySink(level, message);
13
+ } catch {
14
+ }
15
+ },
16
+ setTelemetrySink(sink) {
17
+ telemetrySink = sink;
18
+ },
19
+ debug(message, ...args) {
20
+ if (!debugEnabled) {
21
+ return;
22
+ }
23
+ console.log(withPrefix("DEBUG", message), ...args);
24
+ },
25
+ info(message, ...args) {
26
+ if (!debugEnabled) {
27
+ return;
28
+ }
29
+ console.log(withPrefix("INFO", message), ...args);
30
+ },
31
+ warn(message, ...args) {
32
+ if (!debugEnabled) {
33
+ return;
34
+ }
35
+ console.warn(withPrefix("WARN", message), ...args);
36
+ },
37
+ error(message, ...args) {
38
+ if (!debugEnabled) {
39
+ return;
40
+ }
41
+ console.error(withPrefix("ERROR", message), ...args);
42
+ },
43
+ critical(message, ...args) {
44
+ console.error(withPrefix("CRITICAL", message), ...args);
45
+ }
46
+ };
47
+ }
48
+ export {
49
+ createLogger
50
+ };
@@ -0,0 +1,11 @@
1
+ // src/low-events/capture-sequence.ts
2
+ var captureSequence = 0;
3
+ function reserveCaptureSeq() {
4
+ if (captureSequence === 4294967295) {
5
+ throw new Error("Low-event capture sequence exhausted");
6
+ }
7
+ return ++captureSequence;
8
+ }
9
+ export {
10
+ reserveCaptureSeq
11
+ };
@@ -0,0 +1,26 @@
1
+ // src/low-events/ceremony-initiator.ts
2
+ function inferCeremonyInitiator(operation, spec) {
3
+ if (operation === "passkey-login") {
4
+ switch (spec) {
5
+ case "passkey-known-identifier":
6
+ case "passkey-no-identifier":
7
+ return "passkey-button";
8
+ case "passkey-known-identifier-auto":
9
+ case "passkey-no-identifier-auto":
10
+ case "passkey-immediate":
11
+ return "automatic";
12
+ }
13
+ } else if (operation === "passkey-enrollment") {
14
+ switch (spec) {
15
+ case "manual":
16
+ return "passkey-button";
17
+ case "auto":
18
+ case "conditional":
19
+ return "automatic";
20
+ }
21
+ }
22
+ return void 0;
23
+ }
24
+ export {
25
+ inferCeremonyInitiator
26
+ };
@@ -0,0 +1,46 @@
1
+ // src/low-events/form-input-binding.ts
2
+ import { LowEventInputTracker } from "./input-tracker.mjs";
3
+ var FormInputBinding = class {
4
+ constructor(logger, sink) {
5
+ this.logger = logger;
6
+ this.sink = sink;
7
+ }
8
+ trackers = /* @__PURE__ */ new Map();
9
+ destroyed = false;
10
+ /** Bind exactly these fields; an empty list detaches every field. */
11
+ setFields(fields) {
12
+ if (this.destroyed) return;
13
+ const next = new Set(fields);
14
+ for (const [field, tracker] of this.trackers) {
15
+ if (next.has(field)) continue;
16
+ this.safe(() => tracker.destroy());
17
+ this.trackers.delete(field);
18
+ }
19
+ for (const field of next) {
20
+ if (this.trackers.has(field)) continue;
21
+ this.safe(() => this.trackers.set(field, new LowEventInputTracker(this.logger, field, this.sink())));
22
+ }
23
+ }
24
+ capturePrefill(newCapture = false) {
25
+ for (const tracker of this.trackers.values()) this.safe(() => tracker.capturePrefill(newCapture));
26
+ }
27
+ flushPending() {
28
+ for (const tracker of this.trackers.values()) this.safe(() => tracker.flushPending());
29
+ }
30
+ destroy() {
31
+ if (this.destroyed) return;
32
+ this.destroyed = true;
33
+ for (const tracker of this.trackers.values()) this.safe(() => tracker.destroy());
34
+ this.trackers.clear();
35
+ }
36
+ safe(run) {
37
+ try {
38
+ run();
39
+ } catch (error) {
40
+ this.logger.error("Form input binding failed", error);
41
+ }
42
+ }
43
+ };
44
+ export {
45
+ FormInputBinding
46
+ };
@@ -0,0 +1,133 @@
1
+ // src/low-events/grouped-input-tracker.ts
2
+ import { InputChangeRecorder } from "./input-change-recorder.mjs";
3
+ import { InputPasteObserver } from "./input-paste-observer.mjs";
4
+ var LowEventGroupedInputTracker = class {
5
+ constructor(logger, fields, sink) {
6
+ this.logger = logger;
7
+ this.sink = sink;
8
+ this.inputs = [...new Set(fields)];
9
+ this.changes = new InputChangeRecorder(
10
+ this.length(),
11
+ (low) => sink.enqueueLowEvent(low),
12
+ logger,
13
+ sink.reserveCaptureSeq
14
+ );
15
+ this.paste = new InputPasteObserver(
16
+ this.inputs,
17
+ logger,
18
+ (ts, _inserted, captureSeq) => {
19
+ this.cancelChange();
20
+ this.changes.flush();
21
+ this.changes.markInputObserved();
22
+ const oldLength = this.changes.previousLength;
23
+ this.changes.previousLength = this.length();
24
+ sink.enqueueLowEvent({
25
+ ...captureSeq === void 0 ? {} : { captureSeq },
26
+ lowType: "input-paste",
27
+ ts,
28
+ inputEffect: this.length() === 0 ? "clear" : oldLength === 0 ? "fill" : "modify"
29
+ });
30
+ },
31
+ sink.reserveCaptureSeq
32
+ );
33
+ this.unregisterInputs = sink.registerInputs?.(this.inputs, () => this.flushPending());
34
+ this.capturePrefill();
35
+ for (const input of this.inputs) {
36
+ this.listen(input, "input", (event) => {
37
+ if (this.paste.handlesInput(event)) return;
38
+ this.composing ||= event.isComposing || event.inputType === "insertCompositionText";
39
+ if (this.changeTimer !== null) return;
40
+ this.changeTime = Date.now();
41
+ this.changeSeq = sink.reserveCaptureSeq?.();
42
+ this.changeTimer = setTimeout(() => this.safe(() => this.flushChanges()), 0);
43
+ });
44
+ this.listen(input, "focus", (event) => {
45
+ if (this.contains(event.relatedTarget)) return;
46
+ this.emit("focus");
47
+ });
48
+ this.listen(input, "blur", (event) => {
49
+ if (this.contains(event.relatedTarget)) return;
50
+ this.flushPending();
51
+ this.emit("blur");
52
+ });
53
+ for (const type of ["click", "pointerdown", "pointerup"]) this.listen(input, type, () => this.emit(type));
54
+ this.listen(input, "keyup", (event) => {
55
+ if (event.key === "Escape") this.emit("keyup-escape");
56
+ });
57
+ }
58
+ if (typeof window !== "undefined")
59
+ this.listen(window, "pagehide", () => {
60
+ this.flushPending();
61
+ sink.flushKeepalive();
62
+ });
63
+ if (typeof document !== "undefined" && this.contains(document.activeElement)) {
64
+ this.emit("focus");
65
+ }
66
+ }
67
+ inputs;
68
+ changes;
69
+ paste;
70
+ unregisterInputs;
71
+ listeners = [];
72
+ changeTimer = null;
73
+ changeTime = 0;
74
+ changeSeq;
75
+ composing = false;
76
+ destroyed = false;
77
+ contains(target) {
78
+ return this.inputs.some((field) => field === target);
79
+ }
80
+ length() {
81
+ return this.inputs.reduce((total, field) => total + field.value.length, 0);
82
+ }
83
+ safe(action) {
84
+ try {
85
+ action();
86
+ } catch (error) {
87
+ this.logger.error("Failed to observe grouped input", error);
88
+ }
89
+ }
90
+ listen(target, type, handler) {
91
+ const wrapped = (event) => this.safe(() => handler(event));
92
+ target.addEventListener(type, wrapped);
93
+ this.listeners.push(() => target.removeEventListener(type, wrapped));
94
+ }
95
+ cancelChange() {
96
+ if (this.changeTimer !== null) clearTimeout(this.changeTimer);
97
+ this.changeTimer = null;
98
+ }
99
+ flushChanges() {
100
+ if (this.changeTimer === null) return;
101
+ this.cancelChange();
102
+ if (!this.paste.pending) this.changes.record(this.length(), this.changeTime, this.composing, this.changeSeq);
103
+ this.composing = false;
104
+ }
105
+ emit(lowType) {
106
+ this.flushChanges();
107
+ this.changes.flush();
108
+ this.sink.enqueueLowEvent({ lowType, ts: Date.now() });
109
+ }
110
+ capturePrefill(newCapture = false) {
111
+ if (this.destroyed) return;
112
+ this.flushPending();
113
+ this.changes.capturePrefill(this.length(), newCapture);
114
+ }
115
+ flushPending() {
116
+ this.paste.flush();
117
+ this.flushChanges();
118
+ this.changes.flush();
119
+ }
120
+ destroy() {
121
+ if (this.destroyed) return;
122
+ this.destroyed = true;
123
+ this.safe(() => {
124
+ this.flushPending();
125
+ this.paste.destroy();
126
+ this.unregisterInputs?.();
127
+ for (const remove of this.listeners) remove();
128
+ });
129
+ }
130
+ };
131
+ export {
132
+ LowEventGroupedInputTracker
133
+ };
@@ -0,0 +1,200 @@
1
+ // src/low-events/input-change-recorder.ts
2
+ var INPUT_IDLE_MS = 3e3;
3
+ var BIG_INPUT_DELTA_THRESHOLD = 3;
4
+ var AUTOFILL_STREAK_LENGTH = 5;
5
+ var AUTOFILL_FAST_INTERVAL_MS = 80;
6
+ var AUTOFILL_UNIFORM_SPREAD_MS = 25;
7
+ var InputChangeRecorder = class {
8
+ constructor(previousLength, pushLow, logger, reserveCaptureSeq) {
9
+ this.previousLength = previousLength;
10
+ this.pushLow = pushLow;
11
+ this.logger = logger;
12
+ this.reserveCaptureSeq = reserveCaptureSeq;
13
+ }
14
+ prefillSettled = false;
15
+ inputBatch = null;
16
+ currentEffect = "modify";
17
+ ringEffect = "modify";
18
+ autofillTimestamps = [];
19
+ autofillStreak = null;
20
+ idleTimer = null;
21
+ markInputObserved() {
22
+ this.prefillSettled = true;
23
+ }
24
+ capturePrefill(length, newCapture = false) {
25
+ if (newCapture) this.prefillSettled = false;
26
+ if (this.prefillSettled) return;
27
+ this.previousLength = length;
28
+ if (length === 0) return;
29
+ this.prefillSettled = true;
30
+ this.pushLow({ lowType: "input-prefill", ts: Date.now(), initiator: "unknown" });
31
+ }
32
+ flush() {
33
+ this.cancelIdle();
34
+ this.flushInputBatch();
35
+ this.flushAutofillStreak();
36
+ }
37
+ cancelIdle() {
38
+ if (this.idleTimer !== null) clearTimeout(this.idleTimer);
39
+ this.idleTimer = null;
40
+ }
41
+ scheduleIdle() {
42
+ this.cancelIdle();
43
+ this.idleTimer = setTimeout(() => {
44
+ this.idleTimer = null;
45
+ try {
46
+ this.flushInputBatch(true);
47
+ this.flushAutofillStreak();
48
+ } catch (error) {
49
+ this.logger.error("Failed to flush idle input", error);
50
+ }
51
+ }, INPUT_IDLE_MS);
52
+ }
53
+ extendInputBatch(timestamp, effect = this.currentEffect, captureSeq) {
54
+ const batch = this.inputBatch;
55
+ if (!batch) {
56
+ this.inputBatch = { firstTimestamp: timestamp, lastTimestamp: timestamp, effect, captureSeq };
57
+ return;
58
+ }
59
+ batch.lastTimestamp = timestamp;
60
+ }
61
+ drainAutofillRingToBatch() {
62
+ for (const ts of this.autofillTimestamps) {
63
+ this.extendInputBatch(ts.ts, this.ringEffect, ts.captureSeq);
64
+ }
65
+ this.autofillTimestamps.length = 0;
66
+ }
67
+ flushInputBatch(idle = false) {
68
+ this.drainAutofillRingToBatch();
69
+ const batch = this.inputBatch;
70
+ if (!batch) {
71
+ return;
72
+ }
73
+ this.pushLow({
74
+ ...batch.captureSeq === void 0 ? {} : { captureSeq: batch.captureSeq },
75
+ lowType: idle ? "input-idle" : "input",
76
+ ts: batch.firstTimestamp,
77
+ durationMs: batch.lastTimestamp - batch.firstTimestamp,
78
+ inputEffect: batch.effect
79
+ });
80
+ this.inputBatch = null;
81
+ }
82
+ flushAutofillStreak() {
83
+ const streak = this.autofillStreak;
84
+ if (!streak) {
85
+ return;
86
+ }
87
+ this.pushLow({
88
+ ...streak.captureSeq === void 0 ? {} : { captureSeq: streak.captureSeq },
89
+ lowType: "big-input-add",
90
+ ts: streak.firstTimestamp,
91
+ durationMs: streak.lastTimestamp - streak.firstTimestamp,
92
+ inputEffect: streak.effect
93
+ });
94
+ this.autofillStreak = null;
95
+ }
96
+ detectAutofillStreak() {
97
+ const ring = this.autofillTimestamps;
98
+ if (ring.length < AUTOFILL_STREAK_LENGTH) {
99
+ return false;
100
+ }
101
+ let minInterval = Infinity;
102
+ let maxInterval = -Infinity;
103
+ for (let i = 1; i < ring.length; i++) {
104
+ const interval = ring[i].ts - ring[i - 1].ts;
105
+ if (interval > AUTOFILL_FAST_INTERVAL_MS) {
106
+ return false;
107
+ }
108
+ if (interval < minInterval) {
109
+ minInterval = interval;
110
+ }
111
+ if (interval > maxInterval) {
112
+ maxInterval = interval;
113
+ }
114
+ }
115
+ return maxInterval - minInterval <= AUTOFILL_UNIFORM_SPREAD_MS;
116
+ }
117
+ record(newLength, timestamp = Date.now(), composing = false, captureSeq = this.reserveCaptureSeq?.()) {
118
+ this.markInputObserved();
119
+ this.scheduleIdle();
120
+ const oldLength = this.previousLength;
121
+ this.currentEffect = newLength === 0 ? "clear" : oldLength === 0 ? "fill" : "modify";
122
+ const delta = newLength - oldLength;
123
+ this.previousLength = newLength;
124
+ if (newLength === 0 && oldLength > 0) {
125
+ this.flush();
126
+ this.pushLow({
127
+ ...captureSeq === void 0 ? {} : { captureSeq },
128
+ lowType: "input-clear",
129
+ ts: timestamp,
130
+ inputEffect: "clear"
131
+ });
132
+ return;
133
+ }
134
+ if (composing) {
135
+ this.flushAutofillStreak();
136
+ this.drainAutofillRingToBatch();
137
+ this.extendInputBatch(timestamp, this.currentEffect, captureSeq);
138
+ return;
139
+ }
140
+ if (delta >= BIG_INPUT_DELTA_THRESHOLD) {
141
+ this.flushInputBatch();
142
+ this.flushAutofillStreak();
143
+ this.pushLow({
144
+ ...captureSeq === void 0 ? {} : { captureSeq },
145
+ lowType: "big-input-add",
146
+ ts: timestamp,
147
+ inputEffect: this.currentEffect
148
+ });
149
+ return;
150
+ }
151
+ if (delta <= -BIG_INPUT_DELTA_THRESHOLD) {
152
+ this.flushInputBatch();
153
+ this.flushAutofillStreak();
154
+ this.pushLow({
155
+ ...captureSeq === void 0 ? {} : { captureSeq },
156
+ lowType: "big-input-rem",
157
+ ts: timestamp,
158
+ inputEffect: this.currentEffect
159
+ });
160
+ return;
161
+ }
162
+ const activeStreak = this.autofillStreak;
163
+ if (activeStreak) {
164
+ if (delta === 1 && timestamp - activeStreak.lastTimestamp <= AUTOFILL_FAST_INTERVAL_MS) {
165
+ activeStreak.lastTimestamp = timestamp;
166
+ return;
167
+ }
168
+ this.flushAutofillStreak();
169
+ }
170
+ if (delta !== 1) {
171
+ this.drainAutofillRingToBatch();
172
+ this.extendInputBatch(timestamp, this.currentEffect, captureSeq);
173
+ return;
174
+ }
175
+ const ring = this.autofillTimestamps;
176
+ if (ring.length === 0) this.ringEffect = this.currentEffect;
177
+ ring.push({ ts: timestamp, captureSeq });
178
+ if (ring.length > AUTOFILL_STREAK_LENGTH) {
179
+ const graduated = ring.shift();
180
+ this.extendInputBatch(graduated.ts, this.ringEffect, graduated.captureSeq);
181
+ this.ringEffect = "modify";
182
+ }
183
+ if (!this.detectAutofillStreak()) {
184
+ return;
185
+ }
186
+ const streakStart = ring[0];
187
+ const streakLast = ring[ring.length - 1];
188
+ ring.length = 0;
189
+ this.flushInputBatch();
190
+ this.autofillStreak = {
191
+ firstTimestamp: streakStart.ts,
192
+ captureSeq: streakStart.captureSeq,
193
+ lastTimestamp: streakLast.ts,
194
+ effect: this.ringEffect
195
+ };
196
+ }
197
+ };
198
+ export {
199
+ InputChangeRecorder
200
+ };
@@ -0,0 +1,91 @@
1
+ // src/low-events/input-paste-observer.ts
2
+ var InputPasteObserver = class {
3
+ constructor(inputs, logger, changed, reserveCaptureSeq) {
4
+ this.inputs = inputs;
5
+ this.logger = logger;
6
+ this.changed = changed;
7
+ this.reserveCaptureSeq = reserveCaptureSeq;
8
+ for (const input of inputs) {
9
+ input.addEventListener("paste", this.onPaste, true);
10
+ input.addEventListener("beforeinput", this.onBeforeInput, true);
11
+ }
12
+ }
13
+ before = null;
14
+ timestamp = 0;
15
+ captureSeq;
16
+ pasteEvent;
17
+ timer = null;
18
+ onPaste = (event) => this.begin(event);
19
+ onBeforeInput = (event) => {
20
+ if (event.inputType === "insertFromPaste") this.begin(event);
21
+ else this.flush();
22
+ };
23
+ /** An unrelated input event invalidates pending paste provenance before recording its change. */
24
+ handlesInput(event) {
25
+ if (!this.before) return false;
26
+ const inPasteDispatch = this.pasteEvent?.type === "paste" && (this.pasteEvent.eventPhase ?? 0) > 0;
27
+ if (event.inputType === "insertFromPaste" || inPasteDispatch) {
28
+ return true;
29
+ }
30
+ this.clear();
31
+ return false;
32
+ }
33
+ get pending() {
34
+ return this.before !== null;
35
+ }
36
+ begin(event) {
37
+ if (!event.isTrusted || this.before) return;
38
+ try {
39
+ this.before = this.inputs.map((input) => input.value);
40
+ this.pasteEvent = event;
41
+ this.timestamp = Date.now();
42
+ this.captureSeq = this.reserveCaptureSeq?.();
43
+ this.timer = setTimeout(() => this.flush(), 0);
44
+ } catch (error) {
45
+ this.logger.error("Failed to observe paste", error);
46
+ this.clear();
47
+ }
48
+ }
49
+ flush() {
50
+ const before = this.before;
51
+ if (!before) return;
52
+ const timestamp = this.timestamp;
53
+ const captureSeq = this.captureSeq;
54
+ this.clear();
55
+ try {
56
+ let inserted = 0;
57
+ let changed = false;
58
+ this.inputs.forEach((input, index) => {
59
+ const old = before[index];
60
+ const value = input.value;
61
+ if (old === value) return;
62
+ changed = true;
63
+ let start = 0;
64
+ while (start < old.length && start < value.length && old[start] === value[start]) start++;
65
+ let end = 0;
66
+ while (end < old.length - start && end < value.length - start && old[old.length - end - 1] === value[value.length - end - 1])
67
+ end++;
68
+ inserted += value.length - start - end;
69
+ });
70
+ if (changed) this.changed(timestamp, inserted, captureSeq);
71
+ } catch (error) {
72
+ this.logger.error("Failed to finish paste observation", error);
73
+ }
74
+ }
75
+ clear() {
76
+ if (this.timer !== null) clearTimeout(this.timer);
77
+ this.timer = null;
78
+ this.before = null;
79
+ this.pasteEvent = void 0;
80
+ }
81
+ destroy() {
82
+ this.flush();
83
+ for (const input of this.inputs) {
84
+ input.removeEventListener("paste", this.onPaste, true);
85
+ input.removeEventListener("beforeinput", this.onBeforeInput, true);
86
+ }
87
+ }
88
+ };
89
+ export {
90
+ InputPasteObserver
91
+ };