@ysgroup/core 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ysgroup/core",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
package/src/index.ts CHANGED
@@ -2,11 +2,4 @@
2
2
 
3
3
  export { YsWsClient, type Envelope } from "./ws";
4
4
  export { useYsStore } from "./store";
5
- export {
6
- YsRest,
7
- YsRestError,
8
- type DeleteBlocker,
9
- type DeleteImpact,
10
- type RevealedCredential,
11
- type TopologyNode,
12
- } from "./rest";
5
+ export { YsRest, YsRestError, type YsAdminRpcCall, type DeleteBlocker, type DeleteImpact, type RevealedCredential, type TopologyNode } from "./rest";
package/src/rest.ts CHANGED
@@ -1,4 +1,13 @@
1
- /* @ysgroup/core —— REST 客户端(管理接口,带 JWT)。 */
1
+ /* @ysgroup/core —— 管理接口客户端(支持 HTTP 或统一 WS RPC 传输)。 */
2
+
3
+ export type YsAdminRpcCall = (data: {
4
+ method: string;
5
+ path: string;
6
+ access_token: string;
7
+ body?: unknown;
8
+ upload?: { name: string; content_type: string; base64: string };
9
+ user_agent: string;
10
+ }) => Promise<unknown>;
2
11
 
3
12
  export class YsRestError extends Error {
4
13
  constructor(
@@ -33,9 +42,23 @@ export class YsRest {
33
42
  constructor(
34
43
  private baseUrl: string,
35
44
  private getToken: () => string,
45
+ private rpcCall?: YsAdminRpcCall,
36
46
  ) {}
37
47
 
38
48
  private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
49
+ if (this.rpcCall) {
50
+ try {
51
+ return (await this.rpcCall({
52
+ method,
53
+ path,
54
+ access_token: this.getToken(),
55
+ body,
56
+ user_agent: navigator.userAgent,
57
+ })) as T;
58
+ } catch (error) {
59
+ throw rpcError(error);
60
+ }
61
+ }
39
62
  const resp = await fetch(`${this.baseUrl}${path}`, {
40
63
  method,
41
64
  headers: {
@@ -59,6 +82,23 @@ export class YsRest {
59
82
  }
60
83
 
61
84
  private async upload<T>(path: string, file: File): Promise<T> {
85
+ if (this.rpcCall) {
86
+ try {
87
+ return (await this.rpcCall({
88
+ method: "POST",
89
+ path,
90
+ access_token: this.getToken(),
91
+ upload: {
92
+ name: file.name,
93
+ content_type: file.type || "application/octet-stream",
94
+ base64: await fileToBase64(file),
95
+ },
96
+ user_agent: navigator.userAgent,
97
+ })) as T;
98
+ } catch (error) {
99
+ throw rpcError(error);
100
+ }
101
+ }
62
102
  const form = new FormData();
63
103
  form.append("upload", file);
64
104
  const resp = await fetch(`${this.baseUrl}${path}`, {
@@ -73,6 +113,10 @@ export class YsRest {
73
113
  return (await resp.json()) as T;
74
114
  }
75
115
 
116
+ raw<T>(method: string, path: string, body?: unknown) {
117
+ return this.request<T>(method, path, body);
118
+ }
119
+
76
120
  list<T = Record<string, unknown>>(table: string) {
77
121
  return this.request<T[]>("GET", `/api/tables/${table}`);
78
122
  }
@@ -199,7 +243,14 @@ export class YsRest {
199
243
  listOwnerFiles(ownerId: string) {
200
244
  return this.request<Record<string, unknown>[]>("GET", `/api/owners/${ownerId}/files`);
201
245
  }
202
- downloadFile(fileId: string) {
246
+ async downloadFile(fileId: string) {
247
+ if (this.rpcCall) {
248
+ const result = await this.request<{
249
+ base64: string;
250
+ content_type: string;
251
+ }>("GET", `/api/files/${fileId}/download`);
252
+ return base64ToBlob(result.base64, result.content_type);
253
+ }
203
254
  return fetch(`${this.baseUrl}/api/files/${fileId}/download`, {
204
255
  headers: { Authorization: `Bearer ${this.getToken()}` },
205
256
  }).then(async (resp) => {
@@ -212,6 +263,35 @@ export class YsRest {
212
263
  }
213
264
  }
214
265
 
266
+ function rpcError(error: unknown): YsRestError {
267
+ const message = String(error).replace(/^Error:\s*/, "");
268
+ const match = message.match(/^\[(\d+)\]\s*(.*)$/s);
269
+ if (!match) return new YsRestError(500, message);
270
+ let detail: unknown = match[2];
271
+ try {
272
+ detail = JSON.parse(match[2]);
273
+ } catch {
274
+ // 普通文本错误保持原文。
275
+ }
276
+ return new YsRestError(Number(match[1]), detail);
277
+ }
278
+
279
+ function fileToBase64(file: File): Promise<string> {
280
+ return new Promise((resolve, reject) => {
281
+ const reader = new FileReader();
282
+ reader.onerror = () => reject(reader.error ?? new Error("读取上传文件失败"));
283
+ reader.onload = () => resolve(String(reader.result).split(",", 2)[1] ?? "");
284
+ reader.readAsDataURL(file);
285
+ });
286
+ }
287
+
288
+ function base64ToBlob(value: string, contentType: string): Blob {
289
+ const binary = atob(value);
290
+ const bytes = new Uint8Array(binary.length);
291
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
292
+ return new Blob([bytes], { type: contentType });
293
+ }
294
+
215
295
  export interface TopologyNode {
216
296
  name: string;
217
297
  title: string;
package/src/store.ts CHANGED
@@ -139,7 +139,9 @@ export const useYsStore = defineStore("ys", () => {
139
139
  if (!ws || !accessToken.value) throw new Error("未登录");
140
140
  if (
141
141
  String(user.value?.role ?? "") === "Root" ||
142
- String(user.value?.username ?? "").trim().toLowerCase() === "root"
142
+ String(user.value?.username ?? "")
143
+ .trim()
144
+ .toLowerCase() === "root"
143
145
  ) {
144
146
  throw new Error("Root 不允许修改登录密码");
145
147
  }
@@ -196,6 +198,11 @@ export const useYsStore = defineStore("ys", () => {
196
198
  clearSession();
197
199
  }
198
200
 
201
+ function forceLogout(message = "") {
202
+ clearSession();
203
+ sessionMessage.value = message;
204
+ }
205
+
199
206
  function clearSession() {
200
207
  user.value = null;
201
208
  accessToken.value = "";
@@ -207,8 +214,22 @@ export const useYsStore = defineStore("ys", () => {
207
214
  }
208
215
 
209
216
  return {
210
- user, accessToken, refreshToken, connected, isLoggedIn, sessionMessage, tables,
211
- tableList, connect, login, loginByCode, waitUntilConnected, changeMyPassword, renew, logout,
217
+ user,
218
+ accessToken,
219
+ refreshToken,
220
+ connected,
221
+ isLoggedIn,
222
+ sessionMessage,
223
+ tables,
224
+ tableList,
225
+ connect,
226
+ login,
227
+ loginByCode,
228
+ waitUntilConnected,
229
+ changeMyPassword,
230
+ renew,
231
+ logout,
232
+ forceLogout,
212
233
  client: () => ws,
213
234
  };
214
235
  });