Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Sinch Getting started: Lookup a number
# Sinch Getting started: Lookup a number (Node.js)

This code is related to [Look up a number with the Node.js SDK](https://developers.sinch.com/docs/number-lookup-api-v2/getting-started/node/lookup-number#create-your-file).

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Sinch Node.js Snippet
* See: https://github.com/sinch/sinch-sdk-node/docs/snippets
*/
import { SinchClient } from '@sinch/sdk-core';
import * as dotenv from 'dotenv';
dotenv.config();

async function main() {
const projectId = process.env.SINCH_PROJECT_ID ?? 'MY_PROJECT_ID';
const keyId = process.env.SINCH_KEY_ID ?? 'MY_KEY_ID';
const keySecret = process.env.SINCH_KEY_SECRET ?? 'MY_KEY_SECRET';
const conversationRegion = process.env.SINCH_CONVERSATION_REGION ?? 'MY_CONVERSATION_REGION';

// Channel identities to fetch the last message (can be phone numbers, social media IDs, etc. depending on the channel)
const channelIdentities = ['CHANNEL_IDENTITY_1', 'CHANNEL_IDENTITY_2'];

const sinch = new SinchClient({ projectId, keyId, keySecret, conversationRegion });

try {
const response = await sinch.conversation.messages.listLastMessagesByChannelIdentity({
listLastMessagesByChannelIdentityRequestBody: {
channel_identities: channelIdentities,
messages_source: 'DISPATCH_SOURCE',
},
});
if (response.data.length === 0) {
console.log('No Messages found for the specified identities.');
return;
}
console.log(`✅ Found ${response.data.length} Messages.`);
response.data.forEach((message) => {
console.log(message);
});
} catch (err) {
console.error('❌ Failed to list the Messages for the specified identities:');
console.error(err);
}
}

main();
1 change: 1 addition & 0 deletions docs/snippets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"conversation:messages:send": "node conversation/messages/send.js",
"conversation:messages:get": "node conversation/messages/get.js",
"conversation:messages:list": "node conversation/messages/list.js",
"conversation:messages:listLastMessagesByChannelIdentity": "node conversation/messages/listLastMessagesByChannelIdentity.js",
"conversation:messages:update": "node conversation/messages/update.js",
"conversation:messages:delete": "node conversation/messages/delete.js",
"conversation:templates:v1:create": "node conversation/templates/v1/create.js",
Expand Down
1 change: 1 addition & 0 deletions examples/simple-examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"conversation:messages:sendTextMessage": "ts-node src/conversation/messages/sendTextMessage.ts",
"conversation:messages:get": "ts-node src/conversation/messages/get.ts",
"conversation:messages:list": "ts-node src/conversation/messages/list.ts",
"conversation:messages:listLastMessagesByIdentities": "ts-node src/conversation/messages/listLastMessagesByIdentities.ts",
"conversation:messages:update": "ts-node src/conversation/messages/update.ts",
"conversation:messages:delete": "ts-node src/conversation/messages/delete.ts",
"conversation:conversation:create": "ts-node src/conversation/conversation/create.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Conversation, PageResult } from '@sinch/sdk-core';
import {
getAppIdFromConfig,
getContactIdFromConfig, getConversationIdFromConfig, getPhoneNumberFromConfig,
getPrintFormat,
initConversationService,
printFullResponse,
} from '../../config';

const populateMessagesList = (
conversationPage: PageResult<Conversation.ConversationMessage>,
conversationList: Conversation.ConversationMessage[],
conversationDetailsList: string[],
) => {
conversationPage.data.map((message: Conversation.ConversationMessage) => {
conversationList.push(message);
conversationDetailsList.push(`${message.id} - ${message.accept_time}`);
});
};

(async () => {
console.log('******************************************');
console.log('* Messages_ListMessagesByChannelIdentity *');
console.log('******************************************');

const phoneNumber = getPhoneNumberFromConfig().substring(1);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add a comment to explain "why" .substring(1) or having a dedicated helper function with naming helping to understand


const requestData: Conversation.ListLastMessagesByChannelIdentityRequestData = {
listLastMessagesByChannelIdentityRequestBody: {
channel_identities: [phoneNumber],
messages_source: 'DISPATCH_SOURCE',
},
};

const conversationService = initConversationService();

// ----------------------------------------------
// Method 1: Fetch the data page by page manually
// ----------------------------------------------
let response = await conversationService.messages.listLastMessagesByChannelIdentity(requestData);

const messageList: Conversation.ConversationMessage[] = [];
const messagesDetailsList: string[] = [];

// Loop on all the pages to get all the active numbers
let reachedEndOfPages = false;
while (!reachedEndOfPages) {
populateMessagesList(response, messageList, messagesDetailsList);
if (response.hasNextPage) {
response = await response.nextPage();
} else {
reachedEndOfPages = true;
}
}

const printFormat = getPrintFormat(process.argv);

if (printFormat === 'pretty') {
console.log(messagesDetailsList.length > 0
? 'List of messages:\n' + messagesDetailsList.join('\n')
: 'Sorry, no messages were found.');
} else {
printFullResponse(messageList);
}

// ---------------------------------------------------------------------
// Method 2: Use the iterator and fetch data on more pages automatically
// ---------------------------------------------------------------------
for await (const message of conversationService.messages.listLastMessagesByChannelIdentity(requestData)) {
if (printFormat === 'pretty') {
console.log(`${message.id} - ${message.accept_time}`);
} else {
console.log(message);
}
}

})();
5 changes: 4 additions & 1 deletion packages/conversation/src/models/v1/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ export type ConversationMetadataUpdateStrategy = 'REPLACE' | 'MERGE_PATCH' | str
*/
export type CardHeight = 'UNSPECIFIED_HEIGHT' | 'SHORT' | 'MEDIUM' | 'TALL' | string;

export type ConversationDirection = 'UNDEFINED_DIRECTION' | 'TO_APP' | 'TO_CONTACT' | string;
/**
* The direction of the message flow, indicating whether the message was sent to or from the Conversation API app.
*/
export type ConversationDirection = 'TO_APP' | 'TO_CONTACT' | string;

export type ConversationMergeStrategy = 'MERGE' | string;

Expand Down
1 change: 1 addition & 0 deletions packages/conversation/src/models/v1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export * from './kakaotalk-image';
export * from './kakaotalk-message';
export * from './kakaotalk-pricing';
export * from './list-apps-response';
export * from './list-last-messages-by-channel-identity-request';
export * from './list-message';
export * from './list-section';
export * from './list-webhooks-response';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { ListLastMessagesByChannelIdentityRequest } from './list-last-messages-by-channel-identity-request';
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ConversationChannel } from '../conversation-channel';
import { ConversationDirection, ConversationMessagesView, MessageSource } from '../enums';

