diff --git a/hello-voice/assets/index.html b/hello-voice/assets/index.html index ed4ef115..56836ea0 100644 --- a/hello-voice/assets/index.html +++ b/hello-voice/assets/index.html @@ -1,92 +1,137 @@ - + - - - - - Get started with your Twilio Functions! + + + + + Get started with your Twilio Functions! - - + + - - - - -
-
- - -
+ + + + +
+
+ -
-
-

- -
-

Welcome!

-

Your live application with Twilio is ready to use!

-
-

-
-

Get started with your application

-

- Follow these steps to try out your new app: -

-

- This app is a basic Twilio Voice TwiML template Function that will return the - TwiML - to say "Hello World" -

-
    -
  1. Call your Twilio phone number
  2. -
  3. Be greeted by a robot voice saying "Hello World"
  4. -
-
+ +
+
+
+
+

+ +
+

Welcome!

+

Your live application with Twilio is ready to use!

+
+

+
+

Get started with your application

+

Follow these steps to try out your new app:

+

+ This app is a basic Twilio Voice TwiML template Function that will + return the + TwiML + to say "Hello World" +

+
    +
  1. Call your Twilio phone number
  2. +
  3. Be greeted by a robot voice saying "Hello World"
  4. +
+
-
- -
-
-

Troubleshooting

-
    -
  • - Check the - - phone number configuration - - and make sure the Twilio phone number you want for your app has a voice webhook - configured to point at the following URL -
    - - -
    -
  • -
-
-
-
- - +
+ +
+
+

Troubleshooting

+ +
+
+ + + diff --git a/hello-voice/changelog.md b/hello-voice/changelog.md index 3982d461..144828c2 100644 --- a/hello-voice/changelog.md +++ b/hello-voice/changelog.md @@ -3,6 +3,7 @@ ## [Unreleased] ## [1.0.0] + ### Added -- Initial release. +- Initial release. diff --git a/hello-voice/functions/hello-voice.protected.js b/hello-voice/functions/hello-voice.protected.js index 2ee54adc..3fe3b0c0 100644 --- a/hello-voice/functions/hello-voice.protected.js +++ b/hello-voice/functions/hello-voice.protected.js @@ -1,5 +1,14 @@ +/* + * hello-voice.protected.js + * This file contains an AWS Lambda function handler that generates a Twilio Voice Response. When triggered, it creates a TwiML response that instructs Twilio to speak "Hello World" to the caller. + */ + +// This is an AWS Lambda function handler for a Twilio Voice Response exports.handler = function (context, event, callback) { + // Create a new TwiML VoiceResponse object const twiml = new Twilio.twiml.VoiceResponse(); + // Add a 'Say' verb to the TwiML response, which will speak the text 'Hello World' to the caller twiml.say('Hello World'); + // End the function by calling the callback with the generated TwiML response callback(null, twiml); }; diff --git a/masked-number/.env b/masked-number/.env index fb964af7..77d1dffe 100644 --- a/masked-number/.env +++ b/masked-number/.env @@ -2,7 +2,7 @@ # format: phone_number # required: true # link: https://www.twilio.com/docs/glossary/what-e164 -MY_PHONE_NUMBER=+12223334444 +MY_PHONE_NUMBER=+1234567890 # description: The path to the webhook # configurable: false diff --git a/masked-number/assets/index.html b/masked-number/assets/index.html index 1e172050..528925a9 100644 --- a/masked-number/assets/index.html +++ b/masked-number/assets/index.html @@ -1,105 +1,153 @@ - + - - - - - Get started with your Twilio Functions! + + + + + Get started with your Twilio Functions! - - + + - - - - -
-
- - -
+ + + + +
+
+ -
-
-

- -
-

Welcome!

-

Your live application with Twilio is ready to use!

-
-

-
-

Get started with your application

-

- Follow these steps to try out your new app: -

-

- This app masks your phone number for SMS messages by relaying them through a - Twilio phone number. -

-
    -
  1. - Text your Twilio phone number with a recipient phone number, a - :, and a message. For example, to send the message "hello" to - the number "+12223334444", text your Twilio phone number with: - +12223334444: hello. -
  2. -
  3. Your recipient should receive your message from your Twilio phone number.
  4. -
  5. - When that recipient sends a reply to your Twilio phone number, it will be - relayed to your personal phone number. -
  6. -
