Skip to content

Commit 44d5c33

Browse files
committed
Add low events
1 parent e185904 commit 44d5c33

8 files changed

Lines changed: 808 additions & 12 deletions

File tree

examples/nextjs/tsconfig.json

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
{
22
"compilerOptions": {
33
"target": "es5",
4-
"lib": [
5-
"dom",
6-
"dom.iterable",
7-
"esnext"
8-
],
4+
"lib": ["dom", "dom.iterable", "esnext"],
95
"allowJs": true,
106
"skipLibCheck": true,
117
"strict": true,
@@ -23,9 +19,7 @@
2319
}
2420
],
2521
"paths": {
26-
"@/*": [
27-
"./*"
28-
]
22+
"@/*": ["./*"]
2923
}
3024
},
3125
"include": [
@@ -37,7 +31,5 @@
3731
"scripts/seed.js",
3832
".next/dev/types/**/*.ts"
3933
],
40-
"exclude": [
41-
"node_modules"
42-
]
34+
"exclude": ["node_modules"]
4335
}

packages/connect-react/src/components/login/LoginInitScreen.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
148148
return;
149149
}
150150

151+
if (flags?.hasSupportForEventLow()) {
152+
getConnectService().enqueueLowEvent({ eventType: 'cui-ready', timestamp: Date.now() });
153+
}
154+
151155
let cuiStarted = false;
152156
const res = await getConnectService().conditionalUILogin(
153157
ac => config.onConditionalLoginStart?.(ac),
@@ -187,6 +191,7 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
187191
}
188192

