Skip to content

Commit 1bffb17

Browse files
authored
WIP: update hooks api (#13)
## Breaking changes: - `useAction` hook doesn't use adapter anymore, it accepts `adapter` as a parameter - `useAction` can resolve websites with `actions.json` and interstitials - `useActionAdapter` renamed to `useActionSolanaWalletAdapter` - use `https://actions-registry.dial.to/all` for security registry
1 parent 7e97941 commit 1bffb17

17 files changed

+173
-91
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,5 @@ dist
173173

174174
# Finder (MacOS) folder config
175175
.DS_Store
176+
177+
.vscode

package.json

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,15 @@
2121
"require": "./dist/ext/twitter.cjs",
2222
"types": "./dist/ext/twitter.d.ts"
2323
},
24-
"./react": {
25-
"import": "./dist/react/index.js",
26-
"require": "./dist/react/index.cjs",
27-
"types": "./dist/react/index.d.ts"
24+
"./hooks": {
25+
"import": "./dist/hooks/index.js",
26+
"require": "./dist/hooks/index.cjs",
27+
"types": "./dist/hooks/index.d.ts"
28+
},
29+
"./hooks/solana": {
30+
"import": "./dist/hooks/solana/index.js",
31+
"require": "./dist/hooks/solana/index.cjs",
32+
"types": "./dist/hooks/solana/index.d.ts"
2833
},
2934
".": {
3035
"import": "./dist/index.js",

src/api/ActionsRegistry.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ import { Action } from './Action.ts';
22

33
export type LookupType = 'action' | 'website' | 'interstitial';
44

5+
const DEFAULT_REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes
6+
57
export class ActionsRegistry {
68
private static instance: ActionsRegistry | null = null;
79
private actionsByHost: Record<string, RegisteredEntity>;
810
private websitesByHost: Record<string, RegisteredEntity>;
911
private interstitialsByHost: Record<string, RegisteredEntity>;
1012

13+
private initPromise: Promise<ActionsRegistryConfig> | null = null;
14+
1115
private constructor(config?: ActionsRegistryConfig) {
1216
this.actionsByHost = config
1317
? Object.fromEntries(
@@ -39,7 +43,16 @@ export class ActionsRegistry {
3943
}
4044

4145
public async init(): Promise<void> {
42-
const config = await fetchActionsRegistryConfig();
46+
if (this.initPromise !== null) {
47+
return;
48+
}
49+
await this.refresh();
50+
setInterval(() => this.refresh(), DEFAULT_REFRESH_INTERVAL);
51+
}
52+
53+
public async refresh(): Promise<void> {
54+
this.initPromise = fetchActionsRegistryConfig();
55+
const config = await this.initPromise;
4356
this.actionsByHost = Object.fromEntries(
4457
config.actions.map((action) => [action.host, action]),
4558
);
@@ -171,7 +184,7 @@ export const getExtendedInterstitialState = (
171184

172185
async function fetchActionsRegistryConfig(): Promise<ActionsRegistryConfig> {
173186
try {
174-
const response = await fetch('https://actions-registry.dialectapi.to/all');
187+
const response = await fetch('https://actions-registry.dial.to/all');
175188

176189
if (!response.ok) {
177190
console.error(

src/ext/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './twitter';

src/ext/twitter.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,7 @@ export function setupTwitterObserver(
7272
const twitterReactRoot = document.getElementById('react-root')!;
7373

7474
const refreshRegistry = async () => {
75-
await ActionsRegistry.getInstance().init();
76-
77-
setTimeout(refreshRegistry, 1000 * 60 * 10); // every 10 minutes
75+
return ActionsRegistry.getInstance().init();
7876
};
7977

8078
// if we don't have the registry, then we don't show anything
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
1-
'use client';
21
export { useAction } from './useAction';
3-
export { useActionAdapter } from './useActionAdapter';
42
export { useActionsRegistryInterval } from './useActionRegistryInterval';

src/hooks/solana/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { useActionSolanaWalletAdapter } from './useActionSolanaWalletAdapter';

src/react/useActionAdapter.ts renamed to src/hooks/solana/useActionSolanaWalletAdapter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useWallet } from '@solana/wallet-adapter-react';
33
import { useWalletModal } from '@solana/wallet-adapter-react-ui';
44
import { Connection, VersionedTransaction } from '@solana/web3.js';
55
import { useMemo } from 'react';
6-
import { ActionConfig } from '../api';
6+
import { ActionConfig } from '../../api';
77

88
/**
99
* Hook to create an action adapter using solana's wallet adapter.
@@ -13,7 +13,7 @@ import { ActionConfig } from '../api';
1313
* @param rpcUrlOrConnection
1414
* @see {Action}
1515
*/
16-
export function useActionAdapter(rpcUrlOrConnection: string | Connection) {
16+
export function useActionSolanaWalletAdapter(rpcUrlOrConnection: string | Connection) {
1717
const wallet = useWallet();
1818
const walletModal = useWalletModal();
1919

src/hooks/useAction.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
'use client';
2+
import { useEffect, useState } from 'react';
3+
import { Action, type ActionAdapter } from '../api';
4+
import { unfurlUrlToActionApiUrl } from '../utils/url-mapper.ts';
5+
import { useActionsRegistryInterval } from './useActionRegistryInterval.ts';
6+
7+
interface UseActionOptions {
8+
url: string | URL;
9+
adapter: ActionAdapter;
10+
securityRegistryRefreshInterval?: number;
11+
}
12+
13+
function useActionApiUrl(url: string | URL) {
14+
const [apiUrl, setApiUrl] = useState<string | null>(null);
15+
16+
useEffect(() => {
17+
let ignore = false;
18+
19+
unfurlUrlToActionApiUrl(new URL(url))
20+
.then((apiUrl) => {
21+
if (ignore) {
22+
return;
23+
}
24+
setApiUrl(apiUrl);
25+
})
26+
.catch((e) => {
27+
console.error('[@dialectlabs/blinks] Failed to unfurl action URL', e);
28+
setApiUrl(null);
29+
});
30+
31+
return () => {
32+
ignore = true;
33+
};
34+
}, [url]);
35+
36+
return { actionApiUrl: apiUrl };
37+
}
38+
39+
export function useAction({ url, adapter }: UseActionOptions) {
40+
const { isRegistryLoaded } = useActionsRegistryInterval();
41+
const { actionApiUrl } = useActionApiUrl(url);
42+
const [action, setAction] = useState<Action | null>(null);
43+
const [isLoading, setIsLoading] = useState(false);
44+
45+
useEffect(() => {
46+
setIsLoading(true);
47+
if (!isRegistryLoaded || !actionApiUrl) {
48+
return;
49+
}
50+
51+
let ignore = false;
52+
Action.fetch(actionApiUrl)
53+
.then((action) => {
54+
if (ignore) {
55+
return;
56+
}
57+
setAction(action);
58+
})
59+
.catch((e) => {
60+
console.error('[@dialectlabs/blinks] Failed to fetch action', e);
61+
setAction(null);
62+
})
63+
.finally(() => {
64+
if (!ignore) {
65+
setIsLoading(false);
66+
}
67+
});
68+
69+
return () => {
70+
ignore = true;
71+
};
72+
}, [actionApiUrl, isRegistryLoaded]);
73+
74+
useEffect(() => {
75+
action?.setAdapter(adapter);
76+
}, [action, adapter]);
77+
78+
return { action, isLoading };
79+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use client';
2+
import { useEffect, useState } from 'react';
3+
import { ActionsRegistry } from '../api';
4+
5+
export function useActionsRegistryInterval() {
6+
const [isRegistryLoaded, setRegistryLoaded] = useState(false);
7+
8+
useEffect(() => {
9+
ActionsRegistry.getInstance()
10+
.init()
11+
.then(() => {
12+
setRegistryLoaded(true);
13+
});
14+
}, [isRegistryLoaded]);
15+
16+
return { isRegistryLoaded };
17+
}

0 commit comments

Comments
 (0)