-
+ +
+
+
+
+

+ +
+

Welcome!

+

Your live application with Twilio is ready to use!

+
+

+
+

Get started with your application

+

Follow these steps to try out your new app:

+

+ This app masks your phone number for SMS messages by relaying them + through a Twilio phone number. +

+
    +
  1. + Text your Twilio phone number with a recipient phone number, a + :, and a message. For example, to send the message + "hello" to the number "+12223334444", text your Twilio phone + number with: +12223334444: hello. +
  2. +
  3. + Your recipient should receive your message from your Twilio phone + number. +
  4. +
  5. + When that recipient sends a reply to your Twilio phone number, it + will be relayed to your personal phone number. +
  6. +
+
-
- -
-
-

Troubleshooting

-
    -
  • - Check the - - phone number configuration - - and make sure the Twilio phone number you want for your app has a SMS webhook - configured to point at the following URL -
    - - -
    -
  • -
  • - Ensure that MY_PHONE_NUMBER is set to the phone number you want - to relay messages to, in - - E.164 format - . -
  • -
-
-
-
- - +
+ +
+
+

Troubleshooting

+ +
+
+ + + diff --git a/masked-number/changelog.md b/masked-number/changelog.md index 3982d461..144828c2 100644 --- a/masked-number/changelog.md +++ b/masked-number/changelog.md @@ -3,6 +3,7 @@ ## [Unreleased] ## [1.0.0] + ### Added -- Initial release. +- Initial release. diff --git a/masked-number/functions/relay-sms.protected.js b/masked-number/functions/relay-sms.protected.js index c3d9f801..55ca85cd 100644 --- a/masked-number/functions/relay-sms.protected.js +++ b/masked-number/functions/relay-sms.protected.js @@ -1,32 +1,88 @@ +/* + * Masking your phone number for SMS Forwarding and Responding + * + * Description: + * This function acts as a message-forwarding service + * If the message comes from the user, it forwards the message to another number. + * If it comes from someone else, it forwards it to the user. + * + * Contents: + * 1. Initialize Client and Response + * 2. Sender Check + * 3. Extracting Recipient and Message + * 4. Sending the Message + * 5. Handling Messages from Other Senders + */ + +/* + * 1. Initialize client and response + * + * Here you can initialize a Twilio client (client) to interact with Twilio's API. + * + * A twiml object is created to generate TwiML (Twilio Markup Language) responses, + * which are instructions that tell Twilio how to respond to an incoming message. + */ + +// Defines the main function handler for the Twilio Function. exports.handler = async function (context, event, callback) { const client = context.getTwilioClient(); const twiml = new Twilio.twiml.MessagingResponse(); + /* + * 2. Checking the Sender + * + * Here you can checks if the message came from a specific number. + * This condition ensures that only the user with this phone number can send outgoing messages. + */ if (event.From === context.MY_PHONE_NUMBER) { + // Looks for a : in the message body to separate the recipient's phone number from the message text. const separatorPosition = event.Body.indexOf(':'); + // If there is no :, a response is sent to the sender with a message explaining the required format: recipientPhoneNumber: message. if (separatorPosition < 1) { twiml.message( 'You need to specify a recipient number and a ":" before the message. For example, "+12223334444: message".' ); + + /* + * 3. Extracting Recipient and Message + * + * Here you can processes the incoming message body + * to extract the recipient's phone number and the actual message content + */ } else { + // Extracts the recipient's phone number from the part of the message before the : and removes any extra whitespace. const recipientNumber = event.Body.substr(0, separatorPosition).trim(); + // Extracts the actual message from the part of the message after the ':' const messageBody = event.Body.substr(separatorPosition + 1).trim(); + /* + * 4. Sending the Message + * + * Here a try-catch block attempts to send an SMS message using the Twilio API + */ try { + // Sends the SMS to recipientNumber with the message messageBody. await client.messages.create({ to: recipientNumber, from: event.To, body: messageBody, }); - + // If successful, callback(null) is called to signal that the function executed successfully without returning any TwiML. return callback(null); + // If there is an error (e.g., an invalid phone number), a failure message is sent to the sender explaining that the phone number might be incorrect. } catch (err) { twiml.message( 'There was an issue with the phone number you entered; please verify it is correct and try again.' ); } } + /* + * 5. Handling Messages from Other Senders + * + * If the message is not from context.MY_PHONE_NUMBER, + * the function forwards it to the number stored in context.MY_PHONE_NUMBER, + */ } else { twiml.message( { to: context.MY_PHONE_NUMBER }, diff --git a/masked-number/package.json b/masked-number/package.json index 249bce8d..00db4c3d 100644 --- a/masked-number/package.json +++ b/masked-number/package.json @@ -2,5 +2,19 @@ "name": "masked-number", "version": "1.0.0", "private": true, - "dependencies": {} + "description": "Uses a Twilio phone number to relay SMS messages to and from your phone; since the other party only sees your Twilio number, this effectively allows you to mask your phone number for privacy purposes.", + "main": "index.js", + "directories": { + "test": "tests" + }, + "scripts": { + "test": "jest", + "format": "prettier --write .", + "format:check": "prettier --check ." + }, + "author": "", + "license": "ISC", + "devDependencies": { + "prettier": "^3.3.3" + } } diff --git a/masked-number/tests/relay-sms.test.js b/masked-number/tests/relay-sms.test.js index 19e6b293..685b69fb 100644 --- a/masked-number/tests/relay-sms.test.js +++ b/masked-number/tests/relay-sms.test.js @@ -27,6 +27,12 @@ const baseEvent = { Body: '+16667778888: test message', }; +// Define your constant error messages here +const ERROR_MESSAGE = + 'There was an issue with the phone number you entered; please verify it is correct and try again.'; +const NO_RECIPIENT_ERROR = + 'You need to specify a recipient number and a ":" before the message. For example, "+12223334444: message".'; + beforeAll(() => { helpers.setup(context); }); @@ -41,16 +47,14 @@ beforeEach(() => { }); describe('masked-number function template', () => { - it('should send a message to a given phone number', (done) => { + it('should send a message to a given phone number', async () => { const event = { ...baseEvent }; const callback = (err, _result) => { expect(err).toBeFalsy(); expect(mockTwilioClient.messages.create).toHaveBeenCalled(); - - done(); }; - relaySms(context, event, callback); + await relaySms(context, event, callback); }); it('should relay messages from other phone numbers', (done) => { @@ -73,9 +77,7 @@ describe('masked-number function template', () => { const callback = (err, result) => { expect(err).toBeFalsy(); const twiml = result.toString(); - expect(twiml).toMatch( - `You need to specify a recipient number and a ":" before the message. For example, "+12223334444: message".` - ); + expect(twiml).toMatch(`${NO_RECIPIENT_ERROR}`); done(); }; @@ -89,9 +91,7 @@ describe('masked-number function template', () => { const callback = (err, result) => { expect(err).toBeFalsy(); const twiml = result.toString(); - expect(twiml).toMatch( - `There was an issue with the phone number you entered; please verify it is correct and try again.` - ); + expect(twiml).toMatch(`${ERROR_MESSAGE}`); done(); }; diff --git a/sms-notifications/sms-notifications/.gitignore b/sms-notifications/sms-notifications/.gitignore new file mode 100644 index 00000000..ca7e037f --- /dev/null +++ b/sms-notifications/sms-notifications/.gitignore @@ -0,0 +1,133 @@ +# Twilio Serverless +.twiliodeployinfo + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/sms-notifications/sms-notifications/.nvmrc b/sms-notifications/sms-notifications/.nvmrc new file mode 100644 index 00000000..25bf17fc --- /dev/null +++ b/sms-notifications/sms-notifications/.nvmrc @@ -0,0 +1 @@ +18 \ No newline at end of file diff --git a/sms-notifications/sms-notifications/.twilioserverlessrc b/sms-notifications/sms-notifications/.twilioserverlessrc new file mode 100644 index 00000000..a7f5a1cb --- /dev/null +++ b/sms-notifications/sms-notifications/.twilioserverlessrc @@ -0,0 +1,42 @@ +{ + "commands": {}, + "environments": {}, + "projects": {}, + // "assets": true /* Upload assets. Can be turned off with --no-assets */, + // "assetsFolder": null /* Specific folder name to be used for static assets */, + // "buildSid": null /* An existing Build SID to deploy to the new environment */, + // "createEnvironment": false /* Creates environment if it couldn't find it. */, + // "cwd": null /* Sets the directory of your existing Serverless project. Defaults to current directory */, + // "detailedLogs": false /* Toggles detailed request logging by showing request body and query params */, + // "edge": null /* Twilio API Region */, + // "env": null /* Path to .env file for environment variables that should be installed */, + // "environment": "dev" /* The environment name (domain suffix) you want to use for your deployment. Alternatively you can specify an environment SID starting with ZE. */, + // "extendedOutput": false /* Show an extended set of properties on the output */, + // "force": false /* Will run deployment in force mode. Can be dangerous. */, + // "forkProcess": true /* Disable forking function processes to emulate production environment */, + // "functionSid": null /* Specific Function SID to retrieve logs for */, + // "functions": true /* Upload functions. Can be turned off with --no-functions */, + // "functionsFolder": null /* Specific folder name to be used for static functions */, + // "inspect": null /* Enables Node.js debugging protocol */, + // "inspectBrk": null /* Enables Node.js debugging protocol, stops execution until debugger is attached */, + // "legacyMode": false /* Enables legacy mode, it will prefix your asset paths with /assets */, + // "live": true /* Always serve from the current functions (no caching) */, + // "loadLocalEnv": false /* Includes the local environment variables */, + // "loadSystemEnv": false /* Uses system environment variables as fallback for variables specified in your .env file. Needs to be used with --env explicitly specified. */, + // "logCacheSize": null /* Tailing the log endpoint will cache previously seen entries to avoid duplicates. The cache is topped at a maximum of 1000 by default. This option can change that. */, + // "logLevel": "info" /* Level of logging messages. */, + // "logs": true /* Toggles request logging */, + // "ngrok": null /* Uses ngrok to create a public url. Pass a string to set the subdomain (requires a paid-for ngrok account). */, + // "outputFormat": "" /* Output the results in a different format */, + // "overrideExistingProject": false /* Deploys Serverless project to existing service if a naming conflict has been found. */, + // "port": "3000" /* Override default port of 3000 */, + // "production": false /* Promote build to the production environment (no domain suffix). Overrides environment flag */, + // "properties": null /* Specify the output properties you want to see. Works best on single types */, + // "region": null /* Twilio API Region */, + "runtime": "node18" /* The version of Node.js to deploy the build to. (node18) */, + // "serviceName": null /* Overrides the name of the Serverless project. Default: the name field in your package.json */, + // "serviceSid": null /* SID of the Twilio Serverless Service to deploy to */, + // "sourceEnvironment": null /* SID or suffix of an existing environment you want to deploy from. */, + // "tail": false /* Continuously stream the logs */, + // "template": null /* undefined */, +} \ No newline at end of file diff --git a/sms-notifications/sms-notifications/assets/index.html b/sms-notifications/sms-notifications/assets/index.html new file mode 100644 index 00000000..7ea52f23 --- /dev/null +++ b/sms-notifications/sms-notifications/assets/index.html @@ -0,0 +1,86 @@ + + + + + + + + Hello Twilio Serverless! + + +

