From 1728fffbbbea31434ad0834f9241ef1875ca0b8b Mon Sep 17 00:00:00 2001 From: Antoine SEIN <142824551+asein-sinch@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:10:07 +0200 Subject: [PATCH 1/7] DEVEXP-561: Add templates/client for GA APIs --- templates/client/.env | 19 +++++++++++ templates/client/README.md | 31 +++++++++++++++++ templates/client/package.json | 15 ++++++++ templates/client/src/app.js | 16 +++++++++ .../client/src/numbers/numbers-quickstart.js | 18 ++++++++++ templates/client/src/numbers/snippet.js | 7 ++++ templates/client/src/sinchclient-helper.js | 34 +++++++++++++++++++ templates/client/src/sms/sms-quickstart.js | 18 ++++++++++ templates/client/src/sms/snippet.js | 7 ++++ templates/client/src/verification/snippet.js | 7 ++++ .../verification/verification-quickstart.js | 18 ++++++++++ templates/client/src/voice/snippet.js | 7 ++++ .../client/src/voice/voice-quickstart.js | 18 ++++++++++ templates/client/tsconfig.json | 3 ++ 14 files changed, 218 insertions(+) create mode 100644 templates/client/.env create mode 100644 templates/client/README.md create mode 100644 templates/client/package.json create mode 100644 templates/client/src/app.js create mode 100644 templates/client/src/numbers/numbers-quickstart.js create mode 100644 templates/client/src/numbers/snippet.js create mode 100644 templates/client/src/sinchclient-helper.js create mode 100644 templates/client/src/sms/sms-quickstart.js create mode 100644 templates/client/src/sms/snippet.js create mode 100644 templates/client/src/verification/snippet.js create mode 100644 templates/client/src/verification/verification-quickstart.js create mode 100644 templates/client/src/voice/snippet.js create mode 100644 templates/client/src/voice/voice-quickstart.js create mode 100644 templates/client/tsconfig.json diff --git a/templates/client/.env b/templates/client/.env new file mode 100644 index 0000000..868fd24 --- /dev/null +++ b/templates/client/.env @@ -0,0 +1,19 @@ +# Unified related credentials, used by: +# - Numbers +# - SMS: US/EU are the only supported regions with unified credentials +SINCH_PROJECT_ID = +SINCH_KEY_ID = +SINCH_KEY_SECRET = + +# Application related credentials, used by: +# - Verification +# - Voice +#SINCH_APPLICATION_KEY = +#SINCH_APPLICATION_SECRET = + +# Service related configuration +SMS_REGION = us +# SMS Service Plan ID related credentials +# if set, these credentials will be used and enable to use regions different of US/EU +#SINCH_SERVICE_PLAN_ID = +#SINCH_API_TOKEN = diff --git a/templates/client/README.md b/templates/client/README.md new file mode 100644 index 0000000..b34aaa6 --- /dev/null +++ b/templates/client/README.md @@ -0,0 +1,31 @@ +# Client application based on Sinch Node.js SDK + +This directory contains a client application based on the [Sinch Node.js SDK](https://github.com/sinch/sinch-sdk-node). + +## Prerequisites + + - [Node.js LTS](https://nodejs.org/en) + - [Sinch account](https://dashboard.sinch.com/) + +## Configuration + +Edit [.env](.env) file to set the credentials that will be used to configure the Sinch Client. + - To use [Numbers](https://developers.sinch.com/docs/numbers/) or [SMS](https://developers.sinch.com/docs/sms/), you need to fill the following variables with the values from your Sinch account: + - `SINCH_PROJECT_ID`=Your Sinch Project ID + - `SINCH_KEY_ID`=Your Sinch Access Key ID + - `SINCH_KEY_SECRET`=Your Sinch Key Secret associated to your Sinch Access Key + - To use [Verification](https://developers.sinch.com/docs/verification/) or [Voice](https://developers.sinch.com/docs/voice/), you need to fill the following variables with the values from a Verification / Voice Application + - `SINCH_APPLICATION_KEY`=Your Application Key + - `SINCH_APPLICATION_SECRET`=Your Application Secret + - To use [SMS](https://developers.sinch.com/docs/sms/) with regions others than US and EU, you need to use the "Service Plan ID" and fill the following variables with the values from your [Services](https://dashboard.sinch.com/sms/api/services) section in the dashboard: + - `SINCH_SERVICE_PLAN_ID`=Your Service Plan ID + - `SINCH_API_TOKEN`=Your API Token associated to your Service Plan ID + - `SMS_REGION`=the SMS region (`au` / `br` / `ca`) + +## Usage + +1. Edit the `.env` file with your own credentials (see the paragraph above for details). +2. Replace the content of the `snippet.js` file of the API you want to use with your own code (you can find some snippets examples for each API endpoint in the following repository: https://github.com/sinch/sinch-sdk-node-snippets) +3. Run the code with one of the following commands: + - `npm start` + - `node src/app.js` diff --git a/templates/client/package.json b/templates/client/package.json new file mode 100644 index 0000000..6b21a5a --- /dev/null +++ b/templates/client/package.json @@ -0,0 +1,15 @@ +{ + "name": "@sinch/sdk-client-quickstart-template-client", + "version": "0.0.0", + "author": "Sinch", + "description": "", + "type": "module", + "scripts": { + "start": "node src/app.js", + "compile": "tsc" + }, + "dependencies": { + "@sinch/sdk-core": "^1.1.0", + "dotenv": "^16.4.5" + } +} diff --git a/templates/client/src/app.js b/templates/client/src/app.js new file mode 100644 index 0000000..e28fdf1 --- /dev/null +++ b/templates/client/src/app.js @@ -0,0 +1,16 @@ +import { initSinchClient } from './sinchclient-helper.js'; +import { NumbersQuickstart } from './numbers/numbers-quickstart.js'; +import { SmsQuickstart } from './sms/sms-quickstart.js'; +import { VerificationQuickstart } from './verification/verification-quickstart.js'; +import { VoiceQuickstart } from './voice/voice-quickstart.js'; + +const sinchClient = initSinchClient(); + +try { + new NumbersQuickstart(sinchClient.numbers); + new SmsQuickstart(sinchClient.sms); + new VerificationQuickstart(sinchClient.verification); + new VoiceQuickstart(sinchClient.voice); +} catch (e) { + console.error(e); +} diff --git a/templates/client/src/numbers/numbers-quickstart.js b/templates/client/src/numbers/numbers-quickstart.js new file mode 100644 index 0000000..c525ea3 --- /dev/null +++ b/templates/client/src/numbers/numbers-quickstart.js @@ -0,0 +1,18 @@ +// eslint-disable-next-line no-unused-vars +import { NumbersService } from '@sinch/sdk-core'; +import { execute } from './snippet.js'; + +/** + * Class representing a quickstart for using the Numbers API. + */ +export class NumbersQuickstart { + /** + * @param { NumbersService } numbersService - the NumbersService instance from the Sinch SDK containing the API methods + */ + constructor(numbersService) { + this.numbersService = numbersService; + + // replace by your code and business logic + execute(this.numbersService); + } +} diff --git a/templates/client/src/numbers/snippet.js b/templates/client/src/numbers/snippet.js new file mode 100644 index 0000000..70e32df --- /dev/null +++ b/templates/client/src/numbers/snippet.js @@ -0,0 +1,7 @@ +// eslint-disable-next-line no-unused-vars +import { NumbersService } from '@sinch/sdk-core'; + +/** @param {NumbersService} numbersService */ +export const execute = async (numbersService) => { + console.log('Snippet execution: Numbers service'); +}; diff --git a/templates/client/src/sinchclient-helper.js b/templates/client/src/sinchclient-helper.js new file mode 100644 index 0000000..8acd61f --- /dev/null +++ b/templates/client/src/sinchclient-helper.js @@ -0,0 +1,34 @@ +import { SinchClient, SmsRegion } from '@sinch/sdk-core'; +import * as dotenv from 'dotenv'; +dotenv.config(); + +export const initSinchClient = () => { + // Unified credentials + const projectId = getProperty('SINCH_PROJECT_ID'); + const keyId = getProperty('SINCH_KEY_ID'); + const keySecret = getProperty('SINCH_KEY_SECRET'); + + // SMS legacy configuration + const servicePlanId = getProperty('SINCH_SERVICE_PLAN_ID'); + const apiToken = getProperty('SINCH_API_TOKEN'); + const smsRegion = getProperty('SMS_REGION') || SmsRegion.UNITED_STATES; + + // Application credentials (verification + voice) + const applicationKey = getProperty('SINCH_APPLICATION_KEY'); + const applicationSecret = getProperty('SINCH_APPLICATION_SECRET'); + + return new SinchClient({ + projectId, + keyId, + keySecret, + servicePlanId, + apiToken, + smsRegion, + applicationKey, + applicationSecret, + }); +}; + +const getProperty = (propertyName) => { + return process.env[propertyName] || ''; +}; diff --git a/templates/client/src/sms/sms-quickstart.js b/templates/client/src/sms/sms-quickstart.js new file mode 100644 index 0000000..1649685 --- /dev/null +++ b/templates/client/src/sms/sms-quickstart.js @@ -0,0 +1,18 @@ +// eslint-disable-next-line no-unused-vars +import { SmsService } from '@sinch/sdk-core'; +import { execute } from './snippet.js'; + +/** + * Class representing a quickstart for using the SMS API. + */ +export class SmsQuickstart { + /** + * @param { SmsService } smsService - the SmsService instance from the Sinch SDK containing the API methods + */ + constructor(smsService) { + this.smsService = smsService; + + // replace by your code and business logic + execute(this.smsService); + } +} diff --git a/templates/client/src/sms/snippet.js b/templates/client/src/sms/snippet.js new file mode 100644 index 0000000..5613aa7 --- /dev/null +++ b/templates/client/src/sms/snippet.js @@ -0,0 +1,7 @@ +// eslint-disable-next-line no-unused-vars +import { SmsService } from '@sinch/sdk-core'; + +/** @param {SmsService} smsService */ +export const execute = async (smsService) => { + console.log('Snippet execution: SMS service'); +}; diff --git a/templates/client/src/verification/snippet.js b/templates/client/src/verification/snippet.js new file mode 100644 index 0000000..cffc1e9 --- /dev/null +++ b/templates/client/src/verification/snippet.js @@ -0,0 +1,7 @@ +// eslint-disable-next-line no-unused-vars +import { VerificationService } from '@sinch/sdk-core'; + +/** @param {VerificationService} verificationService */ +export const execute = async (verificationService) => { + console.log('Snippet execution: Verification service'); +}; diff --git a/templates/client/src/verification/verification-quickstart.js b/templates/client/src/verification/verification-quickstart.js new file mode 100644 index 0000000..d0d607d --- /dev/null +++ b/templates/client/src/verification/verification-quickstart.js @@ -0,0 +1,18 @@ +// eslint-disable-next-line no-unused-vars +import { VerificationService } from '@sinch/sdk-core'; +import { execute } from './snippet.js'; + +/** + * Class representing a quickstart for using the Verification API. + */ +export class VerificationQuickstart { + /** + * @param { VerificationService } verificationService - the VerificationService instance from the Sinch SDK containing the API methods + */ + constructor(verificationService) { + this.verificationService = verificationService; + + // replace by your code and business logic + execute(this.verificationService); + } +} diff --git a/templates/client/src/voice/snippet.js b/templates/client/src/voice/snippet.js new file mode 100644 index 0000000..8b35ca6 --- /dev/null +++ b/templates/client/src/voice/snippet.js @@ -0,0 +1,7 @@ +// eslint-disable-next-line no-unused-vars +import { VoiceService } from '@sinch/sdk-core'; + +/** @param {VoiceService} voiceService */ +export const execute = async (voiceService) => { + console.log('Snippet execution: Voice service'); +}; diff --git a/templates/client/src/voice/voice-quickstart.js b/templates/client/src/voice/voice-quickstart.js new file mode 100644 index 0000000..b12ea51 --- /dev/null +++ b/templates/client/src/voice/voice-quickstart.js @@ -0,0 +1,18 @@ +// eslint-disable-next-line no-unused-vars +import { VoiceService } from '@sinch/sdk-core'; +import { execute } from './snippet.js'; + +/** + * Class representing a quickstart for using the Voice API. + */ +export class VoiceQuickstart { + /** + * @param { VoiceService } voiceService - the VoiceService instance from the Sinch SDK containing the API methods + */ + constructor(voiceService) { + this.voiceService = voiceService; + + // replace by your code and business logic + execute(this.voiceService); + } +} diff --git a/templates/client/tsconfig.json b/templates/client/tsconfig.json new file mode 100644 index 0000000..4082f16 --- /dev/null +++ b/templates/client/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} From a8cf81b7012cf0c447fc7a40b9c2b5ec2cbcd8a0 Mon Sep 17 00:00:00 2001 From: Antoine SEIN <142824551+asein-sinch@users.noreply.github.com> Date: Tue, 17 Sep 2024 18:07:40 +0200 Subject: [PATCH 2/7] DEVEXP-562: Add templates/server for GA APIs --- templates/server/.env | 12 +++ templates/server/README.md | 78 +++++++++++++++++++ templates/server/package.json | 16 ++++ templates/server/src/numbers/controller.js | 30 +++++++ .../server/src/numbers/serverBusinessLogic.js | 7 ++ templates/server/src/server.js | 38 +++++++++ templates/server/src/sms/controller.js | 52 +++++++++++++ .../server/src/sms/serverBusinessLogic.js | 59 ++++++++++++++ .../server/src/verification/controller.js | 49 ++++++++++++ .../src/verification/serverBusinessLogic.js | 24 ++++++ templates/server/src/voice/controller.js | 64 +++++++++++++++ .../server/src/voice/serverBusinessLogic.js | 51 ++++++++++++ templates/server/tsconfig.json | 3 + 13 files changed, 483 insertions(+) create mode 100644 templates/server/.env create mode 100644 templates/server/README.md create mode 100644 templates/server/package.json create mode 100644 templates/server/src/numbers/controller.js create mode 100644 templates/server/src/numbers/serverBusinessLogic.js create mode 100644 templates/server/src/server.js create mode 100644 templates/server/src/sms/controller.js create mode 100644 templates/server/src/sms/serverBusinessLogic.js create mode 100644 templates/server/src/verification/controller.js create mode 100644 templates/server/src/verification/serverBusinessLogic.js create mode 100644 templates/server/src/voice/controller.js create mode 100644 templates/server/src/voice/serverBusinessLogic.js create mode 100644 templates/server/tsconfig.json diff --git a/templates/server/.env b/templates/server/.env new file mode 100644 index 0000000..6868dc6 --- /dev/null +++ b/templates/server/.env @@ -0,0 +1,12 @@ +# Express related configuration +port = 3001 + +# The secret value used for webhook calls validation +# See https://developers.sinch.com/docs/numbers/api-reference/numbers/tag/Numbers-Callbacks/ +NUMBERS_WEBHOOK_SECRET = + +# Application related credentials, used by: +# - Verification +# - Voice +SINCH_APPLICATION_KEY = +SINCH_APPLICATION_SECRET = diff --git a/templates/server/README.md b/templates/server/README.md new file mode 100644 index 0000000..5df2e97 --- /dev/null +++ b/templates/server/README.md @@ -0,0 +1,78 @@ +# Backend application built using Sinch Node.js SDK to handle incoming webhooks + +This directory contains a server application based on the [Sinch Node.js SDK](https://github.com/sinch/sinch-sdk-node). + +## Requirements + +- [Node.js LTS](https://nodejs.org/en) +- [Express](https://expressjs.com/) +- [Sinch account](https://dashboard.sinch.com/) +- [ngrok](https://ngrok.com/docs) + +## Configuration +Edit the [.env](.env) file to set the parameters that will be used to configure the Express server and the controllers. + +### Server port +*Default: 3001* + - `port`: the port to be used to listen to incoming requests. Default is `3001` if not set. + +### Controller Configuration + - Numbers controller: `NUMBERS_WEBHOOK_SECRET`. This value can be found thanks to the [Numbers API](https://developers.sinch.com/docs/numbers/api-reference/numbers/tag/Numbers-Callbacks/), using the `/callbackConfiguration` endpoint. + - SMS controller: no support for header validation at the moment + - Verification and Voice controllers: 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 + +## Usage + +### Starting the server + +1. Edit the `.env` file with your own parameters (see the paragraph above for details). +2. Run the code with one of the following commands: + - `npm start` + - `node src/server.js` + +### Endpoints + +When the server is up and running, the declared controllers will listen and respond to the following endpoints: + +| Service | Endpoint | +|--------------|--------------------| +| Numbers | /NumbersEvent | +| SMS | /SmsEvent | +| Verification | /VerificationEvent | +| Voice | /VoiceEvent | + +## Use ngrok to forward the event request 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 URLs, e.g.: + - Numbers: https://cafe-64-220-29-200.ngrok-free.app/NumbersEvent + - SMS: https://cafe-64-220-29-200.ngrok-free.app/SmsEvent + - Verification: https://cafe-64-220-29-200.ngrok-free.app/VerificationEvent + - Voice: https://cafe-64-220-29-200.ngrok-free.app/VoiceEvent + +This value must be used to configure the callback URLs: + - Numbers: the callback URL must be set at Number level with the parameter `callbackUrl` when renting a number or when updating a rented number with the API. + - SMS: the callback URL must be configured at Service level on the [Sinch dashboard](https://dashboard.sinch.com/sms/api/services) in the "Callback URLs" section. + - Verification: the callback URL must be configured at Application level on the [Sinch dashboard](https://dashboard.sinch.com/verification/apps) in the "Settings/Callback URL" section. + - Voice: the callback URL must be configured at Application level on the [Sinch dashboard](https://dashboard.sinch.com/voice/apps) in the "Settings/Handle call events with" section. + + diff --git a/templates/server/package.json b/templates/server/package.json new file mode 100644 index 0000000..1a6d333 --- /dev/null +++ b/templates/server/package.json @@ -0,0 +1,16 @@ +{ + "name": "@sinch/sdk-client-quickstart-template-server", + "version": "0.0.0", + "author": "Sinch", + "description": "", + "type": "module", + "scripts": { + "start": "node src/server.js", + "compile": "tsc" + }, + "dependencies": { + "@sinch/sdk-core": "^1.1.0", + "dotenv": "^16.4.5", + "express": "^4.20.0" + } +} diff --git a/templates/server/src/numbers/controller.js b/templates/server/src/numbers/controller.js new file mode 100644 index 0000000..37cb339 --- /dev/null +++ b/templates/server/src/numbers/controller.js @@ -0,0 +1,30 @@ +import { NumbersCallbackWebhooks } from '@sinch/sdk-core'; +import { handleNumbersEvent } from './serverBusinessLogic.js'; + +export const numbersController = (app) => { + + const callbackSecret = process.env.NUMBERS_WEBHOOK_SECRET || ''; + const numbersCallbackWebhooks = new NumbersCallbackWebhooks(callbackSecret); + + app.post('/NumbersEvent', async (req, res) => { + + // Ensure the authentication is valid before processing the request + const validAuth = numbersCallbackWebhooks.validateAuthenticationHeader( + req.headers, + req.rawBody, + ); + + // Authentication header failed + if (!validAuth) { + return res.status(401).json(); + } + + // Parse the request payload + const event = numbersCallbackWebhooks.parseEvent(req.body); + + // Let the business layer process the request + await handleNumbersEvent(event); + + return res.status(200).json(); + }); +}; diff --git a/templates/server/src/numbers/serverBusinessLogic.js b/templates/server/src/numbers/serverBusinessLogic.js new file mode 100644 index 0000000..5be68f0 --- /dev/null +++ b/templates/server/src/numbers/serverBusinessLogic.js @@ -0,0 +1,7 @@ +/** + * Handles a Numbers Event. + * @param {import("@sinch/sdk-core").NumbersCallback} numbersEvent - The incoming Numbers event notification. + */ +export const handleNumbersEvent = async (numbersEvent) => { + console.log(`Handling Numbers event:\n${JSON.stringify(numbersEvent, null, 2)}`); +}; diff --git a/templates/server/src/server.js b/templates/server/src/server.js new file mode 100644 index 0000000..0e7f803 --- /dev/null +++ b/templates/server/src/server.js @@ -0,0 +1,38 @@ +import express from 'express'; +import { numbersController } from './numbers/controller.js'; +import { smsController } from './sms/controller.js'; +import { verificationController } from './verification/controller.js'; +import { voiceController } from './voice/controller.js'; +import * as dotenv from 'dotenv'; +dotenv.config(); + +const app = express(); +const port = process.env.port || 3001; + +// Middleware to capture the raw body and store it in req.rawBody +app.use((req, res, next) => { + let data = ''; + + req.on('data', (chunk) => { + data += chunk; + }); + + req.on('end', () => { + req.rawBody = data; + req.body = JSON.parse(data); + next(); + }); + + req.on('error', (err) => { + next(err); + }); +}); + +numbersController(app); +smsController(app); +verificationController(app); +voiceController(app); + +app.listen(port, () => { + console.log(`Server is listening on port ${port}`); +}); diff --git a/templates/server/src/sms/controller.js b/templates/server/src/sms/controller.js new file mode 100644 index 0000000..a806a5f --- /dev/null +++ b/templates/server/src/sms/controller.js @@ -0,0 +1,52 @@ +import { SmsCallbackWebhooks } from '@sinch/sdk-core'; +import { + handleIncomingBinaryMessageEvent, + // TODO: uncomment with the version 1.2.0 + // handleIncomingMmsEvent, + handleIncomingSmsEvent, + handleMmsDeliveryReportEvent, + handleMmsRecipientDeliveryReportEvent, + handleSmsDeliveryReportEvent, + handleSmsRecipientDeliveryReportEvent, +} from './serverBusinessLogic.js'; + +export const smsController = (app) => { + + const smsCallbackWebhooks = new SmsCallbackWebhooks(); + + app.post('/SmsEvent', async (req, res) => { + + // Parse the request payload + const event = smsCallbackWebhooks.parseEvent(req.body); + + // Let the business layer process the request + switch (event.type) { + case 'mo_text': + handleIncomingSmsEvent(event); + break; + case 'mo_binary': + handleIncomingBinaryMessageEvent(event); + break; + // TODO: uncomment with the version 1.2.0 + // case 'mo_media': + // handleIncomingMmsEvent(event); + // break; + case 'delivery_report_sms': + handleSmsDeliveryReportEvent(event); + break; + case 'delivery_report_mms': + handleMmsDeliveryReportEvent(event); + break; + case 'recipient_delivery_report_sms': + handleSmsRecipientDeliveryReportEvent(event); + break; + case 'recipient_delivery_report_mms': + handleMmsRecipientDeliveryReportEvent(event); + break; + default: + res.status(500).json({ error: 'Unsupported event type' }); + } + + return res.status(200).json(); + }); +}; diff --git a/templates/server/src/sms/serverBusinessLogic.js b/templates/server/src/sms/serverBusinessLogic.js new file mode 100644 index 0000000..c1787bd --- /dev/null +++ b/templates/server/src/sms/serverBusinessLogic.js @@ -0,0 +1,59 @@ +// eslint-disable-next-line no-unused-vars +import { Sms } from '@sinch/sdk-core'; + +/** + * Handles an incoming SMS Event. + * @param {Sms.MOText} incomingSmsEvent - The incoming SMS event. + */ +export const handleIncomingSmsEvent = (incomingSmsEvent) => { + console.log(`Handling incoming SMS event:\n${JSON.stringify(incomingSmsEvent, null, 2)}`); +}; + +/** + * Handles an incoming binary message Event. + * @param {Sms.MOBinary} incomingBinaryMessageEvent - The incoming binary message event. + */ +export const handleIncomingBinaryMessageEvent = (incomingBinaryMessageEvent) => { + console.log(`Handling incoming binary message event:\n${JSON.stringify(incomingBinaryMessageEvent, null, 2)}`); +}; + +// TODO: uncomment with the version 1.2.0 +// /** +// * Handles an incoming MMS Event. +// * @param {Sms.MOMedia} incomingMmsEvent - The incoming MMS event. +// */ +// export const handleIncomingMmsEvent = (incomingMmsEvent) => { +// console.log(`Handling incoming MMS event:\n${JSON.stringify(incomingMmsEvent, null, 2)}`); +// }; + +/** + * Handles an SMS Delivery Report Event. + * @param {Sms.DeliveryReport} smsDeliveryReportEvent - The SMS delivery report event. + */ +export const handleSmsDeliveryReportEvent = (smsDeliveryReportEvent) => { + console.log(`Handling SMS delivery report event:\n${JSON.stringify(smsDeliveryReportEvent, null, 2)}`); +}; + +/** + * Handles an MMS Delivery Report Event. + * @param {Sms.DeliveryReport} mmsDeliveryReportEvent - The MMS delivery report event. + */ +export const handleMmsDeliveryReportEvent = (mmsDeliveryReportEvent) => { + console.log(`Handling MMS delivery report event:\n${JSON.stringify(mmsDeliveryReportEvent, null, 2)}`); +}; + +/** + * Handles an SMS Recipient Delivery Report Event. + * @param {Sms.RecipientDeliveryReport} smsRecipientDeliveryReportEvent - The SMS recipient delivery report event. + */ +export const handleSmsRecipientDeliveryReportEvent = (smsRecipientDeliveryReportEvent) => { + console.log(`Handling SMS recipient delivery report event:\n${JSON.stringify(smsRecipientDeliveryReportEvent, null, 2)}`); +}; + +/** + * Handles an MMS Recipient Delivery Report Event. + * @param {Sms.RecipientDeliveryReport} mmsRecipientDeliveryReportEvent - The MMS recipient delivery report event. + */ +export const handleMmsRecipientDeliveryReportEvent = (mmsRecipientDeliveryReportEvent) => { + console.log(`Handling MMS recipient delivery report event:\n${JSON.stringify(mmsRecipientDeliveryReportEvent, null, 2)}`); +}; diff --git a/templates/server/src/verification/controller.js b/templates/server/src/verification/controller.js new file mode 100644 index 0000000..d77c273 --- /dev/null +++ b/templates/server/src/verification/controller.js @@ -0,0 +1,49 @@ +import { VerificationCallbackWebhooks } from '@sinch/sdk-core'; +import { handleVerificationRequestEvent, handleVerificationResultEvent } from './serverBusinessLogic.js'; + +export const verificationController = (app) => { + + const applicationKey = process.env.SINCH_APPLICATION_KEY; + const applicationSecret = process.env.SINCH_APPLICATION_SECRET; + + const verificationCallbackWebhooks = new VerificationCallbackWebhooks({ + applicationKey, + applicationSecret, + }); + + app.post('/VerificationEvent', async (req, res) => { + + // Ensure the authentication is valid before processing the request + const validAuth = verificationCallbackWebhooks.validateAuthenticationHeader( + req.headers, + req.rawBody, + '/VerificationEvent', + 'POST', + ); + + // Authentication header failed + if (!validAuth) { + return res.status(401).json(); + } + + // Parse the request payload + const event = verificationCallbackWebhooks.parseEvent(req.body); + + // Let the business layer process the request + let response; + switch (event.event) { + case 'VerificationRequestEvent': + response = handleVerificationRequestEvent(event); + break; + case 'VerificationResultEvent': + response = handleVerificationResultEvent(event); + break; + default: + res.status(500).json({ error: 'Unsupported event type' }); + } + + console.log(`JSON response:\n${JSON.stringify(response, null, 2)}`); + + return res.status(200).json(response); + }); +}; diff --git a/templates/server/src/verification/serverBusinessLogic.js b/templates/server/src/verification/serverBusinessLogic.js new file mode 100644 index 0000000..80eac58 --- /dev/null +++ b/templates/server/src/verification/serverBusinessLogic.js @@ -0,0 +1,24 @@ +// eslint-disable-next-line no-unused-vars +import { Verification } from '@sinch/sdk-core'; + +/** + * Handles a Verification Request Event. + * @param {Verification.VerificationRequestEvent} verificationRequestEvent - The incoming Verification Request event. + * @return {Verification.VerificationRequestEventResponse} the verification request event response + */ +export const handleVerificationRequestEvent = (verificationRequestEvent) => { + console.log(`Handling Verification Request event:\n${JSON.stringify(verificationRequestEvent, null, 2)}`); + + // add your logic here according to SMS, FlashCall, PhoneCall, ... verification + return {}; +}; + +/** + * Handles a Verification Result Event. + * @param {Verification.VerificationResultEvent} verificationResultEvent - The incoming Verification Result notification. + * @return {null} + */ +export const handleVerificationResultEvent = (verificationResultEvent) => { + console.log(`Handling Verification Result event:\n${JSON.stringify(verificationResultEvent, null, 2)}`); + return null; +}; diff --git a/templates/server/src/voice/controller.js b/templates/server/src/voice/controller.js new file mode 100644 index 0000000..002705e --- /dev/null +++ b/templates/server/src/voice/controller.js @@ -0,0 +1,64 @@ +import { VoiceCallbackWebhooks } from '@sinch/sdk-core'; +import { + handleAnsweredCallEvent, + handleDisconnectedCallEvent, + handleIncomingCallEvent, + handleNotifyEvent, + handlePromptInputEvent, +} from './serverBusinessLogic.js'; + +export const voiceController = (app) => { + + const applicationKey = process.env.SINCH_APPLICATION_KEY; + const applicationSecret = process.env.SINCH_APPLICATION_SECRET; + + const voiceCallbackWebhooks = new VoiceCallbackWebhooks({ + applicationKey, + applicationSecret, + }); + + app.post('/VoiceEvent', async (req, res) => { + + // Ensure the authentication is valid before processing the request + const validAuth = voiceCallbackWebhooks.validateAuthenticationHeader( + req.headers, + req.rawBody, + '/VoiceEvent', + 'POST', + ); + + // Authentication header failed + if (!validAuth) { + return res.status(401).json(); + } + + // Parse the request payload + const event = voiceCallbackWebhooks.parseEvent(req.body); + + // Let the business layer process the request + let response; + switch (event.event) { + case 'ice': + response = handleIncomingCallEvent(event); + break; + case 'ace': + response = handleAnsweredCallEvent(event); + break; + case 'pie': + response = handlePromptInputEvent(event); + break; + case 'dice': + response = handleDisconnectedCallEvent(event); + break; + case 'notify': + response = handleNotifyEvent(event); + break; + default: + res.status(500).json({ error: 'Unsupported event type' }); + } + + console.log(`JSON response:\n${JSON.stringify(response, null, 2)}`); + + return res.status(200).json(response); + }); +}; diff --git a/templates/server/src/voice/serverBusinessLogic.js b/templates/server/src/voice/serverBusinessLogic.js new file mode 100644 index 0000000..9108b1e --- /dev/null +++ b/templates/server/src/voice/serverBusinessLogic.js @@ -0,0 +1,51 @@ +import { Voice } from '@sinch/sdk-core'; + +/** + * Handles an Incoming Call Event (ICE). + * @param {Voice.IceRequest} iceRequest - The incoming ICE request object. + * @return {Promise} The formatted ICE response to handle the incoming call. + */ +export const handleIncomingCallEvent = async (iceRequest) => { + console.log(`Handling 'ICE' event:\n${JSON.stringify(iceRequest)}`); + return Voice.customCalloutHelper.formatIceResponse(); +}; + +/** + * Handles an Answered Call Event (ACE). + * @param {Voice.AceRequest} aceRequest - The incoming ACE request object. + * @return {Promise} The formatted ACE response to handle the answered call. + */ +export const handleAnsweredCallEvent = async (aceRequest) => { + console.log(`Handling 'ACE' event:\n${JSON.stringify(aceRequest)}`); + return Voice.customCalloutHelper.formatAceResponse(); +}; + +/** + * Handles a prompt input event (PIE). + * @param {Voice.PieRequest} pieRequest - The incoming PIE request object. + * @return {Promise} An empty string as a response to the prompt input event. + */ +export const handlePromptInputEvent = async (pieRequest) => { + console.log(`Handling 'PIE' event:\n${JSON.stringify(pieRequest)}`); + return ''; +}; + +/** + * Handles a disconnected call event (DICE). + * @param { Voice.DiceRequest } diceRequest - The incoming DICE request object. + * @return {Promise} An empty string as a response to the disconnected call event. + */ +export const handleDisconnectedCallEvent = async (diceRequest) => { + console.log(`Handling 'DICE' event:\n${JSON.stringify(diceRequest)}`); + return ''; +}; + +/** + * Handles a notification event. + * @param { Voice.NotifyRequest } notifyRequest - The incoming notify event object + * @return {Promise} An empty string as a response to the notify event. + */ +export const handleNotifyEvent = async (notifyRequest) => { + console.log(`Handling 'notify' event:\n${JSON.stringify(notifyRequest)}`); + return ''; +}; diff --git a/templates/server/tsconfig.json b/templates/server/tsconfig.json new file mode 100644 index 0000000..4082f16 --- /dev/null +++ b/templates/server/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} From a7399b7282a431d13c5dd09f10e78a8411abc5d4 Mon Sep 17 00:00:00 2001 From: Antoine SEIN <142824551+asein-sinch@users.noreply.github.com> Date: Wed, 18 Sep 2024 09:30:29 +0200 Subject: [PATCH 3/7] DEVEXP-563: Add SMS 'Getting Started' code --- .../respond-to-incoming-message/server/.env | 14 + .../server/README.md | 5 + .../server/package-lock.json | 874 ++++++++++++++++++ .../server/package.json | 16 + .../server/src/server.js | 26 + .../server/src/sms/controller.js | 26 + .../server/src/sms/serverBusinessLogic.js | 24 + .../server/src/sms/validateSignature.js | 5 + .../server/tsconfig.json | 3 + 9 files changed, 993 insertions(+) create mode 100644 getting-started/sms/respond-to-incoming-message/server/.env create mode 100644 getting-started/sms/respond-to-incoming-message/server/README.md create mode 100644 getting-started/sms/respond-to-incoming-message/server/package-lock.json create mode 100644 getting-started/sms/respond-to-incoming-message/server/package.json create mode 100644 getting-started/sms/respond-to-incoming-message/server/src/server.js create mode 100644 getting-started/sms/respond-to-incoming-message/server/src/sms/controller.js create mode 100644 getting-started/sms/respond-to-incoming-message/server/src/sms/serverBusinessLogic.js create mode 100644 getting-started/sms/respond-to-incoming-message/server/src/sms/validateSignature.js create mode 100644 getting-started/sms/respond-to-incoming-message/server/tsconfig.json diff --git a/getting-started/sms/respond-to-incoming-message/server/.env b/getting-started/sms/respond-to-incoming-message/server/.env new file mode 100644 index 0000000..65beb3f --- /dev/null +++ b/getting-started/sms/respond-to-incoming-message/server/.env @@ -0,0 +1,14 @@ +# Express related configuration +port = 3001 + +# Unified related credentials, used by: +# - SMS: US/EU are the only supported regions with unified credentials +SINCH_PROJECT_ID = +SINCH_KEY_ID = +SINCH_KEY_SECRET = +SMS_REGION = us + +# SMS Service Plan ID related credentials +# if set, these credentials will be used and enable to use regions different of US/EU +#SINCH_SERVICE_PLAN_ID = +#SINCH_API_TOKEN = diff --git a/getting-started/sms/respond-to-incoming-message/server/README.md b/getting-started/sms/respond-to-incoming-message/server/README.md new file mode 100644 index 0000000..9201633 --- /dev/null +++ b/getting-started/sms/respond-to-incoming-message/server/README.md @@ -0,0 +1,5 @@ +# Sinch Getting started + +Code is related to [Receive an SMS Message with Node.js](https://developers.sinch.com/docs/sms/getting-started/node-sdk/handle-incoming/). + +See [Server template README](https://github.com/sinch/sinch-sdk-node-quickstart/blob/main/templates/server/README.md) for details about configuration and usage. diff --git a/getting-started/sms/respond-to-incoming-message/server/package-lock.json b/getting-started/sms/respond-to-incoming-message/server/package-lock.json new file mode 100644 index 0000000..6dddfe1 --- /dev/null +++ b/getting-started/sms/respond-to-incoming-message/server/package-lock.json @@ -0,0 +1,874 @@ +{ + "name": "@sinch/getting-started_respond-to-incoming-message", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@sinch/getting-started_respond-to-incoming-message", + "version": "0.0.0", + "dependencies": { + "@sinch/sdk-core": "^1.1.0", + "dotenv": "^16.4.5", + "express": "^4.20.0" + } + }, + "node_modules/@sinch/conversation": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/conversation/-/conversation-1.1.0.tgz", + "integrity": "sha512-8KxUnzO+a8fPmhSmpRX9zGfgF3Hvgg4ZQNF9CNVDVRckcE7p+mWENdWts7GXWSEKMONZIOXwYnosAeENMsZVow==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/@sinch/elastic-sip-trunking": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/elastic-sip-trunking/-/elastic-sip-trunking-1.1.0.tgz", + "integrity": "sha512-1E635dxPFnCfS1e01WGSY2xcLGUsAngcghcsodFFMTXxmAL+2cxhFdmrodmBdNmCVpfPLkMSIdQC/t+1p+awNA==", + "dependencies": { + "@sinch/sdk-client": "^1.0.0" + } + }, + "node_modules/@sinch/fax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/fax/-/fax-1.1.0.tgz", + "integrity": "sha512-89NS1zMg07QAjj6Fr93moVjkfZSkVwYwegZfZ2LEo7SEuFXp9CahL5UoHXzipTNbtCxcouGUgiJkN0fxJBN/VQ==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/@sinch/numbers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/numbers/-/numbers-1.1.0.tgz", + "integrity": "sha512-uEtTDTPabY7f2CU1X/rpuBqWbDutqcHA34vaaJ1HbGWhdK406YfUKxBha60/kTAO5df+eDjVxg2SYiVNRpJNLw==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/@sinch/sdk-client": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/sdk-client/-/sdk-client-1.1.0.tgz", + "integrity": "sha512-YiF8LpZ8RdWFdjyA1mlySyuNfVQPA1EV8gSecrvBUpwO8LyFhvbZF1OsMfaqVJiUr4mA/UArbbO6qSzwA2XgCQ==", + "dependencies": { + "form-data": "^4.0.0", + "node-fetch": "2.7.0" + } + }, + "node_modules/@sinch/sdk-core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/sdk-core/-/sdk-core-1.1.0.tgz", + "integrity": "sha512-kqZ0n0ycClYPPSk5ugy9FFmq/j/qVXmhC8rgUBu2JWTjsOTsRVe9dSnUuQYaxj6V8sEjySUALtLiJBU3neeq8A==", + "dependencies": { + "@sinch/conversation": "^1.1.0", + "@sinch/elastic-sip-trunking": "^1.1.0", + "@sinch/fax": "^1.1.0", + "@sinch/numbers": "^1.1.0", + "@sinch/sms": "^1.1.0", + "@sinch/verification": "^1.1.0", + "@sinch/voice": "^1.1.0" + } + }, + "node_modules/@sinch/sms": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/sms/-/sms-1.1.0.tgz", + "integrity": "sha512-6QXpLujzcl5jTvYFmB+UebVWYGXEHaRBsmE1vG7GAwIAOrCxdpusvSWitHeigCUz9CxrzdjC+3JmlS7q7rbONA==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/@sinch/verification": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/verification/-/verification-1.1.0.tgz", + "integrity": "sha512-UcXf4dsrG0+qATCJIiCEyHwnyLknEtYSK1UKV40EGPkYNn4kVwxLRDglKOIq9txBQZ5MgpbFDTqedbf4AcREZQ==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/@sinch/voice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/voice/-/voice-1.1.0.tgz", + "integrity": "sha512-znOVSxBHRoI7J8zZGcFUrqDHR8hyOifh+IOR1FUnel4c1hS5s9HsHfmcnd4HmUca1P/ln0c19+KxMhNFBVLTAw==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/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/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/getting-started/sms/respond-to-incoming-message/server/package.json b/getting-started/sms/respond-to-incoming-message/server/package.json new file mode 100644 index 0000000..ba98574 --- /dev/null +++ b/getting-started/sms/respond-to-incoming-message/server/package.json @@ -0,0 +1,16 @@ +{ + "name": "@sinch/getting-started_respond-to-incoming-message", + "version": "0.0.0", + "author": "Sinch", + "description": "", + "type": "module", + "scripts": { + "start": "node src/server.js", + "compile": "tsc" + }, + "dependencies": { + "@sinch/sdk-core": "^1.1.0", + "dotenv": "^16.4.5", + "express": "^4.20.0" + } +} diff --git a/getting-started/sms/respond-to-incoming-message/server/src/server.js b/getting-started/sms/respond-to-incoming-message/server/src/server.js new file mode 100644 index 0000000..7ec8110 --- /dev/null +++ b/getting-started/sms/respond-to-incoming-message/server/src/server.js @@ -0,0 +1,26 @@ +import express from 'express'; +import { SmsRegion } from '@sinch/sdk-core'; +import { smsController } from './sms/controller.js'; +import * as dotenv from 'dotenv'; +dotenv.config(); + +const app = express(); +const port = process.env.port || 3001; + +/** @type {import('@sinch/sdk-core').SinchClientParameters} */ +const sinchClientParameters = { + projectId: process.env.SINCH_PROJECT_ID, + keyId: process.env.SINCH_KEY_ID, + keySecret: process.env.SINCH_KEY_SECRET, + servicePlanId: process.env.SINCH_SERVICE_PLAN_ID, + apiToken: process.env.SINCH_API_TOKEN, + smsRegion: process.env.SMS_REGION || SmsRegion.UNITED_STATES, +}; + +app.use(express.json()); + +smsController(app, sinchClientParameters); + +app.listen(port, () => { + console.log(`Server is listening on port ${port}`); +}); diff --git a/getting-started/sms/respond-to-incoming-message/server/src/sms/controller.js b/getting-started/sms/respond-to-incoming-message/server/src/sms/controller.js new file mode 100644 index 0000000..08286f6 --- /dev/null +++ b/getting-started/sms/respond-to-incoming-message/server/src/sms/controller.js @@ -0,0 +1,26 @@ +import { validateSignature } from './validateSignature.js'; +import { SinchClient, SmsCallbackWebhooks } from '@sinch/sdk-core'; +import { handleSmsEvent } from './serverBusinessLogic.js'; +import * as dotenv from 'dotenv'; +dotenv.config(); + +export const smsController = (app, sinchClientParameters) => { + + const sinchClient = new SinchClient(sinchClientParameters); + const smsCallbackWebhooks = new SmsCallbackWebhooks(); + + app.post('/SmsEvent', validateSignature, async (req, res) => { + try { + const event = smsCallbackWebhooks.parseEvent(req.body); + if (event.type === 'mo_text') { + await handleSmsEvent(event, sinchClient.sms); + } else { + res.status(400).json({ error: `Unexpected event type: ${event.type}` }); + } + } catch (error) { + console.error('Error parsing event:', error); + return res.status(400).json({ error: 'Invalid event format' }); + } + res.status(200).json(); + }); +} diff --git a/getting-started/sms/respond-to-incoming-message/server/src/sms/serverBusinessLogic.js b/getting-started/sms/respond-to-incoming-message/server/src/sms/serverBusinessLogic.js new file mode 100644 index 0000000..127a008 --- /dev/null +++ b/getting-started/sms/respond-to-incoming-message/server/src/sms/serverBusinessLogic.js @@ -0,0 +1,24 @@ +// eslint-disable-next-line no-unused-vars +import { SmsService, Sms } from '@sinch/sdk-core'; + +/** + * Handles the incoming SMS event by echoing what has been received to the sender. + * @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 handleSmsEvent = async (incomingTextMessage, smsService) => { + console.log(`Handling event: ${JSON.stringify(incomingTextMessage, null, 2)}`); + + /** @type {Sms.SendSMSRequestData} */ + const smsRequest = { + sendSMSRequestBody: { + to: [incomingTextMessage.from], + body: `You sent ${incomingTextMessage.body}`, + from: incomingTextMessage.to, + }, + }; + + console.log(`Replying with: ${JSON.stringify(smsRequest, null, 2)}`); + + await smsService.batches.send(smsRequest); +}; diff --git a/getting-started/sms/respond-to-incoming-message/server/src/sms/validateSignature.js b/getting-started/sms/respond-to-incoming-message/server/src/sms/validateSignature.js new file mode 100644 index 0000000..bad194f --- /dev/null +++ b/getting-started/sms/respond-to-incoming-message/server/src/sms/validateSignature.js @@ -0,0 +1,5 @@ +export const validateSignature = (req, res, next) => { + console.log('Validating SMS event signature.'); + // Do nothing as this feature is not supported yet. + next(); +}; diff --git a/getting-started/sms/respond-to-incoming-message/server/tsconfig.json b/getting-started/sms/respond-to-incoming-message/server/tsconfig.json new file mode 100644 index 0000000..2217753 --- /dev/null +++ b/getting-started/sms/respond-to-incoming-message/server/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../../../tsconfig.json" +} From 3510886908b1e04983cee01afdcba32dd17bc992 Mon Sep 17 00:00:00 2001 From: Antoine SEIN <142824551+asein-sinch@users.noreply.github.com> Date: Wed, 18 Sep 2024 10:52:20 +0200 Subject: [PATCH 4/7] DEVEXP-564: Add Verification 'Getting Started' code --- .../client/.env | 7 + .../client/README.md | 5 + .../client/package-lock.json | 744 ++++++++++++++++++ .../client/package.json | 16 + .../client/src/app.js | 11 + .../src/verification/verificationSample.js | 118 +++ .../client/tsconfig.json | 3 + 7 files changed, 904 insertions(+) create mode 100644 getting-started/verification/user-verification-using-sms-pin/client/.env create mode 100644 getting-started/verification/user-verification-using-sms-pin/client/README.md create mode 100644 getting-started/verification/user-verification-using-sms-pin/client/package-lock.json create mode 100644 getting-started/verification/user-verification-using-sms-pin/client/package.json create mode 100644 getting-started/verification/user-verification-using-sms-pin/client/src/app.js create mode 100644 getting-started/verification/user-verification-using-sms-pin/client/src/verification/verificationSample.js create mode 100644 getting-started/verification/user-verification-using-sms-pin/client/tsconfig.json diff --git a/getting-started/verification/user-verification-using-sms-pin/client/.env b/getting-started/verification/user-verification-using-sms-pin/client/.env new file mode 100644 index 0000000..b28bb64 --- /dev/null +++ b/getting-started/verification/user-verification-using-sms-pin/client/.env @@ -0,0 +1,7 @@ +# Express related configuration +port = 3001 + +# Application related credentials, used by: +# - Verification +SINCH_APPLICATION_KEY = +SINCH_APPLICATION_SECRET = diff --git a/getting-started/verification/user-verification-using-sms-pin/client/README.md b/getting-started/verification/user-verification-using-sms-pin/client/README.md new file mode 100644 index 0000000..ecadd0b --- /dev/null +++ b/getting-started/verification/user-verification-using-sms-pin/client/README.md @@ -0,0 +1,5 @@ +# Sinch Getting started + +Code is related to [Verify a user using SMS PIN with the Node.js SDK](https://developers.sinch.com/docs/verification/getting-started/node-sdk/sms-verification/). + +See [Client template README](https://github.com/sinch/sinch-sdk-node-quickstart/blob/main/templates/client/README.md) for details about configuration and usage. diff --git a/getting-started/verification/user-verification-using-sms-pin/client/package-lock.json b/getting-started/verification/user-verification-using-sms-pin/client/package-lock.json new file mode 100644 index 0000000..65af615 --- /dev/null +++ b/getting-started/verification/user-verification-using-sms-pin/client/package-lock.json @@ -0,0 +1,744 @@ +{ + "name": "@sinch/getting-started_user-verificartion-using-sms-pin", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@sinch/getting-started_user-verificartion-using-sms-pin", + "version": "0.0.0", + "dependencies": { + "@sinch/sdk-core": "^1.1.0", + "dotenv": "^16.4.5", + "inquirer": "^9.2.14" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.6.tgz", + "integrity": "sha512-yfZzps3Cso2UbM7WlxKwZQh2Hs6plrbjs1QnzQDZhK2DgyCo6D8AaHps9olkNcUFlcYERMqU3uJSp1gmy3s/qQ==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@sinch/conversation": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/conversation/-/conversation-1.1.0.tgz", + "integrity": "sha512-8KxUnzO+a8fPmhSmpRX9zGfgF3Hvgg4ZQNF9CNVDVRckcE7p+mWENdWts7GXWSEKMONZIOXwYnosAeENMsZVow==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/@sinch/elastic-sip-trunking": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/elastic-sip-trunking/-/elastic-sip-trunking-1.1.0.tgz", + "integrity": "sha512-1E635dxPFnCfS1e01WGSY2xcLGUsAngcghcsodFFMTXxmAL+2cxhFdmrodmBdNmCVpfPLkMSIdQC/t+1p+awNA==", + "dependencies": { + "@sinch/sdk-client": "^1.0.0" + } + }, + "node_modules/@sinch/fax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/fax/-/fax-1.1.0.tgz", + "integrity": "sha512-89NS1zMg07QAjj6Fr93moVjkfZSkVwYwegZfZ2LEo7SEuFXp9CahL5UoHXzipTNbtCxcouGUgiJkN0fxJBN/VQ==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/@sinch/numbers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/numbers/-/numbers-1.1.0.tgz", + "integrity": "sha512-uEtTDTPabY7f2CU1X/rpuBqWbDutqcHA34vaaJ1HbGWhdK406YfUKxBha60/kTAO5df+eDjVxg2SYiVNRpJNLw==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/@sinch/sdk-client": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/sdk-client/-/sdk-client-1.1.0.tgz", + "integrity": "sha512-YiF8LpZ8RdWFdjyA1mlySyuNfVQPA1EV8gSecrvBUpwO8LyFhvbZF1OsMfaqVJiUr4mA/UArbbO6qSzwA2XgCQ==", + "dependencies": { + "form-data": "^4.0.0", + "node-fetch": "2.7.0" + } + }, + "node_modules/@sinch/sdk-core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/sdk-core/-/sdk-core-1.1.0.tgz", + "integrity": "sha512-kqZ0n0ycClYPPSk5ugy9FFmq/j/qVXmhC8rgUBu2JWTjsOTsRVe9dSnUuQYaxj6V8sEjySUALtLiJBU3neeq8A==", + "dependencies": { + "@sinch/conversation": "^1.1.0", + "@sinch/elastic-sip-trunking": "^1.1.0", + "@sinch/fax": "^1.1.0", + "@sinch/numbers": "^1.1.0", + "@sinch/sms": "^1.1.0", + "@sinch/verification": "^1.1.0", + "@sinch/voice": "^1.1.0" + } + }, + "node_modules/@sinch/sms": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/sms/-/sms-1.1.0.tgz", + "integrity": "sha512-6QXpLujzcl5jTvYFmB+UebVWYGXEHaRBsmE1vG7GAwIAOrCxdpusvSWitHeigCUz9CxrzdjC+3JmlS7q7rbONA==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/@sinch/verification": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/verification/-/verification-1.1.0.tgz", + "integrity": "sha512-UcXf4dsrG0+qATCJIiCEyHwnyLknEtYSK1UKV40EGPkYNn4kVwxLRDglKOIq9txBQZ5MgpbFDTqedbf4AcREZQ==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/@sinch/voice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/voice/-/voice-1.1.0.tgz", + "integrity": "sha512-znOVSxBHRoI7J8zZGcFUrqDHR8hyOifh+IOR1FUnel4c1hS5s9HsHfmcnd4HmUca1P/ln0c19+KxMhNFBVLTAw==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/inquirer": { + "version": "9.3.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.6.tgz", + "integrity": "sha512-riK/iQB2ctwkpWYgjjWIRv3MBLt2gzb2Sj0JNQNbyTXgyXsLWcDPJ5WS5ZDTCx7BRFnJsARtYh+58fjP5M2Y0Q==", + "dependencies": { + "@inquirer/figures": "^1.0.3", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/getting-started/verification/user-verification-using-sms-pin/client/package.json b/getting-started/verification/user-verification-using-sms-pin/client/package.json new file mode 100644 index 0000000..b7ab1ba --- /dev/null +++ b/getting-started/verification/user-verification-using-sms-pin/client/package.json @@ -0,0 +1,16 @@ +{ + "name": "@sinch/getting-started_user-verification-using-sms-pin", + "version": "0.0.0", + "author": "Sinch", + "description": "", + "type": "module", + "scripts": { + "start": "node src/app.js", + "compile": "tsc" + }, + "dependencies": { + "@sinch/sdk-core": "^1.1.0", + "dotenv": "^16.4.5", + "inquirer": "^9.2.14" + } +} diff --git a/getting-started/verification/user-verification-using-sms-pin/client/src/app.js b/getting-started/verification/user-verification-using-sms-pin/client/src/app.js new file mode 100644 index 0000000..5b2f2b2 --- /dev/null +++ b/getting-started/verification/user-verification-using-sms-pin/client/src/app.js @@ -0,0 +1,11 @@ +import { SinchClient } from '@sinch/sdk-core'; +import { VerificationSample } from './verification/verificationSample.js'; +import * as dotenv from 'dotenv'; +dotenv.config(); + +const sinchClient = new SinchClient({ + applicationKey: process.env.SINCH_APPLICATION_KEY, + applicationSecret: process.env.SINCH_APPLICATION_SECRET, +}); + +new VerificationSample(sinchClient.verification).start(); diff --git a/getting-started/verification/user-verification-using-sms-pin/client/src/verification/verificationSample.js b/getting-started/verification/user-verification-using-sms-pin/client/src/verification/verificationSample.js new file mode 100644 index 0000000..af7e4f6 --- /dev/null +++ b/getting-started/verification/user-verification-using-sms-pin/client/src/verification/verificationSample.js @@ -0,0 +1,118 @@ +// eslint-disable-next-line no-unused-vars +import { Verification, VerificationService, VerificationsApi } from '@sinch/sdk-core'; +import inquirer from 'inquirer'; + +/** + * Class to handle a phone number verification using SMS. + */ +export class VerificationSample { + /** + * @param { VerificationService } verificationService - the VerificationService instance from the Sinch SDK containing the API methods. + */ + constructor(verificationService) { + this.verificationService = verificationService; + } + + /** + * Starts the verification process by prompting the user for a phone number, + * sending a verification request, asking for the verification code, and reporting it. + * @return {Promise} + */ + async start() { + // Step 1: Ask the phone number to verify + const e164Number = await this.promptPhoneNumber(); + + try { + // Step 2: Start the phone number verification + const verificationId = await this.startSmsVerification(this.verificationService.verifications, e164Number); + + // Step 3: Ask the user for the received verification code + const code = await this.promptSmsCode(); + + // Step 4: Report the verification code and complete the process + await this.reportSmsVerification(this.verificationService.verifications, code, verificationId); + + console.log('Verification successfully completed.'); + } catch (error) { + console.error('An error occurred during the verification process:', error); + } + } + + /** + * Prompts the user to enter their phone number. + * @return {Promise} The phone number entered by the user. + */ + async promptPhoneNumber() { + const userInput = await inquirer.prompt([ + { + type: 'input', + name: 'phoneNumber', + message: 'Enter the phone number you want to verify (E.164 format):', + validate: (input) => input ? true : 'Phone number cannot be empty.', + }, + ]); + + return userInput.phoneNumber; + } + + /** + * Sends a request to start SMS verification for a phone number. + * @param {VerificationsApi} verificationStarter - The VerificationsApi instance. + * @param {string} phoneNumber - The phone number to verify. + * @return {Promise} The verification ID if the request is successful. + */ + async startSmsVerification(verificationStarter, phoneNumber) { + console.log(`Sending a verification request to ${phoneNumber}`); + + const requestData = Verification.startVerificationHelper.buildSmsRequest(phoneNumber); + + try { + const response = await verificationStarter.startSms(requestData); + + if (!response.id) { + throw new Error('Verification ID is undefined.'); + } + + console.log(`Verification started successfully. Verification ID: ${response.id}`); + return response.id; + } catch (error) { + console.error('Failed to start SMS verification:', error); + throw error; + } + } + + /** + * Prompts the user to enter the verification code they received. + * @return {Promise} The verification code entered by the user. + */ + async promptSmsCode() { + const answers = await inquirer.prompt([ + { + type: 'input', + name: 'code', + message: 'Enter the verification code you received:', + validate: (input) => input ? true : 'Verification code cannot be empty.', + }, + ]); + return answers.code; + } + + /** + * Sends a request to report the verification code for a specific verification ID. + * @param { VerificationsApi } verificationReporter - The VerificationsApi instance. + * @param {string} code - The verification code to report. + * @param {string} id - The verification ID corresponding to the process. + * @return {Promise} + */ + async reportSmsVerification(verificationReporter, code, id) { + const requestData = Verification.reportVerificationByIdHelper.buildSmsRequest(id, code); + + try { + const response = await verificationReporter.reportSmsById(requestData); + console.log(`Verification reported successfully. Response status: ${response.status}`); + } catch (error) { + console.error('Failed to report SMS verification:', error); + throw error; + } + } +} diff --git a/getting-started/verification/user-verification-using-sms-pin/client/tsconfig.json b/getting-started/verification/user-verification-using-sms-pin/client/tsconfig.json new file mode 100644 index 0000000..2217753 --- /dev/null +++ b/getting-started/verification/user-verification-using-sms-pin/client/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../../../tsconfig.json" +} From 0bfd1ead14d016f170e3cb9a2624ea5995173aee Mon Sep 17 00:00:00 2001 From: Antoine SEIN <142824551+asein-sinch@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:15:23 +0200 Subject: [PATCH 5/7] DEVEXP-565: Add Voice 'Getting Started' code --- .../respond-to-incoming-call/server/.env | 7 + .../respond-to-incoming-call/server/README.md | 5 + .../server/package-lock.json | 874 ++++++++++++++++++ .../server/package.json | 16 + .../server/src/middleware/rawbody.js | 22 + .../server/src/server.js | 22 + .../server/src/voice/controller.js | 41 + .../server/src/voice/serverBusinessLogic.js | 62 ++ .../server/src/voice/validateSignature.js | 13 + .../server/tsconfig.json | 3 + 10 files changed, 1065 insertions(+) create mode 100644 getting-started/voice/respond-to-incoming-call/server/.env create mode 100644 getting-started/voice/respond-to-incoming-call/server/README.md create mode 100644 getting-started/voice/respond-to-incoming-call/server/package-lock.json create mode 100644 getting-started/voice/respond-to-incoming-call/server/package.json create mode 100644 getting-started/voice/respond-to-incoming-call/server/src/middleware/rawbody.js create mode 100644 getting-started/voice/respond-to-incoming-call/server/src/server.js create mode 100644 getting-started/voice/respond-to-incoming-call/server/src/voice/controller.js create mode 100644 getting-started/voice/respond-to-incoming-call/server/src/voice/serverBusinessLogic.js create mode 100644 getting-started/voice/respond-to-incoming-call/server/src/voice/validateSignature.js create mode 100644 getting-started/voice/respond-to-incoming-call/server/tsconfig.json diff --git a/getting-started/voice/respond-to-incoming-call/server/.env b/getting-started/voice/respond-to-incoming-call/server/.env new file mode 100644 index 0000000..13fd8ca --- /dev/null +++ b/getting-started/voice/respond-to-incoming-call/server/.env @@ -0,0 +1,7 @@ +# Express related configuration +port = 3001 + +# Application related credentials, used by: +# - Voice +SINCH_APPLICATION_KEY = +SINCH_APPLICATION_SECRET = diff --git a/getting-started/voice/respond-to-incoming-call/server/README.md b/getting-started/voice/respond-to-incoming-call/server/README.md new file mode 100644 index 0000000..74dff29 --- /dev/null +++ b/getting-started/voice/respond-to-incoming-call/server/README.md @@ -0,0 +1,5 @@ +# Sinch Getting started + +Code is related to [Handle an incoming call with the Node.js SDK](https://developers.sinch.com/docs/voice/getting-started/node-sdk/incoming-call/). + +See [Server template README](https://github.com/sinch/sinch-sdk-node-quickstart/blob/main/templates/server/README.md) for details about configuration and usage. diff --git a/getting-started/voice/respond-to-incoming-call/server/package-lock.json b/getting-started/voice/respond-to-incoming-call/server/package-lock.json new file mode 100644 index 0000000..ff82597 --- /dev/null +++ b/getting-started/voice/respond-to-incoming-call/server/package-lock.json @@ -0,0 +1,874 @@ +{ + "name": "@sinch/getting-started_respond-to-incoming-call", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@sinch/getting-started_respond-to-incoming-call", + "version": "0.0.0", + "dependencies": { + "@sinch/sdk-core": "^1.1.0", + "dotenv": "^16.4.5", + "express": "^4.20.0" + } + }, + "node_modules/@sinch/conversation": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/conversation/-/conversation-1.1.0.tgz", + "integrity": "sha512-8KxUnzO+a8fPmhSmpRX9zGfgF3Hvgg4ZQNF9CNVDVRckcE7p+mWENdWts7GXWSEKMONZIOXwYnosAeENMsZVow==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/@sinch/elastic-sip-trunking": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/elastic-sip-trunking/-/elastic-sip-trunking-1.1.0.tgz", + "integrity": "sha512-1E635dxPFnCfS1e01WGSY2xcLGUsAngcghcsodFFMTXxmAL+2cxhFdmrodmBdNmCVpfPLkMSIdQC/t+1p+awNA==", + "dependencies": { + "@sinch/sdk-client": "^1.0.0" + } + }, + "node_modules/@sinch/fax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/fax/-/fax-1.1.0.tgz", + "integrity": "sha512-89NS1zMg07QAjj6Fr93moVjkfZSkVwYwegZfZ2LEo7SEuFXp9CahL5UoHXzipTNbtCxcouGUgiJkN0fxJBN/VQ==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/@sinch/numbers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/numbers/-/numbers-1.1.0.tgz", + "integrity": "sha512-uEtTDTPabY7f2CU1X/rpuBqWbDutqcHA34vaaJ1HbGWhdK406YfUKxBha60/kTAO5df+eDjVxg2SYiVNRpJNLw==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/@sinch/sdk-client": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/sdk-client/-/sdk-client-1.1.0.tgz", + "integrity": "sha512-YiF8LpZ8RdWFdjyA1mlySyuNfVQPA1EV8gSecrvBUpwO8LyFhvbZF1OsMfaqVJiUr4mA/UArbbO6qSzwA2XgCQ==", + "dependencies": { + "form-data": "^4.0.0", + "node-fetch": "2.7.0" + } + }, + "node_modules/@sinch/sdk-core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/sdk-core/-/sdk-core-1.1.0.tgz", + "integrity": "sha512-kqZ0n0ycClYPPSk5ugy9FFmq/j/qVXmhC8rgUBu2JWTjsOTsRVe9dSnUuQYaxj6V8sEjySUALtLiJBU3neeq8A==", + "dependencies": { + "@sinch/conversation": "^1.1.0", + "@sinch/elastic-sip-trunking": "^1.1.0", + "@sinch/fax": "^1.1.0", + "@sinch/numbers": "^1.1.0", + "@sinch/sms": "^1.1.0", + "@sinch/verification": "^1.1.0", + "@sinch/voice": "^1.1.0" + } + }, + "node_modules/@sinch/sms": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/sms/-/sms-1.1.0.tgz", + "integrity": "sha512-6QXpLujzcl5jTvYFmB+UebVWYGXEHaRBsmE1vG7GAwIAOrCxdpusvSWitHeigCUz9CxrzdjC+3JmlS7q7rbONA==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/@sinch/verification": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/verification/-/verification-1.1.0.tgz", + "integrity": "sha512-UcXf4dsrG0+qATCJIiCEyHwnyLknEtYSK1UKV40EGPkYNn4kVwxLRDglKOIq9txBQZ5MgpbFDTqedbf4AcREZQ==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/@sinch/voice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sinch/voice/-/voice-1.1.0.tgz", + "integrity": "sha512-znOVSxBHRoI7J8zZGcFUrqDHR8hyOifh+IOR1FUnel4c1hS5s9HsHfmcnd4HmUca1P/ln0c19+KxMhNFBVLTAw==", + "dependencies": { + "@sinch/sdk-client": "^1.1.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/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/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/getting-started/voice/respond-to-incoming-call/server/package.json b/getting-started/voice/respond-to-incoming-call/server/package.json new file mode 100644 index 0000000..8f683bd --- /dev/null +++ b/getting-started/voice/respond-to-incoming-call/server/package.json @@ -0,0 +1,16 @@ +{ + "name": "@sinch/getting-started_respond-to-incoming-call", + "version": "0.0.0", + "author": "Sinch", + "description": "", + "type": "module", + "scripts": { + "start": "node src/server.js", + "compile": "tsc" + }, + "dependencies": { + "@sinch/sdk-core": "^1.1.0", + "dotenv": "^16.4.5", + "express": "^4.20.0" + } +} diff --git a/getting-started/voice/respond-to-incoming-call/server/src/middleware/rawbody.js b/getting-started/voice/respond-to-incoming-call/server/src/middleware/rawbody.js new file mode 100644 index 0000000..ae027af --- /dev/null +++ b/getting-started/voice/respond-to-incoming-call/server/src/middleware/rawbody.js @@ -0,0 +1,22 @@ +export const captureRawBody = (req, res, next) => { + let data = ''; + + req.on('data', (chunk) => { + data += chunk; + }); + + req.on('end', () => { + req.rawBody = data; + try { + req.body = JSON.parse(data); // Parse the body as JSON + } catch (err) { + // If the JSON parsing fails, skip it (for cases where the body isn't JSON) + req.body = {}; + } + next(); + }); + + req.on('error', (err) => { + next(err); + }); +}; diff --git a/getting-started/voice/respond-to-incoming-call/server/src/server.js b/getting-started/voice/respond-to-incoming-call/server/src/server.js new file mode 100644 index 0000000..ede4990 --- /dev/null +++ b/getting-started/voice/respond-to-incoming-call/server/src/server.js @@ -0,0 +1,22 @@ +import express from 'express'; +import { captureRawBody } from './middleware/rawbody.js'; +import { voiceController } from './voice/controller.js'; +import * as dotenv from 'dotenv'; +dotenv.config(); + +const app = express(); +const port = process.env.port || 3001; + +/** @type {import('@sinch/sdk-core').SinchClientParameters} */ +const sinchClientParameters = { + applicationKey: process.env.SINCH_APPLICATION_KEY, + applicationSecret: process.env.SINCH_APPLICATION_SECRET, +}; + +app.use(captureRawBody); + +voiceController(app, sinchClientParameters); + +app.listen(port, () => { + console.log(`Server is listening on port ${port}`); +}); diff --git a/getting-started/voice/respond-to-incoming-call/server/src/voice/controller.js b/getting-started/voice/respond-to-incoming-call/server/src/voice/controller.js new file mode 100644 index 0000000..f9fd3e9 --- /dev/null +++ b/getting-started/voice/respond-to-incoming-call/server/src/voice/controller.js @@ -0,0 +1,41 @@ +import { validateSignature } from './validateSignature.js'; +import { VoiceCallbackWebhooks } from '@sinch/sdk-core'; +import { + handleAnsweredCallEvent, + handleDisconnectedCallEvent, + handleIncomingCallEvent, + handleNotifyEvent, + handlePromptInputEvent, +} from './serverBusinessLogic.js'; + +export const voiceController = (app, sinchClientParameters) => { + + const voiceCallbackWebhooks = new VoiceCallbackWebhooks(sinchClientParameters); + + app.post('/VoiceEvent', validateSignature(voiceCallbackWebhooks), async (req, res) => { + + const event = voiceCallbackWebhooks.parseEvent(req.body); + let response = ''; + switch (event.event) { + case 'ice': + response = handleIncomingCallEvent(event); + break; + case 'ace': + response = handleAnsweredCallEvent(event); + break; + case 'pie': + response = handlePromptInputEvent(event); + break; + case 'dice': + response = handleDisconnectedCallEvent(event); + break; + case 'notify': + response = handleNotifyEvent(event); + break; + default: + res.status(400).json({ error: 'Unsupported event type' }); + } + + return res.status(200).json(response); + }); +}; diff --git a/getting-started/voice/respond-to-incoming-call/server/src/voice/serverBusinessLogic.js b/getting-started/voice/respond-to-incoming-call/server/src/voice/serverBusinessLogic.js new file mode 100644 index 0000000..fa42dde --- /dev/null +++ b/getting-started/voice/respond-to-incoming-call/server/src/voice/serverBusinessLogic.js @@ -0,0 +1,62 @@ +// eslint-disable-next-line no-unused-vars +import { Voice } from '@sinch/sdk-core'; + +/** + * Handles an Incoming Call Event (ICE). + * @param {Voice.IceRequest} iceRequest - The incoming ICE request object. + * @return {string} The formatted ICE response to handle the incoming call. + */ +export const handleIncomingCallEvent = (iceRequest) => { + console.log(`Handling 'ICE' event:\n${JSON.stringify(iceRequest, null, 2)}`); + + const instruction = 'Thank you for calling your Sinch number. You have just handled an incoming call.'; + + return Voice.customCalloutHelper.formatIceResponse( + Voice.iceActionHelper.hangup(), + Voice.iceInstructionHelper.say(instruction), + ); +}; + +/** + * Handles an Answered Call Event (ACE). + * @param {Voice.AceRequest} aceRequest - The incoming ACE request object. + * @return {string} The formatted ACE response to handle the answered call. + */ +export const handleAnsweredCallEvent = (aceRequest) => { + console.log(`Handling 'ACE' event:\n${JSON.stringify(aceRequest, null, 2)}`); + + return Voice.customCalloutHelper.formatAceResponse(); +}; + +/** + * Handles a prompt input event (PIE). + * @param {Voice.PieRequest} pieRequest - The incoming PIE request object. + * @return {string} An empty string as a response to the prompt input event. + */ +export const handlePromptInputEvent = (pieRequest) => { + console.log(`Handling 'PIE' event:\n${JSON.stringify(pieRequest, null, 2)}`); + + return ''; +}; + +/** + * Handles a disconnected call event (DICE). + * @param { Voice.DiceRequest } diceRequest - The incoming DICE request object. + * @return {string} An empty string as a response to the disconnected call event. + */ +export const handleDisconnectedCallEvent = (diceRequest) => { + console.log(`Handling 'DICE' event:\n${JSON.stringify(diceRequest, null, 2)}`); + + return ''; +}; + +/** + * Handles a notification event. + * @param { Voice.NotifyRequest } notifyRequest - The incoming notify event object + * @return {string} An empty string as a response to the notify event. + */ +export const handleNotifyEvent = (notifyRequest) => { + console.log(`Handling 'notify' event:\n${JSON.stringify(notifyRequest, null, 2)}`); + + return ''; +}; diff --git a/getting-started/voice/respond-to-incoming-call/server/src/voice/validateSignature.js b/getting-started/voice/respond-to-incoming-call/server/src/voice/validateSignature.js new file mode 100644 index 0000000..a68b1f5 --- /dev/null +++ b/getting-started/voice/respond-to-incoming-call/server/src/voice/validateSignature.js @@ -0,0 +1,13 @@ +export const validateSignature = (voiceCallbackWebhooks) => { + return (req, res, next) => { + const isValid = voiceCallbackWebhooks.validateAuthenticationHeader( + req.headers, req.rawBody, '/VoiceEvent', 'POST', + ); + + if (!isValid) { + return res.status(401).send('Unauthorized'); + } + + next(); + }; +}; diff --git a/getting-started/voice/respond-to-incoming-call/server/tsconfig.json b/getting-started/voice/respond-to-incoming-call/server/tsconfig.json new file mode 100644 index 0000000..2217753 --- /dev/null +++ b/getting-started/voice/respond-to-incoming-call/server/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../../../tsconfig.json" +} From efead4bc5d6d4b11c5b7ef67134cd930404d1ab0 Mon Sep 17 00:00:00 2001 From: Antoine SEIN <142824551+asein-sinch@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:12:44 +0200 Subject: [PATCH 6/7] Remove ACE and PIE handling --- .../server/src/voice/controller.js | 8 ------- .../server/src/voice/serverBusinessLogic.js | 22 ------------------- 2 files changed, 30 deletions(-) diff --git a/getting-started/voice/respond-to-incoming-call/server/src/voice/controller.js b/getting-started/voice/respond-to-incoming-call/server/src/voice/controller.js index f9fd3e9..8836ab9 100644 --- a/getting-started/voice/respond-to-incoming-call/server/src/voice/controller.js +++ b/getting-started/voice/respond-to-incoming-call/server/src/voice/controller.js @@ -1,11 +1,9 @@ import { validateSignature } from './validateSignature.js'; import { VoiceCallbackWebhooks } from '@sinch/sdk-core'; import { - handleAnsweredCallEvent, handleDisconnectedCallEvent, handleIncomingCallEvent, handleNotifyEvent, - handlePromptInputEvent, } from './serverBusinessLogic.js'; export const voiceController = (app, sinchClientParameters) => { @@ -20,12 +18,6 @@ export const voiceController = (app, sinchClientParameters) => { case 'ice': response = handleIncomingCallEvent(event); break; - case 'ace': - response = handleAnsweredCallEvent(event); - break; - case 'pie': - response = handlePromptInputEvent(event); - break; case 'dice': response = handleDisconnectedCallEvent(event); break; diff --git a/getting-started/voice/respond-to-incoming-call/server/src/voice/serverBusinessLogic.js b/getting-started/voice/respond-to-incoming-call/server/src/voice/serverBusinessLogic.js index fa42dde..0f24c29 100644 --- a/getting-started/voice/respond-to-incoming-call/server/src/voice/serverBusinessLogic.js +++ b/getting-started/voice/respond-to-incoming-call/server/src/voice/serverBusinessLogic.js @@ -17,28 +17,6 @@ export const handleIncomingCallEvent = (iceRequest) => { ); }; -/** - * Handles an Answered Call Event (ACE). - * @param {Voice.AceRequest} aceRequest - The incoming ACE request object. - * @return {string} The formatted ACE response to handle the answered call. - */ -export const handleAnsweredCallEvent = (aceRequest) => { - console.log(`Handling 'ACE' event:\n${JSON.stringify(aceRequest, null, 2)}`); - - return Voice.customCalloutHelper.formatAceResponse(); -}; - -/** - * Handles a prompt input event (PIE). - * @param {Voice.PieRequest} pieRequest - The incoming PIE request object. - * @return {string} An empty string as a response to the prompt input event. - */ -export const handlePromptInputEvent = (pieRequest) => { - console.log(`Handling 'PIE' event:\n${JSON.stringify(pieRequest, null, 2)}`); - - return ''; -}; - /** * Handles a disconnected call event (DICE). * @param { Voice.DiceRequest } diceRequest - The incoming DICE request object. From 02c92898dcd17c54ad940c338f3b8e62af3298d1 Mon Sep 17 00:00:00 2001 From: Antoine SEIN <142824551+asein-sinch@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:33:32 +0200 Subject: [PATCH 7/7] Remove 'notify' event handling --- .../server/src/voice/controller.js | 9 +-------- .../server/src/voice/serverBusinessLogic.js | 11 ----------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/getting-started/voice/respond-to-incoming-call/server/src/voice/controller.js b/getting-started/voice/respond-to-incoming-call/server/src/voice/controller.js index 8836ab9..0d9323a 100644 --- a/getting-started/voice/respond-to-incoming-call/server/src/voice/controller.js +++ b/getting-started/voice/respond-to-incoming-call/server/src/voice/controller.js @@ -1,10 +1,6 @@ import { validateSignature } from './validateSignature.js'; import { VoiceCallbackWebhooks } from '@sinch/sdk-core'; -import { - handleDisconnectedCallEvent, - handleIncomingCallEvent, - handleNotifyEvent, -} from './serverBusinessLogic.js'; +import { handleDisconnectedCallEvent, handleIncomingCallEvent } from './serverBusinessLogic.js'; export const voiceController = (app, sinchClientParameters) => { @@ -21,9 +17,6 @@ export const voiceController = (app, sinchClientParameters) => { case 'dice': response = handleDisconnectedCallEvent(event); break; - case 'notify': - response = handleNotifyEvent(event); - break; default: res.status(400).json({ error: 'Unsupported event type' }); } diff --git a/getting-started/voice/respond-to-incoming-call/server/src/voice/serverBusinessLogic.js b/getting-started/voice/respond-to-incoming-call/server/src/voice/serverBusinessLogic.js index 0f24c29..b6b8373 100644 --- a/getting-started/voice/respond-to-incoming-call/server/src/voice/serverBusinessLogic.js +++ b/getting-started/voice/respond-to-incoming-call/server/src/voice/serverBusinessLogic.js @@ -27,14 +27,3 @@ export const handleDisconnectedCallEvent = (diceRequest) => { return ''; }; - -/** - * Handles a notification event. - * @param { Voice.NotifyRequest } notifyRequest - The incoming notify event object - * @return {string} An empty string as a response to the notify event. - */ -export const handleNotifyEvent = (notifyRequest) => { - console.log(`Handling 'notify' event:\n${JSON.stringify(notifyRequest, null, 2)}`); - - return ''; -};