-
Notifications
You must be signed in to change notification settings - Fork 60.3k
Expand file tree
/
Copy pathupstash.ts
More file actions
169 lines (147 loc) · 4.85 KB
/
upstash.ts
File metadata and controls
169 lines (147 loc) · 4.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import { STORAGE_KEY } from "@/app/constant";
import { SyncStore } from "@/app/store/sync";
import { chunks } from "../format";
export type UpstashConfig = SyncStore["upstash"];
export type UpStashClient = ReturnType<typeof createUpstashClient>;
async function httpFetch(
url: string,
options?: RequestInit,
): Promise<Response> {
if (window.__TAURI__) {
// 转换 RequestInit 格式为 Tauri 期望的格式
const method = options?.method || "GET";
const headers: Record<string, string> = {};
// 处理 headers
if (options?.headers) {
if (options.headers instanceof Headers) {
options.headers.forEach((value, key) => {
headers[key] = value;
});
} else if (Array.isArray(options.headers)) {
options.headers.forEach(([key, value]) => {
headers[key] = value;
});
} else {
Object.assign(headers, options.headers);
}
}
// 处理 body
let body: number[] = [];
if (options?.body) {
if (typeof options.body === "string") {
body = Array.from(new TextEncoder().encode(options.body));
} else if (options.body instanceof ArrayBuffer) {
body = Array.from(new Uint8Array(options.body));
} else if (options.body instanceof Uint8Array) {
body = Array.from(options.body);
} else {
// 其他类型转换为字符串
body = Array.from(new TextEncoder().encode(String(options.body)));
}
}
const response = await window.__TAURI__.invoke("http_fetch", {
method,
url,
headers,
body,
});
// 将 Tauri 响应转换为 Response 对象格式
return new Response(new Uint8Array(response.body), {
status: response.status,
statusText: response.status_text,
headers: new Headers(response.headers),
});
}
return fetch(url, options);
}
export function createUpstashClient(store: SyncStore) {
const config = store.upstash;
const storeKey = config.username.length === 0 ? STORAGE_KEY : config.username;
const chunkCountKey = `${storeKey}-chunk-count`;
const chunkIndexKey = (i: number) => `${storeKey}-chunk-${i}`;
const proxyUrl =
store.useProxy && store.proxyUrl.length > 0 ? store.proxyUrl : undefined;
return {
async check() {
try {
const res = await httpFetch(this.path(`get/${storeKey}`, proxyUrl), {
method: "GET",
headers: this.headers(),
});
console.log("[Upstash] check", res.status, res.statusText);
return [200].includes(res.status);
} catch (e) {
console.error("[Upstash] failed to check", e);
}
return false;
},
async redisGet(key: string) {
const res = await httpFetch(this.path(`get/${key}`, proxyUrl), {
method: "GET",
headers: this.headers(),
});
console.log("[Upstash] get key = ", key, res.status, res.statusText);
const resJson = (await res.json()) as { result: string };
return resJson.result;
},
async redisSet(key: string, value: string) {
const res = await httpFetch(this.path(`set/${key}`, proxyUrl), {
method: "POST",
headers: this.headers(),
body: value,
});
console.log("[Upstash] set key = ", key, res.status, res.statusText);
},
async get() {
const chunkCount = Number(await this.redisGet(chunkCountKey));
if (!Number.isInteger(chunkCount)) return;
const chunks = await Promise.all(
new Array(chunkCount)
.fill(0)
.map((_, i) => this.redisGet(chunkIndexKey(i))),
);
console.log("[Upstash] get full chunks", chunks);
return chunks.join("");
},
async set(_: string, value: string) {
// upstash limit the max request size which is 1Mb for “Free” and “Pay as you go”
// so we need to split the data to chunks
let index = 0;
for await (const chunk of chunks(value)) {
await this.redisSet(chunkIndexKey(index), chunk);
index += 1;
}
await this.redisSet(chunkCountKey, index.toString());
},
headers() {
return {
Authorization: `Bearer ${config.apiKey}`,
};
},
path(path: string, proxyUrl: string = "") {
if (window.__TAURI__) {
return config.endpoint + "/" + path;
}
if (!path.endsWith("/")) {
path += "/";
}
if (path.startsWith("/")) {
path = path.slice(1);
}
if (proxyUrl.length > 0 && !proxyUrl.endsWith("/")) {
proxyUrl += "/";
}
let url;
const pathPrefix = "/api/upstash/";
try {
let u = new URL(proxyUrl + pathPrefix + path, window.location.origin);
// add query params
u.searchParams.append("endpoint", config.endpoint);
url = u.toString();
} catch (e) {
url = pathPrefix + path + "?endpoint=" + config.endpoint;
}
return url;
},
};
}