Skip to content

Commit abfc2cf

Browse files
committed
feat(s3-backend): 添加桌面平台专用的请求处理器以绕过CORS
1 parent 786214a commit abfc2cf

1 file changed

Lines changed: 94 additions & 3 deletions

File tree

packages/core/src/sync/s3-backend.ts

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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";
1518
import 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

Comments
 (0)