Skip to content

Commit a672b73

Browse files
committed
wip
1 parent bc08566 commit a672b73

11 files changed

Lines changed: 196 additions & 278 deletions

File tree

apps/api/src/controllers/live.controller.ts

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,52 @@
1-
import type { FastifyRequest } from 'fastify';
2-
import superjson from 'superjson';
3-
41
import type { WebSocket } from '@fastify/websocket';
5-
import {
6-
eventBuffer,
7-
getProfileById,
8-
transformMinimalEvent,
9-
} from '@openpanel/db';
2+
import { eventBuffer } from '@openpanel/db';
103
import { setSuperJson } from '@openpanel/json';
11-
import { subscribeToPublishedEvent } from '@openpanel/redis';
4+
import {
5+
psubscribeToPublishedEvent,
6+
subscribeToPublishedEvent,
7+
} from '@openpanel/redis';
128
import { getProjectAccess } from '@openpanel/trpc';
139
import { getOrganizationAccess } from '@openpanel/trpc/src/access';
10+
import type { FastifyRequest } from 'fastify';
1411

1512
export function wsVisitors(
1613
socket: WebSocket,
1714
req: FastifyRequest<{
1815
Params: {
1916
projectId: string;
2017
};
21-
}>,
18+
}>
2219
) {
2320
const { params } = req;
24-
const unsubscribe = subscribeToPublishedEvent('events', 'saved', (event) => {
25-
if (event?.projectId === params.projectId) {
26-
eventBuffer.getActiveVisitorCount(params.projectId).then((count) => {
27-
socket.send(String(count));
28-
});
21+
const sendCount = () => {
22+
eventBuffer.getActiveVisitorCount(params.projectId).then((count) => {
23+
socket.send(String(count));
24+
});
25+
};
26+
27+
const unsubscribe = subscribeToPublishedEvent(
28+
'events',
29+
'batch',
30+
({ projectId }) => {
31+
if (projectId === params.projectId) {
32+
sendCount();
33+
}
2934
}
30-
});
35+
);
36+
37+
const punsubscribe = psubscribeToPublishedEvent(
38+
'__keyevent@0__:expired',
39+
(key) => {
40+
const [, , projectId] = key.split(':');
41+
if (projectId === params.projectId) {
42+
sendCount();
43+
}
44+
}
45+
);
3146

3247
socket.on('close', () => {
3348
unsubscribe();
49+
punsubscribe();
3450
});
3551
}
3652

@@ -42,18 +58,10 @@ export async function wsProjectEvents(
4258
};
4359
Querystring: {
4460
token?: string;
45-
type?: 'saved' | 'received';
4661
};
47-
}>,
62+
}>
4863
) {
49-
const { params, query } = req;
50-
const type = query.type || 'saved';
51-
52-
if (!['saved', 'received'].includes(type)) {
53-
socket.send('Invalid type');
54-
socket.close();
55-
return;
56-
}
64+
const { params } = req;
5765

5866
const userId = req.session?.userId;
5967
if (!userId) {
@@ -67,24 +75,20 @@ export async function wsProjectEvents(
6775
projectId: params.projectId,
6876
});
6977

78+
if (!access) {
79+
socket.send('No access');
80+
socket.close();
81+
return;
82+
}
83+
7084
const unsubscribe = subscribeToPublishedEvent(
7185
'events',
72-
type,
73-
async (event) => {
74-
if (event.projectId === params.projectId) {
75-
const profile = await getProfileById(event.profileId, event.projectId);
76-
socket.send(
77-
superjson.stringify(
78-
access
79-
? {
80-
...event,
81-
profile,
82-
}
83-
: transformMinimalEvent(event),
84-
),
85-
);
86+
'batch',
87+
({ projectId, count }) => {
88+
if (projectId === params.projectId) {
89+
socket.send(setSuperJson({ count }));
8690
}
87-
},
91+
}
8892
);
8993

9094
socket.on('close', () => unsubscribe());
@@ -96,7 +100,7 @@ export async function wsProjectNotifications(
96100
Params: {
97101
projectId: string;
98102
};
99-
}>,
103+
}>
100104
) {
101105
const { params } = req;
102106
const userId = req.session?.userId;
@@ -123,9 +127,9 @@ export async function wsProjectNotifications(
123127
'created',
124128
(notification) => {
125129
if (notification.projectId === params.projectId) {
126-
socket.send(superjson.stringify(notification));
130+
socket.send(setSuperJson(notification));
127131
}
128-
},
132+
}
129133
);
130134

