get-db9 0.5.0 → 0.6.0
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 +244 -2
- package/dist/{client-UKXFQNll.d.cts → client-B3NUYqOc.d.cts} +188 -20
- package/dist/{client-UKXFQNll.d.ts → client-B3NUYqOc.d.ts} +188 -20
- package/dist/client.cjs +332 -88
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +332 -88
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +336 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +334 -88
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,8 @@ __export(index_exports, {
|
|
|
35
35
|
Db9Error: () => Db9Error,
|
|
36
36
|
Db9NotFoundError: () => Db9NotFoundError,
|
|
37
37
|
FileCredentialStore: () => FileCredentialStore,
|
|
38
|
+
FsClient: () => FsClient,
|
|
39
|
+
FsError: () => FsError,
|
|
38
40
|
MemoryCredentialStore: () => MemoryCredentialStore,
|
|
39
41
|
createDb9Client: () => createDb9Client,
|
|
40
42
|
defaultCredentialStore: () => defaultCredentialStore,
|
|
@@ -303,6 +305,239 @@ function defaultCredentialStore() {
|
|
|
303
305
|
return new FileCredentialStore();
|
|
304
306
|
}
|
|
305
307
|
|
|
308
|
+
// src/ws.ts
|
|
309
|
+
var FsError = class extends Error {
|
|
310
|
+
code;
|
|
311
|
+
constructor(code, message) {
|
|
312
|
+
super(message);
|
|
313
|
+
this.name = "FsError";
|
|
314
|
+
this.code = code;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
var nextId = 1;
|
|
318
|
+
function nextRequestId() {
|
|
319
|
+
return String(nextId++);
|
|
320
|
+
}
|
|
321
|
+
function toBase64(data) {
|
|
322
|
+
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
323
|
+
if (typeof Buffer !== "undefined") {
|
|
324
|
+
return Buffer.from(bytes).toString("base64");
|
|
325
|
+
}
|
|
326
|
+
let binary = "";
|
|
327
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
328
|
+
binary += String.fromCharCode(bytes[i]);
|
|
329
|
+
}
|
|
330
|
+
return btoa(binary);
|
|
331
|
+
}
|
|
332
|
+
function fromBase64(b64) {
|
|
333
|
+
if (typeof Buffer !== "undefined") {
|
|
334
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
335
|
+
}
|
|
336
|
+
const binary = atob(b64);
|
|
337
|
+
const bytes = new Uint8Array(binary.length);
|
|
338
|
+
for (let i = 0; i < binary.length; i++) {
|
|
339
|
+
bytes[i] = binary.charCodeAt(i);
|
|
340
|
+
}
|
|
341
|
+
return bytes;
|
|
342
|
+
}
|
|
343
|
+
var FsClient = class _FsClient {
|
|
344
|
+
ws;
|
|
345
|
+
pending = /* @__PURE__ */ new Map();
|
|
346
|
+
closed = false;
|
|
347
|
+
constructor(ws) {
|
|
348
|
+
this.ws = ws;
|
|
349
|
+
ws.onmessage = (ev) => {
|
|
350
|
+
const text = typeof ev.data === "string" ? ev.data : String(ev.data);
|
|
351
|
+
let resp;
|
|
352
|
+
try {
|
|
353
|
+
resp = JSON.parse(text);
|
|
354
|
+
} catch {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const p = this.pending.get(resp.id);
|
|
358
|
+
if (p) {
|
|
359
|
+
this.pending.delete(resp.id);
|
|
360
|
+
p.resolve(resp);
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
ws.onclose = () => {
|
|
364
|
+
this.closed = true;
|
|
365
|
+
for (const [, p] of this.pending) {
|
|
366
|
+
p.reject(new FsError("CONNECTION_CLOSED", "WebSocket connection closed"));
|
|
367
|
+
}
|
|
368
|
+
this.pending.clear();
|
|
369
|
+
};
|
|
370
|
+
ws.onerror = () => {
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Connect to an fs9 WebSocket server.
|
|
375
|
+
*
|
|
376
|
+
* @param url WebSocket URL, e.g. `wss://host:5480`
|
|
377
|
+
* @param WS WebSocket constructor (native or from `ws` package)
|
|
378
|
+
*/
|
|
379
|
+
static connect(url, WS) {
|
|
380
|
+
return new Promise((resolve, reject) => {
|
|
381
|
+
const ws = new WS(url);
|
|
382
|
+
ws.onopen = () => {
|
|
383
|
+
ws.onopen = null;
|
|
384
|
+
ws.onerror = null;
|
|
385
|
+
resolve(new _FsClient(ws));
|
|
386
|
+
};
|
|
387
|
+
ws.onerror = (ev) => {
|
|
388
|
+
ws.onopen = null;
|
|
389
|
+
ws.onerror = null;
|
|
390
|
+
const msg = ev && typeof ev === "object" && "message" in ev ? String(ev.message) : "WebSocket connection failed";
|
|
391
|
+
reject(new FsError("CONNECTION_ERROR", msg));
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
/** Authenticate with the server. Must be called first after connect. */
|
|
396
|
+
async authenticate(username, password) {
|
|
397
|
+
const resp = await this.sendAndRecv({
|
|
398
|
+
id: nextRequestId(),
|
|
399
|
+
op: "auth",
|
|
400
|
+
username,
|
|
401
|
+
password
|
|
402
|
+
});
|
|
403
|
+
if (!resp.ok) {
|
|
404
|
+
throw new FsError("AUTH_FAILED", this.errorMessage(resp));
|
|
405
|
+
}
|
|
406
|
+
const data = resp.data;
|
|
407
|
+
return {
|
|
408
|
+
user: String(data?.user ?? ""),
|
|
409
|
+
tenant: String(data?.tenant ?? ""),
|
|
410
|
+
keyspace: String(data?.keyspace ?? "")
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/** Get file or directory metadata. */
|
|
414
|
+
async stat(path) {
|
|
415
|
+
const resp = await this.sendAndRecv({
|
|
416
|
+
id: nextRequestId(),
|
|
417
|
+
op: "stat",
|
|
418
|
+
path
|
|
419
|
+
});
|
|
420
|
+
this.expectOk(resp);
|
|
421
|
+
return resp.data;
|
|
422
|
+
}
|
|
423
|
+
/** List directory contents. */
|
|
424
|
+
async readdir(path) {
|
|
425
|
+
const resp = await this.sendAndRecv({
|
|
426
|
+
id: nextRequestId(),
|
|
427
|
+
op: "readdir",
|
|
428
|
+
path
|
|
429
|
+
});
|
|
430
|
+
this.expectOk(resp);
|
|
431
|
+
const data = resp.data;
|
|
432
|
+
return data?.entries ?? [];
|
|
433
|
+
}
|
|
434
|
+
/** Create a directory. Always recursive (mkdir -p). */
|
|
435
|
+
async mkdir(path, recursive = true) {
|
|
436
|
+
const resp = await this.sendAndRecv({
|
|
437
|
+
id: nextRequestId(),
|
|
438
|
+
op: "mkdir",
|
|
439
|
+
path,
|
|
440
|
+
recursive
|
|
441
|
+
});
|
|
442
|
+
this.expectOk(resp);
|
|
443
|
+
}
|
|
444
|
+
/** Read an entire file, returning raw bytes. */
|
|
445
|
+
async readFile(path) {
|
|
446
|
+
const resp = await this.sendAndRecv({
|
|
447
|
+
id: nextRequestId(),
|
|
448
|
+
op: "read",
|
|
449
|
+
path
|
|
450
|
+
});
|
|
451
|
+
this.expectOk(resp);
|
|
452
|
+
const data = resp.data;
|
|
453
|
+
const content = data?.content;
|
|
454
|
+
if (typeof content !== "string") {
|
|
455
|
+
throw new FsError("PROTOCOL", "missing content field in read response");
|
|
456
|
+
}
|
|
457
|
+
return fromBase64(content);
|
|
458
|
+
}
|
|
459
|
+
/** Write (overwrite) a file. Returns bytes written. */
|
|
460
|
+
async writeFile(path, data) {
|
|
461
|
+
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
462
|
+
const resp = await this.sendAndRecv({
|
|
463
|
+
id: nextRequestId(),
|
|
464
|
+
op: "write",
|
|
465
|
+
path,
|
|
466
|
+
content: toBase64(bytes),
|
|
467
|
+
encoding: "base64"
|
|
468
|
+
});
|
|
469
|
+
this.expectOk(resp);
|
|
470
|
+
const result = resp.data;
|
|
471
|
+
return result?.written ?? bytes.byteLength;
|
|
472
|
+
}
|
|
473
|
+
/** Append to a file. Returns bytes written. */
|
|
474
|
+
async appendFile(path, data) {
|
|
475
|
+
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
476
|
+
const resp = await this.sendAndRecv({
|
|
477
|
+
id: nextRequestId(),
|
|
478
|
+
op: "append",
|
|
479
|
+
path,
|
|
480
|
+
content: toBase64(bytes),
|
|
481
|
+
encoding: "base64"
|
|
482
|
+
});
|
|
483
|
+
this.expectOk(resp);
|
|
484
|
+
const result = resp.data;
|
|
485
|
+
return result?.written ?? bytes.byteLength;
|
|
486
|
+
}
|
|
487
|
+
/** Remove a file (non-recursive) or directory (recursive). */
|
|
488
|
+
async rm(path, recursive = false) {
|
|
489
|
+
const resp = await this.sendAndRecv(
|
|
490
|
+
recursive ? { id: nextRequestId(), op: "rm", path, recursive: true } : { id: nextRequestId(), op: "unlink", path }
|
|
491
|
+
);
|
|
492
|
+
this.expectOk(resp);
|
|
493
|
+
}
|
|
494
|
+
/** Rename (move) a file or directory. */
|
|
495
|
+
async rename(oldPath, newPath) {
|
|
496
|
+
const resp = await this.sendAndRecv({
|
|
497
|
+
id: nextRequestId(),
|
|
498
|
+
op: "rename",
|
|
499
|
+
old_path: oldPath,
|
|
500
|
+
new_path: newPath
|
|
501
|
+
});
|
|
502
|
+
this.expectOk(resp);
|
|
503
|
+
}
|
|
504
|
+
/** Gracefully close the WebSocket connection. */
|
|
505
|
+
async close() {
|
|
506
|
+
if (!this.closed) {
|
|
507
|
+
this.ws.close();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
// ── internals ──────────────────────────────────────────────────
|
|
511
|
+
sendAndRecv(request) {
|
|
512
|
+
if (this.closed) {
|
|
513
|
+
return Promise.reject(new FsError("CONNECTION_CLOSED", "WebSocket is closed"));
|
|
514
|
+
}
|
|
515
|
+
return new Promise((resolve, reject) => {
|
|
516
|
+
const id = request.id;
|
|
517
|
+
this.pending.set(id, { resolve, reject });
|
|
518
|
+
try {
|
|
519
|
+
this.ws.send(JSON.stringify(request));
|
|
520
|
+
} catch (err) {
|
|
521
|
+
this.pending.delete(id);
|
|
522
|
+
reject(new FsError("CONNECTION_ERROR", String(err)));
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
expectOk(resp) {
|
|
527
|
+
if (!resp.ok) {
|
|
528
|
+
const detail = resp.error;
|
|
529
|
+
throw new FsError(
|
|
530
|
+
detail?.code ?? "UNKNOWN",
|
|
531
|
+
detail?.message ?? "unknown error"
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
errorMessage(resp) {
|
|
536
|
+
const detail = resp.error;
|
|
537
|
+
return detail ? `${detail.code}: ${detail.message}` : "unknown error";
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
|
|
306
541
|
// src/client.ts
|
|
307
542
|
function createDb9Client(options = {}) {
|
|
308
543
|
const baseUrl = options.baseUrl ?? "https://db9.shared.aws.tidbcloud.com/api";
|
|
@@ -382,44 +617,42 @@ function createDb9Client(options = {}) {
|
|
|
382
617
|
return operation(newClient);
|
|
383
618
|
}
|
|
384
619
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
620
|
+
const fsWsPort = options.wsPort ?? 5480;
|
|
621
|
+
async function resolveFsConn(dbId) {
|
|
622
|
+
const creds = await withAuthRetry(
|
|
623
|
+
(client) => client.get(
|
|
624
|
+
`/customer/databases/${dbId}/credentials`
|
|
625
|
+
)
|
|
626
|
+
);
|
|
627
|
+
const connStr = creds.connection_string;
|
|
628
|
+
const hostMatch = connStr.match(/@([^:/?]+)/);
|
|
629
|
+
const host = hostMatch?.[1];
|
|
630
|
+
if (!host) {
|
|
631
|
+
throw new Error(`Cannot parse host from connection string for database '${dbId}'`);
|
|
632
|
+
}
|
|
633
|
+
const userMatch = connStr.match(/:\/\/([^:@]+)/);
|
|
634
|
+
const username = userMatch?.[1] ?? creds.admin_user;
|
|
635
|
+
const protocol = host === "localhost" || host === "127.0.0.1" ? "ws" : "wss";
|
|
636
|
+
return {
|
|
637
|
+
wsUrl: `${protocol}://${host}:${fsWsPort}`,
|
|
638
|
+
username,
|
|
639
|
+
password: creds.admin_password
|
|
640
|
+
};
|
|
399
641
|
}
|
|
400
|
-
async function
|
|
401
|
-
|
|
402
|
-
|
|
642
|
+
async function withFsClient(dbId, operation) {
|
|
643
|
+
const WS = options.WebSocket ?? (typeof globalThis !== "undefined" ? globalThis.WebSocket : void 0);
|
|
644
|
+
if (!WS) {
|
|
645
|
+
throw new Error(
|
|
646
|
+
"WebSocket constructor not available. Pass `WebSocket` in Db9ClientOptions, or use Node 21+ / a browser environment with native WebSocket support, or install the `ws` package for Node 18\u201320."
|
|
647
|
+
);
|
|
403
648
|
}
|
|
404
|
-
const
|
|
649
|
+
const conn = await resolveFsConn(dbId);
|
|
650
|
+
const client = await FsClient.connect(conn.wsUrl, WS);
|
|
405
651
|
try {
|
|
652
|
+
await client.authenticate(conn.username, conn.password);
|
|
406
653
|
return await operation(client);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
throw err;
|
|
410
|
-
}
|
|
411
|
-
try {
|
|
412
|
-
if (!refreshPromise) {
|
|
413
|
-
refreshPromise = refreshAnonymousToken();
|
|
414
|
-
}
|
|
415
|
-
await refreshPromise;
|
|
416
|
-
} catch {
|
|
417
|
-
throw err;
|
|
418
|
-
} finally {
|
|
419
|
-
refreshPromise = null;
|
|
420
|
-
}
|
|
421
|
-
const newClient = getFsClient(dbId);
|
|
422
|
-
return operation(newClient);
|
|
654
|
+
} finally {
|
|
655
|
+
await client.close();
|
|
423
656
|
}
|
|
424
657
|
}
|
|
425
658
|
function parseSqlError(raw) {
|
|
@@ -440,12 +673,6 @@ function createDb9Client(options = {}) {
|
|
|
440
673
|
}
|
|
441
674
|
return { message: raw };
|
|
442
675
|
}
|
|
443
|
-
async function fsStat(dbId, path) {
|
|
444
|
-
return withFsAuthRetry(
|
|
445
|
-
dbId,
|
|
446
|
-
(client) => client.get("/stat", { path })
|
|
447
|
-
);
|
|
448
|
-
}
|
|
449
676
|
async function fetchAnonymousSecret() {
|
|
450
677
|
return withAuthRetry(
|
|
451
678
|
(client) => client.post("/customer/anonymous-secret", {})
|
|
@@ -519,6 +746,11 @@ function createDb9Client(options = {}) {
|
|
|
519
746
|
`/customer/databases/${databaseId}/reset-password`
|
|
520
747
|
)
|
|
521
748
|
),
|
|
749
|
+
credentials: async (databaseId) => withAuthRetry(
|
|
750
|
+
(client) => client.get(
|
|
751
|
+
`/customer/databases/${databaseId}/credentials`
|
|
752
|
+
)
|
|
753
|
+
),
|
|
522
754
|
observability: async (databaseId) => withAuthRetry(
|
|
523
755
|
(client) => client.get(
|
|
524
756
|
`/customer/databases/${databaseId}/observability`
|
|
@@ -601,70 +833,84 @@ function createDb9Client(options = {}) {
|
|
|
601
833
|
}
|
|
602
834
|
},
|
|
603
835
|
fs: {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
)
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
return withFsAuthRetry(dbId, async (client) => {
|
|
620
|
-
const resp = await client.getRaw("/download", { path });
|
|
621
|
-
return resp.arrayBuffer();
|
|
622
|
-
});
|
|
836
|
+
/**
|
|
837
|
+
* Open a persistent WebSocket connection for multiple fs operations.
|
|
838
|
+
* Caller is responsible for calling `client.close()` when done.
|
|
839
|
+
*/
|
|
840
|
+
connect: async (dbId) => {
|
|
841
|
+
const WS = options.WebSocket ?? (typeof globalThis !== "undefined" ? globalThis.WebSocket : void 0);
|
|
842
|
+
if (!WS) {
|
|
843
|
+
throw new Error(
|
|
844
|
+
"WebSocket constructor not available. Pass `WebSocket` in Db9ClientOptions, or use Node 21+ / a browser environment with native WebSocket support, or install the `ws` package for Node 18\u201320."
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
const conn = await resolveFsConn(dbId);
|
|
848
|
+
const client = await FsClient.connect(conn.wsUrl, WS);
|
|
849
|
+
await client.authenticate(conn.username, conn.password);
|
|
850
|
+
return client;
|
|
623
851
|
},
|
|
852
|
+
/** List directory contents. */
|
|
853
|
+
list: async (dbId, path) => withFsClient(dbId, (client) => client.readdir(path)),
|
|
854
|
+
/** Read a file as text (UTF-8). */
|
|
855
|
+
read: async (dbId, path) => withFsClient(dbId, async (client) => {
|
|
856
|
+
const bytes = await client.readFile(path);
|
|
857
|
+
return new TextDecoder().decode(bytes);
|
|
858
|
+
}),
|
|
859
|
+
/** Read a file as raw bytes. */
|
|
860
|
+
readBinary: async (dbId, path) => withFsClient(dbId, (client) => client.readFile(path)),
|
|
861
|
+
/** Write (overwrite) a file. Accepts string, ArrayBuffer, or Uint8Array. */
|
|
624
862
|
write: async (dbId, path, content) => {
|
|
625
|
-
|
|
626
|
-
await withFsAuthRetry(
|
|
627
|
-
dbId,
|
|
628
|
-
(client) => client.putRaw(`/upload?${new URLSearchParams({ path })}`, content, { "Content-Type": contentType })
|
|
629
|
-
);
|
|
630
|
-
},
|
|
631
|
-
stat: (dbId, path) => {
|
|
632
|
-
return fsStat(dbId, path);
|
|
863
|
+
await withFsClient(dbId, (client) => client.writeFile(path, content));
|
|
633
864
|
},
|
|
865
|
+
/** Append to a file. Returns bytes written. */
|
|
866
|
+
append: async (dbId, path, content) => withFsClient(dbId, (client) => client.appendFile(path, content)),
|
|
867
|
+
/** Get file or directory metadata. */
|
|
868
|
+
stat: async (dbId, path) => withFsClient(dbId, (client) => client.stat(path)),
|
|
869
|
+
/** Check if a file or directory exists. */
|
|
634
870
|
exists: async (dbId, path) => {
|
|
635
871
|
try {
|
|
636
|
-
await
|
|
872
|
+
await withFsClient(dbId, (client) => client.stat(path));
|
|
637
873
|
return true;
|
|
638
874
|
} catch (err) {
|
|
639
|
-
if (err instanceof
|
|
875
|
+
if (err instanceof FsError && err.code === "ENOENT") {
|
|
640
876
|
return false;
|
|
641
877
|
}
|
|
642
878
|
throw err;
|
|
643
879
|
}
|
|
644
880
|
},
|
|
881
|
+
/** Create a directory (recursive by default). */
|
|
645
882
|
mkdir: async (dbId, path) => {
|
|
646
|
-
await
|
|
647
|
-
dbId,
|
|
648
|
-
(client) => client.postRaw(`/mkdir?${new URLSearchParams({ path, recursive: "true" })}`)
|
|
649
|
-
);
|
|
883
|
+
await withFsClient(dbId, (client) => client.mkdir(path, true));
|
|
650
884
|
},
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
(client) => client.delRaw("/remove", { path })
|
|
655
|
-
);
|
|
885
|
+
/** Remove a file or directory. */
|
|
886
|
+
remove: async (dbId, path, opts) => {
|
|
887
|
+
await withFsClient(dbId, (client) => client.rm(path, opts?.recursive ?? false));
|
|
656
888
|
},
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if (options2?.offset !== void 0) params.offset = String(options2.offset);
|
|
661
|
-
if (options2?.path) params.path = options2.path;
|
|
662
|
-
if (options2?.type) params.type = options2.type;
|
|
663
|
-
return withFsAuthRetry(
|
|
664
|
-
dbId,
|
|
665
|
-
(client) => client.get("/events", params)
|
|
666
|
-
);
|
|
889
|
+
/** Rename (move) a file or directory. */
|
|
890
|
+
rename: async (dbId, oldPath, newPath) => {
|
|
891
|
+
await withFsClient(dbId, (client) => client.rename(oldPath, newPath));
|
|
667
892
|
}
|
|
893
|
+
},
|
|
894
|
+
// ── Device Auth Flow ─────────────────────────────────────────
|
|
895
|
+
deviceAuth: {
|
|
896
|
+
/** Start device code flow. Returns codes for user to authorize. */
|
|
897
|
+
createDeviceCode: () => publicClient.post("/customer/device-code"),
|
|
898
|
+
/** Poll for device token after user authorizes. Returns token or error status. */
|
|
899
|
+
pollDeviceToken: async (req) => {
|
|
900
|
+
try {
|
|
901
|
+
return await publicClient.post("/customer/device-token", req);
|
|
902
|
+
} catch (err) {
|
|
903
|
+
if (err instanceof Db9Error && err.statusCode === 400) {
|
|
904
|
+
const errorType = err.message;
|
|
905
|
+
if (errorType === "authorization_pending" || errorType === "expired_token" || errorType === "access_denied") {
|
|
906
|
+
return { error: errorType };
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
throw err;
|
|
910
|
+
}
|
|
911
|
+
},
|
|
912
|
+
/** Submit device verification with user credentials. */
|
|
913
|
+
verifyDevice: (req) => publicClient.post("/customer/device-verify", req)
|
|
668
914
|
}
|
|
669
915
|
};
|
|
670
916
|
}
|
|
@@ -710,6 +956,8 @@ function toResult(db) {
|
|
|
710
956
|
Db9Error,
|
|
711
957
|
Db9NotFoundError,
|
|
712
958
|
FileCredentialStore,
|
|
959
|
+
FsClient,
|
|
960
|
+
FsError,
|
|
713
961
|
MemoryCredentialStore,
|
|
714
962
|
createDb9Client,
|
|
715
963
|
defaultCredentialStore,
|