-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DEVEXP-567: Add 'Qualify-leads app' tutorial (#8)
- Loading branch information
1 parent
6af2f4c
commit e1ab91e
Showing
12 changed files
with
1,994 additions
and
0 deletions.
There are no files selected for viewing
170 changes: 170 additions & 0 deletions
170
tutorials/sms/auto-subscribe-app/src/auto-subsribe-service.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
// eslint-disable-next-line no-unused-vars | ||
import { SmsService, Sms } from '@sinch/sdk-core'; | ||
import { fetchGroup } from './group-lifecycle-manager.js'; | ||
|
||
const SUBSCRIBE_ACTION = 'SUBSCRIBE'; | ||
const STOP_ACTION = 'STOP'; | ||
|
||
/** | ||
* Processes the incoming SMS event, determines if the sender wants to subscribe or unsubscribe from the group, | ||
* and sends a response based on their input. | ||
* | ||
* @param { Sms.MOText } incomingTextMessage - The incoming SMS message event object | ||
* @param { SmsService } smsService - the SMS service instance from the Sinch SDK containing the API methods | ||
*/ | ||
export const processInboundEvent = async (incomingTextMessage, smsService) => { | ||
console.log(`Received event: ${JSON.stringify(incomingTextMessage, null, 2)}`); | ||
|
||
const from = incomingTextMessage.from; | ||
const to = incomingTextMessage.to; | ||
const senderInput = incomingTextMessage.body.trim(); | ||
|
||
const group = await fetchGroup(smsService.groups); | ||
|
||
const membersList = await getMembersList(smsService, group); | ||
const isInGroup = isMemberInGroup(membersList, from); | ||
|
||
const responseText = await processSenderInput(smsService, from, to, senderInput, group, membersList, isInGroup); | ||
|
||
await sendResponse(smsService, to, from, responseText); | ||
}; | ||
|
||
/** | ||
* Fetches the list of members in the group. | ||
* | ||
* @param {SmsService} smsService - The SMS service instance from the Sinch SDK containing the API methods. | ||
* @param {Sms.CreateGroupResponse} group - The group object. | ||
* @return {Promise<string[]>} - A promise that resolves to a list of member phone numbers. | ||
*/ | ||
const getMembersList = async (smsService, group) => { | ||
return await smsService.groups.listMembers({ | ||
group_id: group.id, | ||
}); | ||
}; | ||
|
||
/** | ||
* Checks if the specified member is in the list of members og the group. | ||
* | ||
* @param {string[]} membersList - The list of group members. | ||
* @param {string} member - The phone number of the member to check. | ||
* @return {boolean} - Whether the member is part of the group. | ||
*/ | ||
const isMemberInGroup = (membersList, member) => { | ||
return membersList.includes(member); | ||
}; | ||
|
||
/** | ||
* Processes the sender's input to either subscribe, unsubscribe, or handle unknown actions. | ||
* | ||
* @param {SmsService} smsService - the SMS service instance from the Sinch SDK containing the API methods. | ||
* @param {string} from - The phone number of the sender. | ||
* @param {string} to - The group's phone number. | ||
* @param {string} action - The incoming action (e.g., "SUBSCRIBE" or "STOP"). | ||
* @param {Sms.CreateGroupResponse} group - The group object. | ||
* @param {string[]} membersList - The list of group members. | ||
* @param {boolean} isInGroup - Whether the sender is already in the group. | ||
* @return {Promise<string>} - A promise that resolves to the response text for the sender. | ||
*/ | ||
const processSenderInput = async (smsService, from, to, action, group, membersList, isInGroup) => { | ||
if (action === SUBSCRIBE_ACTION) { | ||
return await subscribe(smsService, group, isInGroup, to, from); | ||
} else if (action === STOP_ACTION) { | ||
return await unsubscribe(smsService, group, isInGroup, to, from); | ||
} | ||
return unknownAction(isInGroup, to); | ||
}; | ||
|
||
/** | ||
* Subscribes a member to the group if they are not already in it. | ||
* | ||
* @param {SmsService} smsService - the SMS service instance from the Sinch SDK containing the API methods | ||
* @param {Sms.CreateGroupResponse} group - The group object. | ||
* @param {boolean} isInGroup - Whether the member is already in the group. | ||
* @param {string} groupPhoneNumber - The group's phone number. | ||
* @param {string} member - The phone number of the member to subscribe. | ||
* @return {Promise<string>} - A promise that resolves to the subscription confirmation message. | ||
*/ | ||
const subscribe = async (smsService, group, isInGroup, groupPhoneNumber, member) => { | ||
if (isInGroup) { | ||
return `You have already subscribed to '${group.name}'. Text "${STOP_ACTION}" to +${groupPhoneNumber} to leave the group.`; | ||
} | ||
|
||
/** @type {Sms.UpdateGroupRequestData } */ | ||
const requestData = { | ||
group_id: group.id, | ||
updateGroupRequestBody: { | ||
add: [member], | ||
}, | ||
}; | ||
|
||
await smsService.groups.update(requestData); | ||
|
||
return `Congratulations! You are now subscribed to '${group.name}'. Text "${STOP_ACTION}" to +${groupPhoneNumber} to leave the group.`; | ||
}; | ||
|
||
/** | ||
* Unsubscribes a member from the group if they belong to it. | ||
* | ||
* @param {SmsService} smsService - the SMS service instance from the Sinch SDK containing the API methods. | ||
* @param {Sms.CreateGroupResponse} group - The group object. | ||
* @param {boolean} isInGroup - Whether the member is in the group. | ||
* @param {string} groupPhoneNumber - The group's phone number. | ||
* @param {string} member - The phone number of the member to unsubscribe. | ||
* @return {Promise<string>} - A promise that resolves to the unsubscription confirmation message. | ||
*/ | ||
const unsubscribe = async (smsService, group, isInGroup, groupPhoneNumber, member) => { | ||
if (!isInGroup) { | ||
return `You haven't subscribed to '${group.name}' yet. Text "${SUBSCRIBE_ACTION}" to +${groupPhoneNumber} to join this group.`; | ||
} | ||
|
||
/** @type {Sms.UpdateGroupRequestData} */ | ||
const requestData = { | ||
group_id: group.id, | ||
updateGroupRequestBody: { | ||
remove: [member], | ||
}, | ||
}; | ||
|
||
await smsService.groups.update(requestData); | ||
|
||
return `We're sorry to see you go. You can always rejoin '${group.name}' by texting "${SUBSCRIBE_ACTION}" to +${groupPhoneNumber}.`; | ||
}; | ||
|
||
/** | ||
* Handles an unknown action by suggesting the sender either subscribe or unsubscribe. | ||
* | ||
* @param {boolean} isInGroup - Whether the sender is already in the group. | ||
* @param {string} groupPhoneNumber - The group's phone number. | ||
* @return {string} - A message suggesting the next action for the sender. | ||
*/ | ||
const unknownAction = (isInGroup, groupPhoneNumber) => { | ||
if (isInGroup) { | ||
return `Thanks for your interest. If you want to unsubscribe from this group, text "${STOP_ACTION}" to +${groupPhoneNumber}.`; | ||
} else { | ||
return `Thanks for your interest. If you want to subscribe to this group, text "${SUBSCRIBE_ACTION}" to +${groupPhoneNumber}.`; | ||
} | ||
}; | ||
|
||
/** | ||
* Sends a response SMS to the sender. | ||
* | ||
* @param {SmsService} smsService - the SMS service instance from the Sinch SDK containing the API methods | ||
* @param {string} to - The group's phone number. | ||
* @param {string} from - The phone number of the sender. | ||
* @param {string} responseText - The text of the response to send. | ||
* @return {Promise<void>} | ||
*/ | ||
const sendResponse = async (smsService, to, from, responseText) => { | ||
/** @type {Sms.SendTextSMSRequestData} */ | ||
const requestData = { | ||
sendSMSRequestBody: { | ||
to: [to], | ||
body: responseText, | ||
from, | ||
}, | ||
}; | ||
|
||
await smsService.batches.sendTextMessage(requestData); | ||
|
||
console.log(`Replied: ${responseText}`); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Express related configuration | ||
port = 3001 | ||
|
||
# Application related credentials, used by: | ||
# - Voice | ||
SINCH_APPLICATION_KEY = <Your Application Key> | ||
SINCH_APPLICATION_SECRET = <Your Application Secret> | ||
|
||
SINCH_NUMBER = <Your Sinch Number associated to your Voice app> | ||
SIP_ADDRESS = <Your SIP address if you are using a SIP infrastructure> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# Qualify-leads application sample | ||
|
||
This directory contains some code related to the Node.js SDK tutorial: [qualify-leads](https://developers.sinch.com/docs/voice/tutorials/) | ||
|
||
## DISCLAIMER | ||
|
||
This tutorial is based on mixing a command-line function with a server-side backend service. | ||
|
||
It is not a correct use of the CLI outside an educational purpose. | ||
|
||
## Requirements | ||
|
||
- [Node.js LTS](https://nodejs.org/en) | ||
- [Express](https://expressjs.com/) | ||
- [Sinch account](https://dashboard.sinch.com/) | ||
- [ngrok](https://ngrok.com/docs) | ||
|
||
## Usage | ||
|
||
### Configure application settings | ||
|
||
Edit the [.env](.env) file to set the parameters that will be used to configure the Express server and the controller. | ||
|
||
#### Sinch credentials | ||
|
||
With the [Voice API](https://developers.sinch.com/docs/voice/), the incoming `Authorization` header is signed with the same credentials as when using the API: | ||
- `SINCH_APPLICATION_KEY`=Your Application Key | ||
- `SINCH_APPLICATION_SECRET`=Your Application Secret | ||
|
||
#### Other parameters | ||
- `SINCH_NUMBER`=The Sinch number associated to your [Voice App](https://dashboard.sinch.com/voice/apps). | ||
- `SIP_ADDRESS`=If you are performing this tutorial with a SIP infrastructure, this is where you would enter your SIP address. | ||
|
||
#### Server port | ||
|
||
*Default: 3001* | ||
- `port`: the port to be used to listen to incoming requests. Default is `3001` if not set. | ||
|
||
### Starting the server locally | ||
|
||
1. Install the dependencies by running the command `npm install`. | ||
2. Edit the `.env` file with your own parameters (see the paragraph above for details). | ||
3. Start the server with the following command: | ||
```bash | ||
npm start | ||
``` | ||
|
||
### Use ngrok to forward requests to the local server | ||
|
||
You can forward the request to your local server of the port it is listening to. | ||
|
||
*Note: The `3001` value is coming from the default configuration and can be changed (see [Server port](#Server port) configuration section)* | ||
|
||
```bash | ||
ngrok http 3001 | ||
``` | ||
|
||
The `ngrok` output will contain something like: | ||
``` | ||
ngrok (Ctrl+C to quit) | ||
... | ||
Forwarding https://cafe-64-220-29-200.ngrok-free.app -> http://localhost:3001 | ||
``` | ||
The line | ||
``` | ||
Forwarding https://cafe-64-220-29-200.ngrok-free.app -> http://localhost:3001 | ||
``` | ||
contains the "`https://cafe-64-220-29-200.ngrok-free.app`" value which will be used to determine the callback URL. | ||
|
||
With this example, given the fact the controller is exposing the path `/VoiceEvent`, the resulting callback URL to configure in your [Sinch dashboard](hhttps://dashboard.sinch.com/voice/apps) will be `https://cafe-64-220-29-200.ngrok-free.app/VoiceEvent` (adapt the value according to your ngrok and controller configurations). |
Oops, something went wrong.