Hello Twilio Serverless!

+ +
+

+ Congratulations you just started a new Twilio + Serverless project. +

+ +

Assets

+

+ Assets are static files, like HTML, CSS, JavaScript, images or audio + files. +

+ +

+ This HTML page is an example of a public asset, you can + access this by loading it in the browser. The HTML also refers to + another public asset for CSS styles. +

+

+ You can also have private assets, there is an example + private asset called message.private.js in the + /assets directory. This file cannot be loaded in the + browser, but you can load it as part of a function by finding its path + using Runtime.getAssets() and then requiring the file. + There is an example of this in + /functions/private-message.js. +

+ +

Functions

+

+ Functions are JavaScript files that will respond to incoming HTTP + requests. There are public and + protected functions. +

+ +

+ Public functions respond to all HTTP requests. There is + an example of a public function in + /functions/hello-world.js. +

+ +

+ Protected functions will only respond to HTTP requests + with a valid Twilio signature in the header. You can read more about + validating requests from Twilio in the documentation. There is an example of a protected function in + /functions/sms/reply.protected.js +

+ +

twilio-run

+ +

+ Functions and assets are served, deployed and debugged using + twilio-run. You can serve the project locally with the command + npm start which is really running + twilio-run --env under the hood. If you want to see what + else you can do with twilio-run enter + npx twilio-run --help on the command line or check out + the project documentation on GitHub. +

