Skip to content

Commit 0819108

Browse files
authored
Merge branch 'main' into mintable-module
2 parents fd1979f + 41158b1 commit 0819108

File tree

7 files changed

+184
-23
lines changed

7 files changed

+184
-23
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
"thirdweb": minor
3+
---
4+
5+
Added new `SiteEmbed` React component for embedding thirdweb-supported sites with seamless wallet connection support.
6+
7+
The component allows you to embed other thirdweb-enabled sites while maintaining wallet connection state, supporting both in-app and ecosystem wallets.
8+
9+
Example usage:
10+
```tsx
11+
import { SiteEmbed } from "thirdweb/react";
12+
13+
<SiteEmbed
14+
src="https://thirdweb.com"
15+
client={client}
16+
ecosystem={ecosystem}
17+
/>
18+
```
19+
20+
Note: Embedded sites must include `<AutoConnect />` and support frame-ancestors in their Content Security Policy.

.github/stale.yml

Lines changed: 0 additions & 17 deletions
This file was deleted.

packages/thirdweb/src/exports/react.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,6 @@ export type {
195195
FarcasterProfile,
196196
LensProfile,
197197
} from "../social/types.js";
198+
199+
// Site Embed
200+
export { SiteEmbed } from "../react/web/ui/SiteEmbed.js";

packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import { useQuery } from "@tanstack/react-query";
44
import type { AsyncStorage } from "../../../../utils/storage/AsyncStorage.js";
5+
import { isEcosystemWallet } from "../../../../wallets/ecosystem/is-ecosystem-wallet.js";
6+
import { ClientScopedStorage } from "../../../../wallets/in-app/core/authentication/client-scoped-storage.js";
57
import { getUrlToken } from "../../../../wallets/in-app/web/lib/get-url-token.js";
68
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
79
import {
@@ -43,8 +45,25 @@ export function useAutoConnectCore(
4345
getStoredActiveWalletId(storage),
4446
]);
4547

