Converting a Node Agent SDK Project to Messaging Platform SDK
The Node Agent SDK (NASDK) is deprecated, and transitioning to the Messaging Platform SDK (MPSDK) is recommended. There are two types of conversions:
- Replacement Conversion
- Rework Conversion
Replacement Conversion
In a replacement conversion, NASDK applications that subscribe to conversation and ring events, issue requests, and consume responses are adapted to MPSDK. The MPSDK exposes responses and events as a stream of JSON objects, similar to NASDK, without requiring the use of abstractions like conversations or dialogs. The converted application benefits from the reconnection logic and automated token maintenance of MPSDK. However, ensure that your existing code, built to work around NASDK’s shortcomings, is compatible with the new MPSDK implementation.
Rework Conversion
In a rework conversion, the application leverages the abstractions offered by MPSDK. MPSDK consumes the stream of JSON
responses and events and builds up stateful abstractions for you. This conversion may require significant changes to your
application, shifting from working with streams to handling state changes. For instance, instead of consuming a notification
to determine that a participant was added, the MPSDK will emit a participant-added
event.
General Recommendation: If you have a running NASDK application, start with a replacement conversion to avoid complications where custom code has no direct translation to MPSDK abstractions. After this initial conversion, consider a full rework if necessary.
Replacement vs. Rework Example
Consider the following NASDK code, which sends hello world!
upon the agent joining a conversation:
const Agent = require('AgentSDK');
// Connects right away
const connection = new Agent({
'accountId': 'testAccount',
'username': 'test',
'appKey': 'test',
'secret': 'test',
'accessToken': 'test',
'accessTokenSecret': 'test'
});
const agentId = 'testAccount.testBot';
connection.on('connected', () => {
// The bot is ready to receive rings
connection.setAgentState({availability: 'ONLINE'});
// Subscribe to all open conversations for this agent
connection.subscribeExConversations({
'agentIds': [agentId],
'convState': ['OPEN']
}, (e, resp) => console.log('subscribeExConversations', this.conf.id || '', resp || e));
// The bot should be ringed for conversations.
connection.subscribeRoutingTasks({});
// Start the keep-alive process by piggybacking the getClock message
connection._pingClock = setInterval(connection.getClock, 30000);
});
connection.on('routing.RoutingTaskNotification', ring => {
// Accept any ring/routing task.
// The bot will join the conversation
ring.changes.forEach(c => {
if (c.type === 'UPSERT') {
c.result.ringsDetails.forEach(r => {
if (r.ringState === 'WAITING') {
connection.updateRingState({
'ringId': r.ringId,
'ringState': 'ACCEPTED'
}, (e, resp) => console.log(resp));
}
});
}
});
});
connection.on('cqm.ExConversationChangeNotification', notification => {
// Extract conversation id from the first change.
// The assumption is that the notification carries at least one change.
// This will break if the assumption is not true.
const conversationId = notification.change[0].result.convId;
connection.publishEvent({
dialogId: conversationId,
event: {
type: 'ContentEvent',
contentType: 'text/plain',
message: 'hello world!'
}
});
});
This NASDK code processes events using callbacks defined on the connection
. NASDK doesn’t maintain internal state,
leaving state management to the developer. NASDK only supports authenticated agent connections.
Replacement Conversion Example
In the replacement conversion, requests are structured differently, using the body
key for requests and the type
key
to specify the request type. The stream is consumed with callbacks on the connection, but event names differ slightly.
Configuring and opening a connection are now separate steps, and the async/await style is used.
const authData = {
"username": "botUser",
"appKey": "6828c2SomeKey",
"secret": "49c59aSomeSecret",
"accessToken": "efde28SomeToken",
"accessTokenSecret": "6f800SomeTokenSecret"
};
// Create a connection with a default subscription to all
// open conversations where the this agent is part of.
const connection = lpm.createConnection({
appId: `example_brand_connection`,
accountId: '12345678',
userType: lpm.UserType.BRAND,
authData,
// Determine that the connection is interested in conversations
// matching the following criteria by default.
defaultSubscriptionQuery: {
'agentId': ['12345678.agent'], // Important: agentId instead of agentIds
'state': ['OPEN'] // Important: state instead of convState
}
});
connection.on('.ams.routing.RoutingTaskNotification', ring => {
// Accept any ring/routing task.
// The bot will join the conversation.
ring.changes.forEach(c => {
if (c.type === 'UPSERT') {
c.result.ringsDetails.forEach(r => {
if (r.ringState === 'WAITING') {
connection._updateRingState({
'ringId': r.ringId,
'ringState': 'ACCEPTED'
}, (e, resp) => console.log(resp));
}
});
}
});
});
connection.on('.ams.aam.ExConversationChangeNotification', notification => {
// Extract conversation id from the first change.
// The assumption is that the notification carries at least one change.
// This will break if the assumption is not true.
const conversationId = notification.change[0].result.convId;
connection.send({
type: '.ams.ms.PublishEvent',
body: {
dialogId: conversationId,
event: {
type: 'ContentEvent',
contentType: 'text/plain',
message: 'hello world!'
}
}
});
});
// Different to the NASDK, the connection has to be opened explicitly
await connection.open();
// Register to be able to accept rings
await connection.createRoutingTaskSubscription();
// The bot is ready to receive rings
await connection.setAgentState({ agentState: lpm.AgentState.ONLINE });
Rework Conversion Example
Using MPSDK abstractions, your code will primarily react to state changes rather than consuming raw events directly:
const authData = {
"username": "botUser",
"appKey": "6828c2SomeKey",
"secret": "49c59aSomeSecret",
"accessToken": "efde28SomeToken",
"accessTokenSecret": "6f800SomeTokenSecret"
};
// Create a connection with a default subscription to all
// open conversations where the this agent is part of.
const connection = lpm.createConnection({
appId: `example_brand_connection`,
accountId: '12345678',
userType: lpm.UserType.BRAND,
authData,
// Determine that the connection is interested in conversations
// matching the following criteria by default.
defaultSubscriptionQuery: {
'agentId': ['12345678.agentId'], // Important: agentId instead of agentIds
'state': ['OPEN'] // Important: state instead of convState
}
});
connection.on('conversation', async conversation => {
await conversation.sendMessage('hello world');
});
connection.on('ring', async ring => {
// Only accept rings which are waiting
if (ring.ringState !== RingState.WAITING) return;
await ring.accept();
});
// Open the connection
await connection.open();
// Register to be able to accept rings
await connection.createRoutingTaskSubscription();
// The bot is ready to receive rings
await connection.setAgentState({ agentState: lpm.AgentState.ONLINE });
For more complex interactions, the ring
object offers a convenience method ring.conversation()
allowing you to await
the conversation:
connection.on('ring', async ring => {
if (ring.ringState !== RingState.WAITING) return;
await ring.accept();
// Awaiting the conversation would pause any subsequent code. Instead, you can utilize then if there is code which
// should be run without a conversation.
// Per default waits for two seconds for the conversation to arrive.
ring.conversation().then(async (conversation) => {
await conversation.sendMessage('hello world');
});
// Ring received will be printed right away
console.log('Ring received');
});
This approach makes the code more concise but requires a complete project conversion.
All Request Types
Converting NASDK requests to MPSDK requests involves wrapping partial requests in an object containing the request type:
const partialPublishEventRequest = {
dialogId: 'MY_DIALOG_ID',
event: {
type: 'ContentEvent',
contentType: 'text/plain',
message: 'hello world!'
}
};
const requestType = '.ams.ms.PublishEvent';
const fullPublishEventRequest = {
type: requestType,
body: partialPublishEventRequest
};
const response = await connection.send(fullPublishEventRequest);
Other partial requests can be converted similarly. Note that certain requests will need additional tweaking before they are sent. Use the following table to look up request types and the next section for a list of required changes per request type:
const REQUEST_TYPES = {
getClock: '.GetClock',
agentRequestConversation: '.ams.cm.AgentRequestConversation',
subscribeExConversations: '.ams.aam.SubscribeExConversations',
unsubscribeExConversations: '.ams.aam.UnsubscribeExConversations',
updateConversationField: '.ams.cm.UpdateConversationField',
publishEvent: '.ams.ms.PublishEvent',
updateRingState: '.ams.routing.UpdateRingState',
subscribeRoutingTasks: '.ams.routing.SubscribeRoutingTasks',
updateRoutingTaskSubscription: '.ams.routing.UpdateRoutingTaskSubscription',
getUserProfile: '.ams.userprofile.GetUserProfile',
setAgentState: '.ams.routing.SetAgentState',
subscribeAgentsState: '.ams.routing.SubscribeAgentsState',
subscribeMessagingEvents: 'ms.SubscribeMessagingEvents',
generateURLForDownloadFile: '.ams.ms.GenerateURLForDownloadFile',
generateURLForUploadFile: '.ams.ms.GenerateURLForUploadFile',
generateDownloadToken: '.ams.ms.token.GenerateDownloadToken'
};
const fullRequest = {
type: REQUEST_TYPES['publishEvent'], // Look up the type for publish event
body: existingJsonRequest
}
const result = await connection.send(fullRequest);
Required changes to the request
Request type | Changes |
---|---|
.ams.cm.AgentRequestConversation |
body.channelType should be equal to 'MESSAGING' |
.ams.cm.UpdateConversationField |
Every member of body.conversationField whose property is called ParticipantsChange should have a property userId equal to the agentId
|
.ams.routing.SubscribeRoutingTasks |
body.channelType should be equal to 'MESSAGING' body.agentId should be equal to the agent id body.brandId should be equal to the accountId |
.ams.routing.SetAgentState |
body.channels should be an array with a value of ['MESSAGING'] body.agentUserId should be equal to the agent id |
.ams.routing.SubscribeAgentsState |
body.agentId should be equal to the agent id body.brandId should be equal to the accountId |
ms.SubscribeMessagingEvents |
type should be equal to .ams.ms.QueryMessages body.newerThanSequence should contain either a sequence number or 0 |