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