@dispatchtickets/sdk 0.1.0 → 0.3.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 +111 -0
- package/dist/index.cjs +113 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +272 -6
- package/dist/index.d.ts +272 -6
- package/dist/index.js +110 -3
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -46,14 +46,49 @@ const client = new DispatchTickets({
|
|
|
46
46
|
timeout: 30000, // Optional, request timeout in ms
|
|
47
47
|
maxRetries: 3, // Optional, retry count for failed requests
|
|
48
48
|
debug: false, // Optional, enable debug logging
|
|
49
|
+
fetch: customFetch, // Optional, custom fetch for testing
|
|
49
50
|
});
|
|
50
51
|
```
|
|
51
52
|
|
|
52
53
|
## Resources
|
|
53
54
|
|
|
55
|
+
### Accounts
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// Get current account
|
|
59
|
+
const account = await client.accounts.me();
|
|
60
|
+
|
|
61
|
+
// Get usage statistics
|
|
62
|
+
const usage = await client.accounts.getUsage();
|
|
63
|
+
console.log(`${usage.ticketsThisMonth}/${usage.plan?.ticketLimit} tickets used`);
|
|
64
|
+
|
|
65
|
+
// List API keys
|
|
66
|
+
const apiKeys = await client.accounts.listApiKeys();
|
|
67
|
+
|
|
68
|
+
// Create a new API key
|
|
69
|
+
const newKey = await client.accounts.createApiKey({
|
|
70
|
+
name: 'Production',
|
|
71
|
+
allBrands: true, // or brandIds: ['br_123']
|
|
72
|
+
});
|
|
73
|
+
console.log('Save this key:', newKey.key); // Only shown once!
|
|
74
|
+
|
|
75
|
+
// Update API key scope
|
|
76
|
+
await client.accounts.updateApiKeyScope('key_abc', {
|
|
77
|
+
allBrands: false,
|
|
78
|
+
brandIds: ['br_123', 'br_456'],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Revoke an API key
|
|
82
|
+
await client.accounts.revokeApiKey('key_abc');
|
|
83
|
+
```
|
|
84
|
+
|
|
54
85
|
### Brands
|
|
55
86
|
|
|
56
87
|
```typescript
|
|
88
|
+
// Get inbound email address for a brand
|
|
89
|
+
const inboundEmail = client.brands.getInboundEmail('br_abc123');
|
|
90
|
+
// Returns: br_abc123@inbound.dispatchtickets.com
|
|
91
|
+
|
|
57
92
|
// Create a brand
|
|
58
93
|
const brand = await client.brands.create({
|
|
59
94
|
name: 'Acme Support',
|
|
@@ -292,6 +327,43 @@ try {
|
|
|
292
327
|
}
|
|
293
328
|
```
|
|
294
329
|
|
|
330
|
+
## Webhook Events
|
|
331
|
+
|
|
332
|
+
Handle webhook events with full type safety:
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
import {
|
|
336
|
+
DispatchTickets,
|
|
337
|
+
parseWebhookEvent,
|
|
338
|
+
isTicketCreatedEvent,
|
|
339
|
+
isTicketUpdatedEvent,
|
|
340
|
+
isCommentCreatedEvent,
|
|
341
|
+
} from '@dispatchtickets/sdk';
|
|
342
|
+
|
|
343
|
+
// Parse and validate webhook payload
|
|
344
|
+
const event = parseWebhookEvent(req.body);
|
|
345
|
+
|
|
346
|
+
// Use type guards for type-safe event handling
|
|
347
|
+
if (isTicketCreatedEvent(event)) {
|
|
348
|
+
// event.data is typed as TicketCreatedData
|
|
349
|
+
console.log('New ticket:', event.data.title);
|
|
350
|
+
console.log('Priority:', event.data.priority);
|
|
351
|
+
console.log('Customer:', event.data.customerEmail);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (isTicketUpdatedEvent(event)) {
|
|
355
|
+
// event.data is typed as TicketUpdatedData
|
|
356
|
+
console.log('Ticket updated:', event.data.id);
|
|
357
|
+
console.log('Changed fields:', event.data.changes);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (isCommentCreatedEvent(event)) {
|
|
361
|
+
// event.data is typed as CommentCreatedData
|
|
362
|
+
console.log('New comment on', event.data.ticketNumber);
|
|
363
|
+
console.log('Author:', event.data.comment.authorType);
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
295
367
|
## Webhook Verification
|
|
296
368
|
|
|
297
369
|
```typescript
|
|
@@ -322,6 +394,45 @@ app.post('/webhooks', (req, res) => {
|
|
|
322
394
|
});
|
|
323
395
|
```
|
|
324
396
|
|
|
397
|
+
## Testing
|
|
398
|
+
|
|
399
|
+
Use the custom `fetch` option to mock API responses in tests:
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
import { DispatchTickets } from '@dispatchtickets/sdk';
|
|
403
|
+
import { vi } from 'vitest';
|
|
404
|
+
|
|
405
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
406
|
+
ok: true,
|
|
407
|
+
status: 200,
|
|
408
|
+
headers: { get: () => 'application/json' },
|
|
409
|
+
json: () => Promise.resolve([{ id: 'br_123', name: 'Test' }]),
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const client = new DispatchTickets({
|
|
413
|
+
apiKey: 'sk_test_123',
|
|
414
|
+
fetch: mockFetch,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const brands = await client.brands.list();
|
|
418
|
+
expect(brands).toHaveLength(1);
|
|
419
|
+
expect(mockFetch).toHaveBeenCalled();
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## Examples
|
|
423
|
+
|
|
424
|
+
See the `/examples` directory for complete working examples:
|
|
425
|
+
|
|
426
|
+
- **[express-webhook.ts](./examples/express-webhook.ts)** - Express.js webhook handler with signature verification
|
|
427
|
+
- **[nextjs-api-route.ts](./examples/nextjs-api-route.ts)** - Next.js App Router webhook handler
|
|
428
|
+
- **[basic-usage.ts](./examples/basic-usage.ts)** - Common SDK operations (tickets, comments, pagination)
|
|
429
|
+
|
|
430
|
+
## Links
|
|
431
|
+
|
|
432
|
+
- [API Reference (Swagger)](https://dispatch-tickets-api.onrender.com/docs)
|
|
433
|
+
- [GitHub](https://github.com/Epic-Design-Labs/app-dispatchtickets-sdk)
|
|
434
|
+
- [Changelog](./CHANGELOG.md)
|
|
435
|
+
|
|
325
436
|
## License
|
|
326
437
|
|
|
327
438
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -76,8 +76,10 @@ var NetworkError = class extends DispatchTicketsError {
|
|
|
76
76
|
// src/utils/http.ts
|
|
77
77
|
var HttpClient = class {
|
|
78
78
|
config;
|
|
79
|
+
fetchFn;
|
|
79
80
|
constructor(config) {
|
|
80
81
|
this.config = config;
|
|
82
|
+
this.fetchFn = config.fetch ?? fetch;
|
|
81
83
|
}
|
|
82
84
|
/**
|
|
83
85
|
* Execute an HTTP request with retry logic
|
|
@@ -157,7 +159,7 @@ var HttpClient = class {
|
|
|
157
159
|
console.log("[DispatchTickets] Body:", JSON.stringify(body, null, 2));
|
|
158
160
|
}
|
|
159
161
|
}
|
|
160
|
-
const response = await
|
|
162
|
+
const response = await this.fetchFn(url, {
|
|
161
163
|
method,
|
|
162
164
|
headers,
|
|
163
165
|
body: body ? JSON.stringify(body) : void 0,
|
|
@@ -270,6 +272,52 @@ var BaseResource = class {
|
|
|
270
272
|
}
|
|
271
273
|
};
|
|
272
274
|
|
|
275
|
+
// src/resources/accounts.ts
|
|
276
|
+
var AccountsResource = class extends BaseResource {
|
|
277
|
+
/**
|
|
278
|
+
* Get the current account
|
|
279
|
+
*/
|
|
280
|
+
async me() {
|
|
281
|
+
return this._get("/accounts/me");
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Get usage statistics for the current account
|
|
285
|
+
*/
|
|
286
|
+
async getUsage() {
|
|
287
|
+
return this._get("/accounts/me/usage");
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* List all API keys for the current account
|
|
291
|
+
*/
|
|
292
|
+
async listApiKeys() {
|
|
293
|
+
return this._get("/accounts/me/api-keys");
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Create a new API key
|
|
297
|
+
*
|
|
298
|
+
* Note: The full key value is only returned once on creation.
|
|
299
|
+
* Store it securely as it cannot be retrieved again.
|
|
300
|
+
*/
|
|
301
|
+
async createApiKey(data) {
|
|
302
|
+
return this._post("/accounts/me/api-keys", data);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Update the brand scope for an API key
|
|
306
|
+
*/
|
|
307
|
+
async updateApiKeyScope(keyId, data) {
|
|
308
|
+
return this._patch(
|
|
309
|
+
`/accounts/me/api-keys/${keyId}/scope`,
|
|
310
|
+
data
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Revoke an API key
|
|
315
|
+
*/
|
|
316
|
+
async revokeApiKey(keyId) {
|
|
317
|
+
await this._delete(`/accounts/me/api-keys/${keyId}`);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
273
321
|
// src/resources/brands.ts
|
|
274
322
|
var BrandsResource = class extends BaseResource {
|
|
275
323
|
/**
|
|
@@ -320,6 +368,28 @@ var BrandsResource = class extends BaseResource {
|
|
|
320
368
|
async updateSchema(brandId, schema) {
|
|
321
369
|
return this._put(`/brands/${brandId}/schema`, schema);
|
|
322
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Get the inbound email address for a brand
|
|
373
|
+
*
|
|
374
|
+
* Emails sent to this address will automatically create tickets.
|
|
375
|
+
*
|
|
376
|
+
* @param brandId - The brand ID
|
|
377
|
+
* @param domain - Optional custom inbound domain (default: inbound.dispatchtickets.com)
|
|
378
|
+
* @returns The inbound email address
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```typescript
|
|
382
|
+
* const email = client.brands.getInboundEmail('br_abc123');
|
|
383
|
+
* // Returns: br_abc123@inbound.dispatchtickets.com
|
|
384
|
+
*
|
|
385
|
+
* // With custom domain:
|
|
386
|
+
* const customEmail = client.brands.getInboundEmail('br_abc123', 'support.mycompany.com');
|
|
387
|
+
* // Returns: br_abc123@support.mycompany.com
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
390
|
+
getInboundEmail(brandId, domain = "inbound.dispatchtickets.com") {
|
|
391
|
+
return `${brandId}@${domain}`;
|
|
392
|
+
}
|
|
323
393
|
};
|
|
324
394
|
|
|
325
395
|
// src/resources/tickets.ts
|
|
@@ -834,6 +904,10 @@ var webhookUtils = {
|
|
|
834
904
|
// src/client.ts
|
|
835
905
|
var DispatchTickets = class {
|
|
836
906
|
http;
|
|
907
|
+
/**
|
|
908
|
+
* Accounts resource for managing the current account and API keys
|
|
909
|
+
*/
|
|
910
|
+
accounts;
|
|
837
911
|
/**
|
|
838
912
|
* Brands (workspaces) resource
|
|
839
913
|
*/
|
|
@@ -883,9 +957,11 @@ var DispatchTickets = class {
|
|
|
883
957
|
apiKey: config.apiKey,
|
|
884
958
|
timeout: config.timeout ?? 3e4,
|
|
885
959
|
maxRetries: config.maxRetries ?? 3,
|
|
886
|
-
debug: config.debug ?? false
|
|
960
|
+
debug: config.debug ?? false,
|
|
961
|
+
fetch: config.fetch
|
|
887
962
|
};
|
|
888
963
|
this.http = new HttpClient(httpConfig);
|
|
964
|
+
this.accounts = new AccountsResource(this.http);
|
|
889
965
|
this.brands = new BrandsResource(this.http);
|
|
890
966
|
this.tickets = new TicketsResource(this.http);
|
|
891
967
|
this.comments = new CommentsResource(this.http);
|
|
@@ -898,6 +974,37 @@ var DispatchTickets = class {
|
|
|
898
974
|
}
|
|
899
975
|
};
|
|
900
976
|
|
|
977
|
+
// src/types/events.ts
|
|
978
|
+
function isTicketCreatedEvent(event) {
|
|
979
|
+
return event.event === "ticket.created";
|
|
980
|
+
}
|
|
981
|
+
function isTicketUpdatedEvent(event) {
|
|
982
|
+
return event.event === "ticket.updated";
|
|
983
|
+
}
|
|
984
|
+
function isCommentCreatedEvent(event) {
|
|
985
|
+
return event.event === "ticket.comment.created";
|
|
986
|
+
}
|
|
987
|
+
function parseWebhookEvent(payload) {
|
|
988
|
+
const event = typeof payload === "string" ? JSON.parse(payload) : payload;
|
|
989
|
+
if (!event || typeof event !== "object") {
|
|
990
|
+
throw new Error("Invalid webhook payload: expected object");
|
|
991
|
+
}
|
|
992
|
+
if (!event.event || typeof event.event !== "string") {
|
|
993
|
+
throw new Error("Invalid webhook payload: missing event type");
|
|
994
|
+
}
|
|
995
|
+
if (!event.id || typeof event.id !== "string") {
|
|
996
|
+
throw new Error("Invalid webhook payload: missing event id");
|
|
997
|
+
}
|
|
998
|
+
if (!event.data || typeof event.data !== "object") {
|
|
999
|
+
throw new Error("Invalid webhook payload: missing data");
|
|
1000
|
+
}
|
|
1001
|
+
const validEvents = ["ticket.created", "ticket.updated", "ticket.comment.created"];
|
|
1002
|
+
if (!validEvents.includes(event.event)) {
|
|
1003
|
+
throw new Error(`Invalid webhook payload: unknown event type "${event.event}"`);
|
|
1004
|
+
}
|
|
1005
|
+
return event;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
901
1008
|
// src/utils/pagination.ts
|
|
902
1009
|
async function collectAll(iterable) {
|
|
903
1010
|
const items = [];
|
|
@@ -918,6 +1025,10 @@ exports.ServerError = ServerError;
|
|
|
918
1025
|
exports.TimeoutError = TimeoutError;
|
|
919
1026
|
exports.ValidationError = ValidationError;
|
|
920
1027
|
exports.collectAll = collectAll;
|
|
1028
|
+
exports.isCommentCreatedEvent = isCommentCreatedEvent;
|
|
1029
|
+
exports.isTicketCreatedEvent = isTicketCreatedEvent;
|
|
1030
|
+
exports.isTicketUpdatedEvent = isTicketUpdatedEvent;
|
|
1031
|
+
exports.parseWebhookEvent = parseWebhookEvent;
|
|
921
1032
|
exports.webhookUtils = webhookUtils;
|
|
922
1033
|
//# sourceMappingURL=index.cjs.map
|
|
923
1034
|
//# sourceMappingURL=index.cjs.map
|