Skip to content

Commit ede28bf

Browse files
committed
chore: setup rpc
1 parent cfbe202 commit ede28bf

File tree

8 files changed

+216
-1
lines changed

8 files changed

+216
-1
lines changed

packages/devtools/src/app/app.vue

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<script setup lang="ts">
22
import { useHead } from '#app/composables/head'
3+
import { shallowRef } from 'vue'
4+
import { createDevBackend } from './backends/dev'
5+
import { backend } from './state/backend'
6+
import { fetchData, rawEvents } from './state/data'
37
48
import 'floating-vue/dist/style.css'
59
import './styles/global.css'
@@ -8,10 +12,24 @@ import './composables/dark'
812
useHead({
913
title: 'Vite DevTools',
1014
})
15+
16+
const error = shallowRef()
17+
18+
createDevBackend()
19+
.then(async (b) => {
20+
backend.value = b
21+
await b.connect()
22+
await fetchData(b)
23+
return b
24+
})
25+
.catch((e) => {
26+
console.error(e)
27+
error.value = e
28+
})
1129
</script>
1230

1331
<template>
1432
<div>
15-
Hello
33+
{{ rawEvents }}
1634
</div>
1735
</template>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { ConnectionMeta } from '../../shared/types'
2+
import { useRuntimeConfig } from '#app/nuxt'
3+
import { createStaticBackend } from './static'
4+
import { createWebSocketBackend } from './websocket'
5+
6+
export async function createDevBackend() {
7+
const config = useRuntimeConfig()
8+
const baseURL = config.app.baseURL
9+
const metadata: ConnectionMeta = await fetch(`${baseURL}api/metadata.json`)
10+
.then(r => r.json())
11+
12+
if (metadata.backend === 'static') {
13+
return createStaticBackend()
14+
}
15+
else {
16+
const url = `${location.protocol.replace('http', 'ws')}//${location.hostname}:${metadata.websocket}`
17+
18+
return createWebSocketBackend({
19+
name: 'dev',
20+
websocketUrl: url,
21+
})
22+
}
23+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Backend } from '../types/backend'
2+
import { shallowRef } from 'vue'
3+
4+
export const backend = shallowRef<Backend>()
5+
6+
export function getBackend() {
7+
return backend.value!
8+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { ServerFunctionsDump } from '../../shared/types'
2+
import type { Backend } from '../types/backend'
3+
import { useRuntimeConfig } from '#app/nuxt'
4+
import { parse } from 'structured-clone-es'
5+
import { ref } from 'vue'
6+
7+
export function createStaticBackend(): Backend {
8+
const config = useRuntimeConfig()
9+
const baseURL = config.app.baseURL
10+
const status: Backend['status'] = ref('connecting')
11+
const error = ref<Error | undefined>(undefined)
12+
const getDump = fetch(`${baseURL}api/rpc-dump.json`)
13+
.then(res => res.text())
14+
.then(text => parse(text) as ServerFunctionsDump)
15+
.then((dump) => {
16+
status.value = 'connected'
17+
return dump
18+
})
19+
.catch((e) => {
20+
status.value = 'error'
21+
console.error('Failed to fetch RPC dump:', e)
22+
error.value = e
23+
throw e
24+
})
25+
26+
return {
27+
name: 'static',
28+
status,
29+
connectionError: error,
30+
connect() {},
31+
functions: {
32+
getPayload: () => {
33+
return getDump.then(dump => dump.getPayload)
34+
},
35+
},
36+
}
37+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import type { ClientFunctions, ServerFunctions } from '../../shared/types'
2+
import type { Backend } from '../types/backend'
3+
import { createBirpc } from 'birpc'
4+
import { parse, stringify } from 'structured-clone-es'
5+
import { ref, shallowRef } from 'vue'
6+
7+
export interface WebSocketBackendOptions {
8+
name: string
9+
websocketUrl: string
10+
}
11+
12+
export function createWebSocketBackend(options: WebSocketBackendOptions): Backend {
13+
const status: Backend['status'] = ref('idle')
14+
const error: Backend['connectionError'] = shallowRef(undefined)
15+
16+
let connectPromise: Promise<WebSocket> | undefined
17+
let onMessage: any = () => {}
18+
19+
const clientFunctions = {} as ClientFunctions
20+
21+
const rpc = createBirpc<ServerFunctions, ClientFunctions>(clientFunctions, {
22+
post: async (d) => {
23+
if (!connectPromise)
24+
connectPromise = connect()
25+
const ws = await connectPromise
26+
while (ws.readyState === ws.CONNECTING) {
27+
await new Promise<void>(resolve => setTimeout(resolve, 100))
28+
}
29+
if (ws.readyState !== ws.OPEN) {
30+
error.value ||= new Error('WebSocket not open, message sending dismissed')
31+
throw error.value
32+
}
33+
ws.send(d)
34+
},
35+
on: (fn) => {
36+
onMessage = fn
37+
},
38+
serialize: stringify,
39+
deserialize: parse,
40+
onError(err, name) {
41+
error.value = err
42+
console.error(`[vite-devtools] RPC error on executing "${name}":`)
43+
console.error(err)
44+
},
45+
timeout: 120_000,
46+
})
47+
48+
async function connect() {
49+
try {
50+
const ws = new WebSocket(options.websocketUrl)
51+
52+
ws.addEventListener('close', () => {
53+
status.value = 'idle'
54+
})
55+
ws.addEventListener('open', () => {
56+
status.value = 'connected'
57+
error.value = undefined
58+
})
59+
ws.addEventListener('error', (e) => {
60+
status.value = 'error'
61+
error.value = e
62+
})
63+
ws.addEventListener('message', (e) => {
64+
status.value = 'connected'
65+
error.value = undefined
66+
onMessage(e.data)
67+
})
68+
69+
return ws
70+
}
71+
catch (e) {
72+
status.value = 'error'
73+
error.value = e
74+
throw e
75+
}
76+
}
77+
78+
return {
79+
name: options.name,
80+
status,
81+
connectionError: error,
82+
async connect() {
83+
if (!connectPromise)
84+
connectPromise = connect()
85+
await connectPromise
86+
},
87+
isDynamic: true,
88+
functions: {
89+
getPayload: async () => {
90+
try {
91+
return await rpc.getPayload()
92+
}
93+
catch (err) {
94+
error.value = err
95+
throw err
96+
}
97+
},
98+
openInEditor: (filename: string) => rpc.openInEditor.asEvent(filename),
99+
openInFinder: (filename: string) => rpc.openInFinder.asEvent(filename),
100+
},
101+
}
102+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import type { Backend } from '../types/backend'
2+
import { shallowRef } from 'vue'
3+
4+
export const backend = shallowRef<Backend | null>(null)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Backend } from '../types/backend'
2+
import { ref } from 'vue'
3+
4+
export const rawEvents = ref<any>([])
5+
6+
export async function fetchData(backend: Backend) {
7+
rawEvents.value = await backend.functions.getPayload()
8+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { Ref } from 'vue'
2+
import type { ServerFunctions } from '../../shared/types'
3+
4+
export type Functions =
5+
& Partial<ServerFunctions>
6+
& Pick<ServerFunctions, 'getPayload'>
7+
8+
export interface Backend {
9+
name: string
10+
status: Ref<'idle' | 'connecting' | 'connected' | 'error'>
11+
connectionError: Ref<unknown | undefined>
12+
connect: () => Promise<void> | void
13+
isDynamic?: boolean
14+
functions: Functions
15+
}

0 commit comments

Comments
 (0)