189193
try {
194+
await getConnectService().flushLowEvents();
190195
await config.onComplete(
191196
connectLoginFinishToComplete(res.val),
192197
getConnectService().encodeClientState(),
@@ -204,6 +209,7 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
204209

205210
setIdentifierBasedLoading(true);
206211
setCurrentIdentifier(identifier);
212+
await getConnectService().flushLowEvents();
207213
config.onLoginStart?.();
208214

209215
const resStart = await getConnectService().loginStart(identifier, PasskeyLoginSource.TextField, loadedMs);
@@ -242,6 +248,7 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
242248
}
243249

244250
try {
251+
await getConnectService().flushLowEvents();
245252
await config.onComplete(
246253
connectLoginFinishToComplete(res.val),
247254
getConnectService().encodeClientState(),
@@ -328,6 +335,7 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
328335
// This is needed to enable multiple login instances on the same page however only one should have the autocomplete
329336
// Else the conditionalUI won't work
330337
const autoComplete = useMemo(() => (flags?.hasSupportForConditionalUI() ? 'username webauthn' : ''), [flags]);
338+
const enableEventLow = useMemo(() => flags?.hasSupportForEventLow() ?? false, [flags]);
331339

332340
switch (loginInitState) {
333341
case LoginInitState.SilentLoading:
@@ -340,6 +348,7 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
340348
isLoading={cuiBasedLoading || identifierBasedLoading}
341349
error={error}
342350
autoComplete={autoComplete}
351+
enableEventLow={enableEventLow}
343352
handleSubmit={() => void handleSubmit()}
344353
handleIdentifierUpdate={(v: string) => setIdentifier(v)}
345354
/>

packages/connect-react/src/components/login/base/LoginInitLoaded.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import React from 'react';
1+
import React, { useRef } from 'react';
22

3+
import useLoginInputEventLow from '../../../hooks/useLoginInputEventLow';
4+
import useShared from '../../../hooks/useShared';
35
import InputField from '../../shared/InputField';
46
import { LinkButton } from '../../shared/LinkButton';
57
import { Notification } from '../../shared/Notification';
@@ -9,6 +11,7 @@ interface Props {
911
isLoading: boolean;
1012
error: string | undefined;
1113
autoComplete: string;
14+
enableEventLow?: boolean;
1215
onSignupClick?: () => void;
1316
handleSubmit: () => void;
1417
handleIdentifierUpdate: (v: string) => void;
@@ -19,9 +22,19 @@ const LoginInitLoaded = ({
1922
error,
2023
onSignupClick,
2124
autoComplete,
25+
enableEventLow = false,
2226
handleSubmit,
2327
handleIdentifierUpdate,
2428
}: Props) => {
29+
const inputRef = useRef<HTMLInputElement>(null);
30+
const { getConnectService } = useShared();
31+
32+
useLoginInputEventLow({
33+
inputRef,
34+
connectService: getConnectService(),
35+
enabled: enableEventLow,
36+
});
37+
2538
return (
2639
<>
2740
{error ? (
@@ -37,6 +50,7 @@ const LoginInitLoaded = ({
3750
autoComplete={autoComplete}
3851
autoFocus={true}
3952
placeholder=''
53+
ref={inputRef}
4054
onChange={e => handleIdentifierUpdate(e.target.value)}
4155
/>
4256
<PrimaryButton
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import type { ConnectService } from '@corbado/web-core';
2+
import type { RefObject } from 'react';
3+
import { useEffect, useRef } from 'react';
4+
5+
type Props = {
6+
inputRef: RefObject<HTMLInputElement>;
7+
connectService: ConnectService;
8+
enabled: boolean;
9+
};
10+
11+
type InputBatch = {
12+
firstTimestamp: number;
13+
lastTimestamp: number;
14+
};
15+
16+
const useLoginInputEventLow = ({ inputRef, connectService, enabled }: Props) => {
17+
const eventLowActiveRef = useRef(false);
18+
const inputBatchRef = useRef<InputBatch | null>(null);
19+
20+
useEffect(() => {
21+
if (!enabled) {
22+
return;
23+
}
24+
25+
const input = inputRef.current;
26+
if (!input) {
27+
return;
28+
}
29+
30+
const flushInputBatch = () => {
31+
const batch = inputBatchRef.current;
32+
if (!batch) {
33+
return;
34+
}
35+
36+
connectService.enqueueLowEvent({
37+
eventType: 'input',
38+
timestamp: batch.firstTimestamp,
39+
durationMs: batch.lastTimestamp - batch.firstTimestamp,
40+
});
41+
inputBatchRef.current = null;
42+
};
43+
44+
const enqueueNonInputEvent = (eventType: string) => {
45+
flushInputBatch();
46+
connectService.enqueueLowEvent({
47+
eventType,
48+
timestamp: Date.now(),
49+
});
50+
};
51+
52+
const handleFocus = () => {
53+
eventLowActiveRef.current = true;
54+
enqueueNonInputEvent('focus');
55+
};
56+
57+
const handleBlur = () => {
58+
enqueueNonInputEvent('blur');
59+
};
60+
61+
const handlePointerDown = () => {
62+
enqueueNonInputEvent('pointerdown');
63+
};
64+
65+
const handlePointerUp = () => {
66+
enqueueNonInputEvent('pointerup');
67+
};
68+
69+
const handleClick = () => {
70+
enqueueNonInputEvent('click');
71+
};
72+
73+
const handleInput = () => {
74+
const timestamp = Date.now();
75+
const currentBatch = inputBatchRef.current;
76+
77+
if (!currentBatch) {
78+
inputBatchRef.current = {
79+
firstTimestamp: timestamp,
80+
lastTimestamp: timestamp,
81+
};
82+
return;
83+
}
84+
85+
currentBatch.lastTimestamp = timestamp;
86+
};
87+
88+
const handleKeyUp = (event: KeyboardEvent) => {
89+
if (event.key !== 'Escape') {
90+
return;
91+
}
92+
93+
enqueueNonInputEvent('keyup-escape');
94+
};
95+
96+
const handleWindowFocus = () => {
97+
if (!eventLowActiveRef.current) {
98+
return;
99+
}
100+
101+
enqueueNonInputEvent('window-focus');
102+
};
103+
104+
const handleWindowBlur = () => {
105+
if (!eventLowActiveRef.current) {
106+
return;
107+
}
108+
109+
enqueueNonInputEvent('window-blur');
110+
};
111+
112+
const handleVisibilityChange = () => {
113+
if (!eventLowActiveRef.current) {
114+
return;
115+
}
116+
117+
enqueueNonInputEvent('document-visibilitychange');
118+
};
119+
120+
const handleVisualViewportResize = () => {
121+
if (!eventLowActiveRef.current) {
122+
return;
123+
}
124+
125+
enqueueNonInputEvent('visualviewport-resize');
126+
};
127+
128+
const handleVisualViewportScroll = () => {
129+
if (!eventLowActiveRef.current) {
130+
return;
131+
}
132+
133+
enqueueNonInputEvent('visualviewport-scroll');
134+
};
135+
136+
const flushForTeardown = () => {
137+
flushInputBatch();
138+
connectService.flushLowEventsKeepalive();
139+
};
140+
141+
input.addEventListener('focus', handleFocus);
142+
input.addEventListener('blur', handleBlur);
143+
input.addEventListener('pointerdown', handlePointerDown);
144+
input.addEventListener('pointerup', handlePointerUp);
145+
input.addEventListener('click', handleClick);
146+
input.addEventListener('input', handleInput);
147+
input.addEventListener('keyup', handleKeyUp);
148+
149+
window.addEventListener('focus', handleWindowFocus);
150+
window.addEventListener('blur', handleWindowBlur);
151+
document.addEventListener('visibilitychange', handleVisibilityChange);
152+
// window.addEventListener('pagehide', flushForTeardown);
153+
window.visualViewport?.addEventListener('resize', handleVisualViewportResize);
154+
window.visualViewport?.addEventListener('scroll', handleVisualViewportScroll);
155+
156+
if (document.activeElement === input) {
157+
handleFocus();
158+
}
159+
160+
console.log('Started login input eventLow');
161+
162+
return () => {
163+
console.log('Tearing down login input eventLow');
164+
flushForTeardown();
165+
166+
input.removeEventListener('focus', handleFocus);
167+
input.removeEventListener('blur', handleBlur);
168+
input.removeEventListener('pointerdown', handlePointerDown);
169+
input.removeEventListener('pointerup', handlePointerUp);
170+
input.removeEventListener('click', handleClick);
171+
input.removeEventListener('input', handleInput);
172+
input.removeEventListener('keyup', handleKeyUp);
173+
174+
window.removeEventListener('focus', handleWindowFocus);
175+
window.removeEventListener('blur', handleWindowBlur);
176+
document.removeEventListener('visibilitychange', handleVisibilityChange);
177+
// window.removeEventListener('pagehide', flushForTeardown);
178+
window.visualViewport?.removeEventListener('resize', handleVisualViewportResize);
179+
window.visualViewport?.removeEventListener('scroll', handleVisualViewportScroll);
180+
};
181+
}, [connectService, enabled, inputRef]);
182+
};
183+
184+
export default useLoginInputEventLow;

packages/connect-react/src/types/flags.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const keyConditionalUI = 'conditional-ui-allowed';
22
const keyAutoAppend = 'automatic-append';
3+
const keyEventLow = 'event-low';
34

45
export class Flags {
56
readonly items: Record<string, string>;
@@ -21,4 +22,8 @@ export class Flags {
2122
hasSupportForAutomaticAppend(): boolean {
2223
return this.items[keyAutoAppend] === 'true';
2324
}
25+
26+
hasSupportForEventLow(): boolean {
27+
return this.items[keyEventLow] === 'true';
28+
}
2429
}

0 commit comments

Comments
 (0)