+
+ + + + diff --git a/sms-notifications/sms-notifications/assets/message.private.js b/sms-notifications/sms-notifications/assets/message.private.js new file mode 100644 index 00000000..d7bbe31b --- /dev/null +++ b/sms-notifications/sms-notifications/assets/message.private.js @@ -0,0 +1,6 @@ +// This is an example of of a private message +const privateMessage = () => { + return 'This is private!'; +}; + +module.exports = privateMessage; diff --git a/sms-notifications/sms-notifications/assets/style.css b/sms-notifications/sms-notifications/assets/style.css new file mode 100644 index 00000000..2a4522db --- /dev/null +++ b/sms-notifications/sms-notifications/assets/style.css @@ -0,0 +1,73 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +::selection { + background: #f22f46; + color: white; +} + +body { + font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, + 'Helvetica Neue', Arial, sans-serif; + color: #0d122b; + border-top: 5px solid #f22f46; +} + +header { + padding: 2em; + margin-bottom: 2em; + max-width: 800px; + margin: 0 auto; +} + +header h1 { + padding-bottom: 14px; + border-bottom: 1px solid rgba(148, 151, 155, 0.2); +} + +a { + color: #008cff; +} + +main { + margin: 0 auto 6em; + padding: 0 2em; + max-width: 800px; +} + +main p { + margin-bottom: 2em; +} + +main p code { + font-size: 16px; + font-family: 'Fira Mono', monospace; + color: #f22f46; + background-color: #f9f9f9; + box-shadow: inset 0 0 0 1px #e8e8e8; + font-size: inherit; + line-height: 1.2; + padding: 0.15em 0.4em; + border-radius: 4px; + display: inline-block; + white-space: pre-wrap; +} + +main h2 { + margin-bottom: 1em; +} + +footer { + margin: 0 auto; + max-width: 800px; + text-align: center; +} + +footer p { + border-top: 1px solid rgba(148, 151, 155, 0.2); + padding-top: 2em; + margin: 0 2em; +} diff --git a/sms-notifications/sms-notifications/functions/hello-world.js b/sms-notifications/sms-notifications/functions/hello-world.js new file mode 100644 index 00000000..8fde2a79 --- /dev/null +++ b/sms-notifications/sms-notifications/functions/hello-world.js @@ -0,0 +1,5 @@ +exports.handler = function (context, event, callback) { + const twiml = new Twilio.twiml.VoiceResponse(); + twiml.say('Hello World!'); + callback(null, twiml); +}; diff --git a/sms-notifications/sms-notifications/functions/private-message.js b/sms-notifications/sms-notifications/functions/private-message.js new file mode 100644 index 00000000..ec200d4a --- /dev/null +++ b/sms-notifications/sms-notifications/functions/private-message.js @@ -0,0 +1,9 @@ +exports.handler = function (context, event, callback) { + const assets = Runtime.getAssets(); + const privateMessageAsset = assets['/message.js']; + const privateMessagePath = privateMessageAsset.path; + const privateMessage = require(privateMessagePath); + const twiml = new Twilio.twiml.MessagingResponse(); + twiml.message(privateMessage()); + callback(null, twiml); +}; diff --git a/sms-notifications/sms-notifications/functions/sms/reply.protected.js b/sms-notifications/sms-notifications/functions/sms/reply.protected.js new file mode 100644 index 00000000..6e768af1 --- /dev/null +++ b/sms-notifications/sms-notifications/functions/sms/reply.protected.js @@ -0,0 +1,5 @@ +exports.handler = function (context, event, callback) { + const twiml = new Twilio.twiml.MessagingResponse(); + twiml.message('Hello World!'); + callback(null, twiml); +}; diff --git a/sms-notifications/sms-notifications/package.json b/sms-notifications/sms-notifications/package.json new file mode 100644 index 00000000..3e41ee57 --- /dev/null +++ b/sms-notifications/sms-notifications/package.json @@ -0,0 +1,20 @@ +{ + "name": "sms-notifications", + "version": "0.0.0", + "private": true, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "twilio-run", + "deploy": "twilio-run deploy" + }, + "dependencies": { + "twilio": "5.0.3", + "@twilio/runtime-handler": "2.0.3" + }, + "devDependencies": { + "twilio-run": "^4.0.3" + }, + "engines": { + "node": "18" + } +}