### 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:

1. **Replacement Conversion**
2. **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:

```javascript
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.

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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](#required-changes-to-the-request) for a list of required changes per request type:

```javascript
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' <br> `body.agentId` should be equal to the agent id <br> `body.brandId` should be equal to the accountId |
| `.ams.routing.SetAgentState`         | `body.channels` should be an array with a value of `['MESSAGING']` <br> `body.agentUserId` should be equal to the agent id                                 |
| `.ams.routing.SubscribeAgentsState`  | `body.agentId` should be equal to the agent id <br> `body.brandId` should be equal to the accountId                                                        |
| `ms.SubscribeMessagingEvents`        | `type` should be equal to `.ams.ms.QueryMessages` <br> `body.newerThanSequence` should contain either a sequence number or 0                               |
