Skip to content

Commit 02bc315

Browse files
feat: emit events
1 parent e723d30 commit 02bc315

File tree

4 files changed

+126
-8
lines changed

4 files changed

+126
-8
lines changed

packages/render/src/frame-app/iframe.ts

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
1-
import { exposeToEndpoint, createIframeEndpoint } from "@farcaster/frame-host";
1+
import {
2+
exposeToEndpoint,
3+
createIframeEndpoint,
4+
type HostEndpoint,
5+
} from "@farcaster/frame-host";
26
import { useEffect, useMemo, useRef } from "react";
37
import type { UseFrameAppOptions, UseFrameAppReturn } from "../use-frame-app";
48
import { useFrameApp } from "../use-frame-app";
59
import { useFreshRef } from "../hooks/use-fresh-ref";
610
import { useDebugLog } from "../hooks/use-debug-log";
711
import { assertNever } from "../assert-never";
12+
import type { HostEndpointEmitter } from "./types";
13+
14+
type UseFrameAppReturnSuccess = Extract<
15+
UseFrameAppReturn,
16+
{ status: "success" }
17+
>;
818

919
export type UseFrameAppInIframeReturn =
1020
| Exclude<UseFrameAppReturn, { status: "success" }>
@@ -13,7 +23,8 @@ export type UseFrameAppInIframeReturn =
1323
src: string | undefined;
1424
ref: React.MutableRefObject<HTMLIFrameElement | null>;
1525
};
16-
} & Extract<UseFrameAppReturn, { status: "success" }>);
26+
emitter: HostEndpointEmitter;
27+
} & Omit<UseFrameAppReturnSuccess, "emitter">);
1728

