diff --git a/.github/.release-please-live-manifest.json b/.github/.release-please-live-manifest.json index 88d1d60..c04b615 100644 --- a/.github/.release-please-live-manifest.json +++ b/.github/.release-please-live-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0" + ".": "1.0.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3512201 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +## [1.0.0](https://github.com/respond-io/typescript-sdk/compare/v0.1.0...v1.0.0) (2025-11-12) + + +### ⚠ BREAKING CHANGES + +* Bump breaking changes version + +### Features + +* Bump breaking changes version ([4c82ff7](https://github.com/respond-io/typescript-sdk/commit/4c82ff7e2fbb5aef3056460c2a9fb9a4ada14d1b)) + + +### Bug Fixes + +* Update License Content ([15d7c3b](https://github.com/respond-io/typescript-sdk/commit/15d7c3b5a7421e0bb6ae12a48f56bdb43d724cef)) diff --git a/README.md b/README.md index 595b991..e3aa664 100644 --- a/README.md +++ b/README.md @@ -149,9 +149,7 @@ await client.contacts.create('email:user@example.com', { ```typescript await client.contacts.update('id:123', { firstName: 'Jane', - custom_fields: [ - { name: 'Role', value: 'Senior Developer' }, - ], + custom_fields: [{ name: 'Role', value: 'Senior Developer' }], }); ``` @@ -166,23 +164,26 @@ const result = await client.contacts.list({ }); // List with filters -const result = await client.contacts.list({ - search: '', - timezone: 'UTC', - filter: { - $and: [ - { - category: 'contactField', - field: 'assigneeUserId', - operator: 'isEqualTo', - value: '123', - }, - ], +const result = await client.contacts.list( + { + search: '', + timezone: 'UTC', + filter: { + $and: [ + { + category: 'contactField', + field: 'assigneeUserId', + operator: 'isEqualTo', + value: '123', + }, + ], + }, }, -}, { - limit: 50, - cursorId: 0, -}); + { + limit: 50, + cursorId: 0, + } +); ``` #### Manage Tags @@ -259,6 +260,27 @@ const message = await client.messaging.get('id:123', 987654); console.log(message.status); // Message delivery status ``` +#### List Messages + +```typescript +// List all messages for a contact +const result = await client.messaging.list('id:123'); +console.log(result.items); // Array of messages +console.log(result.pagination); // Pagination info + +// List messages with pagination +const result = await client.messaging.list('id:123', { + limit: 50, + cursorId: 100, +}); + +// List messages for different contact identifiers +const result = await client.messaging.list('email:user@example.com'); +const result = await client.messaging.list('phone:+1234567890', { + limit: 20, +}); +``` + ### Comments The Comment API allows you to add internal comments to contacts. @@ -392,7 +414,7 @@ try { console.error('Status:', error.statusCode); console.error('Code:', error.code); console.error('Message:', error.message); - + // Check error type if (error.isRateLimitError()) { console.error('Rate limit reached!'); @@ -563,4 +585,4 @@ Contributions are welcome! Please feel free to submit a Pull Request. --- -Made with ❤️ by the respond.io community \ No newline at end of file +Made with ❤️ by the respond.io community diff --git a/package-lock.json b/package-lock.json index e21a006..a46f33b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@respond-io/typescript-sdk", - "version": "0.2.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@respond-io/typescript-sdk", - "version": "0.2.0", + "version": "1.0.0", "license": "MIT", "dependencies": { "axios": "^1.6.0" diff --git a/package.json b/package.json index 858041e..3b260be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@respond-io/typescript-sdk", - "version": "0.2.0", + "version": "1.0.0", "description": "Official TypeScript SDK for the respond.io Developer API v2", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/clients/messaging.ts b/src/clients/messaging.ts index ea8e74e..8ab7b03 100644 --- a/src/clients/messaging.ts +++ b/src/clients/messaging.ts @@ -4,6 +4,8 @@ import { SendMessageRequest, SendMessageResponse, GetMessageResponse, + PaginationParams, + PaginationResponse, } from '../types/index'; /** @@ -35,4 +37,16 @@ export class MessagingClient { ): Promise { return this.http.get(`/contact/${identifier}/message/${messageId}`); } + + /** + * List messages for a contact + * @param identifier - Contact identifier + * @param pagination - Pagination parameters + */ + async list( + identifier: ContactIdentifier, + pagination?: PaginationParams + ): Promise<{ items: GetMessageResponse[]; pagination: PaginationResponse }> { + return this.http.post(`/contact/${identifier}/message/list`, undefined, pagination); + } } diff --git a/src/types/message.ts b/src/types/message.ts index aae640f..fb80362 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -186,6 +186,17 @@ export interface SendMessageResponse { messageId: number; } +/** + * Message sender + */ +export interface MessageSender { + source: 'user' | 'ai_agent' | 'workflow' | 'api' | 'echo' | 'broadcast'; + userId?: number; + teamId?: number; + workflowId?: number; + broadcastHistoryId?: number; +} + /** * Get message response */ @@ -197,4 +208,5 @@ export interface GetMessageResponse { traffic: MessageTraffic; message: Message; status?: MessageStatusEntry[]; + sender?: MessageSender; } diff --git a/tests/clients/messaging.test.ts b/tests/clients/messaging.test.ts index ed3489e..de75dc8 100644 --- a/tests/clients/messaging.test.ts +++ b/tests/clients/messaging.test.ts @@ -207,5 +207,345 @@ describe('MessagingClient', () => { expect(mockHttp.get).toHaveBeenCalledWith('/contact/phone:+1234567890/message/123'); }); + + it('should get message with sender (user)', async () => { + const mockResponse: GetMessageResponse = { + messageId: 123456, + channelMessageId: 'msg-abc', + contactId: 123, + channelId: 999, + traffic: 'outgoing', + message: { + type: 'text', + text: 'Hello World', + }, + sender: { + source: 'user', + userId: 456, + }, + }; + + mockHttp.get.mockResolvedValueOnce(mockResponse); + + const result = await client.get('id:123', 123456); + + expect(result).toEqual(mockResponse); + expect(result.sender?.source).toBe('user'); + expect(result.sender?.userId).toBe(456); + }); + + it('should get message with sender (ai_agent)', async () => { + const mockResponse: GetMessageResponse = { + messageId: 789, + channelMessageId: 'msg-xyz', + contactId: 123, + channelId: 999, + traffic: 'outgoing', + message: { + type: 'text', + text: 'AI response', + }, + sender: { + source: 'ai_agent', + }, + }; + + mockHttp.get.mockResolvedValueOnce(mockResponse); + + const result = await client.get('id:123', 789); + + expect(result).toEqual(mockResponse); + expect(result.sender?.source).toBe('ai_agent'); + }); + + it('should get message with sender (workflow)', async () => { + const mockResponse: GetMessageResponse = { + messageId: 101, + channelMessageId: 'msg-workflow', + contactId: 123, + channelId: 999, + traffic: 'outgoing', + message: { + type: 'text', + text: 'Workflow message', + }, + sender: { + source: 'workflow', + workflowId: 789, + }, + }; + + mockHttp.get.mockResolvedValueOnce(mockResponse); + + const result = await client.get('id:123', 101); + + expect(result).toEqual(mockResponse); + expect(result.sender?.source).toBe('workflow'); + expect(result.sender?.workflowId).toBe(789); + }); + + it('should get message with sender (api)', async () => { + const mockResponse: GetMessageResponse = { + messageId: 202, + channelMessageId: 'msg-api', + contactId: 123, + channelId: 999, + traffic: 'outgoing', + message: { + type: 'text', + text: 'API message', + }, + sender: { + source: 'api', + }, + }; + + mockHttp.get.mockResolvedValueOnce(mockResponse); + + const result = await client.get('id:123', 202); + + expect(result).toEqual(mockResponse); + expect(result.sender?.source).toBe('api'); + }); + + it('should get message with sender (broadcast)', async () => { + const mockResponse: GetMessageResponse = { + messageId: 303, + channelMessageId: 'msg-broadcast', + contactId: 123, + channelId: 999, + traffic: 'outgoing', + message: { + type: 'text', + text: 'Broadcast message', + }, + sender: { + source: 'broadcast', + broadcastHistoryId: 555, + }, + }; + + mockHttp.get.mockResolvedValueOnce(mockResponse); + + const result = await client.get('id:123', 303); + + expect(result).toEqual(mockResponse); + expect(result.sender?.source).toBe('broadcast'); + expect(result.sender?.broadcastHistoryId).toBe(555); + }); + + it('should get message with sender (echo)', async () => { + const mockResponse: GetMessageResponse = { + messageId: 404, + channelMessageId: 'msg-echo', + contactId: 123, + channelId: 999, + traffic: 'outgoing', + message: { + type: 'text', + text: 'Echo message', + }, + sender: { + source: 'echo', + }, + }; + + mockHttp.get.mockResolvedValueOnce(mockResponse); + + const result = await client.get('id:123', 404); + + expect(result).toEqual(mockResponse); + expect(result.sender?.source).toBe('echo'); + }); + }); + + describe('list', () => { + it('should list messages without pagination', async () => { + const mockResponse = { + items: [ + { + messageId: 123456, + channelMessageId: 'msg-abc', + contactId: 123, + channelId: 999, + traffic: 'outgoing' as const, + message: { + type: 'text' as const, + text: 'Hello World', + }, + status: [ + { + value: 'sent' as const, + timestamp: 1234567890, + }, + ], + sender: { + source: 'user' as const, + userId: 456, + }, + }, + { + messageId: 123457, + channelMessageId: 'msg-def', + contactId: 123, + channelId: 999, + traffic: 'incoming' as const, + message: { + type: 'text' as const, + text: 'Hi there', + }, + }, + ], + pagination: { + next: 'https://api.respond.io/v2/contact/id:123/message/list?limit=10&cursorId=20', + previous: '', + }, + }; + + mockHttp.post.mockResolvedValueOnce(mockResponse); + + const result = await client.list('id:123'); + + expect(result).toEqual(mockResponse); + expect(result.items[0].sender?.source).toBe('user'); + expect(result.items[0].sender?.userId).toBe(456); + expect(result.items[1].sender).toBeUndefined(); + expect(mockHttp.post).toHaveBeenCalledWith( + '/contact/id:123/message/list', + undefined, + undefined + ); + }); + + it('should list messages with pagination', async () => { + const pagination = { limit: 50, cursorId: 100 }; + const mockResponse = { + items: [ + { + messageId: 123456, + channelMessageId: 'msg-abc', + contactId: 123, + channelId: 999, + traffic: 'outgoing' as const, + message: { + type: 'text' as const, + text: 'Hello World', + }, + }, + ], + pagination: { + next: 'https://api.respond.io/v2/contact/id:123/message/list?limit=50&cursorId=150', + previous: + 'https://api.respond.io/v2/contact/id:123/message/list?limit=50&cursorId=50', + }, + }; + + mockHttp.post.mockResolvedValueOnce(mockResponse); + + const result = await client.list('id:123', pagination); + + expect(result).toEqual(mockResponse); + expect(mockHttp.post).toHaveBeenCalledWith( + '/contact/id:123/message/list', + undefined, + pagination + ); + }); + + it('should list messages with email identifier', async () => { + const mockResponse = { + items: [ + { + messageId: 789, + channelMessageId: 'msg-xyz', + contactId: 456, + channelId: 888, + traffic: 'outgoing' as const, + message: { + type: 'text' as const, + text: 'Test message', + }, + }, + ], + pagination: { + next: '', + previous: '', + }, + }; + + mockHttp.post.mockResolvedValueOnce(mockResponse); + + const result = await client.list('email:user@example.com'); + + expect(result).toEqual(mockResponse); + expect(mockHttp.post).toHaveBeenCalledWith( + '/contact/email:user@example.com/message/list', + undefined, + undefined + ); + }); + + it('should list messages with phone identifier', async () => { + const pagination = { limit: 20 }; + const mockResponse = { + items: [], + pagination: { + next: '', + previous: '', + }, + }; + + mockHttp.post.mockResolvedValueOnce(mockResponse); + + await client.list('phone:+1234567890', pagination); + + expect(mockHttp.post).toHaveBeenCalledWith( + '/contact/phone:+1234567890/message/list', + undefined, + pagination + ); + }); + + it('should list messages with cursorId only', async () => { + const pagination = { cursorId: 200 }; + const mockResponse = { + items: [ + { + messageId: 123458, + channelMessageId: 'msg-ghi', + contactId: 123, + channelId: 999, + traffic: 'incoming' as const, + message: { + type: 'attachment' as const, + attachment: { + type: 'image' as const, + url: 'https://example.com/image.jpg', + }, + }, + sender: { + source: 'workflow' as const, + workflowId: 789, + }, + }, + ], + pagination: { + next: '', + previous: '', + }, + }; + + mockHttp.post.mockResolvedValueOnce(mockResponse); + + const result = await client.list('id:123', pagination); + + expect(result.items[0].sender?.source).toBe('workflow'); + expect(result.items[0].sender?.workflowId).toBe(789); + expect(mockHttp.post).toHaveBeenCalledWith( + '/contact/id:123/message/list', + undefined, + pagination + ); + }); }); });