@dispatchtickets/sdk 0.5.0 → 0.7.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/dist/index.cjs +311 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +471 -1
- package/dist/index.d.ts +471 -1
- package/dist/index.js +311 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -551,6 +551,66 @@ var BrandsResource = class extends BaseResource {
|
|
|
551
551
|
getInboundEmail(brandId, domain = "inbound.dispatchtickets.com") {
|
|
552
552
|
return `${brandId}@${domain}`;
|
|
553
553
|
}
|
|
554
|
+
// ===========================================================================
|
|
555
|
+
// Portal Token Management
|
|
556
|
+
// ===========================================================================
|
|
557
|
+
/**
|
|
558
|
+
* Generate a portal token for a customer
|
|
559
|
+
*
|
|
560
|
+
* Use this for "authenticated mode" where your backend already knows who the
|
|
561
|
+
* customer is (they're logged into your app). Generate a portal token and
|
|
562
|
+
* pass it to your frontend to initialize the DispatchPortal client.
|
|
563
|
+
*
|
|
564
|
+
* @param brandId - The brand ID
|
|
565
|
+
* @param data - Customer email and optional name
|
|
566
|
+
* @returns Portal token response with JWT token and expiry
|
|
567
|
+
*
|
|
568
|
+
* @example
|
|
569
|
+
* ```typescript
|
|
570
|
+
* // On your backend (Node.js, Next.js API route, etc.)
|
|
571
|
+
* const { token, expiresAt } = await client.brands.generatePortalToken(
|
|
572
|
+
* 'br_abc123',
|
|
573
|
+
* {
|
|
574
|
+
* email: req.user.email,
|
|
575
|
+
* name: req.user.name,
|
|
576
|
+
* }
|
|
577
|
+
* );
|
|
578
|
+
*
|
|
579
|
+
* // Return token to your frontend
|
|
580
|
+
* res.json({ portalToken: token });
|
|
581
|
+
* ```
|
|
582
|
+
*/
|
|
583
|
+
async generatePortalToken(brandId, data) {
|
|
584
|
+
return this._post(`/brands/${brandId}/portal/token`, data);
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Send a magic link email to a customer
|
|
588
|
+
*
|
|
589
|
+
* Use this for "self-auth mode" where customers access the portal without
|
|
590
|
+
* being logged into your app. They receive an email with a link that
|
|
591
|
+
* authenticates them directly.
|
|
592
|
+
*
|
|
593
|
+
* @param brandId - The brand ID
|
|
594
|
+
* @param data - Customer email and portal URL to redirect to
|
|
595
|
+
* @returns Success status
|
|
596
|
+
*
|
|
597
|
+
* @example
|
|
598
|
+
* ```typescript
|
|
599
|
+
* // Customer requests access via your portal login form
|
|
600
|
+
* await client.brands.sendPortalMagicLink('br_abc123', {
|
|
601
|
+
* email: 'customer@example.com',
|
|
602
|
+
* portalUrl: 'https://yourapp.com/support/portal',
|
|
603
|
+
* });
|
|
604
|
+
*
|
|
605
|
+
* // Customer receives email with link like:
|
|
606
|
+
* // https://yourapp.com/support/portal?token=xyz...
|
|
607
|
+
*
|
|
608
|
+
* // Your portal page then calls DispatchPortal.verify(token)
|
|
609
|
+
* ```
|
|
610
|
+
*/
|
|
611
|
+
async sendPortalMagicLink(brandId, data) {
|
|
612
|
+
return this._post(`/brands/${brandId}/portal/magic-link`, data);
|
|
613
|
+
}
|
|
554
614
|
};
|
|
555
615
|
|
|
556
616
|
// src/resources/tickets.ts
|
|
@@ -1296,6 +1356,256 @@ var DispatchTickets = class {
|
|
|
1296
1356
|
}
|
|
1297
1357
|
};
|
|
1298
1358
|
|
|
1359
|
+
// src/resources/portal-tickets.ts
|
|
1360
|
+
var PortalTicketsResource = class extends BaseResource {
|
|
1361
|
+
/**
|
|
1362
|
+
* List the customer's tickets
|
|
1363
|
+
*
|
|
1364
|
+
* Returns a paginated list of tickets belonging to the authenticated customer.
|
|
1365
|
+
*
|
|
1366
|
+
* @param filters - Optional filters and pagination
|
|
1367
|
+
* @param options - Request options
|
|
1368
|
+
* @returns Paginated ticket list
|
|
1369
|
+
*
|
|
1370
|
+
* @example
|
|
1371
|
+
* ```typescript
|
|
1372
|
+
* // List all tickets
|
|
1373
|
+
* const { data: tickets, pagination } = await portal.tickets.list();
|
|
1374
|
+
*
|
|
1375
|
+
* // Filter by status
|
|
1376
|
+
* const openTickets = await portal.tickets.list({ status: 'open' });
|
|
1377
|
+
*
|
|
1378
|
+
* // Paginate
|
|
1379
|
+
* const page2 = await portal.tickets.list({ cursor: pagination.nextCursor });
|
|
1380
|
+
* ```
|
|
1381
|
+
*/
|
|
1382
|
+
async list(filters, options) {
|
|
1383
|
+
const query = this.buildListQuery(filters);
|
|
1384
|
+
return this._get("/portal/tickets", query, options);
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Iterate through all tickets with automatic pagination
|
|
1388
|
+
*
|
|
1389
|
+
* @param filters - Optional filters (cursor is managed automatically)
|
|
1390
|
+
* @returns Async iterator of tickets
|
|
1391
|
+
*
|
|
1392
|
+
* @example
|
|
1393
|
+
* ```typescript
|
|
1394
|
+
* for await (const ticket of portal.tickets.listAll({ status: 'open' })) {
|
|
1395
|
+
* console.log(ticket.title);
|
|
1396
|
+
* }
|
|
1397
|
+
* ```
|
|
1398
|
+
*/
|
|
1399
|
+
async *listAll(filters) {
|
|
1400
|
+
let cursor;
|
|
1401
|
+
let hasMore = true;
|
|
1402
|
+
while (hasMore) {
|
|
1403
|
+
const response = await this.list({ ...filters, cursor });
|
|
1404
|
+
for (const ticket of response.data) {
|
|
1405
|
+
yield ticket;
|
|
1406
|
+
}
|
|
1407
|
+
cursor = response.pagination.nextCursor ?? void 0;
|
|
1408
|
+
hasMore = response.pagination.hasMore;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Get a ticket by ID
|
|
1413
|
+
*
|
|
1414
|
+
* Returns the ticket with its comments (excludes internal comments).
|
|
1415
|
+
*
|
|
1416
|
+
* @param ticketId - Ticket ID
|
|
1417
|
+
* @param options - Request options
|
|
1418
|
+
* @returns Ticket detail with comments
|
|
1419
|
+
*
|
|
1420
|
+
* @example
|
|
1421
|
+
* ```typescript
|
|
1422
|
+
* const ticket = await portal.tickets.get('tkt_abc123');
|
|
1423
|
+
* console.log(ticket.title);
|
|
1424
|
+
* console.log(`${ticket.comments.length} comments`);
|
|
1425
|
+
* ```
|
|
1426
|
+
*/
|
|
1427
|
+
async get(ticketId, options) {
|
|
1428
|
+
return this._get(`/portal/tickets/${ticketId}`, void 0, options);
|
|
1429
|
+
}
|
|
1430
|
+
/**
|
|
1431
|
+
* Create a new ticket
|
|
1432
|
+
*
|
|
1433
|
+
* @param data - Ticket data
|
|
1434
|
+
* @param options - Request options including idempotency key
|
|
1435
|
+
* @returns Created ticket
|
|
1436
|
+
*
|
|
1437
|
+
* @example
|
|
1438
|
+
* ```typescript
|
|
1439
|
+
* const ticket = await portal.tickets.create({
|
|
1440
|
+
* title: 'Need help with billing',
|
|
1441
|
+
* body: 'I was charged twice for my subscription...',
|
|
1442
|
+
* });
|
|
1443
|
+
* ```
|
|
1444
|
+
*/
|
|
1445
|
+
async create(data, options) {
|
|
1446
|
+
return this._post("/portal/tickets", data, {
|
|
1447
|
+
idempotencyKey: options?.idempotencyKey,
|
|
1448
|
+
signal: options?.signal
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Add a comment to a ticket
|
|
1453
|
+
*
|
|
1454
|
+
* @param ticketId - Ticket ID
|
|
1455
|
+
* @param body - Comment text
|
|
1456
|
+
* @param options - Request options including idempotency key
|
|
1457
|
+
* @returns Created comment
|
|
1458
|
+
*
|
|
1459
|
+
* @example
|
|
1460
|
+
* ```typescript
|
|
1461
|
+
* const comment = await portal.tickets.addComment(
|
|
1462
|
+
* 'tkt_abc123',
|
|
1463
|
+
* 'Here is the additional information you requested...'
|
|
1464
|
+
* );
|
|
1465
|
+
* ```
|
|
1466
|
+
*/
|
|
1467
|
+
async addComment(ticketId, body, options) {
|
|
1468
|
+
return this._post(`/portal/tickets/${ticketId}/comments`, { body }, {
|
|
1469
|
+
idempotencyKey: options?.idempotencyKey,
|
|
1470
|
+
signal: options?.signal
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Build query parameters from filters
|
|
1475
|
+
*/
|
|
1476
|
+
buildListQuery(filters) {
|
|
1477
|
+
if (!filters) return void 0;
|
|
1478
|
+
const query = {};
|
|
1479
|
+
if (filters.status) query.status = filters.status;
|
|
1480
|
+
if (filters.sort) query.sort = filters.sort;
|
|
1481
|
+
if (filters.order) query.order = filters.order;
|
|
1482
|
+
if (filters.limit) query.limit = filters.limit;
|
|
1483
|
+
if (filters.cursor) query.cursor = filters.cursor;
|
|
1484
|
+
return Object.keys(query).length > 0 ? query : void 0;
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
|
|
1488
|
+
// src/portal.ts
|
|
1489
|
+
var DEFAULT_BASE_URL = "https://dispatch-tickets-api.onrender.com/v1";
|
|
1490
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
1491
|
+
var DispatchPortal = class {
|
|
1492
|
+
http;
|
|
1493
|
+
config;
|
|
1494
|
+
/**
|
|
1495
|
+
* Tickets resource for viewing and creating tickets
|
|
1496
|
+
*
|
|
1497
|
+
* @example
|
|
1498
|
+
* ```typescript
|
|
1499
|
+
* // List tickets
|
|
1500
|
+
* const { data: tickets } = await portal.tickets.list();
|
|
1501
|
+
*
|
|
1502
|
+
* // Create a ticket
|
|
1503
|
+
* const ticket = await portal.tickets.create({
|
|
1504
|
+
* title: 'Need help',
|
|
1505
|
+
* body: 'Something is broken...',
|
|
1506
|
+
* });
|
|
1507
|
+
*
|
|
1508
|
+
* // Add a comment
|
|
1509
|
+
* await portal.tickets.addComment(ticket.id, 'Here is more info...');
|
|
1510
|
+
* ```
|
|
1511
|
+
*/
|
|
1512
|
+
tickets;
|
|
1513
|
+
/**
|
|
1514
|
+
* Create a new Dispatch Portal client
|
|
1515
|
+
*
|
|
1516
|
+
* @param config - Portal client configuration
|
|
1517
|
+
* @throws Error if token is not provided
|
|
1518
|
+
*/
|
|
1519
|
+
constructor(config) {
|
|
1520
|
+
if (!config.token) {
|
|
1521
|
+
throw new Error("Portal token is required");
|
|
1522
|
+
}
|
|
1523
|
+
this.config = config;
|
|
1524
|
+
const httpConfig = {
|
|
1525
|
+
baseUrl: config.baseUrl || DEFAULT_BASE_URL,
|
|
1526
|
+
apiKey: config.token,
|
|
1527
|
+
// HttpClient uses this as Bearer token
|
|
1528
|
+
timeout: config.timeout ?? DEFAULT_TIMEOUT,
|
|
1529
|
+
maxRetries: 2,
|
|
1530
|
+
// Fewer retries for portal (end-user experience)
|
|
1531
|
+
fetch: config.fetch
|
|
1532
|
+
};
|
|
1533
|
+
this.http = new HttpClient(httpConfig);
|
|
1534
|
+
this.tickets = new PortalTicketsResource(this.http);
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Verify a magic link token and get a portal token
|
|
1538
|
+
*
|
|
1539
|
+
* Call this when the customer clicks the magic link and lands on your portal.
|
|
1540
|
+
* The magic link contains a short-lived token that can be exchanged for a
|
|
1541
|
+
* longer-lived portal token.
|
|
1542
|
+
*
|
|
1543
|
+
* @param magicLinkToken - The token from the magic link URL
|
|
1544
|
+
* @param baseUrl - Optional API base URL
|
|
1545
|
+
* @param fetchFn - Optional custom fetch function (for testing)
|
|
1546
|
+
* @returns Portal token response
|
|
1547
|
+
*
|
|
1548
|
+
* @example
|
|
1549
|
+
* ```typescript
|
|
1550
|
+
* // Customer lands on: https://yourapp.com/portal?token=xyz
|
|
1551
|
+
* const urlToken = new URLSearchParams(window.location.search).get('token');
|
|
1552
|
+
*
|
|
1553
|
+
* const { token, email, name } = await DispatchPortal.verify(urlToken);
|
|
1554
|
+
*
|
|
1555
|
+
* // Store token and create client
|
|
1556
|
+
* localStorage.setItem('portalToken', token);
|
|
1557
|
+
* const portal = new DispatchPortal({ token });
|
|
1558
|
+
* ```
|
|
1559
|
+
*/
|
|
1560
|
+
static async verify(magicLinkToken, baseUrl = DEFAULT_BASE_URL, fetchFn = fetch) {
|
|
1561
|
+
const url = `${baseUrl}/portal/verify`;
|
|
1562
|
+
const response = await fetchFn(url, {
|
|
1563
|
+
method: "POST",
|
|
1564
|
+
headers: {
|
|
1565
|
+
"Content-Type": "application/json",
|
|
1566
|
+
"Accept": "application/json"
|
|
1567
|
+
},
|
|
1568
|
+
body: JSON.stringify({ token: magicLinkToken })
|
|
1569
|
+
});
|
|
1570
|
+
if (!response.ok) {
|
|
1571
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1572
|
+
throw new Error(errorData.message || "Failed to verify magic link token");
|
|
1573
|
+
}
|
|
1574
|
+
return response.json();
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Refresh the current portal token
|
|
1578
|
+
*
|
|
1579
|
+
* Call this before the token expires to get a new token with extended expiry.
|
|
1580
|
+
* The new token will have the same customer context.
|
|
1581
|
+
*
|
|
1582
|
+
* @returns New portal token response
|
|
1583
|
+
*
|
|
1584
|
+
* @example
|
|
1585
|
+
* ```typescript
|
|
1586
|
+
* // Check if token is close to expiry and refresh
|
|
1587
|
+
* const { token: newToken, expiresAt } = await portal.refresh();
|
|
1588
|
+
*
|
|
1589
|
+
* // Create new client with refreshed token
|
|
1590
|
+
* const newPortal = new DispatchPortal({ token: newToken });
|
|
1591
|
+
* ```
|
|
1592
|
+
*/
|
|
1593
|
+
async refresh() {
|
|
1594
|
+
return this.http.request({
|
|
1595
|
+
method: "POST",
|
|
1596
|
+
path: "/portal/refresh"
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Get the current token
|
|
1601
|
+
*
|
|
1602
|
+
* Useful for storing/passing the token.
|
|
1603
|
+
*/
|
|
1604
|
+
get token() {
|
|
1605
|
+
return this.config.token;
|
|
1606
|
+
}
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1299
1609
|
// src/types/events.ts
|
|
1300
1610
|
function isTicketCreatedEvent(event) {
|
|
1301
1611
|
return event.event === "ticket.created";
|
|
@@ -1338,6 +1648,7 @@ async function collectAll(iterable) {
|
|
|
1338
1648
|
|
|
1339
1649
|
exports.AuthenticationError = AuthenticationError;
|
|
1340
1650
|
exports.ConflictError = ConflictError;
|
|
1651
|
+
exports.DispatchPortal = DispatchPortal;
|
|
1341
1652
|
exports.DispatchTickets = DispatchTickets;
|
|
1342
1653
|
exports.DispatchTicketsError = DispatchTicketsError;
|
|
1343
1654
|
exports.NetworkError = NetworkError;
|