├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── LICENSE-THIRD-PARTY ├── README.md ├── images ├── architecture.jpg └── map.jpg ├── sensor ├── .gitignore ├── LICENSE-THIRD-PARTY ├── certs │ └── AmazonRootCA1.pem ├── create-sensors.js ├── delete-sensors.js ├── index.js ├── package-lock.json ├── package.json ├── policy.json └── sensors.json └── web ├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .graphqlconfig.yml ├── README.md ├── amplify ├── auth │ └── resource.ts ├── backend.ts ├── data │ └── resource.ts ├── functions │ ├── list-sensors │ │ ├── handler.ts │ │ └── resource.ts │ └── send-sensor-value │ │ ├── handler.ts │ │ └── resource.ts ├── package.json └── tsconfig.json ├── index.html ├── package-lock.json ├── package.json ├── public └── favicon.ico ├── src ├── App.jsx ├── MapPage.css ├── MapPage.jsx ├── graphql │ ├── mutations.js │ ├── queries.js │ ├── schema.json │ └── subscriptions.js └── main.jsx └── vite.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | #VS Code 6 | .vscode 7 | -------------------------------------------------------------------------------- /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. 5 | -------------------------------------------------------------------------------- /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, or recently closed, 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' 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](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. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 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 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Realtime IoT Dashboard with AWS AppSync and Amazon Location Service 2 | 3 | **Update!!!** this project has been updated to use [**Amplify Gen2**](https://docs.amplify.aws/react/) to deploy the backend services in AWS. 4 | 5 | This application demonstrates a web application dashboard receiving real-time updates from a series of IoT sensors. It depicts a fictitious set of pH sensors deployed around the San Francisco Bay. The solution is built with React, AWS AppSync, Amazon Location Service, AWS Amplify, and AWS IoT technologies. 6 | 7 | ![Image description](images/map.jpg) 8 | 9 | The sensors are represented as the colored dots. Their color will fluxuate between red, green, and yellow based on the messages received from the sensors. 10 | 11 | ## Architecture 12 | 13 | ![Image description](images/architecture.jpg) 14 | 15 | 1. The sensor component is developed with the AWS IoT Device SDK for JavaScript. The sensors are registered as _Things_ in IoT Core and publish random values to the Cloud on a configurable frequency. Metadata about each sensor, such as its geolocation, is stored in a _Thing Shadow_. 16 | 17 | 2. Rules in IoT Core subscribe to the message topic and forward the JSON payload to a Lambda function and the IoT Analytics pipeline. 18 | 19 | 3. The Node.js Lambda function executes a GraphQL mutation in AppSync. The mutation saves the sensor's value in DynamoDB and broadcasts the value in real-time to the web dashboard. The Lambda function uses an IAM role and policy to obtain permissions to interact with AppSync. 20 | 21 | 4. The React web dashboard application is written in JavaScript and subscribes to the AppSync sensor subscriptions. When new values are received, an Amazon Location Service map is updated in real-time to reflect the new sensor values. The application uses Cognito to authenticate users and allow them to perform the AppSync subscription. 22 | 23 | ## Getting Started 24 | 25 | ## **Prerequisites** 26 | 27 | The following software was used in the development of this application. While it may work with alternative versions, we recommend you deploy the specified minimum version. 28 | 29 | 1. An AWS account in which you have Administrator access. 30 | 31 | 2. [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) (v2.15.3) the AWS Command Line Interface (CLI) is used to configure your connection credentials to AWS. These credentials are used by the CDK, Amplify, and the CLI. 32 | 33 | 3. [Node.js](https://nodejs.org/en/download/) (v18.19.0) with NPM (v10.1.0) 34 | 35 | ## **Installation** 36 | 37 | **Clone this code repository** 38 | 39 | ``` 40 | git clone https://github.com/aws-samples/aws-appsync-iot-core-realtime-dashboard.git 41 | ``` 42 | 43 | **Switch to the project's web folder** 44 | 45 | ``` 46 | cd aws-appsync-iot-core-realtime-dashboard/web 47 | ``` 48 | 49 | **Install the web app's Node.js packages** 50 | 51 | ``` 52 | npm install 53 | ``` 54 | 55 | **Deploy the Infrastructure with Amplify Gen2** 56 | 57 | ``` 58 | npx ampx sandbox 59 | ``` 60 | 61 | The deployment is complete when you see the following output: 62 | 63 | ```bash 64 | File written: amplify_outputs.json 65 | ``` 66 | 67 | Press **CTRL-C** to exit the deployment. 68 | 69 | Resources created in your account include: 70 | 71 | - AppSync GraphQL API 72 | - DynamoDB Table 73 | - Cognito User Pool 74 | - Lambda Functions (2) 75 | - IoT Rule 76 | - Amazon Location Service Map 77 | 78 | **Install the IoT sensor simulator** 79 | 80 | Open a new terminal window then switch to the app's **sensor** folder (aws-appsync-iot-core-realtime-dashboard/sensor). 81 | 82 | Install the Node js packages, and run the Node js app to create your sensor as a _Thing_ in AWS IoT Core. It will also create and install the certificates your sensor needs to authenticate to IoT Core. 83 | 84 | From the app's **sensor** folder: 85 | 86 | ``` 87 | npm install 88 | node create-sensors.js 89 | ``` 90 | 91 | _Note - the profile and region arguments are optional. If not specified the app will create the sensors using your default AWS Profile in us-east-1_ 92 | 93 | ## Run the web app 94 | 95 | **Start the IoT sensor** 96 | 97 | From the **sensor** terminal window: 98 | 99 | ``` 100 | node index.js 101 | ``` 102 | 103 | You will see output from the app as it connects to IoT Core and publishes new messages for six sensors every few seconds. 104 | 105 | ``` 106 | published to shadow topic $aws/things/sensor-sf-north/shadow/update {"state":{"reported":{"name":"SF Bay - North","enabled":true,"geo":{"latitude":37.800307,"longitude":-122.354788}}}} 107 | 108 | published to telemetry topic dt/bay-health/SF/sensor-sf-north/sensor-value {"pH":5,"temperature":54.7,"salinity":25,"disolvedO2":6.1,"timestamp":1591831843844} 109 | ``` 110 | 111 | **Start the web app** 112 | 113 | Switch back to the terminal window pointing to the **web** folder and run: 114 | 115 | ``` 116 | npm run dev 117 | ``` 118 | 119 | This will launch the application in your machine's default web browser. 120 | 121 | **Sign-up and Sign-in** 122 | 123 | The web app requires users to authenticate via Cognito. The first screen you will see is a logon screen. Click the **Create account** link and create a new account using your email address. 124 | 125 | Cognito will then email you a confirmation code. Enter this code into the subsequent confirmation screen and logon to the app with your credentials. 126 | 127 | **Use the web app** 128 | 129 | You should now see a screen similar to the one at the top of this guide. If you look at the terminal window running the sensor app, you shoud see the values being published to the Cloud reflected in the web app's sensor icon in real-time. 130 | 131 | ## Cleanup 132 | 133 | Once you are finished working with this project, you may want to delete the resources it created in your AWS account. 134 | 135 | From the **web** folder: 136 | 137 | ``` 138 | npx ampx sandbox delete 139 | ``` 140 | 141 | From the **sensor** folder: 142 | 143 | ``` 144 | node delete-sensors.js 145 | ``` 146 | 147 | _Note - the profile and region arguments are optional. If not specified the app will delete the sensors using your default AWS Profile in us-east-1_ 148 | 149 | ## License 150 | 151 | This sample code is made available under a modified MIT-0 license. See the LICENSE file. 152 | -------------------------------------------------------------------------------- /images/architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-appsync-iot-core-realtime-dashboard/0c9dea2908e162e7e26302ccfa6c45504766faa5/images/architecture.jpg -------------------------------------------------------------------------------- /images/map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-appsync-iot-core-realtime-dashboard/0c9dea2908e162e7e26302ccfa6c45504766faa5/images/map.jpg -------------------------------------------------------------------------------- /sensor/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # node.js 6 | # 7 | node_modules/ 8 | npm-debug.log 9 | yarn-error.log 10 | 11 | # AWS IOT thing certificates 12 | certs/*.crt 13 | certs/*.key 14 | -------------------------------------------------------------------------------- /sensor/certs/AmazonRootCA1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF 3 | ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 4 | b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL 5 | MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv 6 | b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj 7 | ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM 8 | 9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw 9 | IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 10 | VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L 11 | 93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm 12 | jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC 13 | AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA 14 | A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI 15 | U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs 16 | N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv 17 | o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU 18 | 5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy 19 | rqXRfboQnoZsG4q5WTP468SQvvG5 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /sensor/create-sensors.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs').promises; 2 | const { 3 | IoTClient, 4 | DescribeEndpointCommand, 5 | UpdateIndexingConfigurationCommand, 6 | CreatePolicyCommand, 7 | CreateKeysAndCertificateCommand, 8 | CreateThingTypeCommand, 9 | CreateThingCommand, 10 | AttachPolicyCommand, 11 | AttachThingPrincipalCommand 12 | } = require("@aws-sdk/client-iot"); 13 | 14 | const { ArgumentParser } = require('argparse'); 15 | 16 | //constants used in the app - do not change 17 | const SENSORS_FILE = './sensors.json'; 18 | const CERT_FOLDER = './certs/'; 19 | const POLICY_FILE = './policy.json'; 20 | const ROOT_CA_FILE = 'AmazonRootCA1.pem'; 21 | 22 | //open sensor definition file 23 | var sensors = require(SENSORS_FILE); 24 | const policyDocument = require(POLICY_FILE); 25 | 26 | async function createSensors(profile, region){ 27 | 28 | try { 29 | 30 | const iotClient = new IoTClient({ profile: profile, region: region }); 31 | 32 | // get the regional IOT endpoint 33 | var params = { endpointType: 'iot:Data-ATS'}; 34 | var command = new DescribeEndpointCommand(params); 35 | const response = await iotClient.send(command); 36 | const host = response.endpointAddress; 37 | 38 | //enable thing fleet indexing to enable searching things 39 | params = { 40 | thingIndexingConfiguration: { 41 | thingIndexingMode: "REGISTRY_AND_SHADOW" 42 | } 43 | } 44 | 45 | command = new UpdateIndexingConfigurationCommand(params) 46 | var result = await iotClient.send(command) 47 | 48 | //iterate over all sensors and create policies, certs, and things 49 | sensors.forEach(async (sensor) => { 50 | 51 | //set the iot core endpoint 52 | sensor.settings.host = host; 53 | 54 | //create the IOT policy 55 | var policyName = 'Policy-' + sensor.settings.clientId; 56 | var policy = { policyName: policyName, policyDocument: JSON.stringify(policyDocument)}; 57 | command = new CreatePolicyCommand(policy) 58 | result = await iotClient.send(command) 59 | 60 | //create the certificates 61 | command = new CreateKeysAndCertificateCommand({setAsActive:true}); 62 | result = await iotClient.send(command) 63 | sensor.settings.certificateArn = result.certificateArn; 64 | const certificatePem = result.certificatePem; 65 | const privateKey = result.keyPair.PrivateKey; 66 | 67 | //save the certificate 68 | var fileName = CERT_FOLDER + sensor.settings.clientId + '-certificate.pem.crt'; 69 | sensor.settings.certPath = fileName; 70 | await fs.writeFile(fileName, certificatePem); 71 | 72 | //save the private key 73 | fileName = CERT_FOLDER + sensor.settings.clientId + '-private.pem.key'; 74 | sensor.settings.keyPath = fileName; 75 | await fs.writeFile(fileName, privateKey); 76 | 77 | //save the AWS root certificate 78 | sensor.settings.caPath = CERT_FOLDER + ROOT_CA_FILE; 79 | 80 | // //create the thing type 81 | params = { 82 | thingTypeName: sensor.thingTypeName 83 | } 84 | 85 | command = new CreateThingTypeCommand(params) 86 | result = await iotClient.send(command) 87 | 88 | //create the thing 89 | params = { 90 | thingName: sensor.settings.clientId, 91 | attributePayload: { 92 | attributes: { 93 | 'Manufacturer': sensor.manufacturer, 94 | 'Model': sensor.model, 95 | 'Firmware': sensor.firmware 96 | }, 97 | merge: false 98 | }, 99 | thingTypeName: sensor.thingTypeName 100 | }; 101 | 102 | command = new CreateThingCommand(params) 103 | await iotClient.send(command) 104 | 105 | //attach policy to certificate 106 | command = new AttachPolicyCommand({ policyName: policyName, target: sensor.settings.certificateArn}) 107 | await iotClient.send(command) 108 | 109 | //attach thing to certificate 110 | command = new AttachThingPrincipalCommand({thingName: sensor.settings.clientId, principal: sensor.settings.certificateArn}) 111 | await iotClient.send(command) 112 | 113 | //save the updated settings file 114 | let data = JSON.stringify(sensors, null, 2); 115 | await fs.writeFile(SENSORS_FILE, data); 116 | }) 117 | 118 | //display results 119 | console.log('IoT Things provisioned'); 120 | console.log('AWS Profile: ' + profile); 121 | console.log('AWS Region: ' + region); 122 | 123 | sensors.forEach((sensor) => { 124 | console.log('Thing Name: ' + sensor.settings.clientId); 125 | }) 126 | 127 | } 128 | catch (err) { 129 | console.log('Error creating sensors'); 130 | console.log(err.message); 131 | } 132 | 133 | } 134 | 135 | // parse for profile command line arguent 136 | const parser = new ArgumentParser({ 137 | description: 'Creates IoT Things for sensors defined in sensors.json' 138 | }); 139 | 140 | parser.add_argument('--profile', {default: 'default'}); 141 | parser.add_argument('--region', {default: 'us-east-1'}); 142 | 143 | args = parser.parse_args() 144 | 145 | createSensors(args.profile, args.region); 146 | -------------------------------------------------------------------------------- /sensor/delete-sensors.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs').promises; 2 | const { 3 | IoTClient, 4 | DetachThingPrincipalCommand, 5 | DeleteThingCommand, 6 | DetachPolicyCommand, 7 | DeletePolicyCommand, 8 | UpdateCertificateCommand, 9 | DeleteCertificateCommand 10 | } = require("@aws-sdk/client-iot"); 11 | 12 | const { ArgumentParser } = require('argparse'); 13 | 14 | //constants used in the app - do not change 15 | const SENSORS_FILE = './sensors.json'; 16 | 17 | //open sensor definition file 18 | var sensors = require(SENSORS_FILE); 19 | 20 | async function deleteSensors(profile, region){ 21 | 22 | try { 23 | 24 | const iotClient = new IoTClient({ profile: profile, region: region }); 25 | 26 | //iterate over all sensors and create policies, certs, and things 27 | sensors.forEach(async (sensor) => { 28 | 29 | //remove the iot core endpoint 30 | sensor.settings.host = ""; 31 | 32 | //attach thing to certificate 33 | var command = new DetachThingPrincipalCommand({thingName: sensor.settings.clientId, principal: sensor.settings.certificateArn}) 34 | await iotClient.send(command) 35 | 36 | //delete the thing 37 | command = new DeleteThingCommand({thingName: sensor.settings.clientId}) 38 | await iotClient.send(command) 39 | 40 | //detach policy from certificate 41 | var policyName = 'Policy-' + sensor.settings.clientId; 42 | command = new DetachPolicyCommand({ policyName: policyName, target: sensor.settings.certificateArn}) 43 | await iotClient.send(command) 44 | 45 | //delete the IOT policy 46 | command = new DeletePolicyCommand({policyName: policyName}) 47 | await iotClient.send(command) 48 | 49 | //delete the certificates 50 | var certificateId = sensor.settings.certificateArn.split('/')[1]; 51 | command = new UpdateCertificateCommand({certificateId:certificateId, newStatus:"INACTIVE"}) 52 | await iotClient.send(command) 53 | 54 | command = new DeleteCertificateCommand({certificateId:certificateId, forceDelete:true}) 55 | await iotClient.send(command) 56 | sensor.settings.certificateArn = "" 57 | 58 | //delete the certificate files 59 | await fs.unlink(sensor.settings.keyPath); 60 | sensor.settings.keyPath = ""; 61 | 62 | await fs.unlink(sensor.settings.certPath); 63 | sensor.settings.certPath = ""; 64 | sensor.settings.caPath = ""; 65 | 66 | //save the updated settings file 67 | let data = JSON.stringify(sensors, null, 2); 68 | await fs.writeFile(SENSORS_FILE, data); 69 | }) 70 | 71 | //display results 72 | console.log('IoT Things removed: ' + sensors.length); 73 | console.log('AWS Profile: ' + profile); 74 | console.log('AWS Region: ' + region); 75 | 76 | sensors.forEach((sensor) => { 77 | console.log('Thing Name: ' + sensor.settings.clientId); 78 | }) 79 | 80 | } 81 | catch (err) { 82 | console.log('Error deleting sensors'); 83 | console.log(err.message); 84 | } 85 | } 86 | 87 | // parse for profile command line arguent 88 | const parser = new ArgumentParser({ 89 | description: 'Deletes IoT Things for sensors defined in sensors.json' 90 | }); 91 | 92 | parser.add_argument('--profile', {default: 'default'}); 93 | parser.add_argument('--region', {default: 'us-east-1'}); 94 | 95 | args = parser.parse_args() 96 | 97 | deleteSensors(args.profile, args.region); 98 | -------------------------------------------------------------------------------- /sensor/index.js: -------------------------------------------------------------------------------- 1 | const awsIot = require('aws-iot-device-sdk'); 2 | 3 | //load the sensors file that contains the location of the device certificates and the clientId of the sensor 4 | var sensors = require('./sensors.json'); 5 | 6 | //constants used in the application 7 | const SHADOW_TOPIC = "$aws/things/[thingName]/shadow/update"; 8 | const VALUE_TOPIC = "dt/bay-health/SF/[thingName]/sensor-value"; //topic to which sensor values will be published 9 | 10 | //shadow document to be transmitted at statup 11 | var shadowDocument = { 12 | state: { 13 | reported: { 14 | name: "", 15 | enabled: true, 16 | geo: { 17 | latitude: 0, 18 | longitude: 0 19 | } 20 | } 21 | } 22 | } 23 | 24 | async function run(sensor) { 25 | 26 | //initialize the IOT device 27 | var device = awsIot.device(sensor.settings); 28 | 29 | //create a placeholder for the message 30 | var msg = { 31 | pH: 0, 32 | temperature: 0, 33 | salinity: 0, 34 | disolvedO2: 0, 35 | timestamp: new Date().getTime() 36 | } 37 | 38 | device.on('connect', function() { 39 | 40 | console.log('connected to IoT Hub'); 41 | 42 | //publish the shadow document for the sensor 43 | var topic = SHADOW_TOPIC.replace('[thingName]', sensor.settings.clientId); 44 | 45 | shadowDocument.state.reported.name = sensor.name; 46 | shadowDocument.state.reported.enabled = true; 47 | shadowDocument.state.reported.geo.latitude = sensor.geo.latitude; 48 | shadowDocument.state.reported.geo.longitude = sensor.geo.longitude; 49 | 50 | device.publish(topic, JSON.stringify(shadowDocument)); 51 | 52 | console.log('published to shadow topic ' + topic + ' ' + JSON.stringify(shadowDocument)); 53 | 54 | //publish new value readings based on value_rate 55 | setInterval(function(){ 56 | 57 | //calculate randome values for each sensor reading 58 | msg.pH = RandomValue(50, 100) / 10; 59 | msg.temperature = RandomValue(480, 570) / 10; 60 | msg.salinity = RandomValue(200, 350) / 10; 61 | msg.disolvedO2 = RandomValue(40, 120) / 10; 62 | 63 | msg.timestamp = new Date().getTime(); 64 | 65 | //publish the sensor reading message 66 | var topic = VALUE_TOPIC.replace('[thingName]', sensor.settings.clientId); 67 | 68 | device.publish(topic, JSON.stringify(msg)); 69 | 70 | console.log('published to telemetry topic ' + topic + ' ' + JSON.stringify(msg)); 71 | 72 | }, sensor.frequency); 73 | }); 74 | 75 | device.on('error', function(error) { 76 | console.log('Error: ', error); 77 | }); 78 | } 79 | 80 | function RandomValue (min, max) { 81 | return Math.floor(Math.random() * (max - min + 1) ) + min; 82 | } 83 | 84 | //run simulation for each sensor 85 | sensors.forEach((sensor) => { 86 | run(sensor); 87 | }) 88 | -------------------------------------------------------------------------------- /sensor/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-iot-core-appsync-realtime-dashboard-sensor", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "aws-iot-core-appsync-realtime-dashboard-sensor", 9 | "version": "1.0.0", 10 | "license": "MIT-0", 11 | "dependencies": { 12 | "@aws-sdk/client-iot": "^3.708.0", 13 | "argparse": "^2.0.1", 14 | "aws-iot-device-sdk": "^2.2.15" 15 | } 16 | }, 17 | "node_modules/@aws-crypto/sha256-browser": { 18 | "version": "5.2.0", 19 | "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", 20 | "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", 21 | "license": "Apache-2.0", 22 | "dependencies": { 23 | "@aws-crypto/sha256-js": "^5.2.0", 24 | "@aws-crypto/supports-web-crypto": "^5.2.0", 25 | "@aws-crypto/util": "^5.2.0", 26 | "@aws-sdk/types": "^3.222.0", 27 | "@aws-sdk/util-locate-window": "^3.0.0", 28 | "@smithy/util-utf8": "^2.0.0", 29 | "tslib": "^2.6.2" 30 | } 31 | }, 32 | "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { 33 | "version": "2.2.0", 34 | "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", 35 | "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", 36 | "license": "Apache-2.0", 37 | "dependencies": { 38 | "tslib": "^2.6.2" 39 | }, 40 | "engines": { 41 | "node": ">=14.0.0" 42 | } 43 | }, 44 | "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { 45 | "version": "2.2.0", 46 | "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", 47 | "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", 48 | "license": "Apache-2.0", 49 | "dependencies": { 50 | "@smithy/is-array-buffer": "^2.2.0", 51 | "tslib": "^2.6.2" 52 | }, 53 | "engines": { 54 | "node": ">=14.0.0" 55 | } 56 | }, 57 | "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { 58 | "version": "2.3.0", 59 | "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", 60 | "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", 61 | "license": "Apache-2.0", 62 | "dependencies": { 63 | "@smithy/util-buffer-from": "^2.2.0", 64 | "tslib": "^2.6.2" 65 | }, 66 | "engines": { 67 | "node": ">=14.0.0" 68 | } 69 | }, 70 | "node_modules/@aws-crypto/sha256-js": { 71 | "version": "5.2.0", 72 | "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", 73 | "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", 74 | "license": "Apache-2.0", 75 | "dependencies": { 76 | "@aws-crypto/util": "^5.2.0", 77 | "@aws-sdk/types": "^3.222.0", 78 | "tslib": "^2.6.2" 79 | }, 80 | "engines": { 81 | "node": ">=16.0.0" 82 | } 83 | }, 84 | "node_modules/@aws-crypto/supports-web-crypto": { 85 | "version": "5.2.0", 86 | "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", 87 | "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", 88 | "license": "Apache-2.0", 89 | "dependencies": { 90 | "tslib": "^2.6.2" 91 | } 92 | }, 93 | "node_modules/@aws-crypto/util": { 94 | "version": "5.2.0", 95 | "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", 96 | "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", 97 | "license": "Apache-2.0", 98 | "dependencies": { 99 | "@aws-sdk/types": "^3.222.0", 100 | "@smithy/util-utf8": "^2.0.0", 101 | "tslib": "^2.6.2" 102 | } 103 | }, 104 | "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { 105 | "version": "2.2.0", 106 | "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", 107 | "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", 108 | "license": "Apache-2.0", 109 | "dependencies": { 110 | "tslib": "^2.6.2" 111 | }, 112 | "engines": { 113 | "node": ">=14.0.0" 114 | } 115 | }, 116 | "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { 117 | "version": "2.2.0", 118 | "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", 119 | "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", 120 | "license": "Apache-2.0", 121 | "dependencies": { 122 | "@smithy/is-array-buffer": "^2.2.0", 123 | "tslib": "^2.6.2" 124 | }, 125 | "engines": { 126 | "node": ">=14.0.0" 127 | } 128 | }, 129 | "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { 130 | "version": "2.3.0", 131 | "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", 132 | "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", 133 | "license": "Apache-2.0", 134 | "dependencies": { 135 | "@smithy/util-buffer-from": "^2.2.0", 136 | "tslib": "^2.6.2" 137 | }, 138 | "engines": { 139 | "node": ">=14.0.0" 140 | } 141 | }, 142 | "node_modules/@aws-sdk/client-iot": { 143 | "version": "3.709.0", 144 | "resolved": "https://registry.npmjs.org/@aws-sdk/client-iot/-/client-iot-3.709.0.tgz", 145 | "integrity": "sha512-kGwf86ih+yflbJSJoS+0ZzI6oRhWzeXydlkytrLVFu0deHDa4txUJJt406VoIAJNyD1JLHbn7xDmCxzc+MOWlw==", 146 | "license": "Apache-2.0", 147 | "dependencies": { 148 | "@aws-crypto/sha256-browser": "5.2.0", 149 | "@aws-crypto/sha256-js": "5.2.0", 150 | "@aws-sdk/client-sso-oidc": "3.709.0", 151 | "@aws-sdk/client-sts": "3.709.0", 152 | "@aws-sdk/core": "3.709.0", 153 | "@aws-sdk/credential-provider-node": "3.709.0", 154 | "@aws-sdk/middleware-host-header": "3.709.0", 155 | "@aws-sdk/middleware-logger": "3.709.0", 156 | "@aws-sdk/middleware-recursion-detection": "3.709.0", 157 | "@aws-sdk/middleware-user-agent": "3.709.0", 158 | "@aws-sdk/region-config-resolver": "3.709.0", 159 | "@aws-sdk/types": "3.709.0", 160 | "@aws-sdk/util-endpoints": "3.709.0", 161 | "@aws-sdk/util-user-agent-browser": "3.709.0", 162 | "@aws-sdk/util-user-agent-node": "3.709.0", 163 | "@smithy/config-resolver": "^3.0.13", 164 | "@smithy/core": "^2.5.5", 165 | "@smithy/fetch-http-handler": "^4.1.2", 166 | "@smithy/hash-node": "^3.0.11", 167 | "@smithy/invalid-dependency": "^3.0.11", 168 | "@smithy/middleware-content-length": "^3.0.13", 169 | "@smithy/middleware-endpoint": "^3.2.5", 170 | "@smithy/middleware-retry": "^3.0.30", 171 | "@smithy/middleware-serde": "^3.0.11", 172 | "@smithy/middleware-stack": "^3.0.11", 173 | "@smithy/node-config-provider": "^3.1.12", 174 | "@smithy/node-http-handler": "^3.3.2", 175 | "@smithy/protocol-http": "^4.1.8", 176 | "@smithy/smithy-client": "^3.5.0", 177 | "@smithy/types": "^3.7.2", 178 | "@smithy/url-parser": "^3.0.11", 179 | "@smithy/util-base64": "^3.0.0", 180 | "@smithy/util-body-length-browser": "^3.0.0", 181 | "@smithy/util-body-length-node": "^3.0.0", 182 | "@smithy/util-defaults-mode-browser": "^3.0.30", 183 | "@smithy/util-defaults-mode-node": "^3.0.30", 184 | "@smithy/util-endpoints": "^2.1.7", 185 | "@smithy/util-middleware": "^3.0.11", 186 | "@smithy/util-retry": "^3.0.11", 187 | "@smithy/util-utf8": "^3.0.0", 188 | "@types/uuid": "^9.0.1", 189 | "tslib": "^2.6.2", 190 | "uuid": "^9.0.1" 191 | }, 192 | "engines": { 193 | "node": ">=16.0.0" 194 | } 195 | }, 196 | "node_modules/@aws-sdk/client-sso": { 197 | "version": "3.709.0", 198 | "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.709.0.tgz", 199 | "integrity": "sha512-Qxeo8cN0jNy6Wnbqq4wucffAGJM6sJjofoTgNtPA6cC7sPYx7aYC6OAAAo6NaMRY+WywOKdS9Wgjx2QYRxKx7w==", 200 | "license": "Apache-2.0", 201 | "dependencies": { 202 | "@aws-crypto/sha256-browser": "5.2.0", 203 | "@aws-crypto/sha256-js": "5.2.0", 204 | "@aws-sdk/core": "3.709.0", 205 | "@aws-sdk/middleware-host-header": "3.709.0", 206 | "@aws-sdk/middleware-logger": "3.709.0", 207 | "@aws-sdk/middleware-recursion-detection": "3.709.0", 208 | "@aws-sdk/middleware-user-agent": "3.709.0", 209 | "@aws-sdk/region-config-resolver": "3.709.0", 210 | "@aws-sdk/types": "3.709.0", 211 | "@aws-sdk/util-endpoints": "3.709.0", 212 | "@aws-sdk/util-user-agent-browser": "3.709.0", 213 | "@aws-sdk/util-user-agent-node": "3.709.0", 214 | "@smithy/config-resolver": "^3.0.13", 215 | "@smithy/core": "^2.5.5", 216 | "@smithy/fetch-http-handler": "^4.1.2", 217 | "@smithy/hash-node": "^3.0.11", 218 | "@smithy/invalid-dependency": "^3.0.11", 219 | "@smithy/middleware-content-length": "^3.0.13", 220 | "@smithy/middleware-endpoint": "^3.2.5", 221 | "@smithy/middleware-retry": "^3.0.30", 222 | "@smithy/middleware-serde": "^3.0.11", 223 | "@smithy/middleware-stack": "^3.0.11", 224 | "@smithy/node-config-provider": "^3.1.12", 225 | "@smithy/node-http-handler": "^3.3.2", 226 | "@smithy/protocol-http": "^4.1.8", 227 | "@smithy/smithy-client": "^3.5.0", 228 | "@smithy/types": "^3.7.2", 229 | "@smithy/url-parser": "^3.0.11", 230 | "@smithy/util-base64": "^3.0.0", 231 | "@smithy/util-body-length-browser": "^3.0.0", 232 | "@smithy/util-body-length-node": "^3.0.0", 233 | "@smithy/util-defaults-mode-browser": "^3.0.30", 234 | "@smithy/util-defaults-mode-node": "^3.0.30", 235 | "@smithy/util-endpoints": "^2.1.7", 236 | "@smithy/util-middleware": "^3.0.11", 237 | "@smithy/util-retry": "^3.0.11", 238 | "@smithy/util-utf8": "^3.0.0", 239 | "tslib": "^2.6.2" 240 | }, 241 | "engines": { 242 | "node": ">=16.0.0" 243 | } 244 | }, 245 | "node_modules/@aws-sdk/client-sso-oidc": { 246 | "version": "3.709.0", 247 | "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.709.0.tgz", 248 | "integrity": "sha512-1w6egz17QQy661lNCRmZZlqIANEbD6g2VFAQIJbVwSiu7brg+GUns+mT1eLLLHAMQc1sL0Ds8/ybSK2SrgGgIA==", 249 | "license": "Apache-2.0", 250 | "dependencies": { 251 | "@aws-crypto/sha256-browser": "5.2.0", 252 | "@aws-crypto/sha256-js": "5.2.0", 253 | "@aws-sdk/core": "3.709.0", 254 | "@aws-sdk/credential-provider-node": "3.709.0", 255 | "@aws-sdk/middleware-host-header": "3.709.0", 256 | "@aws-sdk/middleware-logger": "3.709.0", 257 | "@aws-sdk/middleware-recursion-detection": "3.709.0", 258 | "@aws-sdk/middleware-user-agent": "3.709.0", 259 | "@aws-sdk/region-config-resolver": "3.709.0", 260 | "@aws-sdk/types": "3.709.0", 261 | "@aws-sdk/util-endpoints": "3.709.0", 262 | "@aws-sdk/util-user-agent-browser": "3.709.0", 263 | "@aws-sdk/util-user-agent-node": "3.709.0", 264 | "@smithy/config-resolver": "^3.0.13", 265 | "@smithy/core": "^2.5.5", 266 | "@smithy/fetch-http-handler": "^4.1.2", 267 | "@smithy/hash-node": "^3.0.11", 268 | "@smithy/invalid-dependency": "^3.0.11", 269 | "@smithy/middleware-content-length": "^3.0.13", 270 | "@smithy/middleware-endpoint": "^3.2.5", 271 | "@smithy/middleware-retry": "^3.0.30", 272 | "@smithy/middleware-serde": "^3.0.11", 273 | "@smithy/middleware-stack": "^3.0.11", 274 | "@smithy/node-config-provider": "^3.1.12", 275 | "@smithy/node-http-handler": "^3.3.2", 276 | "@smithy/protocol-http": "^4.1.8", 277 | "@smithy/smithy-client": "^3.5.0", 278 | "@smithy/types": "^3.7.2", 279 | "@smithy/url-parser": "^3.0.11", 280 | "@smithy/util-base64": "^3.0.0", 281 | "@smithy/util-body-length-browser": "^3.0.0", 282 | "@smithy/util-body-length-node": "^3.0.0", 283 | "@smithy/util-defaults-mode-browser": "^3.0.30", 284 | "@smithy/util-defaults-mode-node": "^3.0.30", 285 | "@smithy/util-endpoints": "^2.1.7", 286 | "@smithy/util-middleware": "^3.0.11", 287 | "@smithy/util-retry": "^3.0.11", 288 | "@smithy/util-utf8": "^3.0.0", 289 | "tslib": "^2.6.2" 290 | }, 291 | "engines": { 292 | "node": ">=16.0.0" 293 | }, 294 | "peerDependencies": { 295 | "@aws-sdk/client-sts": "^3.709.0" 296 | } 297 | }, 298 | "node_modules/@aws-sdk/client-sts": { 299 | "version": "3.709.0", 300 | "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.709.0.tgz", 301 | "integrity": "sha512-cBAvlPg6yslXNL385UUGFPw+XY+lA9BzioNdIFkMo3fEUlTShogTtiWz4LsyLHoN6LhKojssP9DSmmWKWjCZIw==", 302 | "license": "Apache-2.0", 303 | "dependencies": { 304 | "@aws-crypto/sha256-browser": "5.2.0", 305 | "@aws-crypto/sha256-js": "5.2.0", 306 | "@aws-sdk/client-sso-oidc": "3.709.0", 307 | "@aws-sdk/core": "3.709.0", 308 | "@aws-sdk/credential-provider-node": "3.709.0", 309 | "@aws-sdk/middleware-host-header": "3.709.0", 310 | "@aws-sdk/middleware-logger": "3.709.0", 311 | "@aws-sdk/middleware-recursion-detection": "3.709.0", 312 | "@aws-sdk/middleware-user-agent": "3.709.0", 313 | "@aws-sdk/region-config-resolver": "3.709.0", 314 | "@aws-sdk/types": "3.709.0", 315 | "@aws-sdk/util-endpoints": "3.709.0", 316 | "@aws-sdk/util-user-agent-browser": "3.709.0", 317 | "@aws-sdk/util-user-agent-node": "3.709.0", 318 | "@smithy/config-resolver": "^3.0.13", 319 | "@smithy/core": "^2.5.5", 320 | "@smithy/fetch-http-handler": "^4.1.2", 321 | "@smithy/hash-node": "^3.0.11", 322 | "@smithy/invalid-dependency": "^3.0.11", 323 | "@smithy/middleware-content-length": "^3.0.13", 324 | "@smithy/middleware-endpoint": "^3.2.5", 325 | "@smithy/middleware-retry": "^3.0.30", 326 | "@smithy/middleware-serde": "^3.0.11", 327 | "@smithy/middleware-stack": "^3.0.11", 328 | "@smithy/node-config-provider": "^3.1.12", 329 | "@smithy/node-http-handler": "^3.3.2", 330 | "@smithy/protocol-http": "^4.1.8", 331 | "@smithy/smithy-client": "^3.5.0", 332 | "@smithy/types": "^3.7.2", 333 | "@smithy/url-parser": "^3.0.11", 334 | "@smithy/util-base64": "^3.0.0", 335 | "@smithy/util-body-length-browser": "^3.0.0", 336 | "@smithy/util-body-length-node": "^3.0.0", 337 | "@smithy/util-defaults-mode-browser": "^3.0.30", 338 | "@smithy/util-defaults-mode-node": "^3.0.30", 339 | "@smithy/util-endpoints": "^2.1.7", 340 | "@smithy/util-middleware": "^3.0.11", 341 | "@smithy/util-retry": "^3.0.11", 342 | "@smithy/util-utf8": "^3.0.0", 343 | "tslib": "^2.6.2" 344 | }, 345 | "engines": { 346 | "node": ">=16.0.0" 347 | } 348 | }, 349 | "node_modules/@aws-sdk/core": { 350 | "version": "3.709.0", 351 | "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.709.0.tgz", 352 | "integrity": "sha512-7kuSpzdOTAE026j85wq/fN9UDZ70n0OHw81vFqMWwlEFtm5IQ/MRCLKcC4HkXxTdfy1PqFlmoXxWqeBa15tujw==", 353 | "license": "Apache-2.0", 354 | "dependencies": { 355 | "@aws-sdk/types": "3.709.0", 356 | "@smithy/core": "^2.5.5", 357 | "@smithy/node-config-provider": "^3.1.12", 358 | "@smithy/property-provider": "^3.1.11", 359 | "@smithy/protocol-http": "^4.1.8", 360 | "@smithy/signature-v4": "^4.2.4", 361 | "@smithy/smithy-client": "^3.5.0", 362 | "@smithy/types": "^3.7.2", 363 | "@smithy/util-middleware": "^3.0.11", 364 | "fast-xml-parser": "4.4.1", 365 | "tslib": "^2.6.2" 366 | }, 367 | "engines": { 368 | "node": ">=16.0.0" 369 | } 370 | }, 371 | "node_modules/@aws-sdk/credential-provider-env": { 372 | "version": "3.709.0", 373 | "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.709.0.tgz", 374 | "integrity": "sha512-ZMAp9LSikvHDFVa84dKpQmow6wsg956Um20cKuioPpX2GGreJFur7oduD+tRJT6FtIOHn+64YH+0MwiXLhsaIQ==", 375 | "license": "Apache-2.0", 376 | "dependencies": { 377 | "@aws-sdk/core": "3.709.0", 378 | "@aws-sdk/types": "3.709.0", 379 | "@smithy/property-provider": "^3.1.11", 380 | "@smithy/types": "^3.7.2", 381 | "tslib": "^2.6.2" 382 | }, 383 | "engines": { 384 | "node": ">=16.0.0" 385 | } 386 | }, 387 | "node_modules/@aws-sdk/credential-provider-http": { 388 | "version": "3.709.0", 389 | "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.709.0.tgz", 390 | "integrity": "sha512-lIS7XLwCOyJnLD70f+VIRr8DNV1HPQe9oN6aguYrhoczqz7vDiVZLe3lh714cJqq9rdxzFypK5DqKHmcscMEPQ==", 391 | "license": "Apache-2.0", 392 | "dependencies": { 393 | "@aws-sdk/core": "3.709.0", 394 | "@aws-sdk/types": "3.709.0", 395 | "@smithy/fetch-http-handler": "^4.1.2", 396 | "@smithy/node-http-handler": "^3.3.2", 397 | "@smithy/property-provider": "^3.1.11", 398 | "@smithy/protocol-http": "^4.1.8", 399 | "@smithy/smithy-client": "^3.5.0", 400 | "@smithy/types": "^3.7.2", 401 | "@smithy/util-stream": "^3.3.2", 402 | "tslib": "^2.6.2" 403 | }, 404 | "engines": { 405 | "node": ">=16.0.0" 406 | } 407 | }, 408 | "node_modules/@aws-sdk/credential-provider-ini": { 409 | "version": "3.709.0", 410 | "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.709.0.tgz", 411 | "integrity": "sha512-qCF8IIGcPoUp+Ib3ANhbF5gElxFd+kIrtv2/1tKdvhudMANstQbMiWV0LTH47ZZR6c3as4iSrm09NZnpEoD/pA==", 412 | "license": "Apache-2.0", 413 | "dependencies": { 414 | "@aws-sdk/core": "3.709.0", 415 | "@aws-sdk/credential-provider-env": "3.709.0", 416 | "@aws-sdk/credential-provider-http": "3.709.0", 417 | "@aws-sdk/credential-provider-process": "3.709.0", 418 | "@aws-sdk/credential-provider-sso": "3.709.0", 419 | "@aws-sdk/credential-provider-web-identity": "3.709.0", 420 | "@aws-sdk/types": "3.709.0", 421 | "@smithy/credential-provider-imds": "^3.2.8", 422 | "@smithy/property-provider": "^3.1.11", 423 | "@smithy/shared-ini-file-loader": "^3.1.12", 424 | "@smithy/types": "^3.7.2", 425 | "tslib": "^2.6.2" 426 | }, 427 | "engines": { 428 | "node": ">=16.0.0" 429 | }, 430 | "peerDependencies": { 431 | "@aws-sdk/client-sts": "^3.709.0" 432 | } 433 | }, 434 | "node_modules/@aws-sdk/credential-provider-node": { 435 | "version": "3.709.0", 436 | "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.709.0.tgz", 437 | "integrity": "sha512-4HRX9KYWPSjO5O/Vg03YAsebKpvTjTvpK1n7zHYBmlLMBLxUrVsL1nNKKC5p2/7OW3RL8XR1ki3QkoV7kGRxUQ==", 438 | "license": "Apache-2.0", 439 | "dependencies": { 440 | "@aws-sdk/credential-provider-env": "3.709.0", 441 | "@aws-sdk/credential-provider-http": "3.709.0", 442 | "@aws-sdk/credential-provider-ini": "3.709.0", 443 | "@aws-sdk/credential-provider-process": "3.709.0", 444 | "@aws-sdk/credential-provider-sso": "3.709.0", 445 | "@aws-sdk/credential-provider-web-identity": "3.709.0", 446 | "@aws-sdk/types": "3.709.0", 447 | "@smithy/credential-provider-imds": "^3.2.8", 448 | "@smithy/property-provider": "^3.1.11", 449 | "@smithy/shared-ini-file-loader": "^3.1.12", 450 | "@smithy/types": "^3.7.2", 451 | "tslib": "^2.6.2" 452 | }, 453 | "engines": { 454 | "node": ">=16.0.0" 455 | } 456 | }, 457 | "node_modules/@aws-sdk/credential-provider-process": { 458 | "version": "3.709.0", 459 | "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.709.0.tgz", 460 | "integrity": "sha512-IAC+jPlGQII6jhIylHOwh3RgSobqlgL59nw2qYTURr8hMCI0Z1p5y2ee646HTVt4WeCYyzUAXfxr6YI/Vitv+Q==", 461 | "license": "Apache-2.0", 462 | "dependencies": { 463 | "@aws-sdk/core": "3.709.0", 464 | "@aws-sdk/types": "3.709.0", 465 | "@smithy/property-provider": "^3.1.11", 466 | "@smithy/shared-ini-file-loader": "^3.1.12", 467 | "@smithy/types": "^3.7.2", 468 | "tslib": "^2.6.2" 469 | }, 470 | "engines": { 471 | "node": ">=16.0.0" 472 | } 473 | }, 474 | "node_modules/@aws-sdk/credential-provider-sso": { 475 | "version": "3.709.0", 476 | "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.709.0.tgz", 477 | "integrity": "sha512-rYdTDOxazS2GdGScelsRK5CAkktRLCCdRjlwXaxrcW57j749hEqxcF5uTv9RD6WBwInfedcSywErNZB+hylQlg==", 478 | "license": "Apache-2.0", 479 | "dependencies": { 480 | "@aws-sdk/client-sso": "3.709.0", 481 | "@aws-sdk/core": "3.709.0", 482 | "@aws-sdk/token-providers": "3.709.0", 483 | "@aws-sdk/types": "3.709.0", 484 | "@smithy/property-provider": "^3.1.11", 485 | "@smithy/shared-ini-file-loader": "^3.1.12", 486 | "@smithy/types": "^3.7.2", 487 | "tslib": "^2.6.2" 488 | }, 489 | "engines": { 490 | "node": ">=16.0.0" 491 | } 492 | }, 493 | "node_modules/@aws-sdk/credential-provider-web-identity": { 494 | "version": "3.709.0", 495 | "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.709.0.tgz", 496 | "integrity": "sha512-2lbDfE0IQ6gma/7BB2JpkjW5G0wGe4AS0x80oybYAYYviJmUtIR3Cn2pXun6bnAWElt4wYKl4su7oC36rs5rNA==", 497 | "license": "Apache-2.0", 498 | "dependencies": { 499 | "@aws-sdk/core": "3.709.0", 500 | "@aws-sdk/types": "3.709.0", 501 | "@smithy/property-provider": "^3.1.11", 502 | "@smithy/types": "^3.7.2", 503 | "tslib": "^2.6.2" 504 | }, 505 | "engines": { 506 | "node": ">=16.0.0" 507 | }, 508 | "peerDependencies": { 509 | "@aws-sdk/client-sts": "^3.709.0" 510 | } 511 | }, 512 | "node_modules/@aws-sdk/middleware-host-header": { 513 | "version": "3.709.0", 514 | "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.709.0.tgz", 515 | "integrity": "sha512-8gQYCYAaIw4lOCd5WYdf15Y/61MgRsAnrb2eiTl+icMlUOOzl8aOl5iDwm/Idp0oHZTflwxM4XSvGXO83PRWcw==", 516 | "license": "Apache-2.0", 517 | "dependencies": { 518 | "@aws-sdk/types": "3.709.0", 519 | "@smithy/protocol-http": "^4.1.8", 520 | "@smithy/types": "^3.7.2", 521 | "tslib": "^2.6.2" 522 | }, 523 | "engines": { 524 | "node": ">=16.0.0" 525 | } 526 | }, 527 | "node_modules/@aws-sdk/middleware-logger": { 528 | "version": "3.709.0", 529 | "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.709.0.tgz", 530 | "integrity": "sha512-jDoGSccXv9zebnpUoisjWd5u5ZPIalrmm6TjvPzZ8UqzQt3Beiz0tnQwmxQD6KRc7ADweWP5Ntiqzbw9xpVajg==", 531 | "license": "Apache-2.0", 532 | "dependencies": { 533 | "@aws-sdk/types": "3.709.0", 534 | "@smithy/types": "^3.7.2", 535 | "tslib": "^2.6.2" 536 | }, 537 | "engines": { 538 | "node": ">=16.0.0" 539 | } 540 | }, 541 | "node_modules/@aws-sdk/middleware-recursion-detection": { 542 | "version": "3.709.0", 543 | "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.709.0.tgz", 544 | "integrity": "sha512-PObL/wLr4lkfbQ0yXUWaoCWu/jcwfwZzCjsUiXW/H6hW9b/00enZxmx7OhtJYaR6xmh/Lcx5wbhIoDCbzdv0tw==", 545 | "license": "Apache-2.0", 546 | "dependencies": { 547 | "@aws-sdk/types": "3.709.0", 548 | "@smithy/protocol-http": "^4.1.8", 549 | "@smithy/types": "^3.7.2", 550 | "tslib": "^2.6.2" 551 | }, 552 | "engines": { 553 | "node": ">=16.0.0" 554 | } 555 | }, 556 | "node_modules/@aws-sdk/middleware-user-agent": { 557 | "version": "3.709.0", 558 | "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.709.0.tgz", 559 | "integrity": "sha512-ooc9ZJvgkjPhi9q05XwSfNTXkEBEIfL4hleo5rQBKwHG3aTHvwOM7LLzhdX56QZVa6sorPBp6fwULuRDSqiQHw==", 560 | "license": "Apache-2.0", 561 | "dependencies": { 562 | "@aws-sdk/core": "3.709.0", 563 | "@aws-sdk/types": "3.709.0", 564 | "@aws-sdk/util-endpoints": "3.709.0", 565 | "@smithy/core": "^2.5.5", 566 | "@smithy/protocol-http": "^4.1.8", 567 | "@smithy/types": "^3.7.2", 568 | "tslib": "^2.6.2" 569 | }, 570 | "engines": { 571 | "node": ">=16.0.0" 572 | } 573 | }, 574 | "node_modules/@aws-sdk/region-config-resolver": { 575 | "version": "3.709.0", 576 | "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.709.0.tgz", 577 | "integrity": "sha512-/NoCAMEVKAg3kBKOrNtgOfL+ECt6nrl+L7q2SyYmrcY4tVCmwuECVqewQaHc03fTnJijfKLccw0Fj+6wOCnB6w==", 578 | "license": "Apache-2.0", 579 | "dependencies": { 580 | "@aws-sdk/types": "3.709.0", 581 | "@smithy/node-config-provider": "^3.1.12", 582 | "@smithy/types": "^3.7.2", 583 | "@smithy/util-config-provider": "^3.0.0", 584 | "@smithy/util-middleware": "^3.0.11", 585 | "tslib": "^2.6.2" 586 | }, 587 | "engines": { 588 | "node": ">=16.0.0" 589 | } 590 | }, 591 | "node_modules/@aws-sdk/token-providers": { 592 | "version": "3.709.0", 593 | "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.709.0.tgz", 594 | "integrity": "sha512-q5Ar6k71nci43IbULFgC8a89d/3EHpmd7HvBzqVGRcHnoPwh8eZDBfbBXKH83NGwcS1qPSRYiDbVfeWPm4/1jA==", 595 | "license": "Apache-2.0", 596 | "dependencies": { 597 | "@aws-sdk/types": "3.709.0", 598 | "@smithy/property-provider": "^3.1.11", 599 | "@smithy/shared-ini-file-loader": "^3.1.12", 600 | "@smithy/types": "^3.7.2", 601 | "tslib": "^2.6.2" 602 | }, 603 | "engines": { 604 | "node": ">=16.0.0" 605 | }, 606 | "peerDependencies": { 607 | "@aws-sdk/client-sso-oidc": "^3.709.0" 608 | } 609 | }, 610 | "node_modules/@aws-sdk/types": { 611 | "version": "3.709.0", 612 | "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.709.0.tgz", 613 | "integrity": "sha512-ArtLTMxgjf13Kfu3gWH3Ez9Q5TkDdcRZUofpKH3pMGB/C6KAbeSCtIIDKfoRTUABzyGlPyCrZdnFjKyH+ypIpg==", 614 | "license": "Apache-2.0", 615 | "dependencies": { 616 | "@smithy/types": "^3.7.2", 617 | "tslib": "^2.6.2" 618 | }, 619 | "engines": { 620 | "node": ">=16.0.0" 621 | } 622 | }, 623 | "node_modules/@aws-sdk/util-endpoints": { 624 | "version": "3.709.0", 625 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.709.0.tgz", 626 | "integrity": "sha512-Mbc7AtL5WGCTKC16IGeUTz+sjpC3ptBda2t0CcK0kMVw3THDdcSq6ZlNKO747cNqdbwUvW34oHteUiHv4/z88Q==", 627 | "license": "Apache-2.0", 628 | "dependencies": { 629 | "@aws-sdk/types": "3.709.0", 630 | "@smithy/types": "^3.7.2", 631 | "@smithy/util-endpoints": "^2.1.7", 632 | "tslib": "^2.6.2" 633 | }, 634 | "engines": { 635 | "node": ">=16.0.0" 636 | } 637 | }, 638 | "node_modules/@aws-sdk/util-locate-window": { 639 | "version": "3.693.0", 640 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.693.0.tgz", 641 | "integrity": "sha512-ttrag6haJLWABhLqtg1Uf+4LgHWIMOVSYL+VYZmAp2v4PUGOwWmWQH0Zk8RM7YuQcLfH/EoR72/Yxz6A4FKcuw==", 642 | "license": "Apache-2.0", 643 | "dependencies": { 644 | "tslib": "^2.6.2" 645 | }, 646 | "engines": { 647 | "node": ">=16.0.0" 648 | } 649 | }, 650 | "node_modules/@aws-sdk/util-user-agent-browser": { 651 | "version": "3.709.0", 652 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.709.0.tgz", 653 | "integrity": "sha512-/rL2GasJzdTWUURCQKFldw2wqBtY4k4kCiA2tVZSKg3y4Ey7zO34SW8ebaeCE2/xoWOyLR2/etdKyphoo4Zrtg==", 654 | "license": "Apache-2.0", 655 | "dependencies": { 656 | "@aws-sdk/types": "3.709.0", 657 | "@smithy/types": "^3.7.2", 658 | "bowser": "^2.11.0", 659 | "tslib": "^2.6.2" 660 | } 661 | }, 662 | "node_modules/@aws-sdk/util-user-agent-node": { 663 | "version": "3.709.0", 664 | "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.709.0.tgz", 665 | "integrity": "sha512-trBfzSCVWy7ILgqhEXgiuM7hfRCw4F4a8IK90tjk9YL0jgoJ6eJuOp7+DfCtHJaygoBxD3cdMFkOu+lluFmGBA==", 666 | "license": "Apache-2.0", 667 | "dependencies": { 668 | "@aws-sdk/middleware-user-agent": "3.709.0", 669 | "@aws-sdk/types": "3.709.0", 670 | "@smithy/node-config-provider": "^3.1.12", 671 | "@smithy/types": "^3.7.2", 672 | "tslib": "^2.6.2" 673 | }, 674 | "engines": { 675 | "node": ">=16.0.0" 676 | }, 677 | "peerDependencies": { 678 | "aws-crt": ">=1.0.0" 679 | }, 680 | "peerDependenciesMeta": { 681 | "aws-crt": { 682 | "optional": true 683 | } 684 | } 685 | }, 686 | "node_modules/@httptoolkit/websocket-stream": { 687 | "version": "6.0.1", 688 | "resolved": "https://registry.npmjs.org/@httptoolkit/websocket-stream/-/websocket-stream-6.0.1.tgz", 689 | "integrity": "sha512-A0NOZI+Glp3Xgcz6Na7i7o09+/+xm2m0UCU8gdtM2nIv6/cjLmhMZMqehSpTlgbx9omtLmV8LVqOskPEyWnmZQ==", 690 | "license": "BSD-2-Clause", 691 | "dependencies": { 692 | "@types/ws": "*", 693 | "duplexify": "^3.5.1", 694 | "inherits": "^2.0.1", 695 | "isomorphic-ws": "^4.0.1", 696 | "readable-stream": "^2.3.3", 697 | "safe-buffer": "^5.1.2", 698 | "ws": "*", 699 | "xtend": "^4.0.0" 700 | } 701 | }, 702 | "node_modules/@httptoolkit/websocket-stream/node_modules/duplexify": { 703 | "version": "3.7.1", 704 | "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", 705 | "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", 706 | "license": "MIT", 707 | "dependencies": { 708 | "end-of-stream": "^1.0.0", 709 | "inherits": "^2.0.1", 710 | "readable-stream": "^2.0.0", 711 | "stream-shift": "^1.0.0" 712 | } 713 | }, 714 | "node_modules/@httptoolkit/websocket-stream/node_modules/readable-stream": { 715 | "version": "2.3.8", 716 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", 717 | "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", 718 | "license": "MIT", 719 | "dependencies": { 720 | "core-util-is": "~1.0.0", 721 | "inherits": "~2.0.3", 722 | "isarray": "~1.0.0", 723 | "process-nextick-args": "~2.0.0", 724 | "safe-buffer": "~5.1.1", 725 | "string_decoder": "~1.1.1", 726 | "util-deprecate": "~1.0.1" 727 | } 728 | }, 729 | "node_modules/@httptoolkit/websocket-stream/node_modules/safe-buffer": { 730 | "version": "5.1.2", 731 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 732 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 733 | "license": "MIT" 734 | }, 735 | "node_modules/@httptoolkit/websocket-stream/node_modules/string_decoder": { 736 | "version": "1.1.1", 737 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 738 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 739 | "license": "MIT", 740 | "dependencies": { 741 | "safe-buffer": "~5.1.0" 742 | } 743 | }, 744 | "node_modules/@smithy/abort-controller": { 745 | "version": "3.1.9", 746 | "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.9.tgz", 747 | "integrity": "sha512-yiW0WI30zj8ZKoSYNx90no7ugVn3khlyH/z5W8qtKBtVE6awRALbhSG+2SAHA1r6bO/6M9utxYKVZ3PCJ1rWxw==", 748 | "license": "Apache-2.0", 749 | "dependencies": { 750 | "@smithy/types": "^3.7.2", 751 | "tslib": "^2.6.2" 752 | }, 753 | "engines": { 754 | "node": ">=16.0.0" 755 | } 756 | }, 757 | "node_modules/@smithy/config-resolver": { 758 | "version": "3.0.13", 759 | "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.13.tgz", 760 | "integrity": "sha512-Gr/qwzyPaTL1tZcq8WQyHhTZREER5R1Wytmz4WnVGL4onA3dNk6Btll55c8Vr58pLdvWZmtG8oZxJTw3t3q7Jg==", 761 | "license": "Apache-2.0", 762 | "dependencies": { 763 | "@smithy/node-config-provider": "^3.1.12", 764 | "@smithy/types": "^3.7.2", 765 | "@smithy/util-config-provider": "^3.0.0", 766 | "@smithy/util-middleware": "^3.0.11", 767 | "tslib": "^2.6.2" 768 | }, 769 | "engines": { 770 | "node": ">=16.0.0" 771 | } 772 | }, 773 | "node_modules/@smithy/core": { 774 | "version": "2.5.5", 775 | "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.5.tgz", 776 | "integrity": "sha512-G8G/sDDhXA7o0bOvkc7bgai6POuSld/+XhNnWAbpQTpLv2OZPvyqQ58tLPPlz0bSNsXktldDDREIv1LczFeNEw==", 777 | "license": "Apache-2.0", 778 | "dependencies": { 779 | "@smithy/middleware-serde": "^3.0.11", 780 | "@smithy/protocol-http": "^4.1.8", 781 | "@smithy/types": "^3.7.2", 782 | "@smithy/util-body-length-browser": "^3.0.0", 783 | "@smithy/util-middleware": "^3.0.11", 784 | "@smithy/util-stream": "^3.3.2", 785 | "@smithy/util-utf8": "^3.0.0", 786 | "tslib": "^2.6.2" 787 | }, 788 | "engines": { 789 | "node": ">=16.0.0" 790 | } 791 | }, 792 | "node_modules/@smithy/credential-provider-imds": { 793 | "version": "3.2.8", 794 | "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.8.tgz", 795 | "integrity": "sha512-ZCY2yD0BY+K9iMXkkbnjo+08T2h8/34oHd0Jmh6BZUSZwaaGlGCyBT/3wnS7u7Xl33/EEfN4B6nQr3Gx5bYxgw==", 796 | "license": "Apache-2.0", 797 | "dependencies": { 798 | "@smithy/node-config-provider": "^3.1.12", 799 | "@smithy/property-provider": "^3.1.11", 800 | "@smithy/types": "^3.7.2", 801 | "@smithy/url-parser": "^3.0.11", 802 | "tslib": "^2.6.2" 803 | }, 804 | "engines": { 805 | "node": ">=16.0.0" 806 | } 807 | }, 808 | "node_modules/@smithy/fetch-http-handler": { 809 | "version": "4.1.2", 810 | "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.2.tgz", 811 | "integrity": "sha512-R7rU7Ae3ItU4rC0c5mB2sP5mJNbCfoDc8I5XlYjIZnquyUwec7fEo78F6DA3SmgJgkU1qTMcZJuGblxZsl10ZA==", 812 | "license": "Apache-2.0", 813 | "dependencies": { 814 | "@smithy/protocol-http": "^4.1.8", 815 | "@smithy/querystring-builder": "^3.0.11", 816 | "@smithy/types": "^3.7.2", 817 | "@smithy/util-base64": "^3.0.0", 818 | "tslib": "^2.6.2" 819 | } 820 | }, 821 | "node_modules/@smithy/hash-node": { 822 | "version": "3.0.11", 823 | "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.11.tgz", 824 | "integrity": "sha512-emP23rwYyZhQBvklqTtwetkQlqbNYirDiEEwXl2v0GYWMnCzxst7ZaRAnWuy28njp5kAH54lvkdG37MblZzaHA==", 825 | "license": "Apache-2.0", 826 | "dependencies": { 827 | "@smithy/types": "^3.7.2", 828 | "@smithy/util-buffer-from": "^3.0.0", 829 | "@smithy/util-utf8": "^3.0.0", 830 | "tslib": "^2.6.2" 831 | }, 832 | "engines": { 833 | "node": ">=16.0.0" 834 | } 835 | }, 836 | "node_modules/@smithy/invalid-dependency": { 837 | "version": "3.0.11", 838 | "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.11.tgz", 839 | "integrity": "sha512-NuQmVPEJjUX6c+UELyVz8kUx8Q539EDeNwbRyu4IIF8MeV7hUtq1FB3SHVyki2u++5XLMFqngeMKk7ccspnNyQ==", 840 | "license": "Apache-2.0", 841 | "dependencies": { 842 | "@smithy/types": "^3.7.2", 843 | "tslib": "^2.6.2" 844 | } 845 | }, 846 | "node_modules/@smithy/is-array-buffer": { 847 | "version": "3.0.0", 848 | "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", 849 | "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", 850 | "license": "Apache-2.0", 851 | "dependencies": { 852 | "tslib": "^2.6.2" 853 | }, 854 | "engines": { 855 | "node": ">=16.0.0" 856 | } 857 | }, 858 | "node_modules/@smithy/middleware-content-length": { 859 | "version": "3.0.13", 860 | "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.13.tgz", 861 | "integrity": "sha512-zfMhzojhFpIX3P5ug7jxTjfUcIPcGjcQYzB9t+rv0g1TX7B0QdwONW+ATouaLoD7h7LOw/ZlXfkq4xJ/g2TrIw==", 862 | "license": "Apache-2.0", 863 | "dependencies": { 864 | "@smithy/protocol-http": "^4.1.8", 865 | "@smithy/types": "^3.7.2", 866 | "tslib": "^2.6.2" 867 | }, 868 | "engines": { 869 | "node": ">=16.0.0" 870 | } 871 | }, 872 | "node_modules/@smithy/middleware-endpoint": { 873 | "version": "3.2.5", 874 | "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.5.tgz", 875 | "integrity": "sha512-VhJNs/s/lyx4weiZdXSloBgoLoS8osV0dKIain8nGmx7of3QFKu5BSdEuk1z/U8x9iwes1i+XCiNusEvuK1ijg==", 876 | "license": "Apache-2.0", 877 | "dependencies": { 878 | "@smithy/core": "^2.5.5", 879 | "@smithy/middleware-serde": "^3.0.11", 880 | "@smithy/node-config-provider": "^3.1.12", 881 | "@smithy/shared-ini-file-loader": "^3.1.12", 882 | "@smithy/types": "^3.7.2", 883 | "@smithy/url-parser": "^3.0.11", 884 | "@smithy/util-middleware": "^3.0.11", 885 | "tslib": "^2.6.2" 886 | }, 887 | "engines": { 888 | "node": ">=16.0.0" 889 | } 890 | }, 891 | "node_modules/@smithy/middleware-retry": { 892 | "version": "3.0.30", 893 | "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.30.tgz", 894 | "integrity": "sha512-6323RL2BvAR3VQpTjHpa52kH/iSHyxd/G9ohb2MkBk2Ucu+oMtRXT8yi7KTSIS9nb58aupG6nO0OlXnQOAcvmQ==", 895 | "license": "Apache-2.0", 896 | "dependencies": { 897 | "@smithy/node-config-provider": "^3.1.12", 898 | "@smithy/protocol-http": "^4.1.8", 899 | "@smithy/service-error-classification": "^3.0.11", 900 | "@smithy/smithy-client": "^3.5.0", 901 | "@smithy/types": "^3.7.2", 902 | "@smithy/util-middleware": "^3.0.11", 903 | "@smithy/util-retry": "^3.0.11", 904 | "tslib": "^2.6.2", 905 | "uuid": "^9.0.1" 906 | }, 907 | "engines": { 908 | "node": ">=16.0.0" 909 | } 910 | }, 911 | "node_modules/@smithy/middleware-serde": { 912 | "version": "3.0.11", 913 | "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.11.tgz", 914 | "integrity": "sha512-KzPAeySp/fOoQA82TpnwItvX8BBURecpx6ZMu75EZDkAcnPtO6vf7q4aH5QHs/F1s3/snQaSFbbUMcFFZ086Mw==", 915 | "license": "Apache-2.0", 916 | "dependencies": { 917 | "@smithy/types": "^3.7.2", 918 | "tslib": "^2.6.2" 919 | }, 920 | "engines": { 921 | "node": ">=16.0.0" 922 | } 923 | }, 924 | "node_modules/@smithy/middleware-stack": { 925 | "version": "3.0.11", 926 | "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.11.tgz", 927 | "integrity": "sha512-1HGo9a6/ikgOMrTrWL/WiN9N8GSVYpuRQO5kjstAq4CvV59bjqnh7TbdXGQ4vxLD3xlSjfBjq5t1SOELePsLnA==", 928 | "license": "Apache-2.0", 929 | "dependencies": { 930 | "@smithy/types": "^3.7.2", 931 | "tslib": "^2.6.2" 932 | }, 933 | "engines": { 934 | "node": ">=16.0.0" 935 | } 936 | }, 937 | "node_modules/@smithy/node-config-provider": { 938 | "version": "3.1.12", 939 | "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.12.tgz", 940 | "integrity": "sha512-O9LVEu5J/u/FuNlZs+L7Ikn3lz7VB9hb0GtPT9MQeiBmtK8RSY3ULmsZgXhe6VAlgTw0YO+paQx4p8xdbs43vQ==", 941 | "license": "Apache-2.0", 942 | "dependencies": { 943 | "@smithy/property-provider": "^3.1.11", 944 | "@smithy/shared-ini-file-loader": "^3.1.12", 945 | "@smithy/types": "^3.7.2", 946 | "tslib": "^2.6.2" 947 | }, 948 | "engines": { 949 | "node": ">=16.0.0" 950 | } 951 | }, 952 | "node_modules/@smithy/node-http-handler": { 953 | "version": "3.3.2", 954 | "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.2.tgz", 955 | "integrity": "sha512-t4ng1DAd527vlxvOfKFYEe6/QFBcsj7WpNlWTyjorwXXcKw3XlltBGbyHfSJ24QT84nF+agDha9tNYpzmSRZPA==", 956 | "license": "Apache-2.0", 957 | "dependencies": { 958 | "@smithy/abort-controller": "^3.1.9", 959 | "@smithy/protocol-http": "^4.1.8", 960 | "@smithy/querystring-builder": "^3.0.11", 961 | "@smithy/types": "^3.7.2", 962 | "tslib": "^2.6.2" 963 | }, 964 | "engines": { 965 | "node": ">=16.0.0" 966 | } 967 | }, 968 | "node_modules/@smithy/property-provider": { 969 | "version": "3.1.11", 970 | "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.11.tgz", 971 | "integrity": "sha512-I/+TMc4XTQ3QAjXfOcUWbSS073oOEAxgx4aZy8jHaf8JQnRkq2SZWw8+PfDtBvLUjcGMdxl+YwtzWe6i5uhL/A==", 972 | "license": "Apache-2.0", 973 | "dependencies": { 974 | "@smithy/types": "^3.7.2", 975 | "tslib": "^2.6.2" 976 | }, 977 | "engines": { 978 | "node": ">=16.0.0" 979 | } 980 | }, 981 | "node_modules/@smithy/protocol-http": { 982 | "version": "4.1.8", 983 | "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", 984 | "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", 985 | "license": "Apache-2.0", 986 | "dependencies": { 987 | "@smithy/types": "^3.7.2", 988 | "tslib": "^2.6.2" 989 | }, 990 | "engines": { 991 | "node": ">=16.0.0" 992 | } 993 | }, 994 | "node_modules/@smithy/querystring-builder": { 995 | "version": "3.0.11", 996 | "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.11.tgz", 997 | "integrity": "sha512-u+5HV/9uJaeLj5XTb6+IEF/dokWWkEqJ0XiaRRogyREmKGUgZnNecLucADLdauWFKUNbQfulHFEZEdjwEBjXRg==", 998 | "license": "Apache-2.0", 999 | "dependencies": { 1000 | "@smithy/types": "^3.7.2", 1001 | "@smithy/util-uri-escape": "^3.0.0", 1002 | "tslib": "^2.6.2" 1003 | }, 1004 | "engines": { 1005 | "node": ">=16.0.0" 1006 | } 1007 | }, 1008 | "node_modules/@smithy/querystring-parser": { 1009 | "version": "3.0.11", 1010 | "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.11.tgz", 1011 | "integrity": "sha512-Je3kFvCsFMnso1ilPwA7GtlbPaTixa3WwC+K21kmMZHsBEOZYQaqxcMqeFFoU7/slFjKDIpiiPydvdJm8Q/MCw==", 1012 | "license": "Apache-2.0", 1013 | "dependencies": { 1014 | "@smithy/types": "^3.7.2", 1015 | "tslib": "^2.6.2" 1016 | }, 1017 | "engines": { 1018 | "node": ">=16.0.0" 1019 | } 1020 | }, 1021 | "node_modules/@smithy/service-error-classification": { 1022 | "version": "3.0.11", 1023 | "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.11.tgz", 1024 | "integrity": "sha512-QnYDPkyewrJzCyaeI2Rmp7pDwbUETe+hU8ADkXmgNusO1bgHBH7ovXJiYmba8t0fNfJx75fE8dlM6SEmZxheog==", 1025 | "license": "Apache-2.0", 1026 | "dependencies": { 1027 | "@smithy/types": "^3.7.2" 1028 | }, 1029 | "engines": { 1030 | "node": ">=16.0.0" 1031 | } 1032 | }, 1033 | "node_modules/@smithy/shared-ini-file-loader": { 1034 | "version": "3.1.12", 1035 | "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.12.tgz", 1036 | "integrity": "sha512-1xKSGI+U9KKdbG2qDvIR9dGrw3CNx+baqJfyr0igKEpjbHL5stsqAesYBzHChYHlelWtb87VnLWlhvfCz13H8Q==", 1037 | "license": "Apache-2.0", 1038 | "dependencies": { 1039 | "@smithy/types": "^3.7.2", 1040 | "tslib": "^2.6.2" 1041 | }, 1042 | "engines": { 1043 | "node": ">=16.0.0" 1044 | } 1045 | }, 1046 | "node_modules/@smithy/signature-v4": { 1047 | "version": "4.2.4", 1048 | "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.4.tgz", 1049 | "integrity": "sha512-5JWeMQYg81TgU4cG+OexAWdvDTs5JDdbEZx+Qr1iPbvo91QFGzjy0IkXAKaXUHqmKUJgSHK0ZxnCkgZpzkeNTA==", 1050 | "license": "Apache-2.0", 1051 | "dependencies": { 1052 | "@smithy/is-array-buffer": "^3.0.0", 1053 | "@smithy/protocol-http": "^4.1.8", 1054 | "@smithy/types": "^3.7.2", 1055 | "@smithy/util-hex-encoding": "^3.0.0", 1056 | "@smithy/util-middleware": "^3.0.11", 1057 | "@smithy/util-uri-escape": "^3.0.0", 1058 | "@smithy/util-utf8": "^3.0.0", 1059 | "tslib": "^2.6.2" 1060 | }, 1061 | "engines": { 1062 | "node": ">=16.0.0" 1063 | } 1064 | }, 1065 | "node_modules/@smithy/smithy-client": { 1066 | "version": "3.5.0", 1067 | "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.5.0.tgz", 1068 | "integrity": "sha512-Y8FeOa7gbDfCWf7njrkoRATPa5eNLUEjlJS5z5rXatYuGkCb80LbHcu8AQR8qgAZZaNHCLyo2N+pxPsV7l+ivg==", 1069 | "license": "Apache-2.0", 1070 | "dependencies": { 1071 | "@smithy/core": "^2.5.5", 1072 | "@smithy/middleware-endpoint": "^3.2.5", 1073 | "@smithy/middleware-stack": "^3.0.11", 1074 | "@smithy/protocol-http": "^4.1.8", 1075 | "@smithy/types": "^3.7.2", 1076 | "@smithy/util-stream": "^3.3.2", 1077 | "tslib": "^2.6.2" 1078 | }, 1079 | "engines": { 1080 | "node": ">=16.0.0" 1081 | } 1082 | }, 1083 | "node_modules/@smithy/types": { 1084 | "version": "3.7.2", 1085 | "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", 1086 | "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", 1087 | "license": "Apache-2.0", 1088 | "dependencies": { 1089 | "tslib": "^2.6.2" 1090 | }, 1091 | "engines": { 1092 | "node": ">=16.0.0" 1093 | } 1094 | }, 1095 | "node_modules/@smithy/url-parser": { 1096 | "version": "3.0.11", 1097 | "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.11.tgz", 1098 | "integrity": "sha512-TmlqXkSk8ZPhfc+SQutjmFr5FjC0av3GZP4B/10caK1SbRwe/v+Wzu/R6xEKxoNqL+8nY18s1byiy6HqPG37Aw==", 1099 | "license": "Apache-2.0", 1100 | "dependencies": { 1101 | "@smithy/querystring-parser": "^3.0.11", 1102 | "@smithy/types": "^3.7.2", 1103 | "tslib": "^2.6.2" 1104 | } 1105 | }, 1106 | "node_modules/@smithy/util-base64": { 1107 | "version": "3.0.0", 1108 | "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", 1109 | "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", 1110 | "license": "Apache-2.0", 1111 | "dependencies": { 1112 | "@smithy/util-buffer-from": "^3.0.0", 1113 | "@smithy/util-utf8": "^3.0.0", 1114 | "tslib": "^2.6.2" 1115 | }, 1116 | "engines": { 1117 | "node": ">=16.0.0" 1118 | } 1119 | }, 1120 | "node_modules/@smithy/util-body-length-browser": { 1121 | "version": "3.0.0", 1122 | "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", 1123 | "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", 1124 | "license": "Apache-2.0", 1125 | "dependencies": { 1126 | "tslib": "^2.6.2" 1127 | } 1128 | }, 1129 | "node_modules/@smithy/util-body-length-node": { 1130 | "version": "3.0.0", 1131 | "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", 1132 | "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", 1133 | "license": "Apache-2.0", 1134 | "dependencies": { 1135 | "tslib": "^2.6.2" 1136 | }, 1137 | "engines": { 1138 | "node": ">=16.0.0" 1139 | } 1140 | }, 1141 | "node_modules/@smithy/util-buffer-from": { 1142 | "version": "3.0.0", 1143 | "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", 1144 | "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", 1145 | "license": "Apache-2.0", 1146 | "dependencies": { 1147 | "@smithy/is-array-buffer": "^3.0.0", 1148 | "tslib": "^2.6.2" 1149 | }, 1150 | "engines": { 1151 | "node": ">=16.0.0" 1152 | } 1153 | }, 1154 | "node_modules/@smithy/util-config-provider": { 1155 | "version": "3.0.0", 1156 | "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", 1157 | "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", 1158 | "license": "Apache-2.0", 1159 | "dependencies": { 1160 | "tslib": "^2.6.2" 1161 | }, 1162 | "engines": { 1163 | "node": ">=16.0.0" 1164 | } 1165 | }, 1166 | "node_modules/@smithy/util-defaults-mode-browser": { 1167 | "version": "3.0.30", 1168 | "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.30.tgz", 1169 | "integrity": "sha512-nLuGmgfcr0gzm64pqF2UT4SGWVG8UGviAdayDlVzJPNa6Z4lqvpDzdRXmLxtOdEjVlTOEdpZ9dd3ZMMu488mzg==", 1170 | "license": "Apache-2.0", 1171 | "dependencies": { 1172 | "@smithy/property-provider": "^3.1.11", 1173 | "@smithy/smithy-client": "^3.5.0", 1174 | "@smithy/types": "^3.7.2", 1175 | "bowser": "^2.11.0", 1176 | "tslib": "^2.6.2" 1177 | }, 1178 | "engines": { 1179 | "node": ">= 10.0.0" 1180 | } 1181 | }, 1182 | "node_modules/@smithy/util-defaults-mode-node": { 1183 | "version": "3.0.30", 1184 | "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.30.tgz", 1185 | "integrity": "sha512-OD63eWoH68vp75mYcfYyuVH+p7Li/mY4sYOROnauDrtObo1cS4uWfsy/zhOTW8F8ZPxQC1ZXZKVxoxvMGUv2Ow==", 1186 | "license": "Apache-2.0", 1187 | "dependencies": { 1188 | "@smithy/config-resolver": "^3.0.13", 1189 | "@smithy/credential-provider-imds": "^3.2.8", 1190 | "@smithy/node-config-provider": "^3.1.12", 1191 | "@smithy/property-provider": "^3.1.11", 1192 | "@smithy/smithy-client": "^3.5.0", 1193 | "@smithy/types": "^3.7.2", 1194 | "tslib": "^2.6.2" 1195 | }, 1196 | "engines": { 1197 | "node": ">= 10.0.0" 1198 | } 1199 | }, 1200 | "node_modules/@smithy/util-endpoints": { 1201 | "version": "2.1.7", 1202 | "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.7.tgz", 1203 | "integrity": "sha512-tSfcqKcN/Oo2STEYCABVuKgJ76nyyr6skGl9t15hs+YaiU06sgMkN7QYjo0BbVw+KT26zok3IzbdSOksQ4YzVw==", 1204 | "license": "Apache-2.0", 1205 | "dependencies": { 1206 | "@smithy/node-config-provider": "^3.1.12", 1207 | "@smithy/types": "^3.7.2", 1208 | "tslib": "^2.6.2" 1209 | }, 1210 | "engines": { 1211 | "node": ">=16.0.0" 1212 | } 1213 | }, 1214 | "node_modules/@smithy/util-hex-encoding": { 1215 | "version": "3.0.0", 1216 | "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", 1217 | "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", 1218 | "license": "Apache-2.0", 1219 | "dependencies": { 1220 | "tslib": "^2.6.2" 1221 | }, 1222 | "engines": { 1223 | "node": ">=16.0.0" 1224 | } 1225 | }, 1226 | "node_modules/@smithy/util-middleware": { 1227 | "version": "3.0.11", 1228 | "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.11.tgz", 1229 | "integrity": "sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow==", 1230 | "license": "Apache-2.0", 1231 | "dependencies": { 1232 | "@smithy/types": "^3.7.2", 1233 | "tslib": "^2.6.2" 1234 | }, 1235 | "engines": { 1236 | "node": ">=16.0.0" 1237 | } 1238 | }, 1239 | "node_modules/@smithy/util-retry": { 1240 | "version": "3.0.11", 1241 | "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.11.tgz", 1242 | "integrity": "sha512-hJUC6W7A3DQgaee3Hp9ZFcOxVDZzmBIRBPlUAk8/fSOEl7pE/aX7Dci0JycNOnm9Mfr0KV2XjIlUOcGWXQUdVQ==", 1243 | "license": "Apache-2.0", 1244 | "dependencies": { 1245 | "@smithy/service-error-classification": "^3.0.11", 1246 | "@smithy/types": "^3.7.2", 1247 | "tslib": "^2.6.2" 1248 | }, 1249 | "engines": { 1250 | "node": ">=16.0.0" 1251 | } 1252 | }, 1253 | "node_modules/@smithy/util-stream": { 1254 | "version": "3.3.2", 1255 | "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.3.2.tgz", 1256 | "integrity": "sha512-sInAqdiVeisUGYAv/FrXpmJ0b4WTFmciTRqzhb7wVuem9BHvhIG7tpiYHLDWrl2stOokNZpTTGqz3mzB2qFwXg==", 1257 | "license": "Apache-2.0", 1258 | "dependencies": { 1259 | "@smithy/fetch-http-handler": "^4.1.2", 1260 | "@smithy/node-http-handler": "^3.3.2", 1261 | "@smithy/types": "^3.7.2", 1262 | "@smithy/util-base64": "^3.0.0", 1263 | "@smithy/util-buffer-from": "^3.0.0", 1264 | "@smithy/util-hex-encoding": "^3.0.0", 1265 | "@smithy/util-utf8": "^3.0.0", 1266 | "tslib": "^2.6.2" 1267 | }, 1268 | "engines": { 1269 | "node": ">=16.0.0" 1270 | } 1271 | }, 1272 | "node_modules/@smithy/util-uri-escape": { 1273 | "version": "3.0.0", 1274 | "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", 1275 | "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", 1276 | "license": "Apache-2.0", 1277 | "dependencies": { 1278 | "tslib": "^2.6.2" 1279 | }, 1280 | "engines": { 1281 | "node": ">=16.0.0" 1282 | } 1283 | }, 1284 | "node_modules/@smithy/util-utf8": { 1285 | "version": "3.0.0", 1286 | "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", 1287 | "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", 1288 | "license": "Apache-2.0", 1289 | "dependencies": { 1290 | "@smithy/util-buffer-from": "^3.0.0", 1291 | "tslib": "^2.6.2" 1292 | }, 1293 | "engines": { 1294 | "node": ">=16.0.0" 1295 | } 1296 | }, 1297 | "node_modules/@types/node": { 1298 | "version": "22.10.2", 1299 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", 1300 | "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", 1301 | "license": "MIT", 1302 | "dependencies": { 1303 | "undici-types": "~6.20.0" 1304 | } 1305 | }, 1306 | "node_modules/@types/uuid": { 1307 | "version": "9.0.8", 1308 | "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", 1309 | "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", 1310 | "license": "MIT" 1311 | }, 1312 | "node_modules/@types/ws": { 1313 | "version": "8.5.13", 1314 | "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", 1315 | "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", 1316 | "license": "MIT", 1317 | "dependencies": { 1318 | "@types/node": "*" 1319 | } 1320 | }, 1321 | "node_modules/argparse": { 1322 | "version": "2.0.1", 1323 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 1324 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" 1325 | }, 1326 | "node_modules/aws-iot-device-sdk": { 1327 | "version": "2.2.15", 1328 | "resolved": "https://registry.npmjs.org/aws-iot-device-sdk/-/aws-iot-device-sdk-2.2.15.tgz", 1329 | "integrity": "sha512-JEnLUx288ttfuwy+ZJA/g9vFG+M3Mucyo1LXypWC8cNV1D8hGvAxeqHygs9TiinvjUazM7gcXwSA+s3E+tbx/g==", 1330 | "license": "Apache-2.0", 1331 | "dependencies": { 1332 | "@httptoolkit/websocket-stream": "^6.0.1", 1333 | "crypto-js": "4.2.0", 1334 | "minimist": "1.2.6", 1335 | "mqtt": "4.2.8" 1336 | }, 1337 | "engines": { 1338 | "node": ">=8.17.0" 1339 | } 1340 | }, 1341 | "node_modules/balanced-match": { 1342 | "version": "1.0.2", 1343 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1344 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 1345 | }, 1346 | "node_modules/base64-js": { 1347 | "version": "1.5.1", 1348 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 1349 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 1350 | "funding": [ 1351 | { 1352 | "type": "github", 1353 | "url": "https://github.com/sponsors/feross" 1354 | }, 1355 | { 1356 | "type": "patreon", 1357 | "url": "https://www.patreon.com/feross" 1358 | }, 1359 | { 1360 | "type": "consulting", 1361 | "url": "https://feross.org/support" 1362 | } 1363 | ] 1364 | }, 1365 | "node_modules/bl": { 1366 | "version": "4.1.0", 1367 | "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", 1368 | "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", 1369 | "dependencies": { 1370 | "buffer": "^5.5.0", 1371 | "inherits": "^2.0.4", 1372 | "readable-stream": "^3.4.0" 1373 | } 1374 | }, 1375 | "node_modules/bowser": { 1376 | "version": "2.11.0", 1377 | "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", 1378 | "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", 1379 | "license": "MIT" 1380 | }, 1381 | "node_modules/brace-expansion": { 1382 | "version": "1.1.11", 1383 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 1384 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 1385 | "dependencies": { 1386 | "balanced-match": "^1.0.0", 1387 | "concat-map": "0.0.1" 1388 | } 1389 | }, 1390 | "node_modules/buffer": { 1391 | "version": "5.7.1", 1392 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 1393 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 1394 | "funding": [ 1395 | { 1396 | "type": "github", 1397 | "url": "https://github.com/sponsors/feross" 1398 | }, 1399 | { 1400 | "type": "patreon", 1401 | "url": "https://www.patreon.com/feross" 1402 | }, 1403 | { 1404 | "type": "consulting", 1405 | "url": "https://feross.org/support" 1406 | } 1407 | ], 1408 | "dependencies": { 1409 | "base64-js": "^1.3.1", 1410 | "ieee754": "^1.1.13" 1411 | } 1412 | }, 1413 | "node_modules/buffer-from": { 1414 | "version": "1.1.2", 1415 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 1416 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" 1417 | }, 1418 | "node_modules/commist": { 1419 | "version": "1.1.0", 1420 | "resolved": "https://registry.npmjs.org/commist/-/commist-1.1.0.tgz", 1421 | "integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==", 1422 | "dependencies": { 1423 | "leven": "^2.1.0", 1424 | "minimist": "^1.1.0" 1425 | } 1426 | }, 1427 | "node_modules/concat-map": { 1428 | "version": "0.0.1", 1429 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1430 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 1431 | }, 1432 | "node_modules/concat-stream": { 1433 | "version": "2.0.0", 1434 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", 1435 | "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", 1436 | "engines": [ 1437 | "node >= 6.0" 1438 | ], 1439 | "dependencies": { 1440 | "buffer-from": "^1.0.0", 1441 | "inherits": "^2.0.3", 1442 | "readable-stream": "^3.0.2", 1443 | "typedarray": "^0.0.6" 1444 | } 1445 | }, 1446 | "node_modules/core-util-is": { 1447 | "version": "1.0.3", 1448 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 1449 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", 1450 | "license": "MIT" 1451 | }, 1452 | "node_modules/crypto-js": { 1453 | "version": "4.2.0", 1454 | "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", 1455 | "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" 1456 | }, 1457 | "node_modules/debug": { 1458 | "version": "4.3.4", 1459 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 1460 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 1461 | "dependencies": { 1462 | "ms": "2.1.2" 1463 | }, 1464 | "engines": { 1465 | "node": ">=6.0" 1466 | }, 1467 | "peerDependenciesMeta": { 1468 | "supports-color": { 1469 | "optional": true 1470 | } 1471 | } 1472 | }, 1473 | "node_modules/duplexify": { 1474 | "version": "4.1.2", 1475 | "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", 1476 | "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", 1477 | "dependencies": { 1478 | "end-of-stream": "^1.4.1", 1479 | "inherits": "^2.0.3", 1480 | "readable-stream": "^3.1.1", 1481 | "stream-shift": "^1.0.0" 1482 | } 1483 | }, 1484 | "node_modules/end-of-stream": { 1485 | "version": "1.4.4", 1486 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 1487 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 1488 | "dependencies": { 1489 | "once": "^1.4.0" 1490 | } 1491 | }, 1492 | "node_modules/fast-xml-parser": { 1493 | "version": "4.4.1", 1494 | "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", 1495 | "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", 1496 | "funding": [ 1497 | { 1498 | "type": "github", 1499 | "url": "https://github.com/sponsors/NaturalIntelligence" 1500 | }, 1501 | { 1502 | "type": "paypal", 1503 | "url": "https://paypal.me/naturalintelligence" 1504 | } 1505 | ], 1506 | "license": "MIT", 1507 | "dependencies": { 1508 | "strnum": "^1.0.5" 1509 | }, 1510 | "bin": { 1511 | "fxparser": "src/cli/cli.js" 1512 | } 1513 | }, 1514 | "node_modules/fs.realpath": { 1515 | "version": "1.0.0", 1516 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1517 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 1518 | }, 1519 | "node_modules/glob": { 1520 | "version": "7.2.3", 1521 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 1522 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 1523 | "dependencies": { 1524 | "fs.realpath": "^1.0.0", 1525 | "inflight": "^1.0.4", 1526 | "inherits": "2", 1527 | "minimatch": "^3.1.1", 1528 | "once": "^1.3.0", 1529 | "path-is-absolute": "^1.0.0" 1530 | }, 1531 | "engines": { 1532 | "node": "*" 1533 | }, 1534 | "funding": { 1535 | "url": "https://github.com/sponsors/isaacs" 1536 | } 1537 | }, 1538 | "node_modules/help-me": { 1539 | "version": "3.0.0", 1540 | "resolved": "https://registry.npmjs.org/help-me/-/help-me-3.0.0.tgz", 1541 | "integrity": "sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==", 1542 | "dependencies": { 1543 | "glob": "^7.1.6", 1544 | "readable-stream": "^3.6.0" 1545 | } 1546 | }, 1547 | "node_modules/ieee754": { 1548 | "version": "1.2.1", 1549 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 1550 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 1551 | "funding": [ 1552 | { 1553 | "type": "github", 1554 | "url": "https://github.com/sponsors/feross" 1555 | }, 1556 | { 1557 | "type": "patreon", 1558 | "url": "https://www.patreon.com/feross" 1559 | }, 1560 | { 1561 | "type": "consulting", 1562 | "url": "https://feross.org/support" 1563 | } 1564 | ] 1565 | }, 1566 | "node_modules/inflight": { 1567 | "version": "1.0.6", 1568 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1569 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 1570 | "dependencies": { 1571 | "once": "^1.3.0", 1572 | "wrappy": "1" 1573 | } 1574 | }, 1575 | "node_modules/inherits": { 1576 | "version": "2.0.4", 1577 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1578 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 1579 | }, 1580 | "node_modules/isarray": { 1581 | "version": "1.0.0", 1582 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 1583 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", 1584 | "license": "MIT" 1585 | }, 1586 | "node_modules/isomorphic-ws": { 1587 | "version": "4.0.1", 1588 | "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", 1589 | "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", 1590 | "license": "MIT", 1591 | "peerDependencies": { 1592 | "ws": "*" 1593 | } 1594 | }, 1595 | "node_modules/leven": { 1596 | "version": "2.1.0", 1597 | "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", 1598 | "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", 1599 | "engines": { 1600 | "node": ">=0.10.0" 1601 | } 1602 | }, 1603 | "node_modules/minimatch": { 1604 | "version": "3.1.2", 1605 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1606 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1607 | "dependencies": { 1608 | "brace-expansion": "^1.1.7" 1609 | }, 1610 | "engines": { 1611 | "node": "*" 1612 | } 1613 | }, 1614 | "node_modules/minimist": { 1615 | "version": "1.2.6", 1616 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", 1617 | "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" 1618 | }, 1619 | "node_modules/mqtt": { 1620 | "version": "4.2.8", 1621 | "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.2.8.tgz", 1622 | "integrity": "sha512-DJYjlXODVXtSDecN8jnNzi6ItX3+ufGsEs9OB3YV24HtkRrh7kpx8L5M1LuyF0KzaiGtWr2PzDcMGAY60KGOSA==", 1623 | "dependencies": { 1624 | "commist": "^1.0.0", 1625 | "concat-stream": "^2.0.0", 1626 | "debug": "^4.1.1", 1627 | "duplexify": "^4.1.1", 1628 | "help-me": "^3.0.0", 1629 | "inherits": "^2.0.3", 1630 | "minimist": "^1.2.5", 1631 | "mqtt-packet": "^6.8.0", 1632 | "pump": "^3.0.0", 1633 | "readable-stream": "^3.6.0", 1634 | "reinterval": "^1.1.0", 1635 | "split2": "^3.1.0", 1636 | "ws": "^7.5.0", 1637 | "xtend": "^4.0.2" 1638 | }, 1639 | "bin": { 1640 | "mqtt": "bin/mqtt.js", 1641 | "mqtt_pub": "bin/pub.js", 1642 | "mqtt_sub": "bin/sub.js" 1643 | }, 1644 | "engines": { 1645 | "node": ">=10.0.0" 1646 | } 1647 | }, 1648 | "node_modules/mqtt-packet": { 1649 | "version": "6.10.0", 1650 | "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-6.10.0.tgz", 1651 | "integrity": "sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==", 1652 | "dependencies": { 1653 | "bl": "^4.0.2", 1654 | "debug": "^4.1.1", 1655 | "process-nextick-args": "^2.0.1" 1656 | } 1657 | }, 1658 | "node_modules/ms": { 1659 | "version": "2.1.2", 1660 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1661 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1662 | }, 1663 | "node_modules/once": { 1664 | "version": "1.4.0", 1665 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1666 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1667 | "dependencies": { 1668 | "wrappy": "1" 1669 | } 1670 | }, 1671 | "node_modules/path-is-absolute": { 1672 | "version": "1.0.1", 1673 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1674 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 1675 | "engines": { 1676 | "node": ">=0.10.0" 1677 | } 1678 | }, 1679 | "node_modules/process-nextick-args": { 1680 | "version": "2.0.1", 1681 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 1682 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 1683 | }, 1684 | "node_modules/pump": { 1685 | "version": "3.0.0", 1686 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 1687 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 1688 | "dependencies": { 1689 | "end-of-stream": "^1.1.0", 1690 | "once": "^1.3.1" 1691 | } 1692 | }, 1693 | "node_modules/readable-stream": { 1694 | "version": "3.6.2", 1695 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 1696 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 1697 | "dependencies": { 1698 | "inherits": "^2.0.3", 1699 | "string_decoder": "^1.1.1", 1700 | "util-deprecate": "^1.0.1" 1701 | }, 1702 | "engines": { 1703 | "node": ">= 6" 1704 | } 1705 | }, 1706 | "node_modules/reinterval": { 1707 | "version": "1.1.0", 1708 | "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", 1709 | "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==" 1710 | }, 1711 | "node_modules/safe-buffer": { 1712 | "version": "5.2.1", 1713 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1714 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1715 | "funding": [ 1716 | { 1717 | "type": "github", 1718 | "url": "https://github.com/sponsors/feross" 1719 | }, 1720 | { 1721 | "type": "patreon", 1722 | "url": "https://www.patreon.com/feross" 1723 | }, 1724 | { 1725 | "type": "consulting", 1726 | "url": "https://feross.org/support" 1727 | } 1728 | ] 1729 | }, 1730 | "node_modules/split2": { 1731 | "version": "3.2.2", 1732 | "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", 1733 | "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", 1734 | "dependencies": { 1735 | "readable-stream": "^3.0.0" 1736 | } 1737 | }, 1738 | "node_modules/stream-shift": { 1739 | "version": "1.0.2", 1740 | "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.2.tgz", 1741 | "integrity": "sha512-rV4Bovi9xx0BFzOb/X0B2GqoIjvqPCttZdu0Wgtx2Dxkj7ETyWl9gmqJ4EutWRLvtZWm8dxE+InQZX1IryZn/w==" 1742 | }, 1743 | "node_modules/string_decoder": { 1744 | "version": "1.3.0", 1745 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 1746 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1747 | "dependencies": { 1748 | "safe-buffer": "~5.2.0" 1749 | } 1750 | }, 1751 | "node_modules/strnum": { 1752 | "version": "1.0.5", 1753 | "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", 1754 | "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", 1755 | "license": "MIT" 1756 | }, 1757 | "node_modules/tslib": { 1758 | "version": "2.8.1", 1759 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 1760 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 1761 | "license": "0BSD" 1762 | }, 1763 | "node_modules/typedarray": { 1764 | "version": "0.0.6", 1765 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 1766 | "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" 1767 | }, 1768 | "node_modules/undici-types": { 1769 | "version": "6.20.0", 1770 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", 1771 | "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", 1772 | "license": "MIT" 1773 | }, 1774 | "node_modules/util-deprecate": { 1775 | "version": "1.0.2", 1776 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1777 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 1778 | }, 1779 | "node_modules/uuid": { 1780 | "version": "9.0.1", 1781 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", 1782 | "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", 1783 | "funding": [ 1784 | "https://github.com/sponsors/broofa", 1785 | "https://github.com/sponsors/ctavan" 1786 | ], 1787 | "license": "MIT", 1788 | "bin": { 1789 | "uuid": "dist/bin/uuid" 1790 | } 1791 | }, 1792 | "node_modules/wrappy": { 1793 | "version": "1.0.2", 1794 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1795 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 1796 | }, 1797 | "node_modules/ws": { 1798 | "version": "7.5.10", 1799 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", 1800 | "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", 1801 | "license": "MIT", 1802 | "engines": { 1803 | "node": ">=8.3.0" 1804 | }, 1805 | "peerDependencies": { 1806 | "bufferutil": "^4.0.1", 1807 | "utf-8-validate": "^5.0.2" 1808 | }, 1809 | "peerDependenciesMeta": { 1810 | "bufferutil": { 1811 | "optional": true 1812 | }, 1813 | "utf-8-validate": { 1814 | "optional": true 1815 | } 1816 | } 1817 | }, 1818 | "node_modules/xtend": { 1819 | "version": "4.0.2", 1820 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 1821 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 1822 | "engines": { 1823 | "node": ">=0.4" 1824 | } 1825 | } 1826 | } 1827 | } 1828 | -------------------------------------------------------------------------------- /sensor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-iot-core-appsync-realtime-dashboard-sensor", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "author": "David Moser ", 10 | "license": "MIT-0", 11 | "dependencies": { 12 | "@aws-sdk/client-iot": "^3.708.0", 13 | "argparse": "^2.0.1", 14 | "aws-iot-device-sdk": "^2.2.15" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sensor/policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": "iot:Connect", 7 | "Resource": "*" 8 | }, 9 | { 10 | "Effect": "Allow", 11 | "Action": "iot:Publish", 12 | "Resource": "*" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /sensor/sensors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "SF Bay - East", 4 | "thingTypeName": "WATER_QUALITY_SENSOR", 5 | "manufacturer": "Xylem", 6 | "model": "WQ201", 7 | "firmware": "1.30", 8 | "frequency": 3000, 9 | "geo": { 10 | "latitude": 37.692625, 11 | "longitude": -122.200682 12 | }, 13 | "settings": { 14 | "host": "", 15 | "keyPath": "", 16 | "certPath": "", 17 | "caPath": "", 18 | "certificateArn": "", 19 | "clientId": "sensor-sf-east" 20 | } 21 | }, 22 | { 23 | "name": "SF Bay - Southeast", 24 | "thingTypeName": "WATER_QUALITY_SENSOR", 25 | "manufacturer": "Xylem", 26 | "model": "WQ201", 27 | "firmware": "1.30", 28 | "frequency": 3200, 29 | "geo": { 30 | "latitude": 37.612215, 31 | "longitude": -122.162933 32 | }, 33 | "settings": { 34 | "host": "", 35 | "keyPath": "", 36 | "certPath": "", 37 | "caPath": "", 38 | "certificateArn": "", 39 | "clientId": "sensor-sf-southeast" 40 | } 41 | }, 42 | { 43 | "name": "SF Bay - Southwest", 44 | "thingTypeName": "WATER_QUALITY_SENSOR", 45 | "manufacturer": "Xylem", 46 | "model": "WQ201", 47 | "firmware": "1.30", 48 | "frequency": 3400, 49 | "geo": { 50 | "latitude": 37.602464, 51 | "longitude": -122.338036 52 | }, 53 | "settings": { 54 | "host": "", 55 | "keyPath": "", 56 | "certPath": "", 57 | "caPath": "", 58 | "certificateArn": "", 59 | "clientId": "sensor-sf-southwest" 60 | } 61 | }, 62 | { 63 | "name": "SF Bay - West", 64 | "thingTypeName": "WATER_QUALITY_SENSOR", 65 | "manufacturer": "Xylem", 66 | "model": "WQ201", 67 | "firmware": "1.30", 68 | "frequency": 3600, 69 | "geo": { 70 | "latitude": 37.66228, 71 | "longitude": -122.36549 72 | }, 73 | "settings": { 74 | "host": "", 75 | "keyPath": "", 76 | "certPath": "", 77 | "caPath": "", 78 | "certificateArn": "", 79 | "clientId": "sensor-sf-west" 80 | } 81 | }, 82 | { 83 | "name": "SF Bay - Northwest", 84 | "thingTypeName": "WATER_QUALITY_SENSOR", 85 | "manufacturer": "Xylem", 86 | "model": "WQ201", 87 | "firmware": "1.30", 88 | "frequency": 3800, 89 | "geo": { 90 | "latitude": 37.751372, 91 | "longitude": -122.359999 92 | }, 93 | "settings": { 94 | "host": "", 95 | "keyPath": "", 96 | "certPath": "", 97 | "caPath": "", 98 | "certificateArn": "", 99 | "clientId": "sensor-sf-northwest" 100 | } 101 | }, 102 | { 103 | "name": "SF Bay - North", 104 | "thingTypeName": "WATER_QUALITY_SENSOR", 105 | "manufacturer": "Xylem", 106 | "model": "WQ201", 107 | "firmware": "1.30", 108 | "frequency": 4000, 109 | "geo": { 110 | "latitude": 37.800307, 111 | "longitude": -122.354788 112 | }, 113 | "settings": { 114 | "host": "", 115 | "keyPath": "", 116 | "certPath": "", 117 | "caPath": "", 118 | "certificateArn": "", 119 | "clientId": "sensor-sf-north" 120 | } 121 | } 122 | ] -------------------------------------------------------------------------------- /web/.eslintignore: -------------------------------------------------------------------------------- 1 | amplify-codegen-temp/models/models -------------------------------------------------------------------------------- /web/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react-refresh/only-export-components': [ 16 | 'warn', 17 | { allowConstantExport: true }, 18 | ], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | #amplify-do-not-edit-begin 27 | amplify/\#current-cloud-backend 28 | amplify/.config/local-* 29 | amplify/logs 30 | amplify/mock-data 31 | amplify/mock-api-resources 32 | amplify/backend/amplify-meta.json 33 | amplify/backend/.temp 34 | build/ 35 | dist/ 36 | node_modules/ 37 | aws-exports.js 38 | awsconfiguration.json 39 | amplifyconfiguration.json 40 | amplifyconfiguration.dart 41 | amplify-build-config.json 42 | amplify-gradle-config.json 43 | amplifytools.xcconfig 44 | .secret-* 45 | **.sample 46 | #amplify-do-not-edit-end 47 | 48 | team-provider-info.json 49 | 50 | # amplify 51 | .amplify 52 | amplify_outputs* 53 | amplifyconfiguration* 54 | -------------------------------------------------------------------------------- /web/.graphqlconfig.yml: -------------------------------------------------------------------------------- 1 | projects: 2 | sensorsapi: 3 | schemaPath: src/graphql/schema.json 4 | includes: 5 | - src/graphql/**/*.js 6 | excludes: 7 | - ./amplify/** 8 | extensions: 9 | amplify: 10 | codeGenTarget: javascript 11 | generatedFileName: '' 12 | docsFilePath: src/graphql 13 | extensions: 14 | amplify: 15 | version: 3 16 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /web/amplify/auth/resource.ts: -------------------------------------------------------------------------------- 1 | import { defineAuth } from '@aws-amplify/backend'; 2 | 3 | /** 4 | * Define and configure your auth resource 5 | * @see https://docs.amplify.aws/gen2/build-a-backend/auth 6 | */ 7 | export const auth = defineAuth({ 8 | loginWith: { 9 | email: true, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /web/amplify/backend.ts: -------------------------------------------------------------------------------- 1 | import { defineBackend } from "@aws-amplify/backend"; 2 | import { Policy, PolicyStatement, ServicePrincipal } from "aws-cdk-lib/aws-iam"; 3 | import { CfnMap } from "aws-cdk-lib/aws-location"; 4 | import { CfnTopicRule } from "aws-cdk-lib/aws-iot"; 5 | import { auth } from "./auth/resource"; 6 | import { data } from "./data/resource"; 7 | import { listSensors } from "./functions/list-sensors/resource"; 8 | import { sendSensorValue } from "./functions/send-sensor-value/resource"; 9 | 10 | const backend = defineBackend({ 11 | auth, 12 | data, 13 | listSensors, 14 | sendSensorValue, 15 | }); 16 | 17 | // disable unauthenticated access 18 | const { cfnIdentityPool } = backend.auth.resources.cfnResources; 19 | cfnIdentityPool.allowUnauthenticatedIdentities = false; 20 | 21 | // Mapping Resources 22 | const geoStack = backend.createStack("geo-stack"); 23 | 24 | // create a map 25 | const map = new CfnMap(geoStack, "Map", { 26 | mapName: "SensorMap", 27 | description: "Sensor Map", 28 | configuration: { 29 | style: "VectorEsriDarkGrayCanvas", 30 | }, 31 | pricingPlan: "RequestBasedUsage", 32 | tags: [ 33 | { 34 | key: "name", 35 | value: "SensorMap", 36 | }, 37 | ], 38 | }); 39 | 40 | // create an IAM policy to allow interacting with geo resource 41 | const myGeoPolicy = new Policy(geoStack, "GeoPolicy", { 42 | policyName: "myGeoPolicy", 43 | statements: [ 44 | new PolicyStatement({ 45 | actions: [ 46 | "geo:GetMapTile", 47 | "geo:GetMapSprites", 48 | "geo:GetMapGlyphs", 49 | "geo:GetMapStyleDescriptor", 50 | ], 51 | resources: [map.attrArn], 52 | }), 53 | ], 54 | }); 55 | 56 | // apply the policy to the authenticated role 57 | backend.auth.resources.authenticatedUserIamRole.attachInlinePolicy(myGeoPolicy); 58 | 59 | // patch the map resource to the expected output configuration 60 | backend.addOutput({ 61 | geo: { 62 | aws_region: geoStack.region, 63 | maps: { 64 | items: { 65 | [map.mapName]: { 66 | style: "VectorEsriDarkGrayCanvas", 67 | }, 68 | }, 69 | default: map.mapName, 70 | }, 71 | }, 72 | }); 73 | 74 | // IoT Resources 75 | 76 | // grant the list sensors function access to search all IoT devices 77 | const listSensorsLambda = backend.listSensors.resources.lambda; 78 | 79 | listSensorsLambda.addToRolePolicy( 80 | new PolicyStatement({ 81 | actions: ["iot:SearchIndex"], 82 | resources: ["arn:aws:iot:*:*:*"], 83 | }) 84 | ); 85 | 86 | const iotStack = backend.createStack("iot-stack"); 87 | 88 | const sendSensorValueLambda = backend.sendSensorValue.resources.lambda; 89 | 90 | // create a rule to process messages from the sensors - send them to the lambda function 91 | const rule = new CfnTopicRule(iotStack, "SendSensorValueRule", { 92 | topicRulePayload: { 93 | sql: "select * as data, topic(4) as sensorId from 'dt/bay-health/SF/+/sensor-value'", 94 | actions: [ 95 | { 96 | lambda: { 97 | functionArn: sendSensorValueLambda.functionArn, 98 | }, 99 | }, 100 | ], 101 | }, 102 | }); 103 | 104 | // allow IoT rule to invoke the lambda function 105 | sendSensorValueLambda.addPermission("AllowIoTInvoke", { 106 | principal: new ServicePrincipal("iot.amazonaws.com"), 107 | sourceArn: `arn:aws:iot:${iotStack.region}:${iotStack.account}:rule/SendSensorValueRule*`, 108 | }); 109 | -------------------------------------------------------------------------------- /web/amplify/data/resource.ts: -------------------------------------------------------------------------------- 1 | import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; 2 | import { listSensors } from "../functions/list-sensors/resource"; 3 | import { sendSensorValue } from "../functions/send-sensor-value/resource"; 4 | 5 | const schema = a 6 | .schema({ 7 | SensorValue: a.model({ 8 | id: a.id(), 9 | sensorId: a.string().required(), 10 | pH: a.float().required(), 11 | temperature: a.float().required(), 12 | salinity: a.float().required(), 13 | disolvedO2: a.float().required(), 14 | status: a.integer().required(), 15 | timestamp: a.timestamp().required(), 16 | }), 17 | Geo: a.customType({ 18 | latitude: a.float(), 19 | longitude: a.float(), 20 | }), 21 | Sensor: a.customType({ 22 | sensorId: a.string().required(), 23 | name: a.string().required(), 24 | geo: a.ref("Geo").required(), 25 | enabled: a.boolean().required(), 26 | status: a.integer().required(), 27 | }), 28 | listSensors: a 29 | .query() 30 | .returns(a.ref("Sensor").array()) 31 | .handler(a.handler.function(listSensors)), 32 | }) 33 | .authorization((allow) => [ 34 | allow.authenticated(), 35 | allow.resource(sendSensorValue), 36 | ]); 37 | 38 | export type Schema = ClientSchema; 39 | 40 | export const data = defineData({ 41 | schema, 42 | authorizationModes: { 43 | defaultAuthorizationMode: "userPool", 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /web/amplify/functions/list-sensors/handler.ts: -------------------------------------------------------------------------------- 1 | import type { Handler } from "aws-lambda"; 2 | import { IoTClient, SearchIndexCommand } from "@aws-sdk/client-iot"; 3 | 4 | const iotClient = new IoTClient(); 5 | 6 | export const handler: Handler = async (event) => { 7 | console.log("event", event); 8 | 9 | //query all sensors that have reported a shadow and of type water quality sensor 10 | //you must have fleet indexing enabled in IoT Core with REGISTRY_AND_SHADOW indexed 11 | 12 | var params = { 13 | queryString: 14 | "shadow.reported.name:* AND thingTypeName:WATER_QUALITY_SENSOR", 15 | }; 16 | 17 | var command = new SearchIndexCommand(params); 18 | 19 | var result = await iotClient.send(command); 20 | 21 | // return array of sensors 22 | return result.things?.map((thing) => { 23 | const shadow = JSON.parse(thing?.shadow!); 24 | return { 25 | sensorId: thing.thingName, 26 | name: shadow.reported.name || "", 27 | enabled: shadow.reported.enabled || false, 28 | geo: { 29 | latitude: shadow.reported.geo?.latitude || 0, 30 | longitude: shadow.reported.geo?.longitude || 0, 31 | }, 32 | status: shadow.reported.status || 0, 33 | }; 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /web/amplify/functions/list-sensors/resource.ts: -------------------------------------------------------------------------------- 1 | import { defineFunction } from "@aws-amplify/backend"; 2 | 3 | export const listSensors = defineFunction({ 4 | entry: "./handler.ts", 5 | name: "listSensors", 6 | }); 7 | -------------------------------------------------------------------------------- /web/amplify/functions/send-sensor-value/handler.ts: -------------------------------------------------------------------------------- 1 | import type { Handler } from "aws-lambda"; 2 | import crypto from "@aws-crypto/sha256-js"; 3 | import { defaultProvider } from "@aws-sdk/credential-provider-node"; 4 | import { SignatureV4 } from "@aws-sdk/signature-v4"; 5 | import { HttpRequest } from "@aws-sdk/protocol-http"; 6 | import { default as fetch, Request } from "node-fetch"; 7 | 8 | const GRAPHQL_ENDPOINT = process.env.AMPLIFY_DATA_GRAPHQL_ENDPOINT || ""; 9 | const AWS_REGION = process.env.AWS_REGION || ""; 10 | const { Sha256 } = crypto; 11 | 12 | export const handler: Handler = async (event) => { 13 | console.log("event", event); 14 | 15 | //set a random sensor status 1-3 16 | let status = Math.floor(Math.random() * 3) + 1; 17 | 18 | const query = /* GraphQL */ ` 19 | mutation CreateSensorValue($input: CreateSensorValueInput!) { 20 | createSensorValue(input: $input) { 21 | id 22 | sensorId 23 | pH 24 | temperature 25 | salinity 26 | disolvedO2 27 | status 28 | timestamp 29 | createdAt 30 | updatedAt 31 | } 32 | } 33 | `; 34 | 35 | const variables = { 36 | input: { 37 | sensorId: event.sensorId, 38 | pH: event.data.pH, 39 | temperature: event.data.temperature, 40 | salinity: event.data.salinity, 41 | disolvedO2: event.data.disolvedO2, 42 | status: status, 43 | timestamp: event.data.timestamp, 44 | }, 45 | }; 46 | 47 | const endpoint = new URL(GRAPHQL_ENDPOINT); 48 | 49 | const signer = new SignatureV4({ 50 | credentials: defaultProvider(), 51 | region: AWS_REGION, 52 | service: "appsync", 53 | sha256: Sha256, 54 | }); 55 | 56 | const requestToBeSigned = new HttpRequest({ 57 | method: "POST", 58 | headers: { 59 | "Content-Type": "application/json", 60 | host: endpoint.host, 61 | }, 62 | hostname: endpoint.host, 63 | body: JSON.stringify({ query, variables }), 64 | path: endpoint.pathname, 65 | }); 66 | 67 | const signed = await signer.sign(requestToBeSigned); 68 | const request = new Request(endpoint, signed); 69 | 70 | let statusCode = 200; 71 | let body; 72 | let response; 73 | 74 | try { 75 | response = await fetch(request); 76 | body = await response.json(); 77 | console.log(body); 78 | } catch (error: any) { 79 | console.log(error); 80 | statusCode = 500; 81 | body = { 82 | errors: [ 83 | { 84 | message: error.message, 85 | }, 86 | ], 87 | }; 88 | } 89 | 90 | return { 91 | statusCode, 92 | body: JSON.stringify(body), 93 | }; 94 | }; 95 | -------------------------------------------------------------------------------- /web/amplify/functions/send-sensor-value/resource.ts: -------------------------------------------------------------------------------- 1 | import { defineFunction } from "@aws-amplify/backend"; 2 | 3 | export const sendSensorValue = defineFunction({ 4 | entry: "./handler.ts", 5 | name: "sendSensorValue", 6 | resourceGroupName: "data", 7 | }); 8 | -------------------------------------------------------------------------------- /web/amplify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } -------------------------------------------------------------------------------- /web/amplify/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "es2022", 5 | "moduleResolution": "bundler", 6 | "resolveJsonModule": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "skipLibCheck": true, 11 | "paths": { 12 | "$amplify/*": [ 13 | "../.amplify/generated/*" 14 | ] 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Bay Health 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@aws-amplify/ui-react": "^6.10.0", 14 | "@aws-sdk/client-iot": "^3.782.0", 15 | "@aws-sdk/protocol-http": "^3.374.0", 16 | "@aws-sdk/signature-v4": "^3.374.0", 17 | "aws-amplify": "^6.14.1", 18 | "maplibre-gl": "^2.1.2", 19 | "maplibre-gl-js-amplify": "^4.0.2", 20 | "react": "^18.2.0", 21 | "react-dom": "^18.2.0" 22 | }, 23 | "devDependencies": { 24 | "@aws-amplify/backend": "^1.15.0", 25 | "@aws-amplify/backend-cli": "^1.5.0", 26 | "@types/react": "^18.2.48", 27 | "@types/react-dom": "^18.2.18", 28 | "@vitejs/plugin-react": "^4.3.4", 29 | "aws-cdk": "^2.1007.0", 30 | "aws-cdk-lib": "^2.188.0", 31 | "constructs": "^10.4.2", 32 | "esbuild": "^0.25.2", 33 | "eslint": "^8.56.0", 34 | "eslint-plugin-react": "^7.37.5", 35 | "eslint-plugin-react-hooks": "^4.6.0", 36 | "eslint-plugin-react-refresh": "^0.4.5", 37 | "tsx": "^4.19.3", 38 | "typescript": "^5.8.3", 39 | "vite": "^5.0.13" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-appsync-iot-core-realtime-dashboard/0c9dea2908e162e7e26302ccfa6c45504766faa5/web/public/favicon.ico -------------------------------------------------------------------------------- /web/src/App.jsx: -------------------------------------------------------------------------------- 1 | import "@aws-amplify/ui-react/styles.css"; 2 | import { withAuthenticator } from "@aws-amplify/ui-react"; 3 | import { Amplify } from "aws-amplify"; 4 | 5 | import MapPage from "./MapPage"; 6 | 7 | import awsExports from "../amplify_outputs.json"; 8 | 9 | Amplify.configure(awsExports); 10 | 11 | const App = () => { 12 | return ; 13 | }; 14 | 15 | export default withAuthenticator(App); 16 | -------------------------------------------------------------------------------- /web/src/MapPage.css: -------------------------------------------------------------------------------- 1 | body { margin: 0; padding: 0; } 2 | 3 | #container { 4 | height: 100%; 5 | } 6 | 7 | #map { 8 | width: 100%; 9 | height: 100%; 10 | } 11 | 12 | #banner { 13 | position: absolute; 14 | width: 100%; 15 | font: 2em sans-serif; 16 | padding: 12px; 17 | color: #ffffff; 18 | opacity: 75%; 19 | background-color: #1D1C1B; 20 | z-index: 200; 21 | } 22 | 23 | .sensor { 24 | border-radius: 50%; 25 | height: 2em; 26 | width: 2em; 27 | cursor: pointer; 28 | } 29 | 30 | .fullscreen-map { 31 | position: absolute; 32 | top: 0; 33 | bottom: 0; 34 | width: 100%; 35 | height: 100%; 36 | z-index: 100; 37 | } 38 | -------------------------------------------------------------------------------- /web/src/MapPage.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { createMap } from "maplibre-gl-js-amplify"; 3 | import { Marker } from "maplibre-gl"; 4 | import { generateClient } from "aws-amplify/api"; 5 | import { listSensors } from "./graphql/queries"; 6 | import { onCreateSensorValue } from "./graphql/subscriptions"; 7 | 8 | import "maplibre-gl/dist/maplibre-gl.css"; 9 | import "./MapPage.css"; 10 | 11 | const MapPage = () => { 12 | useEffect(() => { 13 | var map; 14 | 15 | const CreateSensorMarker = (sensor) => { 16 | var marker = document.createElement("div"); 17 | marker.id = "sensor-image-" + sensor.sensorId; 18 | marker.className = "sensor"; 19 | 20 | let sensorColor = "white"; 21 | marker.style.backgroundColor = sensorColor; 22 | marker.style.border = "border: 0.1em solid " + sensorColor + ";"; 23 | 24 | return marker; 25 | }; 26 | 27 | // call api to get list of sensors and display them as markers on the map 28 | async function DisplaySensors(map) { 29 | const client = generateClient(); 30 | const response = await client.graphql({ query: listSensors }); 31 | console.log("API Response:", response); 32 | 33 | if (response && response.data) { 34 | console.log("sensors retrived"); 35 | 36 | response.data.listSensors.forEach((sensor) => { 37 | var marker = CreateSensorMarker(sensor); 38 | console.log(sensor); 39 | new Marker({ element: marker }) 40 | .setLngLat([sensor.geo.longitude, sensor.geo.latitude]) 41 | .addTo(map); 42 | }); 43 | } 44 | } 45 | 46 | // configure and display the map 47 | async function initializeMap() { 48 | try { 49 | map = await createMap({ 50 | container: "map", 51 | center: [-122.2, 37.705], 52 | zoom: 10, 53 | maxZoom: 10, 54 | }); 55 | 56 | map.repaint = true; 57 | 58 | console.log("Map Rendered"); 59 | 60 | await DisplaySensors(map); 61 | } catch (error) { 62 | console.log("error fetching sensors", error); 63 | } 64 | } 65 | 66 | initializeMap(); 67 | }, []); 68 | 69 | // start subscription for sensor status changes and update sensor marker color 70 | useEffect(() => { 71 | const UpdateSensorMarker = (sensorId, status) => { 72 | var marker = document.getElementById("sensor-image-" + sensorId); 73 | 74 | if (marker) { 75 | let sensorColor = ""; 76 | 77 | if (status === 1) { 78 | sensorColor = "green"; 79 | } else if (status === 2) { 80 | sensorColor = "yellow"; 81 | } else if (status === 3) { 82 | sensorColor = "red"; 83 | } else { 84 | sensorColor = "white"; 85 | } 86 | 87 | marker.style.backgroundColor = sensorColor; 88 | marker.style.border = `border: 0.1em solid ${sensorColor};`; 89 | 90 | console.log(sensorId + " updated"); 91 | } 92 | }; 93 | 94 | const client = generateClient(); 95 | const createSub = client.graphql({ query: onCreateSensorValue }).subscribe({ 96 | next: ({ data }) => { 97 | UpdateSensorMarker( 98 | data.onCreateSensorValue.sensorId, 99 | data.onCreateSensorValue.status 100 | ); 101 | }, 102 | error: (error) => console.warn(error), 103 | }); 104 | }, []); 105 | 106 | return ( 107 |
108 | 109 |
110 |
111 | ); 112 | }; 113 | 114 | export default MapPage; 115 | -------------------------------------------------------------------------------- /web/src/graphql/mutations.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // this is an auto generated file. This will be overwritten 3 | 4 | export const createSensorValue = /* GraphQL */ ` 5 | mutation CreateSensorValue( 6 | $input: CreateSensorValueInput! 7 | $condition: ModelSensorValueConditionInput 8 | ) { 9 | createSensorValue(input: $input, condition: $condition) { 10 | id 11 | sensorId 12 | pH 13 | temperature 14 | salinity 15 | disolvedO2 16 | status 17 | timestamp 18 | createdAt 19 | updatedAt 20 | __typename 21 | } 22 | } 23 | `; 24 | export const updateSensorValue = /* GraphQL */ ` 25 | mutation UpdateSensorValue( 26 | $input: UpdateSensorValueInput! 27 | $condition: ModelSensorValueConditionInput 28 | ) { 29 | updateSensorValue(input: $input, condition: $condition) { 30 | id 31 | sensorId 32 | pH 33 | temperature 34 | salinity 35 | disolvedO2 36 | status 37 | timestamp 38 | createdAt 39 | updatedAt 40 | __typename 41 | } 42 | } 43 | `; 44 | export const deleteSensorValue = /* GraphQL */ ` 45 | mutation DeleteSensorValue( 46 | $input: DeleteSensorValueInput! 47 | $condition: ModelSensorValueConditionInput 48 | ) { 49 | deleteSensorValue(input: $input, condition: $condition) { 50 | id 51 | sensorId 52 | pH 53 | temperature 54 | salinity 55 | disolvedO2 56 | status 57 | timestamp 58 | createdAt 59 | updatedAt 60 | __typename 61 | } 62 | } 63 | `; 64 | -------------------------------------------------------------------------------- /web/src/graphql/queries.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // this is an auto generated file. This will be overwritten 3 | 4 | export const listSensors = /* GraphQL */ ` 5 | query ListSensors { 6 | listSensors { 7 | sensorId 8 | name 9 | enabled 10 | geo { 11 | latitude 12 | longitude 13 | __typename 14 | } 15 | status 16 | __typename 17 | } 18 | } 19 | `; 20 | export const getSensorValue = /* GraphQL */ ` 21 | query GetSensorValue($id: ID!) { 22 | getSensorValue(id: $id) { 23 | id 24 | sensorId 25 | pH 26 | temperature 27 | salinity 28 | disolvedO2 29 | status 30 | timestamp 31 | createdAt 32 | updatedAt 33 | __typename 34 | } 35 | } 36 | `; 37 | export const listSensorValues = /* GraphQL */ ` 38 | query ListSensorValues( 39 | $filter: ModelSensorValueFilterInput 40 | $limit: Int 41 | $nextToken: String 42 | ) { 43 | listSensorValues(filter: $filter, limit: $limit, nextToken: $nextToken) { 44 | items { 45 | id 46 | sensorId 47 | pH 48 | temperature 49 | salinity 50 | disolvedO2 51 | status 52 | timestamp 53 | createdAt 54 | updatedAt 55 | __typename 56 | } 57 | nextToken 58 | __typename 59 | } 60 | } 61 | `; 62 | -------------------------------------------------------------------------------- /web/src/graphql/subscriptions.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // this is an auto generated file. This will be overwritten 3 | 4 | export const onCreateSensorValue = /* GraphQL */ ` 5 | subscription OnCreateSensorValue( 6 | $filter: ModelSubscriptionSensorValueFilterInput 7 | ) { 8 | onCreateSensorValue(filter: $filter) { 9 | id 10 | sensorId 11 | pH 12 | temperature 13 | salinity 14 | disolvedO2 15 | status 16 | timestamp 17 | createdAt 18 | updatedAt 19 | __typename 20 | } 21 | } 22 | `; 23 | export const onUpdateSensorValue = /* GraphQL */ ` 24 | subscription OnUpdateSensorValue( 25 | $filter: ModelSubscriptionSensorValueFilterInput 26 | ) { 27 | onUpdateSensorValue(filter: $filter) { 28 | id 29 | sensorId 30 | pH 31 | temperature 32 | salinity 33 | disolvedO2 34 | status 35 | timestamp 36 | createdAt 37 | updatedAt 38 | __typename 39 | } 40 | } 41 | `; 42 | export const onDeleteSensorValue = /* GraphQL */ ` 43 | subscription OnDeleteSensorValue( 44 | $filter: ModelSubscriptionSensorValueFilterInput 45 | ) { 46 | onDeleteSensorValue(filter: $filter) { 47 | id 48 | sensorId 49 | pH 50 | temperature 51 | salinity 52 | disolvedO2 53 | status 54 | timestamp 55 | createdAt 56 | updatedAt 57 | __typename 58 | } 59 | } 60 | `; 61 | -------------------------------------------------------------------------------- /web/src/main.jsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client' 2 | import App from './App.jsx' 3 | 4 | ReactDOM.createRoot(document.getElementById('root')).render( 5 | 6 | ) 7 | -------------------------------------------------------------------------------- /web/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | define:{ 8 | global:{} 9 | } 10 | }) 11 | --------------------------------------------------------------------------------