dscs-server 0.1.2 → 0.1.3
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/README.md +2 -2
- package/dist/http.js +75 -2
- package/dist/shared/chain.js +3 -0
- package/package.json +1 -1
- package/web/admin.html +27 -2
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
DSH Session CloudSync 的自部署服务端。
|
|
4
4
|
|
|
5
|
-
当前版本:**0.1.
|
|
6
|
-
旧的 `/v1/*` 已移除;0.0.x
|
|
5
|
+
当前版本:**0.1.3,v2-only**(`PROTOCOL_VERSION = 3`)。设备 API 只有 `/v2/*`,
|
|
6
|
+
旧的 `/v1/*` 已移除;0.0.x 插件不兼容,需要 0.1.0 起的插件。
|
|
7
7
|
|
|
8
8
|
## 能力
|
|
9
9
|
|
package/dist/http.js
CHANGED
|
@@ -375,6 +375,26 @@ export function revokeDevice(db, deviceId) {
|
|
|
375
375
|
const result = db.handle.prepare('UPDATE devices SET revoked_at = ? WHERE dataset = ? AND device_id = ? AND revoked_at IS NULL').run(Date.now(), DEFAULT_DATASET, deviceId);
|
|
376
376
|
return Number(result.changes) > 0;
|
|
377
377
|
}
|
|
378
|
+
export function renameDevice(db, deviceId, label) {
|
|
379
|
+
const result = db.handle.prepare('UPDATE devices SET label = ? WHERE dataset = ? AND device_id = ? AND revoked_at IS NULL').run(label, DEFAULT_DATASET, deviceId);
|
|
380
|
+
if (Number(result.changes) === 0) return undefined;
|
|
381
|
+
const row = db.handle.prepare('SELECT device_id, label, created_at, last_seen_at FROM devices WHERE dataset = ? AND device_id = ?').get(DEFAULT_DATASET, deviceId);
|
|
382
|
+
if (row === undefined) return undefined;
|
|
383
|
+
return {
|
|
384
|
+
deviceId: row.device_id,
|
|
385
|
+
label: row.label,
|
|
386
|
+
createdAt: row.created_at,
|
|
387
|
+
...row.last_seen_at === null ? {} : {
|
|
388
|
+
lastSeenAt: row.last_seen_at
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function requireDeviceLabel(value) {
|
|
393
|
+
const label = typeof value === 'string' ? value.trim() : '';
|
|
394
|
+
if (label === '') throw new SyncError('bad-request', '设备名不能为空');
|
|
395
|
+
if (label.length > 64) throw new SyncError('bad-request', '设备名最长 64 个字符');
|
|
396
|
+
return label;
|
|
397
|
+
}
|
|
378
398
|
export function createHttpServer(options) {
|
|
379
399
|
const { db } = options;
|
|
380
400
|
const version = options.version ?? SERVER_VERSION;
|
|
@@ -463,7 +483,10 @@ export function createHttpServer(options) {
|
|
|
463
483
|
'batches',
|
|
464
484
|
'batch-upload',
|
|
465
485
|
'batch-download',
|
|
466
|
-
'zstd-frame'
|
|
486
|
+
'zstd-frame',
|
|
487
|
+
'me-device-info',
|
|
488
|
+
'me-counts',
|
|
489
|
+
'device-rename'
|
|
467
490
|
],
|
|
468
491
|
limits: {
|
|
469
492
|
batchBytes: MAX_BATCH_BODY_BYTES,
|
|
@@ -1193,6 +1216,25 @@ export function createHttpServer(options) {
|
|
|
1193
1216
|
}) : json(res, 404, errorBody('not-found', '没有这个设备'));
|
|
1194
1217
|
}
|
|
1195
1218
|
},
|
|
1219
|
+
{
|
|
1220
|
+
method: 'PATCH',
|
|
1221
|
+
path: ADMIN_DEVICE_PATH,
|
|
1222
|
+
handler: async (req, res)=>{
|
|
1223
|
+
requireAdmin(req);
|
|
1224
|
+
const match = ADMIN_DEVICE_PATH.exec(new URL(req.url ?? '/', 'http://localhost').pathname);
|
|
1225
|
+
if (match === null) return json(res, 404, errorBody('not-found', '没有这个设备'));
|
|
1226
|
+
const body = await readJson(req);
|
|
1227
|
+
const label = requireDeviceLabel(body.label);
|
|
1228
|
+
const device = renameDevice(db, match[1], label);
|
|
1229
|
+
if (device === undefined) return json(res, 404, errorBody('not-found', '没有这个设备'));
|
|
1230
|
+
return json(res, 200, {
|
|
1231
|
+
ok: true,
|
|
1232
|
+
value: {
|
|
1233
|
+
device
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
},
|
|
1196
1238
|
{
|
|
1197
1239
|
method: 'GET',
|
|
1198
1240
|
path: /^\/admin\/sessions$/u,
|
|
@@ -1216,11 +1258,42 @@ export function createHttpServer(options) {
|
|
|
1216
1258
|
role: 'admin'
|
|
1217
1259
|
}
|
|
1218
1260
|
});
|
|
1261
|
+
const counts = sessionCounts(db);
|
|
1262
|
+
const workspaceRow = db.handle.prepare("SELECT COUNT(DISTINCT json_extract(manifest_json, '$.workspaceKey')) AS n FROM sessions WHERE dataset = ? AND deleted_at IS NULL AND json_extract(manifest_json, '$.workspaceKey') IS NOT NULL").get(DEFAULT_DATASET);
|
|
1219
1263
|
return json(res, 200, {
|
|
1220
1264
|
ok: true,
|
|
1221
1265
|
value: {
|
|
1222
1266
|
role: 'device',
|
|
1223
|
-
device: auth.device
|
|
1267
|
+
device: auth.device,
|
|
1268
|
+
server: {
|
|
1269
|
+
version,
|
|
1270
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
1271
|
+
quotaBytes: effectiveMaxBytes(),
|
|
1272
|
+
usedBytes: usedBlobBytes(db)
|
|
1273
|
+
},
|
|
1274
|
+
counts: {
|
|
1275
|
+
workspaces: Number(workspaceRow?.n ?? 0),
|
|
1276
|
+
sessions: counts.total - counts.deleted
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
method: 'PATCH',
|
|
1284
|
+
path: ME_PATH,
|
|
1285
|
+
handler: async (req, res)=>{
|
|
1286
|
+
const auth = authenticate(db, req);
|
|
1287
|
+
if (auth === undefined) return json(res, 401, errorBody('unauthorized', '未知 token'));
|
|
1288
|
+
if (auth.role !== 'device') return json(res, 401, errorBody('unauthorized', '需要设备 token'));
|
|
1289
|
+
const body = await readJson(req);
|
|
1290
|
+
const label = requireDeviceLabel(body.label);
|
|
1291
|
+
const device = renameDevice(db, auth.device.deviceId, label);
|
|
1292
|
+
if (device === undefined) return json(res, 404, errorBody('not-found', '没有这个设备'));
|
|
1293
|
+
return json(res, 200, {
|
|
1294
|
+
ok: true,
|
|
1295
|
+
value: {
|
|
1296
|
+
device
|
|
1224
1297
|
}
|
|
1225
1298
|
});
|
|
1226
1299
|
}
|
package/dist/shared/chain.js
CHANGED
|
@@ -139,6 +139,9 @@ function canonical(value) {
|
|
|
139
139
|
export function stableStringify(value) {
|
|
140
140
|
return JSON.stringify(canonical(value));
|
|
141
141
|
}
|
|
142
|
+
export function markerEquivalent(local, remote) {
|
|
143
|
+
return local.type === 'session/end-seed' && remote.type === 'session/end-seed' && stableStringify(local.data) === stableStringify(remote.data);
|
|
144
|
+
}
|
|
142
145
|
export function manifestDigest(manifest) {
|
|
143
146
|
return sha256(stableStringify({
|
|
144
147
|
sessionId: manifest.sessionId,
|
package/package.json
CHANGED
package/web/admin.html
CHANGED
|
@@ -380,7 +380,7 @@
|
|
|
380
380
|
<div>
|
|
381
381
|
<span class="eyebrow">Overview</span>
|
|
382
382
|
<h2>同步概览</h2>
|
|
383
|
-
<p class="muted"
|
|
383
|
+
<p class="muted">自部署节点、设备与会话视图;设备可签发、改名、吊销。</p>
|
|
384
384
|
</div>
|
|
385
385
|
<div class="health"><i></i><span id="healthText">服务正常</span></div>
|
|
386
386
|
</section>
|
|
@@ -604,6 +604,31 @@ function renderDevices(devices) {
|
|
|
604
604
|
)
|
|
605
605
|
main.append(label, idLine, meta)
|
|
606
606
|
const actions = el('div', 'device-actions')
|
|
607
|
+
const rename = el('button', 'btn small', '改名')
|
|
608
|
+
rename.type = 'button'
|
|
609
|
+
rename.onclick = async () => {
|
|
610
|
+
const next = prompt('新设备名(1–64 个字符)', device.label || 'device')
|
|
611
|
+
if (next === null) return
|
|
612
|
+
const label = next.trim()
|
|
613
|
+
if (label === '') {
|
|
614
|
+
showToast('设备名不能为空', true)
|
|
615
|
+
return
|
|
616
|
+
}
|
|
617
|
+
rename.disabled = true
|
|
618
|
+
rename.textContent = '改名中…'
|
|
619
|
+
try {
|
|
620
|
+
await api('/admin/devices/' + encodeURIComponent(device.deviceId), {
|
|
621
|
+
method: 'PATCH',
|
|
622
|
+
body: JSON.stringify({ label }),
|
|
623
|
+
})
|
|
624
|
+
showToast('设备已改名')
|
|
625
|
+
await refresh(true)
|
|
626
|
+
} catch (error) {
|
|
627
|
+
showToast(error.message, true)
|
|
628
|
+
rename.disabled = false
|
|
629
|
+
rename.textContent = '改名'
|
|
630
|
+
}
|
|
631
|
+
}
|
|
607
632
|
const revoke = el('button', 'btn small danger', '吊销')
|
|
608
633
|
revoke.type = 'button'
|
|
609
634
|
revoke.onclick = async () => {
|
|
@@ -620,7 +645,7 @@ function renderDevices(devices) {
|
|
|
620
645
|
revoke.textContent = '吊销'
|
|
621
646
|
}
|
|
622
647
|
}
|
|
623
|
-
actions.append(revoke)
|
|
648
|
+
actions.append(rename, revoke)
|
|
624
649
|
row.append(main, actions)
|
|
625
650
|
return row
|
|
626
651
|
}))
|