@@ -5,9 +5,13 @@ import type { DevToolsClientContext, DevToolsClientRpcHost, RpcClientEvents } fr
55import { createRpcClient } from '@vitejs/devtools-rpc'
66import { createWsRpcPreset } from '@vitejs/devtools-rpc/presets/ws/client'
77import { RpcFunctionsCollectorBase } from 'birpc-x'
8+ import { UAParser } from 'my-ua-parser'
89import { createEventEmitter } from '../utils/events'
10+ import { nanoid } from '../utils/nanoid'
11+ import { promiseWithResolver } from '../utils/promise'
912
1013const CONNECTION_META_KEY = '__VITE_DEVTOOLS_CONNECTION_META__'
14+ const CONNECTION_AUTH_ID_KEY = '__VITE_DEVTOOLS_CONNECTION_AUTH_ID__'
1115
1216function isNumeric ( str : string | number | undefined ) {
1317 if ( str == null )
@@ -27,10 +31,29 @@ export interface DevToolsRpcClient {
2731 * The events of the client
2832 */
2933 events : EventEmitter < RpcClientEvents >
34+
35+ /**
36+ * Whether the client is trusted
37+ */
38+ readonly isTrusted : boolean | null
3039 /**
3140 * The connection meta
3241 */
3342 readonly connectionMeta : ConnectionMeta
43+ /**
44+ * Return a promise that resolves when the client is trusted
45+ *
46+ * Rejects with an error if the timeout is reached
47+ *
48+ * @param timeout - The timeout in milliseconds, default to 60 seconds
49+ */
50+ ensureTrusted : ( timeout ?: number ) => Promise < boolean >
51+
52+ /**
53+ * Request trust from the server
54+ */
55+ requestTrust : ( ) => Promise < boolean >
56+
3457 /**
3558 * Call a RPC function on the server
3659 */
@@ -49,6 +72,31 @@ export interface DevToolsRpcClient {
4972 client : DevToolsClientRpcHost
5073}
5174
75+ function getConnectionAuthIdFromWindows ( ) : string {
76+ const getters = [
77+ ( ) => localStorage . getItem ( CONNECTION_AUTH_ID_KEY ) ,
78+ ( ) => ( window as any ) ?. [ CONNECTION_AUTH_ID_KEY ] ,
79+ ( ) => ( globalThis as any ) ?. [ CONNECTION_AUTH_ID_KEY ] ,
80+ ( ) => ( parent . window as any ) ?. [ CONNECTION_AUTH_ID_KEY ] ,
81+ ]
82+
83+ for ( const getter of getters ) {
84+ try {
85+ const value = getter ( )
86+ if ( value ) {
87+ if ( ! localStorage . getItem ( CONNECTION_AUTH_ID_KEY ) )
88+ localStorage . setItem ( CONNECTION_AUTH_ID_KEY , value )
89+ return value
90+ }
91+ }
92+ catch { }
93+ }
94+
95+ const uid = nanoid ( )
96+ localStorage . setItem ( CONNECTION_AUTH_ID_KEY , uid )
97+ return uid
98+ }
99+
52100function findConnectionMetaFromWindows ( ) : ConnectionMeta | undefined {
53101 const getters = [
54102 ( ) => ( window as any ) ?. [ CONNECTION_META_KEY ] ,
@@ -104,6 +152,11 @@ export async function getDevToolsRpcClient(
104152 const context : DevToolsClientContext = {
105153 rpc : undefined ! ,
106154 }
155+ const authId = getConnectionAuthIdFromWindows ( )
156+
157+ let isTrusted = false
158+ const trustedPromise = promiseWithResolver < boolean > ( )
159+
107160 const clientRpc : DevToolsClientRpcHost = new RpcFunctionsCollectorBase < DevToolsRpcClientFunctions , DevToolsClientContext > ( context )
108161
109162 // Create the RPC client
@@ -118,9 +171,60 @@ export async function getDevToolsRpcClient(
118171 } ,
119172 )
120173
174+ async function requestTrust ( ) {
175+ if ( isTrusted )
176+ return true
177+
178+ const info = new UAParser ( navigator . userAgent ) . getResult ( )
179+ const ua = [
180+ info . browser . name ,
181+ info . browser . version ,
182+ '|' ,
183+ info . os . name ,
184+ info . os . version ,
185+ info . device . type ,
186+ ] . filter ( i => i ) . join ( ' ' )
187+
188+ const result = await serverRpc . $call ( 'vite:anonymous:auth' , {
189+ authId,
190+ ua,
191+ } )
192+
193+ isTrusted = result . isTrusted
194+ trustedPromise . resolve ( isTrusted )
195+ events . emit ( 'rpc:is-trusted:updated' , isTrusted )
196+ return result . isTrusted
197+ }
198+
199+ async function ensureTrusted ( timeout = 60_000 ) : Promise < boolean > {
200+ if ( isTrusted )
201+ trustedPromise . resolve ( true )
202+
203+ if ( timeout <= 0 )
204+ return trustedPromise . promise
205+
206+ let clear = ( ) => { }
207+ await Promise . race ( [
208+ trustedPromise . promise . then ( clear ) ,
209+ new Promise ( ( resolve , reject ) => {
210+ const id = setTimeout ( ( ) => {
211+ reject ( new Error ( '[Vite DevTools] Timeout waiting for rpc to be trusted' ) )
212+ } , timeout )
213+ clear = ( ) => clearTimeout ( id )
214+ } ) ,
215+ ] )
216+
217+ return isTrusted
218+ }
219+
121220 const rpc : DevToolsRpcClient = {
122221 events,
222+ get isTrusted ( ) {
223+ return isTrusted
224+ } ,
123225 connectionMeta,
226+ ensureTrusted,
227+ requestTrust,
124228 call : ( ...args : any ) : any => {
125229 // @ts -expect-error casting
126230 return serverRpc . call ( ...args )
@@ -138,6 +242,7 @@ export async function getDevToolsRpcClient(
138242
139243 // @ts -expect-error assign to readonly property
140244 context . rpc = rpc
245+ requestTrust ( )
141246
142247 return rpc
143248}
0 commit comments