Skip to content

Commit a744c67

Browse files
committed
post message poc
1 parent a51e8a7 commit a744c67

File tree

9 files changed

+1603
-861
lines changed

9 files changed

+1603
-861
lines changed

examples/vanilla/frame/index.html

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,56 +11,6 @@ <h1>Vanilla Frame</h1>
1111
</header>
1212
<button id="sign">sign hello</button>
1313
<button id="compose-cast">compose cast</button>
14-
<script type="module">
15-
import { sdk } from 'https://esm.sh/@farcaster/frame-sdk'
16-
import { createStore } from 'mipd'
17-
18-
const store = createStore()
19-
20-
let providers = store.getProviders()
21-
store.subscribe((providerDetails) => {
22-
providers = providerDetails
23-
console.debug('updated providers', providers)
24-
})
25-
26-
setTimeout(() => {
27-
sdk.actions.ready()
28-
Promise.race([
29-
sdk.context,
30-
new Promise<never>((_, reject) => {
31-
setTimeout(() => {
32-
reject(new Error('timed out waiting context'))
33-
}, 50)
34-
}),
35-
])
36-
.then((ctx) => {
37-
console.log(ctx)
38-
})
39-
.catch((e) => {
40-
console.warn(e.message)
41-
})
42-
43-
document.querySelector('#sign').onclick = () => {
44-
sdk.wallet.ethProvider
45-
.request({ method: 'eth_requestAccounts' })
46-
.then((addresses) => {
47-
return sdk.wallet.ethProvider.request({
48-
method: 'personal_sign',
49-
params: [
50-
'0x48656c6c6f2066726f6d2056616e696c6c61204672616d65',
51-
addresses[0],
52-
],
53-
})
54-
})
55-
.then((signature) => {
56-
alert('You signed:\n' + signature)
57-
})
58-
}
59-
60-
document.querySelector('#compose-cast').onclick = async () => {
61-
await sdk.actions.composeCast({ close: true })
62-
}
63-
}, 750)
64-
</script>
14+
<script type="module" src=".//index.ts"></script>
6515
</body>
6616
</html>

