@pandablessing/dsh-user-sync 1.0.14 → 1.0.16
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/lib/client.js +1 -1
- package/lib/index.js +8 -5
- package/lib/transport/api.js +175 -175
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -510,7 +510,7 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
|
|
|
510
510
|
? R.createElement('div', { style: styles.card }, R.createElement('strong', null, '启用前请确认'), R.createElement('p', { style: styles.muted }, '上传内容:技能、插件清单、配置、用户记忆。数据经运营方服务中转并存放在运营方账号(非 DSH 官方服务器);远端只保留 1 份,上传即覆盖。'), R.createElement('div', { style: styles.actions },
|
|
511
511
|
// 用普通按钮:主色按钮看起来像「已选中」,同意是主动作但不应暗示状态
|
|
512
512
|
R.createElement('button', { style: styles.secondary, disabled: busy, onClick: acceptConsent }, '同意并启用上传')))
|
|
513
|
-
: null, R.createElement('div', { style: styles.card }, R.createElement('div', { style: styles.row }, R.createElement('span', null, 'DSH 主目录'), R.createElement('code', null, status?.dshHome ?? '…')), R.createElement('div', { style: styles.row }, R.createElement('span', null, 'Profile'), R.createElement('span', null, status?.profile ?? '…')), R.createElement('div', { style: styles.row }, R.createElement('span', null, '账号'), R.createElement('span', null, status?.account?.hint ? `已配对 · ${status.account.hint}` : '未登录官方账号')), R.createElement('div', { style: styles.row }, R.createElement('span', null, '
|
|
513
|
+
: null, R.createElement('div', { style: styles.card }, R.createElement('div', { style: styles.row }, R.createElement('span', null, 'DSH 主目录'), R.createElement('code', null, status?.dshHome ?? '…')), R.createElement('div', { style: styles.row }, R.createElement('span', null, 'Profile'), R.createElement('span', null, status?.profile ?? '…')), R.createElement('div', { style: styles.row }, R.createElement('span', null, '账号'), R.createElement('span', null, status?.account?.hint ? `已配对 · ${status.account.hint}` : '未登录官方账号')), R.createElement('div', { style: styles.row }, R.createElement('span', null, '版本'), R.createElement('span', null, status?.version ? `v${status.version}` : '加载中…')), R.createElement('div', { style: styles.row }, R.createElement('span', null, '接口'), R.createElement('span', { style: status?.error ? styles.error : undefined }, status?.error ? `错误:${status.error}${status.detail ? `(${status.detail})` : ''}` : status?.installed ? '正常' : '加载中…')), R.createElement('div', { style: styles.row }, R.createElement('span', null, '扫描'), R.createElement('span', { style: scan?.error ? styles.error : undefined }, scan?.error
|
|
514
514
|
? `失败:${scan.error}${scan.detail ? `:${String(scan.detail).slice(0, 160)}` : ''}`
|
|
515
515
|
: `已选 ${scan?.selectedFiles ?? scan?.totalFiles ?? 0} 个文件 · ${fmtBytes(scan?.selectedBytes ?? scan?.totalBytes ?? 0)}(同步范围 ${scan?.syncableFiles ?? scan?.totalFiles ?? 0} 个 · ${fmtBytes(scan?.syncableBytes ?? scan?.totalBytes ?? 0)})`))), R.createElement('div', { style: styles.card }, R.createElement('strong', null, '同步范围'), (scan?.selectedBytes ?? 0) > 10 * 1024 * 1024
|
|
516
516
|
? R.createElement('p', { style: styles.warn }, `预计 ${fmtBytes(scan?.selectedBytes)},超过 10 MiB 单包上限;上传会被拒绝,请取消「会话」或减少同步范围。`)
|
package/lib/index.js
CHANGED
|
@@ -410,14 +410,11 @@ export function apply(ctx) {
|
|
|
410
410
|
},
|
|
411
411
|
},
|
|
412
412
|
{
|
|
413
|
-
//
|
|
413
|
+
// 一键更新:在 profile 里用 pnpm add 真实更新安装包(installBundle 只登记 bundle、不改安装包)
|
|
414
414
|
path: '/api/dsh-user-sync/update',
|
|
415
415
|
methods: ['POST'],
|
|
416
416
|
requestBody: 'buffered',
|
|
417
417
|
fetch: async (request) => {
|
|
418
|
-
const pm = pluginManager;
|
|
419
|
-
if (!pm)
|
|
420
|
-
return json({ ok: false, error: 'E_NO_PLUGIN_MANAGER' }, 503);
|
|
421
418
|
let version = '';
|
|
422
419
|
try {
|
|
423
420
|
const body = (await request.json());
|
|
@@ -430,7 +427,13 @@ export function apply(ctx) {
|
|
|
430
427
|
return json({ ok: false, error: 'E_BAD_VERSION' }, 400);
|
|
431
428
|
const spec = `@pandablessing/dsh-user-sync@${version}`;
|
|
432
429
|
try {
|
|
433
|
-
|
|
430
|
+
const rt = pnpmRuntime();
|
|
431
|
+
if (!rt)
|
|
432
|
+
return json({ ok: false, error: 'E_NO_PNPM' }, 503);
|
|
433
|
+
await runCommand(rt.node, [rt.pnpmCjs, 'add', spec], {
|
|
434
|
+
cwd: paths.profileDir,
|
|
435
|
+
timeout: 240_000,
|
|
436
|
+
});
|
|
434
437
|
}
|
|
435
438
|
catch (error) {
|
|
436
439
|
recordHandlerError('update', error);
|
package/lib/transport/api.js
CHANGED
|
@@ -1,175 +1,175 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { dirname } from 'node:path';
|
|
3
|
-
import { isPackageId } from '../archive.js';
|
|
4
|
-
const OFFICIAL_HOST = /(^|\.)deepseek\.com$/i;
|
|
5
|
-
export class ApiError extends Error {
|
|
6
|
-
code;
|
|
7
|
-
status;
|
|
8
|
-
constructor(code, message, status) {
|
|
9
|
-
super(message);
|
|
10
|
-
this.code = code;
|
|
11
|
-
this.status = status;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* 运营方 API 传输(B 模型)。插件侧只持有用户自己的 token,不接触存储凭据。
|
|
16
|
-
* 认证:register 首次建账号,login 取回同一 token;token 存 auth.json(0600)。
|
|
17
|
-
*/
|
|
18
|
-
export function createApiTransport({ baseUrl, authPath, token: initialToken, fetchImpl = globalThis.fetch, timeoutMs = 30_000, retries = 3, }) {
|
|
19
|
-
if (!baseUrl)
|
|
20
|
-
throw new Error('缺少 baseUrl');
|
|
21
|
-
const host = new URL(baseUrl).hostname;
|
|
22
|
-
if (OFFICIAL_HOST.test(host)) {
|
|
23
|
-
throw new ApiError('E_OFFICIAL_HOST', `拒绝官方域名: ${host}`);
|
|
24
|
-
}
|
|
25
|
-
let token = initialToken ?? null;
|
|
26
|
-
if (!token && authPath && existsSync(authPath)) {
|
|
27
|
-
try {
|
|
28
|
-
token = JSON.parse(readFileSync(authPath, 'utf8')).token ?? null;
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
token = null;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
function persistAuth(next) {
|
|
35
|
-
token = next.token;
|
|
36
|
-
if (!authPath)
|
|
37
|
-
return;
|
|
38
|
-
mkdirSync(dirname(authPath), { recursive: true });
|
|
39
|
-
writeFileSync(authPath, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
|
|
40
|
-
}
|
|
41
|
-
function clearAuth() {
|
|
42
|
-
token = null;
|
|
43
|
-
if (authPath)
|
|
44
|
-
rmSync(authPath, { force: true });
|
|
45
|
-
}
|
|
46
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
47
|
-
async function request(method, path, { body, headers = {}, expectBytes = false } = {}) {
|
|
48
|
-
const url = `${baseUrl.replace(/\/$/, '')}${path}`;
|
|
49
|
-
let lastError;
|
|
50
|
-
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
51
|
-
try {
|
|
52
|
-
const response = await fetchImpl(url, {
|
|
53
|
-
method,
|
|
54
|
-
headers: {
|
|
55
|
-
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
56
|
-
...(body && !expectBytes ? { 'content-type': 'application/json' } : {}),
|
|
57
|
-
...headers,
|
|
58
|
-
},
|
|
59
|
-
body: body,
|
|
60
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
61
|
-
});
|
|
62
|
-
if (response.status === 401 || response.status === 403) {
|
|
63
|
-
throw new ApiError('E_REMOTE_AUTH', `认证失败(${response.status})`, response.status);
|
|
64
|
-
}
|
|
65
|
-
if (response.status >= 400 && response.status < 500) {
|
|
66
|
-
const detail = await response.text().catch(() => '');
|
|
67
|
-
throw new ApiError(`E_HTTP_${response.status}`, detail.slice(0, 200) || `HTTP ${response.status}`, response.status);
|
|
68
|
-
}
|
|
69
|
-
if (!response.ok) {
|
|
70
|
-
throw new ApiError('E_REMOTE_5XX', `服务端错误(${response.status})`, response.status);
|
|
71
|
-
}
|
|
72
|
-
return expectBytes ? Buffer.from(await response.arrayBuffer()) : response;
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
lastError = error;
|
|
76
|
-
const retryable = !(error instanceof ApiError) || error.code === 'E_REMOTE_5XX';
|
|
77
|
-
if (!retryable || attempt === retries)
|
|
78
|
-
throw error;
|
|
79
|
-
await sleep(200 * 2 ** attempt);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
throw lastError;
|
|
83
|
-
}
|
|
84
|
-
const parseJson = async (response) => {
|
|
85
|
-
const text = await response.text();
|
|
86
|
-
return text ? JSON.parse(text) : null;
|
|
87
|
-
};
|
|
88
|
-
/**
|
|
89
|
-
* 用同一 accountKey 取回同一个 token(不签发新 token)。
|
|
90
|
-
* 抽成独立函数:register 里改走 login 时不依赖 `this`,解构调用也不会炸。
|
|
91
|
-
*/
|
|
92
|
-
async function loginWith(accountKey) {
|
|
93
|
-
const response = await request('POST', '/v1/auth/login', { body: JSON.stringify({ accountKey }) });
|
|
94
|
-
const data = await parseJson(response);
|
|
95
|
-
const token = String(data?.token ?? '');
|
|
96
|
-
persistAuth({ token, accountKeyHint: accountKey.slice(0, 6), registeredAt: new Date().toISOString() });
|
|
97
|
-
return { ok: true, token };
|
|
98
|
-
}
|
|
99
|
-
return {
|
|
100
|
-
id: 'api',
|
|
101
|
-
baseUrl,
|
|
102
|
-
get token() {
|
|
103
|
-
return token;
|
|
104
|
-
},
|
|
105
|
-
/** 首次注册;已存在(409)时改走 login,取回同一个 token。 */
|
|
106
|
-
async register(accountKey) {
|
|
107
|
-
try {
|
|
108
|
-
const response = await request('POST', '/v1/auth/register', { body: JSON.stringify({ accountKey }) });
|
|
109
|
-
const data = await parseJson(response);
|
|
110
|
-
const token = String(data?.token ?? '');
|
|
111
|
-
persistAuth({ token, accountKeyHint: accountKey.slice(0, 6), registeredAt: new Date().toISOString() });
|
|
112
|
-
return { ok: true, token };
|
|
113
|
-
}
|
|
114
|
-
catch (error) {
|
|
115
|
-
if (error.code === 'E_HTTP_409')
|
|
116
|
-
return loginWith(accountKey);
|
|
117
|
-
throw error;
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
login: loginWith,
|
|
121
|
-
async reset() {
|
|
122
|
-
await request('POST', '/v1/auth/reset');
|
|
123
|
-
clearAuth();
|
|
124
|
-
return { ok: true };
|
|
125
|
-
},
|
|
126
|
-
async test() {
|
|
127
|
-
try {
|
|
128
|
-
const response = await request('GET', '/v1/quota');
|
|
129
|
-
const data = await parseJson(response);
|
|
130
|
-
return { ok: true, detail: `配额 ${data?.used ?? 0}/${data?.limit ?? 0} 字节` };
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
return { ok: false, detail: error.message };
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
async list() {
|
|
137
|
-
const response = await request('GET', '/v1/packages');
|
|
138
|
-
const data = await parseJson(response);
|
|
139
|
-
return (data?.packages ?? []).map((item) => ({
|
|
140
|
-
name: item.id,
|
|
141
|
-
size: item.size,
|
|
142
|
-
modifiedAt: item.createdAt,
|
|
143
|
-
}));
|
|
144
|
-
},
|
|
145
|
-
async meta(id) {
|
|
146
|
-
if (!isPackageId(id))
|
|
147
|
-
throw new ApiError('E_BAD_NAME', `非法包名: ${id}`);
|
|
148
|
-
const response = await request('GET', `/v1/packages/${encodeURIComponent(id)}/meta`);
|
|
149
|
-
return parseJson(response);
|
|
150
|
-
},
|
|
151
|
-
async get(id) {
|
|
152
|
-
if (!isPackageId(id))
|
|
153
|
-
throw new ApiError('E_BAD_NAME', `非法包名: ${id}`);
|
|
154
|
-
return request('GET', `/v1/packages/${encodeURIComponent(id)}`, { expectBytes: true });
|
|
155
|
-
},
|
|
156
|
-
async put(id, data) {
|
|
157
|
-
if (!isPackageId(id))
|
|
158
|
-
throw new ApiError('E_BAD_NAME', `非法包名: ${id}`);
|
|
159
|
-
await request('PUT', `/v1/packages/${encodeURIComponent(id)}`, {
|
|
160
|
-
body: data,
|
|
161
|
-
expectBytes: true,
|
|
162
|
-
headers: { 'content-type': 'application/octet-stream' },
|
|
163
|
-
});
|
|
164
|
-
},
|
|
165
|
-
async remove(id) {
|
|
166
|
-
if (!isPackageId(id))
|
|
167
|
-
throw new ApiError('E_BAD_NAME', `非法包名: ${id}`);
|
|
168
|
-
await request('DELETE', `/v1/packages/${encodeURIComponent(id)}`);
|
|
169
|
-
},
|
|
170
|
-
/** 导出我的全部数据(服务端打包当前包 + 元数据)。 */
|
|
171
|
-
async exportPackage() {
|
|
172
|
-
return request('GET', '/v1/export', { expectBytes: true });
|
|
173
|
-
},
|
|
174
|
-
};
|
|
175
|
-
}
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { isPackageId } from '../archive.js';
|
|
4
|
+
const OFFICIAL_HOST = /(^|\.)deepseek\.com$/i;
|
|
5
|
+
export class ApiError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
status;
|
|
8
|
+
constructor(code, message, status) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.status = status;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 运营方 API 传输(B 模型)。插件侧只持有用户自己的 token,不接触存储凭据。
|
|
16
|
+
* 认证:register 首次建账号,login 取回同一 token;token 存 auth.json(0600)。
|
|
17
|
+
*/
|
|
18
|
+
export function createApiTransport({ baseUrl, authPath, token: initialToken, fetchImpl = globalThis.fetch, timeoutMs = 30_000, retries = 3, }) {
|
|
19
|
+
if (!baseUrl)
|
|
20
|
+
throw new Error('缺少 baseUrl');
|
|
21
|
+
const host = new URL(baseUrl).hostname;
|
|
22
|
+
if (OFFICIAL_HOST.test(host)) {
|
|
23
|
+
throw new ApiError('E_OFFICIAL_HOST', `拒绝官方域名: ${host}`);
|
|
24
|
+
}
|
|
25
|
+
let token = initialToken ?? null;
|
|
26
|
+
if (!token && authPath && existsSync(authPath)) {
|
|
27
|
+
try {
|
|
28
|
+
token = JSON.parse(readFileSync(authPath, 'utf8')).token ?? null;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
token = null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function persistAuth(next) {
|
|
35
|
+
token = next.token;
|
|
36
|
+
if (!authPath)
|
|
37
|
+
return;
|
|
38
|
+
mkdirSync(dirname(authPath), { recursive: true });
|
|
39
|
+
writeFileSync(authPath, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
|
|
40
|
+
}
|
|
41
|
+
function clearAuth() {
|
|
42
|
+
token = null;
|
|
43
|
+
if (authPath)
|
|
44
|
+
rmSync(authPath, { force: true });
|
|
45
|
+
}
|
|
46
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
47
|
+
async function request(method, path, { body, headers = {}, expectBytes = false } = {}) {
|
|
48
|
+
const url = `${baseUrl.replace(/\/$/, '')}${path}`;
|
|
49
|
+
let lastError;
|
|
50
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetchImpl(url, {
|
|
53
|
+
method,
|
|
54
|
+
headers: {
|
|
55
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
56
|
+
...(body && !expectBytes ? { 'content-type': 'application/json' } : {}),
|
|
57
|
+
...headers,
|
|
58
|
+
},
|
|
59
|
+
body: body,
|
|
60
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
61
|
+
});
|
|
62
|
+
if (response.status === 401 || response.status === 403) {
|
|
63
|
+
throw new ApiError('E_REMOTE_AUTH', `认证失败(${response.status})`, response.status);
|
|
64
|
+
}
|
|
65
|
+
if (response.status >= 400 && response.status < 500) {
|
|
66
|
+
const detail = await response.text().catch(() => '');
|
|
67
|
+
throw new ApiError(`E_HTTP_${response.status}`, detail.slice(0, 200) || `HTTP ${response.status}`, response.status);
|
|
68
|
+
}
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
throw new ApiError('E_REMOTE_5XX', `服务端错误(${response.status})`, response.status);
|
|
71
|
+
}
|
|
72
|
+
return expectBytes ? Buffer.from(await response.arrayBuffer()) : response;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
lastError = error;
|
|
76
|
+
const retryable = !(error instanceof ApiError) || error.code === 'E_REMOTE_5XX';
|
|
77
|
+
if (!retryable || attempt === retries)
|
|
78
|
+
throw error;
|
|
79
|
+
await sleep(200 * 2 ** attempt);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw lastError;
|
|
83
|
+
}
|
|
84
|
+
const parseJson = async (response) => {
|
|
85
|
+
const text = await response.text();
|
|
86
|
+
return text ? JSON.parse(text) : null;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* 用同一 accountKey 取回同一个 token(不签发新 token)。
|
|
90
|
+
* 抽成独立函数:register 里改走 login 时不依赖 `this`,解构调用也不会炸。
|
|
91
|
+
*/
|
|
92
|
+
async function loginWith(accountKey) {
|
|
93
|
+
const response = await request('POST', '/v1/auth/login', { body: JSON.stringify({ accountKey }) });
|
|
94
|
+
const data = await parseJson(response);
|
|
95
|
+
const token = String(data?.token ?? '');
|
|
96
|
+
persistAuth({ token, accountKeyHint: accountKey.slice(0, 6), registeredAt: new Date().toISOString() });
|
|
97
|
+
return { ok: true, token };
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
id: 'api',
|
|
101
|
+
baseUrl,
|
|
102
|
+
get token() {
|
|
103
|
+
return token;
|
|
104
|
+
},
|
|
105
|
+
/** 首次注册;已存在(409)时改走 login,取回同一个 token。 */
|
|
106
|
+
async register(accountKey) {
|
|
107
|
+
try {
|
|
108
|
+
const response = await request('POST', '/v1/auth/register', { body: JSON.stringify({ accountKey }) });
|
|
109
|
+
const data = await parseJson(response);
|
|
110
|
+
const token = String(data?.token ?? '');
|
|
111
|
+
persistAuth({ token, accountKeyHint: accountKey.slice(0, 6), registeredAt: new Date().toISOString() });
|
|
112
|
+
return { ok: true, token };
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (error.code === 'E_HTTP_409')
|
|
116
|
+
return loginWith(accountKey);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
login: loginWith,
|
|
121
|
+
async reset() {
|
|
122
|
+
await request('POST', '/v1/auth/reset');
|
|
123
|
+
clearAuth();
|
|
124
|
+
return { ok: true };
|
|
125
|
+
},
|
|
126
|
+
async test() {
|
|
127
|
+
try {
|
|
128
|
+
const response = await request('GET', '/v1/quota');
|
|
129
|
+
const data = await parseJson(response);
|
|
130
|
+
return { ok: true, detail: `配额 ${data?.used ?? 0}/${data?.limit ?? 0} 字节` };
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
return { ok: false, detail: error.message };
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
async list() {
|
|
137
|
+
const response = await request('GET', '/v1/packages');
|
|
138
|
+
const data = await parseJson(response);
|
|
139
|
+
return (data?.packages ?? []).map((item) => ({
|
|
140
|
+
name: item.id,
|
|
141
|
+
size: item.size,
|
|
142
|
+
modifiedAt: item.createdAt,
|
|
143
|
+
}));
|
|
144
|
+
},
|
|
145
|
+
async meta(id) {
|
|
146
|
+
if (!isPackageId(id))
|
|
147
|
+
throw new ApiError('E_BAD_NAME', `非法包名: ${id}`);
|
|
148
|
+
const response = await request('GET', `/v1/packages/${encodeURIComponent(id)}/meta`);
|
|
149
|
+
return parseJson(response);
|
|
150
|
+
},
|
|
151
|
+
async get(id) {
|
|
152
|
+
if (!isPackageId(id))
|
|
153
|
+
throw new ApiError('E_BAD_NAME', `非法包名: ${id}`);
|
|
154
|
+
return request('GET', `/v1/packages/${encodeURIComponent(id)}`, { expectBytes: true });
|
|
155
|
+
},
|
|
156
|
+
async put(id, data) {
|
|
157
|
+
if (!isPackageId(id))
|
|
158
|
+
throw new ApiError('E_BAD_NAME', `非法包名: ${id}`);
|
|
159
|
+
await request('PUT', `/v1/packages/${encodeURIComponent(id)}`, {
|
|
160
|
+
body: data,
|
|
161
|
+
expectBytes: true,
|
|
162
|
+
headers: { 'content-type': 'application/octet-stream' },
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
async remove(id) {
|
|
166
|
+
if (!isPackageId(id))
|
|
167
|
+
throw new ApiError('E_BAD_NAME', `非法包名: ${id}`);
|
|
168
|
+
await request('DELETE', `/v1/packages/${encodeURIComponent(id)}`);
|
|
169
|
+
},
|
|
170
|
+
/** 导出我的全部数据(服务端打包当前包 + 元数据)。 */
|
|
171
|
+
async exportPackage() {
|
|
172
|
+
return request('GET', '/v1/export', { expectBytes: true });
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|