Skip to content

Commit 9f2fc8c

Browse files
committed
connect inputs 2
1 parent b948dbd commit 9f2fc8c

1 file changed

Lines changed: 108 additions & 7 deletions

File tree

packages/connect-react/src/hooks/useLoginInputEventLow.ts

Lines changed: 108 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,21 @@ type InputBatch = {
1616
lastTimestamp: number;
1717
};
1818

19+
type AutofillStreak = {
20+
firstTimestamp: number;
21+
lastTimestamp: number;
22+
};
23+
1924
const BIG_INPUT_DELTA_THRESHOLD = 3;
25+
const AUTOFILL_STREAK_LENGTH = 5;
26+
const AUTOFILL_FAST_INTERVAL_MS = 80;
27+
const AUTOFILL_UNIFORM_SPREAD_MS = 25;
2028

2129
const useLoginInputEventLow = ({ inputRef, connectService, enabled }: Props) => {
2230
const inputBatchRef = useRef<InputBatch | null>(null);
2331
const previousLengthRef = useRef(0);
32+
const autofillTimestampsRef = useRef<number[]>([]);
33+
const autofillStreakRef = useRef<AutofillStreak | null>(null);
2434

2535
useEffect(() => {
2636
if (!enabled) {
@@ -33,6 +43,8 @@ const useLoginInputEventLow = ({ inputRef, connectService, enabled }: Props) =>
3343
}
3444

3545
previousLengthRef.current = input.value.length;
46+
autofillTimestampsRef.current = [];
47+
autofillStreakRef.current = null;
3648

3749
const isInputFocused = () => document.activeElement === input;
3850

@@ -66,7 +78,27 @@ const useLoginInputEventLow = ({ inputRef, connectService, enabled }: Props) =>
6678
focusProbeTimer = window.setTimeout(runEnvProbe, 250);
6779
};
6880

81+
const extendInputBatch = (timestamp: number) => {
82+
const batch = inputBatchRef.current;
83+
if (!batch) {
84+
inputBatchRef.current = { firstTimestamp: timestamp, lastTimestamp: timestamp };
85+
return;
86+
}
87+
88+
batch.lastTimestamp = timestamp;
89+
};
90+
91+
const drainAutofillRingToBatch = () => {
92+
const ring = autofillTimestampsRef.current;
93+
for (const ts of ring) {
94+
extendInputBatch(ts);
95+
}
96+
ring.length = 0;
97+
};
98+
6999
const flushInputBatch = () => {
100+
drainAutofillRingToBatch();
101+
70102
const batch = inputBatchRef.current;
71103
if (!batch) {
72104
return;
@@ -80,8 +112,23 @@ const useLoginInputEventLow = ({ inputRef, connectService, enabled }: Props) =>
80112
inputBatchRef.current = null;
81113
};
82114

115+
const flushAutofillStreak = () => {
116+
const streak = autofillStreakRef.current;
117+
if (!streak) {
118+
return;
119+
}
120+
121+
connectService.enqueueLowEvent({
122+
eventType: 'big-input-add',
123+
timestamp: streak.firstTimestamp,
124+
durationMs: streak.lastTimestamp - streak.firstTimestamp,
125+
});
126+
autofillStreakRef.current = null;
127+
};
128+
83129
const enqueueNonInputEvent = (eventType: string) => {
84130
flushInputBatch();
131+
flushAutofillStreak();
85132
connectService.enqueueLowEvent({
86133
eventType,
87134
timestamp: Date.now(),
@@ -111,6 +158,30 @@ const useLoginInputEventLow = ({ inputRef, connectService, enabled }: Props) =>
111158
enqueueNonInputEvent('click');
112159
};
113160

161+
const detectAutofillStreak = (): boolean => {
162+
const ring = autofillTimestampsRef.current;
163+
if (ring.length < AUTOFILL_STREAK_LENGTH) {
164+
return false;
165+
}
166+
167+
let minInterval = Infinity;
168+
let maxInterval = -Infinity;
169+
for (let i = 1; i < ring.length; i++) {
170+
const interval = ring[i] - ring[i - 1];
171+
if (interval > AUTOFILL_FAST_INTERVAL_MS) {
172+
return false;
173+
}
174+
if (interval < minInterval) {
175+
minInterval = interval;
176+
}
177+
if (interval > maxInterval) {
178+
maxInterval = interval;
179+
}
180+
}
181+
182+
return maxInterval - minInterval <= AUTOFILL_UNIFORM_SPREAD_MS;
183+
};
184+
114185
const handleInput = () => {
115186
const timestamp = Date.now();
116187

@@ -119,26 +190,55 @@ const useLoginInputEventLow = ({ inputRef, connectService, enabled }: Props) =>
119190
previousLengthRef.current = newLength;
120191

121192
if (delta >= BIG_INPUT_DELTA_THRESHOLD) {
193+
drainAutofillRingToBatch();
194+
flushAutofillStreak();
122195
connectService.enqueueLowEvent({ eventType: 'big-input-add', timestamp });
123196
return;
124197
}
125198

126199
if (delta <= -BIG_INPUT_DELTA_THRESHOLD) {
200+
drainAutofillRingToBatch();
201+
flushAutofillStreak();
127202
connectService.enqueueLowEvent({ eventType: 'big-input-rem', timestamp });
128203
return;
129204
}
130205

131-
const currentBatch = inputBatchRef.current;
206+
const activeStreak = autofillStreakRef.current;
207+
if (activeStreak) {
208+
if (delta === 1 && timestamp - activeStreak.lastTimestamp <= AUTOFILL_FAST_INTERVAL_MS) {
209+
activeStreak.lastTimestamp = timestamp;
210+
return;
211+
}
212+
213+
flushAutofillStreak();
214+
}
215+
216+
if (delta !== 1) {
217+
drainAutofillRingToBatch();
218+
extendInputBatch(timestamp);
219+
return;
220+
}
221+
222+
const ring = autofillTimestampsRef.current;
223+
ring.push(timestamp);
224+
if (ring.length > AUTOFILL_STREAK_LENGTH) {
225+
const graduated = ring.shift() as number;
226+
extendInputBatch(graduated);
227+
}
132228

133-
if (!currentBatch) {
134-
inputBatchRef.current = {
135-
firstTimestamp: timestamp,
136-
lastTimestamp: timestamp,
137-
};
229+
if (!detectAutofillStreak()) {
138230
return;
139231
}
140232

141-
currentBatch.lastTimestamp = timestamp;
233+
const streakStart = ring[0];
234+
const streakLast = ring[ring.length - 1];
235+
ring.length = 0;
236+
237+
flushInputBatch();
238+
autofillStreakRef.current = {
239+
firstTimestamp: streakStart,
240+
lastTimestamp: streakLast,
241+
};
142242
};
143243

144244
const handleKeyUp = (event: KeyboardEvent) => {
@@ -195,6 +295,7 @@ const useLoginInputEventLow = ({ inputRef, connectService, enabled }: Props) =>
195295

196296
const flushForTeardown = () => {
197297
flushInputBatch();
298+
flushAutofillStreak();
198299
resizeBatcher.flush();
199300
scrollBatcher.flush();
200301
connectService.flushLowEventsKeepalive();

0 commit comments

Comments
 (0)