Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fine-mice-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rainbow-me/rainbowkit": minor
---

Added support for Content Security Policy (CSP) by optionally attaching a nonce to <style> tags
1 change: 1 addition & 0 deletions packages/rainbowkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"@vanilla-extract/sprinkles": "1.6.4",
"clsx": "2.1.1",
"cuer": "0.0.2",
"get-nonce": "^1.0.1",
"react-remove-scroll": "2.6.2",
"ua-parser-js": "^1.0.37"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import { ProfileDetails } from '../ProfileDetails/ProfileDetails';
export interface AccountModalProps {
open: boolean;
onClose: () => void;
nonce?: string;
}

export function AccountModal({ onClose, open }: AccountModalProps) {
export function AccountModal({ onClose, open, nonce }: AccountModalProps) {
const { address } = useAccount();
const { balance, ensAvatar, ensName } = useProfile({
address,
Expand All @@ -27,7 +28,7 @@ export function AccountModal({ onClose, open }: AccountModalProps) {
return (
<>
{address && (
<Dialog onClose={onClose} open={open} titleId={titleId}>
<Dialog onClose={onClose} open={open} titleId={titleId} nonce={nonce}>
<DialogContent bottomSheetOnMobile padding="0">
<ProfileDetails
address={address}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import {
export interface ChainModalProps {
open: boolean;
onClose: () => void;
nonce?: string;
}

export function ChainModal({ onClose, open }: ChainModalProps) {
export function ChainModal({ onClose, open, nonce }: ChainModalProps) {
const { chainId } = useAccount();
const { chains } = useConfig();
const [pendingChainId, setPendingChainId] = useState<number | null>(null);
Expand Down Expand Up @@ -57,7 +58,7 @@ export function ChainModal({ onClose, open }: ChainModalProps) {
}

return (
<Dialog onClose={onClose} open={open} titleId={titleId}>
<Dialog onClose={onClose} open={open} titleId={titleId} nonce={nonce}>
<DialogContent bottomSheetOnMobile paddingBottom="0">
<Box display="flex" flexDirection="column" gap="14">
<Box
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import { SignIn } from '../SignIn/SignIn';
export interface ConnectModalProps {
open: boolean;
onClose: () => void;
nonce?: string;
}

export function ConnectModal({ onClose, open }: ConnectModalProps) {
export function ConnectModal({ onClose, open, nonce }: ConnectModalProps) {
const titleId = 'rk_connect_title';
const connectionStatus = useConnectionStatus();

Expand All @@ -35,7 +36,12 @@ export function ConnectModal({ onClose, open }: ConnectModalProps) {

if (connectionStatus === 'disconnected') {
return (
<Dialog onClose={onConnectModalCancel} open={open} titleId={titleId}>
<Dialog
onClose={onConnectModalCancel}
open={open}
titleId={titleId}
nonce={nonce}
>
<DialogContent bottomSheetOnMobile padding="0" wide>
<ConnectOptions onClose={onConnectModalCancel} />
</DialogContent>
Expand All @@ -45,7 +51,12 @@ export function ConnectModal({ onClose, open }: ConnectModalProps) {

if (connectionStatus === 'unauthenticated') {
return (
<Dialog onClose={onAuthCancel} open={open} titleId={titleId}>
<Dialog
onClose={onAuthCancel}
open={open}
titleId={titleId}
nonce={nonce}
>
<DialogContent bottomSheetOnMobile padding="0">
<SignIn onClose={onAuthCancel} onCloseModal={onClose} />
</DialogContent>
Expand Down
14 changes: 13 additions & 1 deletion packages/rainbowkit/src/components/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import React, {
} from 'react';
import { createPortal } from 'react-dom';
import { RemoveScroll } from 'react-remove-scroll';
import { setNonce } from 'get-nonce';
import { isMobile } from '../../utils/isMobile';
import { Box } from '../Box/Box';
import { useThemeRootProps } from '../RainbowKitProvider/RainbowKitProvider';
Expand All @@ -22,9 +23,16 @@ interface DialogProps {
titleId: string;
onMountAutoFocus?: (event: Event) => void;
children: ReactNode;
nonce?: string;
}

export function Dialog({ children, onClose, open, titleId }: DialogProps) {
export function Dialog({
children,
onClose,
open,
titleId,
nonce,
}: DialogProps) {
useEffect(() => {
const handleEscape = (event: KeyboardEvent) =>
open && event.key === 'Escape' && onClose();
Expand All @@ -45,6 +53,10 @@ export function Dialog({ children, onClose, open, titleId }: DialogProps) {
const themeRootProps = useThemeRootProps();
const mobile = isMobile();

if (nonce) {
setNonce(nonce);
}

return (
<>
{open
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ const ModalContext = createContext<ModalContextValue>({

interface ModalProviderProps {
children: ReactNode;
nonce?: string;
}

export function ModalProvider({ children }: ModalProviderProps) {
export function ModalProvider({ children, nonce }: ModalProviderProps) {
const {
closeModal: closeConnectModal,
isModalOpen: connectModalOpen,
Expand Down Expand Up @@ -140,9 +141,21 @@ export function ModalProvider({ children }: ModalProviderProps) {
)}
>
{children}
<ConnectModal onClose={closeConnectModal} open={connectModalOpen} />
<AccountModal onClose={closeAccountModal} open={accountModalOpen} />
<ChainModal onClose={closeChainModal} open={chainModalOpen} />
<ConnectModal
onClose={closeConnectModal}
open={connectModalOpen}
nonce={nonce}
/>
<AccountModal
onClose={closeAccountModal}
open={accountModalOpen}
nonce={nonce}
/>
<ChainModal
onClose={closeChainModal}
open={chainModalOpen}
nonce={nonce}
/>
</ModalContext.Provider>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export interface RainbowKitProviderProps {
avatar?: AvatarComponent;
modalSize?: ModalSizes;
locale?: Locale;
nonce?: string; // optional CSP nonce
}

const defaultTheme = lightTheme();
Expand All @@ -88,6 +89,7 @@ export function RainbowKitProvider({
modalSize = ModalSizeOptions.WIDE,
showRecentTransactions = false,
theme = defaultTheme,
nonce,
}: RainbowKitProviderProps) {
usePreloadImages();
useFingerprint();
Expand Down Expand Up @@ -123,10 +125,11 @@ export function RainbowKitProvider({
<AppContext.Provider value={appContext}>
<ThemeIdContext.Provider value={id}>
<ShowBalanceProvider>
<ModalProvider>
<ModalProvider nonce={nonce}>
{theme ? (
<div {...createThemeRootProps(id)}>
<style
nonce={nonce}
// biome-ignore lint/security/noDangerouslySetInnerHtml: TODO
dangerouslySetInnerHTML={{
// Selectors are sanitized to only contain alphanumeric
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.