@@ -12,8 +12,85 @@ import {
1212 PutObjectCommand ,
1313 S3Client ,
1414} from "@aws-sdk/client-s3" ;
15+ import { HttpResponse } from "@smithy/protocol-http" ;
16+ import { buildQueryString } from "@smithy/querystring-builder" ;
17+ import { getPlatformService } from "../services/platform" ;
1518import type { ISyncBackend , RemoteFile , S3Config } from "./sync-backend" ;
1619
20+ type SmithyHttpRequest = {
21+ protocol : string ;
22+ hostname : string ;
23+ port ?: number ;
24+ method : string ;
25+ path : string ;
26+ query ?: Record < string , string | string [ ] | null > ;
27+ fragment ?: string ;
28+ username ?: string ;
29+ password ?: string ;
30+ headers : Record < string , string > ;
31+ body ?: BodyInit | null ;
32+ } ;
33+
34+ /**
35+ * Desktop-only request handler that routes AWS SDK traffic through the platform
36+ * fetch implementation. In Tauri this uses plugin-http, which avoids webview
37+ * CORS restrictions for S3-compatible providers like UpYun.
38+ */
39+ class PlatformFetchHttpHandler {
40+ readonly metadata = { handlerProtocol : "h1" } as const ;
41+
42+ async handle ( request : SmithyHttpRequest ) : Promise < { response : HttpResponse } > {
43+ const platform = getPlatformService ( ) ;
44+ let path = request . path ;
45+ const queryString = buildQueryString ( request . query ?? { } ) ;
46+ if ( queryString ) {
47+ path += `?${ queryString } ` ;
48+ }
49+ if ( request . fragment ) {
50+ path += `#${ request . fragment } ` ;
51+ }
52+
53+ let auth = "" ;
54+ if ( request . username != null || request . password != null ) {
55+ const username = request . username ?? "" ;
56+ const password = request . password ?? "" ;
57+ auth = `${ username } :${ password } @` ;
58+ }
59+
60+ const url = `${ request . protocol } //${ auth } ${ request . hostname } ${ request . port ? `:${ request . port } ` : "" } ${ path } ` ;
61+ const response = await platform . fetch ( url , {
62+ method : request . method ,
63+ headers : request . headers ,
64+ body : request . method === "GET" || request . method === "HEAD" ? undefined : ( request . body ?? undefined ) ,
65+ } ) ;
66+
67+ const transformedHeaders : Record < string , string > = { } ;
68+ response . headers . forEach ( ( value , key ) => {
69+ transformedHeaders [ key ] = value ;
70+ } ) ;
71+
72+ let responseBody : BodyInit | ReadableStream < Uint8Array > | undefined ;
73+ if ( response . body ) {
74+ responseBody = response . body as ReadableStream < Uint8Array > ;
75+ } else {
76+ responseBody = await response . blob ( ) ;
77+ }
78+
79+ return {
80+ response : new HttpResponse ( {
81+ headers : transformedHeaders ,
82+ reason : response . statusText ,
83+ statusCode : response . status ,
84+ body : responseBody ,
85+ } ) ,
86+ } ;
87+ }
88+
89+ destroy ( ) : void {
90+ // No-op: platform fetch does not keep persistent sockets we need to tear down.
91+ }
92+ }
93+
1794/**
1895 * S3 backend implementation.
1996 * Works with any S3-compatible storage service.
@@ -25,15 +102,28 @@ export class S3Backend implements ISyncBackend {
25102
26103 constructor ( config : S3Config , secretAccessKey : string ) {
27104 this . config = config ;
28- this . client = new S3Client ( {
105+ let requestHandler : PlatformFetchHttpHandler | undefined ;
106+ try {
107+ const platform = getPlatformService ( ) ;
108+ if ( platform . isDesktop ) {
109+ requestHandler = new PlatformFetchHttpHandler ( ) ;
110+ }
111+ } catch {
112+ // Platform service may not be initialized in tests that never touch S3.
113+ }
114+
115+ const clientConfig = {
29116 endpoint : config . endpoint ,
30117 region : config . region ,
31118 credentials : {
32119 accessKeyId : config . accessKeyId ,
33120 secretAccessKey,
34121 } ,
35122 forcePathStyle : config . pathStyle ?? false ,
36- } ) ;
123+ ...( requestHandler ? { requestHandler } : { } ) ,
124+ } ;
125+
126+ this . client = new S3Client ( clientConfig ) ;
37127 }
38128
39129 async testConnection ( ) : Promise < boolean > {
@@ -45,7 +135,8 @@ export class S3Backend implements ISyncBackend {
45135 } ) ,
46136 ) ;
47137 return true ;
48- } catch {
138+ } catch ( error ) {
139+ console . error ( "[S3Backend] testConnection failed:" , error ) ;
49140 return false ;
50141 }
51142 }
0 commit comments