@@ -3,10 +3,10 @@ import * as Effect from "effect/Effect";
33import * as FileSystem from "effect/FileSystem" ;
44import * as Layer from "effect/Layer" ;
55import * as Option from "effect/Option" ;
6- import * as NodeTimersPromises from "node:timers/promises" ;
76import * as Path from "effect/Path" ;
87import * as Mime from "effect/http/Mime" ;
98import * as Ref from "effect/Ref" ;
9+ import * as Schedule from "effect/Schedule" ;
1010import * as Schema from "effect/Schema" ;
1111import * as Scope from "effect/Scope" ;
1212
@@ -150,11 +150,29 @@ const registerDesktopSchemePrivileges = Effect.sync(registerDesktopSchemePrivile
150150
151151export const layerSchemePrivileges = Layer . effectDiscard ( registerDesktopSchemePrivileges ) ;
152152
153- async function proxyRequest (
153+ class ElectronProtocolFetchError extends Schema . TaggedError < ElectronProtocolFetchError > ( ) (
154+ "ElectronProtocolFetchError" ,
155+ { cause : Schema . Defect ( ) } ,
156+ ) { }
157+
158+ const netFetch = ( url : string , init : RequestInit ) =>
159+ Effect . tryPromise ( {
160+ try : ( ) => Electron . net . fetch ( url , init ) ,
161+ catch : ( cause ) => new ElectronProtocolFetchError ( { cause } ) ,
162+ } ) ;
163+
164+ // The dev renderer target can briefly refuse connections while Vite restarts:
165+ // retry idempotent requests after 50ms, then 150ms, and keep the last failure.
166+ const fetchWithTransientRetry = ( url : string , init : RequestInit ) =>
167+ netFetch ( url , init ) . pipe (
168+ Effect . retry ( { schedule : Schedule . exponential ( "50 millis" , 3 ) , times : 2 } ) ,
169+ ) ;
170+
171+ const proxyRequest = Effect . fn ( "desktop.protocol.proxyRequest" ) ( function * (
154172 request : Request ,
155173 targetOrigin : URL ,
156174 contentSecurityPolicy : string ,
157- ) : Promise < Response > {
175+ ) {
158176 const requestUrl = new URL ( request . url ) ;
159177 if ( requestUrl . host !== DESKTOP_HOST ) {
160178 return new Response ( null , { status : 404 } ) ;
@@ -190,12 +208,10 @@ async function proxyRequest(
190208 }
191209 const response =
192210 request . method === "GET" || request . method === "HEAD"
193- ? await fetchWithTransientRetry ( targetUrl . toString ( ) , init )
194- : await Electron . net . fetch ( targetUrl . toString ( ) , init ) ;
211+ ? yield * fetchWithTransientRetry ( targetUrl . toString ( ) , init )
212+ : yield * netFetch ( targetUrl . toString ( ) , init ) ;
195213 return withContentSecurityPolicy ( response , contentSecurityPolicy ) ;
196- }
197-
198- const TRANSIENT_FETCH_RETRY_DELAYS_MS = [ 0 , 50 , 150 ] as const ;
214+ } ) ;
199215
200216// Serves the packaged web client without a backend: files resolve within the
201217// asset directory, and any other path falls back to index.html so the SPA
@@ -238,24 +254,6 @@ const serveDesktopAsset = Effect.fn("desktop.protocol.serveAsset")(function* (
238254 } ) ;
239255} ) ;
240256
241- async function fetchWithTransientRetry ( url : string , init : RequestInit ) : Promise < Response > {
242- let lastError : unknown ;
243-
244- for ( const delayMs of TRANSIENT_FETCH_RETRY_DELAYS_MS ) {
245- if ( delayMs > 0 ) {
246- await NodeTimersPromises . setTimeout ( delayMs ) ;
247- }
248-
249- try {
250- return await Electron . net . fetch ( url , init ) ;
251- } catch ( error ) {
252- lastError = error ;
253- }
254- }
255-
256- throw lastError ;
257- }
258-
259257/** @public Service construction is part of the canonical Effect module API. */
260258export const make = Effect . gen ( function * ( ) {
261259 const registered = yield * Ref . make ( false ) ;
@@ -278,7 +276,14 @@ export const make = Effect.gen(function* () {
278276 contentSecurityPolicy ,
279277 ) ;
280278 }
281- return proxyRequest ( request , input . targetOrigin , contentSecurityPolicy ) ;
279+ // Reject with net.fetch's own error, as an unproxied fetch would.
280+ return runPromise (
281+ proxyRequest ( request , input . targetOrigin , contentSecurityPolicy ) . pipe (
282+ Effect . catchTags ( {
283+ ElectronProtocolFetchError : ( error ) => Effect . die ( error . cause ) ,
284+ } ) ,
285+ ) ,
286+ ) ;
282287 } ) ;
283288 } ,
284289 catch : ( cause ) => new ElectronProtocolRegistrationError ( { scheme : input . scheme , cause } ) ,
0 commit comments