131135
socket.on('close', () => unsubscribe());
@@ -137,7 +141,7 @@ export async function wsOrganizationEvents(
137141
Params: {
138142
organizationId: string;
139143
};
140-
}>,
144+
}>
141145
) {
142146
const { params } = req;
143147
const userId = req.session?.userId;
@@ -164,7 +168,7 @@ export async function wsOrganizationEvents(
164168
'subscription_updated',
165169
(message) => {
166170
socket.send(setSuperJson(message));
167-
},
171+
}
168172
);
169173

170174
socket.on('close', () => unsubscribe());

apps/start/src/components/events/event-listener.tsx

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AnimatedNumber } from '../animated-number';
12
import {
23
Tooltip,
34
TooltipContent,
@@ -8,71 +9,53 @@ import { useDebounceState } from '@/hooks/use-debounce-state';
89
import useWS from '@/hooks/use-ws';
910
import { cn } from '@/utils/cn';
1011

11-
import type { IServiceEvent, IServiceEventMinimal } from '@openpanel/db';
12-
import { useParams } from '@tanstack/react-router';
13-
import { AnimatedNumber } from '../animated-number';
14-
1512
export default function EventListener({
1613
onRefresh,
1714
}: {
1815
onRefresh: () => void;
1916
}) {
20-
const params = useParams({
21-
strict: false,
22-
});
2317
const { projectId } = useAppParams();
2418
const counter = useDebounceState(0, 1000);
25-
useWS<IServiceEventMinimal | IServiceEvent>(
19+
useWS<{ count: number }>(
2620
`/live/events/${projectId}`,
27-
(event) => {
28-
if (event) {
29-
const isProfilePage = !!params?.profileId;
30-
if (isProfilePage) {
31-
const profile = 'profile' in event ? event.profile : null;
32-
if (profile?.id === params?.profileId) {
33-
counter.set((prev) => prev + 1);
34-
}
35-
return;
36-
}
37-
38-
counter.set((prev) => prev + 1);
39-
}
21+
({ count }) => {
22+
counter.set((prev) => prev + count);
4023
},
4124
{
4225
debounce: {
4326
delay: 1000,
4427
maxWait: 5000,
4528
},
46-
},
29+
}
4730
);
4831

4932
return (
5033
<Tooltip>
5134
<TooltipTrigger asChild>
5235
<button
53-
type="button"
36+
className="flex h-8 items-center gap-2 rounded-md border border-border bg-card px-3 font-medium leading-none"
5437
onClick={() => {
5538
counter.set(0);
5639
onRefresh();
5740
}}
58-
className="flex h-8 items-center gap-2 rounded-md border border-border bg-card px-3 font-medium leading-none"
41+
type="button"
5942
>
6043
<div className="relative">
6144
<div
6245
className={cn(
63-
'h-3 w-3 animate-ping rounded-full bg-emerald-500 opacity-100 transition-all',
46+
'h-3 w-3 animate-ping rounded-full bg-emerald-500 opacity-100 transition-all'
6447
)}
6548
/>
6649
<div
6750
className={cn(
68-
'absolute left-0 top-0 h-3 w-3 rounded-full bg-emerald-500 transition-all',
51+
'absolute top-0 left-0 h-3 w-3 rounded-full bg-emerald-500 transition-all'
6952
)}
7053
/>
7154
</div>
7255
{counter.debounced === 0 ? (
7356
'Listening'
7457
) : (
75-
<AnimatedNumber value={counter.debounced} suffix=" new events" />
58+
<AnimatedNumber suffix=" new events" value={counter.debounced} />
7659
)}
7760
</button>
7861
</TooltipTrigger>

apps/start/src/components/events/table/index.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type Props = {
3535
>,
3636
unknown
3737
>;
38+
showEventListener?: boolean;
3839
};
3940

4041
const LOADING_DATA = [{}, {}, {}, {}, {}, {}, {}, {}, {}] as IServiceEvent[];
@@ -215,7 +216,7 @@ const VirtualizedEventsTable = ({
215216
);
216217
};
217218

218-
export const EventsTable = ({ query }: Props) => {
219+
export const EventsTable = ({ query, showEventListener = false }: Props) => {
219220
const { isLoading } = query;
220221
const columns = useColumns();
221222

@@ -272,7 +273,7 @@ export const EventsTable = ({ query }: Props) => {
272273

273274
return (
274275
<>
275-
<EventsTableToolbar query={query} table={table} />
276+
<EventsTableToolbar query={query} table={table} showEventListener={showEventListener} />
276277
<VirtualizedEventsTable table={table} data={data} isLoading={isLoading} />
277278
<div className="w-full h-10 center-center pt-4" ref={inViewportRef}>
278279
<div
@@ -291,9 +292,11 @@ export const EventsTable = ({ query }: Props) => {
291292
function EventsTableToolbar({
292293
query,
293294
table,
295+
showEventListener,
294296
}: {
295297
query: Props['query'];
296298
table: Table<IServiceEvent>;
299+
showEventListener: boolean;
297300
}) {
298301
const { projectId } = useAppParams();
299302
const [startDate, setStartDate] = useQueryState(
@@ -305,7 +308,7 @@ function EventsTableToolbar({
305308
return (
306309
<DataTableToolbarContainer>
307310
<div className="flex flex-1 flex-wrap items-center gap-2">
308-
<EventListener onRefresh={() => query.refetch()} />
311+
{showEventListener && <EventListener onRefresh={() => query.refetch()} />}
309312
<Button
310313
variant="outline"
311314
size="sm"

apps/start/src/components/onboarding/onboarding-verify-listener.tsx

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,13 @@
1-
import type {
2-
IServiceClient,
3-
IServiceEvent,
4-
IServiceProject,
5-
} from '@openpanel/db';
1+
import type { IServiceEvent } from '@openpanel/db';
62
import { CheckCircle2Icon, CheckIcon, Loader2 } from 'lucide-react';
7-
import { useState } from 'react';
8-
import useWS from '@/hooks/use-ws';
93
import { cn } from '@/utils/cn';
104
import { timeAgo } from '@/utils/date';
115

126
interface Props {
13-
project: IServiceProject;
14-
client: IServiceClient | null;
157
events: IServiceEvent[];
16-
onVerified: (verified: boolean) => void;
178
}
189

19-
const VerifyListener = ({ client, events: _events, onVerified }: Props) => {
20-
const [events, setEvents] = useState<IServiceEvent[]>(_events ?? []);
21-
useWS<IServiceEvent>(
22-
`/live/events/${client?.projectId}?type=received`,
23-
(data) => {
24-
setEvents((prev) => [...prev, data]);
25-
onVerified(true);
26-
}
27-
);
28-
10+
const VerifyListener = ({ events }: Props) => {
2911
const isConnected = events.length > 0;
3012

3113
const renderIcon = () => {
@@ -49,16 +31,18 @@ const VerifyListener = ({ client, events: _events, onVerified }: Props) => {
4931
<div
5032
className={cn(
5133
'flex gap-6 rounded-xl p-4 md:p-6',
52-
isConnected ? 'bg-emerald-100 dark:bg-emerald-700' : 'bg-blue-500/10'
34+
isConnected
35+
? 'bg-emerald-100 dark:bg-emerald-700/10'
36+
: 'bg-blue-500/10'
5337
)}
5438
>
5539
{renderIcon()}
5640
<div className="flex-1">
5741
<div className="font-semibold text-foreground/90 text-lg leading-normal">
58-
{isConnected ? 'Success' : 'Waiting for events'}
42+
{isConnected ? 'Successfully connected' : 'Waiting for events'}
5943
</div>
6044
{isConnected ? (
61-
<div className="flex flex-col-reverse">
45+
<div className="mt-2 flex flex-col-reverse gap-1">
6246
{events.length > 5 && (
6347
<div className="flex items-center gap-2">
6448
<CheckIcon size={14} />{' '}
@@ -69,7 +53,7 @@ const VerifyListener = ({ client, events: _events, onVerified }: Props) => {
6953
<div className="flex items-center gap-2" key={event.id}>
7054
<CheckIcon size={14} />{' '}
7155
<span className="font-medium">{event.name}</span>{' '}
72-
<span className="ml-auto text-emerald-800">
56+
<span className="ml-auto text-foreground/50 text-sm">
7357
{timeAgo(event.createdAt, 'round')}
7458
</span>
7559
</div>

0 commit comments

Comments
 (0)