Skip to content

Commit c6f3f5f

Browse files
committed
feat(testapp): add COOP simulation toggle
Made-with: Cursor
1 parent 5c9c044 commit c6f3f5f

4 files changed

Lines changed: 153 additions & 12 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const COOP_QUERY_KEY = 'coop';
2+
const COOP_QUERY_VALUE = 'same-origin';
3+
const COOP_HEADER = 'Cross-Origin-Opener-Policy';
4+
5+
self.addEventListener('install', () => {
6+
self.skipWaiting();
7+
});
8+
9+
self.addEventListener('activate', (event) => {
10+
event.waitUntil(self.clients.claim());
11+
});
12+
13+
self.addEventListener('fetch', (event) => {
14+
const url = new URL(event.request.url);
15+
16+
if (
17+
event.request.mode !== 'navigate' ||
18+
url.origin !== self.location.origin ||
19+
url.searchParams.get(COOP_QUERY_KEY) !== COOP_QUERY_VALUE
20+
) {
21+
return;
22+
}
23+
24+
event.respondWith(
25+
(async () => {
26+
const response = await fetch(event.request);
27+
const headers = new Headers(response.headers);
28+
headers.set(COOP_HEADER, COOP_QUERY_VALUE);
29+
headers.delete('content-encoding');
30+
headers.delete('content-length');
31+
32+
return new Response(response.body, {
33+
status: response.status,
34+
statusText: response.statusText,
35+
headers,
36+
});
37+
})()
38+
);
39+
});

examples/testapp/src/components/RpcMethods/RpcMethodCard.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ import {
1818
Textarea,
1919
VStack,
2020
} from '@chakra-ui/react';
21-
import React, { useCallback } from 'react';
21+
import React, { type ReactNode, useCallback } from 'react';
2222
import { useForm } from 'react-hook-form';
2323
import { Chain, hexToNumber } from 'viem';
2424
import { mainnet } from 'viem/chains';
2525

2626
import { useEIP1193Provider } from '../../context/EIP1193ProviderContextProvider';
27+
import { RpcRequestInput } from './method/RpcRequestInput';
2728
import { verifySignMsg } from './method/signMessageMethods';
2829
import { ADDR_TO_FILL, CHAIN_ID_TO_FILL } from './shortcut/const';
2930
import { multiChainShortcutsMap } from './shortcut/multipleChainShortcuts';
31+
import { ShortcutType } from './shortcut/ShortcutType';
3032

3133
type ResponseType = string;
3234
type RequestFormData = Record<string, unknown>;
@@ -65,7 +67,19 @@ const replaceAddressInValue = async (
6567
return value;
6668
};
6769

