Skip to main content

Creating a new Adapter

Prerequisite

This guide assumes that you have a complete understanding of what an adapter is and its functionality in the system. Please go through this documentation about Adapters if you haven't.

note

Please go through supported adapters, to ensure that the type of adapter you're trying to create doesn't already exist.

Selecting a Channel

The first step of this guide is to select a channel for which an adapter needs to be created. A channel means any client facing platform with which a user may interact with a bots in the system. Examples channels include, Whatsapp, Telegram, Slack, Discord, Email, etc.

Selecting a Provider

The next step of enabling communication on a specific channel is selecting a provider. A provider may be an external/third party provider like Gupshup for Whatsapp, or the platform may itself provide REST APIs to communicate with it's own platform, like in the case of Telegram Bot APIs. Either way, you would need to get hold of a documentation that describes how to use the APIs of that provider.

Selecting Adapter Type

An adapter may implement one of the following interfaces

  • IChatProvider (for chat platforms)
  • IEmailProvider (for email platforms)
  • ISmsProvider (for sms platforms)
  • XMessageProvider (for any generic platform)
tip

Although adapters can implement any one of the above interfaces, XMessageProvider are preferred over other types, since XMessage is the internal language spec of the system and is highly structured and extensible.

Implementing the Adapter

1. The sendMessage function (For One Way Communication)

Every provider "must" define the sendMessage function which is responsible for sending a message to the corresponding channel by calling the actual APIs for a provider. The function accepts a single argument of a specific type based on what interface is implemented and the function should interpret the data provided as an argument and call the corresponding API with relevant data.

2. The convertMessageToXMsg function (For Two Way Communication)

This is an optional function that a provider may define if two way communication is desired (Message can travel from User to Bot). The sole responsibility of this function is to convert the raw data sent by a Webhook/API into an XMessage that the system understands. This function accepts a single argument, that is, the raw data sent by a Webhook, which may vary in type according to the channel and provider service. The function must return an XMessage that contains the entire data sent by the Webhook.

Here is an example of how this function can be implemented. In this specific example, a telegram webhook message data is being converted into an XMessage.

async convertMessageToXMsg(msg: TelegramUpdateMessage): Promise<XMessage> {
let messageType = MessageType.TEXT;
let text = msg.message.text;
if (text.startsWith('\\register')) {
text = text.replace('\\register ', '').trim();
messageType = MessageType.REGISTRATION;
}
const xmessage: XMessage = {
to: {
userID: "admin",
bot: true,
},
from: {
userID: `${msg.message.from.id}`,
bot: false,
},
channelURI: "Telegram",
providerURI: "Telegram",
messageState: MessageState.REPLIED,
messageId: {
channelMessageId: `${msg.update_id}`,
Id: uuid4(),
},
messageType: messageType,
timestamp: msg.message.date,
payload: {
text: text,
},
};

return xmessage;
}

3. Adding the Adapter to Factory

Every newly created adapter needs to be added to the Factory package to be made available for other services.

An adapter name is derived by concatinating the name of the provider + channel it's created for.

Inside the adapter.factory.ts, an adapter's name needs to be added to one of the corresponding groups according to the interface being used.

  • emailTypes
  • smsTypes
  • chatTypes
  • xmessageTypes

The AdapterFactory class contains the getAdapter method which returns the exact object instance based on the type provided.

Here is how you would add an adapter with provider XProvider and channel XChannel.

static getAdapter(
consumerData: GenericAdapterConfig
): IEmailProvider | ISmsProvider | IChatProvider | IPushProvider | XMessageProvider | undefined {
switch (consumerData.type) {
// other adapters
...
case 'XProviderXChannel':
return new MyAdapterClass(consumerData.config);
default:
return undefined;
}
}

Creating IChatProvider Type Adapter

Start by implementing the IChatProvider interface.

  • For one way communication define the sendMessage function. Here is an example which uses discord webhook API to send messages.
async sendMessage(data: IChatOptions): Promise<ISendMessageSuccessResponse> {
const url = new URL(data.webhookUrl);
url.searchParams.set('wait', 'true');
const response = await this.axiosInstance.post(url.toString(), {
content: data.content,
});

return {
id: response.data.id,
date: response.data.timestamp,
};
}
  • For two way communication define the convertMessageToXMsg function. See example here.

  • Add your adapter to the factory package, like shown here.

Creating ISmsProvider Type Adapter

Start by implementing the ISmsProvider interface.

  • For one way communication define the sendMessage function. Here is an example which illustrates the usage of twilio provider to send SMS.
