Skip to content

Commit f1f035a

Browse files
feat: Pre-select first host in rercording (#616)
1 parent 82e7ffa commit f1f035a

File tree

4 files changed

+166
-29
lines changed

4 files changed

+166
-29
lines changed

src/store/generator/slices/recording.utils.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,36 @@ export function extractUniqueHosts(requests: ProxyData[]) {
66
return uniq(requests.map((request) => request.request.host).filter(Boolean))
77
}
88

9+
export function groupHostsByParty(hosts: string[]) {
10+
return hosts.reduce(
11+
(acc, host) => {
12+
const key = isHostThirdParty(host) ? 'thirdParty' : 'firstParty'
13+
return {
14+
...acc,
15+
[key]: [...acc[key], host],
16+
}
17+
},
18+
{ firstParty: [], thirdParty: [] }
19+
)
20+
}
21+
22+
export function isHostThirdParty(host: string) {
23+
const hostPatterns = [
24+
'.google.com',
25+
'.googleapis.com',
26+
'.gstatic.com',
27+
'.googleusercontent.com',
28+
'.googleadservices.com',
29+
'.doubleclick.net',
30+
'.google-analytics.com',
31+
'.googletagmanager.com',
32+
'.googlesyndication.com',
33+
'.googletagservices.com',
34+
'.recaptcha.net',
35+
]
36+
return hostPatterns.some((pattern) => host.includes(pattern))
37+
}
38+
939
export function shouldResetAllowList({
1040
requests,
1141
allowList,

src/views/Generator/Allowlist/Allowlist.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { GlobeIcon } from '@radix-ui/react-icons'
22
import { Button, Dialog, Flex } from '@radix-ui/themes'
3-
import { useState } from 'react'
3+
import { useEffect, useMemo, useState } from 'react'
44

55
import { PopoverDialog } from '@/components/PopoverDialogs'
66
import { useGeneratorStore } from '@/store/generator'
7-
import { extractUniqueHosts } from '@/store/generator/slices/recording.utils'
7+
import {
8+
extractUniqueHosts,
9+
groupHostsByParty,
10+
} from '@/store/generator/slices/recording.utils'
811

912
import { AllowlistDialog } from './AllowlistDialog'
1013

@@ -30,7 +33,19 @@ export function Allowlist() {
3033
(store) => store.setIncludeStaticAssets
3134
)
3235

33-
const hosts = extractUniqueHosts(requests)
36+
const { firstParty, thirdParty } = useMemo(() => {
37+
const uniqueHosts = extractUniqueHosts(requests)
38+
return groupHostsByParty(uniqueHosts)
39+
}, [requests])
40+
41+
useEffect(() => {
42+
// Using allowlist.length would require adding it as a dependency of useEffect.
43+
// This causes an unintended behavior of automatically selecting the first item when the user unselects all checkboxes. (making it impossible to make allowlist empty).
44+
const allowlistCount = useGeneratorStore.getState().allowlist.length
45+
if (firstParty[0] !== undefined && allowlistCount === 0) {
46+
setAllowlist([firstParty[0]])
47+
}
48+
}, [firstParty, setAllowlist])
3449

3550
function handleOpenChange(open: boolean) {
3651
if (!open) {
@@ -43,6 +58,8 @@ export function Allowlist() {
4358
// Show dialog as popover when triggered from the button
4459
const Wrapper = openAsPopover ? PopoverWrapper : DialogWrapper
4560

61+
const allHosts = [...firstParty, ...thirdParty]
62+
4663
const trigger = (
4764
<Button
4865
size="1"
@@ -51,7 +68,7 @@ export function Allowlist() {
5168
onClick={() => setOpenAsPopover(true)}
5269
>
5370
<GlobeIcon />
54-
Allowed hosts [{allowlist.length}/{hosts.length}]
71+
Allowed hosts [{allowlist.length}/{allHosts.length}]
5572
</Button>
5673
)
5774

@@ -62,7 +79,8 @@ export function Allowlist() {
6279
onOpenChange={handleOpenChange}
6380
>
6481
<AllowlistDialog
65-
hosts={hosts}
82+
firstPartyHosts={firstParty}
83+
thirdPartyHosts={thirdParty}
6684
allowlist={allowlist}
6785
requests={requests}
6886
includeStaticAssets={includeStaticAssets}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { CheckboxGroup, Flex, Text } from '@radix-ui/themes'
2+
3+
type AllowlistCheckGroupProps = {
4+
hosts: string[]
5+
allowlist: string[]
6+
onValueChange: (allowlist: string[]) => void
7+
}
8+
9+
export default function AllowlistCheckGroup({
10+
hosts,
11+
allowlist,
12+
onValueChange,
13+
}: AllowlistCheckGroupProps) {
14+
return (
15+
<Flex p="2" pr="4" asChild overflow="hidden">
16+
<CheckboxGroup.Root
17+
size="2"
18+
value={allowlist}
19+
onValueChange={onValueChange}
20+
>
21+
{hosts.map((host) => (
22+
<Text as="label" size="2" key={host}>
23+
<Flex gap="2" align="center">
24+
<CheckboxGroup.Item value={host} /> <Text truncate>{host}</Text>
25+
</Flex>
26+
</Text>
27+
))}
28+
</CheckboxGroup.Root>
29+
</Flex>
30+
)
31+
}

src/views/Generator/Allowlist/AllowlistDialog.tsx

Lines changed: 82 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,42 @@
1-
import { Cross2Icon, MagnifyingGlassIcon } from '@radix-ui/react-icons'
1+
import { css } from '@emotion/react'
2+
import {
3+
Cross2Icon,
4+
InfoCircledIcon,
5+
MagnifyingGlassIcon,
6+
} from '@radix-ui/react-icons'
27
import {
38
Button,
49
Checkbox,
5-
CheckboxGroup,
610
Flex,
711
IconButton,
812
ScrollArea,
913
TextField,
1014
Text,
1115
Card,
1216
Inset,
17+
Separator,
18+
Tooltip,
1319
} from '@radix-ui/themes'
14-
import { isEqual } from 'lodash-es'
20+
import { every, includes } from 'lodash'
1521
import { useMemo, useState } from 'react'
1622

1723
import { Label } from '@/components/Label'
1824
import { ProxyData } from '@/types'
1925
import { isNonStaticAssetResponse } from '@/utils/staticAssets'
2026

27+
import AllowlistCheckGroup from './AllowlistCheckGroup'
28+
2129
export function AllowlistDialog({
22-
hosts,
30+
firstPartyHosts,
31+
thirdPartyHosts,
2332
allowlist,
2433
requests,
2534
includeStaticAssets,
2635
setAllowlist,
2736
setIncludeStaticAssets,
2837
}: {
29-
hosts: string[]
38+
firstPartyHosts: string[]
39+
thirdPartyHosts: string[]
3040
allowlist: string[]
3141
includeStaticAssets: boolean
3242
requests: ProxyData[]
@@ -35,9 +45,14 @@ export function AllowlistDialog({
3545
}) {
3646
const [filter, setFilter] = useState('')
3747

38-
const filteredHosts = useMemo(
39-
() => hosts.filter((host) => host.includes(filter)),
40-
[hosts, filter]
48+
const firstPartyFilteredHosts = useMemo(
49+
() => firstPartyHosts.filter((host) => host.includes(filter)),
50+
[firstPartyHosts, filter]
51+
)
52+
53+
const thirdPartyFilteredHosts = useMemo(
54+
() => thirdPartyHosts.filter((host) => host.includes(filter)),
55+
[thirdPartyHosts, filter]
4156
)
4257

4358
const staticAssetCount = useMemo(() => {
@@ -51,7 +66,7 @@ export function AllowlistDialog({
5166
}, [requests, allowlist])
5267

5368
function handleSelectAll() {
54-
setAllowlist(filteredHosts)
69+
setAllowlist([...allowlist, ...firstPartyFilteredHosts])
5570
}
5671

5772
function handleSelectNone() {
@@ -66,6 +81,11 @@ export function AllowlistDialog({
6681
setIncludeStaticAssets(checked && staticAssetCount > 0)
6782
}
6883

84+
const isSelectAllDisabled = useMemo(
85+
() => every(firstPartyFilteredHosts, (host) => includes(allowlist, host)),
86+
[firstPartyFilteredHosts, allowlist]
87+
)
88+
6989
return (
7090
<>
7191
<Text size="2" as="p" mb="2">
@@ -99,7 +119,7 @@ export function AllowlistDialog({
99119
<Button
100120
size="1"
101121
onClick={handleSelectAll}
102-
disabled={isEqual(filteredHosts, allowlist)}
122+
disabled={isSelectAllDisabled}
103123
>
104124
Select all
105125
</Button>
@@ -117,21 +137,28 @@ export function AllowlistDialog({
117137
<Card size="1" mb="2">
118138
<Inset css={{ height: '210px' }}>
119139
<ScrollArea scrollbars="vertical" type="always">
120-
<Flex p="2" pr="4" asChild overflow="hidden">
121-
<CheckboxGroup.Root
122-
size="2"
123-
value={allowlist}
140+
<Flex direction="column" pt="2">
141+
{firstPartyFilteredHosts.length > 0 && (
142+
<AllowlistSeparator text="Hosts" />
143+
)}
144+
<AllowlistCheckGroup
145+
allowlist={allowlist}
124146
onValueChange={handleChangeHosts}
125-
>
126-
{filteredHosts.map((host) => (
127-
<Text as="label" size="2" key={host}>
128-
<Flex gap="2">
129-
<CheckboxGroup.Item value={host} />{' '}
130-
<Text truncate>{host}</Text>
131-
</Flex>
132-
</Text>
133-
))}
134-
</CheckboxGroup.Root>
147+
hosts={firstPartyFilteredHosts}
148+
/>
149+
{thirdPartyFilteredHosts.length > 0 && (
150+
<>
151+
<AllowlistSeparator
152+
text="3rd party hosts"
153+
tooltip="Selecting third-party hosts may include irrelevant or sensitive data outside your control. It is recommended that only hosts directly related to your app are selected."
154+
/>
155+
<AllowlistCheckGroup
156+
allowlist={allowlist}
157+
onValueChange={handleChangeHosts}
158+
hosts={thirdPartyFilteredHosts}
159+
/>
160+
</>
161+
)}
135162
</Flex>
136163
</ScrollArea>
137164
</Inset>
@@ -150,3 +177,34 @@ export function AllowlistDialog({
150177
</>
151178
)
152179
}
180+
181+
function AllowlistSeparator({
182+
text,
183+
tooltip,
184+
}: {
185+
text: string
186+
tooltip?: string
187+
}) {
188+
return (
189+
<Flex align="center" px="2">
190+
<Text size="1" color="gray">
191+
{text}
192+
</Text>
193+
{tooltip && (
194+
<Tooltip content={tooltip}>
195+
<InfoCircledIcon
196+
css={css`
197+
margin-left: var(--space-1);
198+
`}
199+
/>
200+
</Tooltip>
201+
)}
202+
<Separator
203+
ml="2"
204+
css={css`
205+
flex-grow: 1;
206+
`}
207+
/>
208+
</Flex>
209+
)
210+
}

0 commit comments

Comments
 (0)