68-
export function RpcMethodCard({ format, method, params, shortcuts }) {
70+
export function RpcMethodCard({
71+
format,
72+
method,
73+
params,
74+
shortcuts,
75+
children = null,
76+
}: {
77+
format?: RpcRequestInput['format'];
78+
method: RpcRequestInput['method'];
79+
params?: RpcRequestInput['params'];
80+
shortcuts?: ShortcutType[];
81+
children?: ReactNode;
82+
}) {
6983
const [response, setResponse] = React.useState<unknown>(null);
7084
const [verifyResult, setVerifyResult] = React.useState<string | null>(null);
7185
const [error, setError] = React.useState<Record<string, unknown> | string | number | null>(null);
@@ -144,7 +158,7 @@ export function RpcMethodCard({ format, method, params, shortcuts }) {
144158
if (!provider) return;
145159

146160
const dataToSubmit = { ...data };
147-
let values = dataToSubmit;
161+
let values: object | readonly unknown[] = dataToSubmit;
148162
if (format) {
149163
const getCurrentAddress = async () =>
150164
(await provider.request({ method: 'eth_accounts' })) as [string];
@@ -159,7 +173,7 @@ export function RpcMethodCard({ format, method, params, shortcuts }) {
159173
}
160174
}
161175
}
162-
values = format(dataToSubmit);
176+
values = format(dataToSubmit as Record<string, string>);
163177
}
164178
try {
165179
const response = await provider.request({
@@ -313,6 +327,7 @@ export function RpcMethodCard({ format, method, params, shortcuts }) {
313327
</Code>
314328
</VStack>
315329
)}
330+
{children}
316331
</CardBody>
317332
</Card>
318333
);

examples/testapp/src/components/RpcMethods/shortcut/connectionMethodShortcuts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ const walletConnectShortcuts: ShortcutType[] = [
1616
},
1717
];
1818

19-
export const connectionMethodShortcutsMap = {
19+
export const connectionMethodShortcutsMap: Record<string, ShortcutType[]> = {
2020
wallet_connect: walletConnectShortcuts,
2121
};

examples/testapp/src/pages/index.page.tsx

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { Box, Container, Grid, Heading } from '@chakra-ui/react';
2-
import React, { useEffect } from 'react';
1+
import { Box, Container, Flex, Grid, GridItem, Heading, Switch, Text } from '@chakra-ui/react';
2+
import React, { useCallback, useEffect } from 'react';
3+
import { useRouter } from 'next/router';
34

45
import { EventListenersCard } from '../components/EventListeners/EventListenersCard';
56
import { WIDTH_2XL } from '../components/Layout';
67
import { MethodsSection } from '../components/MethodsSection/MethodsSection';
8+
import { RpcMethodCard } from '../components/RpcMethods/RpcMethodCard';
79
import { connectionMethods } from '../components/RpcMethods/method/connectionMethods';
810
import { ephemeralMethods } from '../components/RpcMethods/method/ephemeralMethods';
911
import { experimentalMethods } from '../components/RpcMethods/method/experimentalMethods';
@@ -23,8 +25,58 @@ import { walletTxShortcutsMap } from '../components/RpcMethods/shortcut/walletTx
2325
import { SDKConfig } from '../components/SDKConfig/SDKConfig';
2426
import { useEIP1193Provider } from '../context/EIP1193ProviderContextProvider';
2527

28+
const COOP_QUERY_KEY = 'coop';
29+
const COOP_QUERY_VALUE = 'same-origin';
30+
31+
async function ensureCoopServiceWorkerReady(basePath: string) {
32+
if (!('serviceWorker' in navigator)) {
33+
return;
34+
}
35+
36+
await navigator.serviceWorker.register(`${basePath}/coop-service-worker.js`);
37+
await navigator.serviceWorker.ready;
38+
39+
if (navigator.serviceWorker.controller) {
40+
return;
41+
}
42+
43+
await new Promise<void>((resolve) => {
44+
const timeout = window.setTimeout(resolve, 1000);
45+
navigator.serviceWorker.addEventListener(
46+
'controllerchange',
47+
() => {
48+
window.clearTimeout(timeout);
49+
resolve();
50+
},
51+
{ once: true }
52+
);
53+
});
54+
}
55+
2656
export default function Home() {
2757
const { provider } = useEIP1193Provider();
58+
const router = useRouter();
59+
const simulateCoop = router.query[COOP_QUERY_KEY] === COOP_QUERY_VALUE;
60+
61+
const handleSimulateCoopToggle = useCallback(
62+
async (event: React.ChangeEvent<HTMLInputElement>) => {
63+
const url = new URL(window.location.href);
64+
65+
if (event.target.checked) {
66+
url.searchParams.set(COOP_QUERY_KEY, COOP_QUERY_VALUE);
67+
try {
68+
await ensureCoopServiceWorkerReady(router.basePath);
69+
} catch {
70+
// Still navigate so the URL reflects the requested simulation state.
71+
}
72+
} else {
73+
url.searchParams.delete(COOP_QUERY_KEY);
74+
}
75+
76+
window.location.assign(url.toString());
77+
},
78+
[router.basePath]
79+
);
2880
const [connected, setConnected] = React.useState(false);
2981
const [chainId, setChainId] = React.useState<number | undefined>(undefined);
3082
// This is for Extension compatibility, Extension with SDK3.9 does not emit connect event
@@ -75,11 +127,46 @@ export default function Home() {
75127
<Box mt={4}>
76128
<SDKConfig />
77129
</Box>
78-
<MethodsSection
79-
title="Wallet Connection"
80-
methods={connectionMethods}
81-
shortcutsMap={connectionMethodShortcutsMap}
82-
/>
130+
<Box mt={4}>
131+
<Heading size="md">Wallet Connection</Heading>
132+
<Grid
133+
mt={2}
134+
templateColumns={{
135+
base: '100%',
136+
md: 'repeat(2, 50%)',
137+
xl: 'repeat(3, 33%)',
138+
}}
139+
gap={2}
140+
>
141+
<GridItem w="100%" key="eth_requestAccounts">
142+
<RpcMethodCard
143+
method="eth_requestAccounts"
144+
params={[]}
145+
format={undefined}
146+
shortcuts={connectionMethodShortcutsMap.eth_requestAccounts}
147+
>
148+
<Flex align="center" justify="space-between" mt={4} pt={3} borderTopWidth={1}>
149+
<Text fontSize="sm" fontWeight="medium">
150+
Simulate COOP
151+
</Text>
152+
<Switch isChecked={simulateCoop} onChange={handleSimulateCoopToggle} />
153+
</Flex>
154+
</RpcMethodCard>
155+
</GridItem>
156+
{connectionMethods
157+
.filter((rpc) => rpc.method !== 'eth_requestAccounts')
158+
.map((rpc) => (
159+
<GridItem w="100%" key={rpc.method}>
160+
<RpcMethodCard
161+
method={rpc.method}
162+
params={rpc.params}
163+
format={rpc.format}
164+
shortcuts={connectionMethodShortcutsMap[rpc.method]}
165+
/>
166+
</GridItem>
167+
))}
168+
</Grid>
169+
</Box>
83170
<MethodsSection
84171
title="Ephemeral Methods"
85172
methods={ephemeralMethods}

0 commit comments

Comments
 (0)