async sendMessage(
options: ISmsOptions
): Promise<ISendMessageSuccessResponse> {
const twilioResponse = await this.twilioClient.messages.create({
body: options.content,
to: options.to,
from: options.from || this.config.from,
});

return {
id: twilioResponse.sid,
date: twilioResponse.dateCreated.toISOString(),
};
}
  • For two way communication define the convertMessageToXMsg function. See example here.

  • Add your adapter to the factory package, like shown here.

Creating IEmailProvider Type Adapter

Start by implementing the IEmailProvider interface.

  • For one way communication define the sendMessage function. Here is an example which uses Mailgun to send emails.
async sendMessage(
emailOptions: IEmailOptions
): Promise<ISendMessageSuccessResponse> {
const mailgunMessageData: Partial<MailgunMessageData> = {
from: emailOptions.from || this.config.from,
to: emailOptions.to,
subject: emailOptions.subject,
html: emailOptions.html,
cc: emailOptions.cc?.join(','),
bcc: emailOptions.bcc?.join(','),
attachment: emailOptions.attachments?.map((attachment) => {
return {
data: attachment.file,
filename: attachment.name,
};
}),
};

if (emailOptions.replyTo) {
mailgunMessageData['h:Reply-To'] = emailOptions.replyTo;
}

const response = await this.mailgunClient.messages.create(
this.config.domain,
mailgunMessageData as MailgunMessageData
);

return {
id: response.id,
date: new Date().toISOString(),
};
}
  • For two way communication define the convertMessageToXMsg function. See example here.

  • Add your adapter to the factory package, like shown here.

Creating XMessageProvider Type Adapter

Start by implementing the XMessageProvider interface.

  • For one way communication define the sendMessage function. Here is an example which uses Gupshup to send Whatsapp messages.
async sendMessage (xMsg: XMessage) {
if (!this.providerConfig) {
console.error("Configuration not set for adapter!");
return;
}
try {
if (
(this.providerConfig.usernameHSM && this.providerConfig.passwordHSM) ||
(this.providerConfig.username2Way && this.providerConfig.password2Way)
) {
let text: string = xMsg.payload.text || '';
let builder = this.getURIBuilder();

let plainText: boolean = true;
const stylingTag: StylingTag | undefined =
xMsg.payload.stylingTag !== null
? xMsg.payload.stylingTag
: undefined;
builder = this.setBuilderCredentialsAndMethod(
builder,
MethodType.SIMPLEMESSAGE,
this.providerConfig.username2Way,
this.providerConfig.password2Way,
);

builder.set('send_to', '91' + xMsg.to.userID);
builder.set('phone_number', '91' + xMsg.to.userID);
builder.set('msg_type', xMsg.messageType);
builder.set('channel', 'Whatsapp');
if (xMsg.messageId.channelMessageId) {
builder.set('msg_id', xMsg.messageId.channelMessageId);
}

if (xMsg.payload.media && xMsg.payload.media.url) {
const media: MessageMedia = xMsg.payload.media;

builder.set('method', MethodType.MEDIAMESSAGE);
builder.set(
'msg_type',
this.getMessageTypeByMediaCategory(media.category)
);

builder.set('media_url', media.url);
builder.set('caption', media.text);
builder.set('isHSM', 'false');
plainText = false;
}

if (plainText) {
text += this.renderMessageChoices(xMsg.payload.buttonChoices || []);
builder.set('msg', text);
}

const expanded = new URL(
`https://media.smsgupshup.com/GatewayAPI/rest?${builder}`
);

try {
const response: GSWhatsappOutBoundResponse = await this.sendOutboundMessage(
expanded.toString()
);
if (response !== null && response.response.status === 'success') {
xMsg.messageId = MessageId.builder()
.setChannelMessageId(response.response.id)
.build();
xMsg.messageState = MessageState.SENT;
return xMsg;
} else {
console.error(
'Gupshup Whatsapp Message not sent: ',
response.response.details
);
xMsg.messageState = MessageState.NOT_SENT;
return xMsg;
}
} catch (error) {
console.error('Error in Send GS Whatsapp Outbound Message', error);
}
} else {
console.error('Credentials not found');
xMsg.messageState = MessageState.NOT_SENT;
return xMsg;
}
} catch (error) {
console.error('Error in processing outbound message', error);
throw error;
}
};

private sendOutboundMessage = (url: string): Promise<GSWhatsappOutBoundResponse> => {
return new Promise<GSWhatsappOutBoundResponse>((resolve, reject) => {
axios.create()
.get<GSWhatsappOutBoundResponse>(url)
.then((response: AxiosResponse<GSWhatsappOutBoundResponse>) => {
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
}
note

Even though the XMessageProvider Type may seem complex, the example here just illustrates the extensibility of the XMessage spec, the implementation may be much simpler if you're only trying to support few message types.

  • For two way communication define the convertMessageToXMsg function. See example here.

  • Add your adapter to the factory package, like shown here.