/**
* Request body for listing messages by channel identity. NOTE: You can use either contact_ids OR channel_identities, but not both in the same request.
*/
export interface ListLastMessagesByChannelIdentityRequest {
/** Optional. Filter messages by `channel_identity`. */
channel_identities?: string[];
/** Optional. Resource name (id) of the contact. In case the messages source is set to `CONVERSATION_SOURCE`: Can list last messages by contact_id. In case the messages source is set to `DISPATCH_SOURCE`: The field is unsupported and cannot be set. */
contact_ids?: string[];
/** Optional. Resource name (id) of the app. */
app_id?: string;
/** Optional. Specifies the message source for which the request will be processed. Default is `DISPATCH_SOURCE`. */
messages_source?: MessageSource;
/** Optional. Maximum number of messages to fetch. Defaults to 10 and the maximum is 1000. */
page_size?: number;
/** Optional. Next page token previously returned if any. */
page_token?: string;
/** Optional. Specifies the representation in which messages should be returned. Default to `WITH_METADATA`. */
view?: ConversationMessagesView;
/** Optional. Only fetch messages with `accept_time` after this date. */
start_time?: Date;
/** Optional. Only fetch messages with `accept_time` before this date. */
end_time?: Date;
/** Optional. Only fetch messages from the `channel`. */
channel?: ConversationChannel;
/** Optional. Only fetch messages with the specified `direction`. If direction is not specified, it will list both TO_APP and TO_CONTACT messages. */
direction?: ConversationDirection;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ConversationMessagesView, MessageSource } from '../../enums';
import { ConversationDirection, ConversationMessagesView, MessageSource } from '../../enums';
import { ConversationChannel } from '../../conversation-channel';
import { Recipient } from '../../recipient';
import {
Expand All @@ -9,9 +9,12 @@ import {
SendListMessageRequest,
SendLocationMessageRequest,
SendMediaMessageRequest,
SendMessageRequest, SendTemplateMessageRequest, SendTextMessageRequest,
SendMessageRequest,
SendTemplateMessageRequest,
SendTextMessageRequest,
} from '../../send-message-request';
import { UpdateMessageRequest } from '../../update-message-request';
import { ListLastMessagesByChannelIdentityRequest } from '../../list-last-messages-by-channel-identity-request';

export interface DeleteMessageRequestData {
/** The unique ID of the message. */
Expand Down Expand Up @@ -50,6 +53,12 @@ export interface ListMessagesRequestData {
'only_recipient_originated'?: boolean;
/** Only fetch messages from the `channel`. */
'channel'?: ConversationChannel;
/** Optional. Only fetch messages with the specified `direction`. If direction is not specified, it will list both TO_APP and TO_CONTACT messages. */
'direction'?: ConversationDirection;
}
export interface ListLastMessagesByChannelIdentityRequestData {
/** Request body for listing messages by channel identity. NOTE: You can use either contact_ids OR channel_identities, but not both in the same request. */
'listLastMessagesByChannelIdentityRequestBody': ListLastMessagesByChannelIdentityRequest;
}
export interface SendMessageRequestData<T extends Recipient> {
/** This is the request body for sending a message. `app_id`, `recipient`, and `message` are all required fields. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { LazyConversationApiClient } from '../conversation-service';
export class ConversationApi extends ConversationDomainApi {

constructor(lazyApiClient: LazyConversationApiClient) {
super(lazyApiClient, 'AppApi');
super(lazyApiClient, 'ConversationApi');
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
SendMessageResponse,
DeleteMessageRequestData,
GetMessageRequestData,
ListLastMessagesByChannelIdentityRequestData,
ListMessagesRequestData,
UpdateMessageRequestData,
SendCardMessageRequestData,
Expand Down Expand Up @@ -34,6 +35,13 @@ export class MessagesApiFixture implements Partial<Readonly<MessagesApi>> {
* Fixture associated to function list
*/
public list: jest.Mock<ApiListPromise<ConversationMessage>, [ListMessagesRequestData]> = jest.fn();
/**
* Fixture associated to function listLastMessagesByChannelIdentity
*/
public listLastMessagesByChannelIdentity: jest.Mock<
ApiListPromise<ConversationMessage>,
[ListLastMessagesByChannelIdentityRequestData]
> = jest.fn();
/**
* Fixture associated to function sendCardMessage
*/
Expand Down
47 changes: 46 additions & 1 deletion packages/conversation/src/rest/v1/messages/messages-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ConversationMessage,
DeleteMessageRequestData,
GetMessageRequestData,
ListLastMessagesByChannelIdentityRequestData,
ListMessagesRequestData,
Recipient,
SendCardMessageRequestData,
Expand Down Expand Up @@ -97,7 +98,6 @@ export class MessagesApi extends ConversationDomainApi {
* @return {ApiListPromise<ConversationMessage>}
*/
public list(data: ListMessagesRequestData): ApiListPromise<ConversationMessage> {
data['messages_source'] = data['messages_source'] !== undefined ? data['messages_source'] : 'CONVERSATION_SOURCE';
const getParams = this.client.extractQueryParams<ListMessagesRequestData>(data, [
'conversation_id',
'contact_id',
Expand All @@ -111,6 +111,7 @@ export class MessagesApi extends ConversationDomainApi {
'messages_source',
'only_recipient_originated',
'channel',
'direction',
]);
const headers: { [key: string]: string | undefined } = {
'Content-Type': 'application/json',
Expand Down Expand Up @@ -145,6 +146,50 @@ export class MessagesApi extends ConversationDomainApi {
return listPromise as ApiListPromise<ConversationMessage>;
}

/**
* List messages by channel identity
* Retrieves the last message sent to specified channel identities. In CONVERSATION_SOURCE mode, you can query either by channel_identities or by contact_ids. Note: Use either contact_ids OR channel_identities per request, not both. DISPATCH_SOURCE mode does not support contact_ids.
* @param { ListLastMessagesByChannelIdentityRequestData } data - The data to provide to the API call.
*/
public listLastMessagesByChannelIdentity(
data: ListLastMessagesByChannelIdentityRequestData,
): ApiListPromise<ConversationMessage> {
const getParams = this.client.extractQueryParams<ListLastMessagesByChannelIdentityRequestData>(data, [] as never[]);
const headers: { [key: string]: string | undefined } = {
'Content-Type': 'application/json',
'Accept': 'application/json',
};

const body: RequestBody = data['listLastMessagesByChannelIdentityRequestBody']
? JSON.stringify(data['listLastMessagesByChannelIdentityRequestBody']) : '{}';
const basePathUrl = `${this.client.apiClientOptions.hostname}/v1/projects/${this.client.apiClientOptions.projectId}/messages:fetch-last-message`;

const requestOptionsPromise
= this.client.prepareOptions(basePathUrl, 'POST', getParams, headers, body || undefined);

const operationProperties: PaginatedApiProperties = {
pagination: PaginationEnum.TOKEN2,
apiName: this.apiName,
operationId: 'ListMessagesByChannelIdentity',
dataKey: 'messages',
};

// Create the promise containing the response wrapped as a PageResult
const listPromise = buildPageResultPromise<ConversationMessage>(
this.client,
requestOptionsPromise,
operationProperties);

// Add properties to the Promise to offer the possibility to use it as an iterator
Object.assign(
listPromise,
createIteratorMethodsForPagination<ConversationMessage>(
this.client, requestOptionsPromise, listPromise, operationProperties),
);

return listPromise as ApiListPromise<ConversationMessage>;
}

/**
* Send a message
* You can send a message from a Conversation app to a contact associated with that app. If the recipient is not associated with an existing contact, a new contact will be created. The message is added to the active conversation with the contact if a conversation already exists. If no active conversation exists a new one is started automatically. You can find all of your IDs and authentication credentials on the [Sinch Customer Dashboard](https://dashboard.sinch.com/convapi/overview).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,65 @@ describe('MessagesApi', () => {
});
});

describe ('listLastMessagesByChannelIdentity', () => {
it('should make a POST request to list the last messages related to the channel identities', async () => {
// Given
const requestData: Conversation.ListLastMessagesByChannelIdentityRequestData = {
listLastMessagesByChannelIdentityRequestBody: {
channel_identities: [
'4712345678',
],
messages_source: 'DISPATCH_SOURCE',
direction: 'TO_CONTACT',
},
};
const mockData: Conversation.ConversationMessage[] = [
{
id: 'id',
direction: 'TO_CONTACT',
app_message: {
explicit_channel_message: {},
text_message: {
text: 'Hello from Sinch - RCS',
},
agent: null,
explicit_channel_omni_message: {},
channel_specific_message: {},
},
channel_identity: {
channel: 'RCS',
identity: '4712345678',
app_id: '',
},
conversation_id: '',
contact_id: '',
metadata: '',
accept_time: new Date('2019-08-24T14:15:22Z'),
sender_id: '',
processing_mode: 'DISPATCH',
injected: false,
message_status: null,
},
];
const expectedResponse = {
data: mockData,
hasNextPage: false,
nextPageValue: '',
nextPage: jest.fn(),
};

// When
fixture.listLastMessagesByChannelIdentity.mockResolvedValue(expectedResponse);
messagesApi.listLastMessagesByChannelIdentity = fixture.listLastMessagesByChannelIdentity;
const response = await messagesApi.listLastMessagesByChannelIdentity(requestData);

// Then
expect(response).toEqual(expectedResponse);
expect(response.data).toBeDefined();
expect(fixture.listLastMessagesByChannelIdentity).toHaveBeenCalledWith(requestData);
});
});

describe ('sendMessage', () => {
// Given
const sendMessageRequest: Omit<Conversation.SendMessageRequest<Conversation.Recipient>, 'recipient'> = {
Expand Down
Loading
Loading