1829
/**
1930
* Handles frame app in iframe.
@@ -22,7 +33,7 @@ export type UseFrameAppInIframeReturn =
2233
*
2334
* @example
2435
* ```
25-
* import { useFrameAppInIframe } from '@frames.js/render/unstable-use-frame-app';
36+
* import { useFrameAppInIframe } from '@frames.js/render/frame-app/iframe';
2637
* import { useWagmiProvider } from '@frames.js/render/frame-app/provider/wagmi';
2738
* import { useFarcasterSigner } from '@frames.js/render/identity/farcaster';
2839
*
@@ -55,10 +66,36 @@ export function useFrameAppInIframe(
5566
const debugRef = useFreshRef(options.debug ?? false);
5667
const frameApp = useFrameApp(options);
5768
const iframeRef = useRef<HTMLIFrameElement | null>(null);
69+
const endpointRef = useRef<HostEndpoint | null>(null);
70+
const emitterRef = useRef<HostEndpointEmitter | null>(null);
5871
const logDebug = useDebugLog(
5972
"@frames.js/render/frame-app/iframe",
6073
debugRef.current
6174
);
75+
const emitter = useMemo<HostEndpointEmitter>(() => {
76+
return {
77+
emit(...args) {
78+
if (emitterRef.current) {
79+
emitterRef.current.emit(...args);
80+
} else {
81+
logDebug(
82+
"endpoint not available, probably not initialized yet, skipping emit",
83+
args
84+
);
85+
}
86+
},
87+
emitEthProvider(...args) {
88+
if (emitterRef.current) {
89+
emitterRef.current.emitEthProvider(...args);
90+
} else {
91+
logDebug(
92+
"endpoint not available, probably not initialized yet, skipping emitEthProvider",
93+
args
94+
);
95+
}
96+
},
97+
};
98+
}, [logDebug]);
6299

63100
const result = useMemo<UseFrameAppInIframeReturn>(() => {
64101
switch (frameApp.status) {
@@ -87,12 +124,13 @@ export function useFrameAppInIframe(
87124
src: frameUrl,
88125
ref: iframeRef,
89126
},
127+
emitter,
90128
};
91129
}
92130
default:
93131
assertNever(frameApp);
94132
}
95-
}, [frameApp]);
133+
}, [frameApp, emitter]);
96134

97135
useEffect(() => {
98136
if (result.status !== "success") {
@@ -128,8 +166,14 @@ export function useFrameAppInIframe(
128166
ethProvider: providerRef.current,
129167
});
130168

169+
endpointRef.current = endpoint;
170+
emitterRef.current = result.getEmitter(endpoint);
171+
131172
return () => {
132173
logDebug("iframe unmounted, cleaning up");
174+
endpointRef.current = null;
175+
iframeRef.current = null;
176+
emitterRef.current = null;
133177
cleanup();
134178
};
135179
}, [result, logDebug, debugRef, providerRef]);

packages/render/src/frame-app/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { HostEndpoint } from "@farcaster/frame-host";
12
import type {
23
AddFrameResult,
34
FrameContext,
@@ -78,3 +79,8 @@ export type OnAddFrameRequestedFunction = (
7879
export type ResolveClientFunction = (options: {
7980
signal: AbortSignal;
8081
}) => Promise<FrameClientConfig>;
82+
83+
export type HostEndpointEmitter = Pick<
84+
HostEndpoint,
85+
"emit" | "emitEthProvider"
86+
>;

packages/render/src/frame-app/web-view.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,17 @@ import {
1414
import { useFreshRef } from "../hooks/use-fresh-ref";
1515
import { useDebugLog } from "../hooks/use-debug-log";
1616
import { assertNever } from "../assert-never";
17+
import type { HostEndpointEmitter } from "./types";
18+
19+
type UseFrameAppReturnSuccess = Extract<
20+
UseFrameAppReturn,
21+
{ status: "success" }
22+
>;
1723

1824
export type UseFrameAppInWebViewReturn =
1925
| Exclude<UseFrameAppReturn, { status: "success" }>
20-
| (Extract<UseFrameAppReturn, { status: "success" }> & {
26+
| (Omit<UseFrameAppReturnSuccess, "emitter"> & {
27+
emitter: HostEndpointEmitter;
2128
webViewProps: {
2229
source: WebViewProps["source"];
2330
onMessage: NonNullable<WebViewProps["onMessage"]>;
@@ -32,7 +39,7 @@ export type UseFrameAppInWebViewReturn =
3239
*
3340
* @example
3441
* ```
35-
* import { useFrameAppInWebView } from '@frames.js/render/unstable-use-frame-app';
42+
* import { useFrameAppInWebView } from '@frames.js/render/frame-app/web-view';
3643
* import { useWagmiProvider } from '@frames.js/render/frame-app/provider/wagmi';
3744
* import { useFarcasterSigner } from '@frames.js/render/identity/farcaster';
3845
*
@@ -66,10 +73,35 @@ export function useFrameAppInWebView(
6673
const debugRef = useFreshRef(options.debug ?? false);
6774
const frameApp = useFrameApp(options);
6875
const endpointRef = useRef<WebViewEndpoint | null>(null);
76+
const emitterRef = useRef<HostEndpointEmitter | null>(null);
6977
const logDebug = useDebugLog(
7078
"@frames.js/render/frame-app/web-view",
7179
debugRef.current
7280
);
81+
const emitter = useMemo<HostEndpointEmitter>(() => {
82+
return {
83+
emit(...args) {
84+
if (emitterRef.current) {
85+
emitterRef.current.emit(...args);
86+
} else {
87+
logDebug(
88+
"endpoint not available, probably not initialized yet, skipping emit",
89+
args
90+
);
91+
}
92+
},
93+
emitEthProvider(...args) {
94+
if (emitterRef.current) {
95+
emitterRef.current.emitEthProvider(...args);
96+
} else {
97+
logDebug(
98+
"endpoint not available, probably not initialized yet, skipping emitEthProvider",
99+
args
100+
);
101+
}
102+
},
103+
};
104+
}, [logDebug]);
73105

74106
const onMessage = useCallback<NonNullable<WebViewProps["onMessage"]>>(
75107
(event) => {
@@ -100,6 +132,7 @@ export function useFrameAppInWebView(
100132

101133
return {
102134
...frameApp,
135+
emitter,
103136
webViewProps: {
104137
source: { uri: frameUrl },
105138
onMessage,
@@ -110,7 +143,7 @@ export function useFrameAppInWebView(
110143
default:
111144
assertNever(frameApp);
112145
}
113-
}, [frameApp, onMessage]);
146+
}, [frameApp, onMessage, emitter]);
114147

115148
useEffect(() => {
116149
if (result.status !== "success") {
@@ -125,7 +158,6 @@ export function useFrameAppInWebView(
125158
}
126159

127160
const endpoint = createWebViewRpcEndpoint(webViewRef);
128-
endpointRef.current = endpoint;
129161
const cleanup = exposeToEndpoint({
130162
endpoint,
131163
frameOrigin: "ReactNativeWebView",
@@ -134,10 +166,14 @@ export function useFrameAppInWebView(
134166
ethProvider: providerRef.current,
135167
});
136168

169+
endpointRef.current = endpoint;
170+
emitterRef.current = result.getEmitter(endpoint);
171+
137172
return () => {
138173
logDebug("WebView unmounted, cleaning up");
139174
webViewRef.current = null;
140175
endpointRef.current = null;
176+
emitterRef.current = null;
141177
cleanup();
142178
};
143179
}, [result, logDebug, debugRef, providerRef]);

packages/render/src/use-frame-app.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { FarcasterSigner } from "./identity/farcaster";
1212
import type {
1313
EthProvider,
1414
FrameClientConfig,
15+
HostEndpointEmitter,
1516
OnAddFrameRequestedFunction,
1617
OnPrimaryButtonSetFunction,
1718
OnSendTransactionRequestFunction,
@@ -184,7 +185,14 @@ export type UseFrameAppReturn =
184185
frame: ParseFramesV2ResultWithFrameworkDetails;
185186
client: FrameClientConfig;
186187
status: "success";
188+
/**
189+
* Creates sdk that must be exposed to frame app endpoint
190+
*/
187191
sdk: (endpoint: HostEndpoint) => Omit<FrameHost, "ethProviderRequestV2">;
192+
/**
193+
* Gets emitter object that can be used to emit events to the frame app.
194+
*/
195+
getEmitter: (endpoint: HostEndpoint) => HostEndpointEmitter;
188196
}
189197
| {
190198
status: "pending";
@@ -276,6 +284,10 @@ export function useFrameApp({
276284
const frame = frameResolutionState.frame;
277285

278286
return {
287+
getEmitter: (endpoint) => ({
288+
emit: endpoint.emit,
289+
emitEthProvider: endpoint.emitEthProvider,
290+
}),
279291
sdk: (endpoint) => ({
280292
async addFrame() {
281293
logDebug("sdk.addFrame() called");
@@ -286,6 +298,11 @@ export function useFrameApp({
286298
) {
287299
logDebug("Invalid frame domain manifest");
288300

301+
endpoint.emit({
302+
event: "frame_add_rejected",
303+
reason: "invalid_domain_manifest",
304+
});
305+
289306
return {
290307
added: false,
291308
reason: "invalid_domain_manifest",
@@ -299,6 +316,11 @@ export function useFrameApp({
299316
) {
300317
logDebug("Frame already requested to be added.");
301318

319+
endpoint.emit({
320+
event: "frame_add_rejected",
321+
reason: "rejected_by_user",
322+
});
323+
302324
return {
303325
added: false,
304326
reason: "rejected_by_user",
@@ -316,12 +338,22 @@ export function useFrameApp({
316338
if (!added) {
317339
logDebug("Frame add request rejected by user");
318340

341+
endpoint.emit({
342+
event: "frame_add_rejected",
343+
reason: "rejected_by_user",
344+
});
345+
319346
return {
320347
added: false,
321348
reason: "rejected_by_user",
322349
};
323350
}
324351

352+
endpoint.emit({
353+
event: "frame_added",
354+
notificationDetails: added.notificationDetails,
355+
});
356+
325357
return added;
326358
},
327359
close() {

0 commit comments

Comments
 (0)