examples/vanilla/frame/index.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ store.subscribe((providerDetails) => {
1111

1212
setTimeout(() => {
1313
sdk.actions.ready()
14-
Promise.race([
15-
sdk.context,
16-
new Promise<never>((_, reject) => {
17-
setTimeout(() => {
18-
reject(new Error('timed out waiting context'))
19-
}, 50)
20-
}),
21-
])
14+
15+
sdk.setShareStateProvider(() => {
16+
return {
17+
path: 'https://www.youtube.com/watch',
18+
params: 'v=dQw4w9WgXcQ',
19+
}
20+
})
21+
22+
Promise.race([sdk.context])
2223
.then((ctx) => {
2324
// biome-ignore lint/suspicious/noConsoleLog: <explanation>
2425
console.log(ctx)

examples/vanilla/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<title>Vanilla Frame Host</title>
77
</head>
88
<body>
9+
<button id="share">share</button>
910
<div id="app"></div>
1011
<script type="module" src="/src/main.ts"></script>
1112
</body>

examples/vanilla/src/main.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type FrameHost, exposeToIframe } from '@farcaster/frame-host'
1+
import { type FrameHost, Rpc, exposeToIframe } from '@farcaster/frame-host'
22
import './style.css'
33

44
declare global {
@@ -52,4 +52,16 @@ const { endpoint } = exposeToIframe({
5252
sdk: frameHost,
5353
ethProvider: window.ethereum,
5454
frameOrigin: window.origin,
55+
debug: true,
5556
})
57+
58+
const appProviderClient = Rpc.createClient<Rpc.AppProviderSchema>({
59+
endpoint: endpoint as Rpc.Endpoint,
60+
channelName: 'appProvider',
61+
})
62+
63+
document.querySelector<HTMLButtonElement>('#share')!.onclick = async () => {
64+
const result = await appProviderClient.request({
65+
method: 'get_share_state',
66+
})
67+
}

packages/frame-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * as Manifest from './manifest'
66
export * from './types'
77
export * from './schemas'
88
export * from './funcs'
9+
export * as Rpc from './rpc'

packages/frame-core/src/rpc.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import {
2+
Provider,
3+
RpcRequest,
4+
RpcResponse,
5+
type RpcSchema,
6+
type RpcTransport,
7+
} from 'ox'
8+
import type { ShareState } from './funcs'
9+
10+
export interface EventSource {
11+
addEventListener(
12+
type: string,
13+
listener: (event: MessageEvent) => void,
14+
options?: {},
15+
): void
16+
17+
removeEventListener(
18+
type: string,
19+
listener: (event: MessageEvent) => void,
20+
options?: {},
21+
): void
22+
}
23+
24+
export interface Endpoint extends EventSource {
25+
postMessage(data?: any): void
26+
}
27+
28+
export function createClient<schema extends RpcSchema.Generic>({
29+
channelName,
30+
endpoint,
31+
origin,
32+
}: {
33+
channelName: string
34+
endpoint: Endpoint
35+
origin?: string
36+
}) {
37+
const pendingRequestCallbacks: Record<string, (response: unknown) => void> =
38+
{}
39+
const store = RpcRequest.createStore<schema>()
40+
41+
const request: RpcTransport.RequestFn<false, {}, schema> = async (
42+
parameters,
43+
) => {
44+
return new Promise((resolve, reject) => {
45+
const request = store.prepare(parameters)
46+
47+
pendingRequestCallbacks[request.id] = (response) => {
48+
try {
49+
resolve(
50+
RpcResponse.parse(response, {
51+
request,
52+
}) as never,
53+
)
54+
} catch (error) {
55+
reject(error)
56+
}
57+
}
58+
59+
endpoint.postMessage({
60+
[channelName]: request,
61+
})
62+
})
63+
}
64+
65+
function handleMessage(
66+
event: MessageEvent<Record<string, RpcResponse.RpcResponse>>,
67+
) {
68+
if (event.origin !== origin) {
69+
return
70+
}
71+
72+
const message = event.data
73+
if (message[channelName]) {
74+
const response = message[channelName]
75+
const callback = pendingRequestCallbacks[response.id]
76+
if (callback) {
77+
delete pendingRequestCallbacks[response.id]
78+
return callback(response)
79+
}
80+
}
81+
}
82+
83+
function destroy() {
84+
endpoint.removeEventListener('message', handleMessage)
85+
86+
for (const [id, cb] of Object.entries(pendingRequestCallbacks)) {
87+
cb({
88+
id: Number(id),
89+
jsonrpc: '2.0',
90+
error: {
91+
code: RpcResponse.InternalError.code,
92+
message: 'Client destroyed',
93+
},
94+
})
95+
}
96+
}
97+
98+
endpoint.addEventListener('message', handleMessage)
99+
100+
return {
101+
request,
102+
destroy,
103+
}
104+
}
105+
106+
export function createServer<schema extends RpcSchema.Generic>({
107+
channelName,
108+
endpoint,
109+
handleRequest,
110+
}: {
111+
channelName: string
112+
endpoint: Endpoint
113+
handleRequest: RpcTransport.RequestFn<false, {}, schema>
114+
}) {
115+
function handleMessage(
116+
event: MessageEvent<Record<string, RpcRequest.RpcRequest>>,
117+
) {
118+
const message = event.data
119+
if (message[channelName]) {
120+
const request = message[channelName]
121+
;(async () => {
122+
const response = await (async () => {
123+
try {
124+
const result = await handleRequest(request as never)
125+
return RpcResponse.from({ result }, { request })
126+
} catch (e) {
127+
if (
128+
e instanceof RpcResponse.BaseError ||
129+
e instanceof Provider.ProviderRpcError
130+
) {
131+
return {
132+
id: request.id,
133+
jsonrpc: request.jsonrpc,
134+
error: {
135+
code: e.code,
136+
message: e.message,
137+
},
138+
}
139+
}
140+
141+
return {
142+
id: request.id,
143+
jsonrpc: request.jsonrpc,
144+
error: {
145+
code: RpcResponse.InternalError.code,
146+
message: (e as Error).message,
147+
},
148+
}
149+
}
150+
})()
151+
152+
endpoint.postMessage({
153+
[channelName]: response,
154+
})
155+
})()
156+
}
157+
158+
return
159+
}
160+
161+
endpoint.addEventListener('message', handleMessage)
162+
163+
function close() {
164+
endpoint.removeEventListener('message', handleMessage)
165+
}
166+
167+
return {
168+
close,
169+
}
170+
}
171+
172+
export type AppProviderSchema = RpcSchema.From<{
173+
Request: {
174+
method: 'get_share_state'
175+
params?: undefined
176+
}
177+
ReturnType: ShareState
178+
}>

packages/frame-host/src/helpers/endpoint.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { FrameHost } from '@farcaster/frame-core'
1+
import { type FrameHost, Rpc } from '@farcaster/frame-core'
22
import type * as Provider from 'ox/Provider'
33
import { useEffect } from 'react'
44
import * as Comlink from '../comlink'

packages/frame-sdk/src/sdk.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import {
22
AddFrame,
33
type FrameClientEvent,
4+
Rpc,
45
type ShareStateProvider,
56
SignIn,
67
} from '@farcaster/frame-core'
7-
import { proxy } from 'comlink'
88
import { EventEmitter } from 'eventemitter3'
9+
import { RpcResponse, type RpcTransport } from 'ox'
10+
import { endpoint } from './endpoint'
911
import { frameHost } from './frameHost'
1012
import { provider } from './provider'
1113
import type { Emitter, EventMap, FrameSDK } from './types'
@@ -77,6 +79,36 @@ async function isInMiniApp(timeoutMs = 50): Promise<boolean> {
7779
return isInMiniApp
7880
}
7981

82+
type RequestFn = RpcTransport.RequestFn<false, {}, Rpc.AppProviderSchema>
83+
84+
const createAppProvider = () => {
85+
let requestFn: RequestFn = () => {
86+
throw new Error('How to handle init state?')
87+
}
88+
89+
Rpc.createServer<Rpc.AppProviderSchema>({
90+
endpoint: endpoint as Rpc.Endpoint,
91+
channelName: 'appProvider',
92+
handleRequest(request) {
93+
if (!requestFn) {
94+
throw new Error('No requestHandler set')
95+
}
96+
97+
return requestFn(request as never)
98+
},
99+
})
100+
101+
function setRequestHandler(fn: RequestFn) {
102+
requestFn = fn
103+
}
104+
105+
return {
106+
setRequestHandler,
107+
}
108+
}
109+
110+
const appProvider = createAppProvider()
111+
80112
export const sdk: FrameSDK = {
81113
...emitter,
82114
isInMiniApp,
@@ -131,7 +163,13 @@ export const sdk: FrameSDK = {
131163
ethProvider: provider,
132164
},
133165
setShareStateProvider: (fn: ShareStateProvider) => {
134-
frameHost.setShareStateProvider.bind(frameHost)(proxy(fn))
166+
appProvider.setRequestHandler(async (request) => {
167+
if (request.method === 'get_share_state') {
168+
return fn() as never
169+
}
170+
171+
throw new RpcResponse.MethodNotFoundError()
172+
})
135173
},
136174
}
137175

0 commit comments

Comments
 (0)