Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve type checking and error handling on web preferences #5607

Merged
merged 1 commit into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 16 additions & 15 deletions src/hooks/useWebData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,36 +85,38 @@ export default function useWebData() {

const getWebProfile = useCallback(async (address: string) => {
const response = address && (await getPreference('profile', address));
if (!response) {
return null;
}

return response?.profile;
}, []);

const updateWebShowcase = useCallback(
async (assetIds: any) => {
if (!webDataEnabled) return;
const response = await getPreference('showcase', accountAddress);
// If the showcase is populated, just updated it
if (response?.ids?.length > 0) {
setPreference(PreferenceActionType.update, 'showcase', accountAddress, assetIds);
} else {
// Initialize showcase and profiles
if (!response || !response.showcase.ids.length) {
await initWebData(assetIds);
logger.log('showcase initialized!');
return;
}

setPreference(PreferenceActionType.update, 'showcase', accountAddress, assetIds);
},
[accountAddress, initWebData, webDataEnabled]
);

const updateWebHidden = useCallback(
async (assetIds: any) => {
const response = await getPreference('hidden', accountAddress);
// If the showcase is populated, just updated it
if (response?.ids?.length > 0) {
setPreference(PreferenceActionType.update, 'hidden', accountAddress, assetIds);
} else {
if (!response || !response.hidden.ids.length) {
await setPreference(PreferenceActionType.init, 'hidden', accountAddress, assetIds);

logger.log('hidden initialized!');
return;
}

setPreference(PreferenceActionType.update, 'hidden', accountAddress, assetIds);
},
[accountAddress]
);
Expand All @@ -126,14 +128,13 @@ export default function useWebData() {
// If webdata is enabled
if (webDataEnabled) {
const response = await getPreference('showcase', accountAddress);
// If the showcase is populated, nothing to do
if (response?.ids?.length > 0) {
logger.log('showcase already initialized. skipping');
} else {
// Initialize
if (!response || !response.showcase.ids.length) {
await initWebData(showcaseTokens);
logger.log('showcase initialized!');
return;
}

logger.log('showcase already initialized. skipping');
}
}
} catch (e) {
Expand Down
106 changes: 80 additions & 26 deletions src/model/preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ import { RainbowFetchClient } from '../rainbow-fetch';
import { EthereumAddress } from '@/entities';
import { getSignatureForSigningWalletAndCreateSignatureIfNeeded, signWithSigningWallet } from '@/helpers/signingWallet';
import { logger } from '@/logger';
import { Address } from 'viem';

export const PREFS_ENDPOINT = 'https://api.rainbow.me';
const preferencesAPI = new RainbowFetchClient({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
timeout: 30000, // 30 secs
});

export enum PreferenceActionType {
update = 'update',
Expand All @@ -10,27 +20,60 @@ export enum PreferenceActionType {
init = 'init',
}

export interface PreferencesResponse {
success: boolean;
reason: string;
data?: Record<string, unknown> | undefined;
export enum PreferenceKeys {
showcase = 'showcase',
profile = 'profile',
}

export const PREFS_ENDPOINT = 'https://api.rainbow.me';
type TokenContract = Address;
type TokenId = string;

const preferencesAPI = new RainbowFetchClient({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
timeout: 30000, // 30 secs
});
type TokenContractWithId = `${TokenContract}_${TokenId}`;

type HiddenPreferencesData = {
hidden: {
ids: [];
};
};

type ShowcasePreferencesData = {
showcase: {
ids: TokenContractWithId[];
};
};

type Profile = {
accountColor: string;
accountSymbol: string | null;
};

type ProfilePreferencesData = {
profile: Profile;
};

type PreferencesDataMap = {
showcase: ShowcasePreferencesData;
profile: ProfilePreferencesData;
hidden: HiddenPreferencesData;
};

type PayloadMap = {
showcase: string[];
profile: Profile;
hidden: string[];
};

export async function setPreference(
type PreferencesResponse<T extends keyof PreferencesDataMap> = {
success: boolean;
data?: T extends keyof PreferencesDataMap ? PreferencesDataMap[T] : never;
reason?: string;
};

export async function setPreference<K extends keyof PreferencesDataMap>(
action: PreferenceActionType,
key: string,
key: K,
address: EthereumAddress,
value?: any | undefined
value?: PayloadMap[K]
): Promise<boolean> {
try {
const signature = await getSignatureForSigningWalletAndCreateSignatureIfNeeded(address);
Expand All @@ -46,17 +89,21 @@ export async function setPreference(
const message = JSON.stringify(objToSign);
const signature2 = await signWithSigningWallet(message);
logger.debug('☁️ SENDING ', { message });
const response = await preferencesAPI.post(`${PREFS_ENDPOINT}/${key}`, {
const { data } = await preferencesAPI.post<PreferencesResponse<K>>(`${PREFS_ENDPOINT}/${key}`, {
message,
signature,
signature2,
});
const responseData: PreferencesResponse = response.data as PreferencesResponse;
logger.debug('☁️ RESPONSE', {
reason: responseData?.reason,
success: responseData?.success,
reason: data?.reason,
success: data?.success,
});
return responseData?.success;

if (!data.data) {
throw new Error('Failed to set preference');
}

return data?.success;
} catch (e) {
logger.warn(`Preferences API failed to set preference`, {
preferenceKey: key,
Expand All @@ -65,17 +112,24 @@ export async function setPreference(
}
}

export async function getPreference(key: string, address: EthereumAddress): Promise<any | null> {
export async function getPreference<K extends keyof PreferencesDataMap>(
key: K,
address: EthereumAddress
): Promise<PreferencesDataMap[K] | null> {
try {
const response = await preferencesAPI.get(`${PREFS_ENDPOINT}/${key}`, {
const { data } = await preferencesAPI.get<PreferencesResponse<K>>(`${PREFS_ENDPOINT}/${key}`, {
params: { address },
});
const responseData: PreferencesResponse = response.data as PreferencesResponse;
logger.debug('☁️ RESPONSE', {
reason: responseData?.reason,
success: responseData?.success,
reason: data?.reason,
success: data?.success,
});
return responseData?.data || null;

if (!data.data) {
return null;
}

return data.data;
} catch (e) {
logger.warn(`Preferences API failed to get preference`, {
preferenceKey: key,
Expand Down