From 915f0361072c0d2014690699a6052592b3303a31 Mon Sep 17 00:00:00 2001
From: loks0n <22452787+loks0n@users.noreply.github.com>
Date: Tue, 18 Jul 2023 10:39:12 +0100
Subject: [PATCH 01/11] feat: WhatsApp with Vonage function
---
whatsapp-with-vonage/.gitignore | 130 +++++++++++++++++
whatsapp-with-vonage/.prettierrc.json | 6 +
whatsapp-with-vonage/README.md | 36 +++++
whatsapp-with-vonage/env.d.ts | 11 ++
whatsapp-with-vonage/package-lock.json | 194 +++++++++++++++++++++++++
whatsapp-with-vonage/package.json | 21 +++
whatsapp-with-vonage/src/main.js | 58 ++++++++
whatsapp-with-vonage/static/index.html | 34 +++++
8 files changed, 490 insertions(+)
create mode 100644 whatsapp-with-vonage/.gitignore
create mode 100644 whatsapp-with-vonage/.prettierrc.json
create mode 100644 whatsapp-with-vonage/README.md
create mode 100644 whatsapp-with-vonage/env.d.ts
create mode 100644 whatsapp-with-vonage/package-lock.json
create mode 100644 whatsapp-with-vonage/package.json
create mode 100644 whatsapp-with-vonage/src/main.js
create mode 100644 whatsapp-with-vonage/static/index.html
diff --git a/whatsapp-with-vonage/.gitignore b/whatsapp-with-vonage/.gitignore
new file mode 100644
index 00000000..6a7d6d8e
--- /dev/null
+++ b/whatsapp-with-vonage/.gitignore
@@ -0,0 +1,130 @@
+# 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.*
\ No newline at end of file
diff --git a/whatsapp-with-vonage/.prettierrc.json b/whatsapp-with-vonage/.prettierrc.json
new file mode 100644
index 00000000..fbe0e552
--- /dev/null
+++ b/whatsapp-with-vonage/.prettierrc.json
@@ -0,0 +1,6 @@
+{
+ "trailingComma": "es5",
+ "tabWidth": 2,
+ "semi": false,
+ "singleQuote": true
+}
\ No newline at end of file
diff --git a/whatsapp-with-vonage/README.md b/whatsapp-with-vonage/README.md
new file mode 100644
index 00000000..83bd3041
--- /dev/null
+++ b/whatsapp-with-vonage/README.md
@@ -0,0 +1,36 @@
+# WhatsApp Reply Function
+
+This function allows you to reply to a WhatsApp message through the [Vonage Messages API](https://developer.vonage.com/en/messaging/sms/overview). When a POST request is sent to this function with the necessary information, it sends a reply to the sender using the content of their original message.
+
+## Environment Variables
+
+For the function to operate correctly, the following variables must be set:
+
+- **VONAGE_API_KEY**: This is your Vonage project's API key. You can obtain this key from the Vonage dashboard under the "Getting Started" section. This key is used to authorize your application's requests to Vonage's APIs.
+
+- **VONAGE_API_SECRET**: This is the secret for your Vonage project's API key. You can obtain this secret from the same section in the Vonage dashboard where you got the API key. The API secret is used together with the API key to form the Basic Auth string for your requests.
+
+- **VONAGE_API_SIGNATURE_SECRET**: This is the signature secret for your Vonage project's API key. You can generate this secret in the "Settings" section of your Vonage dashboard, under "API Settings". This secret is used to verify the JWT token in the incoming request to ensure it's from an authorized source.
+
+## Usage
+
+This function supports two types of requests:
+
+1. **Displaying a Landing Page**
+
+ - **Request Type:** GET
+ - **Response:**
+ - On success, the function will respond with a landing page in HTML format.
+
+2. **Replying to a WhatsApp Message**
+
+ - **Request Type:** POST
+ - **Headers:**
+ - `Authorization`: Bearer token using the `VONAGE_API_SIGNATURE_SECRET`
+ - **Body:**
+ - `from`: Sender's phone number
+ - `text`: (Optional) Text of the sender's message
+ - **Response:**
+ - On success, the function will send a WhatsApp message to the sender's number, including the text "Hi there! You sent me: [text]" where [text] is replaced with the content of the sender's message, and respond with a status message of "OK".
+ - If the `from` field is missing in the request, an error message "Payload invalid." is returned.
+ - If the JWT token in the `Authorization` header is invalid or missing, an error message "Invalid signature." is returned.
\ No newline at end of file
diff --git a/whatsapp-with-vonage/env.d.ts b/whatsapp-with-vonage/env.d.ts
new file mode 100644
index 00000000..22835eb4
--- /dev/null
+++ b/whatsapp-with-vonage/env.d.ts
@@ -0,0 +1,11 @@
+declare global {
+ namespace NodeJS {
+ interface ProcessEnv {
+ VONAGE_API_KEY?: string
+ VONAGE_API_SECRET?: string
+ VONAGE_API_SIGNATURE_SECRET?: string
+ }
+ }
+}
+
+export {}
diff --git a/whatsapp-with-vonage/package-lock.json b/whatsapp-with-vonage/package-lock.json
new file mode 100644
index 00000000..e620314b
--- /dev/null
+++ b/whatsapp-with-vonage/package-lock.json
@@ -0,0 +1,194 @@
+{
+ "name": "censor-with-redact",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "censor-with-redact",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "jsonwebtoken": "^9.0.1",
+ "sha256": "^0.2.0"
+ },
+ "devDependencies": {
+ "prettier": "^3.0.0",
+ "undici": "^5.22.1"
+ }
+ },
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+ },
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dev": true,
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
+ "node_modules/convert-hex": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/convert-hex/-/convert-hex-0.1.0.tgz",
+ "integrity": "sha512-w20BOb1PiR/sEJdS6wNrUjF5CSfscZFUp7R9NSlXH8h2wynzXVEPFPJECAnkNylZ+cvf3p7TyRUHggDmrwXT9A=="
+ },
+ "node_modules/convert-string": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/convert-string/-/convert-string-0.1.0.tgz",
+ "integrity": "sha512-1KX9ESmtl8xpT2LN2tFnKSbV4NiarbVi8DVb39ZriijvtTklyrT+4dT1wsGMHKD3CJUjXgvJzstm9qL9ICojGA=="
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz",
+ "integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==",
+ "dependencies": {
+ "jws": "^3.2.2",
+ "lodash": "^4.17.21",
+ "ms": "^2.1.1",
+ "semver": "^7.3.8"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "dependencies": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "dependencies": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/prettier": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz",
+ "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/semver": {
+ "version": "7.5.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
+ "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sha256": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/sha256/-/sha256-0.2.0.tgz",
+ "integrity": "sha512-kTWMJUaez5iiT9CcMv8jSq6kMhw3ST0uRdcIWl3D77s6AsLXNXRp3heeqqfu5+Dyfu4hwpQnMzhqHh8iNQxw0w==",
+ "dependencies": {
+ "convert-hex": "~0.1.0",
+ "convert-string": "~0.1.0"
+ }
+ },
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/undici": {
+ "version": "5.22.1",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz",
+ "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==",
+ "dev": true,
+ "dependencies": {
+ "busboy": "^1.6.0"
+ },
+ "engines": {
+ "node": ">=14.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ }
+ }
+}
diff --git a/whatsapp-with-vonage/package.json b/whatsapp-with-vonage/package.json
new file mode 100644
index 00000000..3e6fac0b
--- /dev/null
+++ b/whatsapp-with-vonage/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "censor-with-redact",
+ "version": "1.0.0",
+ "description": "",
+ "type": "module",
+ "main": "src/main.js",
+ "scripts": {
+ "format": "prettier --write src/**/*.js"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "jsonwebtoken": "^9.0.1",
+ "sha256": "^0.2.0",
+ "undici": "^5.22.1"
+ },
+ "devDependencies": {
+ "prettier": "^3.0.0"
+ }
+}
diff --git a/whatsapp-with-vonage/src/main.js b/whatsapp-with-vonage/src/main.js
new file mode 100644
index 00000000..72457ed0
--- /dev/null
+++ b/whatsapp-with-vonage/src/main.js
@@ -0,0 +1,58 @@
+import jwt from 'jsonwebtoken'
+import sha256 from 'sha256'
+import { fetch } from 'undici'
+import fs from 'fs'
+import path from 'path'
+import { fileURLToPath } from 'url'
+
+const __filename = fileURLToPath(import.meta.url)
+const __dirname = path.dirname(__filename)
+const staticFolder = path.join(__dirname, '../static')
+
+export default async ({ req, res }) => {
+ const { VONAGE_API_KEY, VONAGE_API_SECRET, VONAGE_API_SIGNATURE_SECRET } =
+ process.env
+
+ if (!VONAGE_API_KEY || !VONAGE_API_SECRET || !VONAGE_API_SIGNATURE_SECRET) {
+ throw new Error('Function is missing required environment variables.')
+ }
+
+ if (req.method === 'GET') {
+ const html = fs
+ .readFileSync(path.join(staticFolder, 'index.html'))
+ .toString()
+ return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' })
+ }
+
+ const token = (req.headers.authorization ?? '').split(' ')[1]
+ var decoded = jwt.verify(token, VONAGE_API_SIGNATURE_SECRET, {
+ algorithms: ['HS256'],
+ })
+
+ if (sha256(req.bodyString) != decoded['payload_hash']) {
+ throw new Error('Invalid signature.')
+ }
+
+ if (!req.body.from) {
+ throw new Error('Payload invalid.')
+ }
+
+ const text = req.body.text ?? 'I only accept text messages.'
+
+ await fetch(`https://messages-sandbox.nexmo.com/v1/messages`, {
+ method: 'POST',
+ body: JSON.stringify({
+ from: '14157386102',
+ to: req.body.from,
+ message_type: 'text',
+ text: `Hi there! You sent me: ${text}`,
+ channel: 'whatsapp',
+ }),
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Basic ${btoa(`${VONAGE_API_KEY}:${VONAGE_API_SECRET}`)}`,
+ },
+ })
+
+ return res.send('OK')
+}
diff --git a/whatsapp-with-vonage/static/index.html b/whatsapp-with-vonage/static/index.html
new file mode 100644
index 00000000..3aa89c07
--- /dev/null
+++ b/whatsapp-with-vonage/static/index.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+ Whats App Bot with Vonage
+
+
+
+
+
+
+
+
+
+
Whats App Bot with Vonage
+
+
+
+ This function listens to incoming webhooks from Vonage regarding
+ WhatsApp messages, and responds to them. To use the function, send
+ message to the WhatsApp user provided by Vonage.
+
+
+
+
+
+
From 880ce77c8d0d3c9f64d6a5b77d4392495931551c Mon Sep 17 00:00:00 2001
From: loks0n <22452787+loks0n@users.noreply.github.com>
Date: Wed, 19 Jul 2023 11:09:06 +0100
Subject: [PATCH 02/11] chore: add semis, del pjson extras
---
whatsapp-with-vonage/.prettierrc.json | 10 +++---
whatsapp-with-vonage/package.json | 2 --
whatsapp-with-vonage/src/main.js | 42 +++++++++++++-------------
whatsapp-with-vonage/static/index.html | 2 +-
4 files changed, 27 insertions(+), 29 deletions(-)
diff --git a/whatsapp-with-vonage/.prettierrc.json b/whatsapp-with-vonage/.prettierrc.json
index fbe0e552..0a725205 100644
--- a/whatsapp-with-vonage/.prettierrc.json
+++ b/whatsapp-with-vonage/.prettierrc.json
@@ -1,6 +1,6 @@
{
- "trailingComma": "es5",
- "tabWidth": 2,
- "semi": false,
- "singleQuote": true
-}
\ No newline at end of file
+ "trailingComma": "es5",
+ "tabWidth": 2,
+ "semi": true,
+ "singleQuote": true
+}
diff --git a/whatsapp-with-vonage/package.json b/whatsapp-with-vonage/package.json
index 3e6fac0b..f8c64e51 100644
--- a/whatsapp-with-vonage/package.json
+++ b/whatsapp-with-vonage/package.json
@@ -8,8 +8,6 @@
"format": "prettier --write src/**/*.js"
},
"keywords": [],
- "author": "",
- "license": "ISC",
"dependencies": {
"jsonwebtoken": "^9.0.1",
"sha256": "^0.2.0",
diff --git a/whatsapp-with-vonage/src/main.js b/whatsapp-with-vonage/src/main.js
index 72457ed0..8da6abdf 100644
--- a/whatsapp-with-vonage/src/main.js
+++ b/whatsapp-with-vonage/src/main.js
@@ -1,43 +1,43 @@
-import jwt from 'jsonwebtoken'
-import sha256 from 'sha256'
-import { fetch } from 'undici'
-import fs from 'fs'
-import path from 'path'
-import { fileURLToPath } from 'url'
+import jwt from 'jsonwebtoken';
+import sha256 from 'sha256';
+import { fetch } from 'undici';
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
-const __filename = fileURLToPath(import.meta.url)
-const __dirname = path.dirname(__filename)
-const staticFolder = path.join(__dirname, '../static')
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const staticFolder = path.join(__dirname, '../static');
export default async ({ req, res }) => {
const { VONAGE_API_KEY, VONAGE_API_SECRET, VONAGE_API_SIGNATURE_SECRET } =
- process.env
+ process.env;
if (!VONAGE_API_KEY || !VONAGE_API_SECRET || !VONAGE_API_SIGNATURE_SECRET) {
- throw new Error('Function is missing required environment variables.')
+ throw new Error('Function is missing required environment variables.');
}
if (req.method === 'GET') {
const html = fs
.readFileSync(path.join(staticFolder, 'index.html'))
- .toString()
- return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' })
+ .toString();
+ return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' });
}
- const token = (req.headers.authorization ?? '').split(' ')[1]
+ const token = (req.headers.authorization ?? '').split(' ')[1];
var decoded = jwt.verify(token, VONAGE_API_SIGNATURE_SECRET, {
algorithms: ['HS256'],
- })
+ });
if (sha256(req.bodyString) != decoded['payload_hash']) {
- throw new Error('Invalid signature.')
+ throw new Error('Invalid signature.');
}
if (!req.body.from) {
- throw new Error('Payload invalid.')
+ throw new Error('Payload invalid.');
}
- const text = req.body.text ?? 'I only accept text messages.'
+ const text = req.body.text ?? 'I only accept text messages.';
await fetch(`https://messages-sandbox.nexmo.com/v1/messages`, {
method: 'POST',
@@ -52,7 +52,7 @@ export default async ({ req, res }) => {
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${VONAGE_API_KEY}:${VONAGE_API_SECRET}`)}`,
},
- })
+ });
- return res.send('OK')
-}
+ return res.send('OK');
+};
diff --git a/whatsapp-with-vonage/static/index.html b/whatsapp-with-vonage/static/index.html
index 3aa89c07..00b6adaa 100644
--- a/whatsapp-with-vonage/static/index.html
+++ b/whatsapp-with-vonage/static/index.html
@@ -1,4 +1,4 @@
-
+
From f571ca66687a3788e9939708f4c6cbf0da14308c Mon Sep 17 00:00:00 2001
From: loks0n <22452787+loks0n@users.noreply.github.com>
Date: Wed, 19 Jul 2023 21:47:58 +0100
Subject: [PATCH 03/11] chore: use environment pattern
---
whatsapp-with-vonage/src/environment.js | 19 +++++++++++++++++++
whatsapp-with-vonage/src/main.js | 7 ++-----
2 files changed, 21 insertions(+), 5 deletions(-)
create mode 100644 whatsapp-with-vonage/src/environment.js
diff --git a/whatsapp-with-vonage/src/environment.js b/whatsapp-with-vonage/src/environment.js
new file mode 100644
index 00000000..18fab8a5
--- /dev/null
+++ b/whatsapp-with-vonage/src/environment.js
@@ -0,0 +1,19 @@
+/**
+ * @param {string} key
+ * @return {string}
+ */
+function getRequiredEnv(key) {
+ const value = process.env[key];
+ if (value === undefined) {
+ throw new Error(`Environment variable ${key} is not set`);
+ }
+ return value;
+}
+
+class EnvironmentService {
+ VONAGE_API_KEY = getRequiredEnv('VONAGE_API_KEY');
+ VONAGE_API_SECRET = getRequiredEnv('VONAGE_API_SECRET');
+ VONAGE_API_SIGNATURE_SECRET = getRequiredEnv('VONAGE_API_SIGNATURE_SECRET');
+}
+
+export default EnvironmentService;
diff --git a/whatsapp-with-vonage/src/main.js b/whatsapp-with-vonage/src/main.js
index 8da6abdf..4684971b 100644
--- a/whatsapp-with-vonage/src/main.js
+++ b/whatsapp-with-vonage/src/main.js
@@ -4,6 +4,7 @@ import { fetch } from 'undici';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
+import EnvironmentService from './environment.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -11,11 +12,7 @@ const staticFolder = path.join(__dirname, '../static');
export default async ({ req, res }) => {
const { VONAGE_API_KEY, VONAGE_API_SECRET, VONAGE_API_SIGNATURE_SECRET } =
- process.env;
-
- if (!VONAGE_API_KEY || !VONAGE_API_SECRET || !VONAGE_API_SIGNATURE_SECRET) {
- throw new Error('Function is missing required environment variables.');
- }
+ new EnvironmentService();
if (req.method === 'GET') {
const html = fs
From 1bbf55f9697abdc8edaea131bb7bb05e0fb090a0 Mon Sep 17 00:00:00 2001
From: loks0n <22452787+loks0n@users.noreply.github.com>
Date: Thu, 20 Jul 2023 10:41:55 +0100
Subject: [PATCH 04/11] chore: new structure
---
{whatsapp-with-vonage => node/whatsapp-with-vonage}/.gitignore | 0
.../whatsapp-with-vonage}/.prettierrc.json | 0
{whatsapp-with-vonage => node/whatsapp-with-vonage}/README.md | 0
{whatsapp-with-vonage => node/whatsapp-with-vonage}/env.d.ts | 0
.../whatsapp-with-vonage}/package-lock.json | 0
{whatsapp-with-vonage => node/whatsapp-with-vonage}/package.json | 0
.../whatsapp-with-vonage}/src/environment.js | 0
{whatsapp-with-vonage => node/whatsapp-with-vonage}/src/main.js | 0
.../whatsapp-with-vonage}/static/index.html | 0
9 files changed, 0 insertions(+), 0 deletions(-)
rename {whatsapp-with-vonage => node/whatsapp-with-vonage}/.gitignore (100%)
rename {whatsapp-with-vonage => node/whatsapp-with-vonage}/.prettierrc.json (100%)
rename {whatsapp-with-vonage => node/whatsapp-with-vonage}/README.md (100%)
rename {whatsapp-with-vonage => node/whatsapp-with-vonage}/env.d.ts (100%)
rename {whatsapp-with-vonage => node/whatsapp-with-vonage}/package-lock.json (100%)
rename {whatsapp-with-vonage => node/whatsapp-with-vonage}/package.json (100%)
rename {whatsapp-with-vonage => node/whatsapp-with-vonage}/src/environment.js (100%)
rename {whatsapp-with-vonage => node/whatsapp-with-vonage}/src/main.js (100%)
rename {whatsapp-with-vonage => node/whatsapp-with-vonage}/static/index.html (100%)
diff --git a/whatsapp-with-vonage/.gitignore b/node/whatsapp-with-vonage/.gitignore
similarity index 100%
rename from whatsapp-with-vonage/.gitignore
rename to node/whatsapp-with-vonage/.gitignore
diff --git a/whatsapp-with-vonage/.prettierrc.json b/node/whatsapp-with-vonage/.prettierrc.json
similarity index 100%
rename from whatsapp-with-vonage/.prettierrc.json
rename to node/whatsapp-with-vonage/.prettierrc.json
diff --git a/whatsapp-with-vonage/README.md b/node/whatsapp-with-vonage/README.md
similarity index 100%
rename from whatsapp-with-vonage/README.md
rename to node/whatsapp-with-vonage/README.md
diff --git a/whatsapp-with-vonage/env.d.ts b/node/whatsapp-with-vonage/env.d.ts
similarity index 100%
rename from whatsapp-with-vonage/env.d.ts
rename to node/whatsapp-with-vonage/env.d.ts
diff --git a/whatsapp-with-vonage/package-lock.json b/node/whatsapp-with-vonage/package-lock.json
similarity index 100%
rename from whatsapp-with-vonage/package-lock.json
rename to node/whatsapp-with-vonage/package-lock.json
diff --git a/whatsapp-with-vonage/package.json b/node/whatsapp-with-vonage/package.json
similarity index 100%
rename from whatsapp-with-vonage/package.json
rename to node/whatsapp-with-vonage/package.json
diff --git a/whatsapp-with-vonage/src/environment.js b/node/whatsapp-with-vonage/src/environment.js
similarity index 100%
rename from whatsapp-with-vonage/src/environment.js
rename to node/whatsapp-with-vonage/src/environment.js
diff --git a/whatsapp-with-vonage/src/main.js b/node/whatsapp-with-vonage/src/main.js
similarity index 100%
rename from whatsapp-with-vonage/src/main.js
rename to node/whatsapp-with-vonage/src/main.js
diff --git a/whatsapp-with-vonage/static/index.html b/node/whatsapp-with-vonage/static/index.html
similarity index 100%
rename from whatsapp-with-vonage/static/index.html
rename to node/whatsapp-with-vonage/static/index.html
From 89493b35d757b849a0d5efdeae1f8b7992b13b93 Mon Sep 17 00:00:00 2001
From: loks0n <22452787+loks0n@users.noreply.github.com>
Date: Thu, 20 Jul 2023 12:11:11 +0100
Subject: [PATCH 05/11] chore: prettier script
---
node/whatsapp-with-vonage/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/node/whatsapp-with-vonage/package.json b/node/whatsapp-with-vonage/package.json
index f8c64e51..790ecabb 100644
--- a/node/whatsapp-with-vonage/package.json
+++ b/node/whatsapp-with-vonage/package.json
@@ -5,7 +5,7 @@
"type": "module",
"main": "src/main.js",
"scripts": {
- "format": "prettier --write src/**/*.js"
+ "format": "prettier --write ."
},
"keywords": [],
"dependencies": {
From 6cf31c84dbea0ea0200e719b7c1069d1854e411c Mon Sep 17 00:00:00 2001
From: loks0n <22452787+loks0n@users.noreply.github.com>
Date: Tue, 25 Jul 2023 15:29:18 +0100
Subject: [PATCH 06/11] docs: update to new template
---
node/whatsapp-with-vonage/README.md | 80 ++++++++++++++++++++---------
1 file changed, 56 insertions(+), 24 deletions(-)
diff --git a/node/whatsapp-with-vonage/README.md b/node/whatsapp-with-vonage/README.md
index 83bd3041..396f8f5f 100644
--- a/node/whatsapp-with-vonage/README.md
+++ b/node/whatsapp-with-vonage/README.md
@@ -1,36 +1,68 @@
-# WhatsApp Reply Function
+# ⚡ Vonage Message Receiver and Responder
-This function allows you to reply to a WhatsApp message through the [Vonage Messages API](https://developer.vonage.com/en/messaging/sms/overview). When a POST request is sent to this function with the necessary information, it sends a reply to the sender using the content of their original message.
+Automatically respond to messages sent to a Vonage WhatsApp number.
-## Environment Variables
+## 🧰 Usage
-For the function to operate correctly, the following variables must be set:
+### `GET /`
-- **VONAGE_API_KEY**: This is your Vonage project's API key. You can obtain this key from the Vonage dashboard under the "Getting Started" section. This key is used to authorize your application's requests to Vonage's APIs.
+Serves a HTML page.
-- **VONAGE_API_SECRET**: This is the secret for your Vonage project's API key. You can obtain this secret from the same section in the Vonage dashboard where you got the API key. The API secret is used together with the API key to form the Basic Auth string for your requests.
+### `POST /`
-- **VONAGE_API_SIGNATURE_SECRET**: This is the signature secret for your Vonage project's API key. You can generate this secret in the "Settings" section of your Vonage dashboard, under "API Settings". This secret is used to verify the JWT token in the incoming request to ensure it's from an authorized source.
+Receives a message, validates its signature, and sends a response back to the sender.
-## Usage
+**Parameters**
-This function supports two types of requests:
+| Name | Description | Location | Type | Sample Value |
+| ------------ | ---------------------------- | -------- | ------------------- | ------------ |
+| Content-Type | Content type of the request | Header | `application/json ` | N/A |
+| from | Sender's identifier. | Body | String | `12345` |
+| text | Text content of the message. | Body | String | `Hello!` |
-1. **Displaying a Landing Page**
+**Response**
- - **Request Type:** GET
- - **Response:**
- - On success, the function will respond with a landing page in HTML format.
+Sample `200` Response:
-2. **Replying to a WhatsApp Message**
+```text
+OK
+```
- - **Request Type:** POST
- - **Headers:**
- - `Authorization`: Bearer token using the `VONAGE_API_SIGNATURE_SECRET`
- - **Body:**
- - `from`: Sender's phone number
- - `text`: (Optional) Text of the sender's message
- - **Response:**
- - On success, the function will send a WhatsApp message to the sender's number, including the text "Hi there! You sent me: [text]" where [text] is replaced with the content of the sender's message, and respond with a status message of "OK".
- - If the `from` field is missing in the request, an error message "Payload invalid." is returned.
- - If the JWT token in the `Authorization` header is invalid or missing, an error message "Invalid signature." is returned.
\ No newline at end of file
+## ⚙️ Configuration
+
+| Setting | Value |
+| ----------------- | ------------- |
+| Runtime | Node (18.0) |
+| Entrypoint | `src/main.js` |
+| Build Commands | `npm install` |
+| Permissions | `any` |
+| Timeout (Seconds) | 15 |
+
+## 🔒 Environment Variables
+
+### VONAGE_API_KEY
+
+API Key to use the Vonage API.
+
+| Question | Answer |
+| ------------ | ---------- |
+| Required | Yes |
+| Sample Value | `abcd1234` |
+
+### VONAGE_API_SECRET
+
+Secret to use the Vonage API.
+
+| Question | Answer |
+| ------------ | ---------- |
+| Required | Yes |
+| Sample Value | `efgh5678` |
+
+### VONAGE_API_SIGNATURE_SECRET
+
+Secret to verify the JWT token sent by Vonage.
+
+| Question | Answer |
+| ------------ | ----------- |
+| Required | Yes |
+| Sample Value | `ijkl91011` |
From 97a0f8a612bb46333068e0c2b6660e2d3e2ddcb1 Mon Sep 17 00:00:00 2001
From: loks0n <22452787+loks0n@users.noreply.github.com>
Date: Thu, 27 Jul 2023 13:21:58 +0100
Subject: [PATCH 07/11] chore: use json
---
node/whatsapp-with-vonage/README.md | 61 +++++++++++++++++---
node/whatsapp-with-vonage/src/environment.js | 19 ------
node/whatsapp-with-vonage/src/main.js | 29 +++++++---
3 files changed, 72 insertions(+), 37 deletions(-)
delete mode 100644 node/whatsapp-with-vonage/src/environment.js
diff --git a/node/whatsapp-with-vonage/README.md b/node/whatsapp-with-vonage/README.md
index 396f8f5f..442cb50d 100644
--- a/node/whatsapp-with-vonage/README.md
+++ b/node/whatsapp-with-vonage/README.md
@@ -4,28 +4,61 @@ Automatically respond to messages sent to a Vonage WhatsApp number.
## 🧰 Usage
-### `GET /`
+### `GET`
Serves a HTML page.
-### `POST /`
+### `POST`
Receives a message, validates its signature, and sends a response back to the sender.
**Parameters**
-| Name | Description | Location | Type | Sample Value |
-| ------------ | ---------------------------- | -------- | ------------------- | ------------ |
-| Content-Type | Content type of the request | Header | `application/json ` | N/A |
-| from | Sender's identifier. | Body | String | `12345` |
-| text | Text content of the message. | Body | String | `Hello!` |
+| Name | Description | Location | Type | Sample Value |
+| ------------- | ----------------------------------- | -------- | ------------------- | ---------------- |
+| Content-Type | Content type of the request | Header | `application/json ` | N/A |
+| Authorization | Authorization token for the request | Header | String | `Bearer ` |
+| from | Sender's identifier. | Body | String | `12345` |
+| text | Text content of the message. | Body | String | `Hello!` |
+
+
+To generate the Bearer token, you can use the following snippet:
+
+````js
+import jwt from 'jsonwebtoken';
+
+const token = jwt.sign(
+ { payload_hash: sha256(JSON.stringify(payload)) },
+ process.env.VONAGE_API_SIGNATURE_SECRET
+);
+```
**Response**
Sample `200` Response:
-```text
-OK
+```json
+{
+ "ok": true
+}
+````
+
+Sample `400` Response:
+
+```json
+{
+ "ok": false,
+ "error": "Missing required parameter: from"
+}
+```
+
+Sample `401` Response:
+
+```json
+{
+ "ok": false,
+ "error": "Payload hash mismatch."
+}
```
## ⚙️ Configuration
@@ -66,3 +99,13 @@ Secret to verify the JWT token sent by Vonage.
| ------------ | ----------- |
| Required | Yes |
| Sample Value | `ijkl91011` |
+
+### VONAGE_WHATSAPP_NUMBER
+
+Vonage WhatsApp number to send messages from.
+
+| Question | Answer |
+| ------------ | ----------- |
+| Required | Yes |
+| Sample Value | `123456789` |
+
diff --git a/node/whatsapp-with-vonage/src/environment.js b/node/whatsapp-with-vonage/src/environment.js
deleted file mode 100644
index 18fab8a5..00000000
--- a/node/whatsapp-with-vonage/src/environment.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * @param {string} key
- * @return {string}
- */
-function getRequiredEnv(key) {
- const value = process.env[key];
- if (value === undefined) {
- throw new Error(`Environment variable ${key} is not set`);
- }
- return value;
-}
-
-class EnvironmentService {
- VONAGE_API_KEY = getRequiredEnv('VONAGE_API_KEY');
- VONAGE_API_SECRET = getRequiredEnv('VONAGE_API_SECRET');
- VONAGE_API_SIGNATURE_SECRET = getRequiredEnv('VONAGE_API_SIGNATURE_SECRET');
-}
-
-export default EnvironmentService;
diff --git a/node/whatsapp-with-vonage/src/main.js b/node/whatsapp-with-vonage/src/main.js
index 4684971b..703bad59 100644
--- a/node/whatsapp-with-vonage/src/main.js
+++ b/node/whatsapp-with-vonage/src/main.js
@@ -4,15 +4,20 @@ import { fetch } from 'undici';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
-import EnvironmentService from './environment.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const staticFolder = path.join(__dirname, '../static');
export default async ({ req, res }) => {
- const { VONAGE_API_KEY, VONAGE_API_SECRET, VONAGE_API_SIGNATURE_SECRET } =
- new EnvironmentService();
+ if (
+ !process.env.VONAGE_API_KEY ||
+ !process.env.VONAGE_API_SECRET ||
+ !process.env.VONAGE_API_SIGNATURE_SECRET ||
+ !process.env.VONAGE_WHATSAPP_NUMBER
+ ) {
+ throw new Error('Missing environment variables.');
+ }
if (req.method === 'GET') {
const html = fs
@@ -22,24 +27,30 @@ export default async ({ req, res }) => {
}
const token = (req.headers.authorization ?? '').split(' ')[1];
- var decoded = jwt.verify(token, VONAGE_API_SIGNATURE_SECRET, {
+ var decoded = jwt.verify(token, process.env.VONAGE_API_SIGNATURE_SECRET, {
algorithms: ['HS256'],
});
if (sha256(req.bodyString) != decoded['payload_hash']) {
- throw new Error('Invalid signature.');
+ return res.json({ ok: false, error: 'Payload hash mismatch.' }, 401);
}
if (!req.body.from) {
- throw new Error('Payload invalid.');
+ return res.json(
+ { ok: false, error: 'Missing required parameter: from.' },
+ 400
+ );
}
const text = req.body.text ?? 'I only accept text messages.';
+ const basicAuthToken = btoa(
+ `${process.env.VONAGE_API_KEY}:${process.env.VONAGE_API_SECRET}`
+ );
await fetch(`https://messages-sandbox.nexmo.com/v1/messages`, {
method: 'POST',
body: JSON.stringify({
- from: '14157386102',
+ from: process.env.VONAGE_WHATSAPP_NUMBER,
to: req.body.from,
message_type: 'text',
text: `Hi there! You sent me: ${text}`,
@@ -47,9 +58,9 @@ export default async ({ req, res }) => {
}),
headers: {
'Content-Type': 'application/json',
- Authorization: `Basic ${btoa(`${VONAGE_API_KEY}:${VONAGE_API_SECRET}`)}`,
+ Authorization: `Basic ${basicAuthToken}`,
},
});
- return res.send('OK');
+ return res.json({ ok: true });
};
From 6575464c2b518d57bb20e326b04e2297f88a485b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Matej=20Ba=C4=8Do?=
Date: Sun, 30 Jul 2023 19:03:02 +0000
Subject: [PATCH 08/11] PR review changes
---
node/whatsapp-with-vonage/README.md | 61 +++++++++------------
node/whatsapp-with-vonage/static/index.html | 4 +-
2 files changed, 29 insertions(+), 36 deletions(-)
diff --git a/node/whatsapp-with-vonage/README.md b/node/whatsapp-with-vonage/README.md
index 442cb50d..1833d59a 100644
--- a/node/whatsapp-with-vonage/README.md
+++ b/node/whatsapp-with-vonage/README.md
@@ -1,12 +1,12 @@
-# ⚡ Vonage Message Receiver and Responder
+# 💬 Node.js WhatsApp Bot with Vonage Function
-Automatically respond to messages sent to a Vonage WhatsApp number.
+Simple bot to answer WhatsApp messages.
## 🧰 Usage
### `GET`
-Serves a HTML page.
+HTML form for interacting with the model.
### `POST`
@@ -14,24 +14,14 @@ Receives a message, validates its signature, and sends a response back to the se
**Parameters**
-| Name | Description | Location | Type | Sample Value |
-| ------------- | ----------------------------------- | -------- | ------------------- | ---------------- |
-| Content-Type | Content type of the request | Header | `application/json ` | N/A |
-| Authorization | Authorization token for the request | Header | String | `Bearer ` |
-| from | Sender's identifier. | Body | String | `12345` |
-| text | Text content of the message. | Body | String | `Hello!` |
+| Name | Description | Location | Type | Sample Value |
+| ------------- | ----------------------------------- | -------- | ------------------- | -------------------- |
+| Content-Type | Content type of the request | Header | `application/json ` | N/A |
+| Authorization | Webhook signature for verification | Header | String | `Bearer ` |
+| from | Sender's identifier. | Body | String | `12345` |
+| text | Text content of the message. | Body | String | `Hello!` |
-
-To generate the Bearer token, you can use the following snippet:
-
-````js
-import jwt from 'jsonwebtoken';
-
-const token = jwt.sign(
- { payload_hash: sha256(JSON.stringify(payload)) },
- process.env.VONAGE_API_SIGNATURE_SECRET
-);
-```
+> All parameters are coming from Vonage webhook. Exact documentation can be found in [Vonage API Docs](https://developer.vonage.com/en/api/messages-olympus#inbound-message).
**Response**
@@ -80,32 +70,35 @@ API Key to use the Vonage API.
| Question | Answer |
| ------------ | ---------- |
| Required | Yes |
-| Sample Value | `abcd1234` |
+| Sample Value | `62...97` |
+| Documentation | [Vonage: Q&A](https://api.support.vonage.com/hc/en-us/articles/204014493-How-do-I-find-my-Voice-API-key-and-API-secret-) |
### VONAGE_API_SECRET
Secret to use the Vonage API.
-| Question | Answer |
-| ------------ | ---------- |
-| Required | Yes |
-| Sample Value | `efgh5678` |
+| Question | Answer |
+| ------------ | ----------- |
+| Required | Yes |
+| Sample Value | `Zjc...5PH` |
+| Documentation | [Vonage: Q&A](https://api.support.vonage.com/hc/en-us/articles/204014493-How-do-I-find-my-Voice-API-key-and-API-secret-) |
### VONAGE_API_SIGNATURE_SECRET
Secret to verify the JWT token sent by Vonage.
-| Question | Answer |
-| ------------ | ----------- |
-| Required | Yes |
-| Sample Value | `ijkl91011` |
+| Question | Answer |
+| ------------ | --------------- |
+| Required | Yes |
+| Sample Value | `NXOi3...IBHDa` |
+| Documentation | [Vonage: Signing Messages](https://developer.vonage.com/en/getting-started/concepts/signing-messages) |
### VONAGE_WHATSAPP_NUMBER
Vonage WhatsApp number to send messages from.
-| Question | Answer |
-| ------------ | ----------- |
-| Required | Yes |
-| Sample Value | `123456789` |
-
+| Question | Answer |
+| ------------ | -------------- |
+| Required | Yes |
+| Sample Value | `+14000000102` |
+| Documentation | [Vonage: Q&A](https://api.support.vonage.com/hc/en-us/articles/4431993282580-Where-do-I-find-my-WhatsApp-Number-Certificate-) |
diff --git a/node/whatsapp-with-vonage/static/index.html b/node/whatsapp-with-vonage/static/index.html
index 00b6adaa..3852090f 100644
--- a/node/whatsapp-with-vonage/static/index.html
+++ b/node/whatsapp-with-vonage/static/index.html
@@ -4,7 +4,7 @@
- Whats App Bot with Vonage
+ WhatsApp Bot with Vonage
@@ -16,7 +16,7 @@