46-
const { authResult, walletId, authProvider } = getUrlToken();
47-
if (authResult && walletId) {
48+
const { authResult, walletId, authProvider, authCookie } = getUrlToken();
49+
const wallet = wallets.find((w) => w.id === walletId);
50+
51+
// If an auth cookie is found and this site supports the wallet, we'll set the auth cookie in the client storage
52+
if (authCookie && wallet) {
53+
const clientStorage = new ClientScopedStorage({
54+
storage,
55+
clientId: props.client.clientId,
56+
ecosystem: isEcosystemWallet(wallet)
57+
? {
58+
id: wallet.id,
59+
partnerId: wallet.getConfig()?.partnerId,
60+
}
61+
: undefined,
62+
});
63+
await clientStorage.saveAuthCookie(authCookie);
64+
}
65+
66+
if (walletId) {
4867
lastActiveWalletId = walletId;
4968
lastConnectedWalletIds = lastConnectedWalletIds?.includes(walletId)
5069
? lastConnectedWalletIds
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { describe, expect, it } from "vitest";
2+
import { render, waitFor } from "../../../../test/src/react-render.js";
3+
import { TEST_CLIENT } from "../../../../test/src/test-clients.js";
4+
import { SiteEmbed } from "./SiteEmbed.js";
5+
6+
describe("SiteEmbed", () => {
7+
it("renders iframe with correct src", () => {
8+
const testUrl = "https://example.com/";
9+
const { container } = render(
10+
<SiteEmbed src={testUrl} client={TEST_CLIENT} />,
11+
);
12+
13+
const iframe = container.querySelector("iframe");
14+
expect(iframe).toBeTruthy();
15+
expect(iframe?.src).toBe(testUrl);
16+
});
17+
18+
it("throws error if clientId is not provided", () => {
19+
const testUrl = "https://example.com/";
20+
expect(() =>
21+
// biome-ignore lint/suspicious/noExplicitAny: testing invalid input
22+
render(<SiteEmbed src={testUrl} client={{} as any} />),
23+
).toThrow("The SiteEmbed client must have a clientId");
24+
});
25+
26+
it("adds wallet params to url when wallet is connected", async () => {
27+
const testUrl = "https://example.com/";
28+
const { container } = render(
29+
<SiteEmbed src={testUrl} client={TEST_CLIENT} />,
30+
{
31+
setConnectedWallet: true,
32+
},
33+
);
34+
35+
const iframe = container.querySelector("iframe");
36+
expect(iframe).toBeTruthy();
37+
await waitFor(() => expect(iframe?.src).toContain("walletId="));
38+
});
39+
});
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"use client";
2+
import { useQuery } from "@tanstack/react-query";
3+
import type { ThirdwebClient } from "../../../client/client.js";
4+
import { getLastAuthProvider } from "../../../react/core/utils/storage.js";
5+
import { webLocalStorage } from "../../../utils/storage/webStorage.js";
6+
import { isEcosystemWallet } from "../../../wallets/ecosystem/is-ecosystem-wallet.js";
7+
import { ClientScopedStorage } from "../../../wallets/in-app/core/authentication/client-scoped-storage.js";
8+
import type { Ecosystem } from "../../../wallets/in-app/core/wallet/types.js";
9+
import { useActiveWallet } from "../../core/hooks/wallets/useActiveWallet.js";
10+
11+
/**
12+
* Embeds another thirdweb-supported site for seamless in-app and ecosystem wallet connection.
13+
*
14+
* @note Make sure the embedded site includes <AutoConnect /> and supports frame ancestors, see [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors) for more information.
15+
*
16+
* @note The embedded site must support the connected wallet (ecosystem or in-app).
17+
*
18+
* @param {Object} props - The props to pass to the iframe
19+
* @param {String} props.src - The URL of the site to embed
20+
* @param {ThirdwebClient} props.client - The client to use for the embedded site
21+
* @param {Ecosystem} [props.ecosystem] - The ecosystem to use for the embedded site
22+
*
23+
* @example
24+
* ```tsx
25+
* import { SiteEmbed } from "thirdweb/react";
26+
*
27+
* <SiteEmbed src="https://thirdweb.com" clientId="thirdweb-client-id" />
28+
* ```
29+
*/
30+
export function SiteEmbed({
31+
src,
32+
client,
33+
ecosystem,
34+
...props
35+
}: {
36+
src: string;
37+
client: ThirdwebClient;
38+
ecosystem?: Ecosystem;
39+
} & React.DetailedHTMLProps<
40+
React.IframeHTMLAttributes<HTMLIFrameElement>,
41+
HTMLIFrameElement
42+
>) {
43+
if (!client.clientId) {
44+
throw new Error("The SiteEmbed client must have a clientId");
45+
}
46+
47+
const activeWallet = useActiveWallet();
48+
const walletId = activeWallet?.id;
49+
50+
const {
51+
data: { authProvider, authCookie } = {},
52+
} = useQuery({
53+
queryKey: ["site-embed", walletId, src, client.clientId, ecosystem],
54+
enabled:
55+
activeWallet && (isEcosystemWallet(activeWallet) || walletId === "inApp"),
56+
queryFn: async () => {
57+
const storage = new ClientScopedStorage({
58+
storage: webLocalStorage,
59+
clientId: client.clientId,
60+
ecosystem,
61+
});
62+
63+
const authProvider = await getLastAuthProvider(webLocalStorage);
64+
const authCookie = await storage.getAuthCookie();
65+
66+
return { authProvider, authCookie };
67+
},
68+
});
69+
70+
const url = new URL(src);
71+
if (walletId) {
72+
url.searchParams.set("walletId", walletId);
73+
}
74+
if (authProvider) {
75+
url.searchParams.set("authProvider", authProvider);
76+
}
77+
if (authCookie) {
78+
url.searchParams.set("authCookie", authCookie);
79+
}
80+
81+
return (
82+
<iframe
83+
src={encodeURI(url.toString())}
84+
width="100%"
85+
height="100%"
86+
allowFullScreen
87+
{...props}
88+
/>
89+
);
90+
}

packages/thirdweb/src/wallets/in-app/web/lib/get-url-token.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export function getUrlToken(): {
99
walletId?: WalletId;
1010
authResult?: AuthStoredTokenWithCookieReturnType;
1111
authProvider?: AuthOption;
12+
authCookie?: string;
1213
} {
1314
if (!window?.location) {
1415
// Not in web
@@ -20,18 +21,24 @@ export function getUrlToken(): {
2021
const authResultString = params.get("authResult");
2122
const walletId = params.get("walletId") as WalletId | undefined;
2223
const authProvider = params.get("authProvider") as AuthOption | undefined;
24+
const authCookie = params.get("authCookie") as string | undefined;
2325

24-
if (authResultString && walletId) {
25-
const authResult = JSON.parse(authResultString);
26-
params.delete("authResult");
26+
if ((authCookie || authResultString) && walletId) {
27+
const authResult = (() => {
28+
if (authResultString) {
29+
params.delete("authResult");
30+
return JSON.parse(decodeURIComponent(authResultString));
31+
}
32+
})();
2733
params.delete("walletId");
2834
params.delete("authProvider");
35+
params.delete("authCookie");
2936
window.history.pushState(
3037
{},
3138
"",
3239
`${window.location.pathname}?${params.toString()}`,
3340
);
34-
return { walletId, authResult, authProvider };
41+
return { walletId, authResult, authProvider, authCookie };
3542
}
3643
return {};
3744
}

0 commit comments

Comments
 (0)