js-bao-wss-client 1.0.15 → 1.0.18
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 +547 -8
- package/dist/JsBaoClient.d.ts +83 -0
- package/dist/JsBaoClient.d.ts.map +1 -1
- package/dist/JsBaoClient.js +253 -39
- package/dist/JsBaoClient.js.map +1 -1
- package/dist/api/documentsApi.d.ts.map +1 -1
- package/dist/api/documentsApi.js +41 -37
- package/dist/api/documentsApi.js.map +1 -1
- package/dist/browser.umd.js +443 -112
- package/dist/browser.umd.js.map +1 -1
- package/dist/internal/documentManager.d.ts +11 -0
- package/dist/internal/documentManager.d.ts.map +1 -1
- package/dist/internal/documentManager.js +90 -29
- package/dist/internal/documentManager.js.map +1 -1
- package/dist/internal/webSocketManager.d.ts +4 -1
- package/dist/internal/webSocketManager.d.ts.map +1 -1
- package/dist/internal/webSocketManager.js +56 -7
- package/dist/internal/webSocketManager.js.map +1 -1
- package/dist/webauthn-large-blob.d.ts.map +1 -1
- package/dist/webauthn-large-blob.js +3 -0
- package/dist/webauthn-large-blob.js.map +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -10,11 +10,13 @@ A TypeScript/JavaScript client library for js-bao-wss that provides HTTP APIs an
|
|
|
10
10
|
- **Realtime Collaboration**: Y.Doc sync over multi-tenant WebSocket
|
|
11
11
|
- **Awareness**: Presence/cursor broadcast and server-triggered refresh
|
|
12
12
|
- **Auth/OAuth**: Client-orchestrated OAuth and cookie refresh
|
|
13
|
+
- **Passkey Authentication**: WebAuthn/passkey support for passwordless sign-in
|
|
13
14
|
- **Automatic Reconnect**: Backoff + re-auth on 401
|
|
14
15
|
- **Token Management**: Proactive refresh in HTTP calls
|
|
15
16
|
- **Analytics**: Buffered event logging API with optional automatic lifecycle events
|
|
16
17
|
- **Blob Storage**: Upload/list/get/downloadUrl/delete per document with offline cache
|
|
17
18
|
- **LLM**: Chat API and model listing
|
|
19
|
+
- **Workflows**: Server-side multi-step processes with LLM, delays, and transformations
|
|
18
20
|
- **Offline-first Open**: Non-blocking open with IndexedDB-backed cache
|
|
19
21
|
- **Offline Blob Cache**: Cache API + IndexedDB backed uploads/reads with eviction and retry
|
|
20
22
|
- **Network Controls**: Online/offline modes, reachability, connection control
|
|
@@ -576,6 +578,161 @@ if (client.isAuthenticated()) {
|
|
|
576
578
|
client.setToken("new-jwt-token");
|
|
577
579
|
```
|
|
578
580
|
|
|
581
|
+
## Passkey Authentication
|
|
582
|
+
|
|
583
|
+
The client supports WebAuthn/passkey authentication for passwordless sign-in. Passkeys must be enabled in the admin console for your app.
|
|
584
|
+
|
|
585
|
+
### Check Passkey Availability
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
// Check if passkeys are enabled for the app
|
|
589
|
+
const config = await client.getOAuthConfig();
|
|
590
|
+
if (config.passkeyEnabled) {
|
|
591
|
+
console.log("Passkeys are available");
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### Sign Up with Passkey (New Account)
|
|
596
|
+
|
|
597
|
+
```typescript
|
|
598
|
+
import {
|
|
599
|
+
startRegistration,
|
|
600
|
+
browserSupportsWebAuthn,
|
|
601
|
+
} from "@simplewebauthn/browser";
|
|
602
|
+
|
|
603
|
+
if (!browserSupportsWebAuthn()) {
|
|
604
|
+
console.error("WebAuthn not supported");
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// 1. Start signup
|
|
609
|
+
const startResponse = await fetch(
|
|
610
|
+
`${apiUrl}/app/${appId}/api/passkey/signup/start`,
|
|
611
|
+
{
|
|
612
|
+
method: "POST",
|
|
613
|
+
headers: { "Content-Type": "application/json" },
|
|
614
|
+
body: JSON.stringify({
|
|
615
|
+
email: "user@example.com",
|
|
616
|
+
name: "User Name", // optional
|
|
617
|
+
}),
|
|
618
|
+
}
|
|
619
|
+
);
|
|
620
|
+
const { options, token: signupToken } = await startResponse.json();
|
|
621
|
+
|
|
622
|
+
// 2. Create passkey with browser
|
|
623
|
+
const credential = await startRegistration({ optionsJSON: options });
|
|
624
|
+
|
|
625
|
+
// 3. Complete signup
|
|
626
|
+
const finishResponse = await fetch(
|
|
627
|
+
`${apiUrl}/app/${appId}/api/passkey/signup/finish`,
|
|
628
|
+
{
|
|
629
|
+
method: "POST",
|
|
630
|
+
headers: { "Content-Type": "application/json" },
|
|
631
|
+
body: JSON.stringify({
|
|
632
|
+
token: signupToken,
|
|
633
|
+
credential,
|
|
634
|
+
deviceName: "MacBook Pro",
|
|
635
|
+
}),
|
|
636
|
+
}
|
|
637
|
+
);
|
|
638
|
+
const { token: authToken, user } = await finishResponse.json();
|
|
639
|
+
|
|
640
|
+
// 4. Initialize client with token
|
|
641
|
+
const client = await initializeClient({
|
|
642
|
+
apiUrl,
|
|
643
|
+
wsUrl,
|
|
644
|
+
appId,
|
|
645
|
+
token: authToken,
|
|
646
|
+
});
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### Sign In with Passkey
|
|
650
|
+
|
|
651
|
+
```typescript
|
|
652
|
+
import { startAuthentication } from "@simplewebauthn/browser";
|
|
653
|
+
|
|
654
|
+
// 1. Start authentication
|
|
655
|
+
const startResponse = await fetch(
|
|
656
|
+
`${apiUrl}/app/${appId}/api/passkey/auth/start`,
|
|
657
|
+
{ method: "POST" }
|
|
658
|
+
);
|
|
659
|
+
const { options, token: authToken } = await startResponse.json();
|
|
660
|
+
|
|
661
|
+
// 2. Authenticate with browser
|
|
662
|
+
const credential = await startAuthentication({ optionsJSON: options });
|
|
663
|
+
|
|
664
|
+
// 3. Complete authentication
|
|
665
|
+
const finishResponse = await fetch(
|
|
666
|
+
`${apiUrl}/app/${appId}/api/passkey/auth/finish`,
|
|
667
|
+
{
|
|
668
|
+
method: "POST",
|
|
669
|
+
headers: { "Content-Type": "application/json" },
|
|
670
|
+
body: JSON.stringify({ token: authToken, credential }),
|
|
671
|
+
}
|
|
672
|
+
);
|
|
673
|
+
const { token, user } = await finishResponse.json();
|
|
674
|
+
|
|
675
|
+
// 4. Initialize client with token
|
|
676
|
+
const client = await initializeClient({
|
|
677
|
+
apiUrl,
|
|
678
|
+
wsUrl,
|
|
679
|
+
appId,
|
|
680
|
+
token,
|
|
681
|
+
});
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
### Add Passkey to Existing Account
|
|
685
|
+
|
|
686
|
+
```typescript
|
|
687
|
+
import { startRegistration } from "@simplewebauthn/browser";
|
|
688
|
+
|
|
689
|
+
// User must be authenticated
|
|
690
|
+
const jwt = client.getToken();
|
|
691
|
+
|
|
692
|
+
// 1. Start registration
|
|
693
|
+
const startResponse = await fetch(
|
|
694
|
+
`${apiUrl}/app/${appId}/api/passkey/register/start`,
|
|
695
|
+
{
|
|
696
|
+
method: "POST",
|
|
697
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
698
|
+
}
|
|
699
|
+
);
|
|
700
|
+
const { options, token: regToken } = await startResponse.json();
|
|
701
|
+
|
|
702
|
+
// 2. Create passkey
|
|
703
|
+
const credential = await startRegistration({ optionsJSON: options });
|
|
704
|
+
|
|
705
|
+
// 3. Complete registration
|
|
706
|
+
await fetch(`${apiUrl}/app/${appId}/api/passkey/register/finish`, {
|
|
707
|
+
method: "POST",
|
|
708
|
+
headers: {
|
|
709
|
+
"Content-Type": "application/json",
|
|
710
|
+
Authorization: `Bearer ${jwt}`,
|
|
711
|
+
},
|
|
712
|
+
body: JSON.stringify({
|
|
713
|
+
token: regToken,
|
|
714
|
+
credential,
|
|
715
|
+
deviceName: "iPhone",
|
|
716
|
+
}),
|
|
717
|
+
});
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### List and Delete Passkeys
|
|
721
|
+
|
|
722
|
+
```typescript
|
|
723
|
+
// List user's passkeys
|
|
724
|
+
const listResponse = await fetch(`${apiUrl}/app/${appId}/api/passkey/list`, {
|
|
725
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
726
|
+
});
|
|
727
|
+
const { passkeys } = await listResponse.json();
|
|
728
|
+
|
|
729
|
+
// Delete a passkey
|
|
730
|
+
await fetch(`${apiUrl}/app/${appId}/api/passkey/${passkeyId}`, {
|
|
731
|
+
method: "DELETE",
|
|
732
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
733
|
+
});
|
|
734
|
+
```
|
|
735
|
+
|
|
579
736
|
## Document Management
|
|
580
737
|
|
|
581
738
|
### Create and List Documents
|
|
@@ -1320,6 +1477,388 @@ console.log(tokens.totalTokens);
|
|
|
1320
1477
|
- Error handling surfaces `JsBaoError` with `code: "GEMINI_ERROR"`; the `details` property contains the raw upstream payload so you can log or render troubleshooting info.
|
|
1321
1478
|
- See `.dev.local.example` for sample environment values and `docs/gemini-direct-plan.md` for architectural details.
|
|
1322
1479
|
|
|
1480
|
+
## Workflows
|
|
1481
|
+
|
|
1482
|
+
Workflows allow you to execute server-side, multi-step processes that can include LLM calls, delays, transformations, and more. The client provides APIs to start workflows, monitor their status, and receive real-time completion events.
|
|
1483
|
+
|
|
1484
|
+
### Starting a Workflow
|
|
1485
|
+
|
|
1486
|
+
```typescript
|
|
1487
|
+
// Start a workflow with input data
|
|
1488
|
+
const result = await client.workflows.start("my-workflow-key", {
|
|
1489
|
+
message: "Hello world",
|
|
1490
|
+
value: 42,
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
console.log("Run started:", result.runKey);
|
|
1494
|
+
console.log("Run ID:", result.runId);
|
|
1495
|
+
console.log("Status:", result.status);
|
|
1496
|
+
```
|
|
1497
|
+
|
|
1498
|
+
#### Start Options
|
|
1499
|
+
|
|
1500
|
+
```typescript
|
|
1501
|
+
const result = await client.workflows.start(
|
|
1502
|
+
"my-workflow-key",
|
|
1503
|
+
{ message: "Hello" },
|
|
1504
|
+
{
|
|
1505
|
+
// Provide a custom runKey for idempotency (auto-generated if omitted)
|
|
1506
|
+
runKey: "unique-run-identifier",
|
|
1507
|
+
// Associate the run with a document
|
|
1508
|
+
contextDocId: "doc-123",
|
|
1509
|
+
// Pass additional metadata
|
|
1510
|
+
meta: { source: "user-action", priority: "high" },
|
|
1511
|
+
}
|
|
1512
|
+
);
|
|
1513
|
+
```
|
|
1514
|
+
|
|
1515
|
+
### Duplicate Workflow Protection (Idempotency)
|
|
1516
|
+
|
|
1517
|
+
When you provide a `runKey`, the server ensures only one workflow run exists for that key. If you call `start()` again with the same `runKey`, the existing run is returned instead of creating a new one:
|
|
1518
|
+
|
|
1519
|
+
```typescript
|
|
1520
|
+
// First call creates the workflow
|
|
1521
|
+
const first = await client.workflows.start(
|
|
1522
|
+
"process-document",
|
|
1523
|
+
{ docId: "abc" },
|
|
1524
|
+
{ runKey: "process-abc-v1" }
|
|
1525
|
+
);
|
|
1526
|
+
console.log(first.existing); // false - new run created
|
|
1527
|
+
|
|
1528
|
+
// Second call with same runKey returns existing run
|
|
1529
|
+
const second = await client.workflows.start(
|
|
1530
|
+
"process-document",
|
|
1531
|
+
{ docId: "abc" },
|
|
1532
|
+
{ runKey: "process-abc-v1" }
|
|
1533
|
+
);
|
|
1534
|
+
console.log(second.existing); // true - existing run returned
|
|
1535
|
+
console.log(second.runId === first.runId); // true - same run
|
|
1536
|
+
```
|
|
1537
|
+
|
|
1538
|
+
This is useful for:
|
|
1539
|
+
- Preventing duplicate processing when users click a button multiple times
|
|
1540
|
+
- Safely retrying failed requests without creating duplicate work
|
|
1541
|
+
- Implementing exactly-once semantics for critical operations
|
|
1542
|
+
|
|
1543
|
+
### Checking Workflow Status
|
|
1544
|
+
|
|
1545
|
+
Poll the status of a running workflow:
|
|
1546
|
+
|
|
1547
|
+
```typescript
|
|
1548
|
+
const status = await client.workflows.getStatus("my-workflow-key", runKey);
|
|
1549
|
+
|
|
1550
|
+
console.log("Status:", status.status); // "running" | "complete" | "failed" | "terminated"
|
|
1551
|
+
|
|
1552
|
+
if (status.status === "complete") {
|
|
1553
|
+
console.log("Output:", status.output);
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
if (status.status === "failed") {
|
|
1557
|
+
console.log("Error:", status.error);
|
|
1558
|
+
}
|
|
1559
|
+
```
|
|
1560
|
+
|
|
1561
|
+
### Listening for Workflow Events
|
|
1562
|
+
|
|
1563
|
+
Subscribe to real-time workflow completion events via WebSocket:
|
|
1564
|
+
|
|
1565
|
+
```typescript
|
|
1566
|
+
// Listen for workflow status changes
|
|
1567
|
+
client.on("workflowStatus", (event) => {
|
|
1568
|
+
console.log("Workflow event:", event.workflowKey, event.runKey);
|
|
1569
|
+
console.log("Status:", event.status); // "completed" | "failed" | "terminated"
|
|
1570
|
+
|
|
1571
|
+
if (event.status === "completed") {
|
|
1572
|
+
console.log("Output:", event.output);
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
if (event.status === "failed") {
|
|
1576
|
+
console.log("Error:", event.error);
|
|
1577
|
+
}
|
|
1578
|
+
});
|
|
1579
|
+
```
|
|
1580
|
+
|
|
1581
|
+
**Note**: To receive workflow events, you must have an active WebSocket connection. Opening a document establishes this connection:
|
|
1582
|
+
|
|
1583
|
+
```typescript
|
|
1584
|
+
// Open a document to establish WebSocket for receiving notifications
|
|
1585
|
+
await client.documents.open(documentId);
|
|
1586
|
+
|
|
1587
|
+
// Now workflow events will be delivered
|
|
1588
|
+
const result = await client.workflows.start("my-workflow", { data: "..." });
|
|
1589
|
+
```
|
|
1590
|
+
|
|
1591
|
+
#### Event Payload
|
|
1592
|
+
|
|
1593
|
+
```typescript
|
|
1594
|
+
interface WorkflowStatusEvent {
|
|
1595
|
+
type: "workflowStatus";
|
|
1596
|
+
workflowKey: string;
|
|
1597
|
+
workflowId: string;
|
|
1598
|
+
runKey: string;
|
|
1599
|
+
runId: string;
|
|
1600
|
+
status: "completed" | "failed" | "terminated";
|
|
1601
|
+
output?: any;
|
|
1602
|
+
error?: string;
|
|
1603
|
+
contextDocId?: string;
|
|
1604
|
+
}
|
|
1605
|
+
```
|
|
1606
|
+
|
|
1607
|
+
### Listing Workflow Runs
|
|
1608
|
+
|
|
1609
|
+
View all workflow runs for the current user:
|
|
1610
|
+
|
|
1611
|
+
```typescript
|
|
1612
|
+
// List all runs
|
|
1613
|
+
const runs = await client.workflows.listRuns();
|
|
1614
|
+
console.log("Total runs:", runs.items.length);
|
|
1615
|
+
|
|
1616
|
+
runs.items.forEach((run) => {
|
|
1617
|
+
console.log(run.runKey, run.status, run.createdAt);
|
|
1618
|
+
});
|
|
1619
|
+
|
|
1620
|
+
// Filter by workflow
|
|
1621
|
+
const filtered = await client.workflows.listRuns({
|
|
1622
|
+
workflowKey: "my-workflow",
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
// Filter by status
|
|
1626
|
+
const running = await client.workflows.listRuns({
|
|
1627
|
+
status: "running",
|
|
1628
|
+
});
|
|
1629
|
+
|
|
1630
|
+
// Pagination
|
|
1631
|
+
const page1 = await client.workflows.listRuns({ limit: 10 });
|
|
1632
|
+
if (page1.cursor) {
|
|
1633
|
+
const page2 = await client.workflows.listRuns({
|
|
1634
|
+
limit: 10,
|
|
1635
|
+
cursor: page1.cursor,
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
```
|
|
1639
|
+
|
|
1640
|
+
#### Run Record Fields
|
|
1641
|
+
|
|
1642
|
+
```typescript
|
|
1643
|
+
interface WorkflowRun {
|
|
1644
|
+
runId: string;
|
|
1645
|
+
runKey: string;
|
|
1646
|
+
instanceId: string;
|
|
1647
|
+
workflowId: string;
|
|
1648
|
+
workflowKey: string;
|
|
1649
|
+
revisionId: string;
|
|
1650
|
+
contextDocId?: string;
|
|
1651
|
+
status: string;
|
|
1652
|
+
createdAt: string;
|
|
1653
|
+
endedAt?: string;
|
|
1654
|
+
}
|
|
1655
|
+
```
|
|
1656
|
+
|
|
1657
|
+
### Terminating a Workflow
|
|
1658
|
+
|
|
1659
|
+
Cancel a running workflow:
|
|
1660
|
+
|
|
1661
|
+
```typescript
|
|
1662
|
+
const result = await client.workflows.terminate("my-workflow-key", runKey);
|
|
1663
|
+
console.log("Terminated, final status:", result.status);
|
|
1664
|
+
```
|
|
1665
|
+
|
|
1666
|
+
### Sending File Attachments (PDFs, Images)
|
|
1667
|
+
|
|
1668
|
+
Workflows can process files like PDFs and images. Files must be base64-encoded before sending:
|
|
1669
|
+
|
|
1670
|
+
```typescript
|
|
1671
|
+
/**
|
|
1672
|
+
* Load a file and convert to base64.
|
|
1673
|
+
* Works in browsers with fetch + FileReader or ArrayBuffer.
|
|
1674
|
+
*/
|
|
1675
|
+
async function loadFileAsBase64(url: string): Promise<string> {
|
|
1676
|
+
const response = await fetch(url);
|
|
1677
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
1678
|
+
const bytes = new Uint8Array(arrayBuffer);
|
|
1679
|
+
let binary = "";
|
|
1680
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1681
|
+
binary += String.fromCharCode(bytes[i]);
|
|
1682
|
+
}
|
|
1683
|
+
return btoa(binary);
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// Load a PDF and send to workflow
|
|
1687
|
+
const pdfBase64 = await loadFileAsBase64("/path/to/document.pdf");
|
|
1688
|
+
|
|
1689
|
+
const result = await client.workflows.start("extract-pdf-data", {
|
|
1690
|
+
attachments: [
|
|
1691
|
+
{
|
|
1692
|
+
data: pdfBase64,
|
|
1693
|
+
type: "application/pdf",
|
|
1694
|
+
},
|
|
1695
|
+
],
|
|
1696
|
+
});
|
|
1697
|
+
```
|
|
1698
|
+
|
|
1699
|
+
#### Loading from File Input (Browser)
|
|
1700
|
+
|
|
1701
|
+
```typescript
|
|
1702
|
+
async function fileToBase64(file: File): Promise<string> {
|
|
1703
|
+
return new Promise((resolve, reject) => {
|
|
1704
|
+
const reader = new FileReader();
|
|
1705
|
+
reader.onload = () => {
|
|
1706
|
+
const dataUrl = reader.result as string;
|
|
1707
|
+
// Remove the data URL prefix (e.g., "data:application/pdf;base64,")
|
|
1708
|
+
const base64 = dataUrl.split(",")[1];
|
|
1709
|
+
resolve(base64);
|
|
1710
|
+
};
|
|
1711
|
+
reader.onerror = reject;
|
|
1712
|
+
reader.readAsDataURL(file);
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
// Handle file input
|
|
1717
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
|
1718
|
+
fileInput.addEventListener("change", async () => {
|
|
1719
|
+
const file = fileInput.files?.[0];
|
|
1720
|
+
if (!file) return;
|
|
1721
|
+
|
|
1722
|
+
const base64Data = await fileToBase64(file);
|
|
1723
|
+
|
|
1724
|
+
const result = await client.workflows.start("process-upload", {
|
|
1725
|
+
attachments: [
|
|
1726
|
+
{
|
|
1727
|
+
data: base64Data,
|
|
1728
|
+
type: file.type, // e.g., "image/png", "application/pdf"
|
|
1729
|
+
filename: file.name,
|
|
1730
|
+
},
|
|
1731
|
+
],
|
|
1732
|
+
});
|
|
1733
|
+
});
|
|
1734
|
+
```
|
|
1735
|
+
|
|
1736
|
+
#### Loading from URL (Node.js)
|
|
1737
|
+
|
|
1738
|
+
```typescript
|
|
1739
|
+
import * as fs from "fs";
|
|
1740
|
+
import * as path from "path";
|
|
1741
|
+
|
|
1742
|
+
function loadFileAsBase64Sync(filePath: string): string {
|
|
1743
|
+
const buffer = fs.readFileSync(filePath);
|
|
1744
|
+
return buffer.toString("base64");
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
const pdfPath = path.join(__dirname, "document.pdf");
|
|
1748
|
+
const pdfBase64 = loadFileAsBase64Sync(pdfPath);
|
|
1749
|
+
|
|
1750
|
+
const result = await client.workflows.start("analyze-document", {
|
|
1751
|
+
attachments: [
|
|
1752
|
+
{
|
|
1753
|
+
data: pdfBase64,
|
|
1754
|
+
type: "application/pdf",
|
|
1755
|
+
},
|
|
1756
|
+
],
|
|
1757
|
+
});
|
|
1758
|
+
```
|
|
1759
|
+
|
|
1760
|
+
### Complete Example: PDF Processing Workflow
|
|
1761
|
+
|
|
1762
|
+
```typescript
|
|
1763
|
+
import { initializeClient } from "js-bao-wss-client";
|
|
1764
|
+
|
|
1765
|
+
async function processPDF(pdfUrl: string) {
|
|
1766
|
+
const client = await initializeClient({
|
|
1767
|
+
apiUrl: "https://api.example.com",
|
|
1768
|
+
wsUrl: "wss://ws.example.com",
|
|
1769
|
+
appId: "my-app",
|
|
1770
|
+
token: "jwt-token",
|
|
1771
|
+
databaseConfig: { type: "sqljs" },
|
|
1772
|
+
});
|
|
1773
|
+
|
|
1774
|
+
// Set up event listener for completion
|
|
1775
|
+
const completionPromise = new Promise<any>((resolve) => {
|
|
1776
|
+
client.on("workflowStatus", (event) => {
|
|
1777
|
+
if (event.status === "completed") {
|
|
1778
|
+
resolve(event.output);
|
|
1779
|
+
}
|
|
1780
|
+
});
|
|
1781
|
+
});
|
|
1782
|
+
|
|
1783
|
+
// Open a document to establish WebSocket connection
|
|
1784
|
+
const { metadata } = await client.documents.create({ title: "temp" });
|
|
1785
|
+
await client.documents.open(metadata.documentId);
|
|
1786
|
+
|
|
1787
|
+
// Load and encode the PDF
|
|
1788
|
+
const response = await fetch(pdfUrl);
|
|
1789
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
1790
|
+
const bytes = new Uint8Array(arrayBuffer);
|
|
1791
|
+
let binary = "";
|
|
1792
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1793
|
+
binary += String.fromCharCode(bytes[i]);
|
|
1794
|
+
}
|
|
1795
|
+
const pdfBase64 = btoa(binary);
|
|
1796
|
+
|
|
1797
|
+
// Start the workflow
|
|
1798
|
+
const result = await client.workflows.start("extract-pdf-data", {
|
|
1799
|
+
attachments: [
|
|
1800
|
+
{
|
|
1801
|
+
data: pdfBase64,
|
|
1802
|
+
type: "application/pdf",
|
|
1803
|
+
},
|
|
1804
|
+
],
|
|
1805
|
+
});
|
|
1806
|
+
|
|
1807
|
+
console.log("Workflow started:", result.runKey);
|
|
1808
|
+
|
|
1809
|
+
// Wait for completion (or poll with getStatus)
|
|
1810
|
+
const output = await completionPromise;
|
|
1811
|
+
console.log("Extracted data:", output);
|
|
1812
|
+
|
|
1813
|
+
// Cleanup
|
|
1814
|
+
await client.documents.delete(metadata.documentId);
|
|
1815
|
+
await client.destroy();
|
|
1816
|
+
|
|
1817
|
+
return output;
|
|
1818
|
+
}
|
|
1819
|
+
```
|
|
1820
|
+
|
|
1821
|
+
### Polling for Completion
|
|
1822
|
+
|
|
1823
|
+
If you prefer polling over WebSocket events:
|
|
1824
|
+
|
|
1825
|
+
```typescript
|
|
1826
|
+
async function waitForCompletion(
|
|
1827
|
+
client: JsBaoClient,
|
|
1828
|
+
workflowKey: string,
|
|
1829
|
+
runKey: string,
|
|
1830
|
+
timeoutMs = 60000,
|
|
1831
|
+
intervalMs = 2000
|
|
1832
|
+
): Promise<any> {
|
|
1833
|
+
const startTime = Date.now();
|
|
1834
|
+
|
|
1835
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
1836
|
+
const status = await client.workflows.getStatus(workflowKey, runKey);
|
|
1837
|
+
|
|
1838
|
+
if (status.status === "complete") {
|
|
1839
|
+
return status.output;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
if (status.status === "failed") {
|
|
1843
|
+
throw new Error(`Workflow failed: ${status.error}`);
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
if (status.status === "terminated") {
|
|
1847
|
+
throw new Error("Workflow was terminated");
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// Still running, wait and retry
|
|
1851
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
throw new Error("Workflow timed out");
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
// Usage
|
|
1858
|
+
const result = await client.workflows.start("my-workflow", { data: "..." });
|
|
1859
|
+
const output = await waitForCompletion(client, "my-workflow", result.runKey);
|
|
1860
|
+
```
|
|
1861
|
+
|
|
1323
1862
|
## Integrations API
|
|
1324
1863
|
|
|
1325
1864
|
Proxy HTTP calls through the tenant-specific integrations defined in the admin UI:
|
|
@@ -2054,7 +2593,7 @@ See [LOCAL_TESTING.md](./LOCAL_TESTING.md) for a comprehensive guide on testing
|
|
|
2054
2593
|
**Quick test:**
|
|
2055
2594
|
|
|
2056
2595
|
```bash
|
|
2057
|
-
|
|
2596
|
+
pnpm run build && pnpm pack
|
|
2058
2597
|
cd ../../../ && mkdir test-package && cd test-package
|
|
2059
2598
|
npm init -y && npm install ../js-bao-wss/src/client/js-bao-wss-client-1.0.0.tgz
|
|
2060
2599
|
echo 'import {JsBaoClient} from "js-bao-wss-client"; console.log("✅ Works!")' > test.js
|
|
@@ -2065,16 +2604,16 @@ sed -i '' 's/"type": "commonjs"/"type": "module"/' package.json && node test.js
|
|
|
2065
2604
|
|
|
2066
2605
|
```bash
|
|
2067
2606
|
cd tests
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2607
|
+
pnpm install
|
|
2608
|
+
pnpm run test:esm # Test ESM imports
|
|
2609
|
+
pnpm run test:umd # Instructions for UMD testing
|
|
2071
2610
|
```
|
|
2072
2611
|
|
|
2073
2612
|
### Build Commands
|
|
2074
2613
|
|
|
2075
2614
|
```bash
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2615
|
+
pnpm run build # Build both ESM and UMD
|
|
2616
|
+
pnpm run build:esm # Build ESM only
|
|
2617
|
+
pnpm run build:umd # Build UMD only
|
|
2618
|
+
pnpm pack # Create publishable package
|
|
2080
2619
|
```
|