├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── upgrade-npm-packages.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── onconnect ├── app.js └── package.json ├── ondisconnect ├── app.js └── package.json ├── sendmessage ├── app.js └── package.json ├── template.yaml └── update-dependencies.sh /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-npm-packages.yml: -------------------------------------------------------------------------------- 1 | name: Upgrade NPM packages 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | schedule: 7 | - cron: "0 0 * * 0" 8 | 9 | jobs: 10 | upgrade-packages: 11 | name: Upgrade packages 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout 🛎️ 17 | uses: actions/checkout@v4 18 | 19 | - name: Set up Node.js ⚙️ 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 'latest' 23 | 24 | - name: Upgrade packages 🔀 25 | run: npm update 26 | 27 | - name: Commit and create PR 🔀 28 | uses: peter-evans/create-pull-request@v6 29 | with: 30 | title: 'build(deps): Upgrade NPM packages (automated)' 31 | branch: 'build-deps-upgrade-npm-packages-automated' 32 | commit-message: 'build(deps): upgrade NPM packages (automated)' 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/ 3 | .aws-sam/ 4 | */dist/* 5 | packaged.yaml 6 | node_modules/ 7 | package-lock.json 8 | samconfig.toml -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws-samples/simple-websockets-chat-app/issues), or [recently closed](https://github.com/aws-samples/simple-websockets-chat-app/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws-samples/simple-websockets-chat-app/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws-samples/simple-websockets-chat-app/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-websockets-chat-app 2 | 3 | **Update**: _We have released an updated and more feature-rich Websocket chat application sample using AWS CDK that you can find [here](https://github.com/aws-samples/websocket-chat-application)._ 4 | 5 | This is the code and template for the `simple-websocket-chat-app`. 6 | There are three functions contained within the directories and a `SAM` template that wires them up to a `DynamoDB` table and provides the minimal set of permissions needed to run the app: 7 | 8 | ``` 9 | . 10 | ├── README.md <-- This instructions file 11 | ├── onconnect <-- Source code onconnect 12 | ├── ondisconnect <-- Source code ondisconnect 13 | ├── sendmessage <-- Source code sendmessage 14 | └── template.yaml <-- SAM template for Lambda Functions and DDB 15 | ``` 16 | 17 | # Deploying to your account 18 | 19 | You have two choices for how you can deploy this code. 20 | 21 | ## Serverless Application Repository 22 | 23 | The first and fastest way is to use AWS's Serverless Application Repository to directly deploy the components of this app into your account without needing to use any additional tools. You'll be able to review everything before it deploys to make sure you understand what will happen. Click through to see the [application details](https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:729047367331:applications~simple-websockets-chat-app). 24 | 25 | ## AWS CLI commands 26 | 27 | If you prefer, you can install the [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) and use it to package, deploy, and describe your application. These are the commands you'll need to use: 28 | 29 | ``` 30 | sam deploy --guided 31 | 32 | aws cloudformation describe-stacks \ 33 | --stack-name simple-websocket-chat-app --query 'Stacks[].Outputs' 34 | ``` 35 | 36 | **Note:** `.gitignore` contains the `samconfig.toml`, hence make sure backup this file, or modify your .gitignore locally. 37 | 38 | ## Testing the chat API 39 | 40 | To test the WebSocket API, you can use [wscat](https://github.com/websockets/wscat), an open-source command line tool. 41 | 42 | 1. [Install NPM](https://www.npmjs.com/get-npm). 43 | 2. Install wscat: 44 | 45 | ```bash 46 | $ npm install -g wscat 47 | ``` 48 | 49 | 3. On the console, connect to your published API endpoint by executing the following command: 50 | 51 | ```bash 52 | $ wscat -c wss://{YOUR-API-ID}.execute-api.{YOUR-REGION}.amazonaws.com/{STAGE} 53 | ``` 54 | 55 | 4. To test the sendMessage function, send a JSON message like the following example. The Lambda function sends it back using the callback URL: 56 | 57 | ```bash 58 | $ wscat -c wss://{YOUR-API-ID}.execute-api.{YOUR-REGION}.amazonaws.com/prod 59 | connected (press CTRL+C to quit) 60 | > {"action":"sendmessage", "data":"hello world"} 61 | < hello world 62 | ``` 63 | 64 | ## License Summary 65 | 66 | This sample code is made available under a modified MIT license. See the LICENSE file. 67 | -------------------------------------------------------------------------------- /onconnect/app.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | const AWS = require('aws-sdk'); 5 | 6 | const ddb = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10', region: process.env.AWS_REGION }); 7 | 8 | exports.handler = async event => { 9 | const putParams = { 10 | TableName: process.env.TABLE_NAME, 11 | Item: { 12 | connectionId: event.requestContext.connectionId 13 | } 14 | }; 15 | 16 | try { 17 | await ddb.put(putParams).promise(); 18 | } catch (err) { 19 | return { statusCode: 500, body: 'Failed to connect: ' + JSON.stringify(err) }; 20 | } 21 | 22 | return { statusCode: 200, body: 'Connected.' }; 23 | }; 24 | -------------------------------------------------------------------------------- /onconnect/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "on-connect", 3 | "version": "1.0.0", 4 | "description": "onconnect example for WebSockets on API Gateway", 5 | "main": "src/app.js", 6 | "author": "SAM CLI", 7 | "license": "MIT", 8 | "dependencies": {}, 9 | "devDependencies": { 10 | "aws-sdk": "^2.1563.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ondisconnect/app.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | // https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-route-keys-connect-disconnect.html 5 | // The $disconnect route is executed after the connection is closed. 6 | // The connection can be closed by the server or by the client. As the connection is already closed when it is executed, 7 | // $disconnect is a best-effort event. 8 | // API Gateway will try its best to deliver the $disconnect event to your integration, but it cannot guarantee delivery. 9 | 10 | const AWS = require('aws-sdk'); 11 | 12 | const ddb = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10', region: process.env.AWS_REGION }); 13 | 14 | exports.handler = async event => { 15 | const deleteParams = { 16 | TableName: process.env.TABLE_NAME, 17 | Key: { 18 | connectionId: event.requestContext.connectionId 19 | } 20 | }; 21 | 22 | try { 23 | await ddb.delete(deleteParams).promise(); 24 | } catch (err) { 25 | return { statusCode: 500, body: 'Failed to disconnect: ' + JSON.stringify(err) }; 26 | } 27 | 28 | return { statusCode: 200, body: 'Disconnected.' }; 29 | }; 30 | -------------------------------------------------------------------------------- /ondisconnect/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "on-disconnect", 3 | "version": "1.0.0", 4 | "description": "ondisconnect example for WebSockets on API Gateway", 5 | "main": "src/app.js", 6 | "author": "SAM CLI", 7 | "license": "MIT", 8 | "dependencies": {}, 9 | "devDependencies": { 10 | "aws-sdk": "^2.1563.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sendmessage/app.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | const AWS = require('aws-sdk'); 5 | 6 | const ddb = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10', region: process.env.AWS_REGION }); 7 | 8 | const { TABLE_NAME } = process.env; 9 | 10 | exports.handler = async event => { 11 | let connectionData; 12 | 13 | try { 14 | connectionData = await ddb.scan({ TableName: TABLE_NAME, ProjectionExpression: 'connectionId' }).promise(); 15 | } catch (e) { 16 | return { statusCode: 500, body: e.stack }; 17 | } 18 | 19 | const apigwManagementApi = new AWS.ApiGatewayManagementApi({ 20 | apiVersion: '2018-11-29', 21 | endpoint: `${event.requestContext.apiId}.execute-api.${process.env.AWS_REGION}.amazonaws.com/${event.requestContext.stage}` 22 | }); 23 | 24 | const postData = JSON.parse(event.body).data; 25 | 26 | const postCalls = connectionData.Items.map(async ({ connectionId }) => { 27 | try { 28 | await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: postData }).promise(); 29 | } catch (e) { 30 | if (e.statusCode === 410) { 31 | console.log(`Found stale connection, deleting ${connectionId}`); 32 | await ddb.delete({ TableName: TABLE_NAME, Key: { connectionId } }).promise(); 33 | } else { 34 | throw e; 35 | } 36 | } 37 | }); 38 | 39 | try { 40 | await Promise.all(postCalls); 41 | } catch (e) { 42 | return { statusCode: 500, body: e.stack }; 43 | } 44 | 45 | return { statusCode: 200, body: 'Data sent.' }; 46 | }; 47 | -------------------------------------------------------------------------------- /sendmessage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "send-message", 3 | "version": "1.0.0", 4 | "description": "sendmessage example for WebSockets on API Gateway", 5 | "main": "src/app.js", 6 | "author": "SAM CLI", 7 | "license": "MIT", 8 | "dependencies": {}, 9 | "devDependencies": { 10 | "aws-sdk": "^2.1563.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: > 4 | simple-websockets-chat-app 5 | 6 | SAM Template for simple-websockets-chat-app that has the DynamoDB table and Lambda 7 | functions needed to demonstrate the Websocket protocol on API Gateway. 8 | 9 | Parameters: 10 | TableName: 11 | Type: String 12 | Default: "simplechat_connections" 13 | Description: (Required) The name of the new DynamoDB to store connection identifiers for each connected clients. Minimum 3 characters 14 | MinLength: 3 15 | MaxLength: 50 16 | AllowedPattern: ^[A-Za-z_]+$ 17 | ConstraintDescription: "Required. Can be characters and underscore only. No numbers or special characters allowed." 18 | 19 | Resources: 20 | SimpleChatWebSocket: 21 | Type: AWS::ApiGatewayV2::Api 22 | Properties: 23 | Name: SimpleChatWebSocket 24 | ProtocolType: WEBSOCKET 25 | RouteSelectionExpression: "$request.body.action" 26 | ConnectRoute: 27 | Type: AWS::ApiGatewayV2::Route 28 | Properties: 29 | ApiId: !Ref SimpleChatWebSocket 30 | RouteKey: $connect 31 | AuthorizationType: NONE 32 | OperationName: ConnectRoute 33 | Target: !Join 34 | - "/" 35 | - - "integrations" 36 | - !Ref ConnectInteg 37 | ConnectInteg: 38 | Type: AWS::ApiGatewayV2::Integration 39 | Properties: 40 | ApiId: !Ref SimpleChatWebSocket 41 | Description: Connect Integration 42 | IntegrationType: AWS_PROXY 43 | IntegrationUri: 44 | Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnConnectFunction.Arn}/invocations 45 | DisconnectRoute: 46 | Type: AWS::ApiGatewayV2::Route 47 | Properties: 48 | ApiId: !Ref SimpleChatWebSocket 49 | RouteKey: $disconnect 50 | AuthorizationType: NONE 51 | OperationName: DisconnectRoute 52 | Target: !Join 53 | - "/" 54 | - - "integrations" 55 | - !Ref DisconnectInteg 56 | DisconnectInteg: 57 | Type: AWS::ApiGatewayV2::Integration 58 | Properties: 59 | ApiId: !Ref SimpleChatWebSocket 60 | Description: Disconnect Integration 61 | IntegrationType: AWS_PROXY 62 | IntegrationUri: 63 | Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnDisconnectFunction.Arn}/invocations 64 | SendRoute: 65 | Type: AWS::ApiGatewayV2::Route 66 | Properties: 67 | ApiId: !Ref SimpleChatWebSocket 68 | RouteKey: sendmessage 69 | AuthorizationType: NONE 70 | OperationName: SendRoute 71 | Target: !Join 72 | - "/" 73 | - - "integrations" 74 | - !Ref SendInteg 75 | SendInteg: 76 | Type: AWS::ApiGatewayV2::Integration 77 | Properties: 78 | ApiId: !Ref SimpleChatWebSocket 79 | Description: Send Integration 80 | IntegrationType: AWS_PROXY 81 | IntegrationUri: 82 | Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SendMessageFunction.Arn}/invocations 83 | Deployment: 84 | Type: AWS::ApiGatewayV2::Deployment 85 | DependsOn: 86 | - ConnectRoute 87 | - SendRoute 88 | - DisconnectRoute 89 | Properties: 90 | ApiId: !Ref SimpleChatWebSocket 91 | Stage: 92 | Type: AWS::ApiGatewayV2::Stage 93 | Properties: 94 | StageName: Prod 95 | Description: Prod Stage 96 | DeploymentId: !Ref Deployment 97 | ApiId: !Ref SimpleChatWebSocket 98 | ConnectionsTable: 99 | Type: AWS::DynamoDB::Table 100 | Properties: 101 | AttributeDefinitions: 102 | - AttributeName: "connectionId" 103 | AttributeType: "S" 104 | KeySchema: 105 | - AttributeName: "connectionId" 106 | KeyType: "HASH" 107 | ProvisionedThroughput: 108 | ReadCapacityUnits: 5 109 | WriteCapacityUnits: 5 110 | SSESpecification: 111 | SSEEnabled: True 112 | TableName: !Ref TableName 113 | OnConnectFunction: 114 | Type: AWS::Serverless::Function 115 | Properties: 116 | CodeUri: onconnect/ 117 | Handler: app.handler 118 | MemorySize: 256 119 | Runtime: nodejs20.x 120 | Environment: 121 | Variables: 122 | TABLE_NAME: !Ref TableName 123 | Policies: 124 | - DynamoDBCrudPolicy: 125 | TableName: !Ref TableName 126 | OnConnectPermission: 127 | Type: AWS::Lambda::Permission 128 | DependsOn: 129 | - SimpleChatWebSocket 130 | Properties: 131 | Action: lambda:InvokeFunction 132 | FunctionName: !Ref OnConnectFunction 133 | Principal: apigateway.amazonaws.com 134 | OnDisconnectFunction: 135 | Type: AWS::Serverless::Function 136 | Properties: 137 | CodeUri: ondisconnect/ 138 | Handler: app.handler 139 | MemorySize: 256 140 | Runtime: nodejs20.x 141 | Environment: 142 | Variables: 143 | TABLE_NAME: !Ref TableName 144 | Policies: 145 | - DynamoDBCrudPolicy: 146 | TableName: !Ref TableName 147 | OnDisconnectPermission: 148 | Type: AWS::Lambda::Permission 149 | DependsOn: 150 | - SimpleChatWebSocket 151 | Properties: 152 | Action: lambda:InvokeFunction 153 | FunctionName: !Ref OnDisconnectFunction 154 | Principal: apigateway.amazonaws.com 155 | SendMessageFunction: 156 | Type: AWS::Serverless::Function 157 | Properties: 158 | CodeUri: sendmessage/ 159 | Handler: app.handler 160 | MemorySize: 256 161 | Runtime: nodejs20.x 162 | Environment: 163 | Variables: 164 | TABLE_NAME: !Ref TableName 165 | Policies: 166 | - DynamoDBCrudPolicy: 167 | TableName: !Ref TableName 168 | - Statement: 169 | - Effect: Allow 170 | Action: 171 | - "execute-api:ManageConnections" 172 | Resource: 173 | - !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${SimpleChatWebSocket}/*" 174 | SendMessagePermission: 175 | Type: AWS::Lambda::Permission 176 | DependsOn: 177 | - SimpleChatWebSocket 178 | Properties: 179 | Action: lambda:InvokeFunction 180 | FunctionName: !Ref SendMessageFunction 181 | Principal: apigateway.amazonaws.com 182 | 183 | Outputs: 184 | ConnectionsTableArn: 185 | Description: "Connections table ARN" 186 | Value: !GetAtt ConnectionsTable.Arn 187 | 188 | OnConnectFunctionArn: 189 | Description: "OnConnect function ARN" 190 | Value: !GetAtt OnConnectFunction.Arn 191 | 192 | OnDisconnectFunctionArn: 193 | Description: "OnDisconnect function ARN" 194 | Value: !GetAtt OnDisconnectFunction.Arn 195 | 196 | SendMessageFunctionArn: 197 | Description: "SendMessage function ARN" 198 | Value: !GetAtt SendMessageFunction.Arn 199 | 200 | WebSocketURI: 201 | Description: "The WSS Protocol URI to connect to" 202 | Value: 203 | !Join [ 204 | "", 205 | [ 206 | "wss://", 207 | !Ref SimpleChatWebSocket, 208 | ".execute-api.", 209 | !Ref "AWS::Region", 210 | ".amazonaws.com/", 211 | !Ref "Stage", 212 | ], 213 | ] 214 | -------------------------------------------------------------------------------- /update-dependencies.sh: -------------------------------------------------------------------------------- 1 | cd onconnect && ncu -u && npm install && cd .. 2 | cd ondisconnect && ncu -u && npm install && cd .. 3 | cd sendmessage && ncu -u && npm install && cd .. --------------------------------------------------------------------------------