@farcaster/frame-host-react-native 0.0.0-canary-20250508150817 → 0.0.0-canary-20250508220359
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/rn-comlink-helpers.d.ts +15 -0
- package/dist/rn-comlink-helpers.js +61 -0
- package/dist/rn-comlink.d.ts +29 -0
- package/dist/rn-comlink.js +73 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/webviewAdapter.js +22 -8
- package/package.json +2 -2
- package/src/rn-comlink-helpers.ts +108 -0
- package/src/rn-comlink.ts +130 -0
- package/src/webviewAdapter.ts +31 -9
package/dist/webviewAdapter.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { exposeToEndpoint
|
|
1
|
+
import { exposeToEndpoint } from '@farcaster/frame-host';
|
|
2
2
|
import { useCallback, useEffect, useMemo, useState, } from 'react';
|
|
3
|
+
import { releaseProxy, wrapWebViewForRnComlink } from './rn-comlink';
|
|
3
4
|
import { createWebViewRpcEndpoint } from './webview';
|
|
4
5
|
/**
|
|
5
6
|
* Returns a handler of RPC message from WebView.
|
|
@@ -12,6 +13,7 @@ export function useWebViewRpcAdapter({ webViewRef, domain, sdk, ethProvider, deb
|
|
|
12
13
|
useEffect(() => {
|
|
13
14
|
const newEndpoint = createWebViewRpcEndpoint(webViewRef, domain);
|
|
14
15
|
setEndpoint(newEndpoint);
|
|
16
|
+
const webViewProxy = wrapWebViewForRnComlink(newEndpoint);
|
|
15
17
|
const cleanup = exposeToEndpoint({
|
|
16
18
|
endpoint: newEndpoint,
|
|
17
19
|
sdk,
|
|
@@ -21,6 +23,7 @@ export function useWebViewRpcAdapter({ webViewRef, domain, sdk, ethProvider, deb
|
|
|
21
23
|
});
|
|
22
24
|
return () => {
|
|
23
25
|
cleanup?.();
|
|
26
|
+
webViewProxy?.[releaseProxy]?.();
|
|
24
27
|
setEndpoint(undefined);
|
|
25
28
|
};
|
|
26
29
|
}, [webViewRef, domain, sdk, ethProvider, debug]);
|
|
@@ -48,11 +51,22 @@ export function useWebViewRpcEndpoint(webViewRef, domain) {
|
|
|
48
51
|
}), [endpoint, onMessage]);
|
|
49
52
|
}
|
|
50
53
|
export function useExposeWebViewToEndpoint({ endpoint, sdk, ethProvider, debug = false, }) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
let webViewProxy = null;
|
|
56
|
+
let cleanup = null;
|
|
57
|
+
if (endpoint) {
|
|
58
|
+
webViewProxy = wrapWebViewForRnComlink(endpoint);
|
|
59
|
+
cleanup = exposeToEndpoint({
|
|
60
|
+
endpoint,
|
|
61
|
+
sdk,
|
|
62
|
+
ethProvider,
|
|
63
|
+
frameOrigin: 'ReactNativeWebView',
|
|
64
|
+
debug,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return () => {
|
|
68
|
+
cleanup?.();
|
|
69
|
+
webViewProxy?.[releaseProxy]?.();
|
|
70
|
+
};
|
|
71
|
+
}, [endpoint, sdk, ethProvider, debug]);
|
|
58
72
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farcaster/frame-host-react-native",
|
|
3
|
-
"version": "0.0.0-canary-
|
|
3
|
+
"version": "0.0.0-canary-20250508220359",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"ox": "^0.4.4",
|
|
29
|
-
"@farcaster/frame-host": "0.0.0-canary-
|
|
29
|
+
"@farcaster/frame-host": "0.0.0-canary-20250508220359"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"clean": "rm -rf dist",
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Remote,
|
|
3
|
+
type TransferHandler,
|
|
4
|
+
type WireValue,
|
|
5
|
+
WireValueType,
|
|
6
|
+
fromWireValue,
|
|
7
|
+
toWireValue,
|
|
8
|
+
} from '@farcaster/frame-host'
|
|
9
|
+
|
|
10
|
+
// The shape of the serialized function reference coming from the WebView
|
|
11
|
+
interface SerializedRnFunction {
|
|
12
|
+
__isRNProxiedFunction: true
|
|
13
|
+
id: string
|
|
14
|
+
type: 'function'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function processArgumentsForRN(
|
|
18
|
+
argumentList: any[],
|
|
19
|
+
): [WireValue[], Transferable[]] {
|
|
20
|
+
const processed = argumentList.map((arg) => toWireValue(arg))
|
|
21
|
+
const wireValues = processed.map((p) => p[0])
|
|
22
|
+
const transferables = processed.flatMap((p) => p[1])
|
|
23
|
+
if (transferables.length > 0) {
|
|
24
|
+
console.warn(
|
|
25
|
+
'[RN Host Comlink] Transferables found in arguments to WebView proxied function are ignored.',
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
return [wireValues, []]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ReactNativeWebViewProxy {
|
|
32
|
+
_callWebViewProxiedFunction: (
|
|
33
|
+
id: string,
|
|
34
|
+
args: WireValue[],
|
|
35
|
+
) => Promise<WireValue>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates a Comlink TransferHandler for deserializing functions proxied from WebView
|
|
40
|
+
* and serializing function stubs created by this handler.
|
|
41
|
+
*/
|
|
42
|
+
export function createReactNativeFunctionDeserializer(
|
|
43
|
+
getWebViewProxy: () => Remote<ReactNativeWebViewProxy> | undefined,
|
|
44
|
+
): TransferHandler<Function, SerializedRnFunction> {
|
|
45
|
+
return {
|
|
46
|
+
// canHandle checks if a given value is a Function stub that this handler can serialize.
|
|
47
|
+
canHandle: (value: unknown): value is Function => {
|
|
48
|
+
return (
|
|
49
|
+
typeof value === 'function' &&
|
|
50
|
+
Object.hasOwn(value, '__functionId__') &&
|
|
51
|
+
typeof (value as any).__functionId__ === 'string'
|
|
52
|
+
)
|
|
53
|
+
},
|
|
54
|
+
// deserialize converts the wire format (SerializedRnFunction) into a callable Function stub.
|
|
55
|
+
deserialize: (serializedValue: SerializedRnFunction): Function => {
|
|
56
|
+
const functionId = serializedValue.id
|
|
57
|
+
console.debug(
|
|
58
|
+
`[RN Host Comlink] Creating stub for WebView function ID: ${functionId}`,
|
|
59
|
+
)
|
|
60
|
+
const webViewFunctionStub = async (...args: any[]) => {
|
|
61
|
+
const webViewProxy = getWebViewProxy()
|
|
62
|
+
if (!webViewProxy || !webViewProxy._callWebViewProxiedFunction) {
|
|
63
|
+
console.error(
|
|
64
|
+
'[RN Host Comlink] WebView proxy or _callWebViewProxiedFunction not available.',
|
|
65
|
+
)
|
|
66
|
+
throw new Error(
|
|
67
|
+
'[RN Host Comlink] WebView proxy not available. Cannot call remote function.',
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
console.debug(
|
|
71
|
+
`[RN Host Comlink] Calling WebView function ID: ${functionId} with args:`,
|
|
72
|
+
args,
|
|
73
|
+
)
|
|
74
|
+
const [wireArgs] = processArgumentsForRN(args)
|
|
75
|
+
try {
|
|
76
|
+
const typedProxy = webViewProxy as Remote<ReactNativeWebViewProxy>
|
|
77
|
+
const wireResult = await typedProxy._callWebViewProxiedFunction(
|
|
78
|
+
functionId,
|
|
79
|
+
wireArgs,
|
|
80
|
+
)
|
|
81
|
+
if (
|
|
82
|
+
wireResult &&
|
|
83
|
+
wireResult.type === WireValueType.HANDLER &&
|
|
84
|
+
(wireResult as any).name === 'throw'
|
|
85
|
+
) {
|
|
86
|
+
return fromWireValue(wireResult)
|
|
87
|
+
}
|
|
88
|
+
return fromWireValue(wireResult)
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error(
|
|
91
|
+
`[RN Host Comlink] Error in stub for function ID ${functionId}:`,
|
|
92
|
+
error,
|
|
93
|
+
)
|
|
94
|
+
throw error
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
;(webViewFunctionStub as any).__functionId__ = functionId
|
|
98
|
+
return webViewFunctionStub
|
|
99
|
+
},
|
|
100
|
+
// serialize converts a Function stub (that this handler knows about) into its wire format.
|
|
101
|
+
serialize: (fnStub: Function): [SerializedRnFunction, Transferable[]] => {
|
|
102
|
+
// canHandle should have already verified this is one of our stubs.
|
|
103
|
+
const id = (fnStub as any).__functionId__
|
|
104
|
+
console.debug(`[RN Host Comlink] Serializing function stub ID: ${id}`)
|
|
105
|
+
return [{ __isRNProxiedFunction: true, id, type: 'function' }, []]
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type FrameHost,
|
|
3
|
+
type Endpoint as FrameHostEndpoint,
|
|
4
|
+
type Remote as FrameHostRemote,
|
|
5
|
+
type TransferHandler as FrameHostTransferHandler,
|
|
6
|
+
expose,
|
|
7
|
+
releaseProxy,
|
|
8
|
+
transferHandlers,
|
|
9
|
+
wrap,
|
|
10
|
+
} from '@farcaster/frame-host'
|
|
11
|
+
import {
|
|
12
|
+
forwardProviderEvents,
|
|
13
|
+
wrapProviderRequest,
|
|
14
|
+
} from '@farcaster/frame-host/src/helpers/provider'
|
|
15
|
+
import { wrapHandlers } from '@farcaster/frame-host/src/helpers/sdk'
|
|
16
|
+
import type { Provider } from 'ox/Provider'
|
|
17
|
+
import {
|
|
18
|
+
type ReactNativeWebViewProxy,
|
|
19
|
+
createReactNativeFunctionDeserializer,
|
|
20
|
+
} from './rn-comlink-helpers'
|
|
21
|
+
|
|
22
|
+
// Re-export releaseProxy symbol for use in cleanup
|
|
23
|
+
export { releaseProxy }
|
|
24
|
+
|
|
25
|
+
let rnHandlersInitialized = false
|
|
26
|
+
let webViewProxyInstance: FrameHostRemote<ReactNativeWebViewProxy> | null = null
|
|
27
|
+
let rnFunctionDeserializer: FrameHostTransferHandler<any, any> | null = null
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Ensures React Native specific Comlink transfer handlers are registered globally.
|
|
31
|
+
*/
|
|
32
|
+
export function ensureRnComlinkHandlers(): void {
|
|
33
|
+
if (rnHandlersInitialized) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const getWebViewProxy = ():
|
|
38
|
+
| FrameHostRemote<ReactNativeWebViewProxy>
|
|
39
|
+
| undefined => {
|
|
40
|
+
return webViewProxyInstance === null ? undefined : webViewProxyInstance
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
rnFunctionDeserializer =
|
|
44
|
+
createReactNativeFunctionDeserializer(getWebViewProxy)
|
|
45
|
+
transferHandlers.set(
|
|
46
|
+
'rn_function',
|
|
47
|
+
rnFunctionDeserializer as FrameHostTransferHandler<any, any>,
|
|
48
|
+
)
|
|
49
|
+
console.debug(
|
|
50
|
+
'[RN Host Comlink] Registered ReactNativeFunctionDeserializer via ensureRnComlinkHandlers.',
|
|
51
|
+
)
|
|
52
|
+
rnHandlersInitialized = true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Wraps a WebView endpoint with Comlink, ensuring RN handlers are set up.
|
|
57
|
+
* Crucially, it sets the webViewProxyInstance needed by the deserializer's callback.
|
|
58
|
+
*
|
|
59
|
+
* If this is not called, deserializing the function will fail.
|
|
60
|
+
*/
|
|
61
|
+
export function wrapWebViewForRnComlink<T extends ReactNativeWebViewProxy>(
|
|
62
|
+
endpoint: FrameHostEndpoint,
|
|
63
|
+
): FrameHostRemote<T> {
|
|
64
|
+
ensureRnComlinkHandlers()
|
|
65
|
+
|
|
66
|
+
const proxy: FrameHostRemote<T> = wrap<T>(
|
|
67
|
+
endpoint,
|
|
68
|
+
) as unknown as FrameHostRemote<T>
|
|
69
|
+
|
|
70
|
+
webViewProxyInstance = proxy as FrameHostRemote<ReactNativeWebViewProxy>
|
|
71
|
+
|
|
72
|
+
return proxy
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Exposes the Host SDK object to the WebView, ensuring RN handlers are set up
|
|
77
|
+
* on the Comlink instance used for the exposure.
|
|
78
|
+
* Replicates logic from `@farcaster/frame-host`'s `exposeToEndpoint` helper
|
|
79
|
+
* but makes sure to set up the RN handlers first.
|
|
80
|
+
*
|
|
81
|
+
* @returns Cleanup function if any event listeners were added (e.g., provider events)
|
|
82
|
+
*/
|
|
83
|
+
export function exposeToWebViewWithRnComlink({
|
|
84
|
+
endpoint,
|
|
85
|
+
sdk,
|
|
86
|
+
ethProvider,
|
|
87
|
+
debug = false,
|
|
88
|
+
}: {
|
|
89
|
+
endpoint: FrameHostEndpoint
|
|
90
|
+
sdk: Omit<FrameHost, 'ethProviderRequestV2'>
|
|
91
|
+
ethProvider?: Provider
|
|
92
|
+
debug?: boolean
|
|
93
|
+
}): (() => void) | undefined {
|
|
94
|
+
ensureRnComlinkHandlers()
|
|
95
|
+
|
|
96
|
+
const extendedSdk = wrapHandlers(sdk as FrameHost)
|
|
97
|
+
|
|
98
|
+
let providerCleanup: (() => void) | undefined
|
|
99
|
+
if (ethProvider) {
|
|
100
|
+
extendedSdk.ethProviderRequestV2 = wrapProviderRequest({
|
|
101
|
+
provider: ethProvider,
|
|
102
|
+
debug,
|
|
103
|
+
})
|
|
104
|
+
try {
|
|
105
|
+
providerCleanup = forwardProviderEvents({
|
|
106
|
+
provider: ethProvider,
|
|
107
|
+
endpoint: endpoint as any,
|
|
108
|
+
})
|
|
109
|
+
} catch (e) {
|
|
110
|
+
console.error(
|
|
111
|
+
'[RN Host Comlink] Error setting up provider event forwarding:',
|
|
112
|
+
e,
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
expose(extendedSdk, endpoint)
|
|
119
|
+
console.debug(
|
|
120
|
+
'[RN Host Comlink] Exposed SDK to WebView via exposeToWebViewWithRnComlink',
|
|
121
|
+
)
|
|
122
|
+
} catch (e) {
|
|
123
|
+
console.warn(
|
|
124
|
+
'[RN Host Comlink] Error exposing API (may already be exposed):',
|
|
125
|
+
e,
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return providerCleanup
|
|
130
|
+
}
|
package/src/webviewAdapter.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
FrameHost,
|
|
3
|
+
Remote as FrameHostRemote,
|
|
4
|
+
} from '@farcaster/frame-host'
|
|
5
|
+
import { exposeToEndpoint } from '@farcaster/frame-host'
|
|
3
6
|
import type { Provider } from 'ox/Provider'
|
|
4
7
|
import {
|
|
5
8
|
type RefObject,
|
|
@@ -10,6 +13,8 @@ import {
|
|
|
10
13
|
} from 'react'
|
|
11
14
|
import type WebView from 'react-native-webview'
|
|
12
15
|
import type { WebViewMessageEvent, WebViewProps } from 'react-native-webview'
|
|
16
|
+
import { releaseProxy, wrapWebViewForRnComlink } from './rn-comlink'
|
|
17
|
+
import type { ReactNativeWebViewProxy } from './rn-comlink-helpers'
|
|
13
18
|
import { type WebViewEndpoint, createWebViewRpcEndpoint } from './webview'
|
|
14
19
|
|
|
15
20
|
/**
|
|
@@ -41,6 +46,9 @@ export function useWebViewRpcAdapter({
|
|
|
41
46
|
const newEndpoint = createWebViewRpcEndpoint(webViewRef, domain)
|
|
42
47
|
setEndpoint(newEndpoint)
|
|
43
48
|
|
|
49
|
+
const webViewProxy =
|
|
50
|
+
wrapWebViewForRnComlink<ReactNativeWebViewProxy>(newEndpoint)
|
|
51
|
+
|
|
44
52
|
const cleanup = exposeToEndpoint({
|
|
45
53
|
endpoint: newEndpoint,
|
|
46
54
|
sdk,
|
|
@@ -51,6 +59,7 @@ export function useWebViewRpcAdapter({
|
|
|
51
59
|
|
|
52
60
|
return () => {
|
|
53
61
|
cleanup?.()
|
|
62
|
+
webViewProxy?.[releaseProxy]?.()
|
|
54
63
|
setEndpoint(undefined)
|
|
55
64
|
}
|
|
56
65
|
}, [webViewRef, domain, sdk, ethProvider, debug])
|
|
@@ -107,11 +116,24 @@ export function useExposeWebViewToEndpoint({
|
|
|
107
116
|
ethProvider?: Provider
|
|
108
117
|
debug?: boolean
|
|
109
118
|
}) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
let webViewProxy: FrameHostRemote<ReactNativeWebViewProxy> | null = null
|
|
121
|
+
let cleanup: (() => void) | undefined | null = null
|
|
122
|
+
|
|
123
|
+
if (endpoint) {
|
|
124
|
+
webViewProxy = wrapWebViewForRnComlink<ReactNativeWebViewProxy>(endpoint)
|
|
125
|
+
|
|
126
|
+
cleanup = exposeToEndpoint({
|
|
127
|
+
endpoint,
|
|
128
|
+
sdk,
|
|
129
|
+
ethProvider,
|
|
130
|
+
frameOrigin: 'ReactNativeWebView',
|
|
131
|
+
debug,
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
return () => {
|
|
135
|
+
cleanup?.()
|
|
136
|
+
webViewProxy?.[releaseProxy]?.()
|
|
137
|
+
}
|
|
138
|
+
}, [endpoint, sdk, ethProvider, debug])
|
|
117
139
|
}
|