Skip to content

Commit

Permalink
chore: add ens localStorage caching
Browse files Browse the repository at this point in the history
  • Loading branch information
magiziz committed May 12, 2024
1 parent 3eb40ff commit 5396572
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 1 deletion.
13 changes: 12 additions & 1 deletion packages/rainbowkit/src/hooks/useMainnetEnsName.ts
Expand Up @@ -4,14 +4,25 @@ import { useEnsName } from 'wagmi';
import { mainnet } from 'wagmi/chains';
import { enhancedProviderHttp } from '../core/network/enhancedProvider';
import { createQueryKey } from '../core/react-query/createQuery';
import { addEnsName, getEnsName } from '../utils/ens';
import { useIsMainnetConfigured } from './useIsMainnetConfigured';

async function getEnhancedProviderEnsName({ address }: { address: Address }) {
const ensName = getEnsName(address);

if (ensName) return ensName;

const response = await enhancedProviderHttp.get<{
data: Address | null;
}>('/v1/resolve-ens', { params: { address } });

return response.data.data;
const enhancedProviderEnsName = response.data.data;

if (enhancedProviderEnsName) {
addEnsName(address, enhancedProviderEnsName);
}

return enhancedProviderEnsName;
}

export function useMainnetEnsName(address?: Address) {
Expand Down
96 changes: 96 additions & 0 deletions packages/rainbowkit/src/utils/ens.test.ts
@@ -0,0 +1,96 @@
import { beforeAll, describe, expect, it, vi } from 'vitest';
import { addEnsName, getEnsName, getStorageKey } from './ens';

// vitalik.eth
const mockAddress = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
const mockEnsName = 'vitalik.eth';

const store: Record<string, string> = {};

const localStorageMock: Storage = {
getItem: (key: string): string => store[key] ?? null,
setItem: (key: string, value: string): void => {
store[key] = value.toString();
},
removeItem: (key: string): void => {
delete store[key];
},
clear: (): void => {},
// biome-ignore lint/correctness/noUnusedVariables: <explanation>
key: (index: number): string | null => '',
length: Object.keys(store).length,
// Easier to get number realtime, due to single object
// reference not allowing us to use localStorage.length realtime
total: () => Object.keys(store).length,
};

beforeAll((): void => {
localStorage = localStorageMock;
});

describe('ens', () => {
it('should return null if ens name is not found', () => {
const ensName = getEnsName(mockAddress);

expect(localStorage.total()).toBe(0);
expect(ensName).toBeNull();
});

it('should return ens name if added to storage', () => {
addEnsName(mockAddress, mockEnsName);

const ensName = getEnsName(mockAddress);

expect(localStorage.total()).toBe(1);
expect(ensName).toBe(mockEnsName);
});

it('should return null if ens name has expired', () => {
const date = new Date();

// 16 minutes passed (expired)
date.setMinutes(date.getMinutes() + 16);

vi.useFakeTimers();
vi.setSystemTime(date);

const ensName = getEnsName(mockAddress);

expect(localStorage.total()).toBe(0);
expect(ensName).toBeNull();
});

it('should return null if ens name from localStorage is not a string', () => {
addEnsName(mockAddress, mockEnsName);

expect(localStorage.total()).toBe(1);

// Pretend someone messed up the localStorage by putting null on ensName field
store[getStorageKey(mockAddress)] = JSON.stringify({
ensName: null,
expires: 0,
});

const ensName = getEnsName(mockAddress);

expect(localStorage.total()).toBe(0);
expect(ensName).toBeNull();
});

it('should return null if ens name expiration from localStorage is NaN', () => {
addEnsName(mockAddress, mockEnsName);

expect(localStorage.total()).toBe(1);

// Pretend someone messed up the localStorage by putting undefined on expires field
store[getStorageKey(mockAddress)] = JSON.stringify({
ensName: mockEnsName,
expires: undefined,
});

const ensName = getEnsName(mockAddress);

expect(localStorage.total()).toBe(0);
expect(ensName).toBeNull();
});
});
57 changes: 57 additions & 0 deletions packages/rainbowkit/src/utils/ens.ts
@@ -0,0 +1,57 @@
import { Address, isAddress } from 'viem';

interface EnsData {
ensName: string;
expires: number;
}

export function getStorageKey(address: Address) {
return `rk-ens-${address}`;
}

function safeParseJsonData(string: string | null): EnsData | null {
try {
const value = string ? JSON.parse(string) : null;
return typeof value === 'object' ? value : null;
} catch {
return null;
}
}

export function addEnsName(address: Address, ensName: string) {
if (!isAddress(address)) return;

const now = new Date();

const expiry = new Date(now.getTime() + 15 * 60_000); // Set expiry to 15 minutes from now

localStorage.setItem(
getStorageKey(address),
JSON.stringify({
ensName,
expires: expiry.getTime(),
}),
);
}

export function getEnsName(address: Address): string | null {
const data = safeParseJsonData(localStorage.getItem(getStorageKey(address)));

if (!data) return null;

const { ensName, expires } = data;

if (typeof ensName !== 'string' || Number.isNaN(Number(expires))) {
localStorage.removeItem(getStorageKey(address));
return null;
}

const now = new Date();

if (now.getTime() > Number(expires)) {
localStorage.removeItem(getStorageKey(address));
return null;
}

return ensName;
}

0 comments on commit 5396572

Please sign in to comment.