Skip to content

Commit b2e6871

Browse files
fshertsmbl
andauthored
Feature/discovery api (#39)
* feat: implement mock hooks * chore: add interfaces * wip hooks implementation * fix after review * add auto fetching in hooks * upd hooks after review * fix: adjust effect and api calls for race conditions, image property now required * fix: handle unmount set state * chore: refactor abort to simple variable * chore: adjust not ok check in requests --------- Co-authored-by: Alexey Tsymbal <[email protected]>
1 parent cadc210 commit b2e6871

File tree

4 files changed

+218
-31
lines changed

4 files changed

+218
-31
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,12 @@
11
export { useAction } from './useAction';
22
export { useActionsRegistryInterval } from './useActionRegistryInterval';
3+
export {
4+
useBlinkList,
5+
type BlinkList,
6+
type BlinkListEntry,
7+
} from './useBlinkList.ts';
8+
export {
9+
useMetadata,
10+
type BlinkMetadata,
11+
type MetadataRow,
12+
} from './useMetadata';
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { useCallback, useEffect, useState } from 'react';
2+
import { BLINK_CLIENT_KEY_HEADER, clientKey } from '../utils/client-key.ts';
3+
4+
export interface BlinkList {
5+
entries: BlinkListEntry[];
6+
}
7+
8+
export interface BlinkListEntry {
9+
id: string;
10+
title: string;
11+
description: string;
12+
blinkUrl: string;
13+
metadataUrl?: string;
14+
image: string;
15+
icon?: string;
16+
}
17+
18+
export const useBlinkList = () => {
19+
const [loading, setLoading] = useState(false);
20+
const [data, setData] = useState<BlinkList>();
21+
22+
const refetch = useCallback(() => {
23+
let ignore = false;
24+
25+
setLoading(true);
26+
fetchBlinkList()
27+
.then((data) => {
28+
if (!ignore) {
29+
setData(data);
30+
}
31+
})
32+
.finally(() => {
33+
if (!ignore) {
34+
setLoading(false);
35+
}
36+
});
37+
38+
return () => {
39+
ignore = true;
40+
};
41+
}, []);
42+
43+
useEffect(() => {
44+
const cancel = refetch();
45+
46+
return () => {
47+
cancel();
48+
};
49+
}, [refetch]);
50+
51+
return {
52+
loading,
53+
refetch,
54+
data: data?.entries ?? [],
55+
};
56+
};
57+
58+
async function fetchBlinkList(): Promise<BlinkList> {
59+
try {
60+
const response = await fetch(
61+
'https://registry.dial.to/v1/private/blinks/list',
62+
{
63+
method: 'GET',
64+
headers: {
65+
Accept: 'application/json',
66+
...(clientKey && { [BLINK_CLIENT_KEY_HEADER]: clientKey }),
67+
},
68+
},
69+
);
70+
if (!response.ok) {
71+
console.error(
72+
`[@dialectlabs/blinks] Failed to fetch blink list, response status: ${response.status}`,
73+
);
74+
return {
75+
entries: [],
76+
};
77+
}
78+
return (await response.json()) as BlinkList;
79+
} catch (e) {
80+
console.error(`[@dialectlabs/blinks] Failed to fetch blink list`, e);
81+
return {
82+
entries: [],
83+
};
84+
}
85+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { useCallback, useEffect, useState } from 'react';
2+
import { proxifyMetadata } from '../utils/proxify.ts';
3+
4+
interface UseMetadataArgs {
5+
wallet?: string; // user wallet address
6+
url: string; // metadata url
7+
}
8+
9+
export interface BlinkMetadata {
10+
rows: MetadataRow[];
11+
extendedDescription?: string;
12+
}
13+
14+
export interface MetadataRow {
15+
key: string;
16+
title: string;
17+
value: string;
18+
icon?: string;
19+
url?: string;
20+
}
21+
22+
export const useMetadata = ({ url, wallet }: UseMetadataArgs) => {
23+
const [loading, setLoading] = useState(false);
24+
const [data, setData] = useState<BlinkMetadata>();
25+
26+
const refetch = useCallback(() => {
27+
let ignore = false;
28+
29+
setLoading(true);
30+
fetchMetadata(url, wallet)
31+
.then((data) => {
32+
if (!ignore) {
33+
setData(data);
34+
}
35+
})
36+
.finally(() => {
37+
if (!ignore) {
38+
setLoading(false);
39+
}
40+
});
41+
42+
return () => {
43+
ignore = true;
44+
};
45+
}, [url, wallet]);
46+
47+
useEffect(() => {
48+
const cancel = refetch();
49+
50+
return () => {
51+
cancel();
52+
};
53+
}, [refetch]);
54+
55+
return {
56+
loading,
57+
refetch,
58+
data,
59+
};
60+
};
61+
62+
async function fetchMetadata(
63+
url: string,
64+
wallet?: string,
65+
): Promise<BlinkMetadata> {
66+
try {
67+
const urlObj = new URL(url);
68+
if (wallet) {
69+
urlObj.searchParams.append('account', wallet);
70+
}
71+
const { url: proxyUrl, headers: proxyHeaders } = proxifyMetadata(
72+
urlObj.toString(),
73+
);
74+
const response = await fetch(proxyUrl, {
75+
method: 'GET',
76+
headers: {
77+
Accept: 'application/json',
78+
...proxyHeaders,
79+
},
80+
});
81+
if (!response.ok) {
82+
console.error(
83+
`[@dialectlabs/blinks] Failed to fetch metadata, response status: ${response.status}`,
84+
);
85+
return {
86+
rows: [],
87+
};
88+
}
89+
return (await response.json()) as BlinkMetadata;
90+
} catch (e) {
91+
console.error(`[@dialectlabs/blinks] Failed to fetch metadata`, e);
92+
return {
93+
rows: [],
94+
};
95+
}
96+
}

packages/blinks-core/src/utils/proxify.ts

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import { BLINK_CLIENT_KEY_HEADER, clientKey } from './client-key.ts';
22

33
let proxyUrl: string | null = 'https://proxy.dial.to';
44

5+
export type ProxifiedResult = {
6+
readonly url: URL;
7+
readonly headers: Record<string, string>;
8+
};
9+
510
export function setProxyUrl(url: string): void {
611
if (!url) {
712
console.warn(
@@ -21,38 +26,35 @@ export function setProxyUrl(url: string): void {
2126
proxyUrl = url;
2227
}
2328

24-
export function proxify(url: string): {
25-
url: URL;
26-
headers: Record<string, string>;
27-
} {
28-
const baseUrl = new URL(url);
29-
if (shouldIgnoreProxy(baseUrl)) {
30-
return {
31-
url: baseUrl,
32-
headers: {},
33-
};
34-
}
35-
const proxifiedUrl = new URL(proxyUrl!);
36-
proxifiedUrl.searchParams.set('url', url);
37-
return {
38-
url: proxifiedUrl,
39-
headers: getProxifiedHeaders(),
40-
};
29+
export function proxify(url: string): ProxifiedResult {
30+
return createProxifiedUrl(url);
4131
}
4232

43-
export function proxifyImage(url: string): {
44-
url: URL;
45-
} {
46-
const baseUrl = new URL(url);
47-
if (shouldIgnoreProxy(baseUrl)) {
33+
export function proxifyImage(url: string): ProxifiedResult {
34+
return createProxifiedUrl(url, 'image');
35+
}
36+
37+
export function proxifyMetadata(url: string): ProxifiedResult {
38+
return createProxifiedUrl(url, 'metadata');
39+
}
40+
41+
function createProxifiedUrl(url: string, endpoint?: string): ProxifiedResult {
42+
const incomingUrl = new URL(url);
43+
if (!proxyUrl || shouldIgnoreProxy(incomingUrl)) {
4844
return {
49-
url: baseUrl,
45+
url: incomingUrl,
46+
headers: {},
5047
};
5148
}
52-
const proxifiedUrl = new URL(`${proxyUrl!}/image`);
49+
50+
const proxifiedUrl = endpoint
51+
? new URL(endpoint, proxyUrl)
52+
: new URL(proxyUrl);
5353
proxifiedUrl.searchParams.set('url', url);
54+
5455
return {
5556
url: proxifiedUrl,
57+
headers: getProxifiedHeaders(),
5658
};
5759
}
5860

@@ -63,11 +65,5 @@ function getProxifiedHeaders(): Record<string, string> {
6365
}
6466

6567
function shouldIgnoreProxy(url: URL): boolean {
66-
if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
67-
return true;
68-
}
69-
if (!proxyUrl) {
70-
return true;
71-
}
72-
return false;
68+
return url.hostname === 'localhost' || url.hostname === '127.0.0.1';
7369
}

0 commit